diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 082931e9ac..58cceae0c7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us improve title: '' -labels: '' +labels: 'ctg-bug' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/bug_report_easy.md b/.github/ISSUE_TEMPLATE/bug_report_easy.md new file mode 100644 index 0000000000..ee17afeec0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report_easy.md @@ -0,0 +1,44 @@ +--- +name: Bug report without tips +about: Template without examples (for experienced QA engineers) +title: '' +labels: 'ctg-bug' +assignees: '' + +--- + +**Description** + + + +**To Reproduce** + +1. Install UnitTestBot plugin built from main in IntelliJ IDEA +2. Open +3. Generate tests for + +**Expected behavior** + + + +**Actual behavior** + + + +**Screenshots, logs** + + + +~~~java + +~~~ + +**Environment** + +IntelliJ IDEA version - +Project - +JDK - + +**Additional context** + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index c5998337e0..0cb037bdd0 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: Feature request about: Suggest an idea for the UTBot project title: '' -labels: '' +labels: 'ctg-enhancement' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/test_request.md b/.github/ISSUE_TEMPLATE/test_request.md index b93ca59e79..60f1ed48d8 100644 --- a/.github/ISSUE_TEMPLATE/test_request.md +++ b/.github/ISSUE_TEMPLATE/test_request.md @@ -1,20 +1,21 @@ --- name: Manual testing checklist about: Checklist of testing process -title: '' -labels: '' +title: 'Manual testing of build#' +labels: 'ctg-qa' assignees: '' - --- **Initial set-up** *Check that the IntelliJ Idea UTBot plugin can be successfully installed* -- [ ] Choose appropriate workflow from the next list (by default, use the latest one) https://github.com/UnitTestBot/UTBotJava/actions/workflows/publish-plugin-and-cli.yml -- [ ] Open IntelliJ IDE +- [ ] Choose appropriate [workflow from the list](https://github.com/UnitTestBot/UTBotJava/actions/workflows/build-and-run-tests.yml?query=branch%3Amain) +- [ ] Download plugin +- [ ] Check downloaded zip-file size < 100 MB +- [ ] Open IntelliJ IDEA - [ ] Remove previously installed UTBot plugin -- [ ] Clone or reuse UTBot project (https://github.com/UnitTestBot/UTBotJava.git) +- [ ] Clone or reuse [UTBot project](https://github.com/UnitTestBot/UTBotJava.git) - [ ] Open the project in the IDE - [ ] Install the downloaded plugin @@ -26,31 +27,36 @@ assignees: '' - [ ] Open the utbot-sample/src/main/java/org/utbot/examples/algorithms/ArraysQuickSort.java file - [ ] Generate tests for the class - [ ] Remove results -- [ ] Generate tests for the methods +- [ ] Generate and Run test for a method +- [ ] Check only expected tests are red (failing on exceptions) +- [ ] Check there are no yellow tests (failing on asserts) - **Manual scenario #2** - [ ] Use default plugin settings - [ ] Open the utbot-sample/src/main/java/org/utbot/examples/mock/CommonMocksExample.java file -- [ ] Generate tests with all available (mocking) options +- [ ] Generate and Run tests with different Mocking options +- [ ] Check only expected tests are red (failing on exceptions) +- [ ] Check there are no yellow tests (failing on asserts) +- [ ] Check generated tests consistency, layout, naming, correct mocking - **Manual scenario #3** -- [ ] Create a new Gradle project -- [ ] Add a simple java file to test -- [ ] Generate a test with a new test root +- [ ] Create a new Gradle project with JDK 17 +- [ ] Add a sample java file to test +- [ ] Generate a test in the existing test root +- [ ] Check generated tests consistency, layout, naming - **Manual scenario #4** -- [ ] Create a new Maven project -- [ ] Add a simple java file to test +- [ ] Create a new Maven project with JDK 8 +- [ ] Add a sample java file to test - [ ] Generate a test with a new test root +- [ ] Check generated tests consistency, layout, naming **Manual scenario #5** -- [ ] Create a new Idea project -- [ ] Add a simple java file to test +- [ ] Create a new IntelliJ project with JDK 11 +- [ ] Add a sample java file to test - [ ] Generate tests for several classes +- [ ] Check generated tests consistency, layout, naming diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..90ca761682 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "gradle" # See documentation for possible values + directories: + - "/utbot-intellij" + - "/utbot-framework" + schedule: + interval: "weekly" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a9313c6e01..afc8194fb8 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,36 +1,61 @@ -# Description +## Labels (hint) -Substitute this text with a concise description of the proposed change. Emphasize, why particular solution was chosen. +Choose the obligatory labels: +- "ctg" (category): _bug-fix_, _enhancement_, _refactoring_, etc. +- "comp" (component): _symbolic-engine_, _fuzzing_, _codegen_, etc. + +Feel free to apply more labels to your PR, e.g.: _lang-java_, _priority-minor_, _spec-performance_ + +## Title (hint) + +Describe what you've changed or added in terms of functionality. + +For example: + +> Add summaries for the nested classes + +> Support test generation for paths with spaces in JavaScript + +> Remove packageName property not defined in Java 8 + +Check that the title contains +* no branch name +* no GitHub nickname +* no copy-pasted issue title + +## Description Fixes # (issue) -## Type of Change +Add more info _if needed_: +* context/purpose for implementing changes +* detailed description of the changes made + +## How to test -Please delete options that are not relevant. +### Automated tests -- Minor bug fix (non-breaking small changes) -- Bug fix (non-breaking change which fixes an issue) -- New feature (non-breaking change which adds functionality) -- Breaking change (fix or feature that would cause existing functionality to not work as expected) +Please specify the _automated tests_ for your code changes: you should either mention the existing tests or add the new ones. -# How Has This Been Tested? +For example: -## Automated Testing +> The proposed changes are verified with tests: +> `utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/FuzzerSmokeTest.kt` -Specify tests that help to verify the change automatically. +### Manual tests -_Example:_ org.utbot.examples.algorithms.BinarySearchTest +If it is impossible to provide the automated tests, please reason why. Usually, it is relevant only for UI- or documentation-related PRs. +If this is your case, share the detailed _manual scenarios_ that help to verify your changes. -## Manual Scenario +## Self-check list -Please, provide several scenarios that you went through to verify that the change worked as expected. +Check off the item if the statement is true. Hint: [x] is a marked item. -# Checklist (remove irrelevant options): +Please do not delete the list or its items. -- [ ] The change followed the style guidelines of the UTBot project -- [ ] Self-review of the code is passed -- [ ] The change contains enough commentaries, particularly in hard-to-understand areas -- [ ] New documentation is provided or existed one is altered -- [ ] No new warnings -- [ ] Tests that prove my change is effective -- [ ] All tests pass locally with my changes +- [ ] I've set the proper **labels** for my PR (at least, for category and component). +- [ ] PR **title** and **description** are clear and intelligible. +- [ ] I've added enough **comments** to my code, particularly in hard-to-understand areas. +- [ ] The functionality I've repaired, changed or added is covered with **automated tests**. +- [ ] **Manual tests** have been provided optionally. +- [ ] The **documentation** for the functionality I've been working on is up-to-date. \ No newline at end of file diff --git a/.github/workflows/build-and-run-tests-from-branch.yml b/.github/workflows/build-and-run-tests-from-branch.yml index e6e8798bac..f93be3be31 100644 --- a/.github/workflows/build-and-run-tests-from-branch.yml +++ b/.github/workflows/build-and-run-tests-from-branch.yml @@ -1,45 +1,369 @@ name: "[M] UTBot Java: build and run tests" +permissions: read-all + on: - workflow_dispatch - + workflow_dispatch: + inputs: + commit_sha: + required: false + type: string + description: "Commit SHA (optional -- otherwise the last commit from the branch will be taken)" + + workflow_call: + inputs: + commit_sha: + required: false + type: string + +env: + REGISTRY: ghcr.io + IMAGE_NAME: utbot_java_cli + DOCKERFILE_PATH: docker/Dockerfile_java_cli + # Environment variable setting gradle options. + # + # When configuring Gradle behavior you can use these methods, + # listed in order of highest to lowest precedence (first one wins): + # - Command-line flags such as --build-cache. + # These have precedence over properties and environment variables. + # - System properties such as systemProp.http.proxyHost=somehost.org + # stored in a gradle.properties file in a root project directory. + # - Gradle properties such as org.gradle.caching=true that are + # typically stored in a gradle.properties file in a project + # directory or in the GRADLE_USER_HOME. + # - Environment variables such as GRADLE_OPTS sourced by the + # environment that executes Gradle. + # + # read more at: https://docs.gradle.org/current/userguide/build_environment.html + # + # example of GRADLE_OPTS: +# GRADLE_OPTS: "-XX:MaxHeapSize=2048m -Dorg.gradle.daemon=false -Dorg.gradle.parallel=false -Dkotlin.compiler.execution.strategy=in-process" + PUSHGATEWAY_HOSTNAME: monitoring.utbot.org + ELK_HOSTNAME: logs.utbot.org + FILEBEAT_DIR: /tmp/filebeat + jobs: - build-and-run-tests: + prepare-matrices: + permissions: read-all + runs-on: ubuntu-latest + # Outputs are used for passing data to dependent jobs. + outputs: + framework-tests-matrix: ${{ steps.set-matrices.outputs.framework-tests-matrix }} + combined-projects-matrix: ${{ steps.set-matrices.outputs.combined-projects-matrix }} + steps: + - name: Print environment variables + run: printenv + + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Check out ${{ github.event.inputs.commit_sha }} commit + if: github.event.inputs.commit_sha != '' + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + git fetch + git checkout ${{ github.event.inputs.commit_sha }} + - id: set-matrices + name: Read and print config from framework-tests-matrix.json and combined-projects-matrix.json + run: | + FRAMEWORK_TESTS=$(echo $(cat .github/workflows/framework-tests-matrix.json)) + COMBINED_PROJECTS=$(echo $(cat .github/workflows/combined-projects-matrix.json)) + echo "framework-tests-matrix=$FRAMEWORK_TESTS" >> $GITHUB_OUTPUT + echo "combined-projects-matrix=$COMBINED_PROJECTS" >> $GITHUB_OUTPUT + echo $FRAMEWORK_TESTS + echo $COMBINED_PROJECTS + + + framework-tests: + permissions: read-all + needs: prepare-matrices + # Using matrices let create multiple jobs runs based on the combinations of the variables from matrices. + # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs + strategy: + # The option forces to execute all jobs even though some of them have failed. + fail-fast: false + matrix: ${{ fromJson(needs.prepare-matrices.outputs.framework-tests-matrix) }} + runs-on: ubuntu-20.04 + container: + image: unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 + volumes: + - "/home/runner/runners:/home/runner/runners" + - "/tmp/filebeat:/tmp/filebeat" + steps: + - name: Print environment variables + run: printenv + + - name: Checkout repository + uses: actions/checkout@v3 + - name: Check out ${{ github.event.inputs.commit_sha }} commit + if: github.event.inputs.commit_sha != '' + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + git fetch + git checkout ${{ github.event.inputs.commit_sha }} + + - name: Run monitoring + # secret uploaded using base64 encoding to have one-line output: + # cat file | base64 -w 0 + continue-on-error: true + run: | + chmod +x ./scripts/project/monitoring.sh + ./scripts/project/monitoring.sh "${PUSHGATEWAY_HOSTNAME}" "${{ secrets.PUSHGATEWAY_USER }}" "${{ secrets.PUSHGATEWAY_PASSWORD }}" + echo "Please visit Grafana to check metrics: https://${PUSHGATEWAY_HOSTNAME}/d/rYdddlPWk/node-exporter-full?orgId=1&from=now-1h&to=now&var-service=github&var-instance=${GITHUB_RUN_ID}-${HOSTNAME}&refresh=1m" + echo --- + printf ${{ secrets.CA_CERT }} | base64 -d > ${{ env.FILEBEAT_DIR }}/ca.crt + printf ${{ secrets.CLIENT_CRT }} | base64 -d > ${{ env.FILEBEAT_DIR }}/client.crt + printf ${{ secrets.CLIENT_KEY }} | base64 -d > ${{ env.FILEBEAT_DIR }}/client.key + chmod +x ./scripts/project/logging.sh + ./scripts/project/logging.sh "${FILEBEAT_DIR}" "${{ secrets.ELK_HOST }}:5044" + echo "Please visit ELK to check logs https://logs.utbot.org/app/discover#/ using the following search pattern: github.env.HOSTNAME:\"${HOSTNAME}\" and github.env.GITHUB_RUN_ID:\"${GITHUB_RUN_ID}\" and not github.log_level:\"INFO\"" + + # cache will use the key you provided and contains the files you specify in path. + # + # When key matches an existing cache, it's called a cache hit, and the action + # restores the cached files to the path directory. + # When key doesn't match an existing cache, it's called a cache miss, and a new + # cache is automatically created if the job completes successfully. + # + # The cache action first searches for cache hits for key and restore-keys in the + # branch containing the workflow run. If there are no hits in the current branch, + # the cache action searches for key and restore-keys in the parent branch and + # upstream branches. + - uses: actions/cache@v3 + with: + path: /root/.gradle/caches + # key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle', '*.gradle.kts', './*.gradle', './*.gradle.kts') }} + # hashFiles returns a single hash for the set of files that matches the path pattern + key: ${{ runner.os }}-gradle-framework-${{ hashFiles('./*.gradle*', './utbot-framework*/*.gradle*') }} + restore-keys: ${{ runner.os }}-gradle-framework + + - name: Run tests + run: | + gradle --no-daemon --build-cache --no-parallel -PprojectType=Ultimate -PgithubActor=${{ github.actor }} -PgithubToken=${{ secrets.PACKAGES_RO_TOKEN }} -Dorg.gradle.jvmargs=-Xmx6g -Dkotlin.daemon.jvm.options=-Xmx4g :utbot-framework-test:test ${{ matrix.project.TESTS_TO_RUN }} + + - name: Upload logs + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: logs ${{ matrix.project.PART_NAME }} + path: utbot-framework-test/logs/* + + - name: Upload UTBot temp directory content + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: utbot_temp ${{ matrix.project.PART_NAME }} + path: | + /tmp/UTBot/generated*/* + /tmp/UTBot/utbot-instrumentedprocess-errors/* + - name: Upload test report if tests have failed + if: ${{ failure() }} + uses: actions/upload-artifact@v3 + with: + name: test_report ${{ matrix.project.PART_NAME }} + path: utbot-framework-test/build/reports/tests/test/* + + + spring-tests: + permissions: read-all + runs-on: ubuntu-20.04 + container: + image: unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 + volumes: + - "/home/runner/runners:/home/runner/runners" + - "/tmp/filebeat:/tmp/filebeat" + steps: + - name: Print environment variables + run: printenv + + - name: Checkout repository + uses: actions/checkout@v3 + - name: Check out ${{ github.event.inputs.commit_sha }} commit + if: github.event.inputs.commit_sha != '' + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + git fetch + git checkout ${{ github.event.inputs.commit_sha }} + + - name: Run monitoring + continue-on-error: true + run: | + chmod +x ./scripts/project/monitoring.sh + ./scripts/project/monitoring.sh "${PUSHGATEWAY_HOSTNAME}" "${{ secrets.PUSHGATEWAY_USER }}" "${{ secrets.PUSHGATEWAY_PASSWORD }}" + echo "Please visit Grafana to check metrics: https://${PUSHGATEWAY_HOSTNAME}/d/rYdddlPWk/node-exporter-full?orgId=1&from=now-1h&to=now&var-service=github&var-instance=${GITHUB_RUN_ID}-${HOSTNAME}&refresh=1m" + echo --- + printf ${{ secrets.CA_CERT }} | base64 -d > ${{ env.FILEBEAT_DIR }}/ca.crt + printf ${{ secrets.CLIENT_CRT }} | base64 -d > ${{ env.FILEBEAT_DIR }}/client.crt + printf ${{ secrets.CLIENT_KEY }} | base64 -d > ${{ env.FILEBEAT_DIR }}/client.key + chmod +x ./scripts/project/logging.sh + ./scripts/project/logging.sh "${FILEBEAT_DIR}" "${{ secrets.ELK_HOST }}:5044" + echo "Please visit ELK to check logs https://logs.utbot.org/app/discover#/ using the following search pattern: github.env.HOSTNAME:\"${HOSTNAME}\" and github.env.GITHUB_RUN_ID:\"${GITHUB_RUN_ID}\" and not github.log_level:\"INFO\"" + + - uses: actions/cache@v3 + with: + path: /root/.gradle/caches + key: ${{ runner.os }}-gradle-spring-${{ hashFiles('./*.gradle*', './utbot-spring*/*.gradle*') }} + restore-keys: ${{ runner.os }}-gradle-spring + + - name: Run tests + run: | + cd utbot-spring-test + gradle --no-daemon --build-cache --no-parallel -PprojectType=Community -PgithubActor=${{ github.actor }} -PgithubToken=${{ secrets.PACKAGES_RO_TOKEN }} -Dorg.gradle.jvmargs=-Xmx6g -Dkotlin.daemon.jvm.options=-Xmx4g :utbot-spring-test:test + + - name: Upload logs + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: logs utbot-spring-test + path: utbot-spring-test/logs/* + - name: Upload UTBot temp directory content + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: utbot_temp utbot-spring-test + path: | + /tmp/UTBot/generated*/* + /tmp/UTBot/utbot-instrumentedprocess-errors/* + - name: Upload test report if tests have failed + if: ${{ failure() }} + uses: actions/upload-artifact@v3 + with: + name: test_report utbot-spring-test + path: utbot-spring-test/build/reports/tests/test/* + + + combined-projects: + # This job does not need to wait for 'prepare-tests-matrix' result. + # GitHub allocates runners portionally. Framework tests are time consuming. That's why we want to force them + # to start execution early. + needs: prepare-matrices + # Using matrices let create multiple jobs runs based on the combinations of the variables from matrices. + # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs + strategy: + # The option forces to execute all jobs even though some of them have failed. + fail-fast: false + matrix: ${{ fromJson(needs.prepare-matrices.outputs.combined-projects-matrix) }} runs-on: ubuntu-20.04 + container: + image: unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 + volumes: + - "/home/runner/runners:/home/runner/runners" + - "/tmp/filebeat:/tmp/filebeat" steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - java-version: '8' - distribution: 'zulu' - java-package: jdk+fx - cache: gradle - - uses: gradle/gradle-build-action@v2 - with: - gradle-version: 6.8 - - - name: Build and run tests in UTBot Java - run: | - export KOTLIN_HOME="/usr" - gradle clean build --no-daemon - - - name: Upload utbot-framework logs - if: ${{ always() }} - uses: actions/upload-artifact@v2 - with: - name: utbot_framework_logs - path: utbot-framework/logs/* - - - name: Upload utbot-framework tests report artifacts if tests have failed - if: ${{ failure() }} - uses: actions/upload-artifact@v2 - with: - name: utbot_framework_tests_report - path: utbot-framework/build/reports/tests/test/* - - - name: Upload utbot-intellij tests report artifacts if tests have failed - if: ${{ failure() }} - uses: actions/upload-artifact@v2 - with: - name: utbot_intellij_tests_report - path: utbot-intellij/build/reports/tests/test/* + - name: Print environment variables + run: printenv + + - name: Checkout repository + uses: actions/checkout@v3 + - name: Check out ${{ github.event.inputs.commit_sha }} commit + if: github.event.inputs.commit_sha != '' + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + git fetch + git checkout ${{ github.event.inputs.commit_sha }} + + - name: Run monitoring + run: | + chmod +x ./scripts/project/monitoring.sh + ./scripts/project/monitoring.sh "${PUSHGATEWAY_HOSTNAME}" "${{ secrets.PUSHGATEWAY_USER }}" "${{ secrets.PUSHGATEWAY_PASSWORD }}" + echo "Please visit Grafana to check metrics: https://${PUSHGATEWAY_HOSTNAME}/d/rYdddlPWk/node-exporter-full?orgId=1&from=now-1h&to=now&var-service=github&var-instance=${GITHUB_RUN_ID}-${HOSTNAME}&refresh=1m" + echo --- + printf ${{ secrets.CA_CERT }} | base64 -d > ${{ env.FILEBEAT_DIR }}/ca.crt + printf ${{ secrets.CLIENT_CRT }} | base64 -d > ${{ env.FILEBEAT_DIR }}/client.crt + printf ${{ secrets.CLIENT_KEY }} | base64 -d > ${{ env.FILEBEAT_DIR }}/client.key + chmod +x ./scripts/project/logging.sh + ./scripts/project/logging.sh "${FILEBEAT_DIR}" "${{ secrets.ELK_HOST }}:5044" + echo "Please visit ELK to check logs https://logs.utbot.org/app/discover#/ using the following search pattern: github.env.HOSTNAME:\"${HOSTNAME}\" and github.env.GITHUB_RUN_ID:\"${GITHUB_RUN_ID}\" and not github.log_level:\"INFO\"" + + - uses: actions/cache@v3 + with: + path: /root/.gradle/caches + key: ${{ runner.os }}-gradle-combined-${{ hashFiles('./*.gradle*', './*/.gradle*') }} + restore-keys: ${{ runner.os }}-gradle-combined- + - name: Build project ${{ matrix.projects.first }} + id: first-project + run: | + cd ${{ matrix.projects.first }} + gradle build --no-daemon --build-cache --no-parallel -PgithubActor=${{ github.actor }} -PgithubToken=${{ secrets.PACKAGES_RO_TOKEN }} -Dorg.gradle.jvmargs=-Xmx6g -Dkotlin.daemon.jvm.options=-Xmx4g + + - name: Build project ${{ matrix.projects.second }} + if: ${{ steps.first-project.outcome != 'cancelled' && steps.first-project.outcome != 'skipped' }} + run: | + cd ${{ matrix.projects.second }} + gradle build --no-daemon --build-cache --no-parallel -PgithubActor=${{ github.actor }} -PgithubToken=${{ secrets.PACKAGES_RO_TOKEN }} -Dorg.gradle.jvmargs=-Xmx6g -Dkotlin.daemon.jvm.options=-Xmx4g + + - name: Upload test report if tests have failed + if: ${{ failure() }} + uses: actions/upload-artifact@v3 + with: + name: test_report ${{ matrix.projects.first }} + path: ${{ matrix.projects.first }}/build/reports/tests/test/* + + - name: Upload test report if tests have failed + if: ${{ failure() }} + uses: actions/upload-artifact@v3 + with: + name: test_report ${{ matrix.projects.second }} + path: ${{ matrix.projects.second }}/build/reports/tests/test/* + + + single-project: + # This job does not need to wait for 'prepare-tests-matrix' result. + # GitHub allocates runners portionally. Framework tests are time consuming. That's why we want to force them + # to start execution early. + needs: prepare-matrices + # Using matrices let create multiple jobs runs based on the combinations of the variables from matrices. + # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs + strategy: + # The option forces to execute all jobs even though some of them have failed. + fail-fast: false + matrix: + project: [utbot-core, utbot-java-fuzzing, utbot-gradle, utbot-junit-contest, utbot-sample] + runs-on: ubuntu-20.04 + container: + image: unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 + volumes: + - "/home/runner/runners:/home/runner/runners" + - "/tmp/filebeat:/tmp/filebeat" + steps: + - name: Print environment variables + run: printenv + + - name: Checkout repository + uses: actions/checkout@v3 + - name: Check out ${{ github.event.inputs.commit_sha }} commit + if: github.event.inputs.commit_sha != '' + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + git fetch + git checkout ${{ github.event.inputs.commit_sha }} + + - name: Run monitoring + run: | + chmod +x ./scripts/project/monitoring.sh + ./scripts/project/monitoring.sh "${PUSHGATEWAY_HOSTNAME}" "${{ secrets.PUSHGATEWAY_USER }}" "${{ secrets.PUSHGATEWAY_PASSWORD }}" + echo "Please visit Grafana to check metrics: https://${PUSHGATEWAY_HOSTNAME}/d/rYdddlPWk/node-exporter-full?orgId=1&from=now-1h&to=now&var-service=github&var-instance=${GITHUB_RUN_ID}-${HOSTNAME}&refresh=1m" + echo --- + printf ${{ secrets.CA_CERT }} | base64 -d > ${{ env.FILEBEAT_DIR }}/ca.crt + printf ${{ secrets.CLIENT_CRT }} | base64 -d > ${{ env.FILEBEAT_DIR }}/client.crt + printf ${{ secrets.CLIENT_KEY }} | base64 -d > ${{ env.FILEBEAT_DIR }}/client.key + chmod +x ./scripts/project/logging.sh + ./scripts/project/logging.sh "${FILEBEAT_DIR}" "${{ secrets.ELK_HOST }}:5044" + echo "Please visit ELK to check logs https://logs.utbot.org/app/discover#/ using the following search pattern: github.env.HOSTNAME:\"${HOSTNAME}\" and github.env.GITHUB_RUN_ID:\"${GITHUB_RUN_ID}\" and not github.log_level:\"INFO\"" + + - uses: actions/cache@v3 + with: + path: /root/.gradle/caches + key: ${{ runner.os }}-gradle-${{ matrix.project }}-${{ hashFiles('./*.gradle*', format('{0}{1}{2}', './', matrix.project, '/*.gradle*')) }} + restore-keys: ${{ runner.os }}-gradle-${{ matrix.project }}- + - name: Run tests + run: | + cd ${{ matrix.project }} + gradle build --no-daemon --build-cache --no-parallel -PgithubActor=${{ github.actor }} -PgithubToken=${{ secrets.PACKAGES_RO_TOKEN }} -Dorg.gradle.jvmargs=-Xmx6g -Dkotlin.daemon.jvm.options=-Xmx4g + + - name: Upload test report if tests have failed + if: ${{ failure() }} + uses: actions/upload-artifact@v3 + with: + name: test_report ${{ matrix.project }} + path: ${{ matrix.project }}/build/reports/tests/test/* diff --git a/.github/workflows/build-and-run-tests.yml b/.github/workflows/build-and-run-tests.yml index 30af5f45ad..51d5a8b330 100644 --- a/.github/workflows/build-and-run-tests.yml +++ b/.github/workflows/build-and-run-tests.yml @@ -1,47 +1,40 @@ name: "UTBot Java: build and run tests" -on: +permissions: read-all + +on: push: - branches: [main] + branches: + - 'main' + - 'unit-test-bot/r**' pull_request: - branches: [main] + branches: + - 'main' + - 'unit-test-bot/r**' jobs: - build_and_run_tests: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - java-version: '8' - distribution: 'zulu' - java-package: jdk+fx - - uses: gradle/gradle-build-action@v2 - with: - gradle-version: 6.8 - - - name: Build and run tests in UTBot Java - run: | - export KOTLIN_HOME="/usr" - gradle clean build --no-daemon - - - name: Upload utbot-framework logs - if: ${{ always() }} - uses: actions/upload-artifact@v2 - with: - name: utbot_framework_logs - path: utbot-framework/logs/* - - - name: Upload utbot-framework tests report artifacts if tests have failed - if: ${{ failure() }} - uses: actions/upload-artifact@v2 - with: - name: utbot_framework_tests_report - path: utbot-framework/build/reports/tests/test/* - - - name: Upload utbot-intellij tests report artifacts if tests have failed - if: ${{ failure() }} - uses: actions/upload-artifact@v2 - with: - name: utbot_intellij_tests_report - path: utbot-intellij/build/reports/tests/test/* + build-and-run-tests: + uses: ./.github/workflows/build-and-run-tests-from-branch.yml + secrets: inherit + + publish_plugin: + needs: build-and-run-tests + uses: ./.github/workflows/publish-plugin-from-branch.yml + with: + # upload artifacts on push action to main only + upload-artifact: ${{ github.event_name == 'push' }} + secrets: inherit + + publish_cli: + needs: build-and-run-tests + uses: ./.github/workflows/publish-cli-from-branch.yml + with: + # upload artifacts on push action to main only + upload-artifact: ${{ github.event_name == 'push' }} + secrets: inherit + + publish-cli-image: + needs: build-and-run-tests + if: ${{ github.event_name == 'push' }} + uses: ./.github/workflows/publish-cli-image-from-branch.yml + secrets: inherit diff --git a/.github/workflows/collect-statistics.yml b/.github/workflows/collect-statistics.yml new file mode 100644 index 0000000000..e330927f95 --- /dev/null +++ b/.github/workflows/collect-statistics.yml @@ -0,0 +1,295 @@ +name: "[M] UTBot Java: collect statistics" + +permissions: read-all + +on: + workflow_call: + inputs: + runners: + description: 'Runners number' + required: false + default: '1' + type: string + run_number: + description: 'Number of run tries per runner (values greater than 1 are not supported with grafana)' + required: false + default: '1' + type: string + message_prefix: + description: 'Commit message prefix' + required: false + default: manual-run + type: string + push_results: + description: 'Push metrics into github' + required: false + default: false + type: boolean + send_to_grafana: + description: 'Send metrics to grafana' + required: false + default: false + type: boolean + + workflow_dispatch: + inputs: + runners: + description: 'Runners number' + required: false + default: '1' + type: string + run_number: + description: 'Number of run tries per runner (values greater than 1 are not supported with grafana)' + required: false + default: '1' + type: string + message_prefix: + description: 'Commit message prefix' + required: false + default: manual-run + type: string + push_results: + description: 'Push metrics into github' + required: false + default: false + type: boolean + send_to_grafana: + description: 'Send metrics to grafana' + required: false + default: false + type: boolean + +env: + data_branch: monitoring-data + data_path: monitoring/data + monitoring_projects: monitoring/projects/ + push_script: monitoring/push_with_rebase.sh + PUSHGATEWAY_HOSTNAME: monitoring.utbot.org + PUSHGATEWAY_ADDITIONAL_PATH: /pushgateway-custom + PROM_ADDITIONAL_LABELS: /service/github + +jobs: + setup_matrix: + runs-on: ubuntu-latest + outputs: + projects: ${{ steps.set-matrix.outputs.projects }} + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Create matrix + id: set-matrix + shell: bash + run: | + read -r -a projects <<< $(ls --format=horizontal --indicator-style=none $monitoring_projects) + projects=(${projects[@]/#/\"}) + projects=(${projects[@]/%/\"}) + printf -v projects '%s,' "${projects[@]}" + projects=$(echo [${projects%,}]) + echo "projects=$projects" >> $GITHUB_OUTPUT + echo $projects + + arr=$(echo [$(seq -s , ${{ inputs.runners }})]) + echo "matrix=$arr" >> $GITHUB_OUTPUT + echo $arr + + build_and_collect_statistics: + needs: setup_matrix + continue-on-error: true + strategy: + # temporary commented, remove completely after 10.23 if all pipelines worked well + #max-parallel: 3 + matrix: + project: ${{ fromJson(needs.setup_matrix.outputs.projects) }} + value: ${{ fromJson(needs.setup_matrix.outputs.matrix) }} + runs-on: ubuntu-20.04 + container: unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 + steps: + - name: Install git + run: | + apt-get update -y + apt-get install git -y + git config --global --add safe.directory $(pwd) + + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Checkout monitoring data + uses: actions/checkout@v3 + with: + ref: ${{ env.data_branch }} + path: ${{ env.data_path }} + + - name: Expand system swap + shell: bash + # trying to configure swap on host from running container + run: | + docker run -d --rm --name busybox --privileged --net=host --pid=host --ipc=host --volume /:/host busybox sleep infinity + docker exec busybox /bin/sh -c 'chroot /host /bin/bash -c "swapoff /mnt/swapfile"' + docker exec busybox /bin/sh -c 'chroot /host /bin/bash -c "dd if=/dev/zero of=/mnt/swapfile bs=1M count=8192 oflag=append conv=notrunc"' + docker exec busybox /bin/sh -c 'chroot /host /bin/bash -c "mkswap /mnt/swapfile"' + docker exec busybox /bin/sh -c 'chroot /host /bin/bash -c "swapon /mnt/swapfile"' + + - name: Run system monitoring + # secret uploaded using base64 encoding to have one-line output: + # cat file | base64 -w 0 + continue-on-error: true + run: | + chmod +x ./scripts/project/monitoring.sh + ./scripts/project/monitoring.sh "${PUSHGATEWAY_HOSTNAME}" "${{ secrets.PUSHGATEWAY_USER }}" "${{ secrets.PUSHGATEWAY_PASSWORD }}" + echo "Please visit Grafana to check metrics: https://${PUSHGATEWAY_HOSTNAME}/d/rYdddlPWk/node-exporter-full?orgId=1&from=now-1h&to=now&var-service=github&var-instance=${GITHUB_RUN_ID}-${HOSTNAME}&refresh=1m" + + - uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Build and run monitoring UTBot Java + run: | + gradle :utbot-junit-contest:monitoringJar + for i in $(seq ${{ inputs.run_number }}) + do + java -jar \ + --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED \ + --add-opens java.base/java.lang.invoke=ALL-UNNAMED \ + --add-opens java.base/java.util.concurrent=ALL-UNNAMED \ + --add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED \ + --add-opens java.base/java.text=ALL-UNNAMED \ + --add-opens java.base/java.time=ALL-UNNAMED \ + --add-opens java.base/java.io=ALL-UNNAMED \ + --add-opens java.base/java.nio=ALL-UNNAMED \ + --add-opens java.base/java.nio.file=ALL-UNNAMED \ + --add-opens java.base/java.net=ALL-UNNAMED \ + --add-opens java.base/sun.security.util=ALL-UNNAMED \ + --add-opens java.base/sun.reflect.generics.repository=ALL-UNNAMED \ + --add-opens java.base/sun.net.util=ALL-UNNAMED \ + --add-opens java.base/sun.net.fs=ALL-UNNAMED \ + --add-opens java.base/java.security=ALL-UNNAMED \ + --add-opens java.base/java.lang.ref=ALL-UNNAMED \ + --add-opens java.base/java.math=ALL-UNNAMED \ + --add-opens java.base/java.util.stream=ALL-UNNAMED \ + --add-opens java.base/java.util=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.misc=ALL-UNNAMED \ + --add-opens java.base/java.lang=ALL-UNNAMED \ + --add-opens java.base/java.lang.reflect=ALL-UNNAMED \ + --add-opens java.base/sun.security.provider=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.event=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.jimage=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.jimage.decompressor=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.jmod=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.jtrfs=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.loader=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.logger=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.math=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.misc=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.module=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.org.objectweb.asm.commons=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.org.objectweb.asm.signature=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.org.objectweb.asm.tree.analysis=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.org.objectweb.asm.util=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.org.xml.sax=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.org.xml.sax.helpers=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.perf=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.platform=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.ref=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.util=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.util.jar=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.util.xml=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.util.xml.impl=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.vm=ALL-UNNAMED \ + --add-opens java.base/jdk.internal.vm.annotation=ALL-UNNAMED \ + -Dutbot.monitoring.settings.path=$monitoring_projects/${{ matrix.project }}/monitoring.properties \ + utbot-junit-contest/build/libs/monitoring.jar \ + stats-$i.json + mv logs/utbot.log logs/utbot-$i.log + done + + - name: Get current date + id: date + run: | + echo "date=$(date +'%Y-%m-%d-%H-%M-%S')" >> $GITHUB_OUTPUT + echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT + echo "last_month=$(date --date='last month' +%s)" >> $GITHUB_OUTPUT + + - name: Get metadata + id: metadata + run: | + echo "commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + echo "short_commit=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "branch=$(git name-rev --name-only HEAD)" >> $GITHUB_OUTPUT + echo "build=$(date +'%Y.%-m')" >> $GITHUB_OUTPUT + + - name: Insert metadata + id: insert + shell: bash + run: | + OUT_FILE="$data_path/$date-$short_commit-${{ matrix.project }}-${{ matrix.value }}.json" + echo "output=$OUT_FILE" >> $GITHUB_OUTPUT + + INPUTS=($(seq ${{ inputs.run_number }})) + INPUTS=(${INPUTS[@]/#/stats-}) + INPUTS=(${INPUTS[@]/%/.json}) + INPUTS=${INPUTS[@]} + echo $INPUTS + + python monitoring/insert_metadata.py \ + --stats_file $INPUTS \ + --output_file "$OUT_FILE" \ + --commit $commit \ + --branch $branch \ + --build "$build" \ + --timestamp $timestamp \ + --source_type "github-action" \ + --source_id $run_id + env: + date: ${{ steps.date.outputs.date }} + timestamp: ${{ steps.date.outputs.timestamp }} + commit: ${{ steps.metadata.outputs.commit }} + short_commit: ${{ steps.metadata.outputs.short_commit }} + branch: ${{ steps.metadata.outputs.branch }} + build: ${{ steps.metadata.outputs.build }} + run_id: ${{ github.run_id }}-${{ matrix.value }} + + - name: Upload statistics + uses: actions/upload-artifact@v3 + with: + name: statistics-${{ matrix.value }}-${{ matrix.project }} + path: ${{ steps.insert.outputs.output }} + + - name: Commit and push statistics + if: ${{ inputs.push_results }} + run: | + chmod +x $push_script + ./$push_script + env: + target_branch: ${{ env.data_branch }} + target_directory: ${{ env.data_path }} + message: ${{ inputs.message_prefix }}-${{ steps.date.outputs.date }} + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Send metrics to grafana + if: ${{ inputs.send_to_grafana }} + run: | + python monitoring/prepare_metrics.py --stats_file $stats_file --output_file grafana_metrics.json + chmod +x scripts/project/json_to_prometheus.py + python3 scripts/project/json_to_prometheus.py grafana_metrics.json | curl -u "${{ secrets.PUSHGATEWAY_USER }}:${{ secrets.PUSHGATEWAY_PASSWORD }}" --data-binary @- "https://${PUSHGATEWAY_HOSTNAME}${PUSHGATEWAY_ADDITIONAL_PATH}/metrics/job/pushgateway-custom/instance/run-${{ matrix.value }}-${{ matrix.project }}${PROM_ADDITIONAL_LABELS}" + echo "Please visit Grafana to check metrics: https://monitoring.utbot.org/d/m6bagaD4z/utbot-nightly-statistic" + env: + stats_file: ${{ steps.insert.outputs.output }} + + - name: Upload logs + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: logs-${{ matrix.value }}-${{ matrix.project }} + path: logs/ + + - name: Upload artifacts + if: ${{ always() }} + uses: actions/upload-artifact@v3 + with: + name: generated-${{ matrix.value }}-${{ matrix.project }} + path: | + /tmp/UTBot/generated*/* diff --git a/.github/workflows/combined-projects-matrix.json b/.github/workflows/combined-projects-matrix.json new file mode 100644 index 0000000000..83130dd191 --- /dev/null +++ b/.github/workflows/combined-projects-matrix.json @@ -0,0 +1,20 @@ +{ + "projects": [ + { + "FIRST": "utbot-intellij", + "SECOND": "utbot-cli" + }, + { + "FIRST": "utbot-instrumentation", + "SECOND": "utbot-instrumentation-tests" + }, + { + "FIRST": "utbot-summary", + "SECOND": "utbot-summary-tests" + }, + { + "FIRST": "utbot-api", + "SECOND": "utbot-framework-api" + } + ] +} diff --git a/.github/workflows/framework-tests-matrix.json b/.github/workflows/framework-tests-matrix.json new file mode 100644 index 0000000000..121eed2a0d --- /dev/null +++ b/.github/workflows/framework-tests-matrix.json @@ -0,0 +1,76 @@ +{ + "project": [ + { + "PART_NAME": "composite-part1", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.manual.*\"" + }, + { + "PART_NAME": "composite-part2", + "TESTS_TO_RUN": "--tests \"org.utbot.engine.*\" --tests \"org.utbot.framework.*\" --tests \"org.utbot.sarif.*\" --tests \"org.utbot.examples.taint.*\"" + }, + { + "PART_NAME": "composite-part3", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.stream.*\"" + }, + { + "PART_NAME": "collections-part1", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.collections.MapValuesTest\" --tests \"org.utbot.examples.collections.OptionalsTest\" --tests \"org.utbot.examples.collections.SetIteratorsTest\" --tests \"org.utbot.examples.collections.CustomerExamplesTest\" --tests \"org.utbot.examples.collections.GenericListsExampleTest\" --tests \"org.utbot.examples.collections.LinkedListsTest\" --tests \"org.utbot.examples.collections.ListAlgorithmsTest\"" + }, + { + "PART_NAME": "collections-part2", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.collections.SetsTest\" --tests \"org.utbot.examples.collections.ListIteratorsTest\" --tests \"org.utbot.examples.collections.ListWrapperReturnsVoidTest\" --tests \"org.utbot.examples.collections.MapEntrySetTest\" --tests \"org.utbot.examples.collections.MapKeySetTest\"" + }, + { + "PART_NAME": "examples-part1", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.annotations.*\" --tests \"org.utbot.examples.arrays.*\" --tests \"org.utbot.examples.casts.*\" --tests \"org.utbot.examples.controlflow.*\" --tests \"org.utbot.examples.enums.*\"" + }, + { + "PART_NAME": "examples-part2", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.invokes.*\" --tests \"org.utbot.examples.lambda.*\" --tests \"org.utbot.examples.make.symbolic.*\" --tests \"org.utbot.examples.math.*\" --tests \"org.utbot.examples.mixed.*\"" + }, + { + "PART_NAME": "examples-part3", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.primitives.*\"" + }, + { + "PART_NAME": "examples-part4", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.thirdparty.numbers.*\" --tests \"org.utbot.examples.types.*\" --tests \"org.utbot.examples.unsafe.*\" --tests \"org.utbot.examples.wrappers.*\" --tests \"org.utbot.examples.recursion.*\" --tests \"org.utbot.examples.statics.substitution.*\" --tests \"org.utbot.examples.stdlib.*\" --tests \"org.utbot.examples.structures.*\"" + }, + { + "PART_NAME": "examples-part5", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.strings.*\"" + }, + { + "PART_NAME": "examples-part6", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.strings11.*\"" + }, + { + "PART_NAME": "examples-part7", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.algorithms.*\"" + }, + { + "PART_NAME": "examples-part8", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.codegen.*\"" + }, + { + "PART_NAME": "examples-part9", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.mock.*\" --tests \"org.utbot.examples.models.*\" --tests \"org.utbot.examples.natives.*\" --tests \"org.utbot.examples.objects.*\" --tests \"org.utbot.examples.reflection.*\" --tests \"org.utbot.examples.threads.*\"" + }, + { + "PART_NAME": "examples-part10", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.exceptions.*\"" + }, + { + "PART_NAME": "examples-lists", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.collections.ListsPart1Test\" --tests \"org.utbot.examples.collections.ListsPart2Test\" --tests \"org.utbot.examples.collections.ListsPart3Test\"" + }, + { + "PART_NAME": "examples-maps-part1", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.collections.MapsPart1Test\"" + }, + { + "PART_NAME": "examples-maps-part2", + "TESTS_TO_RUN": "--tests \"org.utbot.examples.collections.MapsPart2Test\"" + } + ] +} diff --git a/.github/workflows/issue-to-project.yml b/.github/workflows/issue-to-project.yml index 07f44137e2..fc53eb2da5 100644 --- a/.github/workflows/issue-to-project.yml +++ b/.github/workflows/issue-to-project.yml @@ -1,5 +1,7 @@ name: Add issues to UTBot Java project +permissions: read-all + on: issues: types: @@ -14,9 +16,3 @@ jobs: with: project-url: https://github.com/orgs/UnitTestBot/projects/2 github-token: ${{ secrets.COPY_ISSUE_TO_PROJECT }} - - - uses: actions/add-to-project@main - with: - project-url: https://github.com/orgs/UnitTestBot/projects/5 - github-token: ${{ secrets.COPY_ISSUE_TO_PROJECT }} - diff --git a/.github/workflows/night-statistics-monitoring.yml b/.github/workflows/night-statistics-monitoring.yml new file mode 100644 index 0000000000..b0b134521f --- /dev/null +++ b/.github/workflows/night-statistics-monitoring.yml @@ -0,0 +1,18 @@ +name: "UTBot Java: night statistics monitoring" + +permissions: read-all + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + run_monitoring: + uses: ./.github/workflows/collect-statistics.yml + secrets: inherit + with: + runners: 3 + run_number: 1 + message_prefix: night-monitoring + push_results: true + send_to_grafana: true diff --git a/.github/workflows/public-rider-plugin.yml b/.github/workflows/public-rider-plugin.yml new file mode 100644 index 0000000000..587a7cf7bb --- /dev/null +++ b/.github/workflows/public-rider-plugin.yml @@ -0,0 +1,86 @@ +# This is a basic workflow that is manually triggered + +name: Publish Rider plugin + +permissions: read-all + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: + workflow_dispatch: + # Inputs the workflow accepts. + inputs: + minor-release: + type: choice + description: "It adds minor release indicator to version." + required: true + default: 'none' + options: + - 'none' + - '1' + - '2' + - '3' + - '4' + + version-postfix: + type: choice + description: "It adds alpha or beta postfix to version." + required: true + default: no-postfix-prod + options: + - no-postfix-prod + - no-postfix + - alpha + - beta + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "greet" + public_rider_plugin: + # The type of runner that the job will run on + runs-on: ubuntu-20.04 + container: unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Runs a single command using the runners shell + - name: Print environment variables + run: printenv + + - uses: actions/checkout@v3 + + - name: Set environment variables + run: | + # "You can make an environment variable available to any subsequent steps in a workflow job by + # defining or updating the environment variable and writing this to the GITHUB_ENV environment file." + echo "VERSION="$(date +%Y).$(date +%-m).${GITHUB_RUN_NUMBER}"" >> $GITHUB_ENV + echo "POSTFIX=${{ github.event.inputs.version-postfix }}" >> $GITHUB_ENV + + - name: Set production version + if: ${{ github.event.inputs.version-postfix == 'no-postfix-prod' || github.event.inputs.version-postfix == 'alpha' || github.event.inputs.version-postfix == 'beta' }} + run: | + echo "VERSION="$(date +%Y).$(date +%-m)"" >> $GITHUB_ENV + + - name: Set version for minor release + if: ${{ github.event.inputs.minor-release != 'none' }} + run: | + echo "VERSION=${{ env.VERSION }}.${{ github.event.inputs.minor-release }}" >> $GITHUB_ENV + + - name: Create version with postfix + if: ${{ (env.POSTFIX == 'alpha') || (env.POSTFIX == 'beta') }} + run: + echo "VERSION=${{ env.VERSION }}-${{ env.POSTFIX }}" >> $GITHUB_ENV + + - name: Build UTBot Rider plugin + run: | + gradle clean :utbot-rider:buildPlugin --no-daemon --build-cache --no-parallel -PgithubActor=${{ github.actor }} -PgithubToken=${{ secrets.PACKAGES_RO_TOKEN }} -Dorg.gradle.jvmargs=-Xmx2g -Dkotlin.daemon.jvm.options=-Xmx4g -PsemVer=${{ env.VERSION }} -PincludeRiderInBuild=true + cd utbot-rider/build/distributions + unzip utbot-rider-${{ env.VERSION }}.zip + rm utbot-rider-${{ env.VERSION }}.zip + + - name: Archive UTBot Rider plugin + uses: actions/upload-artifact@v3 + with: + name: utbot-rider-${{ env.VERSION }} + path: utbot-rider/build/distributions/* + diff --git a/.github/workflows/publish-cli-from-branch.yml b/.github/workflows/publish-cli-from-branch.yml new file mode 100644 index 0000000000..217407d92e --- /dev/null +++ b/.github/workflows/publish-cli-from-branch.yml @@ -0,0 +1,101 @@ +name: "[M] CLI: publish as archive" + +permissions: read-all + +on: + workflow_call: + inputs: + upload-artifact: + type: string + description: "Upload artifacts or not" + required: false + default: false + commit_sha: + required: false + type: string + description: "(optional) Commit SHA" + custom_version: + type: string + description: "Custom version" + required: false + default: "" + + workflow_dispatch: + inputs: + upload-artifact: + type: choice + description: "Upload artifacts or not" + required: false + default: true + options: + - true + - false + commit_sha: + required: false + type: string + description: "(optional) Commit SHA" + custom_version: + type: string + description: "Custom version" + required: false + default: "" + +jobs: + publish_cli: + strategy: + fail-fast: false # force to execute all jobs even though some of them have failed + matrix: + configuration: + - plugin_type: IC + extra_options: "-PideType=IC" + lang: java + dir: utbot-cli + - plugin_type: IC + extra_options: "-PideType=IC" + lang: python + dir: utbot-cli-python + - plugin_type: IU + extra_options: "-PideType=IU" + lang: go + dir: utbot-cli-go + - plugin_type: IU + extra_options: "-PideType=IU" + lang: js + dir: utbot-cli-js + runs-on: ubuntu-20.04 + container: unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 + steps: + - uses: actions/checkout@v3 + - name: Check out ${{ github.event.inputs.commit_sha }} commit + if: github.event.inputs.commit_sha != '' + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + git fetch + git checkout ${{ github.event.inputs.commit_sha }} + + # "You can make an environment variable available to any subsequent steps in a workflow job by + # defining or updating the environment variable and writing this to the GITHUB_ENV environment file." + - name: Setup custom version + if: ${{ github.event.inputs.custom_version != '' }} + run: | + echo "VERSION=${{ github.event.inputs.custom_version }}" >> $GITHUB_ENV + - name: Setup version + if: ${{ github.event.inputs.custom_version == '' }} + shell: bash + run: | + echo "VERSION=${GITHUB_REF_NAME:0:4}-$(date +%Y).$(date +%-m).${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV + + - name: Print environment variables + run: printenv + + - name: Build UTBot CLI + run: | + cd "${{ matrix.configuration.dir }}" + gradle clean build --no-daemon --build-cache --no-parallel ${{ matrix.configuration.extra_options }} -PgithubActor=${{ github.actor }} -PgithubToken=${{ secrets.PACKAGES_RO_TOKEN }} -Dorg.gradle.jvmargs=-Xmx2g -Dkotlin.daemon.jvm.options=-Xmx4g -PsemVer=${{ env.VERSION }} + + - name: Archive UTBot CLI + if: ${{ inputs.upload-artifact == 'true' }} + uses: actions/upload-artifact@v3 + with: + name: utbot-cli-${{ matrix.configuration.lang }}-${{ env.VERSION }} + path: ${{ matrix.configuration.dir }}/build/libs/${{ matrix.configuration.dir }}-${{ env.VERSION }}.jar diff --git a/.github/workflows/publish-cli-image-from-branch.yml b/.github/workflows/publish-cli-image-from-branch.yml new file mode 100644 index 0000000000..d53b47dd87 --- /dev/null +++ b/.github/workflows/publish-cli-image-from-branch.yml @@ -0,0 +1,99 @@ +name: "[M] CLI: publish docker image" + +permissions: read-all + +on: + workflow_call: + workflow_dispatch: + +env: + REGISTRY: ghcr.io + +jobs: + publish-cli-image: + strategy: + fail-fast: false # force to execute all jobs even though some of them have failed + matrix: + configuration: + - image_name: utbot_java_cli + directory: utbot-cli + extra_options: "" + - image_name: utbot_js_cli + directory: utbot-cli-js + extra_options: "-PbuildType=ALL" + # we can't use utbot_python_cli image name because of the bug while pushing image + # ERROR: unexpected status: 403 Forbidden + - image_name: utbot_py_cli + directory: utbot-cli-python + extra_options: "" + runs-on: ubuntu-20.04 + container: unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 + steps: + - uses: actions/checkout@v3 + + # "You can make an environment variable available to any subsequent steps in a workflow job by + # defining or updating the environment variable and writing this to the GITHUB_ENV environment file." + - name: Set environment variables + run: | + echo VERSION="$(date +%Y).$(date +%-m)" >> $GITHUB_ENV + echo DOCKER_TAG="$(date +%Y).$(date +%-m).$(date +%-d)-$(echo -n ${GITHUB_SHA} | cut -c 1-7)" >> $GITHUB_ENV + + - name: Print environment variables + run: printenv + + - name: Build UTBot CLI + run: | + cd ${{ matrix.configuration.directory }} + gradle build --no-daemon --build-cache --no-parallel ${{ matrix.configuration.extra_options }} -PgithubActor=${{ github.actor }} -PgithubToken=${{ secrets.PACKAGES_RO_TOKEN }} -Dorg.gradle.jvmargs=-Xmx2g -Dkotlin.daemon.jvm.options=-Xmx4g -x test -PsemVer=${{ env.VERSION }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + # fix of containerd issue, see https://github.com/containerd/containerd/issues/7972 + # could be removed as soon as new containerd version will be released and included in buildkit + driver-opts: | + image=moby/buildkit:v0.10.6 + + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ matrix.configuration.image_name }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx-${{ matrix.configuration.image_name }}- + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ github.repository }}/${{ matrix.configuration.image_name }} + tags: | + type=raw,value=${{ env.DOCKER_TAG }} + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: | + ${{ steps.meta.outputs.tags }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new + file: ${{ matrix.configuration.directory }}/Dockerfile + build-args: | + ARTIFACT_PATH=${{ matrix.configuration.directory }}/build/libs/${{ matrix.configuration.directory }}-${{ env.VERSION }}.jar + + # Temp fix + # https://github.com/docker/build-push-action/issues/252 + # https://github.com/moby/buildkit/issues/1896 + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache diff --git a/.github/workflows/publish-cli-image.yml b/.github/workflows/publish-cli-image.yml deleted file mode 100644 index fbc98c35a9..0000000000 --- a/.github/workflows/publish-cli-image.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: "CLI: publish image" -on: - push: - branches: [main] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: utbot_java_cli - DOCKERFILE_PATH: docker/Dockerfile_java_cli - -jobs: - build-and-publish-docker: - runs-on: ubuntu-20.04 - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set timezone - uses: szenius/set-timezone@v1.0 - with: - timezoneLinux: "Europe/Moscow" - - - name: Set environment variables - run: - echo "COMMIT_SHORT_SHA="$(git rev-parse --short HEAD)"" >> $GITHUB_ENV - - - name: Set docker tag - run: - echo "DOCKER_TAG="$(date +%Y).$(date +%-m).$(date +%-d)-${{ env.COMMIT_SHORT_SHA }}"" >> $GITHUB_ENV - - - name: Log in to the Container registry - uses: docker/login-action@v1 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Cache Docker layers - uses: actions/cache@v2 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - - name: Docker meta - id: meta - uses: docker/metadata-action@v3 - with: - images: ${{ env.REGISTRY }}/${{ github.repository }}/${{ env.IMAGE_NAME }} - tags: | - type=raw,value=${{ env.DOCKER_TAG }} - - - name: Docker Buildx (build and push) - run: | - docker buildx build \ - -f ${{ env.DOCKERFILE_PATH }} \ - --cache-from "type=local,src=/tmp/.buildx-cache" \ - --cache-to "type=local,dest=/tmp/.buildx-cache-new" \ - --tag ${{ steps.meta.outputs.tags }} \ - --build-arg ACCESS_TOKEN=${{ secrets.GITHUB_TOKEN }} \ - --push . - - # Temp fix - # https://github.com/docker/build-push-action/issues/252 - # https://github.com/moby/buildkit/issues/1896 - - name: Move cache - run: | - rm -rf /tmp/.buildx-cache - mv /tmp/.buildx-cache-new /tmp/.buildx-cache diff --git a/.github/workflows/publish-on-github-packages.yml b/.github/workflows/publish-on-github-packages.yml index 9fb1e3e2cf..31bbbfd47f 100644 --- a/.github/workflows/publish-on-github-packages.yml +++ b/.github/workflows/publish-on-github-packages.yml @@ -1,60 +1,43 @@ name: "[M] Publish on GitHub Packages" +permissions: read-all + on: workflow_dispatch: inputs: - commit-sha: + commit_sha: type: string required: true description: "commit SHA: e.g. cab4799c" jobs: + build-and-run-tests: + if: ${{ github.actor == 'korifey' || github.actor == 'denis-fokin' || github.actor == 'victoriafomina' || github.actor == 'bissquit' }} + uses: ./.github/workflows/build-and-run-tests-from-branch.yml + with: + commit_sha: ${{ github.event.inputs.commit_sha }} + secrets: inherit + publish_on_github_packages: - if: ${{ github.actor == 'korifey' || github.actor == 'denis-fokin' || github.actor == 'victoriafomina' || - github.actor == 'bissquit' }} + needs: build-and-run-tests runs-on: ubuntu-20.04 - permissions: - packages: write - contents: read steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 - with: - java-version: '8' - distribution: 'zulu' - java-package: jdk+fx - cache: gradle - - uses: gradle/gradle-build-action@v2 - with: - gradle-version: 6.8 - - - name: Check out ${{ github.event.inputs.commit-sha }} commit + - name: Print environment variables + run: printenv + + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Check out ${{ github.event.inputs.commit_sha }} commit + if: github.event.inputs.commit_sha != '' run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} git fetch - git checkout ${{ github.event.inputs.commit-sha }} - - - name: "UTBot Java: build and run tests" - run: | - export KOTLIN_HOME="/usr" - gradle clean build --no-daemon - - - name: Upload utbot-framework logs - if: ${{ failure() }} - uses: actions/upload-artifact@v2 - with: - name: utbot_framework_logs - path: utbot-framework/logs/* - - - name: Upload utbot-framework tests report artifacts if tests have failed - if: ${{ failure() }} - uses: actions/upload-artifact@v2 - with: - name: utbot_framework_tests_report - path: utbot-framework/build/reports/tests/test/* - - - uses: gradle/gradle-build-action@v2 + git checkout ${{ github.event.inputs.commit_sha }} + + - uses: gradle/gradle-build-action@v2.9.0 with: - gradle-version: 6.8 + gradle-version: 7.4.2 arguments: publish env: GITHUB_ACTOR: ${{ github.actor }} diff --git a/.github/workflows/publish-plugin-and-cli-from-branch.yml b/.github/workflows/publish-plugin-and-cli-from-branch.yml deleted file mode 100644 index af6006cb32..0000000000 --- a/.github/workflows/publish-plugin-and-cli-from-branch.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: "[M] Plugin and CLI: publish as archives" - -on: - workflow_dispatch: - inputs: - version-postfix: - type: choice - description: "It adds alpha or beta postfix to version." - required: true - default: no-postfix - options: - - no-postfix - - alpha - - beta - -jobs: - publish_plugin_and_cli: - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - java-version: '8' - distribution: 'zulu' - java-package: jdk+fx - cache: gradle - - uses: gradle/gradle-build-action@v2 - with: - gradle-version: 6.8 - - - name: Set environment variables - run: | - echo "VERSION="$(date +%Y).$(date +%-m)"" >> $GITHUB_ENV - echo "POSTFIX=${{ github.event.inputs.version-postfix }}" >> $GITHUB_ENV - - - name: Create version with postfix - if: ${{ (env.POSTFIX == 'alpha') || (env.POSTFIX == 'beta') }} - run: - echo "VERSION=${{ env.VERSION }}-${{ env.POSTFIX }}" >> $GITHUB_ENV - - - name: Build UTBot IntelliJ IDEA plugin - run: | - export KOTLIN_HOME="/usr" - gradle buildPlugin --no-daemon -PsemVer=${{ env.VERSION }} - cd utbot-intellij/build/distributions - unzip utbot-intellij-${{ env.VERSION }}.zip - rm utbot-intellij-${{ env.VERSION }}.zip - - - name: Archive UTBot IntelliJ IDEA plugin - uses: actions/upload-artifact@v2 - with: - name: utbot-intellij-${{ env.VERSION }} - path: utbot-intellij/build/distributions/* - - - name: Build UTBot CLI - run: | - export KOTLIN_HOME="/usr" - cd utbot-cli - gradle clean build --no-daemon -PsemVer=${{ env.VERSION }} - cd build/libs - - - name: Archive UTBot CLI - uses: actions/upload-artifact@v2 - with: - name: utbot-cli-${{ env.VERSION }} - path: utbot-cli/build/libs/utbot-cli-${{ env.VERSION }}.jar diff --git a/.github/workflows/publish-plugin-and-cli.yml b/.github/workflows/publish-plugin-and-cli.yml deleted file mode 100644 index 5629063d3f..0000000000 --- a/.github/workflows/publish-plugin-and-cli.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: "Plugin and CLI: publish as archives" -on: - push: - branches: [main] - -jobs: - publish_plugin_and_cli: - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - java-version: '8' - distribution: 'zulu' - java-package: jdk+fx - - uses: gradle/gradle-build-action@v2 - with: - gradle-version: 6.8 - - - name: Set environment variables - run: - echo "VERSION="$(date +%Y).$(date +%-m)"" >> $GITHUB_ENV - - - name: Build UTBot IntelliJ IDEA plugin - run: | - export KOTLIN_HOME="/usr" - gradle buildPlugin --no-daemon -PsemVer=${{ env.VERSION }} - cd utbot-intellij/build/distributions - unzip utbot-intellij-${{ env.VERSION }}.zip - rm utbot-intellij-${{ env.VERSION }}.zip - - - name: Archive UTBot IntelliJ IDEA plugin - uses: actions/upload-artifact@v2 - with: - name: utbot-intellij-${{ env.VERSION }} - path: utbot-intellij/build/distributions/* - - - name: Build UTBot CLI - run: | - export KOTLIN_HOME="/usr" - cd utbot-cli - gradle clean build --no-daemon -PsemVer=${{ env.VERSION }} - cd build/libs - - - name: Archive UTBot CLI - uses: actions/upload-artifact@v2 - with: - name: utbot-cli-${{ env.VERSION }} - path: utbot-cli/build/libs/utbot-cli-${{ env.VERSION }}.jar diff --git a/.github/workflows/publish-plugin-from-branch.yml b/.github/workflows/publish-plugin-from-branch.yml new file mode 100644 index 0000000000..7fcb8c56c6 --- /dev/null +++ b/.github/workflows/publish-plugin-from-branch.yml @@ -0,0 +1,98 @@ +name: "[M] Plugin: publish as archive" + +permissions: read-all + +on: + workflow_call: + inputs: + upload-artifact: + type: string + description: "Upload artifacts or not" + required: false + default: false + commit_sha: + required: false + type: string + description: "(optional) Commit SHA" + custom_version: + type: string + description: "Custom version" + required: false + default: "" + + workflow_dispatch: + inputs: + upload-artifact: + type: choice + description: "Upload artifacts or not" + required: false + default: true + options: + - true + - false + commit_sha: + required: false + type: string + description: "(optional) Commit SHA" + custom_version: + type: string + description: "Custom version" + required: false + default: "" + +jobs: + publish_plugin: + strategy: + fail-fast: false # force to execute all jobs even though some of them have failed + matrix: + configuration: + - plugin_type: IC + extra_options: "-PideType=IC -PprojectType=Community" + directory: utbot-intellij-main + - plugin_type: IU + extra_options: "-PideType=IU -PprojectType=Ultimate" + directory: utbot-intellij-main + - plugin_type: PY + extra_options: "-PideType=PY -PprojectType=Ultimate" + directory: utbot-python-pycharm + runs-on: ubuntu-20.04 + container: unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 + steps: + - uses: actions/checkout@v3 + - name: Check out ${{ github.event.inputs.commit_sha }} commit + if: github.event.inputs.commit_sha != '' + run: | + git config --global --add safe.directory ${GITHUB_WORKSPACE} + git fetch + git checkout ${{ github.event.inputs.commit_sha }} + + # "You can make an environment variable available to any subsequent steps in a workflow job by + # defining or updating the environment variable and writing this to the GITHUB_ENV environment file." + - name: Setup custom version + if: ${{ github.event.inputs.custom_version != '' }} + run: | + echo "VERSION=${{ github.event.inputs.custom_version }}" >> $GITHUB_ENV + echo "VERSION_ARCHIVE=${{ github.event.inputs.custom_version }}" >> $GITHUB_ENV + - name: Setup version + if: ${{ github.event.inputs.custom_version == '' }} + shell: bash + run: | + echo "VERSION=$(date +%Y).$(date +%-m).${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV + echo "VERSION_ARCHIVE=${GITHUB_REF_NAME:0:4}-$(date +%Y).$(date +%-m).${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV + + - name: Print environment variables + run: printenv + + - name: Build UTBot IntelliJ IDEA plugin + run: | + gradle clean buildPlugin --no-daemon --build-cache --no-parallel -PgithubActor=${{ github.actor }} -PgithubToken=${{ secrets.PACKAGES_RO_TOKEN }} ${{ matrix.configuration.extra_options }} -Dorg.gradle.jvmargs=-Xmx2g -Dkotlin.daemon.jvm.options=-Xmx4g -PsemVer=${{ env.VERSION }} + cd ${{ matrix.configuration.directory }}/build/distributions + unzip ${{ matrix.configuration.directory }}-${{ env.VERSION }}.zip + rm ${{ matrix.configuration.directory }}-${{ env.VERSION }}.zip + + - name: Archive UTBot IntelliJ IDEA plugin + if: ${{ inputs.upload-artifact == 'true' }} + uses: actions/upload-artifact@v3 + with: + name: utbot-intellij-${{ matrix.configuration.plugin_type }}-${{ env.VERSION_ARCHIVE }} + path: ${{ matrix.configuration.directory }}/build/distributions/* diff --git a/.github/workflows/run-chosen-tests-from-branch.yml b/.github/workflows/run-chosen-tests-from-branch.yml index 968a2982fe..2e24f6fbd1 100644 --- a/.github/workflows/run-chosen-tests-from-branch.yml +++ b/.github/workflows/run-chosen-tests-from-branch.yml @@ -1,6 +1,7 @@ - name: "[M] Run chosen tests" +permissions: read-all + on: workflow_dispatch: inputs: @@ -12,49 +13,64 @@ on: options: - utbot-analytics - utbot-cli + - utbot-core - utbot-framework-api - utbot-framework - - utbot-fuzzers + - utbot-java-fuzzing - utbot-gradle - utbot-instrumentation-tests - utbot-instrumentation - utbot-intellij + - utbot-sample + - utbot-summary + - utbot-summary-tests tests-bunch-name: type: string required: true - description: "{package-name}.{class-name}.{test-name-optional}" - + description: "{package-name}.{class-name-optional}.{test-name-optional}" + +env: + PUSHGATEWAY_HOSTNAME: monitoring.utbot.org + jobs: run-chosen-tests: runs-on: ubuntu-20.04 - + container: unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 + steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - java-version: '8' - distribution: 'zulu' - java-package: jdk+fx - cache: gradle - - uses: gradle/gradle-build-action@v2 - with: - gradle-version: 6.8 + - name: Print environment variables + run: printenv + + - uses: actions/checkout@v3 + + - name: Run monitoring + run: | + echo Find your Prometheus metrics using label {instance=\"${GITHUB_RUN_ID}-${HOSTNAME}\"} + chmod +x ./scripts/project/monitoring.sh + ./scripts/project/monitoring.sh ${PUSHGATEWAY_HOSTNAME} ${{ secrets.PUSHGATEWAY_USER }} ${{ secrets.PUSHGATEWAY_PASSWORD }} - name: Run chosen tests run: | - export KOTLIN_HOME="/usr" - gradle :${{ github.event.inputs.project-name }}:test --tests ${{ github.event.inputs.tests-bunch-name }} + gradle :${{ github.event.inputs.project-name }}:test -PprojectType=Ultimate --no-daemon --build-cache --no-parallel -PgithubActor=${{ github.actor }} -PgithubToken=${{ secrets.PACKAGES_RO_TOKEN }} -Dorg.gradle.jvmargs=-Xmx2g -Dkotlin.daemon.jvm.options=-Xmx4g --tests ${{ github.event.inputs.tests-bunch-name }} - name: Upload ${{ github.event.inputs.project-name }} tests report if tests have failed if: ${{ failure() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: ${{ github.event.inputs.project-name }}-tests-report path: ${{ github.event.inputs.project-name }}/build/reports/tests/test/* - - name: Upload utbot-framework logs if utbot-framework tests have failed - if: ${{ failure() || (github.event.inputs.project-name == 'utbot-framework') }} - uses: actions/upload-artifact@v2 + - name: Upload generated tests + if: ${{ always() && github.event.inputs.project-name == 'utbot-framework' }} + uses: actions/upload-artifact@v3 + with: + name: generated-tests + path: | + /tmp/UTBot/generated*/* + /tmp/UTBot/utbot-instrumentedprocess-errors/* + - name: Upload utbot-framework logs + if: ${{ always() && github.event.inputs.project-name == 'utbot-framework' }} + uses: actions/upload-artifact@v3 with: - name: utbot_framework_logs + name: utbot-framework-logs path: utbot-framework/logs/* diff --git a/.gitignore b/.gitignore index 7db9331a69..5b11d761cb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ target/ .idea/ .gradle/ *.log +*.rdgen +utbot-intellij/src/main/resources/settings.properties +__pycache__ +.dmypy.json diff --git a/.run/Debug All.run.xml b/.run/Debug All.run.xml new file mode 100644 index 0000000000..a0cf8bd64d --- /dev/null +++ b/.run/Debug All.run.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.run/Debug Engine Process.run.xml b/.run/Debug Engine Process.run.xml new file mode 100644 index 0000000000..6a2c301a76 --- /dev/null +++ b/.run/Debug Engine Process.run.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.run/Debug Instrumented Process.run.xml b/.run/Debug Instrumented Process.run.xml new file mode 100644 index 0000000000..b1c4187629 --- /dev/null +++ b/.run/Debug Instrumented Process.run.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.run/Debug Spring Analyzer Process.run.xml b/.run/Debug Spring Analyzer Process.run.xml new file mode 100644 index 0000000000..91c80ebf73 --- /dev/null +++ b/.run/Debug Spring Analyzer Process.run.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.run/Listen for Engine Process.run.xml b/.run/Listen for Engine Process.run.xml new file mode 100644 index 0000000000..292a84b296 --- /dev/null +++ b/.run/Listen for Engine Process.run.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.run/Listen for Instrumented Process.run.xml b/.run/Listen for Instrumented Process.run.xml new file mode 100644 index 0000000000..d3ef5650cf --- /dev/null +++ b/.run/Listen for Instrumented Process.run.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.run/Listen for Spring Analyzer Process.run.xml b/.run/Listen for Spring Analyzer Process.run.xml new file mode 100644 index 0000000000..7b9505048f --- /dev/null +++ b/.run/Listen for Spring Analyzer Process.run.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.run/Run IDEA.run.xml b/.run/Run IDEA.run.xml new file mode 100644 index 0000000000..4c6116ceb2 --- /dev/null +++ b/.run/Run IDEA.run.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.run/Run Rider.run.xml b/.run/Run Rider.run.xml new file mode 100644 index 0000000000..2ab584d596 --- /dev/null +++ b/.run/Run Rider.run.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2a95592c8f..9ef2a4bb5f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,36 +1,117 @@ -# How to contribute to UTBot Java +# UnitTestBot contributing guide -To begin with, we are very thankful for your time and willingness to read this and contribute! +--- -The following guideline should help you with suggesting changes to our project, so feel free to use it for your contribution. 😃 +## Welcome! +Hello and thanks for reading this! -## I\`ve found a bug! How to report? +As the UnitTestBot core team we develop tools for automated unit test generation to help programmers test their code +in a more effective way with less effort. We believe that software development is great fun when you spend your time +finding creative solutions and automate the things you already know. If you are curious about making test generation +fast, smart and reliable, we are happy to invite you for contributing! -First of all, please check our [Issues](https://github.com/UnitTestBot/UTBotJava/issues) — this bug may have already been reported, and you just don\`t need to spend your time on a new one. +We welcome absolutely everyone. With one big and kind request: please follow these guidelines to make our communication smooth and to keep UnitTestBot improving. -If you haven\`t found the relevant issue, don\`t hesitate to [create a new one](https://github.com/UnitTestBot/UTBotJava/issues/new?assignees=&labels=&template=bug_report.md&title=), including as much detail as possible — the pre-made template will assist you in it. +## Contributions we are looking for -In case you already have a PR with a solution, please remain so amazing and link it with the created issue. +There are so many ways to contribute. Choose yours and find the relevant short guide below. +| (1) Choose what you like and check the guideline: | (2) Contribute: | +|-------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| +| [Reporting a bug](#Reporting-a-bug) | Create a [bug reporting issue](https://github.com/UnitTestBot/UTBotJava/issues/new?assignees=&labels=&template=bug_report.md&title=) | +| [Requesting a feature](#Requesting-a-feature) | Create a [feature suggestion issue](https://github.com/UnitTestBot/UTBotJava/issues/new?assignees=&labels=&template=feature_request.md&title=) | +| [Contributing the code (bug fix or feature implementation)](#Contributing-the-code-(bug-fix-or-feature-implementation)) | Create a pull request | +| [Reproducing a reported bug](#Reproducing-a-reported-bug) | Comment on the issue | +| [Testing a pull request](#Testing-a-pull-request) | Comment on the pull request | +| [Improving documentation](#Improving-documentation) | Create an issue
Create a pull request
Comment on issues and PRs | +| [Sharing ideas](#Sharing-ideas) | Start the [Discussion](https://github.com/UnitTestBot/UTBotJava/discussions) or join the existing one | -## I have an improvement suggestion! -Want a new feature or to change the existing one? We are very welcome your fresh ideas. 😃 +# How to submit a contribution -Please [create an issue](https://github.com/UnitTestBot/UTBotJava/issues/new?assignees=&labels=&template=feature_request.md&title=) with your proposal and describe your idea with full information about it. By adding some examples you also bring much happiness to our souls! +## Reporting a bug -Give us some time to review your proposal and provide you with our feedback. It will be decided who is preparing the pull request: we may need your help or we will take care of it all. 🙂 +1. Check if the bug (a true bug!) has already been reported: search through [UnitTestBot issues](https://github.com/UnitTestBot/UTBotJava/issues). Please, don't duplicate. +2. Check with the [Naming and labeling conventions](docs/contributing/Conventions.md). +3. Make sure you have all the necessary information as per [template](https://github.com/UnitTestBot/UTBotJava/issues/new?assignees=&labels=&template=bug_report.md&title=) and create the bug reporting issue. +## Requesting a feature -## Coding conventions -Our team adheres to the defined requirements to coding style to optimize for readability. You can take a look on this [Coding style guide](https://github.com/saveourtool/diktat/blob/master/info/guide/diktat-coding-convention.md) to better understand what we expect to see in your code. +1. Check if the feature has already been requested: search through [UnitTestBot issues](https://github.com/UnitTestBot/UTBotJava/issues). +2. Check with our [Naming and labeling conventions](docs/contributing/Conventions.md). +3. Make sure you are able to provide all the necessary information as per [template](https://github.com/UnitTestBot/UTBotJava/issues/new?assignees=&labels=&template=feature_request.md&title=) and create the feature request issue. +## Contributing the code (bug fix or feature implementation) -## How to setup development environment? +### "Good first issue" -Please refer [Developer guide](https://github.com/UnitTestBot/UTBotJava/blob/main/DEVNOTE.md) to setup developer environment, build and run UTBot. +If you have little experience in contributing, try to resolve the issues labeled as the ["good first"](https://github.com/UnitTestBot/UTBotJava/contribute) ones. +### Small or "obvious" fixes -## How to test you PR? +Do you need to create an issue if you want to fix a bug? -Currently, not all checks are automized. It's required to do manual testing after PR. +* Quick fix → no issue → pull request. +* Takes time to fix → detailed issue → pull request. + +### General flow for contributing code + +1. Make sure you've carefully read the [Legal notes](#Legal-notes)! +2. Create your own fork of the code. +3. Clone the forked repository to your local machine. +4. Implement changes. Please refer to the [Developer guide](DEVNOTE.md) for **system requirements**, **code + style** and + **steps for building the project**. +5. Test your code: + * Please, provide regression or integration tests for your code changes. If you don't do that, the reviewer can and highly likely **_will reject_** the PR. It is the contributor's responsibility to provide such tests or to reason why they are missing. + * When implementing something new, it's great to find real users and ask them to try out your feature — to prove + the necessity and quality of your suggestion. +6. Check with the [Naming and labeling conventions](docs/contributing/Conventions.md). +7. Create the pull request, and you'll see if the automated tests pass on GitHub. Your reviewer will possibly recommend + you more tests. + +## Reproducing a reported bug + +If you reproduce an existing issue and it helps to get some additional context on the problem, feel free to comment on the issue. + +## Testing a pull request + +You can merge a pull request into your local copy of the project and test the changes. If you find something you'd like to share, add the outcome of your testing in a comment on the pull request. + +## Improving documentation + +Here at UnitTestBot we regard documentation as code. It means that the general flow for writing and reviewing docs +is the same as for the code. If you'd like to improve the existing docs or to add something new, please follow the flow: + +1. Make sure you've carefully read the [Legal notes](#Legal-notes)! +2. Create your own fork of the code. +3. Clone the forked repository to your local machine. +4. Implement changes to docs (change the existing doc or create a new one). Usually, we create a new doc for a new feature, not for the small fixes. You are not obliged to write a detailed text about the feature you implement. You have to only describe it properly in both the related issue and the pull request, but it will be great if you still provide some docs. +6. Check with the [Naming and labeling conventions](docs/contributing/Conventions.md). +7. Create the pull request, and we'll review it. + +* You can request a new doc — create an issue, using the [guide for a feature request](#Requesting-a-feature). +* You can comment on the docs-related issues or PRs. + +## Sharing ideas + +We have a lot of new ideas, but we always need more! + +These are our main areas of interest: + +* technologies for code analysis, generating unit tests, e. g. symbolic execution, fuzzing, machine learning, etc.; +* real-life examples of using UnitTestBot, as well as possible use cases, scenarios and user stories; +* practices and problems or UX research related to unit testing with or without automated test generation tools. + +If you are keen on these things too, please share your ideas with us. Even if they are sketchy and not ready for being implemented or even requested right now, go ahead and join the existing [Discussions](https://github.com/UnitTestBot/UTBotJava/discussions) or [start](https://github.com/UnitTestBot/UTBotJava/discussions/new) the new one. + +# Code review process +Please choose [denis-fokin](https://github.com/denis-fokin) as a reviewer. He will reassign your PR to someone else from the core team, if necessary. + +We do our best in reviewing, but we can hardly specify the exact timeout for it. Be sure that we'll certainly answer your pull request! + +# Legal notes + +By contributing, you agree that your contributions will be licensed under the [Apache License 2.0](https://github.com/UnitTestBot/UTBotJava/blob/main/LICENSE). + +Feel free to [contact us directly](https://www.utbot.org/about) if that's a concern. \ No newline at end of file diff --git a/COPYRIGHT_HEADER.txt b/COPYRIGHT_HEADER.txt new file mode 100644 index 0000000000..d405aac34e --- /dev/null +++ b/COPYRIGHT_HEADER.txt @@ -0,0 +1,15 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ \ No newline at end of file diff --git a/DEVNOTE.md b/DEVNOTE.md index 3fe8ffb9da..a47be23490 100644 --- a/DEVNOTE.md +++ b/DEVNOTE.md @@ -1,49 +1,24 @@ -# UTBot Java Developer Guide - - Here are the steps for you to jump into UTBot Java. - -## Install UTBot Java from source -1. Clone UTBot Java repository via [Git](https://github.com/UnitTestBot/UTBotJava.git) -2. Open project in IDE +# UnitTestBot developer guide -![image](https://user-images.githubusercontent.com/106974353/174806216-9d4969b4-51fb-4531-a6d0-94e3734a437a.png) +--- -⚠️ Important don\`t forgets at this step: +When you have the forked repository on your local machine, you are almost ready to build your own version of UnitTestBot. -✔️ check your Project SDK and Gradle SDK are of 1.8 Java version. +💡 Before you start coding, please check the [system requirements](https://github.com/UnitTestBot/UTBotJava/wiki/Check-system-requirements) and find instructions on +configuring development environment. -![image](https://user-images.githubusercontent.com/106974353/174812758-fcbabb5b-0411-48d7-aefe-6d69873185e3.png) -![image](https://user-images.githubusercontent.com/106974353/174806632-ed796fb7-57dd-44b5-b499-e9eeb0436f15.png) +💡 Get to know the [code style](https://github.com/saveourtool/diktat/blob/master/info/guide/diktat-coding-convention.md) we are used to. -✔️ check your System environment variables: the KOTLIN_HOME variable path should be set up +## How to build UnitTestBot with your improvements -![image](https://user-images.githubusercontent.com/106974353/175059333-4f3b0083-7964-4886-8fcd-48c475fc1fb3.png) +The project structure is mostly straightforward and the modules are self-explanatory, e.g.: +* ```utbot-framework``` — everything related to UnitTestBot engine (including tests); +* ```utbot-intellij``` — IDE plugin implementation; +* ```utbot-sample``` — a framework with examples to demonstrate engine capacity. -3. Open Gradle tool window -4. Launch Task utbot > Tasks > build > assemble - -![image](https://user-images.githubusercontent.com/106974353/174807962-18c648fd-b67d-4556-90df-eee690abe6e2.png) - -5. Wait for the build to be completed - -Done! You\`re awesome and ready for digging the code. 😃 - - -## Development of UTBot Java with IntelliJ IDEA - -The majority of the code is written in Kotlin. - -The project is divided into Gradle subprojects. The most significant of them are: -1. utbot-framework — all about the engine and tests for it - -2. utbot-intellij — IDE plugin - -3. utbot-sample — a framework with examples to demonstrate engine capacity - -## Testing - -The project contains many tests. They are usually placed in test root of the particular Gradle subproject. - -While developing, it\`s useful to run tests from utbot-framework subproject. The majority of tests from this subproject generate tests for samples from the utbot-sample subproject. +Learn UnitTestBot from inside and implement changes. To verify your improvements open Gradle tool window in IntelliJ IDEA: +* to _run/debug plugin_ in IntelliJ IDEA choose and run the task: **utbot → utbot-intellij → intellij → runIde**; +* to _compile plugin_ choose and run the task: **utbot → utbot-intellij → intellij → buildPlugin**. The resulting ZIP + file is located at ```utbot-intellij/build/distributions```. \ No newline at end of file diff --git a/HowToUseLoggers.md b/HowToUseLoggers.md index 0988afdf02..fdb3da4bed 100644 --- a/HowToUseLoggers.md +++ b/HowToUseLoggers.md @@ -18,7 +18,7 @@ The file is usually in the resource folder. The easiest way is: -- Go in the code that you are going to debug. Let’s assume it is a method in org.utbot.framework.plugin.api.UtBotTestCaseGenerator. +- Go in the code that you are going to debug. Let’s assume it is a method in org.utbot.framework.plugin.api.TestCaseGenerator. - Find out if there is a KotlinLogging object that is used to create a **logger** - If such a logger exists, use the fully qualified class name as the logger name in the next steps
@@ -28,7 +28,7 @@ The easiest way is: Open log4j2.xml and add the logger in the loggers section like this ``` - + ``` diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000000..15230c55f1 --- /dev/null +++ b/NOTICE @@ -0,0 +1,2 @@ +UnitTestBot +Copyright 2022 UnitTestBot contributors (utbot.org) \ No newline at end of file diff --git a/README.md b/README.md index 53ff4ab039..405c00028b 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,109 @@ -[![UTBot Java: build and run tests](https://github.com/UnitTestBot/UTBotJava/actions/workflows/build-and-run-tests.yml/badge.svg)](https://github.com/UnitTestBot/UTBotJava/actions/workflows/build-and-run-tests.yml) +[![UnitTestBot: build and run tests](https://github.com/UnitTestBot/UTBotJava/actions/workflows/build-and-run-tests.yml/badge.svg)](https://github.com/UnitTestBot/UTBotJava/actions/workflows/build-and-run-tests.yml) [![Plugin and CLI: publish as archives](https://github.com/UnitTestBot/UTBotJava/actions/workflows/publish-plugin-and-cli.yml/badge.svg)](https://github.com/UnitTestBot/UTBotJava/actions/workflows/publish-plugin-and-cli.yml) -# What is UTBotJava? +👉 Find UnitTestBot on [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/19445-unittestbot). -UTBotJava generates test cases by code, trying to cover maximum statements and execution paths. We treat source code as source of truth assuming that behavior is correct and corresponds to initial user demand. Generated tests are placed in so-called regression suite. Thus, we fixate current behavior by generated test cases. Using UTBotJava developers obtain full control of their code. No future change can break the code without being noticed once it's covered with tests generated by UTBot. This way, modifications made by developers to an existing code are much safer. Hence, with the help of generated unit tests, UTBot provides dramatic code quality improvement. +👉 Visit the [official UnitTestBot website](https://www.utbot.org/). -# UTBot Java IntelliJ IDEA plugin +# What is UnitTestBot? -UTBot Java provides users with **IntelliJ IDEA** plugin. +UnitTestBot is the tool for **automated unit test generation** and **precise code analysis**. It produces ready-to-use +test +cases for +Java — with +valid inputs and comments. It can even predict whether the tests fail or pass. You can analyze them, run them, show coverage — as if you've created them personally. -_The plugin was tested on **Win64**, **Linux64** and **MacOSx86_64**._ +The **symbolic execution engine** paired with a **smart fuzzing technique** constitutes the core of UnitTestBot. It helps to **find errors** and **prevent regressions** in the code in a much more efficient way — UnitTestBot **maximizes path coverage** while **minimizing the number of tests and false positives**. -# How to download IntelliJ IDEA plugin +UnitTestBot represents all the test summaries in a **human-readable format**. The intelligible test method names and comments help you to control the whole testing process. Test failed? The summary refers you to the related branch or the condition under test. -You can download the plugin from [GitHub Releases](https://github.com/UnitTestBot/UTBotJava/releases). +# Get started -# How to install IntelliJ IDEA plugin +Try the **[online demo](https://www.utbot.org/demo)** to generate unit tests with one click. -See [step-by-step guide](https://github.com/UnitTestBot/UTBotJava/wiki/intellij-idea-plugin) explaining how to install the plugin. +Get to know the **full version** of UnitTestBot plugin with this quick guide: -# How to use IntelliJ IDEA plugin +
+ Install UnitTestBot plugin for IntelliJ IDEA -See [step-by-step guide](https://github.com/UnitTestBot/UTBotJava/wiki/generate-tests-with-plugin) explaining how to use the plugin. +Try the most straightforward path to start using UnitTestBot plugin. +1. Please check the [system requirements](https://github.com/UnitTestBot/UTBotJava/wiki/Check-system-requirements). +2. Open your IntelliJ IDEA. +3. Go to **File > Settings... > Plugins > Marketplace**. +4. In the search field type *UnitTestBot* — you'll see the UnitTestBot plugin page. +5. Press the **Install** button and wait until it changes to **Installed**, then click **OK**. -# How to contribute to UTBot Java +Now you can find the UnitTestBot plugin enabled in the **File > Settings > Plugins** window. -See [**Contributing guidelines**](CONTRIBUTING.md). +Do you want to manually choose the build or to update the plugin? Please refer to [Install or update plugin](https://github.com/UnitTestBot/UTBotJava/wiki/Install-or-update-plugin) in our user guide. + +____________ +
+ +
+ Generate tests with default configuration + +Proceed to generating unit tests for the existing Java project. If you don't have one, create it using the [JetBrains tutorial](https://www.jetbrains.com/help/idea/creating-and-running-your-first-java-application.html). + +1. Open your Java project in IntelliJ IDEA. +2. Right-click the required package or a file in the Project tool window, scroll the menu down to the bottom and + choose **Generate Tests with UnitTestBot...** +3. In the **Generate Tests with UnitTestBot** window tick the classes or methods you'd like to cover with unit tests and + press **Generate Tests** or **Generate and Run**. + +Now you can see the resulting test class or classes in the Editor tool window. + +Need to configure testing framework, mocking strategy or parameterization? Please check all [configuration options](https://github.com/UnitTestBot/UTBotJava/wiki/Fine-tune-test-generation). + +____________ +
+ +
+ Make use of generated tests + +What can you do with the output? + +1. To *find and fix the errors* in your code: + +* Run the generated tests: right-click the test class or a folder with tests and choose **Run**. + +* In the Run tool window you can see the tests failed with the brief failure explanation. + +* Fix your errors if needed. + +2. To *prevent regressions*: + +* Having your errors fixed, run the tests again. "Passed"? Commit them as the regression suite. + +* Introduce changes in the code and run your tests as often as needed! + +* Tests failed? Decide whether it is a bug or a feature and generate new tests if necessary. + +3. To *view coverage*: + +Right-click the test class, choose **More Run/Debug > Run ... with Coverage**. + +Want to know more about test descriptions or SARIF reports? Please learn how to [Get use of test results](https://github.com/UnitTestBot/UTBotJava/wiki/Get-use-of-test-results). + +____________ +
+ +# Contribute to UnitTestBot + +UnitTestBot is an open source project. We welcome everyone who wants to make UnitTestBot better — introduce a new feature or report a bug. We have only one kind request for our contributors: we expect you to prove the necessity and quality of the suggested changes. + +How can you do this? Refer to our [Contributing guide](https://github.com/UnitTestBot/UTBotJava/blob/main/CONTRIBUTING.md). + +Feel free to join the [Discussions](https://github.com/UnitTestBot/UTBotJava/discussions)! + +And thank you for your time and effort! ⭐ + +# Find support + +Having troubles with using UnitTestBot? Contact us [directly](https://www.utbot.org/about). + +# Find more UnitTestBot products + +You can also try [UnitTestBot](https://github.com/UnitTestBot/UTBotCpp) developed especially for C/C++. + +You are welcome to [contribute](https://github.com/UnitTestBot/UTBotCpp/blob/main/CONTRIBUTING.md) to it too! diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 02ef9119b3..0000000000 --- a/build.gradle +++ /dev/null @@ -1,67 +0,0 @@ -group 'org.utbot' - -apply plugin: 'java' - -if (project.hasProperty('semVer')) { - project.version = project.semVer -} else { - project.version = '1.0-SNAPSHOT' -} - -buildscript { - repositories { - mavenCentral() - } - - dependencies { - classpath group: 'org.jetbrains.kotlin', name: 'kotlin-gradle-plugin', version: kotlin_version - classpath group: 'org.jetbrains.kotlin', name: 'kotlin-allopen', version: kotlin_version - } -} - -subprojects { - group = rootProject.group - version = rootProject.version - - apply plugin: 'base' - apply plugin: 'java' - apply plugin: 'maven-publish' - - publishing { - publications { - jar(MavenPublication) { - from components.java - groupId 'org.utbot' - artifactId project.name - } - } - } - - repositories { - mavenCentral() - maven { url 'https://jitpack.io' } - } -} - -configure([ - project(':utbot-api'), - project(':utbot-core'), - project(':utbot-framework'), - project(':utbot-framework-api'), - project(':utbot-fuzzers'), - project(':utbot-instrumentation'), - project(':utbot-summary') -]) { - publishing { - repositories { - maven { - name = "GitHubPackages" - url = "https://maven.pkg.github.com/UnitTestBot/UTBotJava" - credentials { - username = System.getenv("GITHUB_ACTOR") - password = System.getenv("GITHUB_TOKEN") - } - } - } - } -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000000..823b6c22ba --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,211 @@ +import java.text.SimpleDateFormat +import org.gradle.api.JavaVersion.* +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +group = "org.utbot" + +val kotlinVersion: String by project +val semVer: String? by project +val coroutinesVersion: String by project +val collectionsVersion: String by project +val junit5Version: String by project +val dateBasedVersion: String = SimpleDateFormat("YYYY.MM").format(System.currentTimeMillis()) // CI proceeds the same way + +version = semVer ?: "$dateBasedVersion-SNAPSHOT" + +plugins { + `java-library` + kotlin("jvm") version "1.8.0" + `maven-publish` +} + +allprojects { + + apply { + plugin("maven-publish") + plugin("kotlin") + } + + tasks { + withType { + sourceCompatibility = "1.8" + targetCompatibility = "1.8" + options.encoding = "UTF-8" + options.compilerArgs = options.compilerArgs + "-Xlint:all" + } + withType { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class", "-Xcontext-receivers") + allWarningsAsErrors = false + } + } + compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class", "-Xcontext-receivers") + allWarningsAsErrors = false + } + } + withType { + // uncomment if you want to see loggers output in console + // this is useful if you debug in docker + // testLogging.showStandardStreams = true + // testLogging.showStackTraces = true + + // set heap size for the test JVM(s) + minHeapSize = "128m" + maxHeapSize = "3072m" + jvmArgs = listOf( + "-XX:MaxHeapSize=3072m", + "--add-opens", "java.base/java.util.concurrent.atomic=ALL-UNNAMED", + "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED", + "--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED", + "--add-opens", "java.base/java.util.concurrent.locks=ALL-UNNAMED", + "--add-opens", "java.base/java.text=ALL-UNNAMED", + "--add-opens", "java.base/java.time=ALL-UNNAMED", + "--add-opens", "java.base/java.io=ALL-UNNAMED", + "--add-opens", "java.base/java.nio=ALL-UNNAMED", + "--add-opens", "java.base/java.nio.file=ALL-UNNAMED", + "--add-opens", "java.base/java.net=ALL-UNNAMED", + "--add-opens", "java.base/sun.security.util=ALL-UNNAMED", + "--add-opens", "java.base/sun.reflect.generics.repository=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.util=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.fs=ALL-UNNAMED", + "--add-opens", "java.base/java.security=ALL-UNNAMED", + "--add-opens", "java.base/java.lang.ref=ALL-UNNAMED", + "--add-opens", "java.base/java.math=ALL-UNNAMED", + "--add-opens", "java.base/java.util.stream=ALL-UNNAMED", + "--add-opens", "java.base/java.util=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED", + "--add-opens", "java.base/java.lang=ALL-UNNAMED", + "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", + "--add-opens", "java.base/sun.security.provider=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.event=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.jimage=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.jimage.decompressor=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.jmod=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.jtrfs=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.loader=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.logger=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.math=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.module=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.objectweb.asm.commons=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.objectweb.asm.signature=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.objectweb.asm.tree.analysis=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.objectweb.asm.util=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.xml.sax=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.xml.sax.helpers=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.perf=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.platform=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.ref=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.reflect=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.util=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.util.jar=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.util.xml=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.util.xml.impl=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.vm=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED" + ) + + withType { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } + + useJUnitPlatform { + excludeTags = setOf("slow", "IntegrationTest") + } + + addTestListener(object : TestListener { + override fun beforeSuite(suite: TestDescriptor) {} + override fun beforeTest(testDescriptor: TestDescriptor) {} + override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) { + println("[$testDescriptor.classDisplayName] [$testDescriptor.displayName]: $result.resultType, length - ${(result.endTime - result.startTime) / 1000.0} sec") + if (result.resultType == TestResult.ResultType.FAILURE) { + println("Exception: " + result.exception?.stackTraceToString()) + } + } + + override fun afterSuite(testDescriptor: TestDescriptor, result: TestResult) { + if (testDescriptor.parent == null) { // will match the outermost suite + println("Test summary: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)") + } + } + }) + } + } + + repositories { + mavenCentral() + maven("https://jitpack.io") + maven("https://s01.oss.sonatype.org/content/repositories/orgunittestbotsoot-1004/") + maven("https://plugins.gradle.org/m2") + maven("https://www.jetbrains.com/intellij-repository/releases") + maven("https://cache-redirector.jetbrains.com/maven-central") + } + + dependencies { + implementation(group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version = coroutinesVersion) + implementation( + group = "org.jetbrains.kotlinx", + name = "kotlinx-collections-immutable-jvm", + version = collectionsVersion + ) + implementation(group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version = kotlinVersion) + implementation(group = "org.jetbrains.kotlin", name = "kotlin-reflect", version = kotlinVersion) + + testImplementation("org.junit.jupiter:junit-jupiter") { + version { + strictly(junit5Version) + } + } + } +} + +subprojects { + group = rootProject.group + version = rootProject.version + + publishing { + publications { + create("jar") { + from(components["java"]) + groupId = "org.utbot" + artifactId = project.name + } + } + } +} + +dependencies { + implementation(group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version = kotlinVersion) + implementation(group = "org.jetbrains.kotlin", name = "kotlin-allopen", version = kotlinVersion) +} + +configure( + listOf( + project(":utbot-api"), + project(":utbot-core"), + project(":utbot-framework"), + project(":utbot-framework-api"), + project(":utbot-java-fuzzing"), + project(":utbot-instrumentation"), + project(":utbot-rd"), + project(":utbot-summary") + ) +) { + publishing { + repositories { + maven { + name = "GitHubPackages" + url = uri("https://maven.pkg.github.com/UnitTestBot/UTBotJava") + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } + } +} diff --git a/buildSrc/src/main/java/SettingsTemplateHelper.java b/buildSrc/src/main/java/SettingsTemplateHelper.java new file mode 100644 index 0000000000..d55390eaac --- /dev/null +++ b/buildSrc/src/main/java/SettingsTemplateHelper.java @@ -0,0 +1,197 @@ +import org.gradle.api.*; + +import java.io.*; +import java.text.*; +import java.util.*; +import java.util.function.*; + +/** + * The purpose of this helper is to convert UtSettings.kt source code + * to template resource file settings.properties (top-level entry in plugin JAR file). + * There are two stages: parsing of input to build models and then rendering models to output file + */ + +public class SettingsTemplateHelper { + private static final String[] apacheLines = + ("Copyright (c) " + new SimpleDateFormat("yyyy").format(System.currentTimeMillis()) + " utbot.org\n" + + "\n" + + "Licensed under the Apache License, Version 2.0 (the \"License\");\n" + + "you may not use this file except in compliance with the License.\n" + + "You may obtain a copy of the License at\n" + + "\n" + + " http://www.apache.org/licenses/LICENSE-2.0\n" + + "\n" + + "Unless required by applicable law or agreed to in writing, software\n" + + "distributed under the License is distributed on an \"AS IS\" BASIS,\n" + + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + + "See the License for the specific language governing permissions and\n" + + "limitations under the License.").split("\n"); + + public static void proceed(Project project) { + File settingsSourceDir = new File(project.getBuildDir().getParentFile().getParentFile(), "utbot-framework-api/src/main/kotlin/org/utbot/framework/"); + String sourceFileName = "UtSettings.kt"; + File settingsResourceDir = new File(project.getBuildDir().getParentFile().getParentFile(), "utbot-intellij-main/src/main/resources/"); + String settingsFileName = "settings.properties"; + + Map dictionary = new HashMap<>(); + dictionary.put("Int.MAX_VALUE", String.valueOf(Integer.MAX_VALUE)); + + List models = new ArrayList<>(); + List enums = new ArrayList<>(); + StringBuilder acc = new StringBuilder(); + List docAcc = new ArrayList<>(); + // Stage one: parsing sourcecode + try(BufferedReader reader = new BufferedReader(new FileReader(new File(settingsSourceDir, sourceFileName)))) { + for (String s = reader.readLine(); s != null; s = reader.readLine()) { + s = s.trim(); + if (s.startsWith("enum class ")) {//Enum class declaration + enums.add(new EnumInfo(s.substring(11, s.length() - 2))); + } else if (s.matches("[A-Z_]+,?") && !enums.isEmpty()) {//Enum value + String enumValue = s.substring(0, s.length()); + if (enumValue.endsWith(",")) enumValue = enumValue.substring(0, enumValue.length() - 1); + enums.get(enums.size() - 1).docMap.put(enumValue, new ArrayList<>(docAcc)); + } else if (s.startsWith("const val ")) {//Constand value to be substitute later if need + int pos = s.indexOf(" = "); + dictionary.put(s.substring(10, pos), s.substring(pos + 3)); + } else if (s.equals("/**")) {//Start of docuemntation block + docAcc.clear(); + } else if (s.startsWith("* ")) { + if (!s.contains("href")) {//Links are not supported, skip them + docAcc.add(s.substring(2)); + } + } else if (s.startsWith("var") || s.startsWith("val")) {//Restart accumulation + acc.delete(0, acc.length()); + acc.append(s); + } else if (s.isEmpty() && acc.length() > 0) {//Build model from accumulated lines + s = acc.toString(); + acc.delete(0, acc.length()); + if (s.startsWith("var") || s.startsWith("val")) { + int i = s.indexOf(" by ", 3); + if (i > 0) { + String key = s.substring(3, i).trim(); + if (key.contains(":")) { + key = key.substring(0, key.lastIndexOf(':')); + } + PropertyModel model = new PropertyModel(key); + models.add(model); + s = s.substring(i + 7); + i = s.indexOf("Property"); + if (i > 0) model.type = s.substring(0, i); + if (i == 0) { + i = s.indexOf('<', i); + if (i != -1) { + s = s.substring(i+1); + i = s.indexOf('>'); + if (i != -1) { + model.type = s.substring(0, i); + } + } + } + + i = s.indexOf('(', i); + if (i > 0) { + s = s.substring(i + 1); + String defaultValue = s.substring(0, s.indexOf(')')).trim(); + if (defaultValue.contains(",")) defaultValue = defaultValue.substring(0, defaultValue.indexOf(',')); + defaultValue = dictionary.getOrDefault(defaultValue, defaultValue); + if (defaultValue.matches("[\\d_]+L")) { + defaultValue = defaultValue.substring(0, defaultValue.length() - 1).replace("_", ""); + } + if (defaultValue.matches("^\".+\"$")) { + defaultValue = defaultValue.substring(1, defaultValue.length() - 1); + } + model.defaultValue = defaultValue; + model.docLines.addAll(docAcc); + } + } + } + } else if (acc.length() > 0) { + acc.append(" " + s); + } + } + } catch (IOException ioe) { + System.err.println("Unexpected error when processing " + sourceFileName); + ioe.printStackTrace(); + } + + // Stage two: properties file rendering + try (PrintWriter writer = new PrintWriter(new File(settingsResourceDir, settingsFileName))) { + for (String apacheLine : apacheLines) { + writer.println("# " + apacheLine); + } + for (PropertyModel model : models) { + if (model.type.equals("Enum")) { + String[] split = model.defaultValue.split("\\."); + if (split.length > 1) { + model.defaultValue = split[1]; + EnumInfo enumInfo = enums.stream().filter(new Predicate() { + @Override + public boolean test(EnumInfo enumInfo) { + return enumInfo.className.equals(split[0]); + } + }).findFirst().orElse(null); + if (enumInfo != null) { + model.docLines.add(""); + for (Map.Entry> entry : enumInfo.docMap.entrySet()) { + String enumValue = entry.getKey(); + if (entry.getValue().size() == 1) { + model.docLines.add(enumValue + ": " + entry.getValue().get(0)); + } else { + model.docLines.add(enumValue); + for (String line : entry.getValue()) { + model.docLines.add(line); + } + } + } + } + } + } + writer.println(); + writer.println("#"); + for (String docLine : model.docLines) { + if (docLine.isEmpty()) { + writer.println("#"); + } else { + writer.println("# " + docLine); + } + } + boolean defaultIsAlreadyMentioned = model.docLines.stream().anyMatch(new Predicate() { + @Override + public boolean test(String s) { + return s.toLowerCase(Locale.ROOT).contains("default"); + } + }); + if (!defaultIsAlreadyMentioned) { + writer.println("#"); + writer.println("# Default value is [" + model.defaultValue + "]"); + } + writer.println("#" + model.key + "=" + model.defaultValue); + } + writer.flush(); + writer.close(); + } catch (IOException ioe) { + System.err.println("Unexpected error when saving " + settingsFileName); + ioe.printStackTrace(); + } + } + + private static class PropertyModel { + final String key; + String type = ""; + String defaultValue = ""; + List docLines = new ArrayList<>(); + + PropertyModel(String key) { + this.key = key; + } + } + + private static class EnumInfo { + final String className; + Map> docMap = new LinkedHashMap<>(); + + public EnumInfo(String className) { + this.className = className; + } + } +} \ No newline at end of file diff --git a/docker/Dockerfile_java_cli b/docker/Dockerfile_java_cli deleted file mode 100644 index 35c9536c62..0000000000 --- a/docker/Dockerfile_java_cli +++ /dev/null @@ -1,26 +0,0 @@ -FROM openjdk:8 - -ARG ACCESS_TOKEN - -WORKDIR /usr/src/ - -RUN apt-get update \ - && apt-get install -y curl \ - unzip \ - python3 \ - python3-requests \ - && apt-get clean - -# Install UTBot Java CLI -COPY docker/get_java_cli_download_url.py . - -ENV JAVA_CLI_ZIP_NAME "utbot_java_cli.zip" - -RUN curl -H "Authorization: Bearer ${ACCESS_TOKEN}" \ - -L "$(python3 get_java_cli_download_url.py)" \ - -o "${JAVA_CLI_ZIP_NAME}" \ - && unzip "${JAVA_CLI_ZIP_NAME}" \ - && rm "${JAVA_CLI_ZIP_NAME}" - -ENV JAVA_CLI_PATH="$(find /usr/src -type f -name utbot-cli*)" -RUN ln -s "${JAVA_CLI_PATH}" $pwd/utbot-cli.jar diff --git a/docker/get_java_cli_download_url.py b/docker/get_java_cli_download_url.py deleted file mode 100644 index ad2f6b03a5..0000000000 --- a/docker/get_java_cli_download_url.py +++ /dev/null @@ -1,14 +0,0 @@ -import json -import requests - -JAVA_ARTIFACTS_URL="https://api.github.com/repos/UnitTestBot/UTBotJava/actions/artifacts" - -request = requests.get(url = JAVA_ARTIFACTS_URL) -data = request.json() -artifacts = data['artifacts'] - -for artifact in artifacts: - if "utbot-cli" in artifact['name']: - print(artifact['archive_download_url']) - break - diff --git a/docs/AndroidStudioSupport.md b/docs/AndroidStudioSupport.md index 7049d52571..6feabebca2 100644 --- a/docs/AndroidStudioSupport.md +++ b/docs/AndroidStudioSupport.md @@ -1,28 +1,47 @@ # Android Studio support -## Installing AS +## Installing Android Studio -> Install latest AS +> Install the latest version of Android Studio +> * ### Installing Lombok plugin -> Use the first advice from the following link +> Lombok plugin is not required to use UnitTest Bot. +> However, if this plugin is required for your own goals, do the following: > -> +> * go to https://plugins.jetbrains.com/plugin/6317-lombok/versions +> +> * download .zip with the latest version +> +> * unpack it to ~/android-studio/plugins (use your path to Android Studio) +> +> * restart IDE ## Prerequisites > Install and setup gradle version 7.2+ (version 7.4 tested) > -> Use JDK 8 for Gradle in\ +> Use JDK 11 for Gradle in\ > `File -> Settings -> Build, Execution, Deployment -> Build Tools -> Gradle -> Gradle JVM` +> +> \ +> If you want to use JDK 8, you can: +> 1. Generate tests with JDK 8 +> 2. Switch to JDK 11 and compile tests +> 3. Switch back to JDK 8 and run tests +> +> The reason for it is the Android Gradle Plugin, which requires Java 11 to build anything. -## Running in AS +## Running in Android Studio > For now, running Utbot is supported only for Kotlin libraries. You can > create one like this: > > +> +> To run generated tests, you must create separate JUnit configuration.\ +> ("Green arrows" will not work, since they launch Android Emulator.) > ## Debug Intellij code diff --git a/docs/ChoosingLanguageSpecificIDE.md b/docs/ChoosingLanguageSpecificIDE.md new file mode 100644 index 0000000000..6512898ecc --- /dev/null +++ b/docs/ChoosingLanguageSpecificIDE.md @@ -0,0 +1,23 @@ +# Choosing language specific IDE + +Some language-specific modules depends on specific IntelliJ IDE: +* Python can work with IntelliJ Community, IntelliJ Ultimate, PyCharm Community, PyCharm Professional +* JavaScript can work with IntelliJ Ultimate, PyCharm Professional and WebStorm +* Java and Kotlin - IntelliJ Community and IntelliJ Ultimate + +You should select correct IDE in `gradle.properties` file: +``` +ideType= +ideVersion=<222.4167.29> +``` + +### IDE marking + +| Mark | Full name | Supported plugin | +|------|----------------------|----------------------------------------| +| IC | IntelliJ Community | JVM, Python, AndroidStudio | +| IU | IntelliJ Ultimate | JVM, Python, JavaScript, AndroidStudio | +| PC | PyCharm Community | Python | +| PY | PyCharm Professional | Python, JavaScript | + +[IntelliJ Platform Plugin SDK documentation](https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#tasks-runpluginverifier) \ No newline at end of file diff --git a/docs/CodeGenerationAndRendering.md b/docs/CodeGenerationAndRendering.md new file mode 100644 index 0000000000..eab4d56723 --- /dev/null +++ b/docs/CodeGenerationAndRendering.md @@ -0,0 +1,339 @@ +# Code generation and rendering + +Code generation and rendering are a part of the test generation process in UnitTestBot (find the overall picture in the +[UnitTestBot architecture +overview](https://github.com/UnitTestBot/UTBotJava/blob/main/docs/OverallArchitecture.md)). +UnitTestBot gets the synthetic representation of generated test cases from the fuzzer or the symbolic engine. +This representation (or model) is implemented in the `UtExecution` class. + +The `codegen` module generates the real test code based on this `UtExecution` model and renders it in a +human-readable form in accordance with the requested configuration (considering programming language, testing +framework, mocking and parameterization options). + +The `codegen` module +- converts `UtExecution` test information into an Abstract Syntax Tree (AST) representation using `CodeGenerator`, +- renders this AST according to the requested programming language and other configurations using `renderer`. + +## Example + +Consider the following method under test: + +```java +package pack; + +public class Example { + + public int maxIfNotEquals(int a, int b) throws IllegalArgumentException { + if (a == b) throw new IllegalArgumentException("a == b"); + if (a > b) return a; else return b; + } +} +``` + +The standard UnitTestBot-generated tests for this method (without test summaries and clustering into regions) +look like this: + +```java +package pack; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public final class ExampleStandardTest { + + @Test + @DisplayName("maxIfNotEquals: a == b : False -> a > b") + public void testMaxIfNotEquals_AGreaterThanB() { + Example example = new Example(); + + int actual = example.maxIfNotEquals(1, 0); + + assertEquals(1, actual); + } + + @Test + @DisplayName("maxIfNotEquals: a == b -> ThrowIllegalArgumentException") + public void testMaxIfNotEquals_AEqualsB() { + Example example = new Example(); + + assertThrows(IllegalArgumentException.class, () -> example.maxIfNotEquals(-255, -255)); + } + + @Test + @DisplayName("maxIfNotEquals: a < 0, b > 0 -> return 1") + public void testMaxIfNotEqualsReturnsOne() { + Example example = new Example(); + + int actual = example.maxIfNotEquals(-1, 1); + + assertEquals(1, actual); + } +} +``` + +Here is an example of the parameterized tests for this method. We also implement the data provider method — the +argument source. + +```java +package pack; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +public final class ExampleParameterizedTest { + + @ParameterizedTest + @MethodSource("pack.ExampleTest#provideDataForMaxIfNotEquals") + public void parameterizedTestsForMaxIfNotEquals(Example example, int a, int b, Integer expectedResult, Class expectedError) { + try { + int actual = example.maxIfNotEquals(a, b); + + assertEquals(expectedResult, actual); + } catch (Throwable throwable) { + assertTrue(expectedError.isInstance(throwable)); + } + } + + public static ArrayList provideDataForMaxIfNotEquals() { + ArrayList argList = new ArrayList(); + + { + Example example = new Example(); + + Object[] testCaseObjects = new Object[5]; + testCaseObjects[0] = example; + testCaseObjects[1] = 1; + testCaseObjects[2] = 0; + testCaseObjects[3] = 1; + testCaseObjects[4] = null; + argList.add(arguments(testCaseObjects)); + } + { + Example example = new Example(); + + Object[] testCaseObjects = new Object[5]; + testCaseObjects[0] = example; + testCaseObjects[1] = -255; + testCaseObjects[2] = -128; + testCaseObjects[3] = -128; + testCaseObjects[4] = null; + argList.add(arguments(testCaseObjects)); + } + { + Example example = new Example(); + + Object[] testCaseObjects = new Object[5]; + testCaseObjects[0] = example; + testCaseObjects[1] = -255; + testCaseObjects[2] = -255; + testCaseObjects[3] = null; + testCaseObjects[4] = IllegalArgumentException.class; + argList.add(arguments(testCaseObjects)); + } + + return argList; + } +} +``` + +## Configurations + +UnitTestBot renders code in accordance with the chosen programming language, testing framework, +mocking and parameterization options. + +Supported languages for code generation are: +- Java +- Kotlin (experimental) — we have significant problems with the support for nullability and generics +- Python and JavaScript — in active development + +Supported testing frameworks are: +- JUnit 4 +- JUnit 5 +- TestNG (only for the projects with JDK 11 or later) + +Supported mocking options are: +- No mocking +- Mocking with Mockito framework +- Mocking static methods with Mockito + +Parameterized tests can be generated in Java only. Parameterization is not supported with the mocks enabled or +with JUnit 4 chosen as the testing framework. + +## Entry points + +The `codegen` module gets calls from various UnitTestBot components. The most common scenario is to call `codegen` +from integration tests as well as from the `utbot-intellij` project and its `CodeGenerationController` class. The +`utbot-online` and `utbot-cli` projects call `codegen` as well. + +The `codegen` entry points are: +- `CodeGenerator.generateAsString()` +- `CodeGenerator.generateAsStringWithTestReport()` + +The latter gets `UtExecution` information received from the symbolic engine or the fuzzer and converts it into the +`codegen`-related data units, each called `CgMethodTestSet`. As a result of further processing, the test code is +generated as a string with a test generation report (see [Reports](#Reports) for details). + +Previously, `CgMethodTestSet` has been considerably different from `UtMethodTestSet` as it has been using +`ExecutableId` instead of the legacy `UtMethod` (has been removed recently). +For now, `CgMethodTestSet` contains utility functions required for code generation, mostly for parameterized tests. + +## Abstract Syntax Tree (AST) + +The `codegen` module converts `UtExecution` information to the AST representation. +We create one AST per one source class (and one resulting test class). We use our own AST implementation. + +We generate a `UtUtils` class containing a set of utility functions, when they are necessary for a given test class. +If the `UtUtils` class has not been created previously, its AST representation is generated as well. To learn more +about the `UtUtils` class and how it is generated, refer to the +[design doc](https://github.com/UnitTestBot/UTBotJava/blob/main/docs/UtUtilsClass.md). + +All the AST elements are `CgElement` inheritors. +`CgClassFile` is the top level element — it contains `CgClass` with the required imports. + +The class has the body (`CgClassBody`) as well as minor properties declared: documentation comments, annotations, +superclasses, interfaces, etc. + +The class body is a set of `CgRegion` elements, having the `header` and the corresponding `content`, which is mostly +the set of `CgMethod` elements. + +The further AST levels are created similarly. The AST leaves are `CgLiteral`, `CgVariable`, +`CgLogicalOr`, `CgEmptyLine`, etc. + +## Test method + +The below-mentioned functionality is implemented in `CgMethodConstructor`. + +To create a test method: +* store the initial values of the static fields and perform the seven steps for creating test method body mentioned later; +* if the static field values undergo changes, perform these seven steps in the `try` block and recover these values in the `finally` block accordingly. + +To create test method body: +1. substitute static fields with local variables +2. set up instrumentation (get mocking information from `UtExecution`) +3. create a variable for the current instance +4. create variables for method-under-test arguments +5. record an actual result by calling method under test +6. generate result assertions +7. for successful tests, generate field state assertions + +_Note:_ generating assertions has pitfalls. In primitive cases, like comparing two integers, we can use the standard +assertions of a selected test framework. To compare two objects of an arbitrary type, we need a +custom implementation of equality assertion, e.g. using `deepEquals()`. The `deepEquals()` method compares object +structures field by field. The method is recursive: if the current field is not of the primitive type, we call +`deepEquals()` for this field. The maximum recursion depth is limited. + +For the parameterized tests +- we do not support mocking, so we do not set up the initial environment; +- we do not generate field state assertions. + +`UtExecution` usually represents a single test scenario, and one `UtExecution` instance is used to create a single +test method. Parameterized tests are supposed to cover several test scenarios, so several `UtExecution` instances +are used for generating test methods. + +## Generic execution + +Parameterization often helps to reveal similarities between test scenarios. The combined outcome is sometimes more +expressive. To represent these similarities, we construct generic executions. + +Generic execution is a synthetic execution, formed by a group of real executions, that have +- the same type of result, +- the same modified static fields. + +Otherwise, we create several generic executions and several parameterized tests. The logic of splitting executions +into the test sets is implemented in the `CgMethodTestSet` class. + +From the group of `UtExecution` elements, we take the first successful execution with the non-nullable result. See +`CgMethodConstructor.chooseGenericExecution()` for more details. + +## Renderer + +We have a general approach for rendering the code of test classes. `UtUtils` class is rendered differently: we +hardcode the required method implementations for the specific code generation language. + +All the renderers implement `CgVisitor` interface. It has a separate `visit()` method for each supported +`CgElement` item. + +There are three renderers: +- `CgAbstractRenderer` for elements that are similar in Kotlin and Java +- `CgJavaRenderer` for Java-specific elements +- `CgKotlinRenderer` for Kotlin-specific elements + +Each renderer method visits the current `CgElement`. It means that all the required instructions are printed properly. +If an element contains the other element, the first one delegates rendering to its _child_. + +`CgVisitor` refers us to the _Visitor_ design pattern. Delegating means asking the _child_ element to +accept the renderer and manage it. Then we go through the list of `CgElement` types to find the first +matching `visit()` method. + +_Note:_ the order of `CgElement` items listed in `CgElement.accept()` is important. + +Rendering may be terminated if the generated code is too long (e.g. due to test generation bugs). + +## Reports + +While constructing the test class, we create test generation reports. It contains basic statistical information: the +number of generated tests, the number of successful tests, etc. It also may contain information on potential problems +like trying to use mocks when mocking framework is not installed. + +The report is an HTML string with clickable links. + +_Note:_ no test generation reports are created for parameterized tests. + +## Services + +Services help the `codegen` module to produce human-readable test code. + +### Name generator + +With this service, we create names for variables and methods. It assures avoiding duplicates in names, +resolving conflicts with keywords, etc. It also adds suffixes if we process a mock or a static item. + +Name generator is called directly from `CgStatementConstructor`. + +_Note:_ if you need a new variable, you should better use this service (e.g. the `newVar()` method) instead of calling +the `CgVariable` constructor manually. + +### Framework and language services + +Framework services help the `codegen` module to generate constructions (e.g. assertions) according to a given +testing or mocking framework. +Language services provide the `codegen` module with language-specific information on test class extensions, +prohibited keywords, etc. + +See the `Domain` file for more framework- and language-specific implementations. + +### CgFieldStateManager + +`CgFieldStateManager` stores the initial and the final environment states for the given method under test. +These states are used for generating assertions. Usually, the environment state consists of three parts: +* current instance state, +* argument state, +* static field state. + +All the state-related variables are marked as `INITIAL` or `FINAL`. + +### CgCallableAccessManager + +This service helps to validate access. For example, if the current argument +list is valid for the method under test, `CgCallableAccessManager` checks if one can call this method with these +arguments without using _Reflection_. + +`CgCallableAccessManager` analyzes callables as well as fields for accessibility. + +## CgContext + +`CgContext` contains context information for code generation. The `codegen` module uses one +context per one test class. `CgContext` also stores information about the scope for the inner context elements: e.g. when +they should be instantiated and cleared. For example, the context of the nested class is the part of the owner class context. + +`CgContext` is the so-called "God object" and should be split into independent storages and +helpers. This task seems to be difficult and is postponed for now. \ No newline at end of file diff --git a/docs/Fuzzing Platform.md b/docs/Fuzzing Platform.md new file mode 100644 index 0000000000..37b2314faa --- /dev/null +++ b/docs/Fuzzing Platform.md @@ -0,0 +1,246 @@ +# Fuzzing Platform (FP) Design + +**Problem:** fuzzing is a versatile technique for generating values to be used as method arguments. Normally, +to generate values, one needs information on a method signature, or rather on the parameter types (if a fuzzer is +able to "understand" them). +The _white-box_ approach also requires AST, and the _grey-box_ approach needs coverage +information. To generate values that may serve as method arguments, the fuzzer uses generators, mutators, and +predefined values. + +* _Generators_ yield concrete objects created by descriptions. The basic description for creating objects is _type_. + Constants, regular expressions, and other structured object specifications (e.g. in HTML) may also be used as + descriptions. + +* _Mutators_ modify the object in accordance with some logic that usually means random changes. To get better + results, mutators obtain feedback (information on coverage and the inner state of the + program) during method call. + +* _Predefined values_ work well for known problems, e.g. incorrect symbol sequences. To discover potential problems, one can analyze parameter names as well as the specific constructs or method calls inside the method body. + +The general API for using the fuzzer looks like this: + +``` +fuzz( + params = "number", "string", "object: number, string", + seedGenerator = (type: Type) -> seeds + details: (constants, providers, etc) +).forEveryGeneratedValues { values: List -> + feedback = exec(values); + return feedback +} +``` + +The fuzzer gets the list of types, +which can be provided in different formats: as a string, an object, or a Class<*> in Java. +The seed generator accepts these types and produces seeds. +The seeds are base objects for value generation and mutations. + +It is the fuzzer, which is responsible for choosing, combining and mutating values from the seed set. +The fuzzer API should not provide access to the inner fuzzing logic. +Only general configuration is available. + +## Parameters + +The general fuzzing process gets the list of parameter descriptions as input and returns the corresponding list of values. The simplest description is the specific object type, for example: + +```kotlin +[Int, Bool] +``` + +In this particular case, the fuzzing process can generate the set of all the pairs having integer as the first value +and `true` or `false` as the second one. +If values `-3, 0, 10` are generated to be the `Int` values, the set of all the possible combinations has six items: +`(-3, false), (0, false), (10, false), (-3, true), (0, true), (10, true)`. +Depending on the programming language, +one may use interface descriptions or annotations (type hints) instead of defining the specific type. +Fuzzing platform (FP) is not able to create the concrete objects as it does not deal with the specific languages. +It can still convert the descriptions to the known constructs it can work with. + +Say, in most of the programming languages, any integer may be represented as a bit array, and the fuzzer can construct and +modify bit arrays. So, in the general case, the boundary values for the integer are these bit arrays: + +* [0, 0, 0, ..., 0] — zero +* [1, 0, 0, ..., 0] — minimum value +* [0, 1, 1, ..., 1] — maximum value +* [0, 0, ..., 0, 1] — plus 1 +* [1, 1, 1, ..., 1] — minus 1 + +One can correctly use this representation for unsigned integers as well: + +* [0, 0, 0, ..., 0] — zero (minimum value) +* [1, 0, 0, ..., 0] — maximum value / 2 +* [0, 1, 1, ..., 1] — maximum value / 2 + 1 +* [0, 0, ..., 0, 1] — plus 1 +* [1, 1, 1, ..., 1] — maximum value + +Thus, FP interprets the _Byte_ and _Unsigned Byte_ descriptions in different ways: in the former case, +the maximum value is [0, 1, 1, 1, 1, 1, 1, 1], while in the latter case it is [1, 1, 1, 1, 1, 1, 1, 1]. +FP types are described in detail further. + +## Refined parameter description + +During the fuzzing process, some parameters get the refined description, for example: + +``` +public boolean isNaN(Number n) { + if (!(n instanceof Double)) { + return false; + } + return Double.isNaN((Double) n); +} +``` + +In the above example, let the parameter be `Integer`. Considering the feedback, the fuzzer suggests that nothing but `Double` might increase coverage, so the type may be downcasted to `Double`. This allows for filtering out a priori unfitting values. + +## Statically and dynamically generated values +Predefined, or _statically_ generated, values help to define the initial range of values, which could be used as method arguments. + +These values allow us to: +* check if it is possible to call the given method with at least some set of values as arguments; +* gather statistics on executing the program; +* refine the parameter description. + +_Dynamic_ values are generated in two ways: +* internally, via mutating the existing values, successfully performed as method arguments (i.e. seeds); +* externally, via obtaining feedback that can return not only the statistics on the execution (the paths explored, + the time spent, etc.) but also the set of new values to be blended with the values already in use. + +Dynamic values should have a higher priority for a sample; +that is why they should be chosen either first or at least more likely than the statically generated ones. +In general, the algorithm that guides the fuzzing process looks like this: + +``` +# dynamic values are stored with respect to their return priority +dynamic_values = empty_priority_queue() +# static values are generated beforehand +static_values = generate() +# "good" values +seeded_values = [] +# +filters = [] + +# the loop runs until coverage reaches 100% +while app.should_fuzz(seeded_values.feedbacks): + # first we choose all dynamic values + # if there are no dynamic values, choose the static ones + value = dynamic_values.take() ?: static_values.take_random() + # if there is no value or it was filtered out (static values are generated in advance — they can be random and unfitting), try to generate new values via mutating the seeds + if value is null or filters.is_filtered(value): + value = mutate(seeded_values.random_value()) + # if there is still no value at this point, it means that there are no appropriate values at all, and the process stops + if value is null: break + + # run with the given values and obtain feedback + feedback = yield_run(value) + # feedback says if it is reasonable to add the current value to the set of seeds + if feedback is good: + seeded_values[feedback] += value + # feedback may also provide fuzzer with the new values + if feedback has suggested_value: + dynamic_values += feedback.suggested_values() with high_priority + + # mutate the static value thus allowing fuzzer to alternate static and dynamic values + if value.is_static_generated: + dynamic_values += mutate(seeded_values.random_value()) with low_priority +``` + +## Helping fuzzer via code modification + +Sometimes it is reasonable to modify the source code so that it makes applying fuzzer to it easier. This is one of possible approaches: to split the complex _if_-statement into the sequence of simpler _if_-statements. See [Circumventing Fuzzing Roadblocks with Compiler Transformations](https://lafintel.wordpress.com/2016/08/15/circumventing-fuzzing-roadblocks-with-compiler-transformations/) for details. + +## Generators + +There are two types of generators: +* yielding values of primitive data types: integers, strings, booleans +* yielding values of recursive data types: objects, lists + +Sometimes it is necessary not only to create an object but to modify it as well. We can apply fuzzing to +the fuzzer-generated values that should be modified. For example, you have the `HashMap.java` class, and you need to +generate +three +modifications for it using `put(key, value)`. For this purpose, you may request for applying the fuzzer to six +parameters `(key, value, key, value, key, value)` and get the necessary modified values. + +Primitive type generators allow for yielding: +1. Signed integers of a given size (8, 16, 32, and 64 bits, usually) +2. Unsigned integers of a given size +3. Floating-point numbers with a given size of significand and exponent according to IEEE 754 +4. Booleans: _True_ and _False_ +5. Characters (in UTF-16 format) +6. Strings (consisting of UTF-16 characters) + +The fuzzer should be able to provide out-of-the-box support for these types — be able to create, modify, and process +them. +To work with multiple languages, it is enough to specify the possible type size and to describe and create +concrete objects based on the FP-generated values. + +The recursive types include two categories: +* Collections (arrays and lists) +* Objects + +Collections may be nested and have _n_ dimensions (one, two, three, or more). + +Collections may be: +* of a fixed size (e.g., arrays) +* of a variable size (e.g., lists and dictionaries) + +Objects may have: +1. Constructors with parameters +2. Modifiable inner fields +3. Modifiable global values (the static ones) +4. Calls for modifying methods + +FP should be able to create and describe such objects in the form of a tree. The semantics of actual modifications is under the responsibility of a programming language. + + +## Typing + +FP does not use the concept of _type_ for creating objects. Instead, FP introduces the _task_ concept — it +encapsulates the description of a type, which should be used to create an object. Generally, this task consists of two +blocks: the task for initializing values and the list of tasks for modifying the initialized value. + +``` +Task = [ + Initialization: [T1, T2, T3, ..., TN] + Modification(Initialization): [ + М1: [T1, T2, ..., TK], + М2: [T1, T2, ..., TJ], + МH: [T1, T2, ..., TI], + ] +] +``` + +Thus, we can group the tasks as follows: + +``` +1. Trivial task = [ + Initialization: [INT|UNSIGNED.INT|FLOAT.POINT.NUMBER|BOOLEAN|CHAR|STRING] + Modification(Initialization): [] +] + + +2. Task for creating an array = [ + Initialization: [UNSIGNED.INT] + Modification(UNSIGNED.INT) = [T] * UNSIGNED.INT +] + +or + +2. Task for creating an array = [ + Initialization: [UNSIGNED.INT] + Modification(UNSIGNED.INT) = [[T * UNSIGNED.INT]] +] + +where "*" means repeating the type the specified number of times + +3. Task for creating an object = [ + Initialization: [Т1, Т2, ... ТN], + Modification(UNSIGNED.INT) = [ + ... + ] +] + +``` + +Therefore, each programming language defines how to interpret a certain type and how to infer it. This allows fuzzer +to store and mutate complex objects without any additional support from the language. diff --git a/docs/GoSupport.md b/docs/GoSupport.md new file mode 100644 index 0000000000..10518a49a0 --- /dev/null +++ b/docs/GoSupport.md @@ -0,0 +1,145 @@ +# UnitTestBot Go + +UnitTestBot Go automatically generates ready-to-use unit tests for Go programs. + +With UnitTestBot Go, you can find bugs in your code and fixate the desired program behavior with less effort. + +The project is under development, +so feel free to [contribute](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-go/docs/DEVELOPER_GUIDE.md). + +## Features and details + +UnitTestBot Go now implements the _basic fuzzing technique_. +It generates input values considering the parameter types, +inserts these values into the user functions, and executes the resulting test cases. + +### Supported types for function parameters + +Now UnitTestBot Go can generate values for _primitive types_, _arrays_, _slices_, _maps_, _structs_, _channels_, _interfaces_, and _pointers_. + +For _floating point types_, UnitTestBot Go supports working with _infinity_ and _NaN_. + +### Supported types for the returned results + +For the returned function results, +UnitTestBot Go supports the `error` type as well as all the types supported for the function parameters except _interfaces_ and _channels_. + +It also captures `panic` cases correctly. + +Check the examples of [supported functions](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-go/go-samples/simple/samples.go). + +### Keeping tests near the source code + +[Testing code typically +lives in the same package as the code it tests](https://gobyexample.com/testing). +By default, UnitTestBot Go generates tests into the `[name of source file]_go_ut_test.go` file located in the same +directory and Go package as the corresponding source file. + +If you need to change the location for the generated tests, +use the `generateGo` CLI command and set the generated test output mode to +`StdOut` (`-p, --print-test` flag). +Then, with bash primitives, redirect the output to an arbitrary file. + +### Requirements to source code + +To simplify handling dependencies and generating tests, UnitTestBot Go requires the code under test _to +be a part of a Go project_ consisting of a module and packages. + +To create a simple project, refer to the [starter tutorial](https://go.dev/doc/tutorial/getting-started) if necessary. + +For larger projects, try the [Create a Go module](https://go.dev/doc/tutorial/create-module) +and [Call your code from another module](https://go.dev/doc/tutorial/call-module-code) tutorials. + +To create a new module rooted in the current directory, use the `go mod init` command. + +To add missing module requirements necessary to build the current module’s packages and dependencies, +use the `go mod tidy` command. For editing and formatting `go.mod` files, use the `go mod edit` command. + +In the future, we plan to make UnitTestBot Go working with arbitrary code as input and generate the simplest +Go projects automatically. + +## IntelliJ IDEA plugin + +### Requirements + +* IntelliJ IDEA Ultimate — for compatibility, see [UnitTestBot on JetBrains Marketplace](https://plugins.jetbrains.com/plugin/19445-unittestbot/versions). +* Go SDK 1.18 or later +* Compatible [Go plugin](https://plugins.jetbrains.com/plugin/9568-go) for IntelliJ IDEA +* Properly configured `go.mod` file for the code under test +* `github.com/stretchr/testify/assert` Go module installed (IntelliJ IDEA automatically offers to install it as soon as the tests are generated) + +### Installation + +Please refer to [UnitTestBot user guide](https://github.com/UnitTestBot/UTBotJava/wiki/Install-or-update-plugin). + +### Usage + +1. In your IntelliJ IDEA, go to **File** > **Settings** > **Tools**, choose **UnitTestBot** and enable **Experimental languages support**. + + **(!) NOTE:** be sure to enable this option for **_each_** project. + +2. Open a `.go` file and press **Alt+Shift+U**. +3. In the **Generate Tests with UnitTestBot** window, you can configure the settings. + +## Command-line interface (CLI) + +### Requirements + +* Java SDK 17 or later +* Go SDK 1.18 or later +* Properly configured `go.mod` file for the code under test +* GCC and `github.com/stretchr/testify/assert` installed + +### Installation + +To install the UnitTestBot Go CLI application, go to GitHub, scroll through the release notes and click **Assets**. +Download the zip-archive named like **utbot-cli-VERSION**. +Extract the JAR file from the archive. + +### Usage + +Run the extracted JAR file using a command line: `generateGo` and `runGo` actions are supported for now. +To find info about the options for these actions, +insert the necessary JAR file name instead of `utbot-cli-2022.8-beta.jar` in the example and run the following commands: + +```bash +java -jar utbot-cli-2022.8-beta.jar generateGo --help +``` +or +```bash +java -jar utbot-cli-2022.8-beta.jar runGo --help +``` + +`generateGo` options: + +* `-s, --source TEXT`, _required_: specifies a Go source file to generate tests for. +* `-f, --function TEXT`: specifies a function name to generate tests for. Can be used multiple times to select multiple functions. +* `-m, --method TEXT`: specifies a method name to generate tests for. Can be used multiple times to select multiple methods. +* `-go TEXT`, _required_: specifies a path to a Go executable. For example, `/usr/local/go/bin/go`. +* `-gopath TEXT`, _required_: specifies a path to your workspace location. It defaults to a directory named `go` in your home directory: `$HOME/go` for Unix, `$home/go` for Plan 9, and `%USERPROFILE%\go` (usually `C:\Users\YourName\go`) for Windows. +* `-parallel INT`: specifies the number of fuzzing processes running at once (eight by default). +* `-et, --each-execution-timeout INT`: specifies a timeout in milliseconds for each target function execution. + The default timeout is 1,000 ms. +* `-at, --all-execution-timeout INT`: specifies a timeout in milliseconds for all target function executions. + The default timeout is 60,000 ms. +* `-p, --print-test`: specifies whether a test should be printed out to `StdOut`. Disabled by default. +* `-w, --overwrite`: specifies whether to overwrite the output test file if it already exists. Disabled by default. +* `-fm, --fuzzing-mode`: stops test generation when a `panic` situation, an error, or timeout occurs (only one test will be generated for one of these cases). +* `-h, --help`: shows a help message and exits. + +`runGo` options: + +* `-p, --package TEXT`, _required_: specifies a Go package to run tests for. +* `-go, --go-path TEXT`, _required_: specifies a path to a Go executable. For example, `/usr/local/go/bin/go`. +* `-v, --verbose`: specifies whether an output should be verbose. Disabled by default. +* `-j, --json`: specifies whether an output should be in JSON format. Disabled by default. +* `-o, --output TEXT`: specifies an output file for a test run report. Prints to `StdOut` by default. +* `-cov-mode, --coverage-mode [html|func|json]`: specifies whether a test coverage report should be generated and defines the report format. + Coverage report generation is disabled by default. +* `-cov-out, --coverage-output TEXT`: specifies the output file for a test coverage report. Required if `[--coverage-mode]` is + set. + +## Contributing to UnitTestBot Go + +To take part in project development or learn more about UnitTestBot Go, check +out the [Developer guide](../utbot-go/docs/DEVELOPER_GUIDE.md) and our [plans](../utbot-go/docs/FUTURE_PLANS.md). \ No newline at end of file diff --git a/docs/JavaScriptSupport.md b/docs/JavaScriptSupport.md new file mode 100644 index 0000000000..cfafa94e42 --- /dev/null +++ b/docs/JavaScriptSupport.md @@ -0,0 +1,117 @@ +# UnitTestBot JavaScript + +[UnitTestBot](https://www.utbot.org/) is the tool for automated unit test generation available as an IntelliJ IDEA plugin, or a command-line interface. + +Now UnitTestBot provides fuzzing-based support for JavaScript. + +## IntelliJ IDEA plugin + +### Requirements + +1. IntelliJ IDEA Ultimate — for compatibility, see [UnitTestBot on JetBrains Marketplace](https://plugins.jetbrains.com/plugin/19445-unittestbot/versions). +2. UnitTestBot plugin: please refer to [UnitTestBot user guide](https://github.com/UnitTestBot/UTBotJava/wiki/Install-or-update-plugin). +3. [Node.js 10.0.0 or later](https://nodejs.org/en/download/) (we recommend that you add Node.js to environment variables) + +_Note:_ when _npm_ cannot install requirements, try troubleshooting. +1. If the system prohibits installation: run _cmd_ via `sudo` or with administrator access, run `npm install -g `. +2. If Node.js is missing, or _npm_ is not installed: install Node.js with default configuration from the official website. + +### How to use + +1. In your IntelliJ IDEA, go to **File** > **Settings** > **Tools**, choose **UnitTestBot** and enable **Experimental languages support**. + + **(!) NOTE:** be sure to enable this option for **_each_** project. + +2. Go to **File** > **Settings** > **Languages & Frameworks**, choose **Node.js** and check if the path to Node.js executable file is specified. +3. In a JavaScript file, press **Alt+Shift+U** to open the generation dialog. + +## Command-line interface (CLI) + +### Build + +JAR file can be built in [GitHub Actions](https://github.com/UnitTestBot/UTBotJava/actions/workflows/publish-plugin-and-cli-from-branch.yml) with the `publish-plugin-and-cli-from-branch` script. + +### Requirements + +* [Node.js 10.0.0 or later](https://nodejs.org/en/download/) +* [Java 11 or later](https://www.oracle.com/java/technologies/downloads/) +* _nyc_ 15.1.0 or later: `> npm install -g nyc` +* Mocha 10.0.0 or later: `> npm install -g mocha` + +_Note:_ for each new project, _npm_ needs internet connection to install the required packages. + +### Generate tests: `generate_js` + + java -jar utbot-cli.jar generate_js --source="dir/file_with_sources.js" --output="dir/generated_tests.js" + + This will generate tests for top-level functions from `file_with_sources.js`. + +#### Options + +- `-s, --source ` + + _(required)_ Source code file for test generation. +- `-c, --class ` + + Specifies the class to generate tests for. + If not specified, tests for top-level functions or a single class are generated. + +- `-o, --output ` + + File for generated tests. +- `-p, --print-test` + + Specifies whether a test should be printed out to `StdOut` (default = false). +- `-t, --timeout ` + + Timeout for a single test case to generate: in seconds (default = 15). +- `--coverage-mode ` + + Specifies the coverage mode for test generation (used for coverage-based optimization). For now, the fast mode cannot deal with exceeding timeouts, but works faster (default = FAST). Do not use the fast mode if you guess there might be infinite loops in your code. +- `--path-to-node ` + + Sets a path to Node.js executable (default = "node"). +- `--path-to-nyc ` + + Sets a path to _nyc_ executable (default = "nyc"). +- `--path-to-npm ` + + Sets a path to _npm_ executable (default = "npm"). + +### Run generated tests: `run_js` + + java -jar utbot-cli.jar run_js --fileOrDir="generated_tests.js" + + This will run generated tests from a file or directory. + +#### Options + +- `-f, --fileOrDir` + + _(required)_ File or directory with tests. +- `-o, --output` + + Specifies the output TXT file for a test framework result (if empty, prints the result to `StdOut`). + +- `-t, --test-framework ` + + Test framework to use for test running (default = "Mocha"). + +### Generate a coverage report: `coverage_js` + + java -jar utbot-cli.jar coverage_js --source=dir/generated_tests.js + + This will generate a coverage report for generated tests and print it to `StdOut`. + +#### Options + +- `-s, --source ` + + _(required)_ File with tests to generate a report for. + +- `-o, --output` + + Specifies the output JSON file for a coverage report (if empty, prints the report to `StdOut`). +- `--path-to-nyc ` + + Sets a path to _nyc_ executable (default = "nyc"). diff --git a/docs/NightStatisticsMonitoring.md b/docs/NightStatisticsMonitoring.md new file mode 100644 index 0000000000..226356b97f --- /dev/null +++ b/docs/NightStatisticsMonitoring.md @@ -0,0 +1,241 @@ +# Night Statistics Monitoring + +## What is the problem? + +As UnitTestBot contributors, we'd like to constantly improve our product. There are many of us introducing code changes simultaneously — unfortunately, some changes or combinations of them may lead to reduced plugin efficiency. To avoid such an unlucky result we need to monitor statistics on test generation performance. + +## Why monitor nightly? + +It would be great to collect statistics as soon as the contributor changes the code. In case you have a huge project it takes too long to run the monitoring system after each push into master. +Thus, we decided to do it every night when (hopefully!) no one makes changes. + +## How do we collect statistics? + +To find the algorithm you can refer to `StatisticsMonitoring.kt`. Shortly speaking, it is based on `ContestEstimator.kt`, which runs test generation on the sample projects and then compile the resulting tests. We repeat the whole process several times to reduce measurement error. + +## Statistics monitoring usage + +### Collecting statistics + +To run statistics monitoring you have to specify the name of the JSON output file. + +Input arguments: ``. + +Output format: you get the JSON file, which contains an array of objects with statistics and input parameters on each run. + +More about each statistic: `Statistics.kt`. + +More about monitoring settings: `MonitoringSettings.kt`. + +Input example: + +``` +stats.json +``` + +Output example (the result of three runs during one night): + +```json5 +[ + { + "parameters": { + "fuzzing_ratio": 0.1, // how long does fuzzing takes + "class_timeout_sec": 20, // test generation timeout for one class + "run_timeout_min": 20 // total timeout for this run + }, + "targets": [ // projects that have been processed + { + "target": "guava", // name of project + "summarised_metrics": { // summarised metrics for processed project + "total_classes": 20, // classes count + "testcases_generated": 1042, // generated unit-tests count + "classes_failed_to_compile": 0, // classes that's tests are not compilable + "classes_canceled_by_timeout": 4, // classes that's generation was canceled because of timeout + "total_methods": 526, // methods count + "methods_with_at_least_one_testcase_generated": 345, // methods with at least one successfully generated test + "methods_with_at_least_one_exception": 32, // methods that's generation contains exceptions + "methods_without_any_tests_and_exceptions": 59, // suspicious methods without any tests and exceptions + "covered_bytecode_instructions": 4240, // amount of bytecode instructions that were covered by generated tests + "covered_bytecode_instructions_by_fuzzing": 2946, // amount of bytecode instructions that were covered by fuzzing's generated tests + "covered_bytecode_instructions_by_concolic": 3464, // amount of bytecode instructions that were covered by concolic's generated tests + "total_bytecode_instructions": 9531, // total amount of bytecode instructions in methods with at least one testcase generated + "averaged_bytecode_instruction_coverage_by_classes": 0.5315060991492891 // mean bytecode coverage by class + }, + "metrics_by_class": [ // metrics for all classes in this project + { + "class_name": "com.google.common.math.LongMath", // name of processed class + "metrics": { // metrics for specified class + "testcases_generated": 91, // amount of generated unit-tests + "failed_to_compile": false, // compilation generated tests are failure + "canceled_by_timeout": true, // generation is interrupted because of timeout + "total_methods_in_class": 31, // methods count in this class + "methods_with_at_least_one_testcase_generated": 26, // methods with at least one successfully generated test + "methods_with_at_least_one_exception": 0, // methods that's generation contains exceptions + "methods_without_any_tests_and_exceptions": 5, // suspicious methods without any tests and exceptions + "covered_bytecode_instructions_in_class": 585, // amount of bytecode instructions that were covered by generated tests + "covered_bytecode_instructions_in_class_by_fuzzing": 489, // amount of bytecode instructions that were covered by fuzzing's generated tests + "covered_bytecode_instructions_in_class_by_concolic": 376, // amount of bytecode instructions that were covered by concolic's generated tests + "total_bytecode_instructions_in_class": 1442 // total amount of bytecode instructions in methods with at least one testcase generated + } + }, + // ... + ] + } + ] + } +] +``` + +### Metadata + +Our main goal is to find code changes or run conditions related to the reduced UnitTestBot performance. Thus, we collect metadata about each run: the commit hash, the UnitTestBot build number, and also information about the environment (including JDK and build system versions, and other parameters). + +The `insert_metadata.py` script is responsible for doing this. To run it you have to specify the following arguments. + +To get more information about input arguments call script with option `--help`. + +Output format: you get the JSON file, containing metadata, statistics and parameters grouped by target project and classes. + +Input example: +``` +--stats_file stats.json --output_file data/meta-stats.json +--commit 66a1aeb6 --branch main +--build 2022.8 --timestamp 1661330445 +--source_type github-action --source_id 2917672580 +``` + +Output example (statistics followed by metadata): +```json5 +{ + "version": 2, // version of json format + "targets": [ // projects and methods that have been processed + { + "target": "guava", // name of project + "summarised": [ // list of summarised metrics with parameters on each run + { + "parameters": { + "fuzzing_ratio": 0.1, // how long does fuzzing takes + "class_timeout_sec": 20, // test generation timeout for one class + "run_timeout_min": 20 // total timeout for this run + }, + "metrics": { + "total_classes": 20, // classes count + "testcases_generated": 1042, // generated unit-tests count + "classes_failed_to_compile": 0, // classes that's tests are not compilable + "classes_canceled_by_timeout": 4, // classes that's generation was canceled because of timeout + "total_methods": 526, // methods count + "methods_with_at_least_one_testcase_generated": 345, // methods with at least one successfully generated test + "methods_with_at_least_one_exception": 32, // methods that's generation contains exceptions + "methods_without_any_tests_and_exceptions": 59, // suspicious methods without any tests and exceptions + "total_bytecode_instruction_coverage": 0.44486412758, // total bytecode coverage of generated tests + "total_bytecode_instruction_coverage_by_fuzzing": 0.30909663204, // total bytecode coverage of fuzzing's generated tests + "total_bytecode_instruction_coverage_by_concolic": 0.36344559857, // total bytecode coverage of concolic's generated tests + "averaged_bytecode_instruction_coverage_by_classes": 0.5315060991492891 // mean bytecode coverage by class + } + }, + // ... + ], + "by_class": [ // list of metrics and parameters for all classes in project and on each run + { + "class_name": "com.google.common.math.LongMath", // name of processed class + "data": [ // metrics and parameters on each run + { + "parameters": { // parameters on this run + "fuzzing_ratio": 0.1, // how long does fuzzing takes + "class_timeout_sec": 20, // test generation timeout for one class + "run_timeout_min": 20 // total timeout for this run + }, + "metrics": { // metrics for specified class on this run + "testcases_generated": 91, // amount of generated unit-tests + "failed_to_compile": false, // compilation generated tests are failure + "canceled_by_timeout": true, // generation is interrupted because of timeout + "total_methods_in_class": 31, // methods count in this class + "methods_with_at_least_one_testcase_generated": 26, // methods with at least one successfully generated test + "methods_with_at_least_one_exception": 0, // methods that's generation contains exceptions + "methods_without_any_tests_and_exceptions": 5, // suspicious methods without any tests and exceptions + "total_bytecode_instruction_coverage_in_class": 0.40568654646, // bytecode coverage of generated tests + "total_bytecode_instruction_coverage_in_class_by_fuzzing": 0.33911234396, // bytecode coverage of fuzzing's generated tests + "total_bytecode_instruction_coverage_in_class_by_concolic": 0.26074895977 // bytecode coverage of concolic's generated tests + } + }, + // ... + ] + }, + // ... + ] + }, + // ... + ], + "metadata": { // device's properties + "source": { // information about runner + "type": "github-action", + "id": "2917672580" + }, + "commit_hash": "66a1aeb6", // commit hash of used utbot build + "branch": "main", // branch of used utbot build + "build_number": "2022.8", // build number of used utbot build + "timestamp": 1661330445, // run timestamp + "date": "2022-08-24T08:40:45", // human-readable run timestamp + "environment": { // device's environment + "host": "fv-az183-700", + "OS": "Linux version #20~20.04.1-Ubuntu SMP Fri Aug 5 12:16:53 UTC 2022", + "java_version": "openjdk version \"11.0.16\" 2022-07-19 LTS\nOpenJDK Runtime Environment Zulu11.58+15-CA (build 11.0.16+8-LTS)\nOpenJDK 64-Bit Server VM Zulu11.58+15-CA (build 11.0.16+8-LTS, mixed mode)\n", + "gradle_version": "Gradle 7.4.2", + "JAVA_HOME": "/opt/hostedtoolcache/Java_Zulu_jdk+fx/11.0.16-8/x64", + "KOTLIN_HOME": "/usr", + "PATH": "/opt/hostedtoolcache/Python/3.9.13/x64/bin:/opt/hostedtoolcache/Python/3.9.13/x64:/home/runner/gradle-installations/installs/gradle-7.4.2/bin:/opt/hostedtoolcache/Java_Zulu_jdk+fx/11.0.16-8/x64/bin:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/home/runner/.local/bin:/opt/pipx_bin:/home/runner/.cargo/bin:/home/runner/.config/composer/vendor/bin:/usr/local/.ghcup/bin:/home/runner/.dotnet/tools:/snap/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin" + } + } +} +``` + +### Datastorage structure + +We store the collected statistics in our repository. You can find two special branches: `monitoring-data` and `monitoring-aggregated-data`. + +The `monitoring-data` branch is a storage for raw statistics data as well as metadata. + +The filename format: `--
-------.json` + +### Grafana + +#### Usage + +We can use [Grafana](https://monitoring.utbot.org) for more dynamic and detailed statistics visualisation. Grafana pulls data from our repository automatically. + +#### Metrics format + +Our goal after collecting statistics is uploading results into grafana. For this we should prepare data and send it to our server. + +The `prepare_metrics.py` script is responsible for doing this. To run it you have to specify the following arguments. + +To get more information about input arguments call script with option `--help`. + +Output format: you get the JSON file, containing array of metrics with some labels. + +Output example: +```json5 +[ + // summarised + { + "metric": "testcases_generated", + "labels": { + "project": "guava", + "fuzzing_ratio": 0.1 + }, + "value": 1024 + }, + // ... + // by classes + { + "metric": "testcases_generated", + "labels": { + "project": "guava", + "class": "com.google.common.math.LongMath", + "fuzzing_ratio": 0.1 + }, + "value": 91 + }, + // ... +] +``` diff --git a/docs/OverallArchitecture.md b/docs/OverallArchitecture.md new file mode 100644 index 0000000000..ba9a48562a --- /dev/null +++ b/docs/OverallArchitecture.md @@ -0,0 +1,380 @@ +# UnitTestBot overall architecture + +Get the bird's-eye view of UnitTestBot overall architecture in the following chart. Check the purpose of each component in the descriptions below. + +```mermaid +flowchart TB + subgraph Clients + direction LR + IntellijPlugin + MavenPlugin["Maven/Gradle plugins"] + GithubAction-->MavenPlugin + ExternalJavaClient[\External Java Client\] + CLI + ContestEstimator + + end + + subgraph Facades + direction LR + EngineMain[[EngineMain]] + UtBotJavaApi[[UtBotJavaApi]] + GenerateTestsAndSarifReport[[GenerateTestsAndSarifReport]] + end + IntellijPlugin--Rd-->EngineMain + MavenPlugin-->GenerateTestsAndSarifReport + ExternalJavaClient-->UtBotJavaApi + + subgraph API["Generation API"] + direction LR + TestCaseGenerator[[TestCaseGenerator]] + CodeGenerator[[CodeGenerator]] + end + Facades-->API + CLI-->API + ContestEstimator-->API + + + + subgraph Components + direction LR + SymbolicEngine-->jlearch + SymbolicEngine-->ConcreteExecutor + Fuzzer-->ConcreteExecutor + SarifReport + Minimizer + CodeRenderer + Summaries + jlearch + end + CodeGenerator-->CodeRenderer + TestCaseGenerator-->SymbolicEngine + TestCaseGenerator-->Fuzzer + TestCaseGenerator-->Minimizer + TestCaseGenerator-->Summaries + GenerateTestsAndSarifReport-->SarifReport + + UTSettings((UTSettings)) + UTSettings<--Rd/local-->Components + UTSettings<---->Clients + TestCaseGenerator--warmup-->ConcreteExecutor + ConcreteExecutor--Rd-->InstrumentedProcess + +``` + +## Typical interaction between components + +An interaction diagram for UnitTesBot components is presented below. See how it starts from IntelliJ IDEA plugin UI and follow the flow. +```mermaid +sequenceDiagram + autonumber + actor user as User + participant ij as IDE process + participant engine as Engine process + participant concrete as Instrumented process + + user ->> ij: Invoke "Generate Tests with UnitTestBot" + ij ->> ij: Calculate methods, framework to show + ij ->> user: Show UI + + break User clicked "Cancel" + user -->> user: Exit + end + user ->> ij: Click "Generate Tests" + ij ->> ij: Calculate what JAR needs to be installed + + opt Some JARs need to be installed + ij ->> ij: Install JARs + end + + ij ->> engine: Start process + activate engine + ij ->> engine: Setup up context + + loop For all files + ij ->> engine: Generate UtExecution models + loop For all UtExecution models: for the method found by engine + engine ->> concrete: Run concretely + end + engine --> engine: Minimize the number of tests for the method + engine --> engine: Generate summaries for the method + end + ij ->> engine: Render code + engine ->> ij: File with tests + deactivate engine + +``` + +## Clients + +### IntelliJ IDEA plugin + +The plugin provides +* a UI for the IntelliJ-based IDEs to use UnitTestBot directly from source code, +* the linkage between IntelliJ Platform API and UnitTestBot API, +* support for the most popular programming languages and frameworks for end users (the plugin and its optional dependencies are described in [plugin.xml](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-intellij/src/main/resources/META-INF/plugin.xml) and nearby, in the [`META-INF`](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-intellij/src/main/resources/META-INF) folder). + +The main plugin module is [utbot-intellij](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-intellij), providing support for Java and Kotlin. +Also, there is an auxiliary [utbot-ui-commons](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-ui-commons) module to support providers for other languages. + +As for the UI, there are two entry points: +* [GenerateTestAction](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt) for _preparing and calling_ test generation; +* [SettingsWindow](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/SettingsWindow.kt) for _per-project_ UnitTestBot configuring. + +The main plugin-specific features are: +* A common action for generating tests right from the editor or a project tree — with a generation scope from a single method up to the whole source root. See [GenerateTestAction](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt) — the same for all supported languages. +* Auto-installation of the user-chosen testing framework as a project library dependency (JUnit 4, JUnit 5, and TestNG are supported). See [UtIdeaProjectModelModifier](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/UtIdeaProjectModelModifier.kt) and the Maven-specific version: [UtMavenProjectModelModifier](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/UtMavenProjectModelModifier.kt). +* Suggesting the location for a test source root and auto-generating the `utbot_tests` folder there, providing users with a sandbox in their code space. +* Optimizing generated code with IDE-provided intentions (experimental). See [IntentionHelper](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/IntentionHelper.kt) for details. +* An option for distributing generation time between symbolic execution and fuzzing explicitly. +* Running generated tests while showing coverage with the IDE-provided measurement tools. See [RunConfigurationHelper](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt) for implementation. +* Displaying the UnitTestBot-found code defects as IntelliJ-specific inspections and quickfixes in the "Problems" tool window. See the [inspection](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection) package. +* Two kinds of Javadoc comments in the generated code: rendered as plain text and structured via custom tags. See the [javadoc](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc) package. +* A self-documenting [`settings.properties`](https://github.com/UnitTestBot/UTBotJava/blob/main/docs/SettingsProperties.md) file with the settings for low-level UnitTestBot tuning. + +### Gradle/Maven plugins + +Plugins for Gradle/Maven build systems provide the UnitTestBot `GenerateTestsAndSarifReportFacade` component with the user-chosen settings (test generation timeout, testing framework, etc.). This component runs test generation and creates SARIF reports. + +For more information on the plugins, please refer to the detailed design documents: +- [UnitTestBot Gradle plugin](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-gradle/docs/utbot-gradle.md) +- [UnitTestBot Maven plugin](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-maven/docs/utbot-maven.md) + +You can find the modules here: +* [utbot-gradle](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-gradle) +* [utbot-maven](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-maven) + +### GitHub Action + +UnitTestBot GitHub Action displays the detected code defects in the GitHub "Security Code Scanning Alerts" section. + +You can find UnitTestBot GitHub Action in the separate [repository](https://github.com/UnitTestBot/UTBotJava-action). + +UnitTestBot GitHub Action uses the [UnitTestBot Gradle plugin](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-gradle) +to run UnitTestBot on the user's repository and imports the SARIF output into the "Security Code Scanning Alerts" section. +Please note that at the moment this action cannot work with Maven projects because +the [UnitTestBot Maven plugin](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-maven) has not been published yet. + +For more information on UnitTestBot GitHub Action, please refer to the [related docs](https://github.com/UnitTestBot/UTBotJava-action#readme). +You can also find a detailed [usage example](https://github.com/UnitTestBot/UTBotJava-action-example). + +### Command-line interface (CLI) + +With CLI, one can run UnitTestBot from the command line. + +CLI implementation is placed in the [utbot-cli](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-cli) module. UnitTestBot CLI has two main commands: `generate` and `run` — use `--help` to find more info on their options. + +An executable CLI is distributed as a JAR file. + +We provide Linux Docker images containing CLI. They are stored on [GitHub Packages](https://github.com/UnitTestBot/UTBotJava/pkgs/container/utbotjava%2Futbot_java_cli). + +### Contest estimator + +Contest estimator runs UnitTestBot on the provided projects and returns the generation statistics such as instruction coverage. + +Contest estimator is placed in the [utbot-junit-contest](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-junit-contest) module and has two entry points: +- [ContestEstimator.kt](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt) is the main entry point. It runs UnitTestBot on the specified projects, calculates statistics for the target classes and projects, and outputs them to a console. +- [StatisticsMonitoring.kt](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-junit-contest/src/main/kotlin/org/utbot/monitoring/StatisticsMonitoring.kt) is an additional entry point, which does the same as the previous one but can be configured from a file and dumps the resulting statistics to a file. + It is used to [monitor and chart](https://github.com/UnitTestBot/UTBotJava/blob/main/docs/NightStatisticsMonitoring.md) statistics nightly. + +## Components + +### Symbolic engine + +Symbolic engine is a component maintaining the whole analysis process: from the moment it takes information about a method under test (MUT) till the moment it returns a set of execution results along with the information required to reproduce the MUT's execution paths. The engine uses symbolic execution to explore the paths in a MUT's control flow graph (CFG). + +The technique is rather complex, so the engine consists of several subcomponents, each responsible for a certain part of the analysis: + +* [UtBotSymbolicEngine.kt](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt) contains a `UtBotSymbolicEngine` class — it manages interaction between different parts of the system and controls the analysis flow. This class is an entry point for symbolic execution in UnitTestBot. Using `UtBotSymbolicEngine` API, the users or UnitTestBot components can start, suspend or stop the analysis. `UtBotSymbolicEngine` provides a connection between the components by taking execution states from [PathSelector](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathSelector.kt) and pushing them either into [Traverser](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt) or in [ConcreteExecutor](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt), depending on their status and settings. + In a few words, the pipeline looks like this: the engine takes a state from [PathSelector](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathSelector.kt), pushes it into [Traverser](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt), and then gets an updated state from it. If this state is not terminal, `UtBotSymbolicEngine` pushes it to the queue in the path selector. If this state is terminal, it either calls `ConcreteExecutor` to get a concrete result state or yields a symbolic result into the resulting flow. +* [PathSelector](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathSelector.kt) is a class making a decision on which instruction of the program should be processed next. It is located in `PathSelector.kt`, but the whole [selectors](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-framework/src/main/kotlin/org/utbot/engine/selectors) package is related to it. `PathSelector` has a pretty simple interface: it can put a state in the queue or return a state from that queue. +* [Traverser](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt) processes a given state. It contains information about CFG, a hierarchy of classes in the program, a symbolic type system, and mocking information. `Traverser` is the most important class in the symbolic engine module. It knows how to process instructions in CFG, how to update the dependent symbolic memory, and which constraints should be added to go through a certain path. Having processed an instruction from the given state, `Traverser` creates a new one, with updated memory and path constraints. + +There are other important classes in the symbolic engine subsystem. Here are some of them: +* [Memory](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt) is responsible for the symbolic memory representation of a state in the program. +* [TypeRegistry](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeRegistry.kt) contains information about a type system. +* `Mocker` decides whether we should mock an object or not. + +You can find all the engine-related classes in the [engine](https://github.com/UnitTestBot/UTBotJava/tree/main/utbot-framework/src/main/kotlin/org/utbot/engine) module. + +### Concrete executor + +`ConcreteExecutor` is the input point for the _instrumented process_ used by UnitTestBot symbolic engine and fuzzer. This class provides a smooth and concise interaction between the _instrumented process_ and a user, whereas the _instrumented process_ executes a given function with the supplied arguments. + +`ConcreteExecutor` is parameterized with `Instrumentation` and its return type via the generic arguments. `Instrumentation` is an interface, so inheritors have to implement the logic of a specific method invocation in an isolated environment as well as the `transform` function used for instrumenting classes. For our purposes, we use `UtExecutionInstrumentation`. + +The main `ConcreteExecutor` function is +```kotlin +suspend fun executeAsync( + kCallable: KCallable<*>, + arguments: Array, + parameters: Any? +): TResult +``` +It serializes the arguments and some parameters (e.g., static fields), sends it to the _instrumented process_ and retrieves the result. + +Internally, `ConcreteExecutor` uses `Rd` for interprocess communication and `Kryo` for objects serialization. You don't need to provide a marshaller, as `Kryo` serializes the objects (sometimes it fails). + +`ConcreteExecutor` is placed in the [utbot-instrumentation](../utbot-instrumentation) module. You can find the corresponding tests in the [utbot-instrumentation-tests](../utbot-instrumentation-tests) module. + +### Instrumented process + +`Instrumented process` concretely runs the user functions with the specified arguments and returns the result of execution. + +Additionally, `Instrumented process` evaluates instruction coverage and mocks function invocations and creating instances via the user's Java bytecode instrumentation. + +The main concept is _instrumentation_. `Instrumentation` is a class that implements the [corresponding](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/Instrumentation.kt) interface. It transforms the user code and provides invoking user functions. + +`Instrumented process` supports the following commands described in [InstrumentedProcessModel.kt](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-rd/src/main/rdgen/org/utbot/rd/models/InstrumentedProcessModel.kt): +- `AddPaths` tells where the `Instrumented process` should search for the user classes. +- `Warmup` forces loading and instrumenting user classes. +- `SetInstrumentation` tells which instrumentation the `Instrumented process` should use. +- `InvokeMethodCommand` requests the `Instumented process` to invoke a given user function and awaits the results. +- `StopProcess` just stops the `Instrumented process`. +- `CollectCoverage` requests collecting code coverage for the specified class. +- `ComputeStaticField` requests the specified static field value. + +These commands are delivered from the other processes by `Rd`. + +The main instrumentation of UnitTestBot is [UtExecutionInstrumentation](https://github.com/UnitTestBot/UTBotJava/blob/main/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt). + +### Code generator + +Code generation and rendering are a part of the test generation process in UnitTestBot. +UnitTestBot gets the synthetic representation of generated test cases from the fuzzer or the symbolic engine. +This representation (or model) is implemented in the `UtExecution` class. +The `codegen` module generates the real test code based on this `UtExecution` model +and renders it in a human-readable form. + +The `codegen` module +- converts `UtExecution` test information into an Abstract Syntax Tree (AST) representation using `CodeGenerator`, +- renders this AST according to the requested configuration (considering programming language, testing + framework, mocking and parameterization options) using `renderer`. + +The `codegen` entry points are: +- `CodeGenerator.generateAsString()` +- `CodeGenerator.generateAsStringWithTestReport()` + +The detailed implementation is described in the [Code generation and rendering](https://github.com/UnitTestBot/UTBotJava/blob/main/docs/CodeGenerationAndRendering.md) design doc. + +### Fuzzer + +Fuzzing is a versatile technique for "guessing" values to be used as method arguments. To generate these kinds of values, the fuzzer uses generators, mutators, and predefined values. + +Fuzzing has been previously implemented in UnitTestBot as the solution for Java. For now, we have developed the generic platform that supports generating fuzzing tests for different languages. The Java fuzzing solution in UnitTestBot is marked as deprecated — it will soon be replaced with the fuzzing platform. + +You can find the relevant code here: +- `utbot-fuzzing` is the general fuzzing platform module. The related API is located in `org/utbot/fuzzing/Api.kt`. +- `utbot-fuzzer` is the module with the fuzzing solution for Java. Find the corresponding API in `org/utbot/fuzzing/JavaLanguage.kt`. + +Entry point for generating values (Java): `org/utbot/fuzzing/JavaLanguage.kt#runJavaFuzzing(...)` + +You can find the detailed description in the [Fuzzing Platform](https://github.com/UnitTestBot/UTBotJava/blob/main/docs/Fuzzing%20Platform.md) design doc. + +### Minimizer + +Minimization is used to decrease the amount of `UtExecution` instances without decreasing coverage. + +The entry point is the [minimizeTestCase](https://github.com/UnitTestBot/UTBotJava/blob/d2d2e350bc75943b78f2078002a5cabc5dd62072/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt#L38) function. It receives a set of `UtExecution` instances and a grouping function (grouping by `UtExecution::utExecutionResult`). Then the minimization procedure divides the set of `UtExecution` instances into several groups. Each group is minimized independently. + +We have different groups — here are some of them: +- A _successful regression suite_ that consists of `UtSuccess` and `UtExplicitlyThrownException` executions. +- An _error suite_ consisting of `UtImplicitlyThrownException` executions. +- A _timeout suite_ that consists of `UtTimeoutException` executions. +- A _crash suite_ consisting of executions where some parts of the engine failed. + +Each `UtExecution` instance provided should have coverage information, otherwise we add this execution to the test suite instantly. Coverage data are usually obtained from the _instrumented process_ and consist of covered lines. + +To minimize the number of executions in a group, we use a simple greedy algorithm: +1. Pick an execution that covers the maximum number of the previously uncovered lines. +2. Add this execution to the final suite and mark new lines as covered. +3. Repeat the first step and continue till there are executions containing uncovered lines. + +The whole minimization procedure is located in the [org.utbot.framework.minimization](../utbot-framework/src/main/kotlin/org/utbot/framework/minimization) package inside the [utbot-framework](../utbot-framework) module. + +### Summarization module + +Summarization is the process of generating detailed test descriptions consisting of +- test method names +- testing framework annotations (including `@DisplayName`) +- Javadoc comments for tests +- cluster comments for groups of tests (_Regions_) + +Each of these description elements can be turned off via `{userHome}/.utbot/settings.properties` (which gets information from `UtSettings.kt`). +If the summarization process fails due to an error or insufficient information, then the test method receives a unique name and no additional meta-information. + +This meta-information is generated for each type of `UtExecution` model and thus may vary significantly. +Also, Javadoc comments can be rendered in two styles: as plain text or in a special format enriched with the [custom Javadoc tags](https://github.com/UnitTestBot/UTBotJava/blob/main/docs/summaries/CustomJavadocTags.md). + +The whole summarization subsystem is located in the `utbot-summary` module. + +For detailed information, please refer to the Summarization architecture design document. + +### SARIF report generator + +SARIF (Static Analysis Results Interchange Format) is a JSON-based format for displaying static analysis results. + +All the necessary information about the format and its usage can be found +in the [official documentation](https://github.com/microsoft/sarif-tutorials/blob/main/README.md) +and in the related [GitHub Docs](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning). + +In UnitTestBot, the `SarifReport` class is responsible for generating SARIF reports. +We use them to display UnitTestBot-detected errors such as unchecked exceptions, overflows, assertion errors, etc. + +For example, for the class below +```Java +public class Main { + int example(int x) { + return 1 / x; + } +} +``` + +UnitTestBot creates a report containing the following information: +- `java.lang.ArithmeticException: / by zero` may occur in line 3 +- The exception occurs if `x == 0` +- To reproduce this error, the user can run the generated `MainTest.testExampleThrowsAEWithCornerCase` test +- The exception stack trace: + - `Main.example(Main.java:3)` + - `MainTest.testExampleThrowsAEWithCornerCase(MainTest.java:39)` + +## Cross-cutting subsystems + +### Rd + +UnitTestBot consists of three processes (according to the execution order): +* _IDE process_ — the process where the plugin part executes. +* _Engine process_ — the process where the test generation engine executes. +* _Instrumented process_ — the process where concrete execution takes place. + +These processes are built on top of the [Reactive distributed communication framework (Rd)](https://github.com/JetBrains/rd) developed by JetBrains. + +One of the main Rd concepts is a _Lifetime_ — it helps to release shared resources upon the object's termination. +You can find the Rd basic ideas and UnitTestBot implementation details in the [Multiprocess architecture](https://github.com/UnitTestBot/UTBotJava/blob/main/docs/RD%20for%20UnitTestBot.md) design doc. + +### Settings + +In UnitTestBot, _settings_ are the set of _properties_. Each _property_ is a _key=value_ pair and affects some important aspect of UnitTestBot behavior. UnitTestBot as an IntelliJ IDEA plugin, a CI-tool, or a CLI-tool has low-level _core settings_. The UnitTestBot plugin also has per-project _plugin-specific_ settings. + +Core settings are persisted in the _settings file_: `{userHome}/.utbot/settings.properties`. This file is designed for reading only. The defaults for the core settings are provided in source code (`UtSettings.kt`). + +The plugin-specific settings are stored per project in the _plugin configuration file_: `{projectDir}/.idea/utbot-settings.xml`. Nobody is expected to edit this file manually. + +The end user has three places to change UnitTestBot behavior: +1. A `{userHome}/.utbot/settings.properties` file — for global settings. +2. Plugin settings UI (**File** > **Settings** > **Tools** > **UnitTestBot**) — for per-project settings. +3. Controls in the **Generate Tests with UnitTestBot window** dialog — for per-generation settings. + +### Logging + +The UnitTestBot Java logging system is implemented across the IDE process, the Engine process, and the Instrumented process. + +UnitTestBot Java logging relies on `log4j2` library. +The custom Rd logging system is recommended as the default one for the Instrumented process. + +In the [Logging](../docs/contributing/InterProcessLogging.md) document, +you can find how to configure the logging system when UnitTestBot Java is used +* as an IntelliJ IDEA plugin, +* as Contest estimator or the Gradle/Maven plugins, via CLI or during the CI test runs. + +Implementation details, log level and performance questions are also addressed [here](../docs/contributing/InterProcessLogging.md). \ No newline at end of file diff --git a/docs/PythonSupport.md b/docs/PythonSupport.md new file mode 100644 index 0000000000..eea6d80909 --- /dev/null +++ b/docs/PythonSupport.md @@ -0,0 +1,31 @@ +# UnitTestBot Python + +[UnitTestBot](https://www.utbot.org/) is the tool for automated unit test generation available as an IntelliJ IDEA plugin, or a command-line interface. + +Now UnitTestBot provides fuzzing-based support for Python. + +## Requirements + +1. IntelliJ IDEA — for compatibility, see [UnitTestBot on JetBrains Marketplace](https://plugins.jetbrains.com/plugin/19445-unittestbot/versions). +2. Python 3.8 or later +3. [Python plugin](https://plugins.jetbrains.com/plugin/631-python) for IntelliJ IDEA + +If you already have a Python project, you usually have no need to install or configure anything else, but if you +have trouble with launching UnitTestBot for Python code, please refer to [advanced requirements section](../utbot-python/docs/CLI.md#requirements). + +## How to install and use + +To try UnitTestBot Python in your IntelliJ IDEA: +1. To install the plugin, please refer to [UnitTestBot user guide](https://github.com/UnitTestBot/UTBotJava/wiki/Install-or-update-plugin). +2. Configure the Python interpreter for your project and make sure IntelliJ IDEA resolves all the imports. +3. In your IntelliJ IDEA, go to **File** > **Settings** > **Tools**, choose **UnitTestBot** and enable **Experimental languages support**. + + **(!) NOTE:** be sure to enable this option for **_each_** project. + +4. To generate tests, place the caret at the required function and press **Alt+Shift+U**. To find the appropriate shortcut for the OS you are using, check the context menu. + +To use UnitTestBot Python via command-line interface, please refer to the [CLI guide](../utbot-python/docs/CLI.md). + +## How to contribute and get support + +To get information on contributing and getting support, please see [UnitTestBot Java Readme](https://github.com/UnitTestBot/UTBotJava#readme). diff --git a/docs/RD for UnitTestBot.md b/docs/RD for UnitTestBot.md new file mode 100644 index 0000000000..49ffba4e4d --- /dev/null +++ b/docs/RD for UnitTestBot.md @@ -0,0 +1,235 @@ +# Multiprocess architecture + +## Overview + +UnitTestBot consists of three processes: +1. `IDE process` — the process where the plugin part executes. We also call it the _plugin process_ or the + _IntelliJ IDEA process_. +2. `Engine process` — the process where the test generation engine executes. +3. `Instrumented process` — the process where concrete execution takes place. + +These processes are built on top of the [Reactive distributed communication framework (Rd)](https://github.com/JetBrains/rd) developed by JetBrains. Rd plays a crucial role in UnitTestBot machinery, so we briefly +describe this library here. + +To gain an insight into Rd, one should grasp these Rd concepts: +1. Lifetime +2. Rd entities +3. `Rdgen` + +## Lifetime concept + +Imagine an object holding resources that should be released upon the object's death. In Java, +`Closeable` and `AutoCloseable` interfaces are introduced to help release resources that the object is holding. +Support for `try`-with-resources in Java and `Closeable.use` in Kotlin are also implemented to assure that the +resources are closed after the execution of the given block. + +Though, releasing resources upon the object's death is still problematic: +1. An object's lifetime can be more complicated than the scope of `Closeable.use`. +2. If `Closeable` is a function parameter, should we close it? +3. Multithreading and concurrency may lead to more complex situations. +4. Considering all these issues, how should we correctly close the objects that depend on some other object's + lifetime? How can we perform this task in a fast and easy way? + +So, Rd introduces the concept of `Lifetime`. + +### `Lifetime` + +_Note:_ the described relationships and behavior refer to the JVM-related part of Rd. + +`Lifetime` is an abstract class, with `LifetimeDefinition` as its inheritor. `LifetimeDefinition` +has only one difference from its parent: `LifetimeDefinition` can be terminated. +Each `Lifetime` variable is an instance of `LifetimeDefinition` (we later call it "`Lifetime` instance"). You +can register callbacks in this `Lifetime` instance — all of them will be executed upon the termination. + +Though all `Lifetime` objects are instances of `LifetimeDefinition`, there are conventions for using them: +1. Do not cast `Lifetime` to `LifetimeDefinion` unless you are the one who created `LifetimeDefinition`. +2. If you introduce `LifetimeDefinition` somewhere, you should attach it to another `Lifetime` or provide + the code that terminates it. + +A `Lifetime` instance has these useful methods: +- `onTermination` executes _lambda_/_closeable_ when the `Lifetime` instance is terminated. If an instance has been + already terminated, it executes _lambda_/_closeable_ instantly. Termination proceeds on a thread that has invoked + `LifetimeDefinition.terminate`. Callbacks are executed in the **reversed order**, which is _LIFO_: the last added + callback is executed first. +- `onTerminationIfAlive` is the same as `onTermination`, but the callback is executed only if the `Lifetime` + instance is `Alive`. +- `executeIfAlive` executes _lambda_ if the `Lifetime` instance is `Alive`. This method guarantees that the `Lifetime` + instance is alive (i.e. will not be terminated) during the whole time of _lambda_ execution. +- `createdNested` creates the _child_ `LifetimeDefinition` instance: it can be terminated if the _parent_ + instance is terminated as well; or it can be terminated separately, while the parent instance stays alive. +- `usingNested` is the same as the `createNested` method but behaves like the `Closeable.use` pattern. + +See also: +- `Lifetime.Eternal` is a global `Lifetime` instance that is never terminated. +- `Lifetime.Terminated` is a global `Lifetime` instance that has been already terminated. +- `status` — find more details in the +[LifetimeStatus.kt](https://github.com/JetBrains/rd/blob/9b806ccc770515f6288c778581c54607420c16a7/rd-kt/rd-core/src/main/kotlin/com/jetbrains/rd/util/lifetime/LifetimeStatus.kt) class from the Rd repository. There are three + convenient methods: `IsAlive`, `IsNotAlive`, `IsTerminated`. + +### `LifetimeDefinition` + +`LifetimeDefinition` instances have the `terminate` method that terminates a `Lifetime` instance and invokes all +the registered callbacks. If multiple concurrent terminations occur, the method may sometimes return before +executing all the callbacks because some other thread executes them. + +## Rd entities + +Rd is a lightweight reactive one-to-one RPC protocol, which is cross-language as well as cross-platform. It can +work on the same or different machines via the Internet. + +These are some Rd entities: +- `Protocol` encapsulates the logic of all Rd communications. All the entities should be bound to `Protocol` before + being used. `Protocol` contains `IScheduler`, which executes a _runnable_ instance on a different thread. +- `RdSignal` is an entity allowing one to **fire and forget**. You can add a callback for every received message + via the `advise(lifetime, callback)` method. There are two interfaces: `ISink` that only allows advising for + messages and `ISignal` that can also `fire` events. There is also a `Signal` class with the same behavior + but without remote communication. + +**Important:** if you `advise` and `fire` from the same process, your callback receives _not only_ +messages from the other process, but also the ones you `fire`. + +- `RdProperty` is a stateful property. You can get the current value and advise the callback — an advised + callback is executed on a current value and every change. +- `RdCall` is the remote procedure call. + +There are `RdSet`, `RdMap`, and other entities. + +An `async` property allows you to `fire` entities from any thread. Otherwise, you would need to do it from +the `Protocol.scheduler` thread: all Rd entities should be bound to the `Protocol` from the `scheduler` thread, or you +would get an exception. + +## `Rdgen` + +`Rdgen` generates custom classes and requests that can be bound to protocol and advised. There is a special model DSL +for it. + +### Model DSL + +Examples: +1. [Korifey](https://github.com/korifey/rd_example/blob/main/src/main/kotlin/org/korifey/rd_example/model/Root.kt) — + a simple one. +2. [Rider Unity plugin](https://github.com/JetBrains/resharper-unity/tree/net223/rider/protocol/src/main/kotlin/model) — a complicated one. + +First, you need to define a `Root` object: only one instance of each `Root` can be assigned to `Protocol`. + +There is a `Root` extension — `Ext(YourRoot)` — where you can define custom types and model entities. You can assign +multiple `Root` extensions to the `Protocol`. To generate the auxiliary structures, define them as direct fields. + +DSL: +- `structdef` is a structure with fields that cannot be bound to `Protocol` but can be serialized. This structure + can be `openstruct`, i.e. open for inheritance, and `basestruct`, i.e. abstract. Only `field` can be a member. +- `classdef` is a class that can be bound to a model. It can have `property`, `signal`, `call`, etc. + as members. It is possible to inherit: the class can be `openclass`, `baseclass`. +- `interfacedef` is provided to define interfaces. Use `method` to create a signature. + +You can use `extends` and `implements` to implement inheritance. + +_Note:_ `Rdgen` can generate models for C# and C++. Their structs and classes have different behavior. + +Rd entities — only in bindable models (`Ext`, `classdef`): +- `property` +- `signal` +- `source` +- `sink` +- `array` and `immutablelist` + +Useful properties in DSL entities: +- `async` — the same as `async` in Rd entities +- `docs` — provides KDoc/Javadoc documentation comments for the generated entity + +### Gradle + +[Example](https://github.com/korifey/rd_example/blob/main/build.gradle) + +`RdGenExtension` configures `Rdgen`. The properties are: +- `sources` — the folders with DSL `.kt` files. If there are no `sources`, scan classpath for the inheritors of `Root` + and `Ext`. +- `hashfile` — a folder to store the `.rdgen` hash file for incremental generation. +- `packages` — Java package names to search in toplevels, delimited by `,`. Example: `com.jetbrains.rd.model.nova,com, + org`. + +Configure model generation with the `RdGenExtension.generator` method: +- `root` — for which root this generator is declared. +- `namespace` — which namespace should be used in the generated source. In Kotlin, it configures the generated package + name. +- `directory` — where to put the generated files. +- `transform` — can be `symmetric`, `asis`, and `reversed`. It allows configuring of different model interfaces for + various client-server scenarios. _Note:_ in 99% of cases you should use `symmetric`. If you need another option, consult with someone. +- `language` — can be `kotlin`, `cpp` or `csharp`. + +## UnitTestBot project + +The `utbot-rd` Gradle project contains model sources in `rdgenModels`. You can find them at +[`utbot-rd/src/main/rdgen/org/utbot/rd/models`](../utbot-rd/src/main/rdgen/org/utbot/rd/models). + +### IDE process + +An _IDE process_ uses bundled JetBrains JDK. Code in `utbot-intellij` _**must**_ be compatible will all JDKs and plugin +SDKs, used by our officially supported Intellij IDEA versions. +See `sinceBuild` and `untilBuild` in [`utbot-intellij/build.gradle.kts`](../utbot-intellij/build.gradle.kts). + +The _IDE process_ starts the _Engine process_. The _IDE process_ keeps the `UtSettings` instance in memory and gets updates for it from Intellij IDEA. The other processes "ask" the _IDE process_ about settings via Rd RPC. + +### Engine process + +`TestCaseGenerator` and `UtBotSymbolicEngine` run here, in the _Engine process_. The process classpath contains all +the plugin JAR files (it uses the plugin classpath). + +The _Engine process_ _**must**_ run on the JDK that is used in the project under analysis. Otherwise, there will be +numerous problems with code analysis, `soot`, _Reflection_, and the divergence of the generated code Java API will occur. + +Currently, it is prohibited to run more than **one** generation process simultaneously (the limitation is related to +the characteristics of the native libraries). The process logging mechanism relies on +that fact, so UnitTestBot processes can exclusively write to a log file. + +The starting point in the _IDE process_ is the +[`EngineProcess`](../utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt) class. +The _Engine process_ start file is +[`EngineProcessMain`](../utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt). +The _Engine process_ starts the _Instrumented process_. + +### Instrumented process + +The starting points in the _Engine process_ are the +[`InstrumentedProcess`](../utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt) +and the [`ConcreteExecutor`](../utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt) +classes. The first one encapsulates the state, while the second one implements the request logic for concrete execution. + +The _Instrumented process_ runs on the same JDK as the _Engine process_ to prevent deviation from the _Engine process_. +Sometimes the _Instrumented process_ may unexpectedly die due to concrete execution. + +### Useful info + +1. If you need to use Rd, add the following dependencies: + ``` + implementation group: 'com.jetbrains.rd', name: 'rd-framework', version: rdVersion + + implementation group: 'com.jetbrains.rd', name: 'rd-core', version: rdVersion + ``` +2. There are useful classes in `utbot-rd` to work with Rd and processes: + - `LifetimedProcess` binds a `Lifetime` instance to a process. If the process dies, the `Lifetime` instance + terminates, and vice versa. You can terminate the `Lifetime` instance manually — this will destroy the process. + - `ProcessWithRdServer` starts the Rd server and waits for the connection. + - `ClientProtocolBuilder` — you can use it in a client process to correctly connect to `ProcessWithRdServer`. +3. How `ProcessWithRdServer` communication works: + - Choose a free port. + - Create a client process and pass the port as an argument. + - Both processes create protocols, bind the model and setup callbacks. + - A server process cannot send messages until the _child_ creates a protocol (otherwise, messages are lost), so + the client process has to signal that it is ready. + - The client process creates a special file in the `temp` directory, which is observed by a _parent_ process. + - When the parent process spots the file, it deletes this file and sends a special message to the client process + confirming communication success. + - Only when the answer of the client process reaches the server, the processes are ready. +4. How to write custom RPC commands: + - Add a new `call` in a model, for example, in `EngineProcessModel`. + - Re-generate models: there are special Gradle tasks for this in the `utbot-rd/build.gradle` file. + - Add a callback for the new `call` in the corresponding start files, for example, in `EngineProcessMain.kt`. + - **Important**: do not add [`Rdgen`](https://mvnrepository.com/artifact/com.jetbrains.rd/rd-gen) as + an implementation dependency — it breaks some JAR files as it contains `kotlin-compiler-embeddable`. +5. Logging & debugging: + - [Interprocess logging](contributing/InterProcessLogging.md) + - [Interprocess debugging](./contributing/InterProcessDebugging.md) +6. Custom protocol marshaling types: do not spend time on it until `UtModels` get simpler, e.g. compatible with + `kotlinx.serialization`. + diff --git a/docs/ResultAndErrorHandlingApiOfTheInstrumentedProcess.md b/docs/ResultAndErrorHandlingApiOfTheInstrumentedProcess.md new file mode 100644 index 0000000000..e605f8c1e6 --- /dev/null +++ b/docs/ResultAndErrorHandlingApiOfTheInstrumentedProcess.md @@ -0,0 +1,92 @@ +# Instrumented process API: handling errors and results + +In UnitTestBot Java, there are three processes: +* IDE process +* Engine process +* Instrumented process + +The IDE process launches the plugin so a user can request test generation. +Upon the user request, the Engine process is initiated — it is responsible for the input values generation. + +Here, in the Engine process, there is a `ConcreteExecutor` class, +conveying the generated input values to the `InstrumentedProcess` class. +The `InstrumentedProcess` class creates the third physical process — +the Instrumented process that runs the user functions concretely with the provided input values +and returns the execution result. + +A _client_ is an object that uses the `ConcreteExecutor` directly — it works in the Engine process as well. + +`ConcreteExecutor` expects an `Instrumentation` object, which is responsible for, say, mocking static methods. In UnitTestBot Java, we use `UtExecutionInstrumentation` that implements the `Instrumentation` interface. + +Basically, if an exception occurs in the Instrumented process, +it is rethrown to the client object in the Engine process via Rd. + +## Concrete execution outcomes + +`ConcreteExecutor` is parameterized with `UtExecutionInstrumentation`. When the `ConcreteExecutor::executeAsync` method is called, it leads to one of the three possible outcomes: + +* `InstrumentedProcessDeathException` + +Some errors lead to the instant termination of the Instrumented process. + Such errors are wrapped in `InstrumentedProcessDeathException`. + Prior to processing the next request, the Instrumented process is restarted automatically, though it can take time. +`InstrumentedProcessDeathException` means that there is an Instrumented process internal issue. +Nonetheless, this exception is handled in the Engine process. + +* `InstrumentedProcessError` + +Errors that do not cause the Instrumented process termination are wrapped in `InstrumentedProcessError`. + The process is not restarted, so client's requests will be handled by the same process. + We believe that the Instrumented process state is consistent but in some tricky situations it _may be not_. + These situations should be reported as bugs. +`InstrumentedProcessError` also means +that there is an Instrumented process internal issue that should be handled by the client object +(in the Engine process). +The issue may occur because the client provides the wrong configuration or parameters, +but the Instrumented process cannot exactly determine what's wrong with the client's data: +one can find a description of the phase the exception has been thrown from. + +* `UtConcreteExecutionResult` + +If the Instrumented process performs well, +or something is broken but the Instrumented process knows exactly what is wrong with the input, `UtConcreteExecutionResult` is returned. +The Instrumented process guarantees that the state is _consistent_. +A `UtConcreteExecutionResult::result` field helps to find the exact reason for a failure: +* `UtSandboxFailure` — permission violation; +* `UtTimeoutException` — test execution time exceeds the provided time limit (`UtConcreteExecutionData::timeout`); +* `UtExecutionSuccess` — successful test execution; +* `UtExplicitlyThrownException` — explicitly thrown exception for a target method (via `throw` instruction); +* `UtImplicitlyThrownException` — implicitly thrown exception for a target method (`NPE`, `OOB`, etc., or an exception thrown inside the system library). + +## Error handling implementation + +The pipeline of `UtExecutionInstrumentation::invoke` includes 6 phases: +1. `ValueConstructionPhase` — constructs values from the models; +2. `PreparationPhase` — prepares statics, etc.; +3. `InvocationPhase` — invokes the target method; +4. `StatisticsCollectionPhase` — collects coverage and execution-related data; +5. `ModelConstructionPhase` — constructs the result models from the heap objects (`Any?`); +6. `PostprocessingPhase` — restores statics, clears mocks, etc. + +Each phase can throw two kinds of exceptions: +- `ExecutionPhaseStop` — indicates that the phase tries to stop the invocation pipeline completely because it already has a result. The returned result is the `ExecutionPhaseStop::result` field. +- `ExecutionPhaseError` — indicates that an unexpected error has occurred during the phase execution, and this error is rethrown to the Engine process. + +`PhasesController::computeConcreteExecutionResult` then matches on the exception type: +* it rethrows the exception if the type is `ExecutionPhaseError`, +* it returns the result if type is `ExecutionPhaseStop`. + +## Timeout + +Concrete execution is limited in time: the `UtExecutionInstrumentation::invoke` method is subject to timeout as well. + +For `UtExecutionInstrumentation` in the Instrumented process, we wrap the phases that can take a long time with the `executePhaseInTimeout` block. +This block tracks the elapsed time. +If a phase wrapped with this block exceeds the timeout, it returns `TimeoutException`. + +One cannot be sure that the cancellation request immediately breaks the invocation pipeline inside the Instrumented process. +Invocation is guaranteed to finish within timeout. +It may or _may not_ finish earlier. +The request that has been sent to the Instrumented process is _uncancellable_ by design. + +Even if the `TimeoutException` occurs, the Instrumented process is ready to process the new requests. \ No newline at end of file diff --git a/docs/Sandboxing.md b/docs/Sandboxing.md new file mode 100644 index 0000000000..15d2961146 --- /dev/null +++ b/docs/Sandboxing.md @@ -0,0 +1,110 @@ +# Sandboxing tests with Java Security Manager + +## What is sandboxing? + +Sandboxing is a security technique to find unsafe code fragments and prevent them from being executed. + +What do we mean by "unsafe code" in Java? The most common forbidden actions are: + +* working with files (read, write, create, delete), +* connecting to [sockets](https://github.com/UnitTestBot/UTBotJava/issues/792), +* invoking `System.exit()`, +* accessing system properties or JVM properties, +* using reflection. + +## Why do we need sandboxing for test generation? + +During test generation, UnitTestBot executes the source code with the concrete values. All the fuzzer runs require +concrete execution and some of the symbolic execution processes invoke it as well. If the source code contains +potentially unsafe operations, executing them with the concrete values may lead to fatal errors. It is safer to catch +these operations and break the concrete execution process with `AccessControlException` thrown. + +## What do the sandboxed tests look like? + +When the source code fragments are suspicious and the corresponding test generation processes are interrupted, the tests with the `@Disabled` annotation and a stack trace appear in the output: + + public void testPropertyWithBlankString() { + SecurityCheck securityCheck = new SecurityCheck(); + + /* This test fails because method [com.company.security.SecurityCheck.property] produces [java.security.AccessControlException: access denied ("java.util.PropertyPermission" " " "read")] + java.security.AccessControlContext.checkPermission(AccessControlContext.java:472) + java.security.AccessController.checkPermission(AccessController.java:886) + java.lang.SecurityManager.checkPermission(SecurityManager.java:549) + java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1294) + java.lang.System.getProperty(System.java:719) + com.company.security.SecurityCheck.property(SecurityCheck.java:32) */ + } + +## How does UnitTestBot sandbox code execution? + +UnitTestBot for Java/Kotlin uses [Java Security Manager](https://docs.oracle.com/javase/tutorial/essential/environment/security.html) for sandboxing. In general, the Security Manager allows applications to implement a security policy. It determines whether an operation is potentially safe or not and interrupts the execution if needed. + +In UnitTestBot the **secure mode is enabled by default**: only a small subset of runtime permissions necessary for +test generation are given (e.g. fields reflection is permitted by default). To extend the list of permissions learn +[How to handle sandboxing](#How-to-handle-sandboxing). + +Java Security Manager monitors [all the code](https://github.com/UnitTestBot/UTBotJava/issues/791) for the risk of performing forbidden operations, including code in _class constructors, private methods, static blocks, [threads](https://github.com/UnitTestBot/UTBotJava/issues/895)_, and combinations of all of the above. + +## How to handle sandboxing + +You can **add permissions** by creating and editing the `~\.utbot\sandbox.policy` file. Find more about [Policy File and Syntax](https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html#Examples) and refer to the [Full list of permissions](https://docs.oracle.com/javase/1.5.0/docs/guide/security/spec/security-spec.doc3.html) to choose the proper approach. + +If the permission was added but somehow [not recognized](https://github.com/UnitTestBot/UTBotJava/issues/796), the UnitTestBot plugin will fail to start and generate no tests. + +If you are sure you want the code to be executed as is (**including the unsafe operations!**) you can **turn sandboxing off**: + +* You can add `AllPermission` to `~\.utbot\sandbox.policy`. Be careful! +* Alternatively, you can add `useSandbox=false` to `~\.utbot\settings.properties`. Create this file manually if you don't have one. Find [more information](https://github.com/UnitTestBot/UTBotJava/pull/857) on how to manage sandboxing to test the UnitTestBot plugin itself. + +It is reasonable to regard the `@Disabled` tests just as supplemental information about the source code, not as the tests for actual usage. + +## How to improve sandboxing + +For now there are several unsolved problems related to sandboxing in UnitTestBot: + +1. We need to replace Java Security Manager with our own tool. + + [Java Security Manager is deprecated since JDK 17](https://openjdk.org/jeps/411) and is subject to removal in some + future version. It is still present in JDK 19 but with limited functionality. E.g., in Java 18, a Java application or library is prevented from dynamically installing a Security Manager unless the end user has explicitly opted to allow it. Obviously, we cannot rely upon the deprecated tool and need to create our own one. + + +2. We need to provide a unified and readable description for disabled tests. + + UnitTestBot supports three testing frameworks and their annotations are slightly different: + +JUnit 4: `@Ignore("")` + +JUnit 5: `@Disabled("")` + +TestNG: `@Ignore` as an alternative to `@Test(enabled=false)` + +* How should we unify these annotations? +* How should we show info in Javadoc comments? +* Do we need to print a stack trace? + + +3. We need to add emulation for restricted operations (a kind of mocks) + + Emulating unsafe operations will allow UnitTestBot to generate useful tests even for the sandboxed code and run them instead of disabling. + +4. We need to provide a user with the sandboxing settings. + + The UnitTestBot plugin UI provides no information about configuring the behavior of Security Manager. Information on [How to + handle +sandboxing](#How-to-handle-sandboxing) is available only on GitHub. + +* Should we add Sandboxing (or Security Manager) settings to plugin UI? E.g.: **File operations: Forbidden / Allowed / Emulated in sandbox**. + +* Should we add a hyperlink to a piece of related documentation or to the `~\.utbot\sandbox.policy` file? + +## How to test sandboxing + +See the [short manual testing scenario](https://github.com/UnitTestBot/UTBotJava/pull/625) and the [full manual testing checklist](https://github.com/UnitTestBot/UTBotJava/issues/790). + +## Related links + +Initial feature request: [Add SecurityManager support to block suspicious code #622](https://github.com/UnitTestBot/UTBotJava/issues/622) + +Pull request: [Add SecurityManager support to block suspicious code #622 #625](https://github.com/UnitTestBot/UTBotJava/pull/625) + +Improvement request: [Improve sandbox-relative description in generated tests #782](https://github.com/UnitTestBot/UTBotJava/issues/782) \ No newline at end of file diff --git a/docs/SettingsProperties.md b/docs/SettingsProperties.md new file mode 100644 index 0000000000..3cc80ef60a --- /dev/null +++ b/docs/SettingsProperties.md @@ -0,0 +1,86 @@ +# UnitTestBot settings + +First, let's define "**settings**" as the set of "**properties**". +Each property is a _key=value_ pair, and it may be represented as source code or plain text stored in a file. +This property set affects the key aspects of UnitTestBot behavior. + +UnitTestBot is available as +- an IntelliJ IDEA plugin, +- a continuous integration (CI) tool, +- a command-line interface (CLI). + +These three application types have low-level _**core**_ settings. The plugin also has per-project _**plugin-specific**_ settings. + +## Core settings + +Core settings are persisted in the **_settings file_**: `{userHome}/.utbot/settings.properties`. This file is designed for reading only. + +The defaults for the core settings are provided in source code (namely in `UtSettings.kt`) so the file itself may be absent or exist with a few of the customized properties only. For example, a file with just one line like `utBotGenerationTimeoutInMillis=15000` is valid and useful. + +## Plugin-specific settings + +IDE persists the plugin-specific settings automatically (per project!) in the **plugin configuration file**: `{projectDir}/.idea/utbot-settings.xml`. Nobody is expected to edit this file manually. + +At the moment, the core and plugin-specific settings have very small intersection (i.e. the keys of different levels control the same behavior aspects). +If they still intersect, the core settings should provide the defaults for the plugin-specific settings. +As for now, this concept is partially implemented. + +## Property categories + +A developer usually represents the new feature settings as a subset of properties and has to choose the proper "level" for them. In practice, we have these property categories: + +- **Hardcoded directly as constants in source code** +_One can build the plugin with their own hardcoded preset._ +- **Experimental or temporary** +_These properties can disappear from the settings file or jump back to the hardcoded constants. We do not expect the end user to change these properties, but it is still possible to specify them in the settings file._ +- **Engine-level tuning with reasonable defaults** +_Designed for low-level tuning during contests, etc._ +- **Rarely used, good to be changed once per PC** +- **Project-level setup in IDE** +_The end user can change them via **File** > **Settings** > **Tools** > **UnitTestBot**_. +- **Small set of per-generation options** + +Thereby, some properties can be considered as public API while the rest are pretty "internal". + +The end user has three places to change UnitTestBot behavior: +1. Settings file, which is PC-wide — read by all the UnitTestBot instances across PC. +For example, CLI and two different projects in IDE will re-use it. +2. Plugin settings UI (**File** > **Settings** > **Tools** > **UnitTestBot**). +3. Controls in the **Generate Tests with UnitTestBot window** dialog. + +Properties from the plugin settings UI and the dialog are plugin-specific, and they are automatically persisted in `{projectDir}/.idea/utbot-settings.xml`. _Note:_ only non-default values are stored here. + +## Configuring UnitTestBot with auto-generated `settings.properties` + +Common users usually change UnitTestBot settings via UI: +* in the **Generate Tests with UnitTestBot** dialog, +* via **File** > **Settings** > **Tools** > **UnitTestBot**. + +Advanced users and contributors require advanced settings. + +### How to configure advanced settings: motivation to improve + +Advanced settings were not visible in UnitTestBot UI and were configurable only via `settings.properties`. +UnitTestBot did not provide this file by default, so you had to create it manually in your `{home}/.utbot` directory. +You could configure advanced settings here if you knew available options — they are listed in UnitTestBot source code, +namely, `UtSettings.kt`. As UnitTestBot is a developing product, it often gets new features and new settings +that UnitTestBot users sometimes are not aware of. + +### Implemented `settings.properties` improvements + +Currently, UnitTestBot generates a template `settings.properties` file with the up-to-date list of available setting +options, corresponding default values, and explicit descriptions for each option. + +This template file is auto-generated on the basis of `UtSettings.kt` doc comments. The template file consists of +the commented lines, so you can uncomment the line to enable the setting or easily get back to defaults. + +_Idea to be implemented: we can annotate properties in UtSettings.kt as @api to provide the template file with a narrow subset of properties._ + +Generating `settings.properties` is a part of a Gradle task in IntelliJ IDEA. The `settings.properties` file is +bundled with the published UnitTestBot plugin as a top-level entry inside the `utbot-intellij-{version}.jar` file. + +Upon IntelliJ IDEA start, the UnitTestBot plugin loads its settings and checks whether the template setting file exists +in the local file system as `{home}/.utbot/settings.properties`: +* If there is no such file, it is created (along with the hidden `{home}/.utbot` directory if needed). +* An existing file is updated with new settings and corresponding info if necessary. +* UnitTestBot does not re-write `settings.properties` if the file exists and has already been customized. \ No newline at end of file diff --git a/docs/SpeculativeFieldNonNullability.md b/docs/SpeculativeFieldNonNullability.md index b0985acb7c..0cd70fd67f 100644 --- a/docs/SpeculativeFieldNonNullability.md +++ b/docs/SpeculativeFieldNonNullability.md @@ -16,18 +16,31 @@ is desirable, as it increases the coverage, but it has a downside. It is possibl most of generated branches would be `NPE` branches, while useful paths could be lost due to timeout. Beyond that, in many cases the `null` value of a field can't be generated using the public API -of the class. This is particularly true for final fields, especially in system classes. -Automatically generated tests assign `null` values to fields in questions using reflection, +of the class. + +- First of all, this is particularly true for final fields, especially in system classes. +it is also often true for non-public fields from standard library and third-party libraries (even setters often do not +allow `null` values). Automatically generated tests assign `null` values to fields using reflection, but these tests may be uninformative as the corresponding `NPE` branches would never occur in the real code that limits itself to the public API. +- After that, field may be declared with some annotation that shows that null value is actually impossible. +For example, in Spring applications `@InjectMocks` and `@Mock` annotations on the fields of class under test +mean that these fields always have value, so `NPE` branches for them would never occur in real code. + + ## The solution To discard irrelevant `NPE` branches, we can speculatively mark fields we as non-nullable even they -do not have an explicit `@NotNull` annotation. In particular, we can use this approach to final -fields of system classes, as they are usually correctly initialized and are not equal `null`. +do not have an explicit `@NotNull` annotation. + +- In particular, we can use this approach to final and non-public +fields of system classes, as they are usually correctly initialized and are not equal `null` +- For Spring application, we use this approach for the fields of class +under test not obtained from Spring bean definitions -At the same time, we can't always add the "not null" hard constraint for the field: it would break +At the same time, for non-Spring classes, +we can't always add the "not null" hard constraint for the field: it would break some special cases like `Optional` class, which uses the `null` value of its final field as a marker of an empty value. @@ -38,18 +51,18 @@ no way to check whether the address corresponds to a final field, as the corresp of the global graph would refer to a local variable. The only place where we have the complete information about the field is this method. -We use the following approach. If the field is final and belongs to a system class, -we mark it as a speculatively non-nullable in the memory +We use the following approach. If the field belongs to a library class (according to `soot.SootClass.isLibraryClass`) +and is final or non-public, we mark it as a speculatively non-nullable in the memory (see `org.utbot.engine.Memory.speculativelyNotNullAddresses`). During the NPE check we will add the `!isSpeculativelyNotNull(addr(field))` constraint to the `NPE` branch together with the usual `addr(field) == null` constraint. -For final fields, these two conditions can't be satisfied at the same time, as we speculatively -mark final fields as non-nullable. As a result, the NPE branch would be discarded. If a field -is not final, the condition is satisfiable, so the NPE branch would stay alive. +For final/non-public fields, these two conditions can't be satisfied at the same time, as we speculatively +mark such fields as non-nullable. As a result, the NPE branch would be discarded. If a field +is public or not final, the condition is satisfiable, so the NPE branch would stay alive. -We limit this approach to the system classes only, because it is hard to speculatively assume -something about non-nullability of final fields in the user code. +We limit this approach to the library classes only, because it is hard to speculatively assume +something about non-nullability of final/non-public fields in the user code. The same approach can be extended for other cases where we want to speculatively consider some fields as non-nullable to prevent `NPE` branch generation. diff --git a/docs/StaticInitializersAnalysis.md b/docs/StaticInitializersAnalysis.md new file mode 100644 index 0000000000..7c0507201f --- /dev/null +++ b/docs/StaticInitializersAnalysis.md @@ -0,0 +1,73 @@ +# Symbolic analysis of static initializers + +## Problem + +Before the [Prohibit to set static fields from library classes](https://github.com/UnitTestBot/UTBotJava/pull/699) +change was implemented, every static field outside the `` block (the so-called _meaningful_ static fields) +was stored in `modelBefore` and `modelAfter`. These _meaningful_ static fields were set (and reset for test isolation) during code generation. This led to explicit static field initializations, which looked unexpected for a user. For example, an `EMPTY` static field from the `Optional` class might be set for the following method under test + +```java +class OptionalEmptyExample { + public java.util.Optional optionalExample(boolean isEmpty) { + return isEmpty ? java.util.Optional.empty() : java.util.Optional.of(42); + } +} +``` + +like: + +```java +setStaticField(optionalClazz, "EMPTY", empty); +``` + +**Goal**: we should not set such kind of static fields with initializers. + +## Current solution + +Having merged [Prohibit to set static fields from library classes](https://github.com/UnitTestBot/UTBotJava/pull/699) +, we now do not explicitly set the static fields of the classes from the so-called _trusted_ libraries (by default, +they are JDK packages). This behavior is guided by the `org.utbot.framework. +UtSettings#getIgnoreStaticsFromTrustedLibraries` setting. Current solution possibly **leads to coverage regression** +and needs to be investigated: [Investigate coverage regression because of not setting static fields](https://github.com/UnitTestBot/UTBotJava/issues/716). +So, take a look at other ways to fix the problem. + +## Alternative solutions + +### Use concrete values as soft constraints _(not yet implemented)_ + +The essence of the problem is assigning values to the static fields that should be set at runtime. To prevent it, +we can try to create models for the static fields according to their runtime values and filter out the static fields +that are equal to runtime values, using the following algorithm: + +1. Extract a concrete value for a static field. +2. Create `UtModel` for this value and store it. +3. Transform the produced model to soft constraints. +4. Add them to the current symbolic state. +5. Having resolved `stateBefore`, compare the resulting `UtModel` for the static field with the stored model and then drop the resulting model from `stateBefore` if they are equal. + +### Propagate information on the read static fields _(not yet implemented)_ + +We can define the _meaningful_ static fields in a different way: we can mark the static fields as _meaningful_ if only they affect the method-under-test result. To decide if they do: + +- find out whether a given statement reads a specific static value or not and store this info, +- while traversing the method graph, propagate this stored info to each of the following statements in a tree, +- upon reaching the `return` statement of the method under test, mark all these read static fields as _meaningful_. + +### Filter out static methods: check if they affect `UtExecution` _(not yet implemented)_* +Having collected all executions, we can analyze them and check whether the given static field affects the result of a current execution. Changing the static field value may have the same effect on every execution or no effect at all. It may also be required as an entry point during the executions (e.g., an _if_-statement as the first statement in the method under test): + +```java +class AlwaysThrowingException { + public void throwIfMagic() { + if (ClassWithStaticField.staticField == 42) { + throw new RuntimeException("Magic number"); + } + } +} + +class ClassWithStaticField { + public final static int staticField = 42; +} +``` + +*This solution should only be used with the [propagation](#propagate-information-on-the-read-static-fields) solution. \ No newline at end of file diff --git a/docs/Summarization module.md b/docs/Summarization module.md new file mode 100644 index 0000000000..b9b3d6cab6 --- /dev/null +++ b/docs/Summarization module.md @@ -0,0 +1,80 @@ +# Summarization module + +## Overview + +UnitTestBot minimizes the number of tests so that they are necessary and sufficient, but sometimes there are still a lot of them. Tests may look very similar to each other, and it may be hard for a user to distinguish between them. To ease test case comprehension, UnitTestBot generates summaries, or human-readable test descriptions. Summaries also facilitate navigation: they structure the whole collection of generated tests by clustering them into groups. + +Summarization module generates detailed meta-information: +- test method names +- testing framework annotations (including `@DisplayName`) +- Javadoc comments for tests +- cluster comments for groups of tests (_regions_) + +Javadoc comments can be rendered in two styles: as plain text or in a special format enriched with the [custom Javadoc tags](https://github.com/UnitTestBot/UTBotJava/blob/main/docs/summaries/CustomJavadocTags.md). + +If the summarization process fails due to an error or insufficient information, then the test method receives a unique name and no meta-information. + +The whole summarization subsystem is located in the `utbot-summary` module. + +## Implementation + +At the last stage of test generation process, the `UtMethodTestSet.summarize` method is called. +As input, this method receives the set of `UtExecution` models with empty `testMethodName`, `displayName`, and `summary` fields. It fills these fields with the corresponding meta-information, groups the received `UtExecution` models into clusters and generates cluster names. + +Currently, there are three main `UtExecution` implementations: +* `UtSymbolicExecution`, +* `UtFailedExecution`, +* `UtFuzzedExecution`. + +To construct meta-information for the `UtFuzzedExecution` models, the summarization module uses method parameters with their values and types as well as the return value type. To generate summaries for each `UtSymbolicExecution`, it uses the symbolic code analysis results. + +Let's describe this process in detail for `UtSymbolicExecution` and `UtFuzzedExecution`. + +### Constructing meta-information for `UtSymbolicExecution` + +1. **Producing _Jimple statements_.** + For each method under test (or MUT), the symbolic execution engine generates `UtMethodTestSet` consisting of `UtExecution` models, i.e. a test suite consisting of unit tests. A unit test (or `UtExecution`) in this suite is a set of execution steps that traverses a particular path in the MUT. An execution `Step` contains info on a statement, the depth of execution step and an execution decision. +* A statement (`stmt`) is a Jimple statement, provided with the [Soot](https://github.com/soot-oss/soot) framework. A Jimple statement is a simplified representation of the Java program that is based on the three-address code. The symbolic engine accepts Java bytecode and transforms it to the Jimple statements for the analytical traversal of execution paths. +* The depth of execution step (`depth`) depicts an execution depth of the statement in a call graph where the MUT is a root. +* An execution decision (`decision`) is a number indicating the execution direction inside the control flow graph. If there are two edges coming out of the execution statement in the control flow graph, a decision number shows what edge is chosen to be executed next. + +2. **_Tagging_.** + For each pair of `UtMethodTestSet` and its source code file, the summarization module identifies unique execution steps, recursions, iteration cycles, skipped iterations, etc. These code constructs are marked with tags or meta-tags, which represent the execution paths in a structural view. The summarization module uses these tags directly to create meta-information, or summaries. + +At this moment, the summarization module is able to assign the following tags: +- Uniqueness of a statement: + - _Unique_: no other execution path in the cluster contains this step, so only one execution triggers this statement in its cluster. + - _Common_: all the paths execute these statements. + - _Partly Common_: only some executions in a cluster contain this step. +- The decision in the CFG (branching): _Right_, _Left_, _Return_ +- The number of statement executions in a given test +- Dealing with loops: _starting/ending an iteration_, _invoking the recursion_, etc. + +We use our own implementation of the [DBSCAN](https://en.wikipedia.org/wiki/DBSCAN) clustering algorithm with the non-euclidean distance measure based on the Minimum Edit Distance to identify _unique_, _common_ and _partly common_ execution steps. Firstly, we manually divided execution paths into groups: +- successfully executed paths (only this group is clustered into different regions with DBSCAN) +- paths with expected exceptions +- paths with unexpected exceptions +- other groups with errors and exceptions based on the given `UtResult` + +3. **Building _sentences_.** + _Sentences_ are the blocks for the resulting summaries. + To build the _sentence_, the summarization module +- parses the source file (containing the MUT) using [JavaParser](https://javaparser.org/) to get AST representations; +- maps the AST representations to Jimple statements (so each statement is mapped to AST node); +- builds the _sentence_ blocks (to provide custom Javadoc tags or plain-text mode); +- builds the _final sentence_ (valid for plain-text mode only); +- generates the `@DisplayName` annotation and test method names using the following rule: find the last _unique_ statement in each path (preferably, the condition statement) that has been executed once (being satisfied or unsatisfied); then the AST node of this statement is used for naming the execution; +- builds the cluster names based on the _common_ execution paths. + +### Constructing meta-information for `UtFuzzedExecution` + +For `UtFuzzedExecution`, meta-information is also available as test method names, `@DisplayName` annotations, Javadoc comments, and cluster comments. + +The difference is that clustering tests for `UtFuzzedExecution` is based on `UtResult`. No subgroups are generated for the successfully completed tests. + +The algorithm for generating meta-information is described in the `ModelBasedNameSuggester` class, which is the registration point for `SingleModelNameSuggester` interface. This interface is implemented in `PrimitiveModelNameSuggester` and `ArrayModelNameSuggester`. + +Depending on the received `UtExecutionResult` type, `ModelBasedNameSuggester` produces the basic part of the method name or the `@DisplayName` annotation. `UtFuzzedExecution` provides `FuzzedMethodDescription` and `FuzzedValue` that supplement the generated basic part for test name with information about the types, names and values of the MUT parameters. + +_Note:_ test method names and `@DisplayName` annotations are generated if only the number of MUT parameters is no more than three, otherwise they are not generated. + diff --git a/docs/TaintAnalysis.md b/docs/TaintAnalysis.md new file mode 100644 index 0000000000..67c4db3747 --- /dev/null +++ b/docs/TaintAnalysis.md @@ -0,0 +1,527 @@ +# Taint analysis + +## Introduction to the technique + +Taint analysis allows you to track the propagation of unverified external data through the program. +If this kind of data reaches critical code sections, it may lead to vulnerabilities, including SQL injections, +cross-site scripting (XSS) and others. +Attackers can use these vulnerabilities to disrupt correct system operation, get confidential data, or perform other unauthorized operations. +Taint analysis helps to find these mistakes at the compilation stage. + +The key idea of the approach is that any variable an external user can change stores a potential security +threat. If the variable is used in some expression, then the value of this expression also becomes suspicious. +The algorithm tracks situations when the variables marked as suspicious are used in dangerous +command execution, for example, in direct queries to a database or an operating system. + +Taint analysis requires a configuration, where you assign one of the following +roles to each method in a program. + +- Taint source — a source of unverified data. + For example, it can be a method for reading user input, or a method for getting a parameter of an incoming HTTP + request. + The Taint source execution result is marked. The method semantics determines the mark to be applied + to the variable. + The name of the mark can be completely arbitrary, since it is chosen by the one who writes the + configuration. + For example, according to configuration, the `getPassword()` method marks its return value with a "sensitive-data" + mark. +- Taint pass — a function that marks the return value taking into account the marks in its arguments. + Depending on the implementation, marks can be applied not only to method results but also to a `this` object, and to + input parameters. For example, you can configure the `concat(String a, String b)` concatenation method + to mark its result with all the marks from `a` and `b`. +- Taint cleaner — a function that removes a given set of marks from the passed arguments. + Most often, this is a kind of validation method that verifies that the user has entered data in the expected + format. + For example, the `validateEmail(String email)` method removes the "XSS" mark upon successful check completion, + because no unverified data in the `email` object that can now lead to cross-site + scripting vulnerability. +- Taint sink — a receiver, a critical section of an application. + It can be a method that accesses a database or a file system directly, or perform other potentially dangerous + operations. + For the Taint sink, you can set a list of marks. Variables with specified marks should not leak into this taint sink. + For example, if a value marked as "sensitive-data" is passed to a logging function, which prints its arguments + directly to the console, then this is a developer mistake, since data leaks. + +The taint analysis algorithm scans the data flow graph, trying to detect a route between a method from a set of Taint +sources and a method from Taint sinks. The UnitTesBot taint analysis feature is implemented inside the symbolic engine +to avoid a large number of false positives. + +**Example** + +Consider an example of a simple function with an SQL injection vulnerability inside: +if an attacker enters the string `"name'); DROP TABLE Employees; --"` into the variable name, then it will be possible +to delete the `Employees` table with the data in it. + +```java +class Example { + void example(Connection connection) { + Scanner sc = new Scanner(System.in); + String name = sc.nextLine(); + Statement stmt = connection.createStatement(); + String query = "INSERT INTO Employees(name) VALUES('" + .concat(name) + .concat("')"); + stmt.executeUpdate(query); + } +} +``` + +For taint analysis, you have to set the configuration. + +- Taint source is a `java.util.Scanner.nextLine` method that adds a "user-input" mark to the returned value. +- Taint pass is a `java.lang.String.concat` method that passes the "user-input" marks through itself received + either from the first argument or from the object on which this method is called (`this`). +- Taint sink is a `java.sql.Statement.executeUpdate` method that checks variables marked as "user-input". + +Any correct implementation of the taint analysis algorithm should detect a mistake in this code: the variable with +the "user-input" mark is passed to `executeUpdate` (the sink). + +Note that the algorithm is not responsible for detecting specific data that an attacker could +enter to harm the program. It only discovers the route connecting the source and the sink. + +## UnitTestBot implementation + +No unified configuration format is provided for taint analysis in the world, and all static analyzers describe their +own way of configuration. Thus, we provide a custom configuration scheme to describe the rules: sources, passes, +cleaners, and sinks. + +### Configuration: general structure + +The general structure of the configuration format (based on YAML) is presented below. + +```yaml +sources: + - + - + - <...> +passes: + - + - <...> +cleaners: + - + - <...> +sinks: + - + - <...> +``` + +That is, these are just lists of rules related to a specific type. + +Each rule has a set of characteristics. + +- The unique identifier of the method that this rule describes. + It consists of the method's full name, including the package name and the class name, + as well as the signature of the method — a set of argument types (the `signature` key in the YAML file). +- Some `conditions` that must be met during execution for the rule to work. +- A set of `marks` that the rule uses. +- A set of specific mark management actions that occur when the rule is triggered (`add-to`, `get-from`, + `remove-from`, or `check`, depending on the semantics of the rule). + +For example, the rule for the taint source can look like this. + +```yaml +com.abc.ClassName.methodName: + signature: [ , _, ] + conditions: + arg1: + not: [ -1, 1 ] + add-to: [ this, arg2, return ] + marks: user-input +``` + +This rule is defined for a method named `methodName` from the `ClassName` class located in the `com.abc` package. +The method takes exactly 3 arguments: the first one has the `int` type, the second can be anything, +and the last one has the `java.lang.Object` type. +The `signature` key may not be specified, then any `methodName` overload is appropriate. + +The rule is triggered when the first argument (`arg1`) is not equal to either -1 or 1 as specified by the `conditions` +key (the list is interpreted as logical OR). +This parameter is optional, if it is absent, no conditions are checked. + +The described source adds a "user-input" mark to the variables corresponding to `this`, `arg2` and `return`: +to the `ClassName` class object on which `methodName` is called, to the second argument of the function +and to the return value. Moreover, the `add-to` and `marks` keys can contain both a list, and a single value. + +The other rule types have the same syntax as the source, except for the `add-to` key. + +- Taint pass transfers marks from one set of objects to another, so two keys are defined for it: + `get-from` and `add-to`, respectively. The marks specified in `marks` are added on `add-to` if there is a mark in + `get-from`. +- Taint cleaner removes marks from objects, so its key is called `remove-from`. +- Taint sink checks for the marks in variables, which locates under the `check` key. + +### Configuration: details + +Fully qualified method names can be written in one line or using nested structure: the package name is specified first, +then the class name appears, and finally, there is the method name itself. + +```yaml +- com.abc.def.Example.example: ... +``` + +or + +```yaml +- com: + - abc.def: + - Example.example: ... +``` + +Note that regular expressions in names are not supported yet. + +The `add-to`, `get-from`, `remove-from`, and `check` fields specify the objects (or entities) to be marked. +You can specify only one value here or a whole list. + +Possible values are: + +- `this` +- `arg1` +- `arg2` +- ... +- `return` + +The user can define arbitrary mark names or specify an empty list (`[]`) for all possible marks. + +```yaml +passes: + - java.lang.String.concat: + get-from: this + add-to: return + marks: [ user-input, sensitive-data, my-super-mark ] +``` + +or + +```yaml +passes: + - java.lang.String.concat: + get-from: this + add-to: return + marks: [ ] # all possible marks +``` + +To check the conformity to `conditions`, you can set: + +- the specific values of method arguments +- their runtime types + +Values can be set for the following types: `boolean`, `int`, `float` or `string` (and `null` value for all nullable +types). + +The full type name must be specified in the angle brackets `<>`. + +The `conditions` field specifies runtime conditions for arguments (`arg1`, `arg2`, ...). +Conditions can also be specified for `this` and `return` if it makes sense. +For sinks, checking conditions for a return value makes no sense, so this functionality is not supported. + +```yaml +conditions: + this: # this should be java.lang.String + arg1: "test" # the first argument should be equal to "test" + arg2: 227 # int + arg3: 227.001 # float + arg4: null # null + return: true # return value should be equal to `true` +``` + +Values and types can be negated using the `not` key, as well as combined using lists (`or` semantics). + +Nesting is allowed. + +```yaml +conditions: + this: [ "in", "out" ] # this should be equal to either "in" or "out" + arg1: [ , ] # arg1 should be int or float + arg2: { not: 0 } # arg2 should not be equal to 0 + arg3: + not: [ 1, 2, 3, 5, 8 ] # should not be equal to any of the listed numbers + arg4: [ "", { not: } ] # should be an empty string or not a string at all +``` + +If several rules are suitable for one method call, they are all applied. + +**Overall example** + +```yaml +sources: + - com: + - abc: + - method1: + signature: [ _, _, ] + add-to: return + marks: xss + - method1: + signature: [ , _, ] + add-to: [ this, return ] + marks: sql-injection + - bca.method2: + conditions: + this: + not: "in" + add-to: return + marks: [ sensitive-data, xss ] + +passes: + - com.abc.method2: + get-from: [ this, arg1, arg3 ] + add-to: return + marks: sensitive-data + - org.example.method3: + conditions: + arg1: { not: "" } + get-from: arg1 + add-to: [ this, return ] + marks: sql-injection + +cleaners: + - com.example.pack.method7: + conditions: + return: true + remove-from: this + marks: [ sensitive-data, sql-injection ] + +sinks: + - org.example: + - log: + check: arg1 + marks: sensitive-data + - method17: + check: [ arg1, arg3 ] + marks: [ sql-injection, xss ] +``` + +**Usage examples** + +`java.lang.System.getenv` is a source of the “environment” mark. There are two overloads of this method: with one string +parameter and no parameters at all. We want to describe only the first overload: + + ```yaml + sources: + - java.lang.System.getenv: + signature: [ ] + add-to: return + marks: environment + ``` + +`java.lang.String.concat` is a pass-through only if `this` is marked and not equal to `""`, or if `arg1` is marked and +not equal to `""`: + + ```yaml + passes: + - java.lang.String: + - concat: + conditions: + this: { not: "" } + get-from: this + add-to: return + marks: sensitive-data + - concat: + conditions: + arg1: { not: "" } + get-from: arg1 + add-to: return + marks: sensitive-data + ``` + +If you want to define a `+` operator for strings as taint pass, you should write the following rules: + +```yaml +passes: + - java.lang.StringBuilder.append: + get-from: arg1 + add-to: this + marks: [ ] + - java.lang.StringBuilder.toString: + get-from: this + add-to: return + marks: [ ] +``` + +`java.lang.String.isEmpty` is a cleaner only if it returns `true`: + + ```yaml + cleaners: + - java.lang.String.isEmpty: + conditions: + return: true + remove-from: this + marks: [ sql-injection, xss ] + ``` + +Suppose that the `org.example.util.unsafe` method is a sink for the “environment” mark if the second argument is +an `Integer` and equal to zero: + + ```yaml + sinks: + - org.example.util.unsafe: + signature: [ _, ] + conditions: + arg2: 0 + check: arg2 + marks: environment + ``` + +The configuration above checks the type at compile time, but sometimes we want to check the type at runtime: + + ```yaml + sinks: + - org.example.util.unsafe: + conditions: + arg2: + not: [ { not: 0 }, { not: } ] + check: arg2 + marks: environment + ``` + +Perhaps explicit `and` for `conditions` will be added in the future. + +### Algorithm implementation details + +The main idea of the implemented approach is that each symbolic variable is associated with a taint vector — a 64-bit +value, where each `i` bit is responsible for the presence of a mark with the number `i` in this object. +After that, during the symbolic execution, these mappings are maintained and updated in accordance with +the classical taint analysis algorithm. + +The implementation mostly affects the `Traverser` and `Memory` classes, as well as the new `TaintMarkRegistry` +and `TaintMarkManager` classes. The diagram below shows a high-level architecture of the taint module (in the actual +code, the implementation is a bit different, but to understand the idea, the diagram is greatly simplified). + + + +The `TaintMarkRegistry` class stores a mapping between the mark name and its ordinal number from 0 to 63. +The number of marks is limited to 64. However, firstly, this is enough for almost any reasonable example, +and secondly, the decision was made due to performance issues — operations with the `Long` data type are +performed much faster than if a bit array was used. + +The `TaintModel` component (data classes at `org.utbot.taint.model`) is responsible for providing access +to the configuration. In particular, it defines a way to convert conditions (the value of the `conditions` key +in a YAML document) into logical expressions over symbolic variables. + +`Memory` stores the values of the taint bit-vectors for symbolic variables. Only simple methods were implemented there +(functions to update vectors and get them at the address of a symbolic object). +All the complex logic of adding and removing marks, based on taint analysis theory, +was written in a separate `TaintMarkManager` class. In other words, this class wraps low-level memory work into +domain-friendly operations. + +The information about the marked variables is updated during the `Traverser` work. Before each `invoke()` +instruction corresponding to the method call in the user code, a special `Traverser. +processTaintSink` handler is called, and after the `invoke` instruction, the `Traverser.processTaintSource`, +`Traverser.processTaintPass` and `Traverser.processTaintCleaner` handlers are called. This order is set because all the +rules, except tose for the sinks, need the result of the function. At the same time, the fact of transferring +the tainted data occurs when launching the sink function, therefore, you can report the vulnerability +found even before it is executed. + +The listed rule handlers get the configuration and perform the taint analysis semantics. The `processTaintSink` method +requests information from the `TaintMarkManager` about the marks already set and adds constraints to the SMT +solver: the satisfiability corresponds to the defect detection. The other handlers modify the symbolic +`Memory` through the `TaintMarkManager`, adding and removing marks from the selected symbolic variables. + +### Code generator modification + +UnitTestBot produces unit tests (and the SARIF reports). `CodeGenerator` is launched on each +found test case, and generates the test (as Java code). Moreover, the test, which leads to throwing an unhandled exception, +should not pass. Taint analysis errors are not real from the language perspective, since they +are not real exceptions. However, we still have to highlight such tests as failed. The code generator was modified +so that an artificial error was added at the end of each test to ensure a fail (the same strategy +was used in the integer overflow detection). + +```java +fail("'java.lang.String' marked 'user-input' was passed into 'Example.example' method"); +``` + +The solution allows us to automatically integrate with the SARIF reports and to visualize the results +in the IntelliJ IDEA _Problems_ tool window. The found test case is treated as a real exception, +and all the necessary logic has already been written for them. + +**Example** + +Consider the code below. + +```java +public class User { + + String getLogin() { /* some logic */ } + + String getPassword() { /* some logic */ } + + String userInfo(String login, String password) { + return login + "#" + password; + } + + void printUserInfo() { + var login = getLogin(); + var password = getPassword(); + var info = userInfo(login, password); + System.out.println(info); + } +} +``` + +The `getPassword` method returns sensitive data that should never leak out of the application, but the programmer prints +them to the `stdout`, which is a serious mistake. First, we write the corresponding configuration and save it to the +`./.idea/utbot-taint-config.yaml` file for the analyzer to read from. + +```yaml +sources: + - User.getPassword: + add-to: return + marks: sensitive-data + +passes: + - User.userInfo: + get-from: [ arg1, arg2 ] + add-to: return + marks: [ ] # all + +sinks: + - java.io.PrintStream.println: + check: arg1 + marks: sensitive-data +``` + +Then we enable taint analysis in settings and run the UnitTestBot plugin in IntelliJ IDEA. + + + +Generated code: + +```java +public final class UserTest { + // some omitted code + + ///region SYMBOLIC EXECUTION: TAINT ANALYSIS for method printUserInfo() + + @Test + @DisplayName("printUserInfo: System.out.println(info) : True -> DetectTaintAnalysisError") + public void testPrintUserInfo_PrintStreamPrintln_1() { + User user = new User(); + + user.printUserInfo(); + fail("'java.lang.String' marked 'sensitive-data' was passed into 'PrintStream.println' method"); + } + + ///endregion +} +``` + +We can see the detected problem in the _Problems_ tool window: + + + +**A brief explanation** + +Upon executing the `getPassword` method, the symbol corresponding to the password variable +is marked as "sensitive-data" (a zero bit is set to 1 in its taint vector). Upon calling `userInfo`, the `info` +variable is also marked, since `userInfo` is a taint pass that adds the marks from +both of its arguments to the return value. Before printing `info` to the console, the `processTaintSink` handler function adds a constraint +to the SMT solver, so that its satisfiability corresponds to throwing an artificial error. The logical +formula for +this path is satisfiable, so the analyzer reports an error detected, which we eventually observe. + +### Unit tests + +Taint analysis unit tests are located at the `./utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/` +directory. + +Test use examples are located at `utbot-sample/src/main/java/org/utbot/examples/taint`. Each example has its own +configuration file stored at `utbot-sample/src/main/resources/taint`. diff --git a/docs/UnitTestBotDecomposition.md b/docs/UnitTestBotDecomposition.md new file mode 100644 index 0000000000..ac118e9036 --- /dev/null +++ b/docs/UnitTestBotDecomposition.md @@ -0,0 +1,71 @@ +# UnitTestBot decomposition + +This document is a part of UnitTestBot roadmap for the nearest future. We plan to decompose the whole UnitTestBot mechanism into the following standalone systems. + +## Fuzzing platform + +Entry points: +* `org.utbot.fuzzing.Fuzzing` +* `fuzz` extension function + +Exit point: +overridden `Fuzzing#run` method + +Probable fields of use (without significant implementation changes): +1. Test generation +2. Taint analysis +3. Finding security vulnerabilities +4. Static analysis validation +5. Automatic input minimization +6. Specific input-output search + +## Symbolic engine platform + +Probable fields of use (without significant implementation changes): +1. Test generation +2. Taint analysis +3. Type inference + +A more abstract interface can be extracted. For instance, we can use the interface to solve type constraints for Python or other languages. +Currently, there are two levels of abstraction: +1. Java-oriented abstraction that is intended to mimic heap and stack memory. +2. More low-level and less Java-coupled API to store constraints for Z3 solver. + +There is a room for improvement, namely we can extract more high-level abstraction, which can be extended for different languages. + +Entry points: +* `org.utbot.engine.Memory` +* `org.utbot.engine.state.LocalVariableMemory` +* `org.utbot.engine.SymbolicValue` + +Exit point: +`org.utbot.engine.Resolver` → `UtModel` + +Another level of abstraction is `UtExpression`. Basically, it is a thin abstraction over Z3 solver. + +## Program synthesis system + +An implementation that allows `UtAssembleModel` to keep information about object creation in a human-readable format. Otherwise, the object state should be initiated with _Reflection_ or sufficient constructor call. The synthesizing process is built upon the UnitTestBot symbolic execution memory model and is supposed to preserve construction information during the analysis process. + +Entry and exit point: +`org.utbot.framework.synthesis.Synthesizer` + +## Program analysis system + +We use an outdated approach with the [Soot](https://github.com/soot-oss/soot) framework. It is not worth being extracted as a separate service. A good substitution is the [JacoDB](https://github.com/UnitTestBot/jacodb) library. Currently, this library provides an API to work with Java code, send queries, provide custom indexes, and so on. + +## Code generation system + +The current domain of code generation is specific for generating tests, though it could be reused for other purposes. Currently, the engine can be used to generate tests for different test frameworks. One can use the code generator to generate test templates inside the IntelliJ-based IDEs. + +Entry and exit point: +`org.utbot.framework.codegen.generator.CodeGenerator#generateAsStringWithTestReport` + +Note that for Spring projects `SpringCodeGenerator` is used. It supports both unit and integration tests generation. + +## SARIF report visualizer + +UnitTestBot represents the result of analysis using SARIF — the format that is widely used in the GitHub community. SARIF allows users to easily represent the results in the built-in GitHub viewer. Additionally, we provide our own SARIF report visualizer for IntelliJ IDEA. + +Entry and exit point: +`org.utbot.gradle.plugin.GenerateTestsAndSarifReportTask` \ No newline at end of file diff --git a/docs/UtUtilsClass.md b/docs/UtUtilsClass.md new file mode 100644 index 0000000000..af99a94bbe --- /dev/null +++ b/docs/UtUtilsClass.md @@ -0,0 +1,57 @@ +# UtUtils class + +## What are the utility methods + +_Utility methods_ implement common, often re-used operations which are helpful for accomplishing tasks in many +classes. In UnitTestBot, _utility methods_ include those related to creating instances, checking deep +equality, mocking, using lambdas and so on — miscellaneous methods necessary for generated tests. + +## Why to create UtUtils class + +Previously, UnitTestBot generated _utility methods_ for each test class when they were needed — and only those which +were necessary for the given class. They were declared right in the generated test class, occupying space. Generating multiple test classes often resulted in duplicating _utility methods_ and consuming even more space. + +For now UnitTestBot provides a special `UtUtils` class containing all _utility methods_ if at least one test class needs some of them. This class is generated once and the specific methods are imported from it if necessary. No need for _utility methods_ — no `UtUtils` class is generated. + +We create a separate `UtUtils` class for each supported language (if required). + +## What does it look like + +Here is an example of a documentation comment inherent to every `UtUtils` class: + +![Documentation](images/utbot_ututils_2.0.png) + +As one can see, the comment mentions two characteristics of the `UtUtils` class: + +1. _Version_ + +If the generated tests require additional _utility methods_, the +existing `UtUtils` class is upgraded and gets a new version number, which should be defined here: + +`org.utbot.framework.codegen.domain.builtin.UtilClassFileMethodProvider.UTIL_CLASS_VERSION` + +_2. Mockito support_ + +UnitTestBot uses [Mockito framework](https://site.mockito.org/) to implement mocking. When generated tests imply mocking, the +`deepEquals()` +_utility method_ should be configured — it should have a check: whether the compared object is a mock or not. That is why the `UtUtils` class for the tests with mocking differs from the one without mocking support. + +If you have previously generated tests with mocking, the next generated `UtUtils` class supports mocking as well — +even if +its version is upgraded or current tests do not need mocking, so that the existing tests can still +rely on the proper methods from `UtUtils` class. + +## Where to find it + +`UtUtils` class is usually located in the chosen **Test sources root** near the generated test classes. The corresponding package name mentions the language of the generated tests: e.g. `org.utbot.runtime.utils.java`. + +## How to test + +If you want to test `UtUtils` class generation using UnitTestBot project itself, refer to the [Manual testing of +UtUtils class generation #1233](https://github.com/UnitTestBot/UTBotJava/issues/1233). + +## How to improve + +UnitTestBot does not currently support generating tests for classes from multiple modules simultaneously. If this option was possible, we would probably have to generate a separate `UtUtils` class for each module. Perhaps we could find a special location for a `UtUtils` class reachable from every module. + +For now, you can generate separate `UtUtils` classes for different modules only if you manually choose the different **Test sources roots** when generating tests. \ No newline at end of file diff --git a/docs/UtbotFamilyChanges.md b/docs/UtbotFamilyChanges.md new file mode 100644 index 0000000000..e739aab937 --- /dev/null +++ b/docs/UtbotFamilyChanges.md @@ -0,0 +1,87 @@ +# Changes + +## Main settings + +| File | Changes | +|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `gradle.properties` | Add version parameters and IDE dependencies | +| `settings.gradle` | Rewrite to kts | + +## utbot-framework-api + +| File | Changes | +|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt` | Make `UtModel` open | +| `utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/CoverageApi.kt` | Add field `missedInstructions` to class `Coverage` (default empty) | + +## utbot-framework + +| File | Changes | +|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `utbot-framework/src/main/kotlin/org/utbot/engine/ValueConstructor.kt` | Add default else branch for Python and JS models in method `construct` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt` | Add default else branch for Python and JS models in method `assembleModel` | + +## utbot-framework > codegen + +| File | Changes | +|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt` | Make class `Import` abstract (for python imports), make class `TestFramework` open, field `assertEquals` and methdod `assertionId` open. Add nullable field `testSuperClass` to `TestFramework` (contains superclass for test class). | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Keywords.kt` | Move function `getLanguageKeywords` into `CgLanguageAssistant` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/CodeGenerator.kt` | Make class `CodeGenerator`, field `context` and methods open. Swap fields in class `CodeGeneratorResult`. | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/CgMethodTestSet.kt` | Add new constructors for `CgMethodTestSet` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/TestClassContext.kt` | Remove internal from data class `TestClassContext` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt` | Remove internal from class `UtilMethodProvider` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt` | Remove internal from classes `Context` and `CgContextOwner`. Add field `cgLanguageAssistant` into `CgContextOwner`. Move logic from `CgContext.__outerMostTestClassContext` and `CgContext.outerMostTestClass` to `CgLanguageAssistant` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/name/CgNameGenerator.kt` | Remove internal from `CgNameGenerator` and `CgNameGeneratorImpl`, change `codegenLanguage` argument to `cgLanguageAssistant` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt` | Remove internal from interface `CgFieldStateManager` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt` | Change private to protected and add empty else branches in `UtModel`-when | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt` | Change private to open or protected, add `cgLanguageAssistant` call instead standard implementations | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt` | Change private to open and add else branch in `UtModel`-when | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/MockFrameworkManager.kt` | Remove internal and add else branch in `UtModel`-when | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/TestFrameworkManager.kt` | Remove internal from `TestFrameworkManager` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt` | Remove internal from `EnvironmentFieldStateCache`, `FieldStateCache`, `CgFieldState`, `CgContextOwner.importIfNeeded` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt` | Add visit for `CgForEachLoop` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyPatterns.kt` | Add else-branch in `TestFramework`-whens | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/TreeUtil.kt` | Remove internal from `buildExceptionHandler` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt` | Add `CgForEachLoop` visit function, change private to protected some methods, move `makeRender` logic to `CgLanguageAssistant` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt` | Remove `language` field | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt` | Remove `language` field, change `context.codegenLanugage` to `context.cgLanguageAssistant` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgRendererContext.kt` | Remove internal and add `cgLanguageAssistant` field | +| `utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt` | Add visit for `CgForEachLoop` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt` | Add else-branch in `UtModel`-when | +| `utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt` | Remove internal | +| `utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt` | Add else-branch in `UtModel`-when | +| `utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt` | Add else-branch in `UtModel`-when | +| `utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/CgLanguageAssistant.kt` | New file with `CgLanguageAssistant` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/JavaCgLanguageAssistant.kt` | Implementation `CgLanguageAssistant` for Java | +| `utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/KotlinCgLanguageAssistant.kt` | Implementation `CgLanguageAssistant` for Kotlin | +| `utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/LanguageTestFrameworkManager.kt` | New file with `LanguageTestFrameworkManager` | +| `utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/JVMTestFrameworkManager.kt` | Implementation `LanguageTestFrameworkManager` for JVM (Java + Kotlin) | + +## utbot-fuzzers + +| File | Changes | +|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt` | Make class `FuzzedMethodDescription` open | + + +## utbot-intellij and utbot-ui-commons + +| File | Changes | +|-----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt` | Move `GenerateTestsModel.getAllTestSourceRoots()` to `BaseTestModel` method, add empty else branch to `insertImports` | +| `utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/language/JavaLanguage.kt` | Implementation `LanguageAssistant` for Java | +| `utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt` | Move common logic to `BaseTestModel` | +| `utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt` | Add else-branches in `TestFramework`-whens | +| `utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt` | Move all logic to `JvmLanguageAssistant` | +| `utbot-intellij/src/main/resources/META-INF/` | Add config xml files for Java, Kotlin, Android, Python, JS | +| `utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/language/agnostic/LanguageAssistant.kt` | New class for Actions logic | +| `utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/models/BaseTestModel.kt` | New parent class for TestModels | +| `utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt` | New file, moved from `utbot-intellij` | +| `utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/components/CodeGenerationSettingItemRenderer.kt` | New file, moved from `utbot-intellij` | +| `utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestFolderComboWithBrowseButton.kt` | New file, moved from `utbot-intellij` | +| `utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestSourceDirectoryChooser.kt` | New file, moved from `utbot-intellij` | +| `utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ErrorUtils.kt` | New file, moved from `utbot-intellij` | +| `utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt` | New file, moved from `utbot-intellij` | +| `utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/RootUtils.kt` | New file, moved from `utbot-intellij` | +| `utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt` | New file, moved from `utbot-intellij` | \ No newline at end of file diff --git a/docs/ci/ci-in-utbot-java.md b/docs/ci/ci-in-utbot-java.md new file mode 100644 index 0000000000..8fd539fcd7 --- /dev/null +++ b/docs/ci/ci-in-utbot-java.md @@ -0,0 +1,61 @@ + + +# CI features + +UTBot Java offers contributors bunch of workflows e.g., the workflow _building the project and running tests_, the workflow _archiving plugin and CLI_. + +The main CI features in UTBot Java: +* reproducible environment +* available monitoring processes + +## Reproducible environment + +Depending on the resources where you are intended to build and test software environment will be different. The key goal is to provide the same environment on different resources. To do that we use Docker images with appropriate software, environment variables and OS settings. + +Crucial CI workflows run in those docker containers thus you can reproduce the environment locally. The environment can be used for running tests or for debugging ([see detailed information](https://github.com/UnitTestBot/UTBotJava/wiki/docker-for-utbot-java)). + +If you have any questions of where images are placed, how many they are, what software versions are used, visit [repository](https://github.com/UnitTestBot/infra-images) please (now is private, will be changed in the future), leave an issue with your questions or ask in DM. + +## All stages Monitoring + +Since the workflow has started you can check access to the metrics on our monitoring service (ask teammates for url). The server offers developers the following dashboards: + +* **Node Exporter Full** - metrics of consuming the RAM, CPU, Network and other resources on the host +* **JVM dashboard** (don't forget to set job to `pushgateway`) - Java metrics +* **Test executor statistics*** - RAM consuming by Java processes +* **cAdvisor: container details*** - system resources consuming by certain container +* **cAdvisor: host summary*** - summarized system resources consuming by all containers + +**Note:** * developed by UTBot team + +When you open a dashboard you need to choose valid instance. GitHub runs **each job on separate runner** so instance ID (`HOSTNAME` env var) would be different. But all instances have **the same Run ID** (`GITHUB_RUN_ID` env var). Follow this steps: + +1. Go to Actions and open your Run; +2. Expand job list and choose any job you need; +3. At the right you'll see a list of steps. You need step `Run monitoring`; +4. Find the string like: +``` +Find your Prometheus metrics using label {instance="2911909439-7f83f93ff335"} +``` +5. Copy value between double quotes and go to monitoring dashboard. Set `github` service and expand instance list, CTRL+F and paste copied value. Choose your instance + +image + +**Note:** label consists of two part - `${GITHUB_RUN_ID}-${HOSTNAME}`. Use only one part to find all jobs of your Run. + +# Available workflows + +| Workflow name | What it's supposed to do | What it triggers on | +| --- | --- | --- | +| UTBot Java: build and run tests | Builds the project and runs tests for it | **push** or **pull request** to the **main** branch | +| [M] UTBot Java: build and run tests | Builds the project and runs tests for it | **manual** call or call from **another workflow** | +| [M] Run chosen tests | Runs a single test or tests in chosen package/class | **manual** call | +| Plugin and CLI: publish as archives | Archives plugin and CLI and stores them attached to the workflow run report | **push** to the **main** branch | +| [M] Plugin and CLI: publish as archives | Archives plugin and CLI and stores them attached to the workflow run report | **manual** call or call from **another workflow** | +| [M] Publish on GitHub Packages | Publishes artifacts such as _utbot-api_, _utbot-core_, _utbot-framework_, etc., on GitHub Packages | **manual** call | \ No newline at end of file diff --git a/docs/ci/docker-for-utbot-java.md b/docs/ci/docker-for-utbot-java.md new file mode 100644 index 0000000000..2a4d417826 --- /dev/null +++ b/docs/ci/docker-for-utbot-java.md @@ -0,0 +1,95 @@ + + +# Reproducible environment + +It's available to download docker image with the environment for UTBot. The environment is also used in the crucial CI scripts focused on building project and running tests. + +The docker image pre-installed environment includes: +1. *Java 17* + *JDK* package +3. *Gradle 7.6.1* +3. *Kotlin compiler 1.8.0* + +It's based on Ubuntu [SOME VERSION]. + +## How to install Docker + +Using reproducible environment requires Docker installed. + +The detailed information of how to install Docker can be found on the [official site](https://docs.docker.com/engine/install/). + +## How to run tests in docker container + +Do the following steps to run tests in docker container: + +1. Pull docker image +``` +docker pull unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 +``` +2. Run docker container +```bash +# -v :/usr/utbot-debug - mounts the host directory into the container directory +# -it - make the container look like a terminal connection session +# -w /usr/utbot-tests - sets up working directory inside the container +docker run -it -v :/usr/utbot-tests --name utbot-tests -w /usr/utbot-tests unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 +``` +3. Do whatever you want + +* Build UTBot and run tests: +``` +gradle clean build --no-daemon +``` +* Build UTBot without running tests: +``` +gradle clean build --no-daemon -x test +``` +* Run tests for *utbot-framework* project *CustomerExamplesTest* class: +``` +gradle :utbot-framework:test --no-daemon --tests "org.utbot.examples.collections.CustomerExamplesTest" +``` +4. Exit container +``` +exit +``` + +## How to debug UTBot in docker container + +Do the following steps to debug UTBot in docker container: + +1. Set up configuration for remote debug in IntelliJ IDEA + +**Run/Debug Configurations** → **Add New Configuration** → Choose **Remote JVM Debug** → Set up **Configuration name** → **Ok** + +2. Pull docker image +``` +docker pull unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 +``` +3. Run docker container +```bash +# -v :/usr/utbot-debug - mounts the host directory into the container directory +# -it - make the container look like a terminal connection session +# -w /usr/utbot-tests - sets up working directory inside the container +# -p 5005:5005 - mounts the host port into the container port (debugging port) +docker run -it -p 5005:5005 -v :/usr/utbot-debug --name utbot-debug -w /usr/utbot-tests unittestbot/java-env:java17-zulu-jdk-gradle7.6.1-kotlinc1.8.0 +``` +4. Set up gradle options for remote debug: +``` +export GRADLE_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 +``` +5. Start building and running tests +``` +gradle clean build --no-daemon +``` +6. Attach in IntelliJ IDEA to the gradle process in the container + +Set up **breakpoints** wherever you want → **Run** new **Configuration** in **Debug** mode + +7. Exit container +``` +exit +``` diff --git a/docs/ci/ssh-session-with-github-agent.md b/docs/ci/ssh-session-with-github-agent.md new file mode 100644 index 0000000000..5f8b8b48d6 --- /dev/null +++ b/docs/ci/ssh-session-with-github-agent.md @@ -0,0 +1,21 @@ + + +# SSH session with GitHub agent + +It's available to use **action** letting set up SSH session with GitHub agent in your **workflows**. The detailed documentation with the examples of use can be found in the [official repository](https://github.com/mxschmitt/action-tmate). + +The action setting SSH session can be easily plugged in your workflow with the example below: +``` +- name: Setup tmate session + uses: mxschmitt/action-tmate@v3 +``` + +When the action is plugged in the workflow log (the part corresponding to tmate action log) can be found the URL. By the URL you can access the terminal of your host. + +There are also some ways to setup action behavior. E.g., the default behavior of the action is to remain SSH session open until the workflow times out. It's available to setup timeout parameter yourself. \ No newline at end of file diff --git a/docs/contributing/Conventions.md b/docs/contributing/Conventions.md new file mode 100644 index 0000000000..306f3c1749 --- /dev/null +++ b/docs/contributing/Conventions.md @@ -0,0 +1,44 @@ +# Naming and labeling conventions + +--- + +## Naming conventions + +### How to name a branch + +We use feature branches for development. Our best practice is to use the "my-github-username" prefix for each branch and to split words with the low line, e.g.: + +**_githubuser/my_feature_branch_** + +### How to name issues, commits and pull requests + +We have been using GitHub for a while, and now we have a couple of tips for naming issues, commits and pull requests ( +PRs). You are welcome to stick to them too 🙂 + +Our favorite recipes are: + +**issue title = feature request or bug description + issue ID** + +**commit message = PR title = fix description + issue ID + (PR number)** + +How to insert the issue ID into the commit message and the PR title?
+— Manually. + +How to append the PR number to the PR title?
+— It appends automatically. + +How to insert the PR number into the commit message?
+— *Push* the feature branch + *Create pull request* on GitHub and then →
+ +1) The preferred and the easiest flow: +
*Squash and merge* on GitHub → the PR number automatically appends to the resulting commit message +2) The flow for advanced users: +
(a) squash the commits locally → insert the PR number in parentheses (!) manually into the resulting commit + message + *Force Push* the resulting commit → *Rebase and merge* on GitHub +
or +
(b) change the commit message locally → insert the PR number in parentheses (!) manually + *Force Push* the + commit → *Rebase and merge* on GitHub + +## Labeling conventions + +To choose the proper labels for your issue or PR, refer to the [Label usage guidelines](https://github.com/UnitTestBot/UTBotJava/wiki/Labels-usage-guidelines). \ No newline at end of file diff --git a/docs/contributing/InterProcessDebugging.md b/docs/contributing/InterProcessDebugging.md new file mode 100644 index 0000000000..d488d7f56f --- /dev/null +++ b/docs/contributing/InterProcessDebugging.md @@ -0,0 +1,122 @@ +# Interprocess debugging of UnitTestBot Java + +### Background + +We have split the UnitTestBot machinery into three processes. See the [document on UnitTestBot multiprocess +architecture](../RD%20for%20UnitTestBot.md). +This approach has improved UnitTestBot capabilities, e.g., provided support for various JVMs and scenarios but also +complicated the debugging flow. + +These are UnitTestBot processes (according to the execution order): + +* _IDE process_ +* _Engine process_ +* _Instrumented process_ + +Usually, the main problems happen in the _Engine process_, but it is not the process we run first. +See how to debug UnitTestBot processes effectively. + +### Enable debugging + +Debugging the _IDE process_ is pretty straightforward: start the debugger session (**Shift+F9**) for the `runIde` +Gradle task in `utbot-intellij` project from your IntelliJ IDEA. + +To debug the _Engine process_ and the _Instrumented process_, you need to enable the debugging options: +1. Open [`UtSettings.kt`](../../utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt). +2. There are two similar options: `runEngineProcessWithDebug` and `runInstrumentedProcessWithDebug` — enable the + relevant one(s). There are two ways to do this: + * You can create the `~/.utbot/settings.properties` file and write the following: + + ``` + runEngineProcessWithDebug=true + runInstrumentedProcessWithDebug=true + ``` + Then restart the IntelliJ IDEA instance you want to debug. + + * **Discouraged**: you can change the options in the source file, but this will involve moderate project + recompilation. +3. You can set additional options for the Java Debug Wire Protocol (JDWP) agent if debugging is enabled: + * `engineProcessDebugPort` and `instrumentedProcessDebugPort` are the ports for debugging. + + Default values: + - 5005 for the _Engine process_ + - 5006 for the _Instrumented process_ + + * `suspendEngineProcessExecutionInDebugMode` and `suspendInstrumentedProcessExecutionInDebugMode` define whether + the JDWP agent should suspend the process until the debugger is connected. + + More formally, if debugging is enabled, the following switch is added to the _Engine process_ JVM at the start by + default: + ``` + "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=5005" + ``` + + These options set `suspend` and `address` values. For example, with the following options in `~/.utbot/settings.properties`: + ``` + runEngineProcessWithDebug=true + engineProcessDebugPort=12345 + suspendEngineProcessExecutionInDebugMode=false + ``` + the resulting switch will be: + ``` + "-agentlib:jdwp=transport=dt_socket,server=n,suspend=n,quiet=y,address=12345" + ``` + See `org.utbot.intellij.plugin.process.EngineProcess.Companion.debugArgument` for switch implementation. +4. For information about logs, refer to the [Interprocess logging](InterProcessLogging.md) guide. + +### Run configurations for debugging the Engine process + +There are three basic run configurations: +1. `Run IDE` configuration allows running the plugin in IntelliJ IDEA. +2. `Utility Configurations/Listen for Instrumented Process` configuration allows listening to port 5006 to check if + the _Instrumented process_ is available for debugging. +3. `Utility Configurations/Listen for Engine Process` configuration allows listening to port 5005 to check if the _Engine process_ is available for debugging. + +On top of them, there are three compound run configurations for debugging: +1. `Debug Engine Process` and `Debug Instrumented Process` — a combination for debugging the _IDE process_ and + the selected process. +3. `Debug All` — a combination for debugging all three processes. + +To make debug configurations work properly, you need to set the required properties in `~/.utbot/settings.properties`. If you change the _port number_ and/or the _suspend mode_, do change these default values in the corresponding Utility Configuration. + +### How to debug + +Let's walk through an example illustrating how to debug the "_IDE process_ → _Engine process_" communication. + +1. In your current IntelliJ IDEA with source code, use breakpoints to define where the program needs to be stopped. For example, set the breakpoints at `EngineProcess.generate` and somewhere in `watchdog.wrapActiveCall(generate)`. +2. Select the `Debug Engine Process` configuration, add the required parameters to `~/.utbot/settings.properties` and + start the debugger session. +3. Generate tests with UnitTestBot in the debug IDE instance. Make sure symbolic execution is turned on, otherwise some processes do not even start. +4. The debug IDE instance will stop generation (if you have not changed the debug parameters). If you take no action, test generation will be canceled by timeout. +5. When the _Engine process_ has started (build processes have finished, and the progress bar says: _"Generate + tests: read classes"_), there will be another debug window — "Listen for Engine Process", — which automatically + connects and starts debugging. +6. Wait for the program to be suspended upon reaching the first breakpoint in the _Engine process_. + +### Interprocess call mapping + +Now you are standing on a breakpoint in the _IDE process_, for example, the process stopped on: + + EngineProcess.generate() + +If you go along the execution, it reaches the next line (you are still in the _IDE process_): + + engineModel.generate.startBlocking(params) + +It seems that test generation itself should occur in the _Engine process_ and there should be an entry point in the _Engine process_. +How can we find it? + +Standing on the breakpoint at `engineModel.generate.startBlocking(params)`, right-click on +`EngineProcessModel.generate` and **Go to** > **Declaration or Usages**. This navigates to the `RdCall` definition (which is +responsible for cross-process communication) in the `EngineProcesModel.Generated.kt` file. + +Now **Find Usages** for `EngineProcessModel.generate` and see the point where `RdCall` is passed to the next method: + + watchdog.wrapActiveCall(generate) + +This is the point where `RdCall` is called in the _Engine process_. + +You could have skipped the previous step and used **Find Usages** right away, but it is useful to know +where `RdCall` is defined. + +If you are interested in the trailing lambda of `watchdog.wrapActiveCall(generate)`, set the breakpoint here. \ No newline at end of file diff --git a/docs/contributing/InterProcessLogging.md b/docs/contributing/InterProcessLogging.md new file mode 100644 index 0000000000..a023dba59e --- /dev/null +++ b/docs/contributing/InterProcessLogging.md @@ -0,0 +1,248 @@ +# Interprocess logging + +This document describes +how logging is implemented across the UnitTestBot Java [processes](https://github.com/UnitTestBot/UTBotJava/blob/main/docs/RD%20for%20UnitTestBot.md): +the IDE process, the Engine process, and the Instrumented process. + +## Architecture + +The UnitTestBot Java logging system relies on `log4j2` library. + +For UnitTestBot Java used as an IntelliJ IDEA plugin, the configuration file for logging is [`utbot-intellij/log4j2.xml`](../../utbot-intellij/src/main/resources/log4j2.xml). + +When used as Contest estimator or the Gradle/Maven plugins, via CLI or during the CI test runs, +UnitTestBot Java engine searches classpath for the first `log4j2.xml` in the `resources` directory. + +### IDE process + +The IDE process writes logging information to standard `idea.log` files and puts them into the default log directory. + +To configure logs for the IDE process, use the log configuration file in a straightforward way. + +To change the log configuration for the prebuilt plugin, +go to **Help** > **Diagnostic Tools** > **Debug Log Settings...** and configure `log4j2.xml`. + +To store log data for the Engine process started from the IDE process, the UnitTestBot Java plugin creates a directory: +`org.utbot.intellij.plugin.process.EngineProcessKt.engineProcessLogDirectory`. + +### Engine process + +The Engine process can be started either from IntelliJ IDEA or separately — as a standalone engine. + +#### Engine process started from IntelliJ IDEA + +As the plugin does not support multiple generation processes, +the logs from the Engine process are written to the same file. + +The default log file directory is `%user_temp%/UtBot/rdEngineProcessLogs`. + +The [`utbot-intellij/log4j2.xml`](../../utbot-intellij/src/main/resources/log4j2.xml) file is copied to the +UnitTestBot Java temporary directory: +`org.utbot.intellij.plugin.process.EngineProcessKt.engineProcessLogConfigurationsDirectory`. +Then this file is provided to the Engine process via the following CLI switch: + ``` + -Dlog4j2.configurationFile=%configuration_file% + ``` + +Here, `%configuration_file%` can take one of two values: +1. A modified copy of [`utbot-intellij/log4j2.xml`](../../utbot-intellij/src/main/resources/log4j2.xml) file, which is stored in UnitTestBot Java temporary directory. + + More precisely, there are 2 appenders in the configuration file: + ```xml + + + + + ``` + By default, `IdeaAppender` is used everywhere in a file for the IDE plugin. + + For the Engine process, a temporary `log4j2.xml` is created, + where the `ref="IdeaAppender"` substring is replaced with `ref="EngineProcessAppender"`: + this replacement changes all the appenders and the log pattern + but keeps categories and log levels for the loggers the same. + + As soon as the file reaches 20 MB size, `RollingFileAppender` writes the logs to the `utbot-engine-current.log` file. + The created log files are named `utbot-engine-%i.log`. + A log file with the largest index is the latest one: `utbot-engine-1.log` has been created earlier than `utbot-engine-10.log`. + + Each time the Engine process starts, the following lines are printed into the IntelliJ IDEA log: + ``` + | UtBot - EngineProcess | Engine process started with PID = 4172 + | UtBot - EngineProcess | Engine process log directory - C:\Users\user_name\AppData\Local\Temp\UTBot\rdEngineProcessLogs + | UtBot - EngineProcess | Engine process log file - C:\Users\user_name\AppData\Local\Temp\UTBot\rdEngineProcessLogs\utbot-engine-current.log + ``` + +2. A path from `UtSettings.engineProcessLogConfigFile`. + + The option provides the external `log4j2` configuration file with the path instead of [`utbot-intellij/log4j2.xml`](../../utbot-intellij/src/main/resources/log4j2.xml). + In the `~/.utbot/settings.properties` file, one can set this path to a custom configuration file applicable to the Engine process, for example: + ``` + engineProcessLogConfigFile=C:\wrk\UTBotJava\engineProcessLog4j2.xml + ``` + This allows you to configure logs for the Engine process even for the prebuilt plugin (you need to restart an IDE). + +#### Engine process started separately + +When used as Contest estimator or the Gradle/Maven plugins, via CLI or during the CI test runs, +UnitTestBot Java engine searches classpath for the first `log4j2.xml` in the `resources` directory +to get configuration information. + +### Instrumented process + +The Instrumented process sends the logs to its parent — to the Engine process. +Logs are sent via the corresponding Rd model: `org.utbot.rd.models.LoggerModel`. + +See also `org.utbot.instrumentation.rd.InstrumentedProcess.Companion.invoke` and +`org.utbot.instrumentation.process.InstrumentedProcessMainKt.main`. + +## Rd logging system + +Rd has the custom logging system based on `com.jetbrains.rd.util.Logger` interface. +It is convenient to set the Rd logging system as default for the Instrumented process: +during concrete execution, +the `log4j2` classes in UnitTestBot Java could be confused with the `log4j2` classes from the project under test. +Duplicated `log4j2` libraries can break instrumentation and coverage statistics. + +You should always override the default Rd logging strategy, which writes log data to `stdout/stderr`. +Use `com.jetbrains.rd.util.Logger.Companion.set` method to provide custom +`com.jetbrains.rd.util.ILoggerFactory`. +The created loggers will be automatically re-instantiated to obtain a new logger from the provided factory. +You can obtain a logger via the `com.jetbrains.rd.util.getLogger` function. +Check `EngineProcessMain` for Rd logging example. + +For available Rd factories, see the `org.utbot.rd.loggers` package: it contains the implemented factories. +The format of the log messages is the same as described in `utbot-intellij/src/main/resources/log4j2.xml`. + +## Implementation details + +### Additivity + +An entry may appear in a log many times due to _additivity_. The resulting log may look like this: +``` +13:55:41.204 | INFO | AnalyticsConfigureUtil | PathSelectorType: INHERITORS_SELECTOR +13:55:41.204 | INFO | AnalyticsConfigureUtil | PathSelectorType: INHERITORS_SELECTOR +``` + +The logger's full name constitutes a tree structure so that the logged events from a child are visible to a parent. + +For example, the following `log4j2.xml` configuration in IntelliJ IDEA will produce such a problem: +```xml +... + + + + + + + + +... +``` + +This happens because the `org.utbot` logger is a parent to `org.utbot.intellij`, and all the events from +`org.utbot.intellij` are also transferred to `org.utbot`. + +To modify this behavior, add the `additivity="false"` tag to all loggers manually: +```xml +... + + + + + + + + +... +``` + +Consider this problem when you manually configure log level and appender for a logger. + +For more information, +refer to the [`log4j2` additivity](https://logging.apache.org/log4j/2.x/manual/configuration.html#Additivity) document. + +### Logging: auxiliary methods + +Find more logging-related methods at `UtRdLogUtil.kt` and `Logging.kt`. + +To trace the execution duration, +use the `measureTime` method (see `Logging.kt`) with the corresponding log level scope. + +In the Engine process, the entries from the Instrumented process are logged by `org.utbot.instrumentation.rd.InstrumentedProcessKt.rdLogger`. + +## Log levels and performance + +For development, the `Debug` level is preferred in most cases. + +The `Info` log level is sufficient for release. + +In Rd, if you choose the `Trace` level for all loggers or set it as default for the root logger, +this enables logging for all technical _send/receive_ events from protocol. +It may cause ~50 MB of additional entries per generation to appear and _heavily_ pollutes the log. This might be useful +for troubleshooting interprocess communication but in all other cases prefer the `Debug` level or +specify the `Trace` level per logger explicitly. + +For the `Debug` level, if a log message requires heavy string interpolation, wrap it in lambda, for example: +```kotlin +val someVeryBigDataStructure = VeryBigDataStructure() + +logger.debug("data structure representation - $someVeryBigDataStructure") // <---- interpolation +``` +Here, even for a message with the `Debug` level, interpolation will always occur because +the message is passed as a parameter, which is evaluated at call site. +If the `Info` level (or higher) is set for a logger, +the message is built, but not logged, +resulting in unnecessary work, possibly causing performance issues. + +Consider using lambdas: +```kotlin +// message will be created only if debug log level is available +logger.debug { "data structure representation - $someVeryBigDataStructure"} +``` + +Here, although the logs are sent from one process to another, no performance penalties have been noticed. + +To reach higher performance, try to use `bufferedIO` and `immediateFlush` properties in `log4j2.xml`. +For example, you can make the following changes to the `log4j2.xml` file in `utbot-intellij`: +```xml + +``` + +This will reduce a number of I/O operations and help to use `log4j2` buffer more efficiently. +This may also have a flip side: +when the process terminates, `log4j2` terminates the logging service before the buffer is flushed, and +you will lose the last portion of logs. +This behavior is undesirable for testing and debugging, +but probably acceptable for release. + +## Docker and Gradle + +To see the logs in Gradle from console, Docker and CI, add the following `build.gradle.kts` file: +```kotlin +allprojects { + tasks { + withType { + testLogging.showStandardStreams = true + testLogging.showStackTraces = true + } + } +} +``` + +## Useful links + +UnitTestBot Java documentation: +1. [Multiprocess architecture](../RD%20for%20UnitTestBot.md) +2. [Interprocess debugging](InterProcessDebugging.md) +3. [How to use loggers](../../HowToUseLoggers.md) + +`log4j2` documentation: +1. [Architecture](https://logging.apache.org/log4j/2.x/manual/architecture.html) — an overall `log4j2` description. +2. [Layouts](https://logging.apache.org/log4j/2.x/manual/layouts.html) — how to format log messages. + (UnitTestBot Java uses `Pattern layout` everywhere.) +3. [Appenders](https://logging.apache.org/log4j/2.x/manual/appenders.html) — + a description of various ways to store log entries (and how to configure the storages). + UnitTestBot Java uses the `Console`, `File` and `RollingFile` appenders. +4. [Configuration](https://logging.apache.org/log4j/2.x/manual/configuration.html) — + how to use a configuration file, how to check the file, and other useful information. + It is **highly advised** to read the `Additivity` part. \ No newline at end of file diff --git a/docs/contributing/LabelUsageGuideline.md b/docs/contributing/LabelUsageGuideline.md new file mode 100644 index 0000000000..05007f6be8 --- /dev/null +++ b/docs/contributing/LabelUsageGuideline.md @@ -0,0 +1,93 @@ +# Label usage guideline + +We recommend to use labels only in these cases + + +![bug](https://user-images.githubusercontent.com/106974353/174105036-53ac8736-2e63-4a02-ac90-1aca34a8fb53.png) + +Something isn't working. +Indicates an unexpected problem or unintended behavior. + +# + +![170533338-082f808e-b74b-437d-802e-568099036b1e-depositphotos-bgremover](https://user-images.githubusercontent.com/106974353/174105268-52897d9b-3939-4063-bfec-2572dcef67f4.png) + +This label applies to the issues and pull requests related to `org.utbot.engine` package. +Use it if your issue or fix deals with model construction (including Soot and Jimple), +memory modeling, symbolic values, wrappers, mocking, value resolving, or interaction +with the SMT solver. + +Path selector issues generally should also have the "engine" label, unless the problem +is specific to a ML-based path selection algorithm. + +# + +![171006007-3ad32d41-1968-4a43-ac4b-f68f016f978b-depositphotos-bgremover](https://user-images.githubusercontent.com/106974353/174105349-f33620af-8694-486b-95af-eaabfd1e4fa7.png) + +This label applies to the issues and pull requests related to `org.utbot.intellij` module. +Use it if your changes in code are related to plugin UI appearance (mostly `ui` package) +or close functionality: frameworks installation, sarif reports generation, etc. + +# + +![171006369-d7810250-258d-4b8d-8321-2742bd0a81db-depositphotos-bgremover](https://user-images.githubusercontent.com/106974353/174105444-beab859e-ea77-47dd-a5c2-27c0be350e82.png) + +This label applies to the issues and pull requests related to `org.utbot.framework.codegen`package. +Use it if your issue or fix deals with generating (rendering) code of unit tests based on obtained +from symbolic engine executions. It may relate to generation on both supported languages (Java and Kotlin). +Code generator related class names are often marked with `Cg` prefix or with `CodeGenerator` suffix. + +# + +![170533255-7fe1342b-4121-44f8-8678-78e52581235e-depositphotos-bgremover](https://user-images.githubusercontent.com/106974353/174105494-23cd502f-6181-445e-85fa-1f80ddc90e5f.png) + +Indicates a need for improvements or additions to documentation. + + +# + +![170533304-e0f95623-1fa5-427b-8545-ceb4113de597-depositphotos-bgremover (1)](https://user-images.githubusercontent.com/106974353/174105712-5ffc4157-142f-4971-8e4b-aced5ed2bc19.png) + +This issue or pull request already exists. +Indicates similar issues, pull requests, or discussions. + +# + +![170537552-fba154f5-14b8-4054-aa3b-d0c7a040677f-depositphotos-bgremover](https://user-images.githubusercontent.com/106974353/174105774-4688face-7e82-4bb6-8b2a-2372e3fb6400.png) + +New feature or request. + +# + +![170537570-ae56bc9f-19b7-4864-8a92-05e5b7f5f342-depositphotos-bgremover](https://user-images.githubusercontent.com/106974353/174105839-f0fbd000-b4c7-40fe-a261-bde8048de13b.png) + +Good for newcomers. +Indicates a good issue for first-time contributors. + +# + +![170537578-37181739-204f-4527-a337-17333d45542d-depositphotos-bgremover](https://user-images.githubusercontent.com/106974353/174105932-596eb120-4f28-4e5c-842a-3fb4c5c375b7.png) + +Extra attention is needed. +Indicates that a maintainer wants help on an issue or pull request. + +# + +![170537586-ef98f24c-d12d-47b3-95eb-e396c2a14337-depositphotos-bgremover](https://user-images.githubusercontent.com/106974353/174106017-4be04dff-0451-46f1-b552-e5f5f3730438.png) + +This issue / PR doesn't seem right. +Indicates that an issue, pull request, or discussion is no longer relevant. + +# + +![170537612-daeed618-7cc2-44e6-9d67-d74939761dae-depositphotos-bgremover1](https://user-images.githubusercontent.com/106974353/174106553-506fe0bc-7ddb-47a7-9609-b7cd1b775f22.png) + +Further information is requested. +Indicates that an issue, pull request, or discussion needs more information. + +# + +![170537619-538ec3a4-1f50-4f19-8bf3-71ce7e2d1afe-depositphotos-bgremover (3)](https://user-images.githubusercontent.com/106974353/174106628-08b7cd36-8dc7-4eb3-82c7-e917d7d11e8f.png) + +This will not be worked on. +Indicates that work won't continue on an issue, pull request, or discussion. \ No newline at end of file diff --git a/docs/images/utbot_custom_javadoc_tags.png b/docs/images/utbot_custom_javadoc_tags.png new file mode 100644 index 0000000000..0025b4bb01 Binary files /dev/null and b/docs/images/utbot_custom_javadoc_tags.png differ diff --git a/docs/images/utbot_settings.png b/docs/images/utbot_settings.png new file mode 100644 index 0000000000..efd775711b Binary files /dev/null and b/docs/images/utbot_settings.png differ diff --git a/docs/images/utbot_ututils_2.0.png b/docs/images/utbot_ututils_2.0.png new file mode 100644 index 0000000000..c1146646c3 Binary files /dev/null and b/docs/images/utbot_ututils_2.0.png differ diff --git a/docs/jlearch/pipeline-training-usage.md b/docs/jlearch/pipeline-training-usage.md index 32cbd0dddf..9c68d35f36 100644 --- a/docs/jlearch/pipeline-training-usage.md +++ b/docs/jlearch/pipeline-training-usage.md @@ -32,6 +32,6 @@ Briefly: To do this, you should: * Be sure that you use `Java 8` by `java` command and set `JAVA_HOME` to `Java 8`. * Put projects, on which you want to learn in `contest_input/projects` folder, then list classes, on which you want to learn in `contest_input/classes//list` (if it is empty, than we will take all classes from project jar). -* Run `pip install -r scripts/requirements.txt`. It is up to you to make it in virtual environment or not. -* List selectors in `scripts/selector_list` and projects in `scripts/prog_list` -* Run `./scripts/train_iteratively.sh ` +* Run `pip install -r scripts/ml/requirements.txt`. It is up to you to make it in virtual environment or not. +* List selectors in `scripts/ml/selector_list` and projects in `scripts/ml/prog_list` +* Run `./scripts/ml/train_iteratively.sh ` diff --git a/docs/jlearch/scripts.md b/docs/jlearch/scripts.md index 64b4294f47..4e23e43f9e 100644 --- a/docs/jlearch/scripts.md +++ b/docs/jlearch/scripts.md @@ -5,7 +5,7 @@ For each scenario: go to root of `UTBotJava` repository - it is `WORKDIR`. Before start of work run: ```bash -./scripts/prepare.sh +./scripts/ml/prepare.sh ``` It will copy contest resources in `contest_input` folder and build the project, because we use jars, so if you want to change something in code and re-run scripts, then you should run: @@ -16,11 +16,11 @@ It will copy contest resources in `contest_input` folder and build the project, ## To Train a few iterations of your models: By default features directory is `eval/features` - it should be created, to change it you should manually do it in source code of scripts. -List projects and selectors on what you want to train in `scripts/prog_list` and `scripts/selector_list`. You will be trained on all methods of all classes from `contest_input/classes//list`. +List projects and selectors on what you want to train in `scripts/ml/prog_list` and `scripts/selector_list`. You will be trained on all methods of all classes from `contest_input/classes//list`. Then just run: ```bash -./scripts/train_iteratively.sh +./scripts/ml/train_iteratively.sh ``` Python command is your command for python3, in the end of execution you will get iterations models in `` folder and features for each selector and project in `//` for `selector` from `selectors_list` and in `/jlearch//` for models. @@ -29,7 +29,7 @@ Check that `srcTestDir` with your project exist in `build.gradle` of `utbot-juni Then just run: ```bash -./scripts/run_with_coverage.sh +./scripts/ml/run_with_coverage.sh ``` In the end of execution you will get jacoco report in `eval/jacoco///` folder. @@ -37,7 +37,7 @@ In the end of execution you will get jacoco report in `eval/jacoco// +./scripts/ml/quality_analysis.sh ``` It will take coverage reports from relative report folders (at `eval/jacoco/project/alias`) and generate charts in `$outputDir//.html`. `outputDir` can be changed in `QualityAnalysisConfig`. Result file will contain information about 3 metrics: diff --git a/docs/jlearch/setup.md b/docs/jlearch/setup.md index d1bf89c364..d24be69503 100644 --- a/docs/jlearch/setup.md +++ b/docs/jlearch/setup.md @@ -1,7 +1,7 @@ # How to setup environment for experiments on Linux * Clone repository, go to root -* `chmod +x ./scripts/*` and `chmod +x gradlew`. +* `chmod +x ./scripts/ml/*` and `chmod +x gradlew`. * Set `Java 8` as default and set `JAVA_HOME` to this `Java`. For example * Go through [this](https://sdkman.io/install) until `Windows installation` @@ -17,19 +17,19 @@ * `python3 -m venv /path/to/new/virtual/environment` * `source /path/to/venv/bin/activate` * Check `which python3`, it should be somewhere in `path/to/env` folder. - * `pip install -r scripts/requirements.txt` -* `./scripts/prepare.sh` -* Change `scripts/prog_list` to run on smaller project or delete some classes from `contest_input/classes//list`. + * `pip install -r scripts/ml/requirements.txt` +* `./scripts/ml/prepare.sh` +* Change `scripts/ml/prog_list` to run on smaller project or delete some classes from `contest_input/classes//list`. # Default settings and how to change it -* You can reduce number of models in `models` variable in `scripts/train_iteratively.sh` +* You can reduce number of models in `models` variable in `scripts/ml/train_iteratively.sh` * You can change amount of required RAM in `run_contest_estimator.sh`: `16 gb` by default * You can change `batch_size` or `device` n `train.py`: `4096` and `gpu` by default * If you are completing setup on server, then you will need to uncomment tmp directory option in `run_contest_estimator.sh` # Continue setup -* `scripts/train_iteratively.sh 30 2 models ` +* `scripts/ml/train_iteratively.sh 30 2 models ` * In `models/` you should get models. * `mkdir eval/jacoco` -* `./scripts/run_with_coverage.sh 30 "NN_REWARD_GUIDED_SELECTOR path/to/model" some_alias`. `path/to/model` should be something like `models/nn32/0`, where `nn32` is a type of model and `0` is the iteration number +* `./scripts/ml/run_with_coverage.sh 30 "NN_REWARD_GUIDED_SELECTOR path/to/model" some_alias`. `path/to/model` should be something like `models/nn32/0`, where `nn32` is a type of model and `0` is the iteration number * You should get jacoco report in `eval/jacoco/guava-26.0/some_alias/` \ No newline at end of file diff --git a/docs/spring.md b/docs/spring.md new file mode 100644 index 0000000000..85a87b4991 --- /dev/null +++ b/docs/spring.md @@ -0,0 +1,370 @@ +# Automated test generation for Spring-based code + +Java developers actively use the Spring framework to implement the inversion of control and dependency injection. +Testing Spring-based applications differs significantly from testing standard Java programs. Thus, we customized +UnitTestBot to analyze Spring projects. + + + * [General notes](#general-notes) + * [Limitations](#limitations) + * [Testability](#testability) + * [Standard unit tests](#standard-unit-tests) + * [Example](#example) + * [Use cases](#use-cases) + * [Spring-specific unit tests](#spring-specific-unit-tests) + * [Example](#example-1) + * [Use cases](#use-cases-1) + * [Side effects](#side-effects) + * [Mechanism](#mechanism) + * [Integration tests](#integration-tests) + * [Service layer](#service-layer) + * [Side effects](#side-effects-1) + * [Use cases](#use-cases-2) + * [Controller layer](#controller-layer) + * [Example](#example-2) + * [Microservice layer](#microservice-layer) + + +## General notes + +UnitTestBot proposes three approaches to automated test generation: +* [standard unit tests](#standard-unit-tests) that mock environmental interactions; +* [Spring-specific unit tests](#spring-specific-unit-tests) that use information about the Spring application context to reduce the number of + mocks; +* and [integration tests](#integration-tests) that validate interactions between application components. + +Hereinafter, by _components_ we mean Spring components. + +For classes under test, one should select an appropriate type of test generation based on their knowledge +about the Spring specifics of the current class. Recommendations on how to choose the test type are provided below. +For developers who are new to Spring, there is a "default" generation type. + +### Limitations + +UnitTestBot Java with Spring support uses symbolic execution to generate unit tests, so typical problems +related to this technique may appear: it may be not so efficient for multithreaded programs, functions with calls to +external libraries, processing large collections, etc. + +### Testability + +Note that UnitTestBot may generate unit tests more efficiently if your code is written to be unit-testable: the +functions are not too complex, each function implements one logical unit, static and global data are used +only if required, etc. Difficulties with automated test generation may have "diagnostic" value: it +may mean that you should refactor your code. + +## Standard unit tests + +The easiest way to test Spring applications is to generate unit tests for components: to +mock the external calls found in the method under test and to test just this method's +functionality. UnitTestBot Java uses the Mockito framework that allows to mark +the to-be-mocked objects with the `@Mock` annotation and to use the `@InjectMock` +annotation for the tested instance injecting all the mocked fields. See [Mockito](https://site.mockito.org/) +documentation for details. + +### Example + +Consider generating unit tests for the `OrderService` class that autowires `OrderRepository`: + +```java + +@Service +public class OrderService { + +@Autowired +private OrderRepository orderRepository ; + +public List getOrders () { +return orderRepository.findAll (); +} +} + +public interface OrderRepository extends JpaRepository +``` +Then we mock the repository and inject the resulting mock into a service: + +```java +public final class OrderServiceTest { + @InjectMocks + private OrderService orderService + + @Mock + private OrderRepository orderRepositoryMock + + @Test + public void testGetOrders () { + when(orderRepositoryMock .findAll()).thenReturn((List)null) + + List actual = orderService .getOrders() + assertNull(actual) + } +``` + +This test type does not process the Spring context of the original application. The components are tested in +isolation. + +It is convenient when the component has its own meaningful logic and may be useless when its main responsibility is to call other components. + +Note that if you autowire several beans of one type or a collection into the class under test, the code of test +class will be a bit different: for example, when a collection is autowired, it is marked with `@Spy` annotation due +to Mockito specifics (not with `@Mock`). + +### Use cases + +When to generate standard unit tests: +* _Service_ or _DAO_ layer of Spring application is tested. +* Class having no Spring specific is tested. +* You would like to test your code in isolation. +* You would like to generate tests as fast as possible. +* You would like to avoid starting application context and be sure the test generation process has no Spring-related side effects. +* You would like to generate tests in one click and avoid creating specific profiles or configuration classes for + testing purposes. + +We suggest using this test generation type for the users that are not so experienced in Spring or would like to get +test coverage for their projects without additional efforts. + +## Spring-specific unit tests + +This is a modification of standard unit tests generated for Spring projects that may allow us to get more +meaningful tests. + +### Example + +Consider the following class under test + +```java +@Service +public class GenderService { + +@Autowired +public Human human + +public String getGender () { +return human.getGender(); +} +} +``` +where `Human` is an interface that has just one implementation actually used in current project configuration. + +```java +public interface Human { +String getGender(); +} + +public class Man implements Human { +public String getGender() { +return “man” +} +} +``` + +The standard unit test generation approach is to mock the _autowired_ objects. It means that the generated test will be +correct but useless. However, there is just one implementation of the `Human` interface, so we may use it directly +and generate a test like this: + +```java +@Test +public void testGetGender_HumanGetGender() { +GenderService genderService = new GenderService(); +genderService.human = new Man(); +String actual = genderService.getGender(); +assertEquals(“man”, actual); +} +``` + +Actually, dependencies in Spring applications are often injected via interfaces, and they often have just one actual +implementation, so it can be used in the generated tests instead of an interface. If a class is injected itself, it +will also be used in tests instead of a mock. + +You need to select a configuration to guide the process of creating unit tests. We support all commonly used +approaches to configure the application: +* using an XML file, +* Java annotation, +* or automated configuration in Spring Boot. + +Although it is possible to use the development configuration for testing purposes, we strictly recommend creating a separate one. + +### Use cases + +When to generate Spring-specific unit tests: +* to reduce the amount of mocks in generated tests +* and to use real object types instead of their interfaces, obtaining tests that simulate the method under test execution. + +### Side effects + +We do not recommend generating Spring-specific unit tests, when you would like to maximize line coverage. +The goal of this approach is to cover the lines that are relevant for the current configuration and are to be used +during the application run. The other lines are ignored. + +When a concrete object is created instead of mocks, it is analyzed with symbolic execution. It means that the +generation process may take longer and may exceed the requested timeout. + +### Mechanism + +A Spring application is created to simulate a user one. It uses configuration importing users one with an additional +bean of a special _bean factory post processor_. + +This _post processor_ is called when bean definitions have already been created, but actual bean initialization has +not been started. It gets all accessible information about bean types from the definitions and destroys these +definitions after that. + +Further Spring context initialization is gracefully crashed as bean definitions do not exist anymore. Thus, this +test generation type is still safe and will not have any Spring-related side effects. + +Bean type information is used in symbolic execution to decide if we should mock the current object or instantiate it. + +## Integration tests + +The main difference of integration testing is that it tests the current component while taking interactions with +other classes into account. + +### _Service_ layer + +Consider an `OrderService` class we have already seen. Actually, this class has just one +responsibility: to return the result of a call to the repository. So, if we mock the repository, our unit test is +actually useless. However, we can test this service in interaction with the repository: save some information to the +database and verify if we have successfully read it in our method. Thus, the test method looks as follows. + +```java + +@Autowired +private OrderService orderService + +@Autowired +private OrderRepository orderRepository + +@Test +public void testGetOrderById() throws Exception { +Order order = new Order(); +Order order1 = orderRepository.save(order); +long id = (Long) getFieldValue(order1, "com.rest.order.models.Order ", "id“); + +Order actual = orderService.getOrderById(id); +assertEquals (order1, actual); +} +``` +The key idea of integration testing is to initialize the context of a Spring application and to autowire a bean of +the class under test, and the beans it depends on. The main difficulty is to mutate the initial _autowired_ state of the +object under test to another state to obtain meaningful tests (e.g. save some data to related repositories). +Here we use fuzzing methods instead of symbolic execution. + +You should take into account that our integration tests do not use mocks at all. It also means that if the method +under test contains calls to other microservices, you need to start the microservice unless you want to test your +component under an assumption that the microservice is not responding. +Writing tests manually, users can investigate the expected behavior of the external service for the current scenario, +but automated test generation tools have no way to do it. + +Note that XML configuration files are currently not supported in integration testing. However, you may create a Java +configuration class importing your XML file as a resource. The list of supported test +frameworks is reduced to JUnit 4 and JUnit 5; TestNG is not supported for integration tests. + +To run integration tests properly, several annotations are generated for the class with tests (some of them may be +missed: for example, we can avoid setting active profiles via the annotation if a default profile is used). + +* `@SpringBootTest` for Spring Boot applications +* `@RunWith(SpringRunner.class)`/`@ExtendWith(SpringExtension.class)` depending on the test framework +* `@BootstrapWith(SpringBootTestContextBootstrapper.class)` for Spring Boot applications +* `@ActiveProfiles(profiles = {profile_names})` to activate requested profiles +* `@ContextConfiguration(classes = {configuration_classes})` to initialize a proper configuration +* `@AutoConfugureTestDatabase` + +Two additional annotations are: + +* `@Transactional`: using this annotation is not a good idea for some developers because it can +hide problems in the tested code. For example, it leads to getting data from the transaction cache instead of real +communication with database. +However, we need to use this annotation during the test generation process due to the +efficiency reasons and the current fuzzing approach. Generating tests in transaction but not running them in +transaction may sometimes lead to failing tests. +In future, we are going to modify the test generation process and to use `EntityManager` and manual flushing to the +database, so running tests in transaction will not have a mentioned disadvantage any more. + +* `@DirtiesContext(classMode=BEFORE_EACH_TEST_METHOD)`: although running test method in transaction rollbacks most +actions in the context, there are two reasons to use `DirtiesContext`. First, we are going to remove +`@Transactional`. After that, the database `id` sequences are not rolled back with the transaction, while we would +like to have a clean context state for each new test to avoid unobvious dependencies between them. + +Currently, we do not have proper support for Spring security issues in UnitTestBot. We are going to improve it in +future releases, but to get at least some results on the classes requiring authorization, we use `@WithMockUser` for +applications with security issues. + +#### Side effects + +Actually, yes! Integration test generation requires Spring context initialization that may contain unexpected +actions: HTTP requests, calls to other microservices, changing the computer parameters. So you need to +validate the configuration carefully before trying to generate integration tests. We strictly recommend avoiding +using _production_ and _development_ configuration classes for testing purposes, and creating separate ones. + +#### Use cases + +When to generate integration tests: +* You have a properly prepared configuration class for testing +* You would like to test your component in interaction with others +* You would like to generate tests without mocks +* You would like to test a controller +* You consent that generation may be much longer than for unit tests + +### _Controller_ layer + +When you write tests for controllers manually, it is recommended to do it a bit differently. Of course, you may just +mock the other classes and generate unit tests looking similarly to the tests we created for services, but they may +not be representative. To solve this problem, we suggest a specific integration test generation approach for controllers. + +#### Example + +Consider testing the following controller method: + +```java + +@RestController +@RequestMapping(value = "/api") +public class OrderController { + + @Autowired + private OrderService orderService; + + @GetMapping(path = "/orders") + public ResponseEntity> getAllOrders() { + return ResponseEntity.ok().body(orderService.getOrders()); + } +} +``` +UnitTestBot generates the following integration test for it: + +```java +@Test +public void testGetAllOrders() throws Exception { +Object[] objectArray = {}; +MockHttpServletRequestBuilder mockHttpServletRequestBuilder = get("/api/orders", objectArray); + +ResultActions actual = mockMvc.perform(mockHttpServletRequestBuilder); + +actual.andDo(print()); +actual.andExpect((status()).is(200)); +actual.andExpect((content()).string("[]")); +} +``` + +Note that generating specific tests for controllers is now in active development, so some parameter annotations and +types have not been supported yet. For example, we have not supported the `@RequestParam` annotation yet. For now, +specific integration tests for controllers are just an experimental feature. + +### _Microservice_ layer + +Actually, during integration test generation we create one specific test that can be considered as a test for the +whole microservice. It is the `contextLoads` test, and it checks if a Spring application context has started normally. +If this test fails, it means that your application is not properly configured, so the failure of other tests is not caused by the regression in the tested code. + +Normally, this test is very simple: + +```java +/** +* This sanity check test fails if the application context cannot start. + */ + @Test + public void contextLoads() { + } +``` + +If there are context loading problems, the test contains a commented exception type, a message, and a +track trace, so it is easier to investigate why context initialization has failed. + diff --git a/docs/summaries/CustomJavadocTags.md b/docs/summaries/CustomJavadocTags.md new file mode 100644 index 0000000000..5b1a7eb819 --- /dev/null +++ b/docs/summaries/CustomJavadocTags.md @@ -0,0 +1,85 @@ +## Custom Javadoc Tags + +Currently, summaries are hard to read because of formatting issues and a lot of details they contain. + +**Goal**: to make test summaries structured and clear. + +**Idea**: to structure summary by introducing custom JavaDoc tags. + +Tags should start with a prefix "utbot" to avoid possible issues with user (or some plugin) custom tags (if they have +the same names). + +**Suggested custom tags (NOT ALL OF THEM ARE USED)** + +| Name | Description | Usage example | +|----------------------------|------------------------------------|-----------------------------------------------------------------| +| `@utbot.classUnderTest` | Inline link to the enclosing class | `@utbot.methodUnderTest {@link main.IntMath}` | +| `@utbot.methodUnderTest` | Inline link to the method we test. | `@utbot.methodUnderTest {@link main.IntMath#pow(int, int)}` | +| `@utbot.expectedResult` | Value we expect to get. | `@utbot.expectedResult 4` | +| `@utbot.actualResult` | Value we got. | `@utbot.actualResult 64` | +| `@utbot.executes` | Executed condition. | `@utbot.executes {@code (k < Integer.SIZE): True}` | +| `@utbot.invokes` | Invoked method. | `@utbot.invokes {@link main.IntMath#mul(int, int)}` | +| `@utbot.triggersRecursion` | Triggered recursion. | `@utbot.recursion triggers recursion of leftBinSearch` | +| `@utbot.activatesSwitch` | Activated switch case. | `@utbot.activatesSwitch {code case 10}` | +| `@utbot.returnsFrom` | Statement we return from. | `@utbot.returnsFrom {@code return (k == 0) ? 1 : 0;}` | +| `@utbot.throwsException` | Thrown exception. | `@utbot.throwsException {@link java.lang.NullPointerException}` | +| `@utbot.caughtException` | Caught exception. | `@utbot.caughtException {@code RuntimeException e}` | + +## Settings + +There is a setting `Javadoc comment style` in the main plugin's `Settings`. It has two options: `Plain` text +and `Structured via custom Javadoc tags` (selected by default). + +![Settings](../images/utbot_settings.png) + +## View + +There are two modes the comment could be shown in IntelliJ IDEA: plain text and rendered view. + +To activate rendered mode, click on the toggle near comment. + +![Example](../images/utbot_custom_javadoc_tags.png) + +## Implementation details + +Implemented `JavadocTagInfo` to introduce our custom JavaDoc tags. + +Implemented `CustomJavadocTagProvider` and registered it in `plugin.xml` to support plugin's custom tags. + +Overrided behavior of `JavaDocumentationProvider#generateRenderedDoc` and registered it in `plugin.xml` to render our +custom JavaDoc tags correctly. + +Added a flag `USE_CUSTOM_TAGS` to settings. + +After plugin's removal, IDE doesn't recognize our custom tags. It doesn't lead to errors, but highlights tags with +yellow color. + +## Test scenarios + +Currently, the feature works only for Symbolic execution engine, so make sure the slider is on the Symbolic execution +side. + +### Default behaviour (the feature is enabled). + +1. Run plugin on any Java project and run tests generation. +2. Check if the comments are generated and pretty formatted. +3. Check that all links are clickable (parts that start with `@link` tag). + +### Manual settings (you can choose any comment style – old and new). + +1. Go to the `Settings` menu, check that the drop-down list `Javadoc comment style` exists and has two options (Plain + text + and Structured via custom Javadoc tags). +2. Select any option, click OK, run tests generation and check that the option is applied and the comments are generated + according to the chosen style. + +### Content + +First, generate comment with one style, then generate with another one and compare its content. If it differs, +please, provide code snippet and both generated comments. It could differ because currently the +style with custom Javadoc tags is a bit simplified. + +### View + +Check that the comments are rendered well. To do it, click on the toggle near the comment (see post about Rendered +view feature in IntelliJ IDEA). \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index cc0c15f08d..f54610ca84 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,47 +1,137 @@ kotlin.code.style=official -org.gradle.caching=false -junit5_version=5.8.0-RC1 -junit4_version=4.4 -junit4_platform_version=1.7.1 -mockito_version=3.5.13 -z3_version=4.8.9.1 -z3_java_api_version=4.8.9 -soot_commit_hash=13be158 -kotlin_version=1.4.20 -log4j2_version=2.13.3 -coroutines_version=1.4.1 -collections_version=0.3.4 -intellij_plugin_version=0.6.4 -jacoco_version=0.8.5 -commons_lang_version=3.11 -commons_io_version=2.8.0 -kotlin_logging_version=1.8.3 -ktor_version=1.4.1 -clikt_version=3.2.0 -guava_version=30.0-jre -apache_commons_exec_version=1.2 -rgxgen_version=1.3 -apache_commons_text_version=1.9 -antlr_version=4.9.2 -kryo_version=5.1.1 -kryo_serializers_version=0.45 -asm_version=8.0.1 -testng_version=7.4.0 -mockito_inline_version=4.0.0 -jackson_version = 2.12.3 -javasmt_solver_z3_version=4.8.9-sosy1 -slf4j_version=1.7.36 -eclipse_aether_version=1.1.0 -maven_wagon_version=3.5.1 -maven_plugin_api_version=3.8.5 -maven_plugin_tools_version=3.6.4 -maven_plugin_testing_version=3.3.0 -maven_resolver_api_version=1.8.0 -sisu_plexus_version=0.3.5 -javacpp_version=1.5.3 -jsoup_version=1.7.2 -djl_api_version=0.17.0 -pytorch_native_version=1.9.1 -# soot also depends on asm, so there could be two different versions - -kotlin.stdlib.default.dependency=false \ No newline at end of file + +# === IDE settings === +# Project Type +# - Community: for Java + Spring + Python (IC supported features) +# - Ultimate: for Java + Spring + Python (IU supported features) + JavaScript + Go +projectType=Ultimate + +communityEdition = Community +ultimateEdition=Ultimate + +# IU, IC, PC, PY +# IC for AndroidStudio +ideType=IC +ideaVersion=232.8660.185 +pycharmVersion=2023.2 +golandVersion=2023.2 +# ALL, NOJS +buildType=NOJS + +# IDE types that supports appropriate language +javaIde=IC,IU +pythonIde=IC,IU,PC,PY +jsIde=IU,PY +jsBuild=ALL +goIde=IU,GO + +# IDE types that require Pycharm plugin +pycharmIdeType=PC,PY + +# In order to run Android Studio instead of IntelliJ Community, specify the path to your Android Studio installation +#androidStudioPath=your_path_to_android_studio + +# Version numbers: https://plugins.jetbrains.com/plugin/7322-python-community-edition/versions +pythonCommunityPluginVersion=232.8660.185 +# Version numbers: https://plugins.jetbrains.com/plugin/631-python/versions +pythonUltimatePluginVersion=232.8660.185 +# Version numbers: https://plugins.jetbrains.com/plugin/9568-go/versions +goPluginVersion=232.8660.142 +# === IDE settings === + +junit5Version=5.8.2 +junit4Version=4.13.2 +junit4PlatformVersion=1.9.0 +# NOTE: Mockito versions 5+ are not compatible with Java 8: https://www.davidvlijmincx.com/posts/upgrade-to-mockito-5 +mockitoVersion=4.11.0 +mockitoInlineVersion=4.11.0 +ksmtVersion=0.5.13 +sootVersion=4.4.0-FORK-2 +kotlinVersion=1.8.0 +log4j2Version=2.13.3 +coroutinesVersion=1.6.4 +collectionsVersion=0.3.5 +# after updating plugin version you should manually bump corresponding versions in plugin +# as they cannot be set from properties +# utbot-intellij/build.gradle.kts +# utbot-rd/build.gradle +# utbot-rider/build.gradle.kts +intellijPluginVersion=1.13.1 +# TODO every time you bump rd version: +# 1. regenerate all models +# 2. check if rider plugin works +# 3. search for previous RD version (as string) in entire project and update it manually in places where it has to be hardcoded +rdVersion=2023.2.0 +# to enable - add -PincludeRiderInBuild=true in build CLI +includeRiderInBuild=false +jacocoVersion=0.8.8 +commonsLangVersion=3.11 +commonsIoVersion=2.8.0 +kotlinLoggingVersion=1.8.3 +ktorVersion=1.4.1 +cliktVersion=3.2.0 +guavaVersion=32.1.2-jre +apacheCommonsExecVersion=1.2 +apacheCommonsTextVersion=1.9 +rgxgenVersion=1.3 +antlrVersion=4.9.2 +kryoVersion=5.4.0 +kryoSerializersVersion=0.45 +asmVersion=9.2 +testNgVersion=7.6.0 +kamlVersion=0.51.0 +jacksonVersion=2.12.3 +kotlinxSerializationVersion=1.5.0 +slf4jVersion=1.7.36 +eclipseAetherVersion=1.1.0 +mavenWagonVersion=3.5.1 +mavenPluginApiVersion=3.8.5 +mavenPluginToolsVersion=3.6.4 +mavenPluginTestingVersion=3.3.0 +mavenResolverApiVersion=1.8.0 +sisuPlexusVersion=0.3.5 +javaCppVersion=1.5.3 +jsoupVersion=1.7.2 +djlApiVersion=0.17.0 +pytorchNativeVersion=1.9.1 +shadowJarVersion=7.1.2 +openblasVersion=0.3.10-1.5.4 +arpackNgVersion=3.7.0-1.5.4 +commonsLoggingVersion=1.2 +commonsIOVersion=2.11.0 +javaxVersion=2.2 +jakartaVersion=3.1.0 +jacoDbVersion=1.4.3 +moshiVersion=1.15.1 +pythonTypesAPIHash=e5a5d9c + +# use latest Java 8 compaitable Spring and Spring Boot versions +springVersion=5.3.28 +springBootVersion=2.7.13 +springSecurityVersion=5.8.5 + +approximationsVersion=bfce4eedde +usvmVersion=72924ad + +# configuration for build server +# +# the following options are passed to gradle command explicitly (see appropriate workflow): +# --build-cache (the same as org.gradle.caching=true) +# --no-daemon (the same as org.gradle.daemon=false) +# +# read about options precedence at: https://docs.gradle.org/current/userguide/build_environment.html +org.gradle.jvmargs="-Xmx6g" + +# configuration for local compilation - much faster +# overriden by some parameters in CI, read below about each option +# +# overrided by --no-daemon +org.gradle.daemon=true +# overrided by -Dkotlin.daemon.jvm.options=-Xmx4g +kotlin.daemon.jvm.options=-Xmx4g +# overrided by --no-parallel +org.gradle.parallel=true +# not overrided, we use cache in CI as well +org.gradle.caching=true +# there is no need to override the option below because parallel execution is disabled by --no-parallel +org.gradle.workers.max=8 diff --git a/gradle/include/jvm-project.gradle b/gradle/include/jvm-project.gradle deleted file mode 100644 index b0049e6304..0000000000 --- a/gradle/include/jvm-project.gradle +++ /dev/null @@ -1,66 +0,0 @@ -apply plugin: 'java' -apply plugin: 'kotlin' - -dependencies { - implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutines_version - implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-collections-immutable-jvm', version: collections_version - implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version - implementation group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version - - testImplementation("org.junit.jupiter:junit-jupiter:$junit5_version"){ - force = true - } -} - -compileKotlin { - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8 - freeCompilerArgs += ["-Xallow-result-return-type", "-Xinline-classes"] - allWarningsAsErrors = false - } -} - -compileTestKotlin { - kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8 - freeCompilerArgs += ["-Xallow-result-return-type", "-Xinline-classes"] - allWarningsAsErrors = false - } -} - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -compileJava { - options.compilerArgs << '-Werror' << '-Xlint:all' - options.encoding = 'UTF-8' -} - -compileTestJava { -// options.compilerArgs << '-Werror' << '-Xlint:all' -// options.encoding = 'UTF-8' -} - -test { - // set heap size for the test JVM(s) - minHeapSize = "128m" - maxHeapSize = "2048m" - - useJUnitPlatform() { - excludeTags 'slow', 'IntegrationTest' - } - - afterTest { descriptor, result -> - println "[$descriptor.classDisplayName] [$descriptor.displayName]: $result.resultType" - } - - testLogging { - afterSuite { desc, result -> - if (!desc.parent) { // will match the outermost suite - println "Test summary: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)" - } - } - } -} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2f..943f0cbfa7 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9938269f3b..6f615571cc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists \ No newline at end of file diff --git a/gradlew b/gradlew index af6708ff22..65dcd68d65 100644 --- a/gradlew +++ b/gradlew @@ -1,78 +1,129 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -89,84 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 6d57edc706..93e3f59f13 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,4 +1,20 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -9,19 +25,23 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,38 +65,26 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/models/0/nn.json b/models/0/nn.json new file mode 100644 index 0000000000..99cee81f4e --- /dev/null +++ b/models/0/nn.json @@ -0,0 +1,18970 @@ +{ + "linearLayers": [ + [ + [ + -0.2812620997428894, + -0.20690025389194489, + 0.025239119306206703, + 0.16666702926158905, + -0.3015841245651245, + -0.2231411188840866, + -0.2339421808719635, + 0.1372300386428833, + -0.09653409570455551, + -0.20102998614311218, + -0.1517055481672287, + 0.06770151108503342, + 0.1736624538898468 + ], + [ + 0.07755041122436523, + 0.2603122293949127, + 0.4763484597206116, + 0.23077495396137238, + 0.18743446469306946, + -0.002347304718568921, + -0.31861305236816406, + -0.5238833427429199, + -0.5596318244934082, + -0.13943715393543243, + -0.4900222718715668, + -0.13874824345111847, + -0.026209624484181404 + ], + [ + 0.005672903265804052, + 0.2812194228172302, + -0.029947057366371155, + 0.2998824715614319, + -0.18556763231754303, + -0.2329135686159134, + 0.03805786743760109, + 0.20632661879062653, + 0.32710668444633484, + 0.11301928013563156, + 0.21198059618473053, + 0.12055762112140656, + 0.12616081535816193 + ], + [ + 0.32202479243278503, + 0.1650446057319641, + -0.2244292050600052, + 0.2871652841567993, + -0.38242897391319275, + 0.20216849446296692, + 0.4162890613079071, + -0.24852490425109863, + -0.3516800105571747, + -0.5501313805580139, + -0.1989777386188507, + -0.1753005087375641, + -0.0997919887304306 + ], + [ + -0.2073894888162613, + -0.026421066373586655, + 0.20444630086421967, + 0.4853132665157318, + 0.13209407031536102, + 0.15783868730068207, + 0.08547142148017883, + -0.10429231822490692, + -0.04476998746395111, + 0.19800862669944763, + 0.3543761968612671, + -0.1627587378025055, + 0.0691845715045929 + ], + [ + -0.2442532181739807, + 0.18623562157154083, + -0.31594496965408325, + 0.040417544543743134, + -0.32911941409111023, + 0.18138383328914642, + 0.02217470109462738, + -0.18134571611881256, + -0.17334285378456116, + -0.04029788821935654, + 0.19143174588680267, + 0.22332844138145447, + -0.12598943710327148 + ], + [ + -0.1842803806066513, + -0.23971040546894073, + -0.5434504151344299, + 0.261478990316391, + 0.1484641134738922, + 0.16955262422561646, + 0.18890511989593506, + 0.016144687309861183, + -0.1485966444015503, + 0.11260021477937698, + -0.19952836632728577, + -0.1845572590827942, + 0.0750923678278923 + ], + [ + -0.3827153742313385, + 0.19904662668704987, + -0.3166623115539551, + -0.09563996642827988, + -0.029124295338988304, + -0.5873277187347412, + 0.28851452469825745, + 0.15390384197235107, + 0.13283896446228027, + -0.27946051955223083, + -0.03319583460688591, + 0.017873387783765793, + -0.2255309373140335 + ], + [ + 0.213275745511055, + -0.1601620465517044, + -0.23047587275505066, + -0.04881864786148071, + 0.10892008990049362, + 0.39467450976371765, + 0.4419712722301483, + 0.15421095490455627, + -0.27766215801239014, + -0.3739401698112488, + -0.12171220779418945, + -0.124486543238163, + 0.21379370987415314 + ], + [ + -0.3494093716144562, + -0.016638662666082382, + -0.19239097833633423, + -0.2745112180709839, + -0.22391216456890106, + 0.024962177500128746, + 0.41822320222854614, + -0.25197410583496094, + 0.06916783004999161, + -0.08406639099121094, + 0.025617875158786774, + -0.06838371604681015, + -0.11775310337543488 + ], + [ + -0.27412572503089905, + -0.04075736179947853, + -0.10035921633243561, + -0.1511864960193634, + -0.3486115038394928, + 0.284804105758667, + 0.2807717025279999, + -0.022341251373291016, + 0.40422338247299194, + 0.11742650717496872, + 0.18943603336811066, + -0.07526147365570068, + -0.29136690497398376 + ], + [ + 0.08565166592597961, + -0.17808888852596283, + 0.23337233066558838, + -0.01672634296119213, + -0.4955122768878937, + -0.029994111508131027, + 0.30877816677093506, + 0.04895417392253876, + 0.026867445558309555, + -0.003204688662663102, + 0.025512006133794785, + 0.28954002261161804, + -0.2107226848602295 + ], + [ + -0.3008478283882141, + -0.14909999072551727, + 0.09242149442434311, + 0.09902229905128479, + -0.31035372614860535, + 0.5288253426551819, + 0.12252070754766464, + -0.4221459925174713, + -0.2459046095609665, + -0.22813886404037476, + -0.1747918277978897, + -0.40906938910484314, + 0.14370641112327576 + ], + [ + 0.2579241991043091, + 0.022276267409324646, + 0.22654399275779724, + -0.19261173903942108, + -0.03431123495101929, + -0.7823503017425537, + -0.10113504528999329, + 0.09389126300811768, + 0.25544309616088867, + 0.04565619304776192, + -0.052542056888341904, + 0.2994041442871094, + -0.19028615951538086 + ], + [ + 0.2774119973182678, + 0.24429869651794434, + 0.1913207620382309, + -0.20841751992702484, + -0.1672147959470749, + 0.28855234384536743, + 0.25656870007514954, + -0.6109725832939148, + 0.19135800004005432, + -0.41654518246650696, + -0.10192546248435974, + 0.4338851273059845, + 0.3700643479824066 + ], + [ + 0.0926647037267685, + 0.053513601422309875, + 0.2527933716773987, + 0.13285507261753082, + 0.24765196442604065, + -0.09636160731315613, + -0.33351197838783264, + -0.17052969336509705, + -0.23085607588291168, + -0.3964312970638275, + 0.10264739394187927, + -0.014026672579348087, + 0.2935609817504883 + ], + [ + -0.3148519694805145, + -0.20939618349075317, + 0.07581528276205063, + -0.018568221479654312, + -0.29132771492004395, + -0.010084452107548714, + 0.28648287057876587, + -0.2784898281097412, + 0.21446862816810608, + -0.08985134959220886, + -0.02956037037074566, + 0.14641478657722473, + 0.03872504457831383 + ], + [ + 0.33660048246383667, + 0.15843205153942108, + -0.45638447999954224, + -0.07325007021427155, + 0.22450771927833557, + 0.11804356426000595, + 0.39345601201057434, + -0.08022525906562805, + 0.0825900211930275, + 0.05784780904650688, + -0.19954009354114532, + -0.20153969526290894, + 0.16775847971439362 + ], + [ + -0.4311541020870209, + -0.3013177514076233, + -0.06462755799293518, + 0.08752308785915375, + 0.12869223952293396, + 0.07245280593633652, + -0.1027635782957077, + -0.38550764322280884, + -0.12315567582845688, + 0.1788141131401062, + -0.06572850793600082, + 0.3179053068161011, + 0.24983134865760803 + ], + [ + -0.3641359210014343, + -0.09185764938592911, + 0.28328436613082886, + 0.2730138301849365, + 0.12595508992671967, + -0.04021301865577698, + -0.0639706626534462, + -0.07912948727607727, + -0.08967989683151245, + -0.458464652299881, + 0.11333420872688293, + -0.10430294275283813, + 0.12423478066921234 + ], + [ + -0.2626790404319763, + -0.029405439272522926, + 0.1691773235797882, + -0.16682051122188568, + 0.00991673581302166, + 0.33753031492233276, + -0.8686320185661316, + -0.38862550258636475, + 0.18885909020900726, + 0.2196504920721054, + 0.06935793161392212, + -0.21568086743354797, + 0.04005394130945206 + ], + [ + -0.12052804976701736, + 0.15113531053066254, + -0.2258468121290207, + -0.14269405603408813, + -0.007194845471531153, + 0.0751257836818695, + 0.08895590901374817, + -0.3100498616695404, + -0.22949354350566864, + 0.3293784558773041, + -0.12956123054027557, + -0.2043842077255249, + -0.1533811092376709 + ], + [ + -0.49806979298591614, + -0.04830557107925415, + 0.1700863391160965, + 0.418049693107605, + 0.3638938367366791, + 0.3594074249267578, + 0.39813557267189026, + -0.09734770655632019, + 0.23351074755191803, + 0.13417662680149078, + 0.08594613522291183, + 0.4296848177909851, + -0.07181321829557419 + ], + [ + 0.08223146200180054, + -0.0018819124670699239, + -0.26730579137802124, + -0.20485271513462067, + -0.012915043160319328, + 0.28456470370292664, + 0.13464391231536865, + -0.2727796137332916, + 0.15094691514968872, + -0.3096568286418915, + -0.4623531401157379, + -0.05994802713394165, + -0.44913995265960693 + ], + [ + 0.09303081035614014, + 0.12237520515918732, + 0.012606668286025524, + 0.3872867226600647, + 0.10020188242197037, + -0.02615727111697197, + -0.2934131622314453, + 0.4300881028175354, + 0.06807950139045715, + -0.1847943365573883, + -0.2719053030014038, + -0.06416849792003632, + -0.17496007680892944 + ], + [ + 0.10023966431617737, + -0.22861520946025848, + -0.3516126275062561, + -0.5764127373695374, + -0.44583749771118164, + -0.07259082794189453, + 0.35537412762641907, + 0.3627690374851227, + -0.15680935978889465, + -0.07226448506116867, + -0.13952721655368805, + -0.07210751622915268, + 0.23389209806919098 + ], + [ + -0.30529338121414185, + -0.06843209266662598, + -0.29780495166778564, + -0.9098109006881714, + -0.09131325781345367, + -0.3501591384410858, + -0.7427487373352051, + -0.1393917202949524, + 0.08865808695554733, + -0.18189974129199982, + -0.0876726433634758, + 0.14123141765594482, + 0.13371805846691132 + ], + [ + 0.4503017067909241, + 0.16772980988025665, + -0.37819141149520874, + -0.44360822439193726, + 0.060817278921604156, + 0.32554227113723755, + 0.35835281014442444, + 0.01980886608362198, + 0.20961534976959229, + 0.09152323007583618, + 0.36215516924858093, + -0.2382376343011856, + 0.029010865837335587 + ], + [ + 0.1671791672706604, + -0.025726061314344406, + 0.46037933230400085, + 0.33599135279655457, + -0.3053950369358063, + -0.21956385672092438, + 0.018729867413640022, + 0.008232119493186474, + 0.19710946083068848, + -0.05239027366042137, + -0.11997734010219574, + -0.28433626890182495, + -0.05396251007914543 + ], + [ + -0.08888962119817734, + -0.0074840327724814415, + 0.0010366961359977722, + 0.16012191772460938, + -0.4859410524368286, + -0.004400375299155712, + -0.25691479444503784, + -0.2501823306083679, + -0.037692420184612274, + 0.21454042196273804, + -0.06574186682701111, + 0.028364578261971474, + 0.10614991933107376 + ], + [ + -0.19984053075313568, + 0.3999255895614624, + 0.3859013020992279, + -0.008763164281845093, + 0.05337761715054512, + -0.05795661360025406, + 0.19042441248893738, + -0.3980652391910553, + 0.2606085240840912, + -0.36224454641342163, + -0.13887424767017365, + -0.01993805356323719, + 0.25798115134239197 + ], + [ + -0.18213874101638794, + 0.3282216787338257, + -0.5462505221366882, + -0.1092725321650505, + -0.6331356167793274, + 0.12781599164009094, + -0.09713553637266159, + 0.3483908474445343, + 0.11978042870759964, + -0.1137649416923523, + -0.12099278718233109, + 0.024691687896847725, + -0.04209648817777634 + ], + [ + 0.5497441291809082, + -0.0014693968696519732, + 0.2241649627685547, + -0.4881584048271179, + 0.06641259044408798, + -0.3958529233932495, + 0.4216475188732147, + 0.05082586780190468, + 0.20942124724388123, + 0.27624326944351196, + 0.02658577263355255, + -0.1346961259841919, + -0.02302182838320732 + ], + [ + -0.3392588198184967, + 0.20843234658241272, + 0.03391489386558533, + -0.3654923737049103, + -0.11971007287502289, + 0.03183070942759514, + 0.19257013499736786, + 0.18359732627868652, + 0.4768144190311432, + -0.22112081944942474, + 0.24759984016418457, + -0.35773754119873047, + -0.04972255229949951 + ], + [ + 0.1251971572637558, + -0.13569964468479156, + 0.4806353449821472, + -0.0601363442838192, + 0.23355363309383392, + 0.10462657362222672, + -0.8310760855674744, + 0.11379008740186691, + -0.14354120194911957, + 0.2514714002609253, + -0.1564786732196808, + -0.28444764018058777, + 0.10283307731151581 + ], + [ + -0.10372496396303177, + 0.01423429325222969, + 0.4853004217147827, + -0.06958960741758347, + -0.46353626251220703, + -0.0851714089512825, + 0.36282333731651306, + 0.31007006764411926, + 0.2311960607767105, + -0.4897943437099457, + -0.2712634801864624, + 0.008577444590628147, + -0.4192871153354645 + ], + [ + 0.21138827502727509, + -0.38202965259552, + 0.3968730866909027, + 0.18384063243865967, + -0.06147631257772446, + -0.6775612235069275, + -0.07260699570178986, + 0.2006090134382248, + 0.3689287602901459, + -0.08281159400939941, + 0.30175113677978516, + 0.11554694175720215, + 0.005002783611416817 + ], + [ + 0.002080874750390649, + 0.27095964550971985, + -0.05258764326572418, + 0.130062535405159, + -0.0028593065217137337, + 0.4235307276248932, + 0.13979874551296234, + 0.2026497721672058, + 0.10380033403635025, + -0.042964424937963486, + -0.0009023332968354225, + -0.2256675362586975, + 0.11393173038959503 + ], + [ + -0.061181627213954926, + -0.2582927644252777, + 0.31414633989334106, + 0.1496669501066208, + -0.0485113151371479, + 0.5886037945747375, + -0.04424503818154335, + 0.7054523229598999, + 0.18600822985172272, + 0.0029492543544620275, + 0.24034881591796875, + -0.09117217361927032, + -0.1324344426393509 + ], + [ + -0.24351762235164642, + -0.10972386598587036, + 0.1460368037223816, + 0.2663494348526001, + 0.1085638701915741, + 0.2671543061733246, + -0.2784324884414673, + -0.15602554380893707, + 0.21003027260303497, + -0.08113177120685577, + -0.12597565352916718, + -0.2952854037284851, + -0.28854620456695557 + ], + [ + 0.05862046033143997, + -0.05087459459900856, + -0.010610814206302166, + -0.11003550887107849, + 0.03449758514761925, + -0.24074164032936096, + 0.039885833859443665, + 0.2177591174840927, + -0.47024598717689514, + -0.34501907229423523, + -0.46926116943359375, + -0.20928184688091278, + 0.008205903694033623 + ], + [ + -0.17373758554458618, + 0.12019117176532745, + 0.0429859459400177, + -0.42440903186798096, + -0.0805688351392746, + -0.148147314786911, + -0.5859450101852417, + 0.05209624022245407, + 0.031402524560689926, + -0.1676451563835144, + 0.30912086367607117, + 0.27882272005081177, + -0.38265812397003174 + ], + [ + -0.3918400704860687, + -0.2818326950073242, + -0.019607387483119965, + -0.38601183891296387, + -0.25343936681747437, + 0.10142719745635986, + 0.5019767880439758, + 0.014465565793216228, + -0.25335201621055603, + 0.13931375741958618, + -0.04524105787277222, + 0.1716393381357193, + -0.02344362623989582 + ], + [ + -0.2799905836582184, + -0.15262730419635773, + 0.1665252447128296, + -0.1660282164812088, + 0.2298835664987564, + 0.3248575031757355, + 0.1524488925933838, + -0.00577168446034193, + -0.07428950816392899, + -0.17003528773784637, + 0.2906138598918915, + -0.25770461559295654, + -0.10956588387489319 + ], + [ + -0.09922421723604202, + 0.24231721460819244, + 0.033530302345752716, + -0.1840294897556305, + -0.14863182604312897, + -0.3239496350288391, + -0.2615140378475189, + 0.06989920884370804, + -0.471751868724823, + -0.3283047378063202, + 0.02946913056075573, + -0.136993408203125, + -0.06648393720388412 + ], + [ + 0.21499907970428467, + 0.03529290109872818, + -0.08019367605447769, + 0.25295111536979675, + -0.2972573935985565, + -0.6035439968109131, + -0.023684166371822357, + 0.29705801606178284, + 0.34756386280059814, + 0.20807844400405884, + 0.3178033232688904, + 0.02867848612368107, + -0.3608260452747345 + ], + [ + -0.08934739977121353, + -0.04203595966100693, + 0.5485541820526123, + -0.01818658597767353, + -0.5079426765441895, + -0.005266811698675156, + 0.40319061279296875, + -0.016706913709640503, + 0.06470813602209091, + 0.1396273672580719, + 0.026197118684649467, + -0.07552863657474518, + -0.28042691946029663 + ], + [ + -0.24888604879379272, + 0.3297758102416992, + -0.3071618378162384, + -0.0316467359662056, + 0.1978953629732132, + 0.3074512481689453, + 0.1256120502948761, + 0.27231618762016296, + -0.10071903467178345, + 0.06435053050518036, + -0.38230153918266296, + 0.17152005434036255, + 0.007021758705377579 + ], + [ + -0.06701122224330902, + -0.2750074863433838, + -0.4473819434642792, + -0.12073979526758194, + -0.007767376024276018, + 0.030878812074661255, + 0.2267475724220276, + -0.4004344642162323, + 0.3063676655292511, + -0.3240155875682831, + 0.13530080020427704, + -0.04287036508321762, + 0.05107332020998001 + ], + [ + -0.46750450134277344, + 0.16815143823623657, + 0.32987233996391296, + -0.06962510198354721, + 0.09750789403915405, + 0.047996725887060165, + 0.05034244805574417, + -0.1954888552427292, + -0.22761309146881104, + 0.1371261328458786, + -0.08804171532392502, + 0.06251535564661026, + 0.0341489277780056 + ], + [ + 0.09755714982748032, + 0.050311241298913956, + 0.5460689663887024, + -0.42048707604408264, + -0.11051499843597412, + -0.5397922396659851, + -0.08603452146053314, + -0.007517437916249037, + 0.08747653663158417, + 0.10564997792243958, + 0.21647235751152039, + 0.06287024170160294, + -0.032532304525375366 + ], + [ + 0.6455535292625427, + -0.21332912147045135, + -0.5545065402984619, + 0.19560132920742035, + -0.0995100736618042, + 0.3261417746543884, + 0.17067904770374298, + -0.04307596758008003, + 0.1488419622182846, + 0.3426224887371063, + 0.2755790054798126, + 0.006430555135011673, + -0.23489603400230408 + ], + [ + -0.15865539014339447, + 0.20476694405078888, + 0.08771807700395584, + 0.06349878013134003, + 0.10917392373085022, + 0.35466164350509644, + 0.03037361428141594, + -0.14445681869983673, + -0.4520297348499298, + -0.4569331705570221, + -0.14660608768463135, + 0.002050685463473201, + -0.1341574490070343 + ], + [ + 0.15667667984962463, + -0.07297471165657043, + -0.2468172162771225, + 0.23662665486335754, + 0.21779346466064453, + -0.39004045724868774, + -0.3326071798801422, + 0.019281471148133278, + 0.2276054173707962, + 0.08268521726131439, + -0.004261565860360861, + 0.13863730430603027, + 0.2932063937187195 + ], + [ + -0.23732174932956696, + 0.17523734271526337, + 0.16314567625522614, + -0.26516351103782654, + 0.09924314171075821, + -0.3351919949054718, + 0.3986639678478241, + 0.035489097237586975, + -0.17189034819602966, + -0.2672170102596283, + 0.24250206351280212, + 0.2463962435722351, + -0.06627291440963745 + ], + [ + -0.17010390758514404, + -0.016220485791563988, + -0.4636819064617157, + 0.18988023698329926, + -0.44871529936790466, + 0.2544304132461548, + 0.08711282163858414, + -0.00048645163769833744, + 0.23620043694972992, + -0.16803961992263794, + 0.21301205456256866, + 0.15039919316768646, + 0.2291892021894455 + ], + [ + 0.14537300169467926, + 0.09081799536943436, + -0.3272501528263092, + -0.02106560207903385, + 0.12440982460975647, + -0.44731810688972473, + -0.06430705636739731, + -0.02917785383760929, + 0.3398036062717438, + 0.19899587333202362, + -0.06645840406417847, + 0.21473471820354462, + 0.16143213212490082 + ], + [ + -0.25066161155700684, + -0.47398641705513, + -0.2939445972442627, + -0.02203419804573059, + -0.033632926642894745, + -0.01639193668961525, + 0.1068260446190834, + 0.019428517669439316, + 0.21907247602939606, + 0.20629872381687164, + -0.2705807685852051, + -0.07170672714710236, + 0.11179506033658981 + ], + [ + 0.41125601530075073, + 0.2971354126930237, + -0.16849102079868317, + 0.3532737195491791, + 0.2555796504020691, + 0.19581814110279083, + -0.08383610844612122, + -0.017013592645525932, + -0.432533860206604, + 0.025904299691319466, + 0.2505236864089966, + -0.04781761392951012, + -0.02294793538749218 + ], + [ + 0.1652211993932724, + 0.03988897427916527, + 0.04730714485049248, + 0.253953218460083, + 0.337531715631485, + -0.443236380815506, + 0.08540553599596024, + -0.5470725893974304, + 0.016836510971188545, + -0.42600706219673157, + -0.2048584520816803, + 0.02917277254164219, + 0.3407105505466461 + ], + [ + -0.3491149842739105, + 0.11713100969791412, + 0.0007417536107823253, + -0.04195642098784447, + 0.07632623612880707, + 0.4266873598098755, + 0.2728149890899658, + 0.0772416889667511, + 0.2699781358242035, + 0.0138264624401927, + 0.37652042508125305, + -0.08127383887767792, + -0.4270368814468384 + ], + [ + -0.18139664828777313, + -0.03625515475869179, + 0.06310547888278961, + 0.3512726426124573, + 0.09623705595731735, + -0.704629123210907, + 0.23323501646518707, + -0.011263130232691765, + -0.13306908309459686, + -0.1254596710205078, + -0.2687686085700989, + -0.08939646929502487, + 0.12440017610788345 + ], + [ + -0.3647765815258026, + -0.010604667477309704, + -0.21224327385425568, + 0.22111843526363373, + 0.18880735337734222, + -0.25782427191734314, + 0.061157021671533585, + -0.375694215297699, + 0.28101179003715515, + -0.05660352483391762, + -0.1162019744515419, + 0.13459806144237518, + -0.010806424543261528 + ], + [ + -0.29103779792785645, + -0.051839955151081085, + 0.4144618511199951, + -0.0376129113137722, + 0.1778261661529541, + -0.18882791697978973, + 0.08701316267251968, + 0.10776394605636597, + -0.17684288322925568, + -0.3458026945590973, + 0.02329202927649021, + 0.07106459885835648, + 0.0950731411576271 + ], + [ + 0.1390323042869568, + -0.23956452310085297, + -0.5130435228347778, + -0.17717060446739197, + 0.2641371488571167, + 0.4788528084754944, + -0.0034938743337988853, + -0.27584993839263916, + 0.05254009738564491, + 0.022839419543743134, + 0.13870586454868317, + -0.08523643761873245, + 0.124752476811409 + ], + [ + -0.3572181463241577, + 0.029650887474417686, + -0.09930124878883362, + 0.31546223163604736, + -0.1751370131969452, + 0.1705775409936905, + -0.2079431563615799, + -0.17675724625587463, + -0.6910910606384277, + -0.17347437143325806, + 0.20725809037685394, + -0.0918068066239357, + -0.0344637855887413 + ], + [ + -0.20070190727710724, + -0.340415894985199, + -0.12730151414871216, + -0.8188026547431946, + -0.055193398147821426, + -0.2129935771226883, + -0.02483142912387848, + -0.2894546687602997, + -0.08048653602600098, + 0.07144548743963242, + 0.39024782180786133, + 0.015637502074241638, + 0.03475908190011978 + ], + [ + -0.18702059984207153, + -0.30298954248428345, + -0.1267576813697815, + 0.09775310009717941, + -0.18700116872787476, + 0.0017182804876938462, + -0.005887078586965799, + 0.008853036910295486, + -0.14848875999450684, + 0.2002364546060562, + -0.11037757247686386, + -0.1156177669763565, + -0.26102134585380554 + ], + [ + -0.15566055476665497, + 0.03335363045334816, + -0.29146015644073486, + -0.08010867238044739, + 0.1009998545050621, + 0.39260533452033997, + 0.12639951705932617, + -0.054133277386426926, + -0.18723736703395844, + 0.07253473252058029, + 0.07900170236825943, + -0.14228202402591705, + -0.23021511733531952 + ], + [ + -0.2746272087097168, + -0.12257865816354752, + -0.27903813123703003, + 0.27374038100242615, + 0.37847673892974854, + -0.3476799428462982, + 0.12571293115615845, + 0.08535917103290558, + 0.06525271385908127, + -0.15264751017093658, + 0.1868678331375122, + 0.19229933619499207, + -0.07252367585897446 + ], + [ + -0.14401091635227203, + -0.018945656716823578, + 0.01930980756878853, + -0.1692747175693512, + 0.29491451382637024, + -0.39945587515830994, + -0.4657893180847168, + 0.25520092248916626, + 0.15174074470996857, + -0.007469676434993744, + 0.21958154439926147, + -0.05757985636591911, + 0.15635746717453003 + ], + [ + -0.08630824834108353, + 0.03445489704608917, + -0.1587214320898056, + -0.29107725620269775, + -0.41421937942504883, + -0.03159508481621742, + 0.383916974067688, + 0.15776893496513367, + -0.723247230052948, + -0.4182873070240021, + -0.07698579132556915, + -0.3728782534599304, + -0.19663608074188232 + ], + [ + -0.003848988562822342, + -0.07503966242074966, + -0.19928322732448578, + -0.010966913774609566, + 0.2619953155517578, + 0.2057601511478424, + 0.39855146408081055, + -0.18244194984436035, + 0.004585072863847017, + -0.19169408082962036, + -0.1245870515704155, + 0.04967397078871727, + 0.07290806621313095 + ], + [ + 0.35430967807769775, + -0.3794298470020294, + 0.38995155692100525, + -0.3296014964580536, + 0.14668290317058563, + 0.2387242466211319, + 0.2088487595319748, + -0.34669339656829834, + 0.18535639345645905, + 0.2890818417072296, + 0.08807805180549622, + -0.025784548372030258, + 0.2272142767906189 + ], + [ + 0.007772244047373533, + 0.367416113615036, + 0.5523821115493774, + 0.031086372211575508, + 0.4815885126590729, + -0.04104354605078697, + 0.301297664642334, + 0.12076990306377411, + -0.003298743162304163, + 0.11882071942090988, + 0.22718319296836853, + 0.029434815049171448, + 0.09854426234960556 + ], + [ + -0.35811614990234375, + -0.0029762613121420145, + 0.11649613827466965, + 0.181138813495636, + 0.05056029185652733, + -0.5606780052185059, + -0.13362886011600494, + -0.3810370862483978, + -0.5162398219108582, + -0.2884252965450287, + -0.2629288136959076, + 0.21195419132709503, + -0.16714325547218323 + ], + [ + 0.2704832851886749, + 0.17021799087524414, + -0.05895324796438217, + -0.3384433686733246, + -0.3909049928188324, + 0.38553494215011597, + 0.01870807074010372, + -0.1617358922958374, + 0.18386588990688324, + -0.0516870841383934, + -0.28463879227638245, + 0.02212064154446125, + 0.08001381903886795 + ], + [ + 0.18656830489635468, + 0.12879183888435364, + -0.23685885965824127, + 0.26886171102523804, + 0.056689467281103134, + 0.6753718256950378, + -0.29059427976608276, + 0.2333647906780243, + -0.5413393974304199, + 0.006537044420838356, + 0.02271474525332451, + -0.33299899101257324, + -0.004220182541757822 + ], + [ + 0.16717033088207245, + 0.03256445750594139, + 0.052902307361364365, + -0.377789169549942, + 0.2971380352973938, + 0.39744600653648376, + 0.10926368832588196, + 0.10302362591028214, + 0.29225292801856995, + -0.09506676346063614, + 0.23629330098628998, + -0.12180318683385849, + -0.36593934893608093 + ], + [ + -0.13522948324680328, + -0.2980266511440277, + 0.35451021790504456, + 0.3378157317638397, + -0.13947799801826477, + -0.2996511459350586, + -0.0011742900824174285, + 0.32273370027542114, + 0.21668893098831177, + 0.13573935627937317, + 0.02383076772093773, + -0.19183333218097687, + 0.315691739320755 + ], + [ + 0.6778576970100403, + -0.04117833077907562, + 0.10995204746723175, + -0.3809323310852051, + 0.2231072336435318, + -0.1827763319015503, + -0.3075316548347473, + 0.08501533418893814, + -0.027295786887407303, + -0.37125441431999207, + -0.1212998703122139, + -0.10183098912239075, + -0.21415814757347107 + ], + [ + -0.0762210413813591, + 0.09954733401536942, + -0.15605054795742035, + 0.22454659640789032, + 0.11856045573949814, + 0.43652135133743286, + -0.6064560413360596, + -0.36591899394989014, + -0.1464727520942688, + 0.029258253052830696, + 0.16411681473255157, + 0.11690740287303925, + -0.0962761715054512 + ], + [ + 0.199836865067482, + 0.22389492392539978, + -0.3283970355987549, + 0.138694167137146, + -0.20468315482139587, + -0.33403313159942627, + 0.1508159339427948, + -0.5105782151222229, + 0.12724126875400543, + -0.6165580749511719, + -0.21978545188903809, + 0.1782406121492386, + -0.23975792527198792 + ], + [ + -0.6032638549804688, + -0.08001580089330673, + 0.2054527848958969, + 0.13716137409210205, + 0.22460231184959412, + 0.0669151246547699, + -0.22342315316200256, + -0.21532557904720306, + 0.1287473440170288, + 0.2931123971939087, + 0.11478902399539948, + -0.17180760204792023, + 0.12911562621593475 + ], + [ + 0.30323949456214905, + -0.19083261489868164, + -0.2938980758190155, + 0.11518747359514236, + 0.19093647599220276, + -0.3192421495914459, + -0.10780483484268188, + -0.009294162504374981, + 0.2394648641347885, + -0.2130894511938095, + -0.05057841166853905, + -0.3542744815349579, + 0.3232356309890747 + ], + [ + 0.08124428242444992, + -0.0520966537296772, + -0.305100679397583, + 0.12247081100940704, + 0.2345213145017624, + -0.1901734620332718, + -0.15933141112327576, + -0.42104876041412354, + 0.4137115776538849, + 0.09924782812595367, + -0.13499872386455536, + 0.08106622844934464, + 0.0781460553407669 + ], + [ + 0.15870879590511322, + 0.09990561008453369, + 0.27799907326698303, + 0.2629215717315674, + 0.13102057576179504, + -0.2877577543258667, + 0.40693390369415283, + -0.48053330183029175, + 0.2045319676399231, + -0.016775695607066154, + 0.18923497200012207, + 0.026728512719273567, + -0.22340798377990723 + ], + [ + 0.03815097734332085, + 0.006906353402882814, + -0.7048632502555847, + 0.18865975737571716, + 0.43252676725387573, + 0.10937977582216263, + -0.17060090601444244, + 0.10367278009653091, + 0.3629976212978363, + 0.10823354125022888, + 0.13696011900901794, + -0.12976552546024323, + 0.13773807883262634 + ], + [ + 0.081364206969738, + 0.014384191483259201, + -0.2249748408794403, + 0.237743079662323, + 0.16067472100257874, + 0.07851991057395935, + -0.25540047883987427, + 0.29653051495552063, + -0.33306562900543213, + 0.0682794377207756, + 0.2870417833328247, + 0.07474291324615479, + -0.3287220299243927 + ], + [ + 0.07060158252716064, + -0.024512024596333504, + -0.03694286197423935, + 0.42087429761886597, + -0.18074922263622284, + 0.11496856063604355, + -0.16152912378311157, + 0.0698959156870842, + -0.4579349458217621, + -0.2639523148536682, + -0.009549521841108799, + 0.06875064224004745, + 0.057391270995140076 + ], + [ + 0.23166728019714355, + 0.007819642312824726, + -0.39796069264411926, + -0.21911300718784332, + -0.5087413787841797, + -0.12054325640201569, + -0.7190596461296082, + 0.16286888718605042, + 0.46715089678764343, + 0.3178914785385132, + 0.31097346544265747, + 0.07557210326194763, + -0.233867347240448 + ], + [ + 0.5317090153694153, + 0.0028773273807018995, + -0.13949105143547058, + 0.018030831590294838, + 0.33018386363983154, + -0.09435267746448517, + 0.08993704617023468, + 0.20230573415756226, + 0.08943954855203629, + -0.5996811985969543, + -0.2202225923538208, + 0.12224872410297394, + -0.19368217885494232 + ], + [ + 0.45899030566215515, + 0.046330150216817856, + 0.4984212815761566, + -0.02563645876944065, + 0.23955535888671875, + 0.3887278139591217, + 0.07934596389532089, + 0.15366822481155396, + 0.0895312950015068, + -0.02693980745971203, + -0.024568097665905952, + -0.03571607545018196, + -0.045017536729574203 + ], + [ + -0.10204503685235977, + -0.05773258954286575, + -0.3882671296596527, + -0.21408991515636444, + 0.2925959527492523, + 0.21455040574073792, + -0.09583521634340286, + 0.2944086790084839, + -0.09217576682567596, + 0.11946061253547668, + -0.09293468296527863, + -0.007104878313839436, + -0.2693921625614166 + ], + [ + -0.18011142313480377, + -0.2118837982416153, + 0.0850146934390068, + 0.1694103628396988, + -0.32840582728385925, + 0.14944404363632202, + 0.23146238923072815, + 0.20516279339790344, + 0.42950439453125, + -0.06966119259595871, + -0.0071711004711687565, + -0.10337621718645096, + 0.31259724497795105 + ], + [ + 0.07168317586183548, + 0.21827077865600586, + -0.27759888768196106, + 0.29261383414268494, + -0.06325048208236694, + -0.5041850209236145, + -0.05989640951156616, + -0.5404871702194214, + -0.022050760686397552, + -0.29488927125930786, + 0.08027458935976028, + 0.2240152806043625, + 0.25452467799186707 + ], + [ + 0.2295425683259964, + -0.01091746985912323, + -0.6873616576194763, + 0.14215250313282013, + 0.10926423221826553, + 0.39578527212142944, + 0.37126031517982483, + -0.3968247175216675, + 0.1312207579612732, + 0.36060577630996704, + -0.11749473959207535, + -0.14821843802928925, + -0.0017166062025353312 + ], + [ + -0.15426145493984222, + -0.09958052635192871, + -0.30049723386764526, + 0.3120000660419464, + 0.4002348482608795, + -0.2877107858657837, + 0.18284407258033752, + -0.19913774728775024, + -0.21860559284687042, + 0.18161757290363312, + -0.18319052457809448, + 0.19935055077075958, + -0.26109716296195984 + ], + [ + -0.1182982549071312, + -0.16623692214488983, + -0.5328167676925659, + 0.06709256768226624, + 0.3430011570453644, + 0.1386127769947052, + 0.19337624311447144, + -0.16799069941043854, + -0.08994141966104507, + 0.16045938432216644, + -0.08240030705928802, + 0.09082669764757156, + 0.07869929075241089 + ], + [ + 0.373100608587265, + 0.48242470622062683, + 0.08913648873567581, + 0.11254587024450302, + 0.2600317895412445, + 0.4537370502948761, + -0.011607207357883453, + -0.3495674431324005, + 0.11303351819515228, + 0.031104419380426407, + 0.00182831019628793, + -0.08544334024190903, + 0.06462124735116959 + ], + [ + -0.04729556664824486, + -0.46088406443595886, + -0.025429124012589455, + -0.01890188828110695, + -0.08289019018411636, + -0.22444570064544678, + 0.0013382409233599901, + -0.5519993901252747, + 0.26369428634643555, + -0.07250865548849106, + -0.03536079078912735, + 0.12147058546543121, + -0.2906624674797058 + ], + [ + 0.3772434592247009, + -0.2750411927700043, + -0.1991979479789734, + 0.07719862461090088, + 0.04119456931948662, + -0.0746307447552681, + 0.1634635031223297, + -0.1075339987874031, + 0.084819495677948, + 0.4463717043399811, + -0.24016119539737701, + -0.4854397475719452, + 0.28085842728614807 + ], + [ + -0.16955941915512085, + -0.03763536736369133, + -0.06554953753948212, + -0.09292160719633102, + 0.2598607540130615, + -0.005644072778522968, + 0.02511029876768589, + -0.09065071493387222, + 0.17232531309127808, + 0.1180548220872879, + 0.05640468746423721, + 0.003190274117514491, + 0.16484257578849792 + ], + [ + -0.19256524741649628, + -0.035885315388441086, + -0.23651671409606934, + 0.3579004108905792, + -0.26118576526641846, + 0.12682922184467316, + 0.10423010587692261, + 0.4358757436275482, + -0.14004173874855042, + 0.050905436277389526, + 0.30501025915145874, + -0.2547728717327118, + 0.02909197099506855 + ], + [ + -0.00884551927447319, + -0.21905195713043213, + -0.016784757375717163, + 0.3731747567653656, + 0.05382183939218521, + 0.4842839241027832, + -0.1682250052690506, + 0.04149759188294411, + 0.2835145890712738, + 0.1944265067577362, + -0.4236717224121094, + 0.1908518522977829, + -0.351588636636734 + ], + [ + 0.6380516290664673, + -0.023523081094026566, + -0.3044954836368561, + 0.2128516435623169, + 0.1512865275144577, + 0.40091660618782043, + 0.037681423127651215, + 0.14179585874080658, + -0.48863479495048523, + 0.04738679528236389, + 0.3187534213066101, + 0.00045700688497163355, + 0.014066076837480068 + ], + [ + 0.0046538542956113815, + 0.011670686304569244, + -0.37707990407943726, + 0.07988515496253967, + -0.18374578654766083, + 0.10665654391050339, + -0.8602948188781738, + -0.43007123470306396, + -0.20295551419258118, + -0.015174103900790215, + -0.1182069182395935, + 0.11701013147830963, + -0.2779524326324463 + ], + [ + 0.11691876500844955, + -0.2790720462799072, + 0.3339858949184418, + -0.17912739515304565, + -0.002426190534606576, + -0.01318464893847704, + -0.9231696128845215, + -0.2608726918697357, + 0.40690794587135315, + 0.26437997817993164, + 0.042692236602306366, + 0.052349288016557693, + 0.22482885420322418 + ], + [ + 0.2128976732492447, + 0.1569250524044037, + 0.2805536091327667, + -0.12567950785160065, + 0.0372452437877655, + -0.3785509765148163, + 0.0754832774400711, + 0.19418121874332428, + -0.6833413243293762, + -0.18037380278110504, + 0.23891323804855347, + 0.15983836352825165, + 0.2443924844264984 + ], + [ + -0.045922715216875076, + 0.11699707061052322, + 0.057824309915304184, + -0.08571387827396393, + 0.32767918705940247, + -0.29945218563079834, + 0.05165475234389305, + 0.11789916455745697, + 0.3142908811569214, + -0.011986806988716125, + 0.17400377988815308, + -0.3995846211910248, + -0.19427980482578278 + ], + [ + -0.004445775877684355, + 0.15081952512264252, + -0.4417363107204437, + 0.15362563729286194, + 0.21907241642475128, + -0.08657603710889816, + 0.21606825292110443, + 0.3533935546875, + 0.20068368315696716, + -0.24532738327980042, + 0.20568029582500458, + 0.03230249509215355, + -0.18857671320438385 + ], + [ + -0.5563863515853882, + 0.19991205632686615, + 0.7072317600250244, + -0.2047976851463318, + -0.07489289343357086, + 0.34304749965667725, + 0.3296198546886444, + 0.09766529500484467, + -0.0784999430179596, + -0.34556302428245544, + -0.09842559695243835, + 0.039142169058322906, + -0.021947449073195457 + ], + [ + -0.1367456018924713, + -0.0030623823404312134, + 0.057499416172504425, + -0.3996986746788025, + 0.2854032516479492, + -0.3701781928539276, + 0.21766968071460724, + -0.05653444305062294, + -0.23038651049137115, + -0.01095053181052208, + -0.17491167783737183, + 0.10995952039957047, + -0.0757833868265152 + ], + [ + -0.10128746926784515, + 0.10665752738714218, + 0.008848436176776886, + 0.07620900124311447, + 0.0059105996042490005, + 7.205065776361153e-05, + 0.3944801390171051, + 0.3251148462295532, + 0.23769496381282806, + -0.15892939269542694, + 0.07100225985050201, + -0.4332124888896942, + 0.010096431709825993 + ], + [ + -0.1768210083246231, + -0.18340276181697845, + 0.001623425050638616, + 0.20159699022769928, + -0.08927533030509949, + 0.6392431259155273, + -0.9325793385505676, + 0.25249183177948, + -0.24121147394180298, + 0.33376017212867737, + 0.4692442715167999, + 0.035955436527729034, + 0.08090293407440186 + ], + [ + -0.22564229369163513, + -0.2561354637145996, + -1.3654130697250366, + -0.15530513226985931, + -0.0026599070988595486, + 0.08762144297361374, + -0.38049525022506714, + 0.10087984055280685, + -0.09655912220478058, + -0.02611776441335678, + 0.012519799172878265, + 0.030432825908064842, + -0.09684382379055023 + ], + [ + 0.28824716806411743, + -0.21870803833007812, + -0.16924481093883514, + 0.46756282448768616, + -0.3193212151527405, + -0.20083653926849365, + -0.45283883810043335, + 0.4936756193637848, + -0.25036272406578064, + 0.1074955016374588, + 0.13981464505195618, + -0.07062017917633057, + 0.20453374087810516 + ], + [ + -0.37632426619529724, + 0.023242535069584846, + 0.403758704662323, + -0.5405698418617249, + -0.04872119426727295, + -0.1674855798482895, + -0.5529700517654419, + -0.3500245213508606, + -0.5704483389854431, + -0.014192325994372368, + -0.0592123307287693, + -0.11486945301294327, + -0.2126927375793457 + ], + [ + -0.20464110374450684, + -0.0850566029548645, + -0.029002338647842407, + 0.12800747156143188, + -0.6059226393699646, + 0.3300586938858032, + 0.28265616297721863, + -0.02119620144367218, + 0.3012149930000305, + -0.30299875140190125, + 0.09099925309419632, + 0.07387415319681168, + 0.11487554013729095 + ], + [ + 0.1986282914876938, + 0.19519412517547607, + -0.38607245683670044, + 0.3127345144748688, + 0.002946534426882863, + -0.4678138792514801, + -0.2860093116760254, + 0.09629431366920471, + 0.270903080701828, + -0.12366373091936111, + 0.12661738693714142, + -0.30580514669418335, + -0.3104628920555115 + ], + [ + -0.2955853044986725, + -0.3251827359199524, + -0.2509268820285797, + 0.07475117594003677, + -0.20850567519664764, + -0.44456949830055237, + 0.051270436495542526, + 0.018012693151831627, + 0.11225541681051254, + -0.1231650710105896, + -0.2772364616394043, + -0.22073239088058472, + -0.0653977021574974 + ], + [ + 0.1933528631925583, + -0.1599702686071396, + 0.3890591859817505, + 0.1769028753042221, + -0.1299305111169815, + 0.11517280340194702, + 0.21320852637290955, + -0.023867473006248474, + -0.08731769770383835, + -0.238845095038414, + -0.06022835522890091, + 0.20325517654418945, + 0.26722362637519836 + ], + [ + -0.34047064185142517, + -0.07236069440841675, + 0.06118741258978844, + 0.08961661905050278, + 0.17382913827896118, + 0.2728501558303833, + 0.25996580719947815, + -0.28523385524749756, + -0.447998970746994, + -0.16263441741466522, + 0.030397776514291763, + 0.07321569323539734, + 0.10687745362520218 + ], + [ + -0.32778340578079224, + -0.36357372999191284, + 0.10514234751462936, + 0.1822606474161148, + 0.026659611612558365, + -0.002128040883690119, + -0.16742902994155884, + 0.22414827346801758, + 0.34101632237434387, + -0.414081871509552, + -0.24726711213588715, + -0.1910814791917801, + -0.25300133228302 + ], + [ + 0.20284202694892883, + 0.22310879826545715, + -0.09175264835357666, + -0.40912482142448425, + -0.1802767962217331, + -0.2787109911441803, + -0.3064904510974884, + 0.22596676647663116, + -0.3635615408420563, + 0.2812367081642151, + 0.11356999725103378, + -0.318589448928833, + 0.08336939662694931 + ], + [ + 0.38195160031318665, + 0.03335738927125931, + -0.5335058569908142, + -0.03819908946752548, + -0.02460385486483574, + -0.3300994336605072, + 0.3992239832878113, + -0.07268156111240387, + 0.03751498460769653, + 0.17805716395378113, + -0.08026189357042313, + -0.3458326756954193, + -0.20366187393665314 + ], + [ + -0.46825602650642395, + -0.0029622504953294992, + -0.09496761858463287, + 0.1982409507036209, + 0.15728545188903809, + -0.2035212367773056, + 0.16241173446178436, + -0.38586297631263733, + 0.3763304650783539, + -0.03425220027565956, + -0.22369584441184998, + -0.31587332487106323, + -0.13464592397212982 + ], + [ + -0.13012930750846863, + 0.3396182954311371, + -0.4679093360900879, + -0.3572634756565094, + -0.19409683346748352, + 0.04419347643852234, + 0.24613319337368011, + 0.061356887221336365, + 0.4071618914604187, + 0.47669270634651184, + -0.09375584870576859, + -0.19679337739944458, + -0.44209086894989014 + ] + ], + [ + [ + 0.018219739198684692, + -0.09646723419427872, + 0.07454709708690643, + 0.06161380931735039, + -0.006799010559916496, + 0.10184838622808456, + 0.08798973262310028, + -0.07850953191518784, + 0.011188327334821224, + 0.08524810522794724, + -0.1571773737668991, + 0.035052474588155746, + 0.1785498410463333, + 0.029873937368392944, + -0.08988912403583527, + 0.022495459765195847, + 0.03982220217585564, + 0.029116356745362282, + -0.08388350903987885, + -0.03617621585726738, + -0.02884429693222046, + -0.04017459228634834, + -0.017280157655477524, + 0.04433999955654144, + -0.02471611648797989, + 0.029065009206533432, + 0.013151707127690315, + 0.07672682404518127, + -0.02299288846552372, + -0.0760030522942543, + 0.03338173031806946, + -0.021944932639598846, + -0.0662168487906456, + -0.0046288142912089825, + -0.020774496719241142, + 0.16680222749710083, + -0.03899861127138138, + 0.05796864256262779, + 0.0340903140604496, + 0.049473755061626434, + -0.047948434948921204, + -0.1550675332546234, + 0.07000972330570221, + 0.07917586714029312, + 0.016673870384693146, + -0.009089440107345581, + 0.017897728830575943, + -0.015161716379225254, + 0.06361237168312073, + -0.062846340239048, + -0.07507970184087753, + 0.02215183526277542, + 0.0723285973072052, + 0.053254906088113785, + 0.026656638830900192, + -0.09317565709352493, + 0.11220590770244598, + 0.07024280726909637, + 0.0561104454100132, + 0.02861620858311653, + 0.03564126044511795, + -0.009296980686485767, + 0.0075655654072761536, + -0.009516087360680103, + -0.02917361631989479, + -0.21397820115089417, + 0.0018511217785999179, + 0.00492468848824501, + -0.01981751248240471, + -0.09233584254980087, + -0.18141627311706543, + 0.061435017734766006, + 0.010503634810447693, + 0.0050401524640619755, + -0.03528561443090439, + -0.0941401794552803, + 0.07366326451301575, + 0.0035352243576198816, + 0.05383983254432678, + -0.06497901678085327, + 0.08547446876764297, + 0.019188271835446358, + 0.013645362108945847, + -0.07891425490379333, + 0.10479230433702469, + 0.11281126737594604, + 0.04737356677651405, + 0.046065255999565125, + 0.007549292407929897, + 0.03802464157342911, + -0.048982586711645126, + -0.07231000810861588, + 0.05617180094122887, + -0.05134878680109978, + -0.06102363392710686, + 0.06286761909723282, + 0.10445437580347061, + -0.06788648664951324, + -8.455992792733014e-05, + -0.053546905517578125, + 0.0407492071390152, + -0.05465124920010567, + 0.0558830089867115, + -0.05688697472214699, + -0.05511736869812012, + 0.01887454278767109, + -0.036653291434049606, + -0.09590993076562881, + 0.08518277108669281, + 0.04751770943403244, + 0.00690359016880393, + 0.01838502660393715, + -0.0657363012433052, + 0.043211743235588074, + 0.09584445506334305, + -0.01736079715192318, + 0.005686406046152115, + -0.14709696173667908, + -0.027912955731153488, + 0.044494785368442535, + 0.04404277354478836, + -0.018893957138061523, + 0.031774066388607025, + -0.030790507793426514, + 0.10381884127855301, + 0.10012118518352509, + 0.04456346854567528, + -0.12345439940690994 + ], + [ + -0.04366224259138107, + -0.03988340497016907, + -0.027272675186395645, + -0.08578596264123917, + 0.09593483805656433, + 0.1438608467578888, + 0.22205762565135956, + -0.054564010351896286, + -0.029638443142175674, + 0.24980893731117249, + 0.2250930815935135, + 0.038504816591739655, + 0.02610793709754944, + 0.14048533141613007, + -0.013169611804187298, + 0.0033675716258585453, + 0.1559501588344574, + -0.08512993901968002, + -0.009641251526772976, + 0.04013749212026596, + -0.16436025500297546, + 0.02284420095384121, + 0.04611126706004143, + 0.011338652111589909, + -0.05414551869034767, + -0.0033125155605375767, + 0.055500030517578125, + 0.15653949975967407, + -0.051860034465789795, + 0.034047193825244904, + 0.09617786854505539, + 0.017718710005283356, + 0.1395457237958908, + 0.059239376336336136, + -0.08237612247467041, + 0.0016281215939670801, + -0.12236908078193665, + 0.05766136944293976, + 0.030708888545632362, + 0.0032188640907406807, + -0.1876683384180069, + 0.07252556830644608, + -0.05732478201389313, + 0.024640005081892014, + -0.2488022893667221, + -0.036899764090776443, + 0.08544021844863892, + 0.023594796657562256, + 0.12174620479345322, + -0.10455157607793808, + 0.07488745450973511, + -0.008201503194868565, + -0.5354313850402832, + -0.032505642622709274, + -0.1553194522857666, + 0.024152345955371857, + 0.020946798846125603, + 0.11037244647741318, + -0.08579467982053757, + 0.054161135107278824, + 0.054597996175289154, + -0.18702523410320282, + -0.06077027693390846, + -0.045753903687000275, + -0.06247496232390404, + -0.1746106743812561, + -0.01596141792833805, + -0.041279226541519165, + 0.026474345475435257, + -0.06815830618143082, + -0.07384060323238373, + -0.04557356610894203, + -0.02851514145731926, + -0.15558405220508575, + 0.032944124191999435, + -0.1305556744337082, + 0.15945951640605927, + -0.09210573881864548, + 0.045924678444862366, + -0.03189486265182495, + -0.016103772446513176, + -0.1000695675611496, + 0.08418722450733185, + 0.02808430790901184, + -0.018935440108180046, + -0.006368079222738743, + 0.1346205174922943, + 0.06244322657585144, + 0.10128394514322281, + -0.21042297780513763, + -0.011791245080530643, + 0.00697533180937171, + 0.047211967408657074, + 0.03484340012073517, + -0.020177500322461128, + 0.05033306032419205, + -0.08386674523353577, + -0.21777044236660004, + -0.03588562831282616, + -0.02458086423575878, + 0.04479387030005455, + -0.11563028395175934, + 0.014267588965594769, + 0.02016410417854786, + -0.07439658790826797, + -0.03553749620914459, + -0.17706096172332764, + -0.08134593069553375, + 0.09938134998083115, + -0.14681099355220795, + -0.172814279794693, + -0.07844581454992294, + -0.10121552646160126, + 0.06735233962535858, + 0.037241797894239426, + 0.15259318053722382, + -0.03785921633243561, + -0.11281542479991913, + 0.02455972693860531, + -0.05434728041291237, + 0.05339659005403519, + -0.01463327743113041, + -0.04604827240109444, + -0.04048222303390503, + 0.16280102729797363, + 0.12418854981660843, + 0.08864647150039673, + 0.07456153631210327 + ], + [ + -0.08777999877929688, + -0.08828102797269821, + 0.09394829720258713, + -0.044136788696050644, + -0.06294093281030655, + -0.08060112595558167, + 0.05393722280859947, + -0.07617608457803726, + 0.06506304442882538, + 0.05197449028491974, + -0.22411371767520905, + 0.046819448471069336, + 0.26677319407463074, + 0.09424933791160583, + 0.05660441890358925, + 0.08204120397567749, + -0.28375932574272156, + 0.09491997212171555, + -0.027609892189502716, + -0.07027342170476913, + -0.17479217052459717, + 0.30773741006851196, + -0.16301515698432922, + -0.12343518435955048, + 0.011471367441117764, + 0.12639306485652924, + -0.3234254717826843, + -0.02480117790400982, + -0.07915925234556198, + 0.14103731513023376, + 0.0663117840886116, + -0.12133550643920898, + 0.07859761267900467, + -0.06832479685544968, + 0.047317929565906525, + -0.18684375286102295, + 0.019010545685887337, + 0.07009278982877731, + 0.049190275371074677, + -0.3498014509677887, + -0.13270793855190277, + -0.2593308091163635, + 0.1141480877995491, + -0.1748884916305542, + -0.20433549582958221, + -0.027941307052969933, + 0.10660794377326965, + -0.04801894724369049, + -0.0168705265969038, + -0.1668137162923813, + -0.03204342722892761, + 0.08961070328950882, + -0.022071899846196175, + 0.0973498672246933, + 0.04838287830352783, + 0.025685787200927734, + -0.008137140423059464, + 0.1332472860813141, + 0.0861138254404068, + 0.07386302947998047, + 0.15232205390930176, + 0.15091367065906525, + 0.032322537153959274, + -0.04015224426984787, + 0.1709579974412918, + 0.37716659903526306, + 0.18268543481826782, + 0.1602870225906372, + 0.024307414889335632, + -0.4708167314529419, + 0.04431665316224098, + -0.03973490372300148, + -0.28667396306991577, + 0.07172611355781555, + -0.052345603704452515, + 0.13505011796951294, + -0.0028523753862828016, + 0.0018361147958785295, + -0.025408020243048668, + -0.044182341545820236, + 0.002621020656079054, + 0.10347174108028412, + -0.3277919888496399, + -0.0511430948972702, + -0.1834374964237213, + 0.03346710652112961, + -0.017552994191646576, + -0.15763753652572632, + -0.18161317706108093, + -0.028803296387195587, + 0.134186252951622, + -0.08387858420610428, + 0.07384360581636429, + -0.07357937097549438, + -0.0451325997710228, + 0.1873624175786972, + 0.13002994656562805, + 0.20409730076789856, + -0.28164854645729065, + -0.041622135788202286, + 0.011276671662926674, + 0.09881874173879623, + -0.1091432124376297, + -0.05511726438999176, + -0.06761851906776428, + -0.06233453005552292, + -0.002571213524788618, + 0.0007353940745815635, + 0.08992432057857513, + -0.02546185441315174, + 0.08591751754283905, + 0.03086784854531288, + -0.4728988707065582, + -0.16527283191680908, + 0.0025602495297789574, + 0.0685092955827713, + -0.01541915349662304, + 0.2594982385635376, + -0.0031214922200888395, + -0.04352348670363426, + -0.2147342562675476, + 0.14683598279953003, + -0.3219284415245056, + -0.5988808870315552, + -0.08926576375961304, + 0.049239370971918106, + -0.12583103775978088, + -0.054186515510082245 + ], + [ + 0.07710544764995575, + -0.18329750001430511, + 0.09492483735084534, + 0.07396245747804642, + 0.005324447527527809, + 0.08248968422412872, + -0.15271462500095367, + -0.22876857221126556, + -0.045491866767406464, + -0.0427100732922554, + 0.016563596203923225, + 0.09627670049667358, + -0.3218867778778076, + 0.20546002686023712, + 0.11841712146997452, + 0.11589130014181137, + -0.09981642663478851, + 0.04615166783332825, + -0.18844769895076752, + -0.2854122221469879, + 0.06752467900514603, + 0.08963949233293533, + -0.13537634909152985, + 0.05911797657608986, + 0.1287340670824051, + -0.03183428570628166, + -0.058367349207401276, + 0.1617414504289627, + 0.17339740693569183, + 0.10734125971794128, + -0.12121203541755676, + -0.14875103533267975, + 0.06654571741819382, + -0.05614486709237099, + 0.13991184532642365, + 0.01342823076993227, + 0.22406667470932007, + 0.023757832124829292, + 0.049936648458242416, + -0.010973641648888588, + -0.06290178000926971, + 0.11927430331707001, + -0.006864798720926046, + 0.0794132724404335, + 0.02207094430923462, + -0.030406678095459938, + 0.10549483448266983, + -0.043844159692525864, + -0.040650878101587296, + 0.000997487804852426, + 0.25994622707366943, + 0.16689202189445496, + -0.08382531255483627, + 0.08189453184604645, + -0.24978703260421753, + -0.009402989409863949, + 0.171400249004364, + 0.004062807187438011, + -0.052251726388931274, + -0.1314944475889206, + 0.06613542884588242, + -0.14117494225502014, + -0.0469946451485157, + 0.04849262163043022, + 0.051265522837638855, + 0.014989004470407963, + 0.04981909319758415, + -0.06804874539375305, + -0.09144828468561172, + -0.06569790095090866, + -0.03956082463264465, + -0.0069680605083703995, + -0.14746804535388947, + 0.08820489794015884, + -0.015624400228261948, + -0.2035723179578781, + 0.1816118061542511, + 0.07732156664133072, + 0.07195135951042175, + 0.10831501334905624, + 0.17344556748867035, + -0.010874085128307343, + -0.009266512468457222, + 0.009654657915234566, + 0.15002234280109406, + 0.0868125930428505, + -0.01889617182314396, + -0.1041441559791565, + 0.061954792588949203, + -0.017732758074998856, + -0.002066943095996976, + 0.08301135152578354, + 0.033814750611782074, + 0.12804098427295685, + 0.0940321758389473, + 0.05929211899638176, + 0.08111003786325455, + -0.06460170447826385, + -0.18669556081295013, + -0.02501339092850685, + 0.07367009669542313, + 0.19835789501667023, + 0.07208416610956192, + 0.06555461883544922, + 0.05209244042634964, + 0.12305328249931335, + 0.02143392339348793, + 0.21838442981243134, + 0.1600487381219864, + -0.11680774390697479, + 0.13360601663589478, + -0.08702053874731064, + -0.03975346311926842, + 0.020558465272188187, + 0.14914433658123016, + -0.22512729465961456, + 0.026435425505042076, + 0.02134077437222004, + -0.050070423632860184, + -0.16032899916172028, + -0.04838675633072853, + 0.05955909937620163, + -0.3921580910682678, + -0.2889035642147064, + -0.018992487341165543, + 0.1880977749824524, + -0.22202685475349426, + 0.12896431982517242 + ], + [ + -0.14982032775878906, + -0.0438121072947979, + 0.1618519425392151, + 0.06273909658193588, + -0.06122744455933571, + -0.08573876321315765, + -0.03334282711148262, + -0.06522249430418015, + 0.10430960357189178, + -0.19786617159843445, + 0.3869205117225647, + 0.10419587790966034, + -0.08232583850622177, + -0.045139115303754807, + -0.005762174259871244, + 0.01088549755513668, + 0.0731579065322876, + -0.3183622658252716, + 0.012427416630089283, + -0.08503033965826035, + -0.7466164231300354, + 0.011984540149569511, + -0.10463400930166245, + 0.18085621297359467, + 0.027885498479008675, + -0.21805012226104736, + -0.11751596629619598, + 0.09866130352020264, + 0.3014037609100342, + 0.13392266631126404, + 0.19781625270843506, + 0.05038033798336983, + -0.38718441128730774, + 0.1848674714565277, + 0.21683885157108307, + -0.5937297940254211, + -0.07603869587182999, + 0.20278185606002808, + -0.15553180873394012, + 0.03819640725851059, + -0.009890005923807621, + -0.05258834734559059, + -0.5933769345283508, + 0.1463337540626526, + -0.40082675218582153, + -0.27249711751937866, + 0.15393826365470886, + -0.11246295273303986, + 0.1274707019329071, + 0.06036047264933586, + -0.24703647196292877, + 0.09616688638925552, + 0.029776768758893013, + -0.18739250302314758, + -0.2982015311717987, + 0.08034591376781464, + 0.0705164447426796, + 0.10652870684862137, + 0.0401625894010067, + 0.061956360936164856, + -0.20484572649002075, + 0.06912866234779358, + -0.18469657003879547, + -0.12315119802951813, + 0.03582711145281792, + 0.08491581678390503, + -0.007683210540562868, + -0.26193612813949585, + 0.083865687251091, + 0.05912237986922264, + 0.14736482501029968, + -0.13348844647407532, + 0.06776504963636398, + -0.0034948443062603474, + 0.03021770715713501, + 0.03882430121302605, + 0.17496226727962494, + -0.11695406585931778, + -0.16507813334465027, + -0.10649487376213074, + 0.006603171583265066, + 0.06768722832202911, + -0.050011735409498215, + -0.27403905987739563, + -0.10870315879583359, + 0.02423289604485035, + 0.16262875497341156, + 0.023541515693068504, + -0.029662471264600754, + 0.11368146538734436, + -0.010363632813096046, + 0.04634997621178627, + -0.682279109954834, + 0.23176009953022003, + 0.09290780872106552, + -0.00039114730316214263, + -0.18280769884586334, + -0.00850045494735241, + 0.11619364470243454, + -0.001821874058805406, + 0.03447658196091652, + 0.01767871342599392, + -0.03021698258817196, + 0.09701815247535706, + -0.29362040758132935, + -0.06804905831813812, + 0.00643650908023119, + 0.009058542549610138, + -0.23731356859207153, + 0.007565407082438469, + -0.31476372480392456, + 0.12198641151189804, + 0.07928924262523651, + -0.05933317169547081, + 0.014917676337063313, + 0.17360010743141174, + -0.02559753507375717, + -0.0659712553024292, + 0.18342210352420807, + 0.05127304419875145, + -0.02824457176029682, + -0.04391869157552719, + 0.013763032853603363, + -0.04619654268026352, + 0.04905478283762932, + -0.0899888351559639, + -0.010250585153698921, + 0.003663488896563649 + ], + [ + 0.11707988381385803, + -0.012062357738614082, + -0.21134260296821594, + -0.0901583656668663, + -0.04336472973227501, + -0.08038540929555893, + 0.12857629358768463, + 0.037310514599084854, + 0.001155287493020296, + -0.18154838681221008, + -0.4831831753253937, + -0.18810047209262848, + -0.20520667731761932, + -0.12275321036577225, + -0.07644340395927429, + 0.16898515820503235, + -1.1960588693618774, + 0.13621988892555237, + -0.29121333360671997, + -0.35968175530433655, + -0.08596019446849823, + 0.08861041069030762, + -0.005422593094408512, + -0.01172693818807602, + 0.13935968279838562, + -0.3189579248428345, + 0.01244546938687563, + -0.008065720088779926, + 0.12723392248153687, + -0.05961800366640091, + 0.03831127658486366, + -0.05634935945272446, + -0.3498627245426178, + -0.6863208413124084, + 0.06956834346055984, + 0.1816672384738922, + -0.3183107078075409, + -0.0981999933719635, + -0.002650436945259571, + 0.2681486904621124, + 0.007516677025705576, + 0.10718858987092972, + 0.35853880643844604, + 0.09932074695825577, + 0.19860641658306122, + -0.5967840552330017, + -0.4281823933124542, + 0.18975435197353363, + 0.012677052058279514, + -0.14243347942829132, + 0.0359477661550045, + -0.002246828516945243, + 0.15934644639492035, + 0.21286478638648987, + 0.31106454133987427, + -0.27341189980506897, + 0.024886377155780792, + 0.13637128472328186, + -0.023705124855041504, + -0.3759010136127472, + -0.1565374881029129, + 0.0526781901717186, + 0.1087900698184967, + 0.20917214453220367, + -0.05167149007320404, + -0.12696072459220886, + -0.18906660377979279, + 0.018862957134842873, + 0.06601996719837189, + -0.01790613681077957, + -0.08147965371608734, + -0.07037747651338577, + -0.22820989787578583, + -0.18264365196228027, + -0.18189352750778198, + 0.11085999011993408, + -0.20925398170948029, + 0.14725826680660248, + -0.05900055542588234, + -0.29020750522613525, + 0.04318507760763168, + 0.0020446127746254206, + 0.033521197736263275, + -0.23804806172847748, + 0.108457550406456, + 0.06655804067850113, + 0.03150900825858116, + -0.4711359441280365, + 0.02820977196097374, + 0.0003283287223894149, + 0.18346089124679565, + -0.22149167954921722, + -0.638874351978302, + 0.16683624684810638, + 0.3318592607975006, + 0.04412943497300148, + 0.16905361413955688, + 0.08307899534702301, + -0.07756691426038742, + 0.08944916725158691, + 0.11442209035158157, + 0.039660077542066574, + 0.032522059977054596, + -0.15192092955112457, + 0.10905548185110092, + 0.01011120155453682, + -0.0063481261022388935, + 0.2228701114654541, + -0.03282828629016876, + -0.07650118321180344, + 0.2302437424659729, + -0.03907551243901253, + 0.1344173550605774, + 0.06473687291145325, + 0.07323276996612549, + 0.06255131959915161, + 0.23272645473480225, + 0.07507288455963135, + -0.2470303773880005, + 0.21013285219669342, + -0.06068211421370506, + -0.028370413929224014, + 0.1136016845703125, + 0.1780325174331665, + -0.3764476776123047, + -0.008990650065243244, + -0.02255677618086338, + -0.2012152522802353 + ], + [ + 0.03936747461557388, + 0.05760778859257698, + 0.17184151709079742, + 0.061656009405851364, + 0.02115621790289879, + -0.02682637609541416, + -0.23889857530593872, + 0.032917097210884094, + -0.1310713291168213, + -0.052512649446725845, + 0.06268664449453354, + 0.011497918516397476, + -0.32750463485717773, + 0.1271658092737198, + 0.029513025656342506, + 0.07868492603302002, + -0.14726945757865906, + -0.10937400907278061, + 0.05438733473420143, + -0.02940277010202408, + -0.07206796854734421, + 0.014690808020532131, + -0.038904741406440735, + -0.0755150094628334, + 0.06934578716754913, + -0.09066906571388245, + 0.06439448148012161, + -0.6019983291625977, + 0.24964351952075958, + 0.06756246834993362, + 0.20133481919765472, + -0.31035467982292175, + -0.4670463502407074, + 0.20263099670410156, + 0.1495734304189682, + -0.17337815463542938, + -0.0814167857170105, + 0.16301429271697998, + -0.17113785445690155, + 0.011796075850725174, + 0.07431884109973907, + -0.011949067935347557, + 0.04033703729510307, + -0.0384526289999485, + 0.021888025104999542, + -0.3256795108318329, + 0.09764056652784348, + -0.11547206342220306, + 0.009990040212869644, + -0.0007690232596360147, + 0.08858699351549149, + 0.006568937096744776, + -0.07664135843515396, + -0.110886350274086, + 0.016882408410310745, + 0.03436069190502167, + 0.10611739754676819, + 0.0006027460913173854, + -0.009608699940145016, + 0.059267327189445496, + 0.1073053851723671, + 0.04530549421906471, + -0.01619189977645874, + 0.137496680021286, + 0.12040077894926071, + -0.10403656214475632, + 0.29377231001853943, + -0.12708911299705505, + -0.11599291115999222, + -0.002952129114419222, + 0.06096046045422554, + -0.12166620045900345, + 0.03810232877731323, + 0.02259567566215992, + 0.005838738288730383, + 0.11958830058574677, + -0.14639411866664886, + -0.3870731294155121, + -0.17899182438850403, + -0.17306245863437653, + -0.11419942229986191, + -0.013556916266679764, + 0.05984777957201004, + 0.00554418470710516, + 0.01343554724007845, + 0.13138322532176971, + 0.20628155767917633, + 0.03328976035118103, + -0.08494625985622406, + -0.034574560821056366, + -0.39129430055618286, + 0.12612563371658325, + -0.3977392017841339, + -0.05173712968826294, + -0.07228949666023254, + 0.19712670147418976, + -0.19306683540344238, + 0.10010359436273575, + 0.0014237085124477744, + -0.17489585280418396, + 0.13310351967811584, + -0.07438543438911438, + 0.10692553222179413, + -0.028833646327257156, + -0.13412727415561676, + 0.03237123414874077, + -0.03446381539106369, + 0.06430743634700775, + 0.04411814361810684, + -0.06312329322099686, + -0.1818610429763794, + 0.13397471606731415, + 0.07224933803081512, + 0.040291883051395416, + -0.028984030708670616, + -0.11788427829742432, + 0.06873753666877747, + 0.18020471930503845, + 0.020279547199606895, + 0.12370510399341583, + -0.03191715478897095, + -0.04649142175912857, + -0.0929684266448021, + 0.05522165820002556, + -0.4121522307395935, + -0.1106007993221283, + -0.13722629845142365, + -0.7423602938652039 + ], + [ + -0.10283973067998886, + -0.03984961658716202, + 0.057836320251226425, + -0.07530853152275085, + 0.061661865562200546, + 0.12187274545431137, + 0.017080266028642654, + -0.08751711249351501, + 0.009585625492036343, + 0.15795555710792542, + 0.0414922758936882, + 0.009402278810739517, + -0.10274571925401688, + 0.03383331373333931, + 0.00859924964606762, + -0.14399734139442444, + -0.036325350403785706, + -0.07787557691335678, + -0.07997114211320877, + 0.07484255731105804, + -0.0927937850356102, + 0.007331077940762043, + 0.09539409726858139, + 0.048107896000146866, + -0.15003743767738342, + -0.058698516339063644, + -0.08889590203762054, + 0.05983630195260048, + -0.03845806047320366, + 0.07196547836065292, + 0.20435070991516113, + -0.03439377248287201, + -0.07293073832988739, + 0.003368769772350788, + -0.2043071836233139, + 0.041323915123939514, + -0.03691798448562622, + 0.052060239017009735, + -0.06334474682807922, + 0.06350775063037872, + -0.17736117541790009, + 0.07516203075647354, + 0.03710560128092766, + 0.09054245054721832, + -0.12412932515144348, + -0.30105265974998474, + -0.0462571419775486, + -0.04892895370721817, + 0.09597120434045792, + -0.16400237381458282, + 0.11350036412477493, + -0.06311789155006409, + -0.13055790960788727, + 0.022336307913064957, + 0.0013610244495794177, + 0.013146121054887772, + -0.10032600909471512, + 0.05416052043437958, + 0.11732112616300583, + -0.07413464039564133, + 0.01350784208625555, + -0.004450702108442783, + 0.08555237203836441, + -0.04758218303322792, + 0.062199223786592484, + 0.07473936676979065, + 0.16612428426742554, + -0.03797914460301399, + 0.07917381823062897, + 0.10846436023712158, + 0.016377154737710953, + -0.10064995288848877, + 0.004516888409852982, + -0.006061824504286051, + -0.05185989290475845, + 0.021475743502378464, + -0.000630661437753588, + -0.0030517277773469687, + 0.07590028643608093, + 0.05946324020624161, + 0.02406134642660618, + 0.0054471432231366634, + -0.01517221238464117, + 0.06061382219195366, + -0.001450062613002956, + -0.017832666635513306, + 0.1013614609837532, + -0.10011085867881775, + -0.12895536422729492, + -0.06204682216048241, + -0.05191326513886452, + -0.03854918107390404, + 0.008216272108256817, + 0.06809049844741821, + -0.015553941950201988, + 0.06485951691865921, + 0.04411296173930168, + 0.025506576523184776, + 0.04994133859872818, + 0.015198955312371254, + 0.001620965776965022, + -0.0776236280798912, + 0.1180325299501419, + 0.08628765493631363, + -0.06278582662343979, + 0.010058251209557056, + -0.028645483776926994, + -0.003767607035115361, + -0.11952932178974152, + -0.08966277539730072, + -0.027798842638731003, + -0.010600497014820576, + -0.09261175990104675, + 0.033845532685518265, + -0.15218991041183472, + -0.02115710638463497, + 0.0186784490942955, + -0.09833063930273056, + -0.06596490740776062, + -0.10110572725534439, + -0.00592827470973134, + 0.06700719147920609, + -0.04142523929476738, + 0.024928661063313484, + 0.13474304974079132, + -0.1610475778579712, + -0.03819579258561134, + -0.022631030529737473 + ], + [ + -9.666322720650764e-37, + -6.135865585739077e-41, + -8.511566500161725e-28, + 1.7412244648918062e-38, + -3.579979285938841e-26, + -4.125506756880121e-40, + -4.016765996048515e-40, + -3.4321582897168447e-40, + -1.5084768327768876e-25, + -3.975189470611998e-40, + -5.3744280261942574e-40, + -2.3043589988816146e-34, + 3.186720863690353e-40, + 5.493089980153283e-41, + -1.0469629773169366e-30, + 1.7370776023463297e-40, + -4.910780403303105e-40, + 4.142728715006673e-40, + -2.0641827028736718e-40, + -5.927996971541133e-40, + 9.981448961385672e-42, + 1.7810503481568425e-41, + 6.344995368554912e-40, + 2.1776925911725885e-26, + 1.3744075467944238e-40, + -1.735788407759151e-40, + -3.892428783308974e-40, + -2.1361204014690687e-28, + 3.052871057236312e-26, + 1.5485188809867824e-40, + 5.434389587482716e-40, + -2.2365564269702675e-40, + 4.684750961007512e-40, + -5.2976929222878304e-40, + 5.647468064210619e-32, + -5.234466335577495e-40, + 1.1296716626204542e-28, + 4.5323737659968316e-40, + 1.0851817179401369e-28, + 1.7150688273412533e-30, + -1.975830834697992e-41, + -4.048953821774056e-40, + -4.817944380041586e-41, + -4.389997937527806e-28, + -4.071136376464318e-40, + 2.45059075441124e-41, + 5.4153739673218286e-40, + -4.173697411068251e-40, + -2.5964476737205697e-31, + 4.520364638157568e-40, + 5.708903956643948e-40, + -6.731015824442306e-25, + -4.989042922535646e-40, + -1.400499724200152e-40, + -1.6205316090684347e-40, + -4.026792567846351e-26, + 1.2424209259978265e-37, + -3.31281950859955e-39, + 1.892873965609963e-40, + 3.782132581181968e-40, + 2.8575418414358102e-40, + 5.0180918397011e-40, + -1.3593015493490023e-40, + -2.0541774318383926e-40, + 2.049450616700083e-36, + 4.649788564322608e-41, + -4.855723386639783e-40, + -6.256223110839935e-40, + 8.508964535073154e-41, + -3.407439384806155e-40, + 3.433741756981532e-41, + 2.8361580268702135e-40, + 2.4135684489837784e-40, + -2.1321821059473118e-18, + -1.7413259034302882e-34, + -4.621103984757879e-40, + -1.5678117333183873e-33, + 3.2462760484241577e-40, + -2.078949009707819e-22, + -3.22911935717488e-36, + -4.034360492614637e-24, + 4.000146596261623e-40, + 9.690679530038272e-41, + -2.622642179861041e-40, + 3.3661991710010756e-41, + -4.5952513324421175e-33, + -1.2589111260663081e-39, + -1.2598373843512268e-40, + -3.815455458663612e-41, + 3.719816838473443e-40, + -1.5500719400747935e-38, + 2.6467585264320712e-40, + 1.7360686674520159e-41, + -4.761121727313215e-40, + -3.0532145372122436e-28, + -3.0337691363092992e-40, + -1.1123787469503263e-40, + -3.363788937642437e-40, + -4.434352938417311e-40, + -6.19399144603927e-40, + 3.9098966320906144e-27, + -1.4878426574815178e-40, + -1.0499387647759303e-30, + -2.8359898710544945e-40, + -3.7924492287895625e-31, + 1.8706073330118416e-40, + -5.5168980410621616e-40, + 4.595146595895459e-31, + 4.7183681111666646e-40, + 1.8358411181119428e-40, + 5.649278706986927e-40, + 1.842679454617848e-40, + -4.841696389011892e-40, + 2.0879347118439774e-43, + 1.128930384872886e-21, + -3.618390855625613e-40, + -1.3035158574842313e-40, + -1.3828713895189457e-40, + -2.9586229694761506e-28, + -3.8074680574169605e-40, + -2.2101139249484582e-40, + -2.266097254693074e-33, + -2.775930218873533e-40, + 5.909275624057754e-42, + 1.064874729009715e-40, + -3.775882790031079e-40, + 2.323310814896617e-40, + -2.5513020750422647e-40 + ], + [ + 0.00021852762438356876, + -1.5505507637600533e-40, + -4.5176253479439765e-05, + -2.653624542325872e-11, + -6.598750701414247e-07, + -8.563979463360738e-06, + 4.063408323297138e-23, + 4.2242329072905704e-05, + 3.123431721596681e-11, + -0.000127591731143184, + -3.8600581319769844e-05, + -6.713211405440234e-06, + -3.319737518121112e-17, + -3.1723427010786554e-18, + -3.7361905924626626e-06, + -2.4217660450000786e-40, + -1.3880868228415604e-17, + -3.975427691350933e-40, + -3.226427125468945e-08, + 1.1239814982349358e-41, + -1.9183511387483448e-13, + 0.00013431470142677426, + -3.388894037925638e-05, + -4.558428827294847e-06, + -2.524901611974385e-40, + -3.597216107209533e-07, + 5.9577148931566626e-05, + -9.47430180531228e-06, + -8.637837709102314e-06, + -3.585065496736206e-05, + -6.9372676006485e-15, + -3.300525130978116e-11, + -2.0338314094935778e-13, + -1.0909898678912455e-10, + -5.113709466814358e-26, + -7.692985631628459e-16, + -4.295913811347418e-07, + -2.016092004453185e-08, + -4.182552584097721e-05, + -6.795929863301353e-08, + -1.6368630895158276e-06, + -5.450092794490047e-05, + -6.3585689531464595e-06, + -9.64413970905298e-07, + -6.890433468242918e-08, + -7.16517334353739e-09, + -3.0104324650892522e-06, + -6.384175321727525e-07, + -5.134882741231195e-09, + -6.57685878736094e-17, + -7.812898938919233e-11, + -2.861756001948379e-06, + -5.757629423897015e-06, + -1.0580782827673829e-07, + -2.7564481115405215e-06, + -6.522976764244959e-05, + -2.2290096239885315e-06, + -5.700027759303339e-05, + 1.3968539132966958e-35, + 5.4401068852171616e-40, + -2.7876113861680096e-08, + -4.4541400326088623e-14, + -4.038389670313336e-05, + -2.0077617447592773e-27, + -6.458382983964839e-08, + 7.462894018317456e-07, + -4.1030756392501644e-07, + -2.8058187648612557e-18, + -3.451833308076857e-08, + -1.6308857597735482e-09, + -0.00010331669182050973, + -2.250901616207557e-06, + -3.8933764388458456e-14, + -1.6537527699256316e-05, + -2.8405461307556834e-06, + -1.1430524864408653e-06, + -2.612054913697648e-06, + 1.3419674873453043e-40, + -1.7050508560600974e-08, + -9.576931603305638e-08, + 1.5602897880871108e-40, + -1.427717234037118e-05, + -5.402832688346498e-15, + -3.6862186914010664e-11, + 1.0000400525391159e-35, + -1.9235871207001765e-07, + -1.943041055032468e-18, + -5.4773868214397226e-06, + -1.6660298696180575e-09, + -5.074874570709653e-05, + -9.594439688953571e-06, + -6.077095128145294e-40, + -3.2809651805305873e-15, + -1.0749054126790725e-06, + -8.407909263041802e-06, + -1.1571815409828995e-24, + -1.1426273881058602e-13, + -4.0394415918854065e-06, + 1.5705332798613252e-40, + -6.894764164981637e-13, + -0.00012295591295696795, + 2.1173619795947986e-41, + -1.6745735820222762e-06, + -2.5341823857161216e-05, + -2.464191502758728e-13, + -5.095924571207888e-09, + -8.943602125555117e-08, + -1.4535087757394649e-05, + -7.891808734283856e-11, + -6.715411147151597e-33, + -6.680830199800511e-19, + -1.0938913419522578e-06, + 3.384538516115754e-08, + -7.790715130795434e-07, + -0.00015691990847699344, + -7.950623626129527e-08, + -7.807701152273694e-09, + 0.00030737818451598287, + -7.23006742191501e-05, + 5.10364111094813e-40, + -1.4424562323256396e-05, + -4.383070295599367e-11, + -1.2977021413007606e-07, + 9.971822961923818e-31, + 2.474092699242349e-28, + 4.350849562928195e-40, + -4.582605470204726e-06, + -4.974547736863661e-16 + ], + [ + 0.04827325791120529, + 0.21514892578125, + 0.1942320466041565, + 0.11831087619066238, + -0.23914529383182526, + -0.049641937017440796, + -0.06199654936790466, + -0.09256814420223236, + 0.08374757319688797, + 0.05048320069909096, + 0.08450467139482498, + 0.12681645154953003, + 0.09265699237585068, + 0.04159415885806084, + -0.06953300535678864, + -0.055886656045913696, + -0.018805397674441338, + -0.21417495608329773, + -0.15096667408943176, + 0.39774689078330994, + -0.004762500058859587, + 0.12813067436218262, + -0.1484374701976776, + -0.05632397159934044, + 0.11754769831895828, + -0.07552138715982437, + 0.05153961107134819, + 0.04554073512554169, + 0.1590009182691574, + 0.10840501636266708, + 0.14735138416290283, + 0.07707295566797256, + 0.03371281176805496, + 0.08857610076665878, + 0.05045890063047409, + -0.11738065630197525, + 0.08873655647039413, + 0.12854890525341034, + -0.11114844679832458, + -0.09454745799303055, + 0.06770875304937363, + 0.05067264288663864, + -0.06973784416913986, + 0.023371215909719467, + -0.07201961427927017, + -0.036594197154045105, + -0.022149818018078804, + 0.08335564285516739, + 0.1110721156001091, + -0.29455623030662537, + -0.08380600810050964, + 0.13196788728237152, + 0.0900752916932106, + -0.2703433036804199, + -0.24520330131053925, + 0.1513301283121109, + 0.08282192796468735, + -0.002659645164385438, + -0.08893971145153046, + -0.003290957771241665, + -0.2056335061788559, + -0.032364990562200546, + -0.001887918566353619, + 0.08015158027410507, + -0.07939223945140839, + 0.05478687956929207, + 0.14241234958171844, + -0.04256710410118103, + -0.09351171553134918, + -0.03417447209358215, + -0.06494317948818207, + 0.02894498035311699, + 0.16303542256355286, + -0.09350043535232544, + -0.13421085476875305, + 0.1102103516459465, + 0.1263013333082199, + 0.06418556720018387, + -0.19309304654598236, + -0.051990434527397156, + -0.16825035214424133, + 0.09767627716064453, + 0.026185767725110054, + -0.20945434272289276, + -0.17457975447177887, + -0.05536230280995369, + 0.22240698337554932, + -0.12430312484502792, + 0.10322536528110504, + 0.04315957799553871, + 0.10825398564338684, + 0.04031112417578697, + -0.06651556491851807, + -0.04260280355811119, + 0.12265636026859283, + -0.06199626997113228, + -0.10540129244327545, + 0.11977565288543701, + 0.2967701852321625, + -0.2084561288356781, + 0.13269734382629395, + -0.11665571480989456, + -0.028106898069381714, + 0.05031342804431915, + -0.08633992075920105, + -0.06864435970783234, + 0.046477578580379486, + 0.1503124088048935, + -0.07416870445013046, + -0.2418452352285385, + -0.22392095625400543, + -0.1432890146970749, + 0.0681062862277031, + -0.08762646466493607, + 0.03314952552318573, + -0.09757079184055328, + -0.12986120581626892, + 0.01167116966098547, + 0.16542869806289673, + 0.02823389321565628, + 0.03637228161096573, + -0.06437701731920242, + 0.09288445860147476, + 0.0327470600605011, + 0.07214664667844772, + -0.03767981380224228, + -0.017573459073901176, + 0.1495993435382843 + ], + [ + -0.041472092270851135, + 0.01547745056450367, + -0.02573670819401741, + 0.0338791199028492, + 0.02639606222510338, + 0.05719899758696556, + 0.027113167569041252, + 0.07817833870649338, + -0.08719311654567719, + 0.004046034533530474, + -0.09280002862215042, + -0.04546007513999939, + -0.06504745036363602, + 0.0362648144364357, + 0.04667660966515541, + -0.029684709385037422, + -0.08254169672727585, + -0.08373236656188965, + -0.11791915446519852, + -0.12591414153575897, + 0.0014984642621129751, + 0.048400770872831345, + -0.01895231008529663, + -0.054966773837804794, + 0.06127454340457916, + -0.007002448663115501, + -0.26911380887031555, + 0.03734554350376129, + -0.030913937836885452, + 0.20641818642616272, + 0.18065892159938812, + 0.013039067387580872, + -0.08649847656488419, + -0.1057911291718483, + 0.10698824375867844, + -0.1952851414680481, + -0.004707066807895899, + 0.028329646214842796, + 0.08458753675222397, + 0.047512687742710114, + -0.10218003392219543, + 0.15643644332885742, + 0.025615546852350235, + 0.14368866384029388, + -0.22137250006198883, + 0.09859690070152283, + -0.11498043686151505, + -0.06961458176374435, + -0.13299117982387543, + -0.06046851724386215, + -0.02598375827074051, + 0.04813646525144577, + 0.23292164504528046, + -0.03287215903401375, + -0.04419177025556564, + -0.10097257792949677, + 0.13817471265792847, + -0.045997146517038345, + -0.1634233295917511, + -0.28002509474754333, + -0.053338728845119476, + -0.19627666473388672, + -0.04104764014482498, + 0.02803809382021427, + -0.08308050036430359, + 0.14895769953727722, + -0.027241431176662445, + 0.07953374832868576, + 0.045976027846336365, + 0.22073884308338165, + 0.15013034641742706, + 0.1861959993839264, + -0.20713387429714203, + 0.10499055683612823, + 0.11764609068632126, + -0.11645462363958359, + -0.10664629936218262, + 0.12691327929496765, + -0.038693107664585114, + 0.029057752341032028, + -0.08724923431873322, + 0.19190159440040588, + -0.1494167596101761, + 0.011152287013828754, + -0.16955722868442535, + 0.17099051177501678, + 0.15986573696136475, + -0.21062053740024567, + 0.0629768893122673, + -0.09799345582723618, + 0.07509797066450119, + -0.20448066294193268, + 0.11914660036563873, + 0.05141711235046387, + 0.018616240471601486, + -0.3615768253803253, + -0.0556289479136467, + -0.0795636922121048, + -0.23317621648311615, + 0.03840075805783272, + 0.16618263721466064, + 0.18325117230415344, + 0.02762136422097683, + 0.051379188895225525, + 0.06972934305667877, + 0.018536992371082306, + 0.10861487686634064, + 0.1290348619222641, + -0.021076735109090805, + 0.10722018033266068, + -0.18228723108768463, + 0.020615722984075546, + -0.419887900352478, + -0.026923978701233864, + 0.05717199668288231, + -0.06604387611150742, + 0.07278672605752945, + 0.09682442992925644, + -0.06399456411600113, + 0.26224485039711, + 0.08474847674369812, + 0.01061875931918621, + -0.07568567991256714, + -0.2520374357700348, + -0.15267734229564667, + -0.8471099138259888, + -0.31546881794929504, + -0.09156917780637741 + ], + [ + -0.35277503728866577, + 0.11820149421691895, + -0.0030589995440095663, + -0.053522296249866486, + -0.05968159809708595, + 0.22406208515167236, + -0.04311959445476532, + 0.019874313846230507, + 0.08595995604991913, + -0.09159152209758759, + -0.1711912304162979, + -0.12692248821258545, + -0.0067992620170116425, + 0.10868405550718307, + -0.05567067861557007, + 0.10666446387767792, + 0.04125598073005676, + 0.06319345533847809, + 0.014548756182193756, + 0.0652332454919815, + -0.16421367228031158, + 0.19096538424491882, + 0.20599962770938873, + 0.15232260525226593, + 0.05928250402212143, + 0.23737427592277527, + 0.4701627492904663, + -0.04843895137310028, + -0.05845407769083977, + -0.2441394329071045, + 0.13110405206680298, + -0.19882719218730927, + 0.07783149927854538, + -0.1635933369398117, + 0.0330621711909771, + -0.15153834223747253, + 0.09116355329751968, + -0.030985934659838676, + -0.08226179331541061, + -0.029148802161216736, + -0.10930293053388596, + 0.2359568029642105, + 0.0879155620932579, + 0.06380531936883926, + -0.08510856330394745, + 0.11418550461530685, + -0.058886125683784485, + -0.026658127084374428, + -0.11213912814855576, + 0.17400281131267548, + 0.0070968130603432655, + -0.0024675517342984676, + -0.0650077760219574, + 0.1320456713438034, + 0.1123906597495079, + -0.2018636018037796, + 0.057058390229940414, + -0.05744340270757675, + -0.03860144317150116, + 0.06840206682682037, + -0.062225341796875, + -0.09066668897867203, + -0.020396782085299492, + -0.08101317286491394, + 0.11080241948366165, + 0.06144142150878906, + 0.1512887328863144, + -0.37515541911125183, + -0.03558921068906784, + -0.057247284799814224, + 0.09447670727968216, + -0.07621780782938004, + 0.06213551387190819, + 0.10089583694934845, + 0.06938038021326065, + -0.34581562876701355, + -0.21749567985534668, + -0.10950621962547302, + 0.01688399352133274, + 0.0645124539732933, + 0.1977268010377884, + 0.0567384772002697, + -0.07458191365003586, + 0.031786996871232986, + 0.11169219017028809, + -0.014339459128677845, + -0.015313252806663513, + 0.14144395291805267, + 0.035564225167036057, + -0.012537859380245209, + -0.06328368932008743, + 0.043391548097133636, + 0.08211874216794968, + -0.03450951725244522, + -0.0014689835952594876, + -0.039964113384485245, + 0.0711001604795456, + 0.0064759403467178345, + 0.03688541799783707, + 0.08051969110965729, + 0.08449471741914749, + 0.12263805419206619, + 0.10150298476219177, + -0.16469042003154755, + -0.02410830557346344, + 0.06726542115211487, + 0.0921986773610115, + -0.09149423986673355, + 0.047271478921175, + 0.14061239361763, + 0.033983513712882996, + 0.08036905527114868, + 0.024914074689149857, + 0.011389260180294514, + -0.04575974494218826, + 0.14109128713607788, + 0.002089719520881772, + 0.0386323556303978, + -0.30370646715164185, + -0.023665331304073334, + -0.3174821436405182, + 0.12076598405838013, + 0.06213612109422684, + -0.14683392643928528, + 0.10492662340402603, + 0.17357446253299713, + -0.05210854858160019, + -0.27452170848846436 + ], + [ + -0.2632894217967987, + 0.07958473265171051, + 0.11559826135635376, + -0.042120352387428284, + 0.020214686170220375, + -0.32958775758743286, + 0.10046248137950897, + -0.25719404220581055, + 0.11074749380350113, + 0.0757545530796051, + -0.18737460672855377, + -0.42136794328689575, + 0.07335198670625687, + -0.3403390645980835, + 0.09304189682006836, + 0.1185125857591629, + 0.16296356916427612, + 0.003851336659863591, + -0.01040879637002945, + 0.03535989299416542, + -0.13972064852714539, + -0.12546832859516144, + 0.05829951539635658, + -0.08608102798461914, + 0.01581210270524025, + 0.1052127480506897, + -0.7268087267875671, + -0.07337155938148499, + 0.20192523300647736, + -0.21199262142181396, + -0.007353718392550945, + -0.2468317598104477, + -0.21410875022411346, + -0.2008274644613266, + -0.04810459166765213, + 0.04794730246067047, + 0.02092467062175274, + 0.07563482969999313, + -0.09588226675987244, + -0.0035723051987588406, + -0.14108313620090485, + -0.3728482723236084, + -0.2421407252550125, + 0.06196984648704529, + -0.13556581735610962, + 0.22880704700946808, + 0.024122921749949455, + 0.011857481673359871, + -0.001619938644580543, + 0.010159431956708431, + 0.18034881353378296, + -0.1823078989982605, + -0.008941304869949818, + 0.07899856567382812, + -0.08110322058200836, + 0.08976677060127258, + -0.005852886475622654, + -0.009935887530446053, + 0.017941994592547417, + 0.028338544070720673, + -0.02821565605700016, + 0.07789101451635361, + 0.016744934022426605, + 0.07681363821029663, + 0.14577268064022064, + -0.07020723074674606, + -0.20163577795028687, + 0.3214800953865051, + -0.02065255306661129, + -0.0019298326224088669, + 0.06756987422704697, + -0.540946364402771, + 0.17782072722911835, + 0.016683008521795273, + 0.09398216754198074, + 0.0009104206110350788, + 0.010129650123417377, + 0.13159960508346558, + 0.03592286258935928, + -0.008510339073836803, + -0.1079682931303978, + 0.018063673749566078, + -0.07589083909988403, + 0.053342584520578384, + 0.017478473484516144, + 0.05279942974448204, + 0.05174185708165169, + 0.1171601414680481, + 0.037189844995737076, + 0.036696907132864, + -0.2835801839828491, + 0.02954948879778385, + -0.14355909824371338, + 0.14295746386051178, + -0.006755168549716473, + 0.04022542014718056, + -0.0073356470093131065, + 0.029745694249868393, + 0.16948921978473663, + 0.036058347672224045, + -0.23549194633960724, + -0.068946972489357, + 0.06212791055440903, + 0.08875610679388046, + 0.14584296941757202, + 0.023215170949697495, + -0.6775221824645996, + -0.047236815094947815, + -0.21048469841480255, + 0.05047723650932312, + 0.028359970077872276, + -0.45001962780952454, + 0.047964099794626236, + 0.04154519364237785, + -0.021934567019343376, + -0.596041738986969, + -0.04739311710000038, + -0.48127856850624084, + 0.04063626006245613, + -0.03214060142636299, + 0.1294226497411728, + -0.11068681627511978, + 0.10164506733417511, + 0.042889583855867386, + -0.030884895473718643, + -0.13194581866264343, + 0.09165879338979721, + -0.09304430335760117 + ], + [ + 0.05927729234099388, + -0.04080034792423248, + 0.0045895446091890335, + -0.015026696026325226, + 0.08820954710245132, + -0.01579476334154606, + 0.11670828610658646, + 0.01974877342581749, + -0.16741134226322174, + 0.18911658227443695, + 0.12600496411323547, + 0.01793961226940155, + 0.24705548584461212, + -0.03416374325752258, + 0.017679043114185333, + -0.042155589908361435, + -0.01418888196349144, + -0.0316920168697834, + -0.12243267148733139, + -0.032390058040618896, + -0.03941312059760094, + -0.010874838568270206, + 0.035786811262369156, + -0.12955987453460693, + -0.1394377201795578, + -0.15552803874015808, + -0.030987141653895378, + 0.0941588506102562, + 0.005113832652568817, + 0.007594419177621603, + -0.031324490904808044, + -0.044215474277734756, + -0.018272534012794495, + 0.01632906310260296, + -0.2206863909959793, + 0.1332484483718872, + 0.04491109028458595, + -0.17643041908740997, + 0.02061809040606022, + 0.004753515589982271, + 0.07232297211885452, + -0.06613914668560028, + 0.2680169343948364, + 0.12306854873895645, + 0.15085719525814056, + 0.05817614123225212, + 0.13488906621932983, + -0.033693715929985046, + -0.12351356446743011, + 0.21045829355716705, + -0.0027632650453597307, + 0.16401566565036774, + -0.062194984406232834, + 0.17648883163928986, + 0.186547189950943, + 0.0046088178642094135, + 0.08602715283632278, + 0.07607992738485336, + 0.08184435218572617, + 0.05599053576588631, + 0.12118565291166306, + 0.21106424927711487, + -0.04509493336081505, + 0.01290035992860794, + -0.13690073788166046, + 0.10442542284727097, + 0.05121029168367386, + 0.09293413162231445, + -0.08502528071403503, + -0.026876533403992653, + -0.09911118447780609, + -0.03521854802966118, + -0.14729048311710358, + -0.01226585078984499, + -0.019324135035276413, + 0.20008818805217743, + -0.07489624619483948, + -0.068305104970932, + -0.042914681136608124, + -0.0816175565123558, + -0.20135782659053802, + -0.1433621197938919, + 0.042012039572000504, + -0.16165804862976074, + 0.004701828584074974, + -0.00538162374868989, + -0.1241571232676506, + 0.0637902319431305, + -0.022987039759755135, + 0.03398121893405914, + 0.03981027752161026, + -0.0005038785748183727, + 0.06766356527805328, + -0.10969240963459015, + 0.04098111763596535, + -0.011221442371606827, + 0.019288398325443268, + 0.019808968529105186, + -0.09669153392314911, + -0.10390061885118484, + -0.10525468736886978, + 0.12109336256980896, + -0.0285157673060894, + 0.1513492316007614, + 0.03275969624519348, + 0.03730916231870651, + -0.0036404121201485395, + 0.019858477637171745, + -0.06727392226457596, + 0.07111500948667526, + -0.03786486014723778, + 0.18430368602275848, + -0.04535341262817383, + -0.05884890258312225, + -0.007395537104457617, + -0.046449337154626846, + 0.06282126158475876, + 0.05068972706794739, + -0.00897266622632742, + 0.04791278392076492, + 0.06286835670471191, + -0.12306797504425049, + 0.12870927155017853, + -0.03549109399318695, + 0.19998939335346222, + 0.09145954996347427, + 0.07121636718511581, + 0.12775221467018127 + ], + [ + -0.18863828480243683, + 0.13802814483642578, + 0.011659153737127781, + -0.09028783440589905, + -0.12011153250932693, + -0.1248089000582695, + -0.1540067344903946, + 0.13314023613929749, + 0.02796090766787529, + 0.04794574901461601, + 0.16549047827720642, + 0.14027254283428192, + 0.2159137725830078, + -0.3342932462692261, + -0.10867980867624283, + 0.12314080446958542, + 0.24799218773841858, + 0.007029733154922724, + 0.3517593443393707, + 0.20524971187114716, + -0.1111295148730278, + 0.015840958803892136, + 0.10552038252353668, + -0.028550803661346436, + -0.012343713082373142, + -0.05760304257273674, + 0.07848094403743744, + -0.4578622877597809, + -0.023597905412316322, + 0.023705508559942245, + 0.11210179328918457, + 0.14420783519744873, + -0.02648250013589859, + 0.12285242974758148, + -0.11761555075645447, + 0.025227995589375496, + -0.05908258631825447, + 0.04673607647418976, + 0.25781145691871643, + -0.00534990755841136, + -0.0687614157795906, + -0.10785610973834991, + 0.133162721991539, + 0.042215123772621155, + 0.0449877493083477, + -0.5618857145309448, + -0.028807340189814568, + -0.014855315908789635, + 0.008101169019937515, + 0.1618019938468933, + -0.16945302486419678, + -0.1978403925895691, + -0.06837091594934464, + -0.03944693133234978, + -0.00402437848970294, + 0.16741715371608734, + -0.024829473346471786, + 0.04997527226805687, + -0.10996900498867035, + 0.12391627579927444, + -0.04603812098503113, + 0.04676542803645134, + 0.03522162139415741, + 0.03009788878262043, + -0.016470113769173622, + 0.0056106229312717915, + 0.19896285235881805, + -0.00954469945281744, + -0.10910220444202423, + -0.06468662619590759, + -0.015788929536938667, + -0.11197644472122192, + 0.06381752341985703, + -0.021336887031793594, + -0.019491834565997124, + 0.08358027786016464, + -0.0016401486936956644, + -0.008876850828528404, + -0.22529494762420654, + -0.13062526285648346, + -0.10566311329603195, + -0.12546922266483307, + -0.08323357999324799, + 0.014096773229539394, + -0.14815160632133484, + -0.0045191338285803795, + 0.09776374697685242, + -0.08853740245103836, + -0.18871860206127167, + -0.0019081158097833395, + -0.1800527125597, + -0.018966803327202797, + -0.20852375030517578, + 0.03136094659566879, + -0.012602409347891808, + -0.0258474238216877, + -0.045244768261909485, + 0.0544578917324543, + 0.01456401590257883, + -0.0027459843549877405, + 0.14419884979724884, + 0.04964772239327431, + 0.03521395102143288, + -0.016228221356868744, + -0.02864147536456585, + -0.02788839302957058, + -0.27481281757354736, + -0.20274101197719574, + -0.016517456620931625, + 0.06953984498977661, + -0.046268071979284286, + 0.11017832159996033, + 0.1166820079088211, + -0.096051886677742, + -0.015064023435115814, + -0.046748459339141846, + -0.19804047048091888, + 0.049726299941539764, + 0.12597915530204773, + 0.12882453203201294, + -0.1046181470155716, + 0.014702286571264267, + 0.11417761445045471, + 0.13632147014141083, + -0.1344628632068634, + -0.09819568693637848, + 0.18047554790973663, + 0.004963807296007872 + ], + [ + -0.09002082049846649, + -0.08129559457302094, + -0.04166140407323837, + 0.014535960741341114, + 0.03620045259594917, + 0.08880089968442917, + -0.04947185888886452, + 0.055668700486421585, + -0.04375359043478966, + -0.03112468682229519, + 0.029770459979772568, + 0.11217477172613144, + 0.01137261837720871, + -0.10682950913906097, + 0.06273709237575531, + -0.0478987954556942, + 0.020192114636301994, + -0.06250475347042084, + 0.08323966711759567, + 0.0370660200715065, + 0.07346628606319427, + 0.07532497495412827, + 0.03203747794032097, + 0.09356728941202164, + -0.04986974596977234, + -0.0072738537564873695, + 0.05784117057919502, + -0.03966808319091797, + 0.009404870681464672, + -0.009851645678281784, + -0.03465047851204872, + 0.04189971089363098, + -0.21373358368873596, + 0.002556543331593275, + -0.036918748170137405, + -0.09380887448787689, + 0.002836011815816164, + 0.12916631996631622, + 0.07554636895656586, + 0.040415577590465546, + -0.07146596908569336, + -0.0027707009576261044, + 0.04369776323437691, + -0.012271923944354057, + -0.006677041295915842, + 0.008487197570502758, + -0.026980556547641754, + 0.07671336084604263, + -0.02412811852991581, + 0.11114570498466492, + -0.03412793204188347, + -0.08652301132678986, + -0.005597091745585203, + 0.0009268690482713282, + 0.026081858202815056, + 0.10593872517347336, + -0.06961813569068909, + 0.09318923950195312, + -0.02331349439918995, + 0.05721530690789223, + 0.07916276901960373, + 0.02368125505745411, + 0.0403168685734272, + 0.06356401741504669, + 0.02727501653134823, + -0.023504149168729782, + 0.09742307662963867, + 0.021443061530590057, + 0.007009182590991259, + -0.009172353893518448, + 0.06531183421611786, + 0.004403716418892145, + 0.06598854064941406, + -0.06144307181239128, + -0.03925139456987381, + -0.09862947463989258, + 0.0628843903541565, + -0.00937437079846859, + 0.04518982768058777, + 0.018025649711489677, + -0.10106479376554489, + -0.021943535655736923, + 0.0723634883761406, + 0.11731875687837601, + -0.016155093908309937, + -0.0742952972650528, + -0.07631713151931763, + 0.03005196712911129, + -0.05594811588525772, + 0.06467210501432419, + -0.0912412777543068, + -0.017386876046657562, + -0.04685530811548233, + 0.03757491707801819, + -0.010344387963414192, + -0.015283018350601196, + 0.0583786815404892, + -0.036341916769742966, + 0.10519305616617203, + 0.06956180930137634, + 0.05848216265439987, + 0.02404031716287136, + 0.03199658542871475, + -0.009530849754810333, + 0.025023749098181725, + 0.006529180333018303, + -0.0681510642170906, + -0.07443667948246002, + -0.03589214012026787, + -0.08459828794002533, + -0.019702091813087463, + 0.012889834120869637, + 0.12761518359184265, + 0.05959593504667282, + 0.07163389027118683, + 0.02614159695804119, + 0.01046692207455635, + 0.06726005673408508, + 0.05341964587569237, + -0.07199248671531677, + 0.04260685667395592, + -0.015671614557504654, + -0.06717237830162048, + 0.039600007236003876, + -0.1083158403635025, + 0.0624048076570034, + -0.026848861947655678, + -0.01677272841334343 + ], + [ + -0.050523966550827026, + 0.25891542434692383, + 0.2640231251716614, + -0.28082171082496643, + -0.05332006886601448, + 0.03240310400724411, + -0.2024172693490982, + 0.20008635520935059, + -0.04879515618085861, + -0.08151262998580933, + -0.19367563724517822, + -0.105728879570961, + 0.1392727792263031, + -0.38764488697052, + -0.5616477131843567, + 0.06448723375797272, + 0.0347556509077549, + -0.21958142518997192, + 0.2050047516822815, + 0.23046445846557617, + -0.8099494576454163, + -0.009935270063579082, + 0.3073028028011322, + -0.004186827223747969, + -0.049090199172496796, + -0.016187766566872597, + 0.16877053678035736, + -0.9122653007507324, + -0.08807170391082764, + 0.12996608018875122, + 0.061140939593315125, + 0.14820325374603271, + -0.43787574768066406, + 0.12810663878917694, + -0.05214862525463104, + -0.5566034913063049, + -0.3431795835494995, + 0.20852522552013397, + 0.06425745040178299, + -0.05986493453383446, + 0.10967143625020981, + -0.14359736442565918, + -0.05603840574622154, + 0.18075190484523773, + -0.24507732689380646, + 0.03705955296754837, + -0.23538142442703247, + 0.07322388142347336, + 0.22042463719844818, + 0.10565204173326492, + -0.7055004239082336, + -0.11639048904180527, + 0.07104384154081345, + -0.07209401577711105, + 0.021379850804805756, + 0.09456497430801392, + 0.13697779178619385, + 0.26432329416275024, + -0.36552876234054565, + 0.05876585468649864, + -0.19182252883911133, + 0.10540588945150375, + -0.09928885847330093, + 0.2814381718635559, + -0.025258343666791916, + -0.2293490618467331, + 0.1822151094675064, + -0.05179353058338165, + 0.028584452345967293, + 0.046763114631175995, + 0.06332793831825256, + 0.027545856311917305, + 0.1669316440820694, + -0.036795686930418015, + 0.0316133089363575, + -0.08753926306962967, + 0.36007654666900635, + -0.8609288334846497, + -0.17672213912010193, + -0.35183385014533997, + 0.014326651580631733, + -0.04736480861902237, + -0.4864243268966675, + -0.02096990868449211, + -0.09493817389011383, + 0.06365466117858887, + 0.13896393775939941, + 0.004313118290156126, + -0.544935405254364, + 0.05643470585346222, + -0.8357908725738525, + -0.14578953385353088, + -0.1520068198442459, + -0.03249762952327728, + -0.3681516647338867, + -0.28255191445350647, + -0.6120071411132812, + 0.018742861226201057, + 0.05178051441907883, + -0.3815155029296875, + 0.15658916532993317, + 0.009062564931809902, + 0.13660481572151184, + -0.17090663313865662, + -0.13919861614704132, + -0.5508991479873657, + -0.49128398299217224, + -1.9056625366210938, + -0.0840110257267952, + 0.06814945489168167, + -0.10277565568685532, + 0.6403352618217468, + 0.12030179053544998, + 0.032622989267110825, + -1.382047414779663, + 0.1046234592795372, + 0.4694845974445343, + -0.05390448495745659, + -0.09217114001512527, + 0.4233892858028412, + -0.10116828233003616, + -0.2311173528432846, + 0.10938592255115509, + 0.24289678037166595, + -0.05838168039917946, + -0.7116761207580566, + 0.14414525032043457, + -0.09739848226308823 + ], + [ + -0.030432404950261116, + 0.317840039730072, + 0.013277520425617695, + -0.04185384884476662, + 0.01645059697329998, + 0.020740671083331108, + -0.054308682680130005, + 0.17277248203754425, + -0.07035251706838608, + 0.14960794150829315, + 0.08494533598423004, + 0.03291182592511177, + 0.1033465713262558, + -0.27629250288009644, + -0.07660665363073349, + 0.07581278681755066, + 0.09606751054525375, + 0.014061927795410156, + 0.027231819927692413, + -0.11631883680820465, + 0.16448764503002167, + -0.14898106455802917, + -0.06412886828184128, + -0.00975930504500866, + -0.036471009254455566, + -0.19172635674476624, + -0.07843971252441406, + 0.12440459430217743, + -0.12893244624137878, + -0.18176667392253876, + 0.1183137521147728, + 0.24120889604091644, + 0.05860809609293938, + 0.021550340577960014, + 0.11726503074169159, + -0.17124974727630615, + -0.1591860055923462, + 0.04030216485261917, + 0.032634057104587555, + 0.04396551847457886, + 0.10628453642129898, + 0.053640447556972504, + 0.05780256167054176, + -0.017011012881994247, + -0.5366721153259277, + 0.011472483165562153, + -0.06359584629535675, + 0.0509444922208786, + 0.01921316422522068, + 0.23585614562034607, + -0.10114719718694687, + 0.06528592109680176, + 0.03780950605869293, + 0.010459527373313904, + 0.007124691735953093, + 0.03254646435379982, + -0.11831511557102203, + 0.07821981608867645, + -0.2843676805496216, + -0.5560430288314819, + -0.09154951572418213, + 0.18567122519016266, + 0.005736490245908499, + 0.16698871552944183, + -0.09317448735237122, + 0.19526956975460052, + 0.08188746869564056, + -0.07202699780464172, + -0.08093417435884476, + 0.593601405620575, + 0.09591300785541534, + -0.007259118836373091, + -0.0008278342429548502, + -0.008564295247197151, + -0.2826215922832489, + -0.2186349332332611, + -0.022631848230957985, + 0.057230643928050995, + -0.07506442815065384, + -0.004341702442616224, + 0.09283540397882462, + 0.14537081122398376, + -0.11455120891332626, + 0.11741882562637329, + -0.35539305210113525, + 0.030125517398118973, + 0.1858861893415451, + -0.14165769517421722, + -0.08454698324203491, + -0.3559980094432831, + -0.04340226948261261, + 0.11771798878908157, + -0.10764620453119278, + -0.008347862400114536, + 0.06877920776605606, + -0.23139286041259766, + 0.08371387422084808, + -0.8451933860778809, + -0.0855465680360794, + 0.04454021900892258, + 0.16984042525291443, + -0.21380719542503357, + 0.11908365041017532, + -0.12613028287887573, + 0.008390638045966625, + -0.014750564470887184, + 0.24154506623744965, + -0.09350886195898056, + -0.08326970785856247, + -0.014666804112493992, + -0.08737348765134811, + -0.1543010175228119, + 0.6352481245994568, + 0.029556090012192726, + 0.07766562700271606, + 0.21625633537769318, + 0.21382559835910797, + 0.04633805528283119, + -0.005132277961820364, + -0.0891478955745697, + 0.07734435051679611, + -0.13191959261894226, + -0.1643320620059967, + 0.1717858761548996, + 0.09972892701625824, + -0.8480440378189087, + -0.1490955501794815, + 0.11247878521680832 + ], + [ + 0.056460902094841, + 0.0006469623767770827, + -0.05973278731107712, + 0.0018377829110249877, + -0.00393964909017086, + 0.026966676115989685, + -0.011904871091246605, + 0.029345497488975525, + 0.015089625492691994, + 0.02738172933459282, + 0.058171115815639496, + 0.07294687628746033, + 0.07231537252664566, + 0.025667952373623848, + 0.04503732547163963, + -0.07386339455842972, + -0.005277142859995365, + 0.05141281709074974, + 0.03686732053756714, + 0.025259681046009064, + -0.02375241182744503, + -0.020918671041727066, + -0.004051933065056801, + 0.09533669054508209, + 0.009660796262323856, + -0.036179546266794205, + 0.10656203329563141, + 0.031133655458688736, + 0.04415581002831459, + -0.03401448577642441, + -0.010166311636567116, + -0.02527250535786152, + -0.07661668211221695, + 0.07051537930965424, + 0.006892517674714327, + -0.12280473858118057, + -0.001083078677766025, + 0.03337183594703674, + 0.0533129908144474, + -0.05124905705451965, + -0.06684527546167374, + 0.08395195007324219, + 0.015095474198460579, + -0.010526641272008419, + -0.07194851338863373, + -0.041995901614427567, + 0.054696254432201385, + 0.020865511149168015, + 0.06564145535230637, + 0.04294843226671219, + -0.07302206754684448, + 0.009092512540519238, + 0.009298236109316349, + -0.032159797847270966, + -0.001033599372021854, + 0.014466164633631706, + -0.02877863124012947, + 0.05673256516456604, + -0.06691016256809235, + 0.03824690729379654, + 0.02261652611196041, + 0.02796918712556362, + -0.010538391768932343, + 0.025026218965649605, + 0.06278077512979507, + 0.051100000739097595, + 0.04948653653264046, + -0.0007080921204760671, + -0.04930480197072029, + -0.06682547926902771, + 0.037406180053949356, + -0.04713958874344826, + 0.0850658118724823, + -0.054741021245718, + -0.0626959502696991, + -0.10096988081932068, + 0.06401564925909042, + -0.04253922030329704, + 0.0055317687802016735, + 0.01658863201737404, + 0.05202440917491913, + 0.043474264442920685, + -0.06714737415313721, + 0.06707330048084259, + -0.05137902870774269, + -0.03830952197313309, + 0.012345831841230392, + 0.0024281374644488096, + -0.012845181860029697, + 0.050673093646764755, + 0.05012684687972069, + 0.06361361593008041, + -0.019020620733499527, + 0.0533851720392704, + -0.012245483696460724, + -0.023954059928655624, + 0.023678315803408623, + -0.008655951358377934, + -0.004141473677009344, + 0.009108210913836956, + 0.024071527644991875, + 0.052295394241809845, + 0.048978712409734726, + -0.052522625774145126, + 0.04037332162261009, + -0.0919712707400322, + 0.0285762008279562, + 0.057007383555173874, + 0.005972222425043583, + 0.014043763279914856, + 0.017137160524725914, + -0.034065112471580505, + 0.08476892858743668, + 0.006287887692451477, + 0.0489974170923233, + -0.006413774564862251, + 0.007512648589909077, + -0.025745386257767677, + 0.0031993165612220764, + 0.02622719295322895, + -0.07621300965547562, + 0.028058957308530807, + -0.015017742291092873, + 0.009696311317384243, + -0.02364412695169449, + -0.001216136384755373, + -0.016832483932375908, + 0.07113596051931381 + ], + [ + -0.0036322029773145914, + -0.012416079640388489, + 0.0005242801853455603, + -0.0069754458963871, + -0.010156288743019104, + -0.005528849083930254, + -0.004267557989805937, + -0.0011310884729027748, + -0.0050515769980847836, + -0.004076057579368353, + 0.0033928819466382265, + -6.12938092672266e-05, + -0.00047095734043978155, + 0.0037004859186708927, + -0.007446396164596081, + -0.0021680286154150963, + 0.01011741068214178, + -0.004982414189726114, + 0.008182257413864136, + -0.008148100227117538, + -0.006507983896881342, + -0.002860765904188156, + -0.008440257981419563, + -0.0024951354134827852, + -0.007727718446403742, + 0.008852523751556873, + -0.0028444002382457256, + -0.002813636092469096, + -0.0007463045767508447, + 0.0012131485855206847, + -0.011911490000784397, + 0.002062804065644741, + -0.008210757747292519, + 0.004965110681951046, + 0.0017369335982948542, + 0.0019448067760095, + 0.005741000175476074, + -0.011701030656695366, + -0.0051679168827831745, + -0.0004748135106638074, + 0.0023951856419444084, + -0.0020187923219054937, + -0.0019059327896684408, + 0.002149492036551237, + -0.0015696781920269132, + 0.018494855612516403, + -0.006562946829944849, + -0.008603306487202644, + 0.009217359125614166, + -0.014968653209507465, + -0.007102627772837877, + 0.0014820306096225977, + -0.012520276941359043, + 0.015932366251945496, + -0.004165792837738991, + 0.004580152221024036, + 0.01416345126926899, + 0.010918189771473408, + -0.010895070619881153, + -0.002640943042933941, + -0.003425228875130415, + -0.0028913491405546665, + 0.0036646046210080385, + 0.00031035669962875545, + 0.004694185685366392, + -0.010493380017578602, + 0.008804721757769585, + 0.008817569352686405, + -0.003400815185159445, + -0.002175969071686268, + 0.01352107897400856, + -0.0038962680846452713, + -0.0027411740738898516, + -0.0014935113722458482, + -0.007055308669805527, + 0.0031622129026800394, + -0.007761009968817234, + -0.016658436506986618, + 0.0005871079047210515, + 0.0012646719114854932, + 0.0012463134480640292, + -0.012074711732566357, + -0.00014673982514068484, + -0.00020626875630114228, + 0.007381488103419542, + 0.008660870604217052, + -0.008588679134845734, + 0.010021986439824104, + 0.0002924166328739375, + 0.0013516700128093362, + 0.014274848625063896, + -0.0009187801624648273, + -0.01434001699090004, + 0.009050394408404827, + 0.008913898840546608, + 0.0016001100884750485, + -0.005442966241389513, + 1.758213693392463e-05, + 0.0005831856396980584, + -0.014636417850852013, + 0.0033769968431442976, + -0.00045642093755304813, + 0.006333374418318272, + -0.0003944170312024653, + -0.004370789043605328, + -0.0066308933310210705, + 0.0026707604993134737, + -0.0004724801692645997, + -0.003524726489558816, + 0.00977078266441822, + -0.00014404176909010857, + -0.02232786826789379, + 0.01720087230205536, + -0.004387080669403076, + -0.008015339262783527, + 0.0047739604488015175, + 0.005014955531805754, + 0.00823309738188982, + 0.0021117860451340675, + -0.0005273944698274136, + 0.0037050251848995686, + -0.004796420689672232, + -0.004929990042001009, + 0.010750534012913704, + -0.004486578982323408, + -0.0035492212045937777, + -0.0007282084552571177, + 0.004496285226196051 + ], + [ + -0.07206837087869644, + 0.06448623538017273, + -0.05858830362558365, + 0.051728978753089905, + 0.023024171590805054, + 0.03600110486149788, + -0.07734877616167068, + 0.04986626282334328, + 0.05140470340847969, + -0.12233088910579681, + 0.028330354019999504, + 0.07548289000988007, + -0.006406498607248068, + 0.20547159016132355, + -0.141757532954216, + -0.13326941430568695, + 0.24483036994934082, + -0.12101755291223526, + -0.014380883425474167, + -0.052867595106363297, + -0.22885198891162872, + 0.13686849176883698, + -0.19344015419483185, + -0.012478487566113472, + 0.010642435401678085, + -0.18253260850906372, + 0.03798770532011986, + -0.36676204204559326, + 0.14655658602714539, + 0.12942887842655182, + 0.11087081581354141, + -0.17374074459075928, + -0.30833157896995544, + 0.14044351875782013, + 0.029711250215768814, + -0.09673439711332321, + 0.03407661244273186, + 0.15610165894031525, + 0.09338663518428802, + 0.07154753059148788, + -0.07305078953504562, + -0.22086596488952637, + -0.24463993310928345, + 0.07657655328512192, + -0.22644200921058655, + -0.6683581471443176, + 0.28358837962150574, + -0.12128029018640518, + 0.032413918524980545, + -0.04831603169441223, + -0.09830667078495026, + 0.08720036596059799, + 0.06022457033395767, + 0.08593052625656128, + -0.07814031094312668, + 0.11881227046251297, + 0.16968248784542084, + -0.02303691953420639, + -0.034872591495513916, + 0.040614958852529526, + 0.002785144839435816, + 0.11403393000364304, + 0.04930868372321129, + -0.0002757177862804383, + 0.06357062608003616, + -0.00350449257530272, + 0.060427360236644745, + -0.27137890458106995, + 0.06843067705631256, + 0.06966497749090195, + 0.14403340220451355, + -0.15156199038028717, + 0.07504509389400482, + -0.22142574191093445, + -0.1295275241136551, + 0.03274773433804512, + 0.060876909643411636, + 0.020623942837119102, + -0.231109619140625, + -0.06197154149413109, + -0.24773262441158295, + 0.1310271918773651, + 0.058903567492961884, + -0.053562674671411514, + -0.022867659106850624, + 0.05235036090016365, + 0.033555373549461365, + 0.13029436767101288, + -0.04387717694044113, + 0.012062768451869488, + 0.06135736033320427, + 0.09473945200443268, + -0.39091140031814575, + 0.13202475011348724, + -0.04673781991004944, + 0.11487162113189697, + -0.08407820761203766, + -0.04127135127782822, + 0.027620213106274605, + -0.10787361860275269, + 0.053172044456005096, + 0.11614623665809631, + -0.04375189542770386, + 0.012034821324050426, + 0.03731787949800491, + -0.08775321394205093, + -0.018301252275705338, + 0.15277153253555298, + -0.3587838113307953, + -0.06818961352109909, + 0.048661936074495316, + 0.04346393421292305, + 0.03670266643166542, + -0.0031503329519182444, + 0.10299006849527359, + 0.010888492688536644, + 0.24071373045444489, + 0.058078959584236145, + 0.11848308891057968, + 0.06935173273086548, + -0.14377331733703613, + -0.03845414146780968, + 0.05799301341176033, + 0.08206411451101303, + -0.6852760910987854, + -0.09428318589925766, + 0.07413560152053833, + -0.1140931248664856 + ], + [ + -0.010131812654435635, + 0.10097990930080414, + -0.20737190544605255, + 0.13118232786655426, + 0.24356400966644287, + -0.03779494762420654, + -0.5962477326393127, + -0.32456308603286743, + 0.10129615664482117, + -0.09686974436044693, + 0.19153966009616852, + -0.03318970650434494, + -0.3374251127243042, + 0.002903660060837865, + 0.01882133260369301, + 0.013651769608259201, + -0.5167500972747803, + -0.10395104438066483, + -0.4693738520145416, + 0.21480868756771088, + -0.37346217036247253, + 0.21762922406196594, + -0.661828339099884, + 0.06568561494350433, + 0.07973172515630722, + -0.7144091129302979, + -0.14019142091274261, + -0.042819250375032425, + 0.06731618940830231, + 0.13336831331253052, + -0.33023324608802795, + -0.14978435635566711, + 0.06387696415185928, + 0.11414007842540741, + -0.07744289189577103, + -0.15161573886871338, + -0.16338148713111877, + 0.16705821454524994, + 0.18232817947864532, + -0.017943337559700012, + 0.0005297563620842993, + 0.19244103133678436, + -0.1585940718650818, + -0.01787111908197403, + 0.03209875896573067, + -0.6730806231498718, + 0.1585041731595993, + 0.1018826961517334, + -0.47400572896003723, + 0.04654737561941147, + -0.09544811397790909, + 0.2909339964389801, + 0.02782626263797283, + 0.17401735484600067, + 0.20644189417362213, + -0.3998975455760956, + 0.014406917616724968, + -0.493539422750473, + -0.09538859128952026, + -0.1180504709482193, + -0.04163781926035881, + 0.356374591588974, + 0.0823233425617218, + 0.08939574658870697, + -0.3368508219718933, + -0.03319619223475456, + 0.13779737055301666, + -0.306706964969635, + 0.11117183417081833, + -0.6898229122161865, + -0.013088976964354515, + -0.07064581662416458, + -0.12098126858472824, + -0.2097281515598297, + -0.5898545384407043, + -0.16226722300052643, + -0.027172373607754707, + 0.24326112866401672, + 0.014298086985945702, + 0.2344261258840561, + -0.1030227318406105, + 0.15521332621574402, + -0.08317168802022934, + 0.2321217656135559, + -0.23724855482578278, + 0.2313825488090515, + 0.2077067345380783, + 0.012028300203382969, + -0.0310235433280468, + 0.0321817584335804, + -0.761491060256958, + 0.3588871359825134, + -0.8004235029220581, + -0.25254133343696594, + 0.2385205328464508, + 0.21716426312923431, + -0.01715037412941456, + -0.17999005317687988, + -0.4611869752407074, + 0.03689286485314369, + -0.06774681061506271, + 0.13444410264492035, + -0.16787870228290558, + -0.05301349237561226, + -0.032686930149793625, + -0.10003988444805145, + -0.5625115036964417, + 0.1192416399717331, + -0.09610766172409058, + 0.045969948172569275, + -0.08616435527801514, + 0.03663567453622818, + -0.03506907448172569, + 0.2793661653995514, + 0.01729045808315277, + -0.19029858708381653, + 0.10631711781024933, + -0.02200859785079956, + -0.1375402808189392, + -0.06540774554014206, + -0.11009930074214935, + -0.0791810154914856, + 0.0059525188989937305, + 0.22254206240177155, + 0.13857552409172058, + -0.6382758617401123, + -0.11515527218580246, + -0.13473837077617645 + ], + [ + 0.007818971760571003, + 0.298423171043396, + 0.03873715549707413, + -0.281228631734848, + -0.1448945850133896, + 0.007069116923958063, + -0.16426528990268707, + 0.09726125746965408, + 0.17159706354141235, + -0.01504322700202465, + -0.19063197076320648, + -0.2048942744731903, + 0.2693907916545868, + -0.01348052453249693, + -0.37375348806381226, + 0.06928037106990814, + -0.023305101320147514, + -0.17931342124938965, + 0.14089229702949524, + 0.15701788663864136, + -0.6787843108177185, + -0.11782397329807281, + 0.10762683302164078, + -0.011024505831301212, + -0.020435212180018425, + -0.09656580537557602, + 0.2781142294406891, + -0.42046117782592773, + 0.005417739041149616, + -0.07489031553268433, + 0.09830142557621002, + 0.26509079337120056, + -0.2529531717300415, + 0.2380482703447342, + 0.16759955883026123, + -0.31973791122436523, + -0.27771857380867004, + 0.1473432332277298, + 0.019290458410978317, + 0.01814866065979004, + 0.10067557543516159, + -0.07441364973783493, + -0.03644012659788132, + 0.057341963052749634, + -0.09716664999723434, + 0.29473555088043213, + -0.26572084426879883, + -0.11291752755641937, + 0.20234903693199158, + 0.14151643216609955, + -0.32988405227661133, + -0.48805785179138184, + -0.08222396671772003, + -0.05179349333047867, + 0.06551972031593323, + -0.03339498117566109, + 0.007185961585491896, + 0.16025452315807343, + -0.33218711614608765, + 0.08520130813121796, + 0.032981567084789276, + 0.05687567591667175, + 0.04292065277695656, + 0.23861832916736603, + -0.0718112587928772, + -0.04965909570455551, + 0.2806243300437927, + -0.0873359963297844, + -0.07473232597112656, + 0.04413152486085892, + 0.19899454712867737, + -0.08203402161598206, + 0.17973153293132782, + 0.06589300185441971, + 0.01266455091536045, + 0.0568668395280838, + 0.06539242714643478, + -0.6820963621139526, + 0.024239543825387955, + -0.3339473307132721, + 0.03862283006310463, + -0.0011403487296774983, + -0.17386946082115173, + -0.06420623511075974, + -0.048178914934396744, + 0.06749143451452255, + 0.21186956763267517, + -0.11361432820558548, + -0.5385302305221558, + 0.09507540613412857, + 0.6424960494041443, + -0.1291331648826599, + -0.08993947505950928, + -0.007789966184645891, + -0.7806925773620605, + -0.08591597527265549, + -0.7246087193489075, + 0.07601787894964218, + 0.04514610767364502, + -0.43109065294265747, + 0.07600071281194687, + -0.08408557623624802, + -0.033987198024988174, + -0.025831101462244987, + -0.03607494756579399, + -0.6744252443313599, + -0.21901115775108337, + -0.5505521297454834, + -0.17528541386127472, + 0.13189095258712769, + -0.11955741792917252, + 0.39555177092552185, + 0.1518840789794922, + -0.06862549483776093, + -0.8982878923416138, + -0.004244702402502298, + -0.35918402671813965, + -0.010855907574295998, + -0.06126362085342407, + 0.20009319484233856, + 0.04267139360308647, + -0.3195117115974426, + 0.2037869542837143, + 0.15432150661945343, + -0.32638707756996155, + -0.5833671689033508, + 0.02945803292095661, + -0.21632005274295807 + ], + [ + 0.01756337098777294, + 0.0439942330121994, + -0.032467927783727646, + -0.021272091194987297, + -0.07258469611406326, + 0.04237062856554985, + -0.10320266336202621, + 0.10375718772411346, + -0.01625131070613861, + 0.023569168522953987, + 0.076879121363163, + 0.008875813335180283, + -0.010136953555047512, + -0.10944940149784088, + -0.18386051058769226, + -0.04280684515833855, + 0.04320735111832619, + -0.15730510652065277, + -0.015046875923871994, + 0.05402732267975807, + -0.016210993751883507, + 0.04442676156759262, + -0.08504962176084518, + 0.033600207418203354, + 0.01429572980850935, + -0.21922795474529266, + 0.02802887372672558, + -0.21205009520053864, + -0.035454168915748596, + 0.03202896937727928, + 0.16341613233089447, + -0.09760413318872452, + -0.01456579938530922, + -0.06215277314186096, + 0.08966192603111267, + 0.007286007981747389, + 0.05366678908467293, + 0.11157789081335068, + 0.1960938721895218, + 0.07421774417161942, + 0.015429391525685787, + -0.05549872666597366, + 0.06948407739400864, + 0.05828779563307762, + -0.0999470055103302, + -0.26329275965690613, + 0.0770886167883873, + 0.005755765829235315, + 0.032069284468889236, + 0.07134699076414108, + -0.0726892352104187, + -0.1491439938545227, + -0.006022997200489044, + -0.10928529500961304, + -0.0257433969527483, + 0.043893296271562576, + -0.013246994465589523, + 0.17516061663627625, + -0.07482919096946716, + 0.05218246951699257, + -0.1843772679567337, + 0.08321984112262726, + 0.011118046008050442, + 0.05875139310956001, + 0.07528064399957657, + 0.14080578088760376, + 0.1493387222290039, + 0.10722889751195908, + -0.08045214414596558, + -0.035011500120162964, + 0.00135675142519176, + 0.04414280131459236, + 0.07001380622386932, + 0.05418364703655243, + 0.013209705241024494, + -0.01353182177990675, + 0.07854489237070084, + 0.021544383838772774, + -0.19948545098304749, + 0.025002047419548035, + -0.1435409039258957, + 0.05166541412472725, + -0.06472239643335342, + 0.09890373796224594, + -0.14997293055057526, + -0.09350879490375519, + 0.08047406375408173, + -0.002517084591090679, + -0.1328093558549881, + 0.047247715294361115, + -0.08026081323623657, + 0.0318380743265152, + -0.20526641607284546, + 0.04420655593276024, + 0.08703549951314926, + 0.09282776713371277, + -0.14883428812026978, + 0.04762735217809677, + 0.0919484794139862, + -0.12519468367099762, + 0.07458025962114334, + 0.15720701217651367, + -0.03176441416144371, + 0.09213270992040634, + -0.011261987499892712, + -0.22462774813175201, + -0.1659754067659378, + 0.028723448514938354, + -0.11639855802059174, + -0.04809265956282616, + -0.04830390587449074, + -0.046834252774715424, + 0.2413182556629181, + 0.003912505228072405, + 0.012142417952418327, + -0.038848455995321274, + 0.032365165650844574, + 0.044666290283203125, + 0.058431752026081085, + 0.06045066565275192, + 0.03633192554116249, + 0.002637691330164671, + 0.09035307168960571, + 0.2491893619298935, + -0.3350314795970917, + -0.11550258100032806, + 0.11901245266199112, + -0.0827321857213974 + ], + [ + 0.06081501021981239, + 0.021811971440911293, + 0.018994471058249474, + -0.1085362508893013, + 0.027207819744944572, + 0.0994291678071022, + 0.06965508311986923, + 0.07788306474685669, + 0.055785734206438065, + 0.07858536392450333, + 0.12396637350320816, + 0.023039406165480614, + -0.09751277416944504, + 0.04520241916179657, + -0.03865604102611542, + 0.07621755450963974, + -0.004550247453153133, + -0.16688914597034454, + 0.07097499072551727, + 0.013264700770378113, + -0.046212732791900635, + -0.08326417952775955, + 0.019220933318138123, + -0.043337926268577576, + 0.062000423669815063, + -0.038877248764038086, + -0.09867484867572784, + 0.0031237713992595673, + 0.1132771223783493, + -0.06690185517072678, + -0.245234876871109, + 0.10870189964771271, + 0.009637880139052868, + 0.018742607906460762, + 0.06483510136604309, + 0.0979844331741333, + 0.007492696866393089, + -0.01545935869216919, + 0.037687111645936966, + -0.013536976650357246, + -0.08325828611850739, + -0.06029658764600754, + 0.10618828982114792, + -0.011241868138313293, + -0.11882238835096359, + 0.06578614562749863, + 0.025935450568795204, + 0.03133999556303024, + -0.025853242725133896, + -0.030649367719888687, + -0.02388886548578739, + -0.05595512315630913, + -0.13636866211891174, + -0.026975266635417938, + -0.14394864439964294, + 0.03116626851260662, + -0.14203506708145142, + -0.08463118225336075, + -0.039572007954120636, + -0.0016931180143728852, + -0.0013084778329357505, + -0.13615916669368744, + -0.04098600521683693, + 0.008066005073487759, + -0.07660532742738724, + -0.022627053782343864, + -0.013851710595190525, + 0.006379412487149239, + 0.014232429675757885, + -0.04307379201054573, + -0.10298149287700653, + 0.11398547142744064, + -0.046580877155065536, + -0.024383511394262314, + 0.0020681205205619335, + -0.19350524246692657, + -0.00596274109557271, + 0.0981660708785057, + 0.021524298936128616, + -0.011963463388383389, + 0.026499643921852112, + -0.022242894396185875, + -0.32699689269065857, + 0.013319918885827065, + 0.09131576120853424, + -0.011740774847567081, + -0.05320753529667854, + 0.002008129144087434, + 0.04584449529647827, + -0.07457627356052399, + -0.08834581077098846, + 0.17597413063049316, + 0.11214587092399597, + -0.04941529035568237, + 0.007556448224931955, + -0.17090199887752533, + -0.0963694378733635, + -0.15016445517539978, + -0.3409455120563507, + -0.04626506194472313, + 0.06501035392284393, + 0.04889852926135063, + -0.01648704521358013, + 0.10703827440738678, + 0.05845903977751732, + -0.031656745821237564, + 0.026161422953009605, + 0.08858665823936462, + 0.007542830426245928, + 0.004337786231189966, + -0.06966618448495865, + 0.04399655759334564, + -0.1017666831612587, + 0.09753807634115219, + 0.10695941001176834, + 0.19434699416160583, + 0.11447399854660034, + -0.02287023700773716, + 0.008874362334609032, + 0.007014438509941101, + -0.050857916474342346, + 0.01402221992611885, + -0.00919693149626255, + 0.09567968547344208, + 0.133785679936409, + -0.16450440883636475, + -0.12346841394901276, + 0.0586065836250782 + ], + [ + 0.0506473034620285, + 0.09080252796411514, + 0.04324831813573837, + -0.021858932450413704, + -0.06047965958714485, + 0.08834156394004822, + 0.01593790389597416, + -0.27050769329071045, + 0.017794767394661903, + -0.04993385449051857, + -0.01590183936059475, + -0.015625368803739548, + -0.016758082434535027, + -0.2931942939758301, + 0.0423365943133831, + -0.154631108045578, + -0.17470112442970276, + -0.101689413189888, + 0.03912084922194481, + 0.021804986521601677, + -0.0558139868080616, + -0.1422976553440094, + 0.04069128260016441, + -0.0436636321246624, + 0.08431258052587509, + -0.01221997756510973, + -0.4673442840576172, + -0.07466408610343933, + 0.0036995848640799522, + -0.3759593665599823, + 0.058024290949106216, + -0.13178567588329315, + -0.08892663568258286, + 0.242336243391037, + -0.1493428349494934, + 0.19644270837306976, + 0.05507059767842293, + 0.07590276002883911, + -0.06303295493125916, + -0.0652441456913948, + 0.0648922249674797, + -0.22578628361225128, + -0.026903273537755013, + 0.009222355671226978, + 0.1982722133398056, + -0.1195550486445427, + 0.1269877403974533, + 0.01569732464849949, + 0.012753034010529518, + -0.07449297606945038, + -0.04313609004020691, + -0.01778772659599781, + 0.04421735927462578, + -0.007613425143063068, + -0.016638655215501785, + -0.06784664839506149, + -0.01020299643278122, + -0.0478522889316082, + 0.04801929369568825, + -0.029335783794522285, + -0.009891035966575146, + 0.05118538811802864, + 0.02399996668100357, + 0.0770433098077774, + 0.14091186225414276, + -0.009373219683766365, + 0.11580551415681839, + 0.2879100739955902, + -0.10935626178979874, + 0.0021371745970100164, + -0.007114716339856386, + -0.08222746849060059, + -0.09296872466802597, + -0.011508574709296227, + 0.13260239362716675, + -0.020283570513129234, + 0.057248275727033615, + 0.16446608304977417, + -0.004295936785638332, + -0.09003531187772751, + 0.0825750008225441, + -0.09556286036968231, + 0.030737973749637604, + 0.09053275734186172, + 0.1774456650018692, + 0.07086996734142303, + -0.006485829595476389, + 0.12799857556819916, + 0.001964770257472992, + 0.027441207319498062, + -0.01874486729502678, + 0.11531516164541245, + -0.20297755300998688, + -0.19651903212070465, + -0.0028402444440871477, + -0.11818896234035492, + -0.03262125328183174, + 0.07678226381540298, + 0.03305886685848236, + 0.02443082630634308, + 0.06519877910614014, + 0.05529220029711723, + -0.026260295882821083, + 0.03921579197049141, + -0.08173321187496185, + -0.014017736539244652, + 0.17250540852546692, + -0.16763269901275635, + -0.1453571617603302, + -0.040111467242240906, + -0.00014044708223082125, + 0.054544977843761444, + 0.029216375201940536, + -0.020138872787356377, + 0.07444655895233154, + -0.5583115220069885, + 0.12506945431232452, + -0.006005831528455019, + 0.025875447317957878, + 0.1270401030778885, + 0.10943803191184998, + -0.12334036827087402, + 0.04358352720737457, + 0.20228368043899536, + -0.014483333565294743, + 0.07861541211605072, + -0.019833000376820564, + -0.15551748871803284 + ], + [ + -0.011639087460935116, + 0.14200399816036224, + 0.1254170536994934, + 0.07063907384872437, + -0.18437395989894867, + -0.06451601535081863, + -0.13226501643657684, + -0.07124137878417969, + 0.017211880534887314, + -0.12133882194757462, + -0.025589389726519585, + 0.2197813093662262, + 0.025306561961770058, + -0.005710646044462919, + -0.42807871103286743, + -0.04124094545841217, + 0.3805929720401764, + -0.21973416209220886, + 0.16426579654216766, + -0.16614650189876556, + -0.3328489065170288, + 0.0703621432185173, + -0.5650712251663208, + 0.07689489424228668, + 0.2074332982301712, + -0.4564379155635834, + 0.002528207842260599, + 0.06538008153438568, + 0.13950283825397491, + 0.14383243024349213, + 0.04772702977061272, + 0.10541736334562302, + -0.8667005896568298, + 0.23547309637069702, + 0.05734043940901756, + -0.36548277735710144, + -0.3459267318248749, + 0.2856829762458801, + -0.022237759083509445, + 0.01322904508560896, + -0.004421534948050976, + -0.17731522023677826, + -0.046439602971076965, + 0.04487663507461548, + -0.14736954867839813, + 0.309561550617218, + 0.24844326078891754, + -0.008388372138142586, + 0.05055363103747368, + -0.05554072558879852, + -0.17119255661964417, + -0.09778013080358505, + 0.1296693980693817, + 0.06944109499454498, + -0.2088676542043686, + 0.04874032735824585, + 0.11041762679815292, + 0.08033840358257294, + -0.0006494799163192511, + 0.1268027424812317, + -0.13766083121299744, + 0.10500441491603851, + 0.007430489175021648, + 0.09392397850751877, + 0.07586562633514404, + 0.14607150852680206, + 0.00831131637096405, + 0.05369235575199127, + -0.01884145475924015, + 0.009620679542422295, + 0.05165088176727295, + -0.25311699509620667, + 0.04885581135749817, + -0.036048464477062225, + -0.19700457155704498, + 0.15330146253108978, + 0.18727748095989227, + -0.04983442276716232, + -0.05490219220519066, + -0.9079042673110962, + 0.1045481413602829, + 0.14665232598781586, + -0.20384186506271362, + -0.1602231115102768, + -0.13420605659484863, + -0.06928268074989319, + 0.10773379355669022, + 0.46489647030830383, + -0.30044540762901306, + 0.1402941644191742, + 0.029404953122138977, + 0.12401431798934937, + -0.748126745223999, + -0.046407993882894516, + -0.4952637553215027, + -0.04645443707704544, + -0.20503072440624237, + -0.0680258721113205, + 0.22878368198871613, + -0.1423254758119583, + 0.02721140719950199, + 0.37162694334983826, + -0.19240018725395203, + 0.046682197600603104, + 0.1357332468032837, + -0.018933096900582314, + 0.06522998213768005, + -0.28999894857406616, + -0.17617376148700714, + -0.017141344025731087, + -0.13233938813209534, + 0.16505025327205658, + 0.16044564545154572, + -0.16361786425113678, + 0.06373869627714157, + 0.10295507311820984, + -0.006043627392500639, + 0.10583989322185516, + 0.0997459813952446, + 0.11253242194652557, + -0.03692075237631798, + -0.23435501754283905, + 0.0020479001104831696, + 0.2025112807750702, + -0.07835375517606735, + -0.5716217756271362, + -0.007385284639894962, + 0.06037788838148117 + ], + [ + -0.12404238432645798, + -0.013236228376626968, + -0.148006871342659, + -0.046231310814619064, + -0.053447965532541275, + -0.005558034870773554, + 0.050123102962970734, + 0.256998211145401, + 0.010722680017352104, + 0.010098368860781193, + 0.2576557993888855, + 0.11677505075931549, + -0.01825680583715439, + -0.29400789737701416, + -0.08814606815576553, + -0.07527513056993484, + 0.06657516956329346, + -0.024372555315494537, + 0.2033901810646057, + 0.04944339394569397, + -0.26237985491752625, + 0.07525193691253662, + 0.06951941549777985, + -0.011969679035246372, + 0.11888431012630463, + -0.004548188764601946, + -0.0012968177907168865, + -0.36087870597839355, + -0.07567966729402542, + -0.022990969941020012, + 0.04016467556357384, + 0.1319267898797989, + -0.18434296548366547, + 0.04442383348941803, + -0.35863354802131653, + -0.12749610841274261, + 0.009427308104932308, + 0.10602452605962753, + 0.22727200388908386, + -0.11736952513456345, + -0.010175500065088272, + 0.031655751168727875, + 0.18658724427223206, + 0.07152236998081207, + -0.11835465580224991, + -0.8692449331283569, + -0.06332413852214813, + 0.1189093142747879, + 0.028538502752780914, + 0.06504272669553757, + -0.4836111068725586, + -0.11841586232185364, + 0.13578034937381744, + 0.010862606577575207, + 0.10056337714195251, + 0.12842532992362976, + -0.06341321021318436, + 0.05460076406598091, + 0.002955735893920064, + 0.08676008880138397, + 0.02189541421830654, + -0.011846637353301048, + 0.04860095679759979, + -0.1043035164475441, + -0.05622967332601547, + -0.07120835781097412, + 0.09317672252655029, + 0.01873563602566719, + 0.04013371840119362, + 0.07898939400911331, + -0.030460979789495468, + 0.1807820051908493, + 0.026772527024149895, + 0.09254395961761475, + -0.03634931519627571, + -0.13232854008674622, + 0.012870918959379196, + -0.1563621610403061, + -0.09395180642604828, + 0.012061526998877525, + -0.04741579666733742, + -0.12959454953670502, + 0.01232041697949171, + 0.15393339097499847, + 0.008067005313932896, + -0.054602399468421936, + -0.012753425166010857, + -0.05413351580500603, + -0.040812183171510696, + -0.10593456774950027, + -0.13260237872600555, + -0.0467023029923439, + -0.03831164911389351, + -0.05577610805630684, + -0.019835125654935837, + -0.1155974417924881, + -0.07162481546401978, + 0.06892696768045425, + 0.017427751794457436, + 0.02779991365969181, + -0.005210326984524727, + 0.05573827400803566, + 0.07035822421312332, + 0.09326398372650146, + 0.10539558529853821, + 0.06531088799238205, + -0.5422884225845337, + -0.20574726164340973, + 0.06217137351632118, + 0.049624744802713394, + -0.023385055363178253, + 0.15359143912792206, + 0.15822406113147736, + 0.11612459272146225, + -0.4588245749473572, + 0.02818855084478855, + 0.23407787084579468, + -0.18611784279346466, + 0.23921725153923035, + 0.08852078020572662, + -0.008376184850931168, + -0.14636924862861633, + -0.005404219031333923, + -0.01216423325240612, + -0.05110209062695503, + 0.0464441180229187, + 0.035048432648181915, + 0.006369533017277718 + ], + [ + -1.708605150660319e-29, + -1.2988789608657805e-39, + 5.111334127413396e-35, + 3.2617243854541453e-25, + 6.0757909147941285e-24, + 1.75142155249693e-28, + -1.0891715852437256e-36, + 9.78033773483627e-24, + -9.045351542795455e-26, + 3.815433109634664e-35, + 2.3436503567353303e-34, + -8.888139083937878e-38, + 2.480018022162061e-41, + 4.4757458937550014e-39, + 2.5604207852922065e-26, + 2.507083037013662e-37, + -3.351149225494227e-40, + -9.781707878280813e-40, + -1.636500900822123e-32, + 9.076350283278273e-41, + -2.9281812970224242e-40, + 1.8146736285147296e-33, + -1.3833406672818843e-19, + -1.3267731386883668e-29, + 3.0663072866509215e-40, + 1.0729747991370532e-35, + 7.586568129755033e-31, + -2.7423270816990238e-40, + 7.487999549494996e-37, + -1.8645017549790065e-34, + 4.309334694624108e-39, + -3.5042455892975746e-36, + -5.633528112247916e-40, + -2.3810583246114427e-40, + 7.992670128877319e-40, + 3.1261567440622344e-41, + -1.8386997669791655e-40, + -3.016834849287138e-34, + -2.0352353084487297e-30, + 1.7701930876552538e-39, + 5.924269517626029e-41, + 4.8535351189578935e-37, + 2.6067080150232036e-39, + 7.691804810688665e-35, + 9.19363896474226e-40, + -6.386262207030814e-39, + -4.565101091631138e-39, + 1.7599598421754105e-35, + -5.22538592152867e-40, + 1.4179318770963526e-40, + 1.50144225907779e-39, + -3.7912613917480566e-32, + -2.97095970369481e-32, + -2.987098745219921e-34, + -1.4250700914736233e-39, + 2.6262229897079364e-33, + 2.8236598458669005e-39, + -2.872101332480145e-41, + -6.61410229509813e-37, + -9.53061621295077e-39, + -2.0863289036218217e-34, + 9.904154924322122e-31, + -1.465639417383845e-34, + -1.9758588606672786e-40, + 2.7482224228111523e-36, + -8.822855391081913e-40, + -5.535510535680832e-37, + 5.591040742809588e-40, + -1.3460122042010416e-35, + 6.235714562989698e-35, + 1.2440144826114567e-38, + -2.0554171358591274e-29, + 6.319017554323029e-33, + -5.152504554312874e-35, + -3.417847788041868e-22, + 8.180755321496818e-30, + -8.316623709158394e-39, + 3.2219733291573724e-39, + -3.750531523417206e-30, + 8.187981707536447e-39, + -2.08269946078126e-39, + -1.147525419988483e-36, + 8.68417481998585e-34, + -1.556240035525212e-40, + -4.0461512248454066e-40, + -8.928289087537712e-40, + 1.4475871246932065e-25, + 2.2551320394133573e-39, + 5.4105114616506214e-40, + 4.724657542248512e-36, + 6.214178592719447e-32, + -3.6029667634287896e-39, + -4.987655637055965e-40, + 5.4231007270541156e-39, + 4.2539357611354904e-40, + -6.452367249785041e-32, + 3.959039626972525e-33, + -5.258513514133826e-26, + 1.0520297266364816e-38, + 4.188008872284401e-39, + 2.1196771488656175e-37, + 5.317283074819091e-40, + 8.797509924149389e-35, + -9.914046505251648e-41, + -1.076943843185224e-34, + 2.0402334351967876e-29, + -5.981022105431184e-40, + 4.315228555965058e-40, + 2.5659767460941344e-35, + 2.0062249983891974e-40, + 7.071918946923411e-40, + -3.4643046818493926e-34, + 2.083842920328149e-40, + 3.8033578163651933e-31, + -4.5889631985956254e-26, + -3.039337223783263e-37, + 3.9575821554077565e-39, + 1.643001678455758e-33, + -1.402601559792762e-37, + 1.3832203240469503e-36, + 1.6215827584395662e-29, + -3.3775650334154267e-29, + 6.101667914172018e-33, + -4.150113557913665e-40, + 2.358315250535451e-40, + 1.2305103374665741e-37, + 9.208534380883864e-34, + 5.072490246086189e-40 + ], + [ + -0.06545699387788773, + -0.04897903650999069, + 0.07164176553487778, + -0.14237485826015472, + 0.07290563732385635, + 0.06283052265644073, + 0.04516969621181488, + -0.00392785482108593, + -0.045426707714796066, + -0.0951942503452301, + 0.1255570948123932, + -0.04554799199104309, + -0.019015124067664146, + -0.010990429669618607, + -0.06686249375343323, + -0.05069315433502197, + 0.05434919148683548, + -0.05116482451558113, + 0.06290464103221893, + -0.0571458600461483, + 0.04516392573714256, + 0.10713663697242737, + -0.04579157382249832, + 0.061578456312417984, + -0.11550264805555344, + 0.044599901884794235, + 0.07344415783882141, + -0.002972901100292802, + -0.07708510756492615, + 0.03919694200158119, + -0.021944500505924225, + -0.03224025294184685, + -0.19106832146644592, + 0.10563737899065018, + 0.11656548827886581, + -0.1060548648238182, + 0.026744619011878967, + 0.07047722488641739, + 0.049835205078125, + -0.012871669605374336, + -0.004306422080844641, + 0.11719849705696106, + 0.1060684472322464, + -0.011126157827675343, + -0.07531348615884781, + 0.0003443688037805259, + -0.015840014442801476, + 0.07778692245483398, + 0.09471800923347473, + 0.026601608842611313, + 0.133925661444664, + 0.0148536441847682, + 0.064248226583004, + 0.011701107025146484, + -0.06269572675228119, + 0.056256964802742004, + 0.015230025164783001, + 0.13004684448242188, + -0.033899616450071335, + -0.016843652352690697, + 0.010748421773314476, + 0.0035175310913473368, + 0.015751482918858528, + 0.02207208052277565, + 0.16496455669403076, + 0.08839946240186691, + 0.11167356371879578, + 0.03276721015572548, + 0.03853556886315346, + 0.02582627348601818, + 0.12828928232192993, + -0.036480654031038284, + 0.05266597494482994, + 0.038557372987270355, + -0.05219312384724617, + -0.021443326026201248, + 0.029886135831475258, + 0.019655460491776466, + 0.044996123760938644, + -0.030324753373861313, + -0.11253110319375992, + 0.07725398242473602, + -0.09282790869474411, + 0.02209324762225151, + -0.0745052620768547, + 0.025405997410416603, + 0.055691592395305634, + -0.03355657681822777, + -0.05105552822351456, + -0.06938335299491882, + -0.012865530326962471, + 0.013036256656050682, + -0.08398865163326263, + -0.05079716816544533, + 0.04473594203591347, + -0.0014084501890465617, + 0.003302184399217367, + -0.005270682275295258, + 0.033661309629678726, + 0.11747231334447861, + -0.0449979342520237, + 0.029418306425213814, + -0.003915002103894949, + -0.045601192861795425, + 0.05501389130949974, + -0.0592830665409565, + -0.05754227936267853, + 0.04136306419968605, + -0.16289368271827698, + 0.04573705792427063, + -0.024916160851716995, + -0.052635565400123596, + 0.03121121972799301, + -0.008338334038853645, + 0.1241462454199791, + -0.07093822956085205, + -0.014633173123002052, + 0.023479575291275978, + 0.04268394410610199, + -0.02637697570025921, + 0.032176487147808075, + -0.059986554086208344, + 0.03819979354739189, + 0.021077359095215797, + 0.0035572086926549673, + -0.04531107097864151, + -0.06141228228807449, + -0.00019166302809026092 + ], + [ + -0.10263272374868393, + -0.024178706109523773, + -0.08161205798387527, + -0.2814946174621582, + -0.024108629673719406, + 0.3961687982082367, + 0.037908148020505905, + 0.5050157904624939, + -0.1330016553401947, + -0.041522398591041565, + -0.16912829875946045, + -0.1952224224805832, + 0.3129233717918396, + -0.034844234585762024, + 0.02996155060827732, + -0.14761210978031158, + 0.07469799369573593, + -0.19057175517082214, + 0.17864003777503967, + 0.1913851797580719, + -0.23087769746780396, + -0.04897056147456169, + -0.04464507848024368, + -0.05270995572209358, + -0.06632930040359497, + -0.13792365789413452, + 0.31414762139320374, + -0.11609048396348953, + 0.15994024276733398, + 0.026678692549467087, + 0.3199292719364166, + -0.08578554540872574, + -0.889018714427948, + -0.21729913353919983, + 0.07796023041009903, + -0.5512118935585022, + 0.14663483202457428, + -0.033297210931777954, + 0.006460944190621376, + 0.11077459901571274, + -0.07985985279083252, + -0.11791766434907913, + -0.4736067056655884, + 0.07797112315893173, + -1.0891400575637817, + -0.12804362177848816, + -0.5395496487617493, + -0.004117090255022049, + 0.17611064016819, + 0.00011375117901479825, + -0.12491904944181442, + -0.031984079629182816, + 0.04403652250766754, + 0.1549587845802307, + -0.01655634492635727, + 0.08033283799886703, + 0.1404501348733902, + 0.09893602132797241, + 0.020104531198740005, + 0.1358717381954193, + -0.20212911069393158, + -0.05355386063456535, + -0.03829466924071312, + -0.20622281730175018, + -0.03670647740364075, + 0.22606143355369568, + 0.1362299919128418, + -0.1598019301891327, + -6.923858018126339e-05, + 0.045194439589977264, + 0.11713188141584396, + -0.4731104373931885, + -0.06767692416906357, + -0.11490077525377274, + 0.051369402557611465, + -0.0731361135840416, + 0.10258733481168747, + -0.10379156470298767, + -0.02376760169863701, + 0.11796100437641144, + -0.3528265357017517, + 0.1414411962032318, + -1.5701152086257935, + 0.03617304563522339, + -0.21721182763576508, + -0.09277713298797607, + -0.044091034680604935, + -0.030396051704883575, + -0.06317027658224106, + 0.08384033292531967, + 0.23110567033290863, + 0.03587409481406212, + -0.7863844633102417, + 0.03728930652141571, + -0.05317725986242294, + -0.5268135666847229, + -0.40828248858451843, + 0.09072817862033844, + 0.1352134644985199, + 0.10524976253509521, + 0.09761006385087967, + -0.019665326923131943, + 0.036212120205163956, + 0.1351386308670044, + -0.07333245128393173, + -0.30613836646080017, + -0.7368185520172119, + -0.09250396490097046, + -0.3917859196662903, + 0.005616565700620413, + -0.046367097645998, + 0.5149053931236267, + 0.08395858854055405, + -0.40043577551841736, + 0.0514216348528862, + 0.41495296359062195, + 0.13496515154838562, + 0.046692922711372375, + 0.18049176037311554, + 0.038725681602954865, + 0.04132773354649544, + -0.1664634793996811, + 0.08423867076635361, + 0.37225568294525146, + -0.906098484992981, + -0.5114443898200989, + 0.13718494772911072, + -1.1319215297698975 + ], + [ + -0.0335046611726284, + -0.04788026213645935, + 0.007197719067335129, + -0.21017304062843323, + 0.025315606966614723, + -0.07944763451814651, + 0.2392961084842682, + -0.027937866747379303, + -0.13278789818286896, + 0.04136377200484276, + -0.08421292901039124, + 0.04871489107608795, + -0.10007507354021072, + -0.08279348164796829, + 0.041378092020750046, + -0.021004287526011467, + -0.08529572933912277, + -0.2952892482280731, + 0.12388627976179123, + -0.17167767882347107, + -0.28919172286987305, + -0.11833585053682327, + 0.056717414408922195, + -0.057562049478292465, + 0.1571905016899109, + -0.1626599133014679, + -0.16962440311908722, + 0.030919494107365608, + -0.07790686190128326, + -0.017600031569600105, + 0.16313159465789795, + 0.002620526123791933, + 0.15113207697868347, + 0.020363179966807365, + -0.0889490619301796, + -0.011963862925767899, + 0.035914961248636246, + 0.038142312318086624, + 0.03540082275867462, + -0.03684143349528313, + -0.037352271378040314, + 0.09549648314714432, + 0.10202938318252563, + 0.08443361520767212, + 0.10503558069467545, + 0.029323631897568703, + 0.0214458666741848, + 0.07123091071844101, + 0.04566793516278267, + 0.03387771174311638, + 0.09672229737043381, + 0.12974688410758972, + 0.03626974672079086, + -0.030160289257764816, + -0.037922222167253494, + -0.06853538006544113, + -0.18037551641464233, + -0.05968530476093292, + -0.20842835307121277, + -0.3195459842681885, + 0.03409985452890396, + -0.012549218721687794, + 0.14143060147762299, + 0.1497703194618225, + 0.09304342418909073, + 0.1560242772102356, + -0.03567025065422058, + 0.09948459267616272, + -0.09501770883798599, + 0.21311041712760925, + -0.040618814527988434, + 0.10558363795280457, + -0.05585762858390808, + 0.09078160673379898, + 0.14704783260822296, + 0.19172899425029755, + -0.1753823310136795, + -0.198561891913414, + 0.019907526671886444, + -0.06567836552858353, + -0.08304981142282486, + -0.006167805753648281, + -0.09157800674438477, + 0.19218815863132477, + -0.012037104927003384, + 0.0014827882405370474, + -0.1669463962316513, + 0.009988355450332165, + 0.09412878006696701, + 0.08156927675008774, + -0.123862124979496, + -0.30522143840789795, + -0.06902176886796951, + -0.018353663384914398, + -0.04862309247255325, + -0.01641397923231125, + 0.10838785022497177, + -0.05183965340256691, + -0.19179269671440125, + -0.09324774891138077, + 0.0569632388651371, + 0.04528842121362686, + -0.007036041002720594, + -0.018942298367619514, + 0.002741041127592325, + -0.06482158601284027, + 0.2134658247232437, + -0.03169829398393631, + 0.08631561696529388, + 0.13091957569122314, + -0.004254992585629225, + 0.03575780615210533, + -0.34110569953918457, + 0.09068125486373901, + 0.07456091046333313, + 0.04731065779924393, + -0.0188747588545084, + 0.12544111907482147, + -0.015446782112121582, + -0.12090529501438141, + -0.028728527948260307, + 0.04233414679765701, + 0.039791740477085114, + 0.026525449007749557, + 0.2940525710582733, + -0.23667584359645844, + -0.04255277290940285, + -0.049276456236839294 + ], + [ + 0.08033234626054764, + 0.011126075871288776, + 0.06015361472964287, + 0.08190982788801193, + 0.06086524575948715, + -0.006511329207569361, + -0.06072274222970009, + -0.0951891764998436, + -0.009333268739283085, + -0.003765768138691783, + -0.17724011838436127, + -0.03330233320593834, + 0.06475704163312912, + -0.16214847564697266, + 0.03739458695054054, + -0.09823985397815704, + 0.2419707030057907, + 0.15730004012584686, + 0.06722060590982437, + 0.17500349879264832, + -0.12022356688976288, + -0.10567204654216766, + 0.017617497593164444, + -0.08158881962299347, + -0.04246135428547859, + -0.24122248589992523, + -0.19832439720630646, + 0.005655268207192421, + 0.10520468652248383, + 0.03245050460100174, + 0.10149715095758438, + 0.2121438831090927, + -0.15958689153194427, + 0.03704299405217171, + -0.007230808027088642, + 0.06204744800925255, + -0.004175259731709957, + 0.10266582667827606, + -0.042029090225696564, + 0.09485592693090439, + -0.2812352180480957, + -0.27786320447921753, + 0.15757513046264648, + -0.026971861720085144, + 0.05896999314427376, + -0.30830663442611694, + 0.04163764789700508, + 0.042336355894804, + -0.147605761885643, + 0.2969270646572113, + 0.07617751508951187, + -0.1007581576704979, + 0.07920914888381958, + -0.020090332254767418, + 0.1909990757703781, + 0.08118858933448792, + 0.04809916391968727, + 0.014959635213017464, + -0.026235394179821014, + -0.1317806839942932, + -0.12965001165866852, + -0.204665869474411, + 0.05153760686516762, + 0.1670066863298416, + 0.08931538462638855, + 0.04323553666472435, + -0.007711630780249834, + 0.050818514078855515, + 0.03548763319849968, + -0.06600844860076904, + -0.054435547441244125, + 0.024020086973905563, + -0.10733914375305176, + 0.0695246234536171, + -0.0809035673737526, + -0.0566566027700901, + 0.01978127658367157, + 0.14850978553295135, + -0.026654664427042007, + 0.004489007405936718, + 0.15804392099380493, + 0.0689757838845253, + -0.15744653344154358, + -0.07116247713565826, + 0.18925458192825317, + -0.05769462510943413, + 0.0955624058842659, + 0.11359070986509323, + 0.1134057566523552, + 0.0775146335363388, + 0.2649472653865814, + 0.26255160570144653, + -0.05409877002239227, + -0.0644068494439125, + -0.07987052202224731, + -0.011506225913763046, + 0.03319195657968521, + -0.3037119209766388, + -0.09792255610227585, + -0.10230862349271774, + 0.0674276202917099, + -0.06492935866117477, + -0.05796622857451439, + -0.17785458266735077, + 0.009549444541335106, + 0.12429839372634888, + -0.11129532009363174, + 0.05488556995987892, + -0.2514827847480774, + -0.19407151639461517, + -0.1453956961631775, + -0.005427065771073103, + -0.28421926498413086, + -0.02033633552491665, + 0.05497220531105995, + 0.16502001881599426, + -0.1572427749633789, + 0.06642760336399078, + -0.11034755408763885, + 0.1571487933397293, + -0.03616500273346901, + 0.10026831179857254, + 0.06464836746454239, + 0.034199658781290054, + -0.23751221597194672, + -0.2752484977245331, + 0.12341517955064774, + 0.0570061020553112 + ], + [ + -0.4499478340148926, + -0.02090173214673996, + 0.13502268493175507, + -0.18730910122394562, + -0.008685216307640076, + -0.22127895057201385, + 0.040365733206272125, + -0.053880397230386734, + -0.007110548671334982, + -0.6644781231880188, + -0.1941748857498169, + 0.011723348870873451, + -0.7054663300514221, + 0.2646782398223877, + -0.07845968753099442, + 0.10926216095685959, + 0.0664115846157074, + 0.04929657280445099, + 0.09111957997083664, + -0.06705573201179504, + -0.7382773756980896, + 0.3041072487831116, + 0.03490478917956352, + -0.09386807680130005, + 0.036665115505456924, + -0.3801749050617218, + 0.2014818638563156, + 0.06814045459032059, + -0.011930946260690689, + -0.6193518042564392, + 0.2516321837902069, + -0.40727540850639343, + -0.12581117451190948, + 0.0021203900687396526, + 0.19118265807628632, + 0.031817395240068436, + 0.22604596614837646, + 0.05851166322827339, + -0.05555621162056923, + 0.006060417275875807, + -0.013567494228482246, + -0.20065130293369293, + -0.22174808382987976, + 0.04621780663728714, + 0.07829253375530243, + -0.038863327354192734, + -0.14879313111305237, + 0.11118511855602264, + -0.05455666780471802, + 0.11403705924749374, + 0.10819169133901596, + -0.3724074065685272, + -0.0967596024274826, + 0.1145261749625206, + 0.0993732139468193, + -0.17160935699939728, + -0.08008844405412674, + 0.16445858776569366, + -0.024098535999655724, + 0.003332944819703698, + -0.04528480023145676, + -0.045690637081861496, + 0.12486085295677185, + 0.0426473394036293, + -0.002572940429672599, + -0.5154967308044434, + 0.1360313445329666, + 0.039235733449459076, + 0.03687521815299988, + 0.050707943737506866, + 0.23833444714546204, + -0.5974093675613403, + -0.05384330824017525, + -0.18579742312431335, + 0.05896877869963646, + -0.09817281365394592, + -0.23853252828121185, + -0.1218176856637001, + 0.08636154234409332, + 0.17812404036521912, + 0.3098451793193817, + -0.07132291793823242, + -0.2333378791809082, + -0.0579184927046299, + -0.008946326561272144, + -0.010417686775326729, + 0.014409639872610569, + 0.12523908913135529, + -0.054609015583992004, + -0.18026545643806458, + 0.1620568484067917, + 0.04899073764681816, + -0.24912399053573608, + 0.1110076904296875, + 0.04097776487469673, + -0.13059133291244507, + -0.053384408354759216, + 0.09581585973501205, + 0.02631315588951111, + -0.038474053144454956, + 0.37592804431915283, + -0.0679982528090477, + 0.1196548193693161, + -0.14403904974460602, + 0.004618452861905098, + -0.13533315062522888, + -0.8610506653785706, + 0.07214914262294769, + -0.08852270990610123, + 0.18590950965881348, + 0.1183350682258606, + 0.0012335111387073994, + 0.22958749532699585, + 0.028851594775915146, + -0.14458759129047394, + 0.46033984422683716, + -0.06436537951231003, + 0.11683231592178345, + -0.5119854807853699, + 0.03787980601191521, + -0.4054548442363739, + 0.004819386173039675, + -0.03476973623037338, + 0.06517913937568665, + 0.15874405205249786, + -0.0900236964225769, + 0.06693416833877563, + 0.138095885515213 + ], + [ + -0.05260346084833145, + -0.033169835805892944, + 0.09669484198093414, + -0.0022011299151927233, + 0.004912170115858316, + -0.04037303477525711, + -0.011524372734129429, + -0.09783994406461716, + -0.020857198163866997, + -0.2736143469810486, + -0.22473059594631195, + 0.007760701235383749, + -0.06426598131656647, + -0.010641085915267467, + -0.1251421868801117, + 0.11825321614742279, + 0.09091941267251968, + -0.13488373160362244, + -0.07934869080781937, + -0.12412875145673752, + 0.1396000236272812, + 0.124903604388237, + -0.05664398893713951, + -0.032827138900756836, + 0.0693807452917099, + -0.07924576103687286, + -0.08994641155004501, + -0.1035076156258583, + 0.04715588316321373, + 0.08542585372924805, + 0.07924637943506241, + -0.27051666378974915, + -0.003081209259107709, + 0.0061918143182992935, + 0.13918916881084442, + -0.030337560921907425, + 0.06526333838701248, + 0.026794789358973503, + -0.023672647774219513, + 0.20516662299633026, + -0.12906862795352936, + 0.015221713110804558, + -0.10222998261451721, + -0.021342845633625984, + 0.14554384350776672, + -0.24554379284381866, + 0.05382183566689491, + -0.17247170209884644, + 0.1396813690662384, + 0.05373959615826607, + 0.03800518810749054, + 0.03738449513912201, + -0.20778071880340576, + -0.007979674264788628, + 0.19082655012607574, + -0.04408673942089081, + 0.07989279180765152, + 0.08704976737499237, + 0.07801361382007599, + 0.07745658606290817, + -0.0019351454684510827, + -0.20926643908023834, + -0.12741585075855255, + -0.01776018738746643, + 0.14131128787994385, + 0.0030498732812702656, + -0.04022841528058052, + -0.13412567973136902, + -0.07757232338190079, + -0.18983638286590576, + 0.15445178747177124, + -0.1873639076948166, + -0.2441592812538147, + 0.10755407810211182, + 0.07389397919178009, + -0.0475320927798748, + -0.04658791422843933, + 0.023123500868678093, + -0.10779251903295517, + 0.006964132655411959, + 0.028984718024730682, + 0.24648156762123108, + 0.15902560949325562, + -0.17108775675296783, + 0.06393293291330338, + 0.1303279548883438, + 0.10256216675043106, + 0.06355912238359451, + -0.05969911441206932, + 0.0243314728140831, + 0.13412874937057495, + 0.12447336316108704, + -0.11127422004938126, + -0.21614541113376617, + -0.08387605100870132, + -0.020364772528409958, + -0.08614076673984528, + -0.0830858126282692, + 0.11122280359268188, + -0.07830213755369186, + 0.057337623089551926, + 0.13403671979904175, + -0.09147528558969498, + -0.0927748903632164, + 0.01820763200521469, + -0.06847359985113144, + 0.3648298978805542, + 0.0024848217144608498, + 0.14939962327480316, + 0.05263481289148331, + 0.20754143595695496, + -0.07436657696962357, + 0.2899438142776489, + -0.05371851846575737, + -0.015335166826844215, + 0.12007083743810654, + -0.16179706156253815, + -0.03613251447677612, + -0.12388832122087479, + 0.027469906955957413, + -0.011571569368243217, + 0.013432160019874573, + -0.5463423132896423, + -0.06565337628126144, + -0.18745405972003937, + -0.04864726588129997, + 0.05153406038880348, + 0.1039930135011673 + ], + [ + 0.0547507181763649, + -0.13324297964572906, + 0.0034405512269586325, + 0.0019103517988696694, + -0.009216834791004658, + 0.039276089519262314, + -0.0753646120429039, + 0.06912734359502792, + -0.05986831337213516, + 0.03323444351553917, + 0.12204858660697937, + -0.05637521669268608, + -0.07268474251031876, + -0.03040204383432865, + -0.07486095279455185, + 0.01884196512401104, + 0.030102644115686417, + 0.05557113140821457, + 0.12982147932052612, + -0.04554924741387367, + -0.07929115742444992, + -0.030071577057242393, + 0.0739666149020195, + 0.0498993955552578, + 0.15491369366645813, + -0.14759260416030884, + -0.07264281064271927, + -0.02411087229847908, + -0.04357026517391205, + 0.07682470977306366, + 0.04371696710586548, + -0.004526807460933924, + 0.02424667403101921, + 0.020074553787708282, + -0.11645350605249405, + -0.007879825308918953, + 0.07230649143457413, + 0.09180980175733566, + -0.005185913760215044, + 0.14393173158168793, + 0.19089555740356445, + 0.0008361217915080488, + -0.08758119493722916, + 0.19326740503311157, + -0.13446691632270813, + -0.16659240424633026, + 0.09314420819282532, + 0.13095930218696594, + 0.07485520094633102, + -0.023014472797513008, + 0.22226788103580475, + -0.09790483862161636, + -0.1137305498123169, + -0.019489891827106476, + -0.01977364346385002, + -0.12470704317092896, + -0.1660533994436264, + 0.03995785489678383, + -0.1410951316356659, + -0.08092427998781204, + 0.09306801110506058, + -0.019549019634723663, + -0.01484761480242014, + -0.019695261493325233, + 0.07125338166952133, + -0.10563130676746368, + -0.025852695107460022, + 0.07193111628293991, + 0.07661047577857971, + -0.0016130432486534119, + 0.0468205027282238, + 0.0046112751588225365, + 0.09907755255699158, + -0.001411248929798603, + 0.06784063577651978, + -0.09297768026590347, + -0.15650172531604767, + -0.16060209274291992, + -0.10276113450527191, + -0.0840572640299797, + -0.04466021806001663, + 0.02996690943837166, + 0.04366299882531166, + 0.1310880333185196, + -0.08641908317804337, + -0.04323498532176018, + -0.08177101612091064, + 0.05205336585640907, + 0.0611598826944828, + 0.0939076691865921, + -0.07394421845674515, + -0.07983823865652084, + -0.0673614963889122, + 0.06676562130451202, + -0.06116902455687523, + -0.06809081137180328, + 0.03222811594605446, + 0.06311822682619095, + 0.036603908985853195, + -0.03409180790185928, + 0.07846218347549438, + 0.13779638707637787, + 0.02977631613612175, + -0.02421778067946434, + 0.044626060873270035, + -0.13350026309490204, + -0.23805078864097595, + -0.37404435873031616, + 0.03790333867073059, + 0.086899533867836, + 0.07224775850772858, + 0.06746301054954529, + 0.13045893609523773, + 0.13177259266376495, + -0.1337258517742157, + 0.07187721133232117, + -0.19010411202907562, + -0.16259363293647766, + -0.03652658686041832, + 0.15200500190258026, + -0.06867792457342148, + 0.07219947129487991, + -0.07604579627513885, + 0.29546672105789185, + 0.016599517315626144, + -0.01660092920064926, + 0.03443552926182747, + 0.2533494830131531 + ], + [ + -0.009517939761281013, + 0.005656349007040262, + 0.016130991280078888, + -0.0487244687974453, + 0.0027237723115831614, + -0.039856813848018646, + -0.02950448729097843, + -0.0068128774873912334, + -0.03140130266547203, + -0.10423456877470016, + 0.005162307061254978, + 0.018079567700624466, + 0.030075017362833023, + -0.02981952391564846, + -0.013558242470026016, + -0.0030866723973304033, + 0.00030467912438325584, + -0.04593023285269737, + 0.020524518564343452, + -0.010797952301800251, + 0.04703512415289879, + -0.0359579436480999, + -0.03892681375145912, + 0.1009056344628334, + 0.0863112062215805, + -0.03258334472775459, + -0.012578295543789864, + -0.006612666882574558, + 0.09632471203804016, + 0.062202755361795425, + 0.07299434393644333, + -0.03476400673389435, + -0.0665699765086174, + 0.04237255081534386, + 0.053223416209220886, + -0.007385658100247383, + 0.03707398101687431, + 0.0336926095187664, + 0.06385356187820435, + 0.043886687606573105, + -0.02275792323052883, + 0.03543055057525635, + -0.006185383070260286, + 0.014214139431715012, + 0.02181536890566349, + -0.032557930797338486, + 0.07797981053590775, + 0.0884004682302475, + -0.00033606545184738934, + 0.06864035874605179, + -0.005153395235538483, + 0.02820206806063652, + 0.024085860699415207, + -0.05961492285132408, + -0.04526258632540703, + -0.0001396029838360846, + 0.08701611310243607, + 0.002242401707917452, + 0.03328903391957283, + -0.004263394046574831, + 0.0016415086574852467, + -0.053362950682640076, + -0.0403766855597496, + 0.030156796798110008, + 0.08290272206068039, + 0.03914862871170044, + 0.07614094763994217, + -0.004560800269246101, + 0.06251002848148346, + 0.04445939511060715, + 0.02866545133292675, + -0.09018179774284363, + 0.033328793942928314, + -0.03800603747367859, + -0.035806380212306976, + -0.04039666801691055, + 0.005018687807023525, + -0.025493012741208076, + -0.0041546281427145, + 0.0045529259368777275, + -0.04347219318151474, + 0.15612812340259552, + -0.06524636596441269, + -0.0025417215656489134, + -0.09165138006210327, + 0.04016523435711861, + 0.002641010330989957, + 0.02468092180788517, + -0.04130743443965912, + 0.07494194060564041, + 0.035178691148757935, + 0.02575530670583248, + -0.023817086592316628, + 0.019138824194669724, + -0.046438563615083694, + -0.042390111833810806, + -0.08182672411203384, + -0.0042299628257751465, + -0.005930098704993725, + 0.003342775395140052, + 0.05706610158085823, + 0.006573065649718046, + 0.032201554626226425, + -0.041722413152456284, + -0.052066121250391006, + -0.07452237606048584, + -0.07026496529579163, + 0.02061188966035843, + -0.0779339000582695, + 0.008546070195734501, + 0.02640659362077713, + -0.03248627856373787, + 0.0015294348122552037, + 0.026766473427414894, + 0.038702208548784256, + -0.028450870886445045, + -0.012687730602920055, + 0.0863153263926506, + 0.04294927790760994, + 0.006022096611559391, + 0.06699233502149582, + -0.03650345653295517, + -0.048530466854572296, + 0.08733171969652176, + -0.09623707085847855, + -0.013353493064641953, + -0.020759008824825287, + -0.05124666914343834 + ], + [ + 0.06337250024080276, + 0.00047817346057854593, + -0.49476802349090576, + 0.04434862360358238, + 0.03751498460769653, + 0.022025275975465775, + -0.08335675299167633, + -0.07335726171731949, + 0.05607231706380844, + 0.06408807635307312, + 0.17238187789916992, + 0.14058710634708405, + 0.2956317663192749, + -0.06060131639242172, + -0.3276984989643097, + 0.06737968325614929, + 0.13147304952144623, + -0.03382835537195206, + -0.005220816470682621, + -0.006617026403546333, + 0.2848731279373169, + 0.06564268469810486, + 0.12224689871072769, + 0.13471971452236176, + 0.020500607788562775, + 0.09435594826936722, + -0.003706794697791338, + 0.0035962683614343405, + 0.03460569679737091, + 0.01810469664633274, + -0.0002998600248247385, + 0.041104577481746674, + 0.11244343221187592, + -0.17400309443473816, + -0.06660496443510056, + 0.05955951660871506, + -0.09198972582817078, + -0.3733079135417938, + 0.009655117057263851, + 0.15534213185310364, + 0.07830478250980377, + 0.06737800687551498, + 0.11815829575061798, + 0.09504736214876175, + 0.023048335686326027, + -0.1096993237733841, + 0.03101031854748726, + -0.03938145563006401, + -0.00013429780665319413, + -0.08179758489131927, + -0.10855596512556076, + -0.15741798281669617, + 0.1835532784461975, + -0.16759376227855682, + 0.23211924731731415, + -0.1174778863787651, + -0.38261574506759644, + -0.057385560125112534, + -0.16832536458969116, + -0.16631650924682617, + 0.15260668098926544, + 0.06000608205795288, + -0.0968955010175705, + -0.007976575754582882, + -0.5044055581092834, + 0.04101806506514549, + 0.036520279943943024, + 0.11278685927391052, + 0.02813011407852173, + -0.08983303606510162, + -0.21969258785247803, + 0.08329517394304276, + -0.2748418152332306, + 0.01720353588461876, + -0.1742541640996933, + -0.045525845140218735, + -0.23969121277332306, + -0.9098151326179504, + -0.0026259147562086582, + -0.0025111983995884657, + 0.17236143350601196, + -0.19832554459571838, + -0.02624594420194626, + -0.18676835298538208, + -0.0444672666490078, + -0.3411053419113159, + -0.46285754442214966, + 0.05635170638561249, + -0.08619407564401627, + 0.08139259368181229, + 0.14407916367053986, + -0.01660725474357605, + -0.7420406937599182, + 0.05104970186948776, + 0.43816521763801575, + -0.08187385648488998, + -0.3539186120033264, + -0.01584855280816555, + -1.3145984411239624, + -0.2741836905479431, + -0.04851241037249565, + -0.0008126030443236232, + -0.16387641429901123, + 0.011566711589694023, + 0.04403159022331238, + 0.1447100192308426, + -0.04823194071650505, + 0.08720572292804718, + 0.32752707600593567, + 0.1479390561580658, + -0.21585047245025635, + 0.0498599112033844, + -0.11495921015739441, + -0.03623342141509056, + 0.19651776552200317, + -0.017318200320005417, + -0.07030058652162552, + 0.04678918421268463, + 0.0411478690803051, + -0.29470574855804443, + 0.07458989322185516, + -0.07512802630662918, + 0.11052272468805313, + 0.12681211531162262, + 0.05583731085062027, + 0.4233972132205963, + 0.0013305017491802573, + 0.4961242973804474 + ], + [ + 0.0611053965985775, + 0.008832607418298721, + -0.01783697120845318, + 0.058197297155857086, + -0.12182396650314331, + 0.12759704887866974, + 0.0654120147228241, + -0.06752464175224304, + 0.03347418084740639, + 0.07702323794364929, + -0.024596912786364555, + 0.06338576227426529, + 0.10838642716407776, + 0.11972390860319138, + -0.13054820895195007, + 0.034602634608745575, + 0.04547745734453201, + -0.006412498652935028, + 0.019995126873254776, + 0.05635034292936325, + -0.22970183193683624, + 0.14679516851902008, + 0.019826875999569893, + 0.03992842882871628, + -0.17804381251335144, + 0.07240664958953857, + -0.21488602459430695, + 0.0836467519402504, + -0.03617909923195839, + -0.009169720113277435, + 0.04190623760223389, + -0.12169156968593597, + -0.008543274365365505, + 0.09737690538167953, + -0.1675516813993454, + 0.15656182169914246, + -0.040045276284217834, + 0.07333347201347351, + -0.10106874257326126, + -0.08109229058027267, + -0.007158433087170124, + -0.12768089771270752, + 0.056647978723049164, + -0.045753363519907, + 0.05319003760814667, + 0.010192306712269783, + 0.027523072436451912, + 0.04169638827443123, + 0.09373556077480316, + 0.1318095177412033, + 0.04733481630682945, + -0.09367816150188446, + -0.06259788572788239, + 0.04148806631565094, + 0.014644120819866657, + 0.01143344771116972, + 0.15117666125297546, + -0.023180939257144928, + -0.01521890889853239, + 0.044746849685907364, + 0.0754413828253746, + -0.009559787809848785, + -0.0024715715553611517, + 0.038263700902462006, + -0.020734179764986038, + -0.32310372591018677, + -0.19517390429973602, + -0.09471254050731659, + 0.0014752349816262722, + 0.02113398350775242, + -0.10969212651252747, + -0.024933502078056335, + 0.03252055495977402, + 0.07257290184497833, + 0.03165343031287193, + 0.0034494525752961636, + -0.09444873780012131, + 0.10011670738458633, + 0.0627204030752182, + -0.07452137023210526, + 0.06886015832424164, + -0.034359801560640335, + 0.08808062225580215, + -0.13163292407989502, + 0.06754579395055771, + 0.07306766510009766, + 0.11326263844966888, + 0.07077378779649734, + -0.09174107760190964, + -0.04550581052899361, + 0.12232532352209091, + 0.09140952676534653, + -0.08785656094551086, + -0.0031950732227414846, + 0.01703548990190029, + 0.041741129010915756, + -0.04815554618835449, + 0.04640478268265724, + 0.08553174883127213, + -0.09814228117465973, + 0.06339576840400696, + 0.0781078189611435, + 0.04357202723622322, + 0.06111442670226097, + -0.006057451479136944, + 0.08007228374481201, + 0.007697467226535082, + -0.04753778129816055, + -0.08178588002920151, + -0.10064343363046646, + -0.05310669168829918, + 0.0630192756652832, + 0.04222767427563667, + -0.11151689291000366, + 0.015316260978579521, + -0.2887967526912689, + 0.13145333528518677, + -0.20421293377876282, + -0.005098188295960426, + 0.06468173116445541, + 0.020133882761001587, + 0.017633484676480293, + 0.07496856898069382, + -0.013967613689601421, + -0.1826077699661255, + 0.1231728047132492, + 0.031234145164489746, + -0.033651433885097504 + ], + [ + 0.13294686377048492, + -0.0811614990234375, + -0.02020018920302391, + -0.20082023739814758, + 0.02798011153936386, + 0.09096059948205948, + -0.02082555554807186, + -0.1372816115617752, + -0.04264388978481293, + 0.026152417063713074, + 0.1676291525363922, + 0.099132239818573, + 0.3096359074115753, + -0.3822519779205322, + 0.06548881530761719, + 0.1643582433462143, + 0.0745316669344902, + -0.09364145249128342, + 0.10256990045309067, + 0.10301602631807327, + 0.10751685500144958, + -0.06337784975767136, + -0.027765465900301933, + -0.09442105144262314, + -0.14722730219364166, + -0.02042597159743309, + -0.07415247708559036, + -0.06341902911663055, + -0.008476982824504375, + 0.036486126482486725, + 0.1462118923664093, + 0.17288637161254883, + -0.04741203784942627, + 0.21063554286956787, + 0.05399256572127342, + -0.08397189527750015, + -0.1295773833990097, + 0.011374620720744133, + -0.04053199663758278, + 0.05415879562497139, + -0.302077978849411, + -0.31518521904945374, + -0.1471225768327713, + -0.016737403348088264, + -0.14340795576572418, + -0.7552106976509094, + 0.08943478018045425, + 0.10891875624656677, + -0.003383783856406808, + -0.0012865595053881407, + 0.11454663425683975, + -0.39786097407341003, + -0.05113276466727257, + 0.1534784436225891, + -0.13942445814609528, + 0.03337053209543228, + -0.0032863295637071133, + 0.08813446015119553, + -0.16051983833312988, + 0.02409067004919052, + -0.18560341000556946, + 0.1949971467256546, + 0.056939590722322464, + 0.016793999820947647, + -0.0034976282622665167, + -0.10333538800477982, + 0.06970233470201492, + -0.14398668706417084, + -0.08183500915765762, + 0.12532691657543182, + -0.007296164054423571, + -0.050551172345876694, + 0.07394740730524063, + 0.021835703402757645, + 0.06198533996939659, + -0.07333151996135712, + 0.06975949555635452, + 0.24688947200775146, + -0.22239701449871063, + -0.06658392399549484, + -0.11053314059972763, + -0.13227412104606628, + 0.07516126334667206, + 0.19503355026245117, + 0.12328717112541199, + 0.012807844206690788, + 0.16478729248046875, + -0.12104983627796173, + -0.4834393858909607, + 0.034283630549907684, + -0.07887432724237442, + -0.10287662595510483, + -0.4061257243156433, + -0.05682339146733284, + -0.08846408128738403, + 0.07114600390195847, + -0.38578280806541443, + -0.032677289098501205, + 0.03394341468811035, + -0.043584614992141724, + 0.026169558987021446, + -0.2013360559940338, + 0.07914037257432938, + -0.08457442373037338, + -0.01121661625802517, + -0.020288260653614998, + 0.01704087108373642, + -0.0853983461856842, + -0.022972460836172104, + 0.09879082441329956, + -0.04369010031223297, + -0.1151723563671112, + 0.03294111415743828, + 0.05052448436617851, + 0.03806797415018082, + 0.05613444373011589, + -0.136430561542511, + -0.3065389096736908, + 0.1232742965221405, + -0.33190709352493286, + -0.017537519335746765, + -0.03552500531077385, + 0.032309580594301224, + 0.16932009160518646, + 0.30665677785873413, + 0.4963979125022888, + 0.26763033866882324, + 0.10775285959243774 + ], + [ + -0.02710394747555256, + -0.0874847024679184, + -0.0534755103290081, + 0.013380386866629124, + -0.017584070563316345, + -0.04145187512040138, + -0.04626079648733139, + 0.064061239361763, + 0.029391758143901825, + -0.0014558819821104407, + 0.10135290026664734, + 0.2564385235309601, + 0.19479160010814667, + 0.02653925120830536, + -0.03650468960404396, + 0.007864090614020824, + 0.15133921802043915, + -0.12359365820884705, + 0.1291312575340271, + 0.17131765186786652, + -0.6594901084899902, + 0.005695527885109186, + -0.025904709473252296, + 0.0077131642028689384, + 0.07507201284170151, + -0.14725883305072784, + -0.11976132541894913, + -0.22606313228607178, + 0.04280661791563034, + 0.19864347577095032, + 0.12513239681720734, + 0.01839076541364193, + 0.08983777463436127, + -0.005026019643992186, + -0.04864382743835449, + -0.18403159081935883, + -0.12508650124073029, + -0.005487199407070875, + 0.2347613275051117, + 0.009238151833415031, + -0.07721509784460068, + -0.009486343711614609, + -0.011060593649744987, + 0.0545852854847908, + -0.015149605460464954, + -0.6301679015159607, + 0.06723957508802414, + -0.1487574279308319, + -0.05060236155986786, + 0.10786332190036774, + -0.010419833473861217, + -0.06401750445365906, + -0.09338469058275223, + -0.1959545612335205, + 0.03326604887843132, + 0.25883764028549194, + -0.021050818264484406, + -0.11232846230268478, + -0.015026367269456387, + 0.11729241907596588, + 0.009399447590112686, + 0.0861792117357254, + 0.004974448122084141, + 0.20268389582633972, + -0.06704418361186981, + 0.12578974664211273, + -0.019175512716174126, + -0.03014247491955757, + -0.0019926263485103846, + 0.03429127857089043, + -0.09525948762893677, + 0.01248068455606699, + 0.15326575934886932, + -0.03648138418793678, + 0.057147227227687836, + 0.028883259743452072, + 0.1634775847196579, + 0.08865201473236084, + -0.1916368156671524, + -0.08679519593715668, + -0.5173342227935791, + -0.07733722031116486, + 0.06933696568012238, + 0.15863750874996185, + -0.26812443137168884, + -0.013752956874668598, + 0.0985134094953537, + -0.04632226377725601, + -0.1769380122423172, + 0.029761439189314842, + 0.07647883892059326, + 0.015910690650343895, + -0.1065220981836319, + 0.03270692378282547, + -0.06884055584669113, + -0.009572180919349194, + -0.03752324730157852, + 0.046956997364759445, + 0.13459935784339905, + 0.05556648597121239, + 0.005140150431543589, + -0.18806786835193634, + -0.02665076218545437, + -0.05613326281309128, + 0.0941164419054985, + -0.09072589874267578, + 0.006406528875231743, + -0.12869223952293396, + -0.11389891803264618, + 0.034323688596487045, + -0.12335806339979172, + -0.024770274758338928, + 0.30221566557884216, + 0.011450733989477158, + -0.19592434167861938, + 0.07453598827123642, + -0.13188333809375763, + -0.17493167519569397, + 0.25114932656288147, + 0.1546190083026886, + 0.0018013914814218879, + -0.09941217303276062, + 0.02141127735376358, + -0.01277556736022234, + -0.31161046028137207, + 0.038346532732248306, + -7.84418371040374e-05, + 0.14943625032901764 + ], + [ + -0.057931337505578995, + 0.04676571115851402, + 0.031560298055410385, + -0.31932875514030457, + 0.03226117789745331, + -0.22664225101470947, + -0.1987306773662567, + 0.24023263156414032, + -0.07392103224992752, + -0.18093419075012207, + 0.133636936545372, + 0.0026879049837589264, + -0.016858773306012154, + -0.15831661224365234, + -0.08322469890117645, + 0.015900209546089172, + 0.07384690642356873, + -0.07268921285867691, + 0.22745943069458008, + 0.11120070517063141, + 0.2824326455593109, + 0.09807176142930984, + -0.09041669219732285, + -0.04711882770061493, + -0.12488167732954025, + -0.22885379195213318, + 0.21135295927524567, + 0.01846795529127121, + 0.09615090489387512, + 0.13404689729213715, + -0.013243069872260094, + -0.10804439336061478, + -0.07252001762390137, + 0.08882910013198853, + -0.05820072069764137, + -0.027983849868178368, + -0.1459638774394989, + 0.031471334397792816, + -0.020571263507008553, + 0.0678873062133789, + 0.02832219935953617, + 0.08901669085025787, + 0.05193544551730156, + -0.00212711188942194, + 0.019466858357191086, + -0.2799759805202484, + 0.027787618339061737, + 0.04066411033272743, + 0.0695410743355751, + 0.11386518180370331, + -0.01207781583070755, + -0.07779944688081741, + -0.05497089400887489, + -0.010085235349833965, + 0.03456110134720802, + -0.004409261979162693, + 0.18323655426502228, + 0.1590157151222229, + -0.14985722303390503, + 0.05943793058395386, + 0.03351476415991783, + -0.03505415841937065, + 0.0415654256939888, + 0.15158125758171082, + -0.10378233343362808, + 0.06679604947566986, + 0.03420516103506088, + -0.19503338634967804, + -0.019222533330321312, + 0.054617252200841904, + 0.07456890493631363, + -0.3173748254776001, + 0.02183847874403, + 0.0011425345437601209, + -0.07108188420534134, + 0.07732230424880981, + -0.015918271616101265, + 0.014112315140664577, + -0.020890481770038605, + -0.018968237563967705, + -0.1597152203321457, + 0.1506420075893402, + -0.06922784447669983, + 0.06597818434238434, + -0.02436762861907482, + -0.031067606061697006, + 0.01890355534851551, + 0.06144223362207413, + -0.22258806228637695, + -0.048629410564899445, + -0.30330151319503784, + -0.12608279287815094, + -0.2021186500787735, + -0.09767087548971176, + 0.06845001131296158, + 0.13384124636650085, + 0.02682410553097725, + 0.07685317099094391, + -0.03651538863778114, + 0.02496396005153656, + -0.0644657090306282, + -0.030506810173392296, + 0.06659489125013351, + -0.008992202579975128, + 0.06352001428604126, + -0.05934641510248184, + -0.23099754750728607, + 0.025136062875390053, + -0.28503480553627014, + -0.04087712988257408, + -0.0946996808052063, + 0.1775009036064148, + 0.04337351396679878, + 0.07568489760160446, + -0.001892865402624011, + -0.032457925379276276, + -0.12819498777389526, + 0.12104827165603638, + 0.1855820119380951, + 0.2587057054042816, + -0.16338424384593964, + -0.05892932415008545, + 0.0708334892988205, + 0.08962573111057281, + -0.29585233330726624, + -0.11862656474113464, + 0.047787055373191833, + -0.32008400559425354 + ], + [ + -0.2844700217247009, + -0.028808237984776497, + 0.013639555312693119, + -0.010822360403835773, + 0.3727062940597534, + -0.06374730169773102, + -0.14647674560546875, + 0.03837158530950546, + -0.048870787024497986, + 0.06730469316244125, + 0.37642326951026917, + 0.14971010386943817, + -0.5286074876785278, + -0.05259702354669571, + 0.028521763160824776, + 0.06119704619050026, + -0.07305024564266205, + 0.045818910002708435, + 0.022086089476943016, + -0.3048144280910492, + 0.30507394671440125, + -0.0393938384950161, + -0.10125887393951416, + -0.0033735898323357105, + 0.049415234476327896, + -0.12388786673545837, + -0.3920873701572418, + 0.055220216512680054, + 0.037233490496873856, + 0.09767460078001022, + 0.027545401826500893, + -0.04818166047334671, + -0.1870957612991333, + -0.5076246857643127, + -0.18586668372154236, + 0.0022905725054442883, + 0.0853985920548439, + 0.06285956501960754, + -0.2037002295255661, + 0.03555610030889511, + 0.11115644872188568, + -0.41570237278938293, + 0.37359389662742615, + -0.05954284220933914, + 0.0063908835873007774, + -0.02368737943470478, + 0.3449402451515198, + 0.06068592146039009, + -0.16658543050289154, + -0.14561904966831207, + -0.16373613476753235, + -0.014388415031135082, + 0.11174788326025009, + -0.21835042536258698, + 0.06988918036222458, + 0.1490606814622879, + 0.15180593729019165, + 0.03899169713258743, + 0.022720851004123688, + -0.08441338688135147, + -0.1744716465473175, + 0.22738522291183472, + 0.07596806436777115, + 0.21617017686367035, + 0.5185579657554626, + -0.4741838276386261, + -0.15975862741470337, + -0.008101433515548706, + -0.08675838261842728, + -0.8212230205535889, + -0.2277543991804123, + 0.12778499722480774, + -0.7383280396461487, + -0.08339580148458481, + 0.08745130151510239, + 0.17725242674350739, + 0.033963073045015335, + 0.3018995523452759, + 0.00753203546628356, + -0.20856428146362305, + 0.07117456942796707, + -0.16139069199562073, + 0.04776102304458618, + 0.051256176084280014, + -0.23810303211212158, + 0.034527070820331573, + 0.027872618287801743, + -0.037180040031671524, + 0.009545322507619858, + 0.06901907920837402, + 0.4433479309082031, + 0.12801507115364075, + -0.2932189702987671, + -0.2749660015106201, + 0.33123689889907837, + 0.05365666002035141, + -0.16338683664798737, + 0.04374559968709946, + -0.1003754660487175, + 0.019461067393422127, + -0.14797253906726837, + 0.0756947249174118, + -0.4872519075870514, + -0.04216231405735016, + -0.2900966703891754, + -0.238920196890831, + -0.035728029906749725, + -0.23591332137584686, + 0.05014707148075104, + -0.07696206122636795, + -0.046538468450307846, + -0.11927056312561035, + 0.21928070485591888, + 0.06161428242921829, + -0.10891838371753693, + -0.2489786148071289, + 0.027256693691015244, + -0.19853946566581726, + -0.03578636422753334, + 0.03161167725920677, + -0.026132851839065552, + -0.017653927206993103, + -0.5730936527252197, + 0.08169857412576675, + -0.08903428167104721, + 0.08605242520570755, + 0.31881317496299744, + 0.15935097634792328 + ], + [ + -0.10992591828107834, + 0.06294616311788559, + 0.05293181911110878, + -0.1327795833349228, + -0.03389513120055199, + 0.06948719173669815, + 0.011268391273915768, + 0.0595281682908535, + 0.18986991047859192, + 0.22330674529075623, + -0.24599291384220123, + -0.16066612303256989, + 0.18291611969470978, + 0.011524345725774765, + -0.5592898726463318, + -0.15485915541648865, + 0.20170776546001434, + 0.07820887118577957, + -0.15635612607002258, + 0.02177715301513672, + 0.02772601880133152, + 0.1269187182188034, + -0.2965095341205597, + -0.1724909543991089, + 0.07977760583162308, + 0.17806535959243774, + -0.4216062128543854, + -0.05006338655948639, + 0.09380391240119934, + -0.18199864029884338, + 0.035103652626276016, + 0.016501834616065025, + -0.1600726693868637, + 0.17796823382377625, + 0.07156526297330856, + -0.2404380440711975, + 0.11371267586946487, + -0.006292153149843216, + 0.17945024371147156, + -0.2574010193347931, + -0.2365780472755432, + 0.04174041748046875, + 0.13886816799640656, + -0.10833429545164108, + -1.1836513294838369e-05, + -0.0591987706720829, + 0.0377790629863739, + 0.048982683569192886, + 0.2529616057872772, + 0.15419423580169678, + -0.15173865854740143, + 0.018042374402284622, + -0.003924254328012466, + 0.15501324832439423, + 0.024113643914461136, + 0.025774510577321053, + 0.2061021775007248, + 0.11098195612430573, + -0.2087148278951645, + 0.1666935831308365, + 0.23932932317256927, + 0.21524085104465485, + 0.17176087200641632, + 0.06099153310060501, + 0.1293404996395111, + 0.17875409126281738, + -0.5659262537956238, + 0.1147182509303093, + -0.08159805089235306, + -0.31950339674949646, + -0.27345433831214905, + -0.025521965697407722, + -0.363013356924057, + -0.05234351009130478, + 0.06978554278612137, + 0.02640795335173607, + -0.0023570405319333076, + 0.08333086222410202, + -0.05796732008457184, + -0.08398536592721939, + -0.16811326146125793, + -0.23297640681266785, + 0.02300517074763775, + -0.1551523506641388, + 0.2743947207927704, + -0.3655557334423065, + 0.05276399478316307, + -0.07430847734212875, + 0.001714037382043898, + -0.04971063882112503, + 0.1742926687002182, + -0.22748109698295593, + -0.34282293915748596, + -0.020815838128328323, + -0.14725050330162048, + 0.011130235157907009, + -0.2401459813117981, + 0.3138495683670044, + -0.5373295545578003, + 0.0023453361354768276, + -0.010622920468449593, + -0.16125701367855072, + -0.178779736161232, + -0.11946995556354523, + -0.055056650191545486, + -0.1897992193698883, + -0.42015761137008667, + 0.31870734691619873, + -0.08792531490325928, + 0.07127426564693451, + -0.006108525674790144, + 0.15865501761436462, + -0.3788885176181793, + -0.04802878573536873, + 0.04939616844058037, + 0.38097894191741943, + 0.09732647985219955, + 0.038004789501428604, + -0.5386629104614258, + 0.09361781924962997, + -0.3959081768989563, + 0.14170975983142853, + 0.24393858015537262, + -0.05158548429608345, + -0.35483941435813904, + 0.08243720233440399, + 0.4355694353580475, + 0.017935259267687798 + ], + [ + -0.09504737704992294, + 0.03814321756362915, + 0.19251589477062225, + -0.07069138437509537, + -0.11735629290342331, + 0.10534565895795822, + -0.10165545344352722, + -0.14102940261363983, + -0.13226591050624847, + 0.007005801424384117, + 0.02985970489680767, + 0.07936964184045792, + -0.6756815314292908, + -0.004462141543626785, + -0.003328886814415455, + -0.054057441651821136, + 0.3961148262023926, + 0.031333133578300476, + 0.3419254422187805, + -1.5545575618743896, + -1.8073573112487793, + 0.04835018888115883, + -0.06621672213077545, + 0.0654698982834816, + 0.11029107868671417, + -0.15954026579856873, + 0.09718896448612213, + 0.01058522891253233, + -0.015305875800549984, + 0.11794907599687576, + -0.07894407212734222, + -0.1756177693605423, + -0.28834086656570435, + -0.06660226732492447, + 0.12221450358629227, + -0.032818201929330826, + -0.5335270762443542, + -0.4405561685562134, + 0.2446363866329193, + -0.08983560651540756, + -0.0665493905544281, + 0.21899861097335815, + 0.1774977147579193, + 0.10739301890134811, + 0.054015152156353, + 0.07766268402338028, + 0.09007930010557175, + -0.11462567746639252, + 0.23740418255329132, + 0.16818732023239136, + 0.08789998292922974, + 0.05478785187005997, + 0.01801237463951111, + 0.20310476422309875, + -0.1059441938996315, + 0.05987584590911865, + -0.3295327126979828, + 0.035538241267204285, + 0.21564818918704987, + -0.5653443336486816, + -0.7986452579498291, + 0.03291596099734306, + 0.1638323962688446, + -0.5297175645828247, + -0.022905895486474037, + 0.0434148870408535, + 0.15279027819633484, + 0.013314558193087578, + -0.07382505387067795, + 0.2002612054347992, + -0.21723191440105438, + -0.12464546412229538, + 0.04300158843398094, + -0.06718987226486206, + 0.11162332445383072, + 0.034300245344638824, + 0.02470703423023224, + 0.10004332661628723, + -0.2567947804927826, + -0.9704762697219849, + 0.04648102819919586, + 0.1361691802740097, + -0.07546725124120712, + 0.9300634264945984, + -0.33756014704704285, + 0.03787078708410263, + -0.01069867704063654, + 0.25141775608062744, + -0.1922580748796463, + -0.10937047004699707, + 0.22346897423267365, + -0.08307121694087982, + -0.844054102897644, + -0.036981116980314255, + -1.2671045064926147, + -0.14072991907596588, + -0.4736660122871399, + -0.047189705073833466, + 0.040674589574337006, + -0.2579832077026367, + 0.09915654361248016, + -0.43463006615638733, + -0.14166904985904694, + -0.7018392086029053, + 0.5571130514144897, + 0.05528317019343376, + 0.046841010451316833, + -0.01242025475949049, + -0.09942496567964554, + 0.5649347901344299, + -0.20540086925029755, + 0.24493055045604706, + -0.04554209113121033, + -0.13907596468925476, + -0.18828994035720825, + -0.09154419600963593, + -0.09863393753767014, + 0.0848577618598938, + -0.04180509224534035, + 0.0427023246884346, + -0.1330210566520691, + -0.20735061168670654, + -0.4460740089416504, + 0.35854384303092957, + 0.350202351808548, + 0.3085081875324249, + 0.09496332705020905, + -0.3187117874622345 + ], + [ + 0.00826207920908928, + -0.17036442458629608, + 0.16956280171871185, + 0.05665018409490585, + -0.41616660356521606, + -0.02198522910475731, + 0.1317703276872635, + 0.02359050139784813, + 0.18789348006248474, + 0.11013378202915192, + 0.25434064865112305, + -0.029478469863533974, + 0.4354928433895111, + -0.13719771802425385, + -0.04760434478521347, + -0.05107756704092026, + 0.03623955324292183, + 0.06056300550699234, + 0.0009544981294311583, + -0.3473834693431854, + -0.7478641271591187, + 0.1584187150001526, + -0.12803016602993011, + 0.049315162003040314, + 0.08214504271745682, + 0.03281671181321144, + 0.009988303296267986, + 0.14750882983207703, + -0.1401960402727127, + -0.019428541883826256, + -0.14678555727005005, + 0.241935133934021, + -0.2653272747993469, + 0.18951725959777832, + -0.15260407328605652, + -0.27242225408554077, + -0.27575019001960754, + 0.184384286403656, + -0.20168530941009521, + -0.17849569022655487, + -0.009748085401952267, + -0.09181280434131622, + -0.09598128497600555, + -0.11298323422670364, + -0.008697286248207092, + -0.3363116979598999, + -0.0323861688375473, + 0.14833243191242218, + 0.12453241646289825, + -0.41279691457748413, + -0.08180126547813416, + -0.030452042818069458, + 0.016901595517992973, + 0.153997540473938, + -0.07433262467384338, + 0.11704505980014801, + 0.2565838396549225, + 0.1644185334444046, + 0.1330583095550537, + -0.10123724490404129, + -0.3928241729736328, + 0.18613408505916595, + -0.012530628591775894, + -0.09419838339090347, + 0.1788119673728943, + 0.2084224671125412, + 0.19888044893741608, + -0.05791282281279564, + 0.010141042992472649, + 0.09553244709968567, + 0.20334787666797638, + 0.03826738893985748, + 0.13083937764167786, + 0.00025820991140790284, + -0.27196016907691956, + 0.09734280407428741, + 0.16093650460243225, + 0.08955198526382446, + -0.04222914204001427, + -0.08410514146089554, + -0.05284685268998146, + 0.013127036392688751, + -0.046610213816165924, + -0.25042954087257385, + -0.051401056349277496, + 0.17128968238830566, + 0.051585063338279724, + 0.16510359942913055, + 0.12969690561294556, + 0.08426947146654129, + 0.13037504255771637, + 0.14512485265731812, + -1.022443413734436, + 0.15745048224925995, + 0.0038077698554843664, + -0.06111006438732147, + -0.07440412044525146, + 0.1388392299413681, + 0.19755159318447113, + -0.015021322295069695, + -0.028497468680143356, + 0.00849470216780901, + -0.0029779034666717052, + 0.005381310824304819, + -0.17882679402828217, + 0.049252431839704514, + 0.07118367403745651, + -0.13490457832813263, + -0.12662304937839508, + 0.003441554494202137, + -0.08777172863483429, + -0.0779477059841156, + 0.22539277374744415, + -0.03571528196334839, + 0.01556404959410429, + 0.1042274758219719, + -0.05476674064993858, + -0.08992290496826172, + 0.03269844502210617, + 0.05469800531864166, + 0.06168848276138306, + -0.16720812022686005, + -0.06592715531587601, + -0.025193560868501663, + -0.07905664294958115, + -0.10791612416505814, + -0.05785524100065231, + 0.1371879130601883 + ], + [ + 0.07062950730323792, + 0.20408162474632263, + 0.10676775127649307, + -0.09831234812736511, + -0.08411221206188202, + 0.0037321457639336586, + 0.12322406470775604, + 0.03196702152490616, + 0.11803048104047775, + 0.10732411593198776, + 0.04050784930586815, + -0.056104060262441635, + 0.3319401443004608, + -0.016812102869153023, + 0.124602310359478, + -0.6146717071533203, + 0.23294201493263245, + 0.5760626196861267, + 0.24226708710193634, + 0.5344154834747314, + 0.07773078233003616, + 0.06783264875411987, + 0.019812947139143944, + 0.10175902396440506, + -0.4809674322605133, + -0.07688675075769424, + 0.17616894841194153, + 0.030312329530715942, + -0.08690944314002991, + 0.07149839401245117, + 0.04005451872944832, + 0.242025226354599, + -0.19808068871498108, + 0.03317704796791077, + -0.24886931478977203, + -0.07284005731344223, + -0.06297941505908966, + 0.18243791162967682, + -0.14910565316677094, + 0.06621099263429642, + -0.14458221197128296, + 0.026793446391820908, + -0.016177134588360786, + -0.24264724552631378, + -0.14334256947040558, + 0.14479684829711914, + 0.07383359968662262, + 0.20937541127204895, + 0.0029576176311820745, + 0.00993114523589611, + 0.14728277921676636, + -0.05470586568117142, + -0.21031762659549713, + 0.026293842121958733, + 0.021250493824481964, + 0.05948925390839577, + -0.17103122174739838, + 0.10194701701402664, + -0.08111989498138428, + -0.021941518411040306, + -0.2832324206829071, + -0.2518366575241089, + 0.14903129637241364, + -0.28011220693588257, + -0.1348002552986145, + -0.05931214243173599, + -0.08532548695802689, + 0.034552522003650665, + -0.03716877102851868, + 0.3281300961971283, + -0.04695723205804825, + 0.007571196183562279, + -0.08623506128787994, + -0.09671597182750702, + -0.16667070984840393, + -0.091814786195755, + 0.14025789499282837, + 0.20195411145687103, + -0.2612152695655823, + 0.028432732447981834, + -0.03167806565761566, + -0.01580817624926567, + 0.08364537358283997, + 0.018187755718827248, + 0.029392287135124207, + 0.020834816619753838, + -0.5257556438446045, + -0.13787657022476196, + 0.08300191164016724, + -0.01668335124850273, + -0.1032351478934288, + 0.505081295967102, + -0.4197598397731781, + -0.21764254570007324, + 0.04332650452852249, + -0.15630537271499634, + 0.0887550413608551, + -0.3400382101535797, + -0.5321564078330994, + -0.21906690299510956, + 0.05037899687886238, + -0.08185530453920364, + -0.06740721315145493, + -0.09015244990587234, + 0.20350199937820435, + 0.22322039306163788, + -0.001217774348333478, + 0.11935678869485855, + -0.3102191090583801, + -0.11313777416944504, + 0.05293910205364227, + 0.0059758019633591175, + -0.13472746312618256, + -0.10790900141000748, + 0.1166258379817009, + 0.3060673475265503, + -0.21816758811473846, + -0.008304454386234283, + 0.07889848947525024, + -0.22403965890407562, + -0.10326699167490005, + 0.18266434967517853, + -0.16318121552467346, + 0.14864173531532288, + 0.219834104180336, + -0.2441873550415039, + 0.20704297721385956, + 0.11781461536884308 + ], + [ + 0.039343640208244324, + -0.05528906360268593, + -0.031396299600601196, + 0.017721079289913177, + -0.005985046271234751, + -0.017162015661597252, + 0.09949550777673721, + 0.03307585418224335, + -0.03866153955459595, + 0.029979346320033073, + -0.055466294288635254, + -0.14494140446186066, + -0.0523548498749733, + -0.10776856541633606, + 0.09713703393936157, + 0.0074309539049863815, + 0.00520939938724041, + 0.020074084401130676, + -0.07243605703115463, + -0.09205352514982224, + 0.1729525923728943, + 0.026043778285384178, + -0.07654813677072525, + -0.08106978982686996, + 0.10888468474149704, + -0.0885794460773468, + -0.025223782286047935, + 0.0611095055937767, + 0.03899270296096802, + -0.16660411655902863, + -0.03989091143012047, + -0.11982592940330505, + 0.057563841342926025, + -0.05798552930355072, + 0.10797049105167389, + -0.03592795506119728, + -0.09989313036203384, + 0.014134765602648258, + 0.042946986854076385, + 0.026015210896730423, + 0.022193163633346558, + -0.006772022694349289, + 0.15258754789829254, + -0.024887550622224808, + 0.10453978925943375, + -0.07259868830442429, + -0.06710069626569748, + -0.050422195345163345, + -0.14139164984226227, + 0.005072456318885088, + 0.19964133203029633, + -0.15547586977481842, + -0.052428994327783585, + 0.00892260018736124, + 0.07510624825954437, + 0.09770054370164871, + -0.026861442252993584, + -0.09976668655872345, + 0.07974722236394882, + -0.009952819906175137, + 0.0021986605133861303, + 0.10029482841491699, + 0.05973726883530617, + -0.028586866334080696, + 0.028565963730216026, + 0.161594420671463, + -0.2518689036369324, + -0.21201564371585846, + 0.06565915048122406, + -0.025195157155394554, + 0.07735827565193176, + 0.050215210765600204, + -0.10829400271177292, + 0.030763404443860054, + -0.043440598994493484, + 0.07601039111614227, + -0.049494218081235886, + -0.008901811204850674, + 0.07696651667356491, + 0.04403172433376312, + -0.021207008510828018, + 0.14552077651023865, + 0.03201063349843025, + 0.0069293431006371975, + -0.057520363479852676, + 0.06712079048156738, + -0.0427376851439476, + 0.01910516992211342, + -0.095307856798172, + 0.11301161348819733, + 0.024013131856918335, + 0.07118772715330124, + 0.07968463003635406, + -0.039409007877111435, + 0.11455775797367096, + -0.11815192550420761, + 0.008788729086518288, + 0.031139807775616646, + -0.1287432312965393, + -0.030268067494034767, + -0.04805348441004753, + -0.11988190561532974, + -0.034271273761987686, + -0.04182395711541176, + 0.004398145712912083, + -0.08602117002010345, + 0.08337955176830292, + -0.046409107744693756, + 0.058283381164073944, + 0.08603472262620926, + 0.0003876144182868302, + 0.018957020714879036, + 0.0010602043475955725, + 0.05096057057380676, + 0.05283350497484207, + 0.080174021422863, + 0.12086799740791321, + -0.026586215943098068, + -0.056771911680698395, + 0.08281164616346359, + 0.009452825412154198, + -0.016265960410237312, + 0.05491306260228157, + -0.00451164785772562, + -0.022449523210525513, + 0.006326174363493919, + -0.047152865678071976, + -0.0791773721575737 + ], + [ + -0.02680901437997818, + 0.04456852376461029, + 0.04995998367667198, + -0.01953991688787937, + -0.03530638664960861, + 0.04610345512628555, + 0.04165656864643097, + 0.05606025829911232, + -0.00023522402625530958, + -0.08275459706783295, + 9.005246101878583e-05, + -0.05516372248530388, + 0.04659361019730568, + -0.05841856449842453, + 0.04125639796257019, + -0.0064588868990540504, + 0.024989528581500053, + 0.08228730410337448, + 0.11557576805353165, + 0.05530920997262001, + 0.0785900205373764, + 0.04960399866104126, + 0.02664068527519703, + 0.11722312867641449, + 0.07923530042171478, + -0.18454985320568085, + -0.017304088920354843, + 0.031935133039951324, + 0.06235995143651962, + -0.01837725006043911, + -0.06213510408997536, + -0.13798704743385315, + -0.08927779644727707, + 0.04174543917179108, + 0.02865150012075901, + -0.00044752334360964596, + -0.09941127151250839, + 0.03495108708739281, + 0.14351266622543335, + -0.008367171511054039, + 0.1453353613615036, + 0.02690790966153145, + 0.0007977214409038424, + 0.07328478246927261, + -0.012772386893630028, + -0.004093134310096502, + -0.056104037910699844, + 0.06982901692390442, + -0.027725908905267715, + 0.001962026348337531, + -0.13644294440746307, + 0.07331959158182144, + -0.06869703531265259, + 0.02389492839574814, + -0.017539825290441513, + -0.0810656026005745, + -0.01983054168522358, + 0.0005192008684389293, + 0.04733427241444588, + 0.023226935416460037, + -0.034762416034936905, + -0.017720580101013184, + -0.002586296759545803, + -0.02743210643529892, + 0.14236494898796082, + 0.06611646711826324, + -0.011383340694010258, + -0.18151947855949402, + -0.009386081248521805, + 0.03433949127793312, + 0.08333136141300201, + 0.07886802405118942, + 0.08572962135076523, + 0.046896569430828094, + 0.00688902148976922, + 0.1124100312590599, + -0.04512825608253479, + -0.010578319430351257, + 0.07748563587665558, + -0.05894193798303604, + -0.01141420193016529, + 0.1266210973262787, + -0.04588965326547623, + -0.050025518983602524, + 0.06369630247354507, + 0.06437646597623825, + -0.02933298982679844, + 0.09911200404167175, + -0.04039739444851875, + -0.006226087920367718, + -0.15052561461925507, + -0.010537318885326385, + 0.018890703096985817, + 0.06434519588947296, + -0.00482111144810915, + -0.056040678173303604, + 0.02842571958899498, + -0.05419834330677986, + 0.09007715433835983, + -0.008179510943591595, + 0.0007091247825883329, + -0.07366126030683517, + 0.11279283463954926, + -0.03337278962135315, + 0.07566022872924805, + 0.07144822180271149, + -0.00028278198442421854, + -0.0710919201374054, + -0.031133705750107765, + -0.027944492176175117, + 0.050594083964824677, + 0.10429167747497559, + 0.0698527842760086, + -0.07157225161790848, + 0.11084455251693726, + 0.024769946932792664, + -0.17032736539840698, + 0.09184965491294861, + -0.028921226039528847, + -0.06164158508181572, + -0.07758671790361404, + -0.003178437938913703, + 0.037643540650606155, + 0.07231781631708145, + -0.10061667114496231, + -0.0186628308147192, + 0.03188207000494003, + -0.012063825502991676 + ], + [ + 0.04184660315513611, + 0.12218637764453888, + 0.187053844332695, + -0.14552491903305054, + 0.07416565716266632, + -0.3564668893814087, + -0.04529449716210365, + -0.024167131632566452, + 0.04651229828596115, + -0.2713988423347473, + -0.12948209047317505, + -0.024510860443115234, + -0.33198797702789307, + 0.12814971804618835, + 0.006487732753157616, + 0.08312752097845078, + -0.03629077225923538, + -0.2233487069606781, + 0.11056563258171082, + 0.11516036838293076, + -0.41133424639701843, + -0.04917778819799423, + -0.005886328872293234, + -0.17711596190929413, + 0.07526475936174393, + -0.20924986898899078, + 0.2977084219455719, + 0.01789751462638378, + 0.07090257108211517, + -0.08760331571102142, + 0.09887681901454926, + -0.06877495348453522, + -0.21875838935375214, + 0.15971097350120544, + 0.026184700429439545, + 0.0031243874691426754, + 0.1127190813422203, + -0.035306379199028015, + 0.07345717400312424, + 0.027339741587638855, + -0.0036064202431589365, + 0.13626790046691895, + -0.11920525878667831, + 0.008525659330189228, + -0.039921585470438004, + -0.003908854443579912, + -0.10399197041988373, + 0.10498303174972534, + -0.1116369366645813, + 0.23612679541110992, + 0.0932052880525589, + 0.1789865642786026, + 0.043144144117832184, + 0.04160340875387192, + 0.12297949939966202, + -0.1065034344792366, + 0.038646407425403595, + 0.036563217639923096, + -0.10904667526483536, + 0.02541879191994667, + -0.3490541875362396, + 0.18042968213558197, + -0.00980855617672205, + -0.08009811490774155, + -0.0907079353928566, + -0.4989135265350342, + 0.18854890763759613, + 0.0683874785900116, + -0.40825873613357544, + 0.002873203018680215, + 0.15703071653842926, + -0.38055479526519775, + -0.122237928211689, + 0.019502032548189163, + -0.132590651512146, + 0.2942887544631958, + 0.0025757476687431335, + -0.12440188974142075, + -0.04862651973962784, + -0.010301540605723858, + 0.21414688229560852, + -0.23572352528572083, + 0.09659972041845322, + 0.07314793020486832, + 0.005589250009506941, + -0.06849091500043869, + 0.10665397346019745, + -0.06187494471669197, + -0.19952359795570374, + -0.2216297686100006, + 0.030555931851267815, + -0.12020791321992874, + -0.19082389771938324, + 0.04788019508123398, + 0.12246611714363098, + 0.10159742832183838, + -0.16049636900424957, + -0.07396385073661804, + -0.012247608043253422, + -0.10770740360021591, + 0.18443670868873596, + -0.08341501653194427, + 0.046902503818273544, + 0.0206928551197052, + -0.015642035752534866, + -0.29878559708595276, + 0.08711623400449753, + -0.06448405981063843, + -0.029813252389431, + 0.11123139411211014, + 0.05302610993385315, + 0.1958516538143158, + 0.02174725942313671, + 0.09459438920021057, + -0.10891860723495483, + -0.8298431038856506, + -0.010869813151657581, + 0.029071323573589325, + -0.234670490026474, + 0.19867867231369019, + 0.2801978886127472, + -0.19348283112049103, + -0.22788499295711517, + 0.19306322932243347, + -0.021936122328042984, + 0.12827585637569427, + 0.27825435996055603, + 0.11344289779663086 + ], + [ + 0.06816712021827698, + 0.1669611632823944, + 0.14921796321868896, + -0.9147858619689941, + -0.005651181563735008, + 0.03556949645280838, + -0.4703153967857361, + 0.10316535085439682, + 0.10405445098876953, + 0.15853941440582275, + 0.2545525133609772, + -0.03948155418038368, + 0.24576494097709656, + -0.21635200083255768, + -0.2729499936103821, + 0.10008502751588821, + -0.025241227820515633, + 0.7085394859313965, + -0.18546108901500702, + -0.6256080865859985, + -0.18095125257968903, + 0.16116711497306824, + -0.09507877379655838, + -0.037821780890226364, + -0.15982376039028168, + -0.27025073766708374, + -0.019091535359621048, + -0.19568999111652374, + -0.09716746211051941, + -0.03246719390153885, + 0.22099968791007996, + -0.4411758482456207, + -0.059055209159851074, + -0.05856786668300629, + -0.28962332010269165, + -0.15297867357730865, + -0.11611264944076538, + -0.08864841610193253, + 0.04635544866323471, + 0.15925022959709167, + -0.32793307304382324, + -0.07744668424129486, + -0.04677401855587959, + -0.11061180382966995, + -0.8263669610023499, + -0.16805681586265564, + 0.4758889377117157, + 0.3626023828983307, + 0.17875823378562927, + -0.1819436401128769, + -0.20459015667438507, + 0.12811842560768127, + 0.08237505704164505, + 0.0014551745261996984, + -0.2037612944841385, + 0.08784633129835129, + 0.1692775934934616, + 0.09130627661943436, + -0.07132097333669662, + -0.09378860890865326, + -0.08448892086744308, + -0.10878828912973404, + -0.05419682711362839, + -0.2101709395647049, + 0.04264992102980614, + 0.0860786885023117, + 0.2168550044298172, + 0.0025893710553646088, + 0.01294972375035286, + -0.039180655032396317, + 0.1871774047613144, + 0.0033453505020588636, + -0.25099948048591614, + -0.2725472152233124, + -0.47173652052879333, + 0.2191726714372635, + -0.280659943819046, + -0.9304627776145935, + -0.355610728263855, + 0.013721353374421597, + -0.13586781919002533, + -0.23808583617210388, + 0.15713149309158325, + -0.06893289089202881, + -0.12002011388540268, + 0.15188805758953094, + 0.14597928524017334, + 0.20122715830802917, + 0.22376807034015656, + -0.013889013789594173, + 0.1933085024356842, + 0.35562169551849365, + 0.009428433142602444, + -0.2196909338235855, + -0.061648860573768616, + -0.1062820553779602, + 0.3600105345249176, + 0.13556723296642303, + -0.09912915527820587, + -0.7553201913833618, + 0.06891516596078873, + -0.09921479225158691, + 0.0920078232884407, + 0.07753375917673111, + -0.36023029685020447, + -0.3539535105228424, + -0.5030076503753662, + 0.027931228280067444, + 0.30297529697418213, + -0.06807316094636917, + 0.2104732245206833, + -0.42509859800338745, + 0.15896400809288025, + 0.191303089261055, + 0.11663074791431427, + 0.09342294186353683, + -0.14819182455539703, + 0.042659543454647064, + 0.024075442925095558, + 0.07538778334856033, + 0.007116188295185566, + 0.02837240882217884, + -0.17887137830257416, + -0.17289553582668304, + -0.20362181961536407, + 0.07188300043344498, + -0.06909217685461044, + -0.11693855375051498 + ], + [ + -0.03659050539135933, + -0.13951465487480164, + 0.09687329083681107, + -0.06617806851863861, + 0.05291234329342842, + 0.010775621980428696, + 0.031072478741407394, + -0.059151846915483475, + 0.05587488040328026, + -0.016148053109645844, + 0.14599159359931946, + 0.13420364260673523, + -0.17319637537002563, + -0.07856179773807526, + -0.08787840604782104, + -0.02872447855770588, + 0.1671314239501953, + -0.21409884095191956, + -0.02647704817354679, + -0.12452127039432526, + -0.040452685207128525, + 0.00316391303204, + -0.15746735036373138, + 0.0555284284055233, + -0.0029142077546566725, + -0.05270341783761978, + 0.1542748659849167, + -0.09783735126256943, + 0.13474851846694946, + 0.08430666476488113, + -0.06649921089410782, + -0.0761638730764389, + -0.10178010165691376, + -0.017518706619739532, + 0.043492089956998825, + 0.012107538059353828, + -0.11644679307937622, + 0.06603296846151352, + -0.15838061273097992, + 0.04585065692663193, + 0.0757729709148407, + -0.05224213749170303, + 0.04748325049877167, + 0.10953782498836517, + -0.010537451133131981, + -0.1867198348045349, + 0.05298299342393875, + -0.14793828129768372, + 0.1583813726902008, + 0.004034178797155619, + -0.035077355802059174, + -0.2747015655040741, + 0.06295948475599289, + 0.035236820578575134, + -0.1072302758693695, + 0.07239974290132523, + 0.2196403294801712, + 0.15601153671741486, + -0.07855222374200821, + -0.0054009114392101765, + -0.13757292926311493, + -0.04407311603426933, + -0.02955280803143978, + 0.05792798101902008, + 0.06891844421625137, + 0.011730763129889965, + 0.18097640573978424, + 0.041041020303964615, + -0.06945661455392838, + -0.04792236536741257, + -0.01661137118935585, + -0.0485394112765789, + 0.13165493309497833, + -0.043900687247514725, + 0.044644422829151154, + -0.004929292947053909, + -0.12835608422756195, + -0.28444793820381165, + -0.0342266708612442, + 0.07114441692829132, + -0.18344828486442566, + 0.026726121082901955, + -0.014703875407576561, + 0.0007623568526469171, + -0.12135091423988342, + 0.002192347077652812, + 0.10958565026521683, + 0.0843985453248024, + 0.006787269841879606, + -0.00619725463911891, + 0.16494961082935333, + -0.01603946089744568, + -0.221614271402359, + 0.0592527911067009, + 0.04577099531888962, + 0.10602207481861115, + -0.04550449922680855, + 0.08952442556619644, + 0.010127106681466103, + -0.23720663785934448, + -0.05248814448714256, + 0.040054965764284134, + -0.002014769008383155, + -0.07009302079677582, + -0.0706329345703125, + -0.11958833783864975, + 0.00848864670842886, + -0.06270486116409302, + 0.01732959970831871, + -0.07989004999399185, + 0.07793155312538147, + 0.025497352704405785, + -0.02718839980661869, + 0.046647511422634125, + -0.057425834238529205, + 0.031431831419467926, + -0.10488121211528778, + 0.14564234018325806, + 0.03156057000160217, + 0.15347854793071747, + -0.10059128701686859, + -0.018728919327259064, + 0.19918720424175262, + 0.22199216485023499, + -0.11636379361152649, + -0.1416979432106018, + 0.08755689114332199, + 0.1108296737074852 + ], + [ + 0.3273962140083313, + -0.09161362051963806, + -0.016535619273781776, + -0.08057495951652527, + -0.08731499314308167, + -0.49288415908813477, + 0.10627418011426926, + -0.021653974428772926, + -0.05700734257698059, + -0.0006110180984251201, + 0.14501552283763885, + -0.11893128603696823, + -1.6352616548538208, + 0.11249319463968277, + -0.08955124765634537, + -0.12836459279060364, + -0.39704203605651855, + 0.07206829637289047, + 0.1330057680606842, + 0.1498335748910904, + 0.14233633875846863, + -0.23813354969024658, + 0.03329815715551376, + -0.22015886008739471, + 0.028160570189356804, + -0.08306238055229187, + 0.3087393641471863, + -0.1009536013007164, + -0.15982866287231445, + -0.3260402977466583, + 0.06664086133241653, + 0.13112951815128326, + 0.1723480373620987, + 0.19691939651966095, + -0.3206341862678528, + -0.15886808931827545, + -0.04317602887749672, + 0.0054625170305371284, + -0.23887322843074799, + 0.12085366249084473, + -0.0029327047523111105, + 0.23097309470176697, + -0.1332031786441803, + -0.17394165694713593, + 0.16312849521636963, + 0.11935395747423172, + -0.05700867623090744, + 0.08909250050783157, + -0.09962256252765656, + 0.24343916773796082, + 0.08672957867383957, + 0.06748337298631668, + -0.1304151713848114, + -0.14422419667243958, + -0.05693608149886131, + -0.1031961590051651, + 0.08272887021303177, + 0.2645004391670227, + -0.15639743208885193, + 0.10261597484350204, + -0.00800550077110529, + 0.1001395508646965, + 0.1287437379360199, + -0.27271321415901184, + -0.0394158661365509, + 0.04946065694093704, + 0.11651189625263214, + -0.2161467969417572, + -0.2123977541923523, + 0.10775340348482132, + 0.2331734299659729, + -0.14402452111244202, + -0.003717035287991166, + -0.02789275534451008, + -0.11394866555929184, + 0.14234599471092224, + 0.03951741009950638, + -0.149675190448761, + -0.07605940848588943, + -0.0012004936579614878, + -0.2231462150812149, + -0.21617433428764343, + -0.0038362254854291677, + 0.018399015069007874, + 0.03414956480264664, + 0.03002401441335678, + -0.09034019708633423, + 0.1557830572128296, + -0.07291919738054276, + 0.043498411774635315, + -0.0919022262096405, + -0.08479759097099304, + -0.06574303656816483, + -0.004369721747934818, + 0.03062618151307106, + -0.11595692485570908, + 0.1092069000005722, + -0.07301148027181625, + 0.07405918091535568, + -0.24496181309223175, + -0.031301192939281464, + -0.18977706134319305, + 0.03765131160616875, + 0.023552514612674713, + -0.0013368577929213643, + -0.11323811113834381, + -0.15886826813220978, + 0.17266668379306793, + -0.11227794736623764, + 0.033062644302845, + 0.07064338028430939, + -0.5133976936340332, + 0.22012941539287567, + 0.10951317846775055, + -0.04302984103560448, + -0.27083370089530945, + 0.1438116431236267, + 0.254339337348938, + -0.016837691888213158, + 0.039612144231796265, + 0.21017570793628693, + -0.2714381217956543, + 0.0793779119849205, + -0.11445919424295425, + -0.3139689564704895, + -0.11331900209188461, + 0.17284204065799713, + -0.024502916261553764 + ], + [ + -0.002941761864349246, + -0.17560790479183197, + -0.1727912873029709, + 0.0512942299246788, + 0.021632233634591103, + 0.03146487846970558, + 0.03634396940469742, + -0.12041804194450378, + -0.05380634963512421, + -0.0481334924697876, + -0.06328552216291428, + -0.05689336732029915, + -0.09020183235406876, + -0.0912633165717125, + 0.10141853988170624, + -0.02628502808511257, + -0.11012677848339081, + -0.00534454733133316, + 0.05631250515580177, + -0.08152817189693451, + 0.032827720046043396, + -0.09533423185348511, + -0.04290631413459778, + 0.11135834455490112, + -0.02895382232964039, + -0.032064538449048996, + 0.005697549320757389, + 0.08756784349679947, + 0.07222053408622742, + -0.015418276190757751, + -0.1028999537229538, + -0.0008464159327559173, + 0.0511598102748394, + -0.05584995448589325, + -0.1956242024898529, + -0.04471680894494057, + -0.01566525362432003, + -0.06455346196889877, + -0.019685450941324234, + 0.06851392984390259, + -0.011926657520234585, + 0.044774651527404785, + 0.07288236916065216, + 0.10694516450166702, + 0.08736639469861984, + 0.1065722182393074, + 0.027580885216593742, + 0.058326590806245804, + -0.014792128466069698, + -0.06975778937339783, + 0.03901944309473038, + -0.004201431758701801, + 0.07069192081689835, + 0.10732332617044449, + -0.09596066921949387, + -0.0616946779191494, + 0.07092032581567764, + 0.007667285855859518, + 0.06712962687015533, + 0.026250364258885384, + 0.08537507057189941, + -0.0014958545798435807, + 0.14142964780330658, + -0.36776426434516907, + 0.025934189558029175, + 0.0898609459400177, + -0.026911459863185883, + 0.03633580729365349, + 0.05672615021467209, + -0.01920611783862114, + -0.1422828733921051, + 0.10418301075696945, + -0.09944427758455276, + 0.12206653505563736, + 0.03547729179263115, + 0.006640707608312368, + 0.12215108424425125, + -0.0810949057340622, + 0.05695689469575882, + 0.0006908435025252402, + -0.019783291965723038, + 0.053446974605321884, + -0.01720094308257103, + -0.035274285823106766, + 0.011445987969636917, + 0.10159288346767426, + -0.01854035072028637, + -0.08366621285676956, + -0.02086693048477173, + -0.013750730082392693, + 0.055133964866399765, + -0.050689373165369034, + 0.08766137063503265, + -0.027531201019883156, + 0.024648349732160568, + 0.011541386134922504, + 0.0587247833609581, + -0.08937090635299683, + 0.1690862625837326, + 0.1028122529387474, + 0.05885342136025429, + 0.00042149046203121543, + -0.007084849756211042, + -0.07005082815885544, + 0.08526858687400818, + -0.014345753006637096, + 0.11207114905118942, + -0.007661192212253809, + 0.006899328902363777, + -0.04091697558760643, + 0.12291263788938522, + 0.02519596740603447, + -0.28693461418151855, + -0.06446775048971176, + -0.08245190232992172, + 0.16229774057865143, + -0.07769344747066498, + 0.11632157117128372, + 0.015632063150405884, + 0.0038552326150238514, + 0.07665443420410156, + 0.030715472996234894, + 0.019107863306999207, + 0.13070039451122284, + -0.024515150114893913, + -0.01673641987144947, + 0.00985648948699236, + -0.09992681443691254 + ], + [ + 0.012301706708967686, + -0.1865394562482834, + 0.12609852850437164, + 0.055411916226148605, + 0.0803336575627327, + 0.08742652088403702, + 0.18860386312007904, + -0.012310086749494076, + 0.10441114008426666, + 0.017437050119042397, + -0.095530666410923, + 0.11900115013122559, + 0.13463552296161652, + -0.11324873566627502, + -0.1682630330324173, + -0.10532485693693161, + 0.005521008744835854, + -0.03265417367219925, + -0.19402135908603668, + -0.01461628545075655, + 0.015015332028269768, + 0.0034590386785566807, + -0.036276496946811676, + 0.017033472657203674, + -0.14103905856609344, + 0.010103310458362103, + -0.009501595981419086, + 0.0038069237489253283, + 0.03574692830443382, + 0.0362301766872406, + 0.34418052434921265, + -0.04488551989197731, + -0.10760709643363953, + 0.09666061401367188, + -0.05151355639100075, + -0.28953415155410767, + -0.013422392308712006, + 0.10261782258749008, + -0.04011663421988487, + -0.0059935650788247585, + -0.0334150567650795, + -0.08485536277294159, + 0.07634840160608292, + -0.0042059230618178844, + 0.006024524569511414, + -0.0859842449426651, + -0.020825102925300598, + 0.022956855595111847, + 0.07369617372751236, + 0.06279696524143219, + -0.06914542615413666, + -0.10594988614320755, + 0.03801831975579262, + -0.1473183035850525, + -0.026315605267882347, + -0.010381347499787807, + 0.13979850709438324, + 0.0016420113388448954, + -0.024791482836008072, + -0.39615005254745483, + 0.112028568983078, + -0.2427929937839508, + -0.020595915615558624, + 0.11579952389001846, + -0.025587081909179688, + 0.018722930923104286, + -0.03667612001299858, + -0.11392521113157272, + 0.05068076774477959, + 0.06799103319644928, + -0.1285630166530609, + 0.1358880251646042, + -0.06018054857850075, + -0.04503580555319786, + -0.014425945468246937, + -0.06896030902862549, + -0.08295333385467529, + -0.06906097382307053, + -0.002480648923665285, + -0.05059168487787247, + 0.07196693867444992, + 0.04057547450065613, + -0.25372809171676636, + 0.20481865108013153, + -0.02248345874249935, + 0.09514666348695755, + 0.061819419264793396, + 0.1796906441450119, + 0.15091896057128906, + 0.10154664516448975, + -0.019665617495775223, + -0.011019846424460411, + -0.3557998836040497, + -0.026316186413168907, + -0.16811715066432953, + -0.20851384103298187, + 0.07865449786186218, + -0.002151824999600649, + 0.0028705610893666744, + -0.1532393991947174, + 0.07377748191356659, + -0.1570206880569458, + 0.034638892859220505, + -0.18472060561180115, + -0.028331344947218895, + 0.014422374777495861, + -0.033516693860292435, + 0.14503003656864166, + -0.10672903060913086, + -0.2824220359325409, + 0.1804901659488678, + -0.08295020461082458, + -0.4120776653289795, + 0.07755880802869797, + 0.05911930650472641, + 0.2060115933418274, + -0.15597741305828094, + -0.0777733102440834, + -0.03537466749548912, + 0.11676358431577682, + -0.0782470852136612, + 0.02059067413210869, + 0.06511933356523514, + 0.033795636147260666, + -0.409798800945282, + -0.06085214018821716, + -0.19383125007152557, + -0.15302999317646027 + ], + [ + -0.0938999280333519, + -0.27419963479042053, + 0.019205676391720772, + 0.07053342461585999, + -0.2604018449783325, + 0.1117512658238411, + 0.01715139113366604, + -0.009433966130018234, + 0.22768385708332062, + -0.0046544563956558704, + 0.08446452766656876, + -0.006287071388214827, + -0.08807071298360825, + -0.17385461926460266, + 0.1414819210767746, + -0.15319262444972992, + 0.0921265259385109, + 0.00534857576712966, + 0.09465035051107407, + -0.01572393625974655, + -0.1698799431324005, + 0.12188563495874405, + -0.09742250293493271, + 0.09272810071706772, + 0.027977686375379562, + 0.04821936786174774, + -0.027223115786910057, + -0.05037993565201759, + -0.019360454753041267, + -0.026345131918787956, + -0.1666080504655838, + -0.05890616774559021, + -0.1733086109161377, + 0.17864570021629333, + -0.1510128527879715, + -0.16279946267604828, + -0.16970008611679077, + 0.07922007143497467, + 0.07036653906106949, + -0.11295412480831146, + 0.029837526381015778, + -0.04210599511861801, + 0.19662699103355408, + 0.07059518992900848, + -0.10013685375452042, + 0.01621786504983902, + 0.16942569613456726, + -0.13478527963161469, + 0.17179371416568756, + 0.0016305040335282683, + -0.18615034222602844, + -0.08935011923313141, + -0.07771198451519012, + 0.20252646505832672, + 0.029728464782238007, + 0.057170260697603226, + 0.08609101176261902, + 0.14518745243549347, + -0.22716273367404938, + -0.030635204166173935, + -0.2850937843322754, + -0.0418272502720356, + 0.055602654814720154, + 0.02186974510550499, + 0.08305298537015915, + 0.1656361222267151, + 0.1152043491601944, + 0.13334456086158752, + 0.11743920296430588, + -0.0701037272810936, + 0.056531842797994614, + -0.006760588847100735, + 0.10336834192276001, + -0.0037316898815333843, + -0.12610146403312683, + -0.00983942300081253, + 0.14811448752880096, + -0.25732919573783875, + -0.027645573019981384, + -0.04392676427960396, + -0.005481339525431395, + 0.07578736543655396, + -0.009134958498179913, + -0.018662868067622185, + -0.06836294382810593, + 0.04037229344248772, + -0.01782483235001564, + 0.13785170018672943, + -0.0684676468372345, + 0.02057214453816414, + 0.12203533947467804, + 0.10938757658004761, + -0.5271665453910828, + 0.007249460555613041, + -0.09394117444753647, + -0.06644684821367264, + -0.0042889011092484, + 0.1557435393333435, + 0.0386505052447319, + -0.21100133657455444, + -0.008710778318345547, + -0.13093531131744385, + -0.06953966617584229, + 0.12960286438465118, + -0.05580013617873192, + -0.31841203570365906, + -0.10488904267549515, + -0.03834470361471176, + -0.31248903274536133, + 0.07162126898765564, + 0.07143654674291611, + -0.05309257656335831, + 0.07239829748868942, + 0.014606340788304806, + -0.09377992153167725, + 0.13139231503009796, + -0.09192992746829987, + 0.07100644707679749, + 0.05311399698257446, + 0.04620150849223137, + 0.005889242514967918, + 0.0042155152186751366, + 0.16965703666210175, + 0.19761410355567932, + -0.19544734060764313, + 0.09606491029262543, + 0.09114355593919754, + 0.06094963103532791 + ], + [ + -0.04857373982667923, + -0.03849109634757042, + 0.06969689577817917, + 0.028014808893203735, + -0.09499533474445343, + -0.035254210233688354, + -0.007704808842390776, + 0.05658813193440437, + -0.0367467999458313, + 0.03566402569413185, + 0.0010474740993231535, + 0.08510836958885193, + -0.0056148492731153965, + 0.04331820085644722, + 0.023484928533434868, + -0.002087409608066082, + -0.00922251958400011, + -0.024757007136940956, + 0.031868599355220795, + 0.053568679839372635, + 0.03889710083603859, + -0.018381262198090553, + -0.028321273624897003, + 0.026673825457692146, + 0.004562058951705694, + -0.04059956967830658, + 0.03845967352390289, + -0.050379421561956406, + 0.0724017322063446, + -0.029374612495303154, + 0.058761246502399445, + 0.010827790945768356, + -0.08855433017015457, + -0.010343647561967373, + 0.09055010229349136, + -0.06187576800584793, + 0.019922679290175438, + 0.08703088760375977, + 0.06471351534128189, + 0.024686604738235474, + -0.042092062532901764, + -0.029733939096331596, + 0.04503351449966431, + 0.08669652044773102, + 0.004962591454386711, + 0.04258982092142105, + 0.0666874423623085, + 0.06931579858064651, + 0.01793333888053894, + -0.04032202810049057, + -0.03002457320690155, + 0.04944921284914017, + 0.06049646809697151, + -0.06458234786987305, + -0.0002273732388857752, + 0.03448314592242241, + 0.015497040934860706, + -0.003382178721949458, + -0.047499191015958786, + -0.01775788702070713, + 0.02234051376581192, + -0.04555080831050873, + -0.035131633281707764, + 0.0378398671746254, + 0.06430835276842117, + -0.006359244231134653, + 0.10617890954017639, + 0.029215866699814796, + -0.0015654778108000755, + 0.045697931200265884, + -0.025392994284629822, + -0.026006117463111877, + 0.016668027266860008, + 0.0831236019730568, + -0.020391790196299553, + -0.08402958512306213, + 0.03982952982187271, + 0.053645018488168716, + -0.05413355678319931, + -0.08806878328323364, + -0.033997561782598495, + 0.05065545439720154, + -0.10042889416217804, + -0.030840162187814713, + -0.048853352665901184, + 0.03035121224820614, + -0.05829929560422897, + 0.016506029292941093, + -0.0055850716307759285, + 0.035155922174453735, + -0.012790851294994354, + -0.038241684436798096, + -0.06887729465961456, + -0.014101521112024784, + -0.04021415486931801, + 0.034962836652994156, + -0.05878506973385811, + -0.008772820234298706, + -0.014887669123709202, + 0.0901917889714241, + 0.10429093986749649, + -0.03917063772678375, + 0.04592658206820488, + 0.017170170322060585, + -0.02180514857172966, + 0.00705066230148077, + -0.0707511156797409, + 0.026745392009615898, + -0.11656858026981354, + 0.027566585689783096, + -0.03350834548473358, + 0.08117581903934479, + 0.05941018462181091, + -0.03594072163105011, + -0.06056724488735199, + 0.027045859023928642, + 0.0011404918041080236, + -0.0402982197701931, + -0.040729038417339325, + 0.03455357998609543, + -0.01117018237709999, + -0.05750822648406029, + -0.0532941110432148, + 0.08046530187129974, + 0.01988973841071129, + -0.04923156648874283, + 0.010052274912595749, + -0.04115899279713631 + ], + [ + -0.21504570543766022, + 0.05922774598002434, + -0.02011696621775627, + -0.03572791814804077, + 0.05884544923901558, + 0.0037848150823265314, + -0.07371830940246582, + 0.06530626863241196, + 0.03066278249025345, + -0.0287181306630373, + 0.1852925419807434, + 0.198503777384758, + 0.02192637510597706, + -0.11467356979846954, + -0.3006402552127838, + 0.028979167342185974, + 0.23845845460891724, + -0.058809589594602585, + 0.07443368434906006, + 0.0677509605884552, + -0.2772158682346344, + 0.011608581990003586, + -0.018562927842140198, + -0.18733108043670654, + 0.06491424143314362, + -0.09906971454620361, + 0.04635152593255043, + 0.1464175134897232, + 0.09490124881267548, + 0.015987366437911987, + 0.10012131184339523, + 0.050205424427986145, + -0.058306898921728134, + 0.11841502040624619, + -0.13470256328582764, + -0.1124657541513443, + -0.17111343145370483, + -0.018373610451817513, + 0.06365609914064407, + 0.09608445316553116, + -0.024028535932302475, + -0.1384543627500534, + 0.11836809664964676, + -0.0689074844121933, + -0.07169156521558762, + -0.4090377390384674, + 0.07384063303470612, + 0.08389701694250107, + -0.05627517029643059, + 0.19364535808563232, + 0.1363641321659088, + -0.06754185259342194, + 0.02933334745466709, + -0.16464069485664368, + 0.173816978931427, + 0.19287432730197906, + 0.004006592556834221, + 0.011305280961096287, + -0.029288990423083305, + 0.023532943800091743, + 0.003494593547657132, + 0.14891336858272552, + 0.023311303928494453, + 0.018758604303002357, + 0.016131097450852394, + 0.180354043841362, + 0.31680530309677124, + -0.1067608892917633, + 0.0007411090773530304, + 0.03980400040745735, + 0.16938519477844238, + -0.09495928883552551, + -0.011301388964056969, + -0.07053639739751816, + -0.00807571318000555, + 0.013265821151435375, + -0.05106000974774361, + 0.05784609541296959, + -0.23246023058891296, + -0.04334859922528267, + -0.4260920584201813, + -0.12201161682605743, + -0.06362245976924896, + 0.11738409847021103, + -0.1367325484752655, + -0.08563429862260818, + 0.18923911452293396, + -0.06269470602273941, + -0.10687152296304703, + 0.06014932319521904, + -0.19487954676151276, + -0.004588622134178877, + -0.3437671661376953, + -0.11401098221540451, + -0.13649882376194, + -0.012418674305081367, + 0.00211523100733757, + 0.05406634137034416, + 0.06685840338468552, + -0.015446076169610023, + -0.028923094272613525, + -0.20431384444236755, + -0.05016496405005455, + 0.04133160784840584, + 0.03251435607671738, + 0.0379076786339283, + -0.3304142355918884, + -0.28439953923225403, + 0.11203805357217789, + 0.10238289833068848, + -0.0054287356324493885, + 0.05789750814437866, + 0.18755927681922913, + 0.02533848211169243, + 0.035066716372966766, + 0.25617852807044983, + -0.2021154910326004, + -0.15181373059749603, + 0.17469677329063416, + 0.2535836696624756, + -0.14478273689746857, + 0.030919602140784264, + 0.08175615966320038, + 0.1892169713973999, + -0.13211478292942047, + -0.05775774270296097, + 0.04185391962528229, + 0.012197066098451614 + ], + [ + 0.0481601282954216, + -0.061157625168561935, + -0.07369223237037659, + 0.08097724616527557, + 0.135075181722641, + 0.07527924329042435, + 0.10031740367412567, + 0.02513836696743965, + 0.0423596017062664, + 0.03949837386608124, + -0.17576013505458832, + -0.02332235686480999, + -0.223618283867836, + -0.15332050621509552, + -0.035081177949905396, + 0.07867221534252167, + 0.10515791922807693, + -0.050876684486866, + -0.07076418399810791, + -0.013631651178002357, + -0.006335076875984669, + -0.03343569487333298, + 0.10709754377603531, + 0.016737397760152817, + 0.018223071470856667, + 0.07643309980630875, + 0.02190515398979187, + 0.04133225604891777, + -0.19691814482212067, + -0.07665999233722687, + -0.037191737443208694, + 0.0022571012377738953, + 0.06433285772800446, + -0.20899134874343872, + -0.07328969985246658, + 0.0069082011468708515, + -0.19624947011470795, + 0.04462210461497307, + -0.06842995434999466, + 0.030763018876314163, + 0.08983220905065536, + 0.03618394955992699, + 0.14377981424331665, + -0.02812904492020607, + 0.0673510730266571, + -0.03778122738003731, + -0.1418229639530182, + 0.0016937657492235303, + -0.029143115505576134, + 0.06990602612495422, + -0.16983073949813843, + 0.030488718301057816, + 0.030644018203020096, + 0.030455900356173515, + 0.0815005749464035, + 0.11532893776893616, + -0.007322211749851704, + -0.0679083913564682, + 0.10911117494106293, + 0.027290139347314835, + 0.13626083731651306, + 0.05758583918213844, + 0.04673340171575546, + 0.010665379464626312, + 0.08507177233695984, + 0.01480881031602621, + -0.008938821032643318, + -0.0669914111495018, + 0.14718011021614075, + -0.016588330268859863, + -0.12196928262710571, + 0.01720470003783703, + -0.04117147997021675, + 0.010331826284527779, + -0.019578462466597557, + 0.186295747756958, + -0.14703603088855743, + -0.03544461354613304, + -0.027966372668743134, + -0.010804167948663235, + -0.046176113188266754, + 0.09422473609447479, + 0.07661699503660202, + -0.09928778558969498, + -0.04642200097441673, + 0.10033243894577026, + -0.112083300948143, + -0.013480034656822681, + 0.06421282142400742, + 0.0959801897406578, + -0.013292577117681503, + -0.03548587113618851, + 0.09605248272418976, + -0.04417917877435684, + 0.06012250855565071, + 0.03609582409262657, + 0.13920722901821136, + 0.007736777421087027, + -0.0633515864610672, + -0.13049522042274475, + 0.07335411012172699, + -0.006070197559893131, + 0.04053718224167824, + -0.016257785260677338, + -0.005418351385742426, + 0.06012902036309242, + 0.1325281411409378, + 0.04571286588907242, + 0.013450359925627708, + -0.15341529250144958, + -7.443396316375583e-05, + -0.025224803015589714, + -0.20845384895801544, + 0.06979311257600784, + -0.002930302871391177, + 0.10761827975511551, + 0.018619906157255173, + 0.12780888378620148, + -0.0044706715270876884, + 0.05954540893435478, + -0.05320749059319496, + 0.06436338275671005, + 0.10935541987419128, + -0.16422146558761597, + 0.1622447520494461, + 0.07779292017221451, + -0.0319506898522377, + 0.09894628077745438 + ], + [ + -0.07148068398237228, + 0.07186494022607803, + -0.010442853905260563, + -0.008349613286554813, + -0.019876906648278236, + 0.0410350002348423, + 0.05922868475317955, + 0.14955107867717743, + -0.06570306420326233, + -0.04596063867211342, + 0.04751412943005562, + 0.04496239870786667, + 0.055645011365413666, + 0.2189396172761917, + -0.4608803391456604, + -0.04176586866378784, + -0.20895196497440338, + -0.0834641307592392, + 0.19289976358413696, + 0.12221415340900421, + -0.11177659779787064, + 0.03295160084962845, + 0.007604540791362524, + -0.11836925148963928, + -0.03455140069127083, + 0.02368893101811409, + -0.12836124002933502, + 0.01528227049857378, + 0.02003459632396698, + 0.10498564690351486, + 0.038948800414800644, + -0.016857784241437912, + 0.008736181072890759, + 0.06447054445743561, + -0.011610905639827251, + -0.21396365761756897, + -0.005474238656461239, + 0.007985532283782959, + 0.03480236604809761, + 0.1628418117761612, + -0.17574210464954376, + -0.30855119228363037, + -0.05605355650186539, + -0.03087301179766655, + -0.04874779284000397, + -0.5468395948410034, + -0.08738940209150314, + 0.08347982913255692, + 0.07734224200248718, + 0.006236698478460312, + 0.051253318786621094, + 0.07475783675909042, + 0.07484301179647446, + -0.11922089755535126, + 0.05622325465083122, + 0.11985598504543304, + 0.13046856224536896, + 0.11870402097702026, + -0.028733275830745697, + 0.03219890594482422, + -0.12446126341819763, + 0.01687629148364067, + -0.06714317202568054, + -0.018795544281601906, + -0.05309830605983734, + 0.0482650026679039, + 0.33604496717453003, + -0.7169981598854065, + -0.1049315333366394, + 0.1490902304649353, + 0.046288859099149704, + -0.07006236165761948, + 0.014085550792515278, + -0.24071957170963287, + 0.01847449503839016, + -0.016695760190486908, + -0.06547152996063232, + -0.12886148691177368, + -0.20510074496269226, + -0.13619078695774078, + -0.4817722737789154, + 0.042510807514190674, + 0.07006017863750458, + 0.21541675925254822, + -0.0585038848221302, + -0.038775015622377396, + 0.06873449683189392, + -0.00852986890822649, + -0.22086603939533234, + -0.043372899293899536, + 0.16485595703125, + -0.063601553440094, + -0.11489581316709518, + -0.08277760446071625, + -0.06621631979942322, + -0.04715970158576965, + -0.018409235402941704, + 0.012982969172298908, + 0.04020953178405762, + 0.04542998969554901, + -0.153661847114563, + -0.13644255697727203, + -0.003173798555508256, + 0.019596440717577934, + -0.07433018088340759, + 0.04207202047109604, + -0.5339446663856506, + -0.7670915722846985, + -0.024869512766599655, + 0.08791644871234894, + -0.05324985086917877, + 0.24774332344532013, + 0.14828473329544067, + 0.10707584023475647, + 0.09689179807901382, + 0.25484010577201843, + -0.18472105264663696, + -0.22993801534175873, + 0.18809428811073303, + 0.25439026951789856, + -0.07040754705667496, + -0.14985765516757965, + 0.14617054164409637, + 0.20696593821048737, + -0.07132156193256378, + 0.02987966313958168, + 0.036686066538095474, + 0.04680192843079567 + ], + [ + -0.045244768261909485, + 0.3794306218624115, + -0.5548692345619202, + 0.05034590885043144, + 0.048649903386831284, + 0.06496565043926239, + -0.00964719895273447, + -0.1994377076625824, + -0.023586932569742203, + -0.0149984210729599, + 0.04819577559828758, + 0.1431717872619629, + 0.08683864772319794, + -0.1565379798412323, + -0.18077297508716583, + 0.1654280722141266, + -0.14776013791561127, + 0.11178776621818542, + -0.07661092281341553, + -0.27433404326438904, + -0.18727880716323853, + 0.0014032491017132998, + -0.305795818567276, + 0.22745664417743683, + -0.056286200881004333, + 0.08865141123533249, + -0.0031820929143577814, + -0.1378864347934723, + 0.061913520097732544, + -0.0007427640375681221, + 0.08155497163534164, + 0.1816399097442627, + -0.00010053376172436401, + 0.2519194483757019, + 0.1527346819639206, + 0.25380739569664, + -0.18240728974342346, + -0.05419950187206268, + -0.20322668552398682, + 0.2103080153465271, + -0.12257878482341766, + -0.41203320026397705, + 0.33740198612213135, + 0.1928902268409729, + -0.31111249327659607, + -0.3706647753715515, + -0.4959532916545868, + -0.009770525619387627, + 0.11780455708503723, + 0.11707772314548492, + -0.08167867362499237, + 0.15761719644069672, + 0.051057204604148865, + -1.8871092796325684, + -0.2801891565322876, + -0.14941871166229248, + 0.05634797364473343, + 0.10048209130764008, + 0.009442216716706753, + -0.7453246116638184, + 0.03072291798889637, + -0.14677654206752777, + 0.09057420492172241, + -0.25736552476882935, + -0.22930273413658142, + -0.2585945725440979, + -0.08632194995880127, + -0.07828109711408615, + 0.06109071150422096, + 0.5685810446739197, + -0.6471291184425354, + -0.001320009003393352, + 0.13091008365154266, + -0.21783024072647095, + -0.6317462921142578, + -0.10361555963754654, + 0.026986438781023026, + -0.1907915621995926, + -0.0012544798664748669, + -0.1997278928756714, + 0.2261386662721634, + -0.07971206307411194, + 0.12231193482875824, + -0.5927475094795227, + 0.1310444176197052, + 0.4252593517303467, + 0.13084083795547485, + -0.467151015996933, + -0.06053933873772621, + -0.023121381178498268, + -0.42861616611480713, + -0.19360394775867462, + -0.6459985971450806, + 0.02312956191599369, + 0.11011771857738495, + -0.09751708805561066, + 0.09812180697917938, + 0.1177060455083847, + -0.014891283586621284, + 0.11921200156211853, + 0.21737244725227356, + -0.06026836484670639, + 0.16511203348636627, + -0.5915645360946655, + 0.05968763306736946, + -0.07154273241758347, + 0.059874217957258224, + 0.16129709780216217, + -0.44610583782196045, + -0.7697670459747314, + 1.168225884437561, + 0.20315514504909515, + 0.10273709893226624, + 0.16441704332828522, + -0.36720362305641174, + 0.00735081173479557, + 0.08029545843601227, + 0.08387130498886108, + -0.025240475311875343, + -0.020988496020436287, + -0.034739598631858826, + -0.07675132900476456, + 0.16072934865951538, + -0.032829802483320236, + 0.15416762232780457, + -0.10919275879859924, + -0.06545328348875046, + 0.11909424513578415 + ], + [ + 0.26019036769866943, + 0.11595030128955841, + -0.10848525911569595, + -0.3267025649547577, + 0.02210596762597561, + 0.09513632208108902, + -0.11097539216279984, + 0.2019355595111847, + -0.0896754264831543, + 0.0035693824756890535, + 0.04895268380641937, + -0.17031319439411163, + 0.06324247270822525, + 0.0041732448153197765, + -0.07114367187023163, + 0.005061843432486057, + 0.017197104170918465, + 0.06674166023731232, + -0.14041705429553986, + -0.025357559323310852, + 0.12483485788106918, + 0.05927359685301781, + 0.03670748695731163, + -0.0863356813788414, + 0.0956553965806961, + -0.26146936416625977, + 0.08888775110244751, + -0.00026469488511793315, + -0.06712319701910019, + -0.2266860008239746, + -0.061417415738105774, + -0.3119480311870575, + -0.09606722742319107, + 0.0716165080666542, + -0.018835440278053284, + 0.006021848879754543, + -0.06163521856069565, + -0.03187023103237152, + 0.11367907375097275, + 0.10403319448232651, + -0.15596464276313782, + 0.22395950555801392, + -0.3441096842288971, + 0.039189115166664124, + -0.4713420569896698, + -0.4937548339366913, + -0.4649128317832947, + 0.14405784010887146, + -0.13395237922668457, + 0.15194503962993622, + -0.19589285552501678, + -0.18754814565181732, + 0.019327238202095032, + 0.164952352643013, + -0.16286931931972504, + -0.010999198071658611, + 0.03618307411670685, + 0.22050799429416656, + -0.007174741476774216, + -0.05980752408504486, + 0.008609647862613201, + 0.2892554700374603, + 0.10214044153690338, + -0.1269078552722931, + 0.06543951481580734, + 0.0558670237660408, + 0.06461811065673828, + -0.08891331404447556, + -0.06449297070503235, + 0.3327935039997101, + 0.14276254177093506, + -0.0323537178337574, + -0.08335665613412857, + 0.04784965515136719, + 0.0036938285920768976, + -0.030511386692523956, + 0.028179265558719635, + 0.005236541852355003, + 0.11425496637821198, + 0.08042522519826889, + -0.29813775420188904, + 0.20346635580062866, + -0.022333061322569847, + 0.12188902497291565, + 0.06478917598724365, + 0.19219474494457245, + -0.005255959928035736, + -0.25419145822525024, + -0.011332983151078224, + -0.24926044046878815, + -0.06341909617185593, + 0.10090446472167969, + -0.08999209105968475, + 0.05501149594783783, + -0.00772486999630928, + 0.007875523529946804, + -0.11819735914468765, + -0.5003669261932373, + -0.290856271982193, + 0.028249207884073257, + 0.15221290290355682, + 0.06040434539318085, + -0.006344416178762913, + -0.13502848148345947, + 0.004639197140932083, + 0.08973339200019836, + 0.20701734721660614, + 0.030042948201298714, + -0.2643253803253174, + -0.005589891225099564, + 0.20749905705451965, + -0.05041229724884033, + 0.06696310639381409, + 0.025624653324484825, + -0.0182812437415123, + 0.0950915515422821, + -0.14911925792694092, + 0.04179513081908226, + 0.07468367367982864, + -0.047000061720609665, + -0.14785103499889374, + -0.013155661523342133, + -0.11454398185014725, + 0.20982779562473297, + -0.46368056535720825, + -0.37305352091789246, + -0.3306875228881836, + 0.0024306117556989193 + ], + [ + 0.06568416953086853, + -0.2791063189506531, + -0.036109648644924164, + -0.24871473014354706, + -0.004380709026008844, + 0.028245078399777412, + 0.06450094282627106, + -0.11274652928113937, + 0.028940770775079727, + 0.24304020404815674, + 0.12306037545204163, + 0.0906049981713295, + -0.04793999716639519, + -0.19205230474472046, + -0.05486350134015083, + 0.05575813725590706, + 0.27927646040916443, + -0.013599712401628494, + 0.14765408635139465, + -0.00602763332426548, + -0.10279069095849991, + -0.2569059133529663, + -0.012026875279843807, + -0.06032100319862366, + 0.07522566616535187, + -0.1778370440006256, + -0.3169967830181122, + -0.010083133354783058, + 0.04046742618083954, + -0.06901399046182632, + -0.07242273539304733, + 0.28400030732154846, + 0.03306419029831886, + 0.11893516033887863, + 0.021151790395379066, + 0.05769673362374306, + -0.07792231440544128, + -0.08355291187763214, + 0.07937081903219223, + -0.0671556368470192, + -0.1223621591925621, + -0.2460363507270813, + -0.0191893819719553, + -0.002892374759539962, + -0.09775484353303909, + -0.0412384457886219, + 0.10504788905382156, + -0.002770237158983946, + 0.013135658577084541, + 0.1574126034975052, + -0.13231532275676727, + 0.05046677589416504, + 0.060082677751779556, + -0.047034986317157745, + -0.06718304008245468, + -0.0357745997607708, + -0.053773824125528336, + 0.05154358223080635, + 0.042201653122901917, + -0.07211587578058243, + -0.020290587097406387, + -0.08697854727506638, + 0.08975496143102646, + 0.06667235493659973, + 0.0836549699306488, + 0.10891953110694885, + 0.17768774926662445, + -0.1961899697780609, + -0.0478990264236927, + 0.18812167644500732, + -0.0759412869811058, + 0.05436466634273529, + -0.14461536705493927, + 0.1367407739162445, + -0.08236604183912277, + 0.05294358357787132, + -0.13463068008422852, + 0.14848092198371887, + 0.04666675627231598, + -0.18833109736442566, + 0.07076703011989594, + -0.05106228217482567, + 0.0720098689198494, + 0.10346229374408722, + -0.19683556258678436, + -0.11017608642578125, + -0.018820585682988167, + 0.10688534379005432, + -0.04455957189202309, + 0.09043433517217636, + -0.008447474800050259, + -0.066454216837883, + -0.06020648032426834, + -0.06622012704610825, + -0.009539714083075523, + 0.12273097038269043, + 0.01243171188980341, + -0.02869386598467827, + 0.2327074110507965, + 0.07600787281990051, + 0.15555445849895477, + 0.10994019359350204, + 0.01747923716902733, + 0.041420578956604004, + -0.006425689905881882, + 0.10728651285171509, + 0.3358995318412781, + 0.061728864908218384, + 0.08362453430891037, + -0.0410432294011116, + 0.10108890384435654, + 0.0632362812757492, + -0.3222469687461853, + -0.21193893253803253, + -0.038742795586586, + -0.07911123335361481, + 0.0005279518663883209, + -0.031995683908462524, + -0.021087072789669037, + -0.13993500173091888, + -0.02896016836166382, + -0.19180016219615936, + 0.13557052612304688, + -0.040076710283756256, + -0.06561626493930817, + -0.04387018084526062, + -0.25297486782073975, + 0.17140574753284454 + ], + [ + 0.027371276170015335, + -0.13096198439598083, + -0.07951635867357254, + -0.1488196849822998, + 0.11525813490152359, + -0.10636518895626068, + -0.03239048644900322, + -0.02812151052057743, + -0.2297532558441162, + -0.1372842937707901, + -0.3430788218975067, + -0.038291171193122864, + -0.1399669200181961, + 0.09292362630367279, + -0.4506731927394867, + 0.02217855118215084, + 0.08011295646429062, + 0.03123338706791401, + 0.027747957035899162, + -0.16811783611774445, + -0.24948477745056152, + -0.043181903660297394, + -0.08399442583322525, + -0.1344296783208847, + -0.008696860633790493, + -0.018243128433823586, + 0.2412552386522293, + 0.02011202834546566, + -0.08929028362035751, + -0.056643661111593246, + 0.2481500506401062, + 0.03610047698020935, + -0.03660980984568596, + -0.007111292332410812, + -0.2629711627960205, + -0.12061819434165955, + 0.0720292404294014, + -0.4117734432220459, + -0.18786503374576569, + -0.20081783831119537, + 0.0033546003978699446, + 0.1655818223953247, + 0.19283747673034668, + 0.12131034582853317, + 0.026933517307043076, + -0.2594786286354065, + 0.059094034135341644, + 0.017047058790922165, + -0.06538544595241547, + -0.002769551007077098, + 0.17899471521377563, + -0.030131665989756584, + -0.055845893919467926, + 0.21494482457637787, + 0.0074417185969650745, + -0.4000815451145172, + 0.16660891473293304, + 0.046350035816431046, + 0.10804317146539688, + 0.25608089566230774, + -0.10413290560245514, + 0.2648656368255615, + 0.09454682469367981, + 0.0649852305650711, + -0.29403719305992126, + -0.04386979714035988, + 0.028146469965577126, + -0.0018743532709777355, + -0.21021027863025665, + 0.08829940855503082, + 0.058079153299331665, + -0.11446014046669006, + -0.2953282594680786, + 0.1226501539349556, + -0.0030804111156612635, + 0.20721645653247833, + -0.08433102816343307, + -0.3549896478652954, + 0.3852939009666443, + 0.011533423326909542, + 0.07648332417011261, + -0.16790619492530823, + 0.011979663744568825, + -0.05175965651869774, + 0.23724019527435303, + -0.05560705065727234, + -0.12400376796722412, + -0.3023795485496521, + 0.16825580596923828, + -0.08474723249673843, + -0.5488306879997253, + -0.10006824880838394, + -1.1237157583236694, + 0.012089727446436882, + -0.0830613523721695, + 0.12209412455558777, + -0.11187675595283508, + 0.09405461698770523, + -0.17009775340557098, + 0.0821494609117508, + 0.048101555556058884, + 0.2580830156803131, + -0.017845511436462402, + -0.24564816057682037, + -0.19216756522655487, + -0.057188112288713455, + 0.13442067801952362, + 0.25046947598457336, + 0.09834036231040955, + 0.1933080554008484, + 0.29352837800979614, + 0.47784411907196045, + -0.5572893619537354, + -0.12900805473327637, + -0.25469014048576355, + 0.029107721522450447, + 0.06511642038822174, + 0.13395795226097107, + -0.2492731660604477, + 0.14317961037158966, + 0.0817793533205986, + -0.2255566269159317, + 0.054880838841199875, + 0.012613444589078426, + 0.15076902508735657, + 0.1751881241798401, + 0.11144432425498962, + 0.01754186674952507 + ], + [ + -0.052724532783031464, + 0.20151333510875702, + 0.0495123565196991, + 0.05786041170358658, + 0.10430985689163208, + -0.05871093273162842, + -0.06345336139202118, + 0.0908084288239479, + 0.010975008830428123, + 0.025828585028648376, + 0.0056916517205536366, + 0.05819700285792351, + -0.18182916939258575, + 0.008546815253794193, + -0.061712052673101425, + 0.06289545446634293, + -0.048413872718811035, + -0.16578726470470428, + -0.0705428421497345, + -0.03866717591881752, + 0.1583447903394699, + 0.117472805082798, + 0.050578441470861435, + -0.059388693422079086, + 0.12913638353347778, + 0.0324852354824543, + -0.18780027329921722, + 0.02496984228491783, + 0.14298689365386963, + 0.14124122262001038, + -0.0940382331609726, + 0.2714848816394806, + 0.15944407880306244, + 0.007683854550123215, + 0.17433695495128632, + -0.300119549036026, + -0.04481125250458717, + -0.03183593973517418, + 0.08603385090827942, + 0.14818483591079712, + -0.06904619932174683, + 0.008772644214332104, + -0.03301456943154335, + 0.0033687599934637547, + -0.22395066916942596, + 0.09041491895914078, + -0.11084616929292679, + 0.05353871360421181, + -0.08922562003135681, + 0.017106659710407257, + -0.09198383241891861, + -0.16019423305988312, + 0.04945001378655434, + 0.053490303456783295, + -0.160686194896698, + 0.04782935231924057, + -0.16094925999641418, + 0.10255873203277588, + -0.07591967284679413, + 0.05642334371805191, + 0.04886668547987938, + -0.12989071011543274, + 0.12681631743907928, + -0.1960798054933548, + 0.003965310286730528, + -0.0001358956506010145, + -0.037027277052402496, + -0.16192691028118134, + 0.09349735081195831, + 0.04733635112643242, + 0.02718685008585453, + 0.09984417259693146, + 0.04803409054875374, + 0.11871228367090225, + -0.027402544394135475, + -0.16820795834064484, + 0.058213356882333755, + -0.11965131759643555, + 0.0234356801956892, + 0.03495835140347481, + 0.14536195993423462, + 0.05553906410932541, + -0.16810408234596252, + 0.046969518065452576, + -0.16614925861358643, + 0.08256086707115173, + -0.038101449608802795, + -0.12318267673254013, + -0.1933308243751526, + 0.03482886403799057, + -0.00040065572829917073, + -0.07787391543388367, + 0.07396192103624344, + -0.0535135380923748, + 0.02319786138832569, + -0.016701024025678635, + -0.12364312261343002, + -0.05576566979289055, + -0.16915275156497955, + 0.08685057610273361, + -0.03589697927236557, + 0.010244089178740978, + 0.05251847207546234, + 0.10600820928812027, + -0.03255327045917511, + -0.12339877337217331, + 0.1170831024646759, + 0.03999081254005432, + 0.03541553393006325, + -0.08758139610290527, + 0.13287609815597534, + -0.19407452642917633, + -0.20194034278392792, + -0.03315987437963486, + 0.025873469188809395, + 0.2585095763206482, + -0.006668754853308201, + -0.056710924953222275, + -0.07468999922275543, + -0.09798334538936615, + -0.18145042657852173, + 0.0033692452125251293, + -0.039363015443086624, + 0.03360946103930473, + -0.202927827835083, + -0.7715777158737183, + -0.0915595293045044, + -0.005878994707018137 + ], + [ + 0.004728531464934349, + 0.05852954089641571, + 0.04849248751997948, + -0.13984383642673492, + -0.023725446313619614, + -0.12018699198961258, + -0.029071349650621414, + 0.06623532623052597, + -0.016359781846404076, + -0.09269599616527557, + 0.05897534266114235, + -0.03196629881858826, + 0.17420673370361328, + -0.23313742876052856, + -0.3192990720272064, + 0.0428418405354023, + 0.19449062645435333, + 0.010704790242016315, + 0.04991776868700981, + 0.07040461152791977, + 0.2763651907444, + -0.08638020604848862, + 0.036100175231695175, + -0.16881170868873596, + -0.011055831797420979, + -0.12782834470272064, + 0.17820312082767487, + -0.005981302820146084, + 0.01869300566613674, + -0.020357200875878334, + 0.3222335875034332, + 0.05555807426571846, + -0.008118633180856705, + 0.13994990289211273, + -0.053270138800144196, + -0.13302534818649292, + -0.20690608024597168, + -0.0858759805560112, + 0.18454551696777344, + -0.023236531764268875, + 0.02120841108262539, + 0.08745836466550827, + -0.033270739018917084, + 0.030456986278295517, + 0.06825754791498184, + -0.6061124801635742, + -0.20366232097148895, + -0.1310543715953827, + -0.04278672859072685, + 0.019910218194127083, + -0.09215665608644485, + -0.06307679414749146, + -0.11189908534288406, + -0.08123145252466202, + 0.01852140575647354, + 0.13358314335346222, + -0.037198346108198166, + -0.06514444202184677, + 0.0448371097445488, + 0.018652522936463356, + 0.011874238960444927, + 0.04452796280384064, + 0.1188950389623642, + 0.1213189885020256, + -0.036167580634355545, + 0.1648382693529129, + 0.19552873075008392, + -0.16189689934253693, + -0.014364805072546005, + 0.018666792660951614, + 0.08146843314170837, + -0.05834528058767319, + 0.07800543308258057, + -0.0012464334722608328, + -0.04570535197854042, + 0.09520899504423141, + -0.09164465963840485, + -0.09111931174993515, + -0.38006627559661865, + -0.12076140195131302, + -0.1124340146780014, + 0.10577534139156342, + -0.07914374768733978, + 0.11781805753707886, + -0.1784798502922058, + 0.06465725600719452, + 0.11622463911771774, + -0.04867926985025406, + -0.052238646894693375, + 0.1504058539867401, + -1.4788333177566528, + 0.06357045471668243, + -0.1535155028104782, + -0.05520036816596985, + -0.1635567843914032, + 0.061029523611068726, + -0.16760055720806122, + -0.010563244111835957, + 0.06692282855510712, + -0.2549525797367096, + 0.0006265058182179928, + -0.021442098543047905, + 0.03819136321544647, + -0.029554719105362892, + -0.17348118126392365, + -0.10895483195781708, + 0.030207861214876175, + 0.3693648874759674, + -0.17279714345932007, + 0.07102468609809875, + -0.031363628804683685, + 0.26303836703300476, + 0.16409261524677277, + 0.003991485107690096, + -0.3972315490245819, + 0.1701420098543167, + -0.006253332365304232, + 0.0020736870355904102, + -0.04710637032985687, + 0.18722307682037354, + -0.02831968106329441, + -0.04297930374741554, + 0.022891419008374214, + 0.20754322409629822, + -0.48658883571624756, + -0.22909937798976898, + 0.11885718256235123, + -0.46902334690093994 + ], + [ + 0.12966272234916687, + -0.007360656745731831, + 0.061235908418893814, + 0.08076929301023483, + 0.023691395297646523, + -0.15586043894290924, + 0.06820093840360641, + -0.06514070183038712, + 0.01214582845568657, + -0.1758643239736557, + -0.1413014680147171, + 0.09423520416021347, + -0.21729187667369843, + 0.10132813453674316, + -0.03141697496175766, + 0.05812502279877663, + 0.08627642691135406, + 0.07192486524581909, + -0.0668289065361023, + 0.05381755158305168, + -0.10642077773809433, + -0.11875130981206894, + 0.07498949766159058, + 0.21271970868110657, + -0.00018062097660731524, + 0.024072658270597458, + -0.5649756789207458, + -0.06204131990671158, + 0.13868620991706848, + -0.0752866119146347, + -0.04851313680410385, + -0.4386079013347626, + 0.0274188369512558, + -0.1550387293100357, + -0.07904964685440063, + 0.2511279284954071, + 0.19192640483379364, + -0.057444505393505096, + -0.03290176764130592, + 0.07186154276132584, + -0.1571197211742401, + -0.13438567519187927, + -0.13997720181941986, + -0.04874617978930473, + -0.031197506934404373, + -0.04387471079826355, + 0.15658770501613617, + 0.04448807239532471, + 0.01751384325325489, + -0.029285045340657234, + 0.048812367022037506, + 0.0407353974878788, + -0.00832420401275158, + 0.18194489181041718, + 0.014224818907678127, + -0.13758373260498047, + 0.11031801253557205, + -0.15505632758140564, + 0.029397763311862946, + 0.08054986596107483, + 0.01083151251077652, + 0.025888880714774132, + 0.057619646191596985, + -0.032670192420482635, + 0.006632988806813955, + -0.4142110347747803, + 0.014740522019565105, + 0.07384705543518066, + 0.1001928374171257, + -0.007263291161507368, + -0.11265186965465546, + -0.08988604694604874, + 0.0462053082883358, + 0.015702437609434128, + 0.0949975848197937, + -0.06872424483299255, + -0.12592889368534088, + 0.11579219996929169, + 0.08150125294923782, + 0.033369582146406174, + -0.048806872218847275, + -0.043363332748413086, + 0.07659149169921875, + -0.09472240507602692, + 0.08889376372098923, + 0.07607120275497437, + 0.09682059288024902, + 0.07842063158750534, + 0.03771235793828964, + 0.09469985216856003, + 0.08529437333345413, + 0.19783252477645874, + -0.02082202211022377, + 0.1421523541212082, + -0.12626637518405914, + 0.0006469184882007539, + -0.04485580325126648, + 0.013545172289013863, + 0.013684321194887161, + 0.09763503819704056, + -0.10885018110275269, + 0.038817327469587326, + 0.03226421773433685, + -0.02596290037035942, + 0.1289830505847931, + 0.041971612721681595, + -0.2893608808517456, + 0.21206432580947876, + 0.013410660438239574, + 0.03840477019548416, + -0.05336631461977959, + -0.2593543231487274, + -0.04276334121823311, + 0.07859902828931808, + -0.09878284484148026, + -0.3722444474697113, + 0.08965621888637543, + -0.5145974159240723, + -0.20321214199066162, + -0.051271162927150726, + 0.09452270716428757, + 0.013266505673527718, + 0.020961331203579903, + -0.025135984644293785, + -0.14911101758480072, + -0.03291100263595581, + 0.05625533312559128, + 0.06336399167776108 + ], + [ + 0.08068303763866425, + -0.4604851305484772, + 0.21835477650165558, + -0.12043691426515579, + -0.11777772009372711, + -0.06417425721883774, + 0.13569742441177368, + -0.027688438072800636, + 0.1009046658873558, + 0.12271740287542343, + 0.16189908981323242, + -0.04504493251442909, + -0.2626798152923584, + -0.575246274471283, + -0.038127969950437546, + 0.38994014263153076, + 0.010613269172608852, + 0.28948789834976196, + -0.13264958560466766, + -0.0005838611978106201, + 0.1139659509062767, + 0.040877360850572586, + 0.1738293170928955, + 0.051417309790849686, + -0.1991862803697586, + -0.10373934358358383, + -0.04598042368888855, + 0.12701651453971863, + -0.29635119438171387, + -0.3122963011264801, + 0.021017741411924362, + -0.3374790549278259, + -0.21292050182819366, + -0.030133269727230072, + -0.0064400071278214455, + -0.10169267654418945, + -0.3108963072299957, + 0.09013724327087402, + 0.14740508794784546, + 0.045348264276981354, + -0.13836686313152313, + -0.08134415745735168, + 0.2040412873029709, + 0.14905764162540436, + 0.32249611616134644, + 0.2916358709335327, + -0.6801439523696899, + 0.13808032870292664, + 0.04117457568645477, + -0.35449719429016113, + 0.22641296684741974, + -0.11808512359857559, + -0.07027171552181244, + -0.10572060197591782, + -0.15515469014644623, + -0.2763161063194275, + -0.4814583361148834, + 0.02502397634088993, + 0.4127350151538849, + -2.3265841007232666, + -0.0951528251171112, + 0.0266332495957613, + 0.2985280454158783, + 0.36305856704711914, + 0.039458323270082474, + 0.015102248638868332, + 0.03162441775202751, + -0.06619574874639511, + 0.011212985031306744, + 0.6458699703216553, + 0.35162439942359924, + -0.1140977144241333, + 0.0463264100253582, + -0.21078985929489136, + -1.115431547164917, + -0.08310552686452866, + -0.06262164562940598, + 0.042927294969558716, + -0.05489038676023483, + -0.24256335198879242, + -0.09005032479763031, + 0.043770480901002884, + -0.2041400521993637, + 0.24453145265579224, + 0.11554188281297684, + 0.2026459127664566, + -0.345918208360672, + -0.6503119468688965, + 0.13783079385757446, + -0.1749541461467743, + -0.008121663704514503, + -0.2794790267944336, + -0.4686086177825928, + 0.19028058648109436, + 0.42985427379608154, + -0.11628211289644241, + -0.14329630136489868, + -0.27742478251457214, + -0.06483302265405655, + -0.018348509445786476, + 0.16192781925201416, + -0.12277788668870926, + 0.09701694548130035, + 0.01128819864243269, + 0.014032882638275623, + 0.07983975112438202, + 0.05060616508126259, + -0.5240373611450195, + -1.2179608345031738, + -0.03863126039505005, + -0.05036527290940285, + 0.03355466574430466, + -0.4347473084926605, + 0.2565353214740753, + -0.11326612532138824, + 0.2726048529148102, + -0.42376112937927246, + -0.023075588047504425, + -0.05473586916923523, + 0.4343847930431366, + -0.0051671178080141544, + 0.3547937273979187, + -0.08823041617870331, + 0.11607196182012558, + -0.9579744935035706, + -0.015813684090971947, + -0.0497591532766819, + 0.10366342961788177 + ], + [ + 0.022100506350398064, + 0.05973423272371292, + -0.006858102045953274, + -0.07625041902065277, + 0.0537552647292614, + -0.07332704961299896, + -0.016882669180631638, + 0.004047991242259741, + 0.00806268397718668, + -0.009886280633509159, + 0.06888791173696518, + 0.0757407695055008, + 0.021822482347488403, + 0.0018336917273700237, + 0.07594286650419235, + -0.0274543184787035, + -0.00705647561699152, + 0.06866790354251862, + 0.09266436100006104, + -0.0003403244190849364, + 0.03812582790851593, + 0.022745877504348755, + 0.03063935413956642, + 0.03293226659297943, + -0.05427825450897217, + -0.052394285798072815, + 0.08560802042484283, + -0.05473952740430832, + 0.06855395436286926, + 0.0021014539524912834, + 0.005207826849073172, + 0.04866959527134895, + -0.02787557616829872, + -0.014721778221428394, + 0.11407733708620071, + -0.08830782026052475, + 0.10983572155237198, + 0.04407014325261116, + 0.07730831205844879, + 0.033464789390563965, + 0.009146101772785187, + 0.020720038563013077, + 0.03308495506644249, + 0.060373518615961075, + 0.045881785452365875, + -0.07335836440324783, + 0.07737629115581512, + 0.013063381426036358, + 0.10062108933925629, + 0.038289185613393784, + 0.03762344270944595, + 0.09670377522706985, + -0.021044831722974777, + 0.04225647822022438, + 0.007198530249297619, + 0.007486679591238499, + 0.08079442381858826, + 0.10847241431474686, + 0.04503866657614708, + -0.06439220160245895, + -0.03718191012740135, + -0.04443495720624924, + -0.034459877759218216, + -0.024855585768818855, + 0.11225103586912155, + -0.012098684906959534, + 0.16411805152893066, + 0.055935293436050415, + 0.03670573607087135, + -0.0026147107128053904, + 0.02849329635500908, + -0.033770378679037094, + -0.023687465116381645, + 0.016730692237615585, + -0.016792554408311844, + -0.05543510243296623, + 0.05381111055612564, + -0.024749239906668663, + 0.06550175696611404, + 0.044264715164899826, + 0.05848730728030205, + 0.06899718940258026, + -0.03510728478431702, + 0.05905619636178017, + -0.05505536124110222, + 0.002036695135757327, + 0.043284349143505096, + 0.09028360992670059, + -0.05221617966890335, + 0.025539042428135872, + 0.00040586444083601236, + 0.050219520926475525, + 0.007055121008306742, + -0.03503123298287392, + -0.019040584564208984, + -0.030668051913380623, + -0.028253722935914993, + -0.05933734029531479, + 0.09187337011098862, + -0.03565239533782005, + 0.007415323983877897, + 0.003446849761530757, + 0.0861821174621582, + -0.028345314785838127, + 0.06000646948814392, + -0.04835960268974304, + -0.009463253431022167, + 0.027377089485526085, + -0.09059581160545349, + -0.02526162937283516, + -0.03889518231153488, + -0.028866000473499298, + 0.03326292335987091, + -0.07780182361602783, + 0.033728089183568954, + -0.03527432680130005, + 0.05570830777287483, + -0.05192122980952263, + 0.04176004230976105, + 0.07814699411392212, + 0.06351935863494873, + 0.07046718895435333, + -0.06931284070014954, + 0.04990892484784126, + -0.005853850860148668, + -0.02124575711786747, + 0.021970102563500404, + 0.011145196855068207 + ], + [ + -0.10874991863965988, + 0.11763397604227066, + -0.04454135149717331, + -0.04409550130367279, + 0.029610177502036095, + -0.0469261072576046, + -0.10175896435976028, + 0.02217860519886017, + 0.02360335923731327, + -0.12451070547103882, + 0.0718151405453682, + 0.040806274861097336, + 0.13956202566623688, + -0.1283247172832489, + -0.021691428497433662, + 0.06577760726213455, + -0.1157815083861351, + -0.05095899850130081, + 0.14206258952617645, + 0.029813125729560852, + 0.047458719462156296, + 0.05395878851413727, + -0.054781120270490646, + -0.03520810976624489, + 0.03340160474181175, + 0.020458677783608437, + -0.013200613670051098, + 0.03782344236969948, + -0.027826769277453423, + -0.0177626870572567, + -0.08389586955308914, + 0.0930219516158104, + -0.17846925556659698, + 0.0683843269944191, + 0.0992787778377533, + -0.40700653195381165, + -0.03024269826710224, + 0.0932631716132164, + 0.07231274247169495, + 0.1799630969762802, + 0.05498238280415535, + 0.08438967913389206, + -0.06030955910682678, + 0.12739457190036774, + -0.014909046702086926, + -0.09680614620447159, + -0.08667949587106705, + 0.07913587987422943, + 0.04462272301316261, + 0.1466536819934845, + -0.1304406374692917, + 0.09753764420747757, + 0.08904200792312622, + 0.0024527590721845627, + -0.1307583898305893, + 0.10451482981443405, + 0.06756467372179031, + 0.05104636773467064, + 0.04643775522708893, + 0.04838382825255394, + 0.01280492264777422, + 0.04848919436335564, + 0.08307463675737381, + 0.012579288333654404, + 0.11235181242227554, + 0.08992814272642136, + 0.1150992214679718, + -0.09207111597061157, + -0.02370542660355568, + 0.10883768647909164, + 0.10126376897096634, + -0.004358799662441015, + 0.06549526751041412, + -0.066440649330616, + -0.06488785892724991, + -0.09109856933355331, + -0.0006314009078778327, + 0.05163821950554848, + 0.08556362986564636, + -0.12821485102176666, + 0.053370941430330276, + 0.11493109166622162, + -0.05387183651328087, + 0.03503965958952904, + 0.01115469727665186, + 0.07940593361854553, + -0.049753088504076004, + -0.002931189024820924, + 0.026441605761647224, + 0.0880935937166214, + 0.10981141030788422, + -0.009769277647137642, + -0.15580111742019653, + 0.08266335725784302, + -0.1104380413889885, + -0.05999383330345154, + -0.04163913428783417, + -0.046950776129961014, + -0.020358119159936905, + 0.06878373771905899, + 0.060373615473508835, + -0.0904647707939148, + -0.001194355427287519, + 0.06163525581359863, + 0.09129751473665237, + -0.06476165354251862, + -0.08394224941730499, + -0.09015800058841705, + -0.1100175529718399, + 0.06773547828197479, + -0.12030615657567978, + 0.11214296519756317, + 0.09926857799291611, + -0.07497785240411758, + -0.07614120095968246, + 0.09117268770933151, + -0.08321798592805862, + 0.04765239357948303, + 0.013470078818500042, + 0.01739692874252796, + -0.09838885068893433, + -0.049362629652023315, + 0.010396691970527172, + 0.03182753548026085, + 0.09512922912836075, + 0.043936651200056076, + 0.04824742302298546, + 0.032766036689281464 + ], + [ + -0.09444815665483475, + -0.10546442866325378, + 0.004151022061705589, + 0.26865676045417786, + -0.09786245971918106, + -0.0870860368013382, + -0.1030690148472786, + 0.151222825050354, + -0.16227436065673828, + -0.07391563802957535, + 0.01781049370765686, + 0.07307354360818863, + 0.06218486651778221, + -0.5777086615562439, + -0.3971981406211853, + -0.11923658847808838, + -0.17235496640205383, + -0.19170288741588593, + 0.4237936735153198, + 0.3947053551673889, + 0.0948033481836319, + 0.07797446101903915, + -0.3725269138813019, + -0.03751477599143982, + 0.32913753390312195, + -0.20175257325172424, + 0.11862052232027054, + -0.1722174882888794, + -0.05023976042866707, + 0.13087758421897888, + 0.332387775182724, + 0.0913136824965477, + -0.31936120986938477, + -0.020207837224006653, + 0.03258311375975609, + 0.26868852972984314, + 0.025287505239248276, + 0.23182709515094757, + -0.0030688790138810873, + 0.07344377785921097, + -0.05932801216840744, + 0.002265426330268383, + -0.013018941506743431, + -0.07382942736148834, + -0.07015806436538696, + -0.037649620324373245, + 0.13419577479362488, + 0.1620219498872757, + 0.17791183292865753, + 0.04432394355535507, + 0.1689101606607437, + 0.3161015808582306, + 0.06653757393360138, + -0.2137129157781601, + 0.2577473819255829, + 0.0035910988226532936, + 0.1699383556842804, + 0.06487233936786652, + -0.22776897251605988, + -0.17907263338565826, + 0.03378954529762268, + -0.04180801287293434, + -0.366566002368927, + -0.03351831063628197, + 0.1308535635471344, + -0.07481737434864044, + 0.3166605830192566, + 0.3140139579772949, + -0.003924658056348562, + -0.3712998628616333, + 0.10029216855764389, + 0.007486800663173199, + 0.10769049823284149, + -0.04785921052098274, + -0.15964457392692566, + 0.18918821215629578, + -0.05380452424287796, + -0.045164965093135834, + -0.05487172678112984, + -0.264744371175766, + -0.3187876045703888, + 0.17602095007896423, + 0.12430839985609055, + 0.2621302306652069, + -0.44947323203086853, + 0.11485685408115387, + -0.10570235550403595, + 0.154866561293602, + -0.017136283218860626, + 0.09437155723571777, + 0.16342693567276, + 0.31610172986984253, + -0.3965337872505188, + 0.10463127493858337, + -0.5655682682991028, + -0.6598596572875977, + 0.15606650710105896, + -0.22883625328540802, + 0.1680314987897873, + 0.05444402992725372, + -0.22881101071834564, + -0.7171896696090698, + -0.25398334860801697, + 0.22015957534313202, + 0.07082755118608475, + -0.3327757716178894, + -0.3070888817310333, + 0.34825870394706726, + -0.4655570089817047, + -0.7730278372764587, + 0.09746160358190536, + 0.12433277815580368, + 0.22675098478794098, + -0.062036577612161636, + 0.020063752308487892, + 0.06953223049640656, + 0.17867273092269897, + -0.10206679999828339, + -0.046225693076848984, + -0.27563706040382385, + -0.19284509122371674, + -0.2345849722623825, + 0.02791452780365944, + 0.200689435005188, + -0.8062997460365295, + -0.6312804818153381, + -0.04716772213578224, + 0.29504427313804626 + ], + [ + 0.020345857366919518, + 0.08258439600467682, + 0.04198909550905228, + -0.012981762178242207, + -0.1486009955406189, + 0.08143780380487442, + 0.03239312767982483, + 0.05183764174580574, + 0.09492125362157822, + 0.03350841999053955, + -0.11826544255018234, + 0.004810255952179432, + 0.01743023656308651, + 0.08030740171670914, + -0.3595708906650543, + -0.07846042513847351, + 0.17077846825122833, + 0.06355525553226471, + 0.0008098255493678153, + 0.16387782990932465, + -0.314718633890152, + 0.011779466643929482, + -0.08589942753314972, + -0.05517808347940445, + 0.07806329429149628, + 0.07241081446409225, + -0.015361661091446877, + -0.0918382853269577, + -0.1521817445755005, + 0.06674808263778687, + 0.13242582976818085, + 0.00159309187438339, + 0.0535249225795269, + 0.013043437153100967, + -0.025688836351037025, + -0.14653842151165009, + -0.06288441270589828, + 0.0830831304192543, + 0.10518289357423782, + -0.1229301393032074, + 0.04864174500107765, + -0.1046496108174324, + -0.031805217266082764, + 0.10773799568414688, + 0.00237857224419713, + -0.250005304813385, + 0.06048933044075966, + 0.037960827350616455, + -0.008680831640958786, + 0.0014409392606467009, + -0.06735807657241821, + -0.08703939616680145, + 0.13478907942771912, + 0.09688401222229004, + 0.007311120629310608, + 0.07687411457300186, + 0.025612207129597664, + 0.08480565994977951, + -0.04219629243016243, + -0.03235746920108795, + -0.06431146711111069, + 0.023429619148373604, + 0.01075203251093626, + 0.08108755946159363, + 0.06615999341011047, + 0.06703384220600128, + 0.11017568409442902, + 0.01596059463918209, + 0.05204823613166809, + 0.08427974581718445, + 0.04228236898779869, + 0.005966749042272568, + 0.05200062319636345, + -0.048708561807870865, + 0.012274525128304958, + 0.02917325869202614, + 0.06927766650915146, + 0.10555313527584076, + -0.07545927911996841, + -0.1694139838218689, + -0.12465400993824005, + -0.04468682035803795, + -0.0652848407626152, + 0.022826163098216057, + -0.022961921989917755, + 0.06117899343371391, + 0.016012853011488914, + 0.13477642834186554, + -0.06917978078126907, + -0.019295673817396164, + 0.337545245885849, + -0.0454840213060379, + -0.1998652219772339, + 0.11409991979598999, + -0.12017957121133804, + -0.027296677231788635, + -0.07282096147537231, + 0.07185978442430496, + 0.06815982609987259, + -0.0940287709236145, + 0.06805936992168427, + 0.052890777587890625, + 0.0917636975646019, + -0.06638538092374802, + -0.06039520353078842, + -0.07764308899641037, + -0.08000923693180084, + -0.33001887798309326, + -0.1008705198764801, + -0.009203345514833927, + -0.038755808025598526, + 0.039360638707876205, + 0.1258840560913086, + -0.09986866265535355, + -0.08260538429021835, + 0.1423780620098114, + -0.10750985890626907, + 0.10695836693048477, + -0.09841299802064896, + -0.05929999426007271, + 0.0009934597183018923, + 0.02132529579102993, + 0.09034831076860428, + 0.028612608090043068, + -0.009373960085213184, + 0.04995357617735863, + -0.05761996656656265, + -0.06977792084217072 + ], + [ + -0.00044584175338968635, + -9.155052410614317e-11, + -2.9199471729413062e-09, + -9.188117360281467e-07, + -1.139207412848009e-07, + -1.284078962271451e-06, + 2.0215529730194248e-05, + -4.659285696106963e-05, + -2.7013120416086167e-05, + -9.8429472927819e-06, + -4.647890010150632e-16, + -1.2911179965158226e-06, + -1.6734784694979737e-27, + -3.427300887537399e-09, + -3.5351957657986485e-40, + -6.171132492337846e-20, + -6.793305045209966e-31, + 2.1716719800224382e-07, + -5.32333133340138e-22, + -2.4571139567441946e-10, + 1.0010455839597196e-40, + -1.7649843186973158e-07, + -2.417084942862857e-06, + -1.8549266087575234e-06, + -3.988310723346267e-11, + -6.969708010728937e-06, + -0.00014265533536672592, + -4.2984069084597576e-29, + -1.9570025600046392e-08, + -2.171365667891223e-05, + 6.236420407867982e-11, + -7.306345537472225e-07, + -3.095748567386386e-41, + -3.624598607822572e-40, + -9.231123623745091e-14, + -6.830018933009765e-23, + -2.7363830312765458e-08, + 8.225140568640654e-09, + -6.475165311421126e-20, + -2.90887214760005e-07, + -5.343924271983269e-07, + -4.114118894449348e-07, + -1.27003657590663e-10, + -2.843396629259587e-10, + -1.3586016756050867e-08, + 6.107489291836499e-40, + -6.156495452325217e-11, + 4.4341055827068487e-10, + -1.7904647393152118e-05, + 6.2674895504931066e-40, + -1.0277708357623406e-17, + -4.68755589899672e-10, + 6.583687588157527e-14, + -3.3520132199441055e-13, + -1.2669014726185424e-09, + -2.5660361302470847e-07, + -2.4041673896135762e-06, + -4.460528725758195e-05, + 4.756451289722463e-06, + -4.384489784570178e-06, + 6.3542059795891576e-21, + -4.125657142139971e-05, + -6.436049261537846e-06, + -1.3688464983960004e-18, + -2.225570977509861e-10, + -1.37610829398227e-08, + -2.397555363131687e-05, + -3.329783671013331e-11, + -3.857734895973408e-07, + -1.3570698683906812e-05, + -1.00075114861653e-09, + -3.794394069700502e-05, + 4.04758975491859e-06, + -8.826262503236765e-22, + -1.3386523960434715e-06, + -0.00011272213305346668, + -5.418936478739696e-15, + 5.921733167405601e-40, + 2.132702221883318e-31, + -1.8009417655535146e-17, + -7.403718610410337e-11, + -2.0494383557709056e-15, + -2.023037086473778e-05, + -7.793586956350573e-12, + -1.7994027601275775e-08, + -9.5906580099836e-07, + -2.033852979366202e-05, + -1.2433476470619098e-09, + -8.323967473213381e-17, + -3.956924956582952e-06, + 4.341337793571986e-32, + -4.3608569200159764e-08, + -3.949672033245256e-18, + -1.478904563916028e-12, + -1.8938862607218105e-17, + -3.9012338675092906e-06, + -1.8966120478580706e-05, + 1.865072044893168e-05, + -1.8906891909864498e-06, + -4.338051011824496e-19, + -0.00028914096765220165, + -3.152842917297782e-11, + -1.0782426898003905e-06, + -2.2289306400580244e-07, + 1.6852294493219233e-07, + -5.130399586050771e-06, + -3.6227349937689723e-06, + -7.151771939912961e-39, + 1.3502351482848207e-40, + 1.5482792999066408e-12, + 3.418707876789995e-07, + 1.1283815754129157e-40, + -2.0737494719065249e-16, + -2.5020722205226775e-06, + -2.0625165411677895e-18, + -8.26649265945889e-05, + -6.290624806428754e-19, + -3.7695743230869994e-05, + -2.135755892140878e-07, + -4.705426817963598e-06, + -0.0008775395690463483, + -1.4698167010498828e-16, + -6.313180165307131e-06, + -2.1105923952990935e-10, + 1.2301018309382542e-40, + -1.5831767008478437e-09, + -3.6177410947857425e-05, + -6.594370443266157e-41 + ], + [ + -0.14104413986206055, + -0.08550803363323212, + -0.056075986474752426, + -0.1392381638288498, + -0.04371555894613266, + 0.08799414336681366, + 0.07550466805696487, + -0.04804478585720062, + 0.006246818695217371, + -0.035008132457733154, + 0.020046859979629517, + 0.04793033376336098, + 0.08181677758693695, + -0.14407086372375488, + -0.13555876910686493, + -0.14684459567070007, + -0.1291399896144867, + -0.05401122570037842, + 0.075422003865242, + 0.2204241156578064, + 0.11591820418834686, + 0.10085967928171158, + 0.05159406736493111, + 0.09519592672586441, + -0.09217249602079391, + 0.0064501091837882996, + 0.15199624001979828, + 0.12175027281045914, + -0.11525428295135498, + 0.1416197121143341, + 0.019238445907831192, + 0.11461994051933289, + -0.17880570888519287, + -0.005189654882997274, + -0.04494582116603851, + -0.02208442986011505, + -0.15709708631038666, + 0.05099504441022873, + 0.12399493902921677, + 0.05006171017885208, + -0.12395890057086945, + 0.10361938923597336, + -0.12926459312438965, + 0.12684008479118347, + -0.07075630873441696, + -0.03219493851065636, + 0.06384515762329102, + 0.1479233205318451, + 0.11414188146591187, + 0.1729881465435028, + -0.026519786566495895, + 0.03900572657585144, + 0.04804244264960289, + -0.081888847053051, + -0.05678464099764824, + 0.05022229254245758, + -0.06730110198259354, + 0.06722192466259003, + 0.07272542268037796, + 0.06768149882555008, + 0.10650067031383514, + -0.07044139504432678, + -0.020618867129087448, + 0.022606302052736282, + 0.1243889331817627, + 0.050003230571746826, + 0.2117871791124344, + -0.07247675210237503, + -0.0433017872273922, + -0.018049292266368866, + -0.07728008925914764, + -0.051487650722265244, + -0.010388107970356941, + -0.03840603679418564, + 0.042314570397138596, + -0.13308650255203247, + 0.17549486458301544, + 0.08729366213083267, + 0.10613762587308884, + -0.1805201917886734, + -0.22303836047649384, + 0.17258907854557037, + -0.12479858845472336, + 0.09327780455350876, + -0.17199286818504333, + -0.03702288120985031, + -0.01579420268535614, + 0.02618500404059887, + -0.004428249318152666, + -0.08325447887182236, + 0.12585794925689697, + -0.065574511885643, + -0.1369810253381729, + 0.1628239005804062, + 0.009094227105379105, + -0.020980389788746834, + 0.04186570644378662, + -0.024476798251271248, + 0.16985087096691132, + 0.028288502246141434, + 0.06904555857181549, + -0.05354343727231026, + 0.0034970175474882126, + 0.01316171232610941, + 0.12500526010990143, + 0.062093738466501236, + -0.029471196234226227, + -0.08351898193359375, + -0.3311784565448761, + 0.027033844962716103, + 0.09369193017482758, + 0.12931285798549652, + 0.1198355183005333, + -0.06606995314359665, + 0.026732195168733597, + 0.03589631989598274, + -0.05032728984951973, + 0.15489289164543152, + 0.032181669026613235, + -0.0541946180164814, + -0.038826677948236465, + -0.10267078876495361, + -0.06151000037789345, + 0.016563208773732185, + 0.05637247860431671, + 0.08317486941814423, + -0.046989064663648605, + -0.05246599018573761 + ], + [ + -0.10186420381069183, + 0.02016870491206646, + 0.054370246827602386, + 0.13046176731586456, + -0.021356552839279175, + 0.07473702728748322, + -0.004555057268589735, + 0.09229419380426407, + 0.029509060084819794, + -0.08054400235414505, + 0.19508476555347443, + -0.04133418947458267, + 0.146103635430336, + -0.08147824555635452, + -0.131513312458992, + -0.03063621185719967, + 0.06328791379928589, + -0.11086396872997284, + 0.02187536470592022, + 0.08178805559873581, + -0.4296962320804596, + -0.015291551128029823, + 0.08149909973144531, + 0.053222719579935074, + -0.054673902690410614, + -0.16605141758918762, + -0.03590128570795059, + -0.28833988308906555, + -0.007345319725573063, + 0.06583943217992783, + 0.005079068709164858, + 0.03580319881439209, + 0.013572314754128456, + -0.04575575888156891, + 0.0785786584019661, + -0.22729343175888062, + -0.0904286652803421, + 0.004339638166129589, + 0.19334560632705688, + 0.0402449332177639, + 0.059076592326164246, + 0.013826125301420689, + -0.11223575472831726, + 0.0489761121571064, + -0.11104200035333633, + -0.12019079923629761, + 0.11134091764688492, + 0.01799221895635128, + 0.06266142427921295, + 0.1326330155134201, + -0.032054029405117035, + -0.1561957597732544, + 0.05022146552801132, + -0.07156559079885483, + 0.10750767588615417, + 0.08466636389493942, + 0.10223737359046936, + 0.05556120350956917, + -0.12165739387273788, + 0.008870034478604794, + -0.15012112259864807, + 0.03577403351664543, + 0.06069657579064369, + 0.06260450929403305, + 0.042518071830272675, + 0.13712525367736816, + 0.06046714261174202, + -0.07188569009304047, + 0.03488211706280708, + 0.056959472596645355, + -0.0702919140458107, + -0.06275980919599533, + -0.04078558087348938, + -0.11212116479873657, + 0.12560388445854187, + -0.0017485315911471844, + -0.005485716741532087, + -0.03272698447108269, + -0.054903946816921234, + 0.009742619469761848, + -0.12828007340431213, + 0.0064230505377054214, + -0.07852432131767273, + 0.06572259217500687, + -0.11800432205200195, + 0.03379007428884506, + 0.06506215035915375, + -0.018542135134339333, + -0.11898178607225418, + 0.11570706218481064, + -0.0810745507478714, + 0.008890210650861263, + -0.3217940926551819, + -0.0547780878841877, + 0.05689295381307602, + 0.064761683344841, + -0.03760334476828575, + -0.04549845680594444, + -0.00040005645132623613, + -0.1264895647764206, + 0.0457359217107296, + -0.0731520727276802, + 0.00581421609967947, + -0.0028948418330401182, + -0.016589587554335594, + -0.1273030787706375, + -0.01079714298248291, + 0.036282576620578766, + -0.0026773165445774794, + 0.008228794671595097, + -0.10860657691955566, + 0.08314163237810135, + 0.0635782927274704, + 0.03370481729507446, + -0.05471549928188324, + 0.15337076783180237, + -0.05022745206952095, + -0.0714479461312294, + -0.07447922229766846, + 0.0042650289833545685, + -0.01152635645121336, + -0.23130302131175995, + -0.010172521695494652, + -0.1043105199933052, + -0.07436321675777435, + 0.03893746808171272, + 0.07749798148870468, + -0.12848053872585297 + ], + [ + -0.23194119334220886, + 0.02223324216902256, + 0.09503065794706345, + 0.03651803731918335, + 0.03210384398698807, + 0.07084006071090698, + 0.12949901819229126, + 0.08622266352176666, + 0.06083107367157936, + -0.021112516522407532, + 0.11472856253385544, + 0.02182413451373577, + 0.047894056886434555, + -0.03157518059015274, + -0.3266664743423462, + 0.05045453459024429, + 0.07629546523094177, + -0.010329179465770721, + 0.05431155860424042, + 0.11045083403587341, + -0.46407878398895264, + -0.05686293914914131, + -0.007122046779841185, + 0.06823472678661346, + 0.06266732513904572, + 0.048078592866659164, + 0.1936330646276474, + 0.0200031790882349, + -0.020903313532471657, + 0.09307220578193665, + 0.05274999141693115, + 0.19260834157466888, + -0.13740763068199158, + -0.06888734549283981, + -0.11812756955623627, + -0.3511490821838379, + -0.03247915208339691, + -0.04806635528802872, + -0.014915005303919315, + -0.13664229214191437, + -0.05732613429427147, + -0.01680649444460869, + -0.07354304939508438, + -0.06927555054426193, + -0.0865766629576683, + -0.16119498014450073, + -0.14710628986358643, + 0.10504905134439468, + 0.1445237547159195, + 0.13133475184440613, + -0.19812551140785217, + 0.10569991916418076, + 0.04979626461863518, + -0.04298693314194679, + 0.001944013754837215, + 0.10887430608272552, + 0.11620942503213882, + 0.08811014145612717, + 0.06621793657541275, + 0.10560007393360138, + 0.02439979277551174, + 0.05429239571094513, + -0.18555310368537903, + -0.0711669921875, + 0.11930855363607407, + 0.016111819073557854, + -0.017016051337122917, + -0.11795993149280548, + 0.018993163481354713, + 0.008340730331838131, + 0.15057677030563354, + -0.04989412799477577, + 0.0342726930975914, + -0.002316840225830674, + 0.03589965030550957, + -0.2172316163778305, + 0.04691608250141144, + -0.08149195462465286, + -0.17072880268096924, + -0.03349434584379196, + -0.010138606652617455, + -0.10026789456605911, + -0.08649274706840515, + 0.15808235108852386, + -0.07925079017877579, + 0.11087116599082947, + 0.028199272230267525, + 0.1862000674009323, + 0.10573423653841019, + -0.024707259610295296, + -0.04990684986114502, + 0.12717404961585999, + -0.1824958324432373, + 0.16071966290473938, + -0.056628912687301636, + -0.08178392052650452, + -0.04939638078212738, + 0.09060322493314743, + 0.019238369539380074, + 0.13549914956092834, + -0.03668870031833649, + -0.01356151606887579, + -0.010227298364043236, + -0.0405622161924839, + -0.09244225919246674, + 0.07932641357183456, + -0.06395259499549866, + -0.07755931466817856, + -0.020049242302775383, + 0.14064288139343262, + -0.05962531641125679, + -0.08709976822137833, + 0.012743543833494186, + -0.004799806047230959, + -0.1648935228586197, + 0.11586228013038635, + -0.2497323453426361, + -0.2673991918563843, + 0.10768316686153412, + 0.051774926483631134, + -0.13987122476100922, + -0.05224622040987015, + 0.09690986573696136, + 0.24265065789222717, + 0.18305596709251404, + 0.008408626541495323, + 0.029821181669831276, + 0.18184328079223633 + ], + [ + 0.06524014472961426, + 0.03542494401335716, + -0.023717235773801804, + 0.074525848031044, + 0.082551971077919, + 0.13401426374912262, + 0.11827995628118515, + 0.004647840745747089, + -0.030520129948854446, + 0.10916905850172043, + -0.0853806659579277, + -0.06702870875597, + -0.031672872602939606, + -0.028118353337049484, + 0.10981348156929016, + -0.026015369221568108, + 0.020694442093372345, + -0.045821718871593475, + -0.16597825288772583, + -0.10307992994785309, + -0.12719495594501495, + -0.021188272163271904, + -0.05058007687330246, + -0.07219534367322922, + 0.015828397125005722, + 0.04573511332273483, + -0.05061129108071327, + 0.09712455421686172, + -0.0346960611641407, + -0.03800984472036362, + 0.084380604326725, + -0.011169150471687317, + 0.03385401517152786, + -0.10393992066383362, + -0.1975974142551422, + 0.12237498909235, + 0.04829597845673561, + 0.039124611765146255, + -0.0065456475131213665, + 0.012799251824617386, + 0.012488883920013905, + -0.011260004714131355, + 0.11188855767250061, + 0.05298624932765961, + 0.12003233283758163, + 0.04985029995441437, + -0.03707883134484291, + 0.08285057544708252, + 0.025311172008514404, + -0.06653886288404465, + -0.06367388367652893, + 0.05146661773324013, + -0.008155238814651966, + -0.03916007652878761, + 0.052029144018888474, + -0.025483082979917526, + 0.0013410589890554547, + 0.05617273226380348, + -0.02646353654563427, + 0.09048664569854736, + 0.02036181651055813, + 0.003363247262313962, + 0.11120794713497162, + -0.12126263976097107, + -0.04787605628371239, + 0.028184540569782257, + -0.007874956354498863, + 0.026851711794734, + 0.0806286558508873, + -0.0474785678088665, + -0.11698351800441742, + 0.08319810777902603, + 0.04081812500953674, + 0.11070805788040161, + -0.023195145651698112, + 0.14310263097286224, + 0.0498601533472538, + -0.019457463175058365, + 0.0014453897019848228, + -0.046539679169654846, + -0.0020168565679341555, + -0.052405837923288345, + 0.030997352674603462, + -0.047029558569192886, + 0.03183538094162941, + 0.10284077376127243, + 0.05554753541946411, + 0.0530998595058918, + 0.06437595188617706, + -0.004071675706654787, + 0.06491346657276154, + 0.028183339163661003, + 0.026170790195465088, + 0.05512348935008049, + 0.09696631133556366, + 0.006638756953179836, + 0.026820234954357147, + -0.03327921777963638, + -0.07775981724262238, + -0.08585870265960693, + 0.09956376999616623, + 0.031181177124381065, + 0.053810689598321915, + 0.11012953519821167, + -0.040220435708761215, + 0.08790276199579239, + 0.0733589306473732, + 0.01823059655725956, + 0.012245666235685349, + -0.030432865023612976, + 0.09484956413507462, + 0.110826276242733, + -0.15462452173233032, + 0.059399645775556564, + -0.08569499105215073, + 0.09312780946493149, + -0.051459502428770065, + -0.02192225307226181, + -0.06574779748916626, + 0.10327831655740738, + 0.03625495731830597, + -0.03052496537566185, + -0.007164822891354561, + -0.1233021542429924, + -0.016860343515872955, + 0.04941525682806969, + 0.03569978103041649, + 0.005020069889724255 + ], + [ + -0.11164629459381104, + 0.15105506777763367, + 0.09701869636774063, + 0.0810626819729805, + 0.12749886512756348, + -0.0319955013692379, + -0.04619266465306282, + -0.04096680507063866, + 0.034487299621105194, + -0.04204656928777695, + -0.19515003263950348, + -0.15646955370903015, + -0.2914915978908539, + -0.05874834209680557, + -0.1132689043879509, + -0.03598152846097946, + 0.2939285337924957, + 0.5754775404930115, + 0.23100292682647705, + -0.10509742051362991, + -0.541304349899292, + 0.03866291418671608, + -0.03214070200920105, + -0.20464955270290375, + 0.061889104545116425, + -0.10301069170236588, + -0.124962218105793, + -0.06609375774860382, + 0.13707496225833893, + 0.09277550131082535, + 0.08434879779815674, + 0.10155724734067917, + -0.11806999146938324, + -0.07780826836824417, + -0.2906869947910309, + -0.21161368489265442, + 0.0009408799814991653, + 0.118462473154068, + 0.06649639457464218, + -0.0676729679107666, + 0.045548100024461746, + 0.025043629109859467, + -0.31727346777915955, + 0.03560110926628113, + 0.0801440104842186, + -0.29722660779953003, + 0.11351663619279861, + 0.10988357663154602, + -0.05790632218122482, + 0.16405612230300903, + -0.12696924805641174, + -0.027369188144803047, + 0.12681254744529724, + -0.08794545382261276, + 0.09446056932210922, + -0.014684640802443027, + -0.01649453677237034, + 0.07423200458288193, + -0.27172020077705383, + -0.40296512842178345, + 0.01592831499874592, + -0.0013568255817517638, + -0.06894074380397797, + 0.20201167464256287, + 0.0751696303486824, + -0.018957577645778656, + 0.18249477446079254, + 0.6739743947982788, + -0.11182788014411926, + -0.15168242156505585, + 0.15883298218250275, + -0.23132164776325226, + 0.0756797343492508, + -0.016765359789133072, + -0.16559961438179016, + 0.2166551798582077, + -0.021698327735066414, + -0.04414715990424156, + 0.0986928939819336, + -0.11694693565368652, + -0.2712996006011963, + -0.03708009421825409, + 0.1996261328458786, + 0.011765626259148121, + 0.09744807332754135, + -0.008408080786466599, + 0.05317723751068115, + 0.02631121501326561, + -0.34671133756637573, + -0.05676514655351639, + 0.25383031368255615, + -0.03342818841338158, + -0.9062691330909729, + -0.10456361621618271, + 0.09077282249927521, + -0.05493927001953125, + -0.029109127819538116, + 0.09489627182483673, + 0.17592523992061615, + -0.2488584667444229, + -0.028514647856354713, + -0.07406992465257645, + 0.1365910768508911, + -0.05706648901104927, + -0.14491045475006104, + -0.1994006335735321, + -0.21311986446380615, + 0.01370824221521616, + -0.3455093801021576, + -0.14846056699752808, + -0.16408434510231018, + 0.12065283209085464, + -0.3558879494667053, + -0.14080731570720673, + 0.004712911322712898, + 0.6059274673461914, + -0.0336279459297657, + -0.02742130123078823, + 0.06461355835199356, + 0.17877726256847382, + -0.5528210401535034, + 0.03950337693095207, + 0.19186638295650482, + 0.04613344371318817, + -0.21826568245887756, + -0.18291588127613068, + -0.2283824235200882, + -0.15731385350227356 + ], + [ + 0.12980566918849945, + -0.010648083873093128, + -0.26158350706100464, + 0.1521385759115219, + 0.06790915876626968, + -0.004507440607994795, + 0.12591896951198578, + 0.042173292487859726, + 0.11879072338342667, + 0.044531818479299545, + -0.34106481075286865, + -0.13566194474697113, + 0.05440749228000641, + -0.4015127718448639, + -0.21474748849868774, + 0.1313474029302597, + -0.42949533462524414, + 0.05412033572793007, + 0.02274465374648571, + 0.07306714355945587, + 0.2414691150188446, + 0.012267409823834896, + -0.01725826971232891, + -0.020107785239815712, + 0.022303052246570587, + -0.004747059661895037, + -0.1255103349685669, + -0.1674378216266632, + -0.029930897057056427, + -0.056021083146333694, + -0.029717588797211647, + 0.01119951717555523, + 0.01748896762728691, + -0.14886710047721863, + -0.13047254085540771, + -0.020692838355898857, + -0.14935362339019775, + -0.03664599359035492, + -0.15636709332466125, + 0.044494468718767166, + 0.2710318863391876, + 0.010894453153014183, + 0.35111433267593384, + -0.009558538906276226, + 0.16209878027439117, + -0.17432287335395813, + -0.2802223563194275, + 0.01656157895922661, + -0.20723950862884521, + 0.10633515566587448, + -0.3867756128311157, + -0.09485997259616852, + 0.16949862241744995, + 0.14491471648216248, + 0.1620754599571228, + -0.05009424686431885, + -0.029779210686683655, + -0.11953331530094147, + 0.010751346126198769, + 0.0020671773236244917, + -0.01434371992945671, + 0.03159945830702782, + 0.03339000418782234, + 0.016721896827220917, + -0.11544930189847946, + 0.15678374469280243, + -0.32710200548171997, + -0.018844982609152794, + 0.079367995262146, + 0.11593242734670639, + -0.10202649980783463, + 0.0951942652463913, + 0.08367416262626648, + -0.014336416497826576, + -0.06342708319425583, + 0.10386951267719269, + -0.10254890471696854, + 0.07274951785802841, + -0.0034639397636055946, + 0.008365362882614136, + 0.03845919296145439, + 0.08889131993055344, + 0.05469471216201782, + 0.043139394372701645, + 0.16031034290790558, + 0.014928297139704227, + -0.04077139124274254, + -0.06610412895679474, + 0.011744723655283451, + 0.18740214407444, + -0.1798553615808487, + 0.011072861962020397, + -0.2645839750766754, + 0.07310065627098083, + 0.07488888502120972, + -0.003921852447092533, + 0.019502058625221252, + 0.1711875945329666, + -0.0007777467253617942, + -0.08872412145137787, + -0.04599153622984886, + 0.026223482564091682, + -0.04111479967832565, + 0.14085891842842102, + 0.029653236269950867, + 0.03985505551099777, + 0.2159292995929718, + 0.4454796314239502, + 0.16199210286140442, + -0.003381438786163926, + 0.05938190221786499, + 0.03670213371515274, + 0.023748282343149185, + 0.028455479070544243, + 0.023952031508088112, + 0.05069076269865036, + 0.09028373658657074, + 0.005668729543685913, + -0.06579168140888214, + 0.06033523008227348, + 0.08485228568315506, + -0.1524336189031601, + 0.12803098559379578, + -0.044908516108989716, + -0.06879984587430954, + -0.07449499517679214, + 0.0857132151722908, + -0.1535177379846573 + ], + [ + -0.07883087545633316, + -0.10804041475057602, + 0.1816711574792862, + 0.05030537024140358, + -0.02058379538357258, + 0.028147835284471512, + -0.0026933837216347456, + -0.09386476874351501, + 0.059356946498155594, + 0.016371026635169983, + 0.1398944854736328, + 0.1731071025133133, + -0.11059224605560303, + 0.11256727576255798, + -0.46799972653388977, + 0.03961014747619629, + 0.15416032075881958, + -0.019011976197361946, + 0.16369004547595978, + 0.01502049807459116, + -0.3820514976978302, + 0.10203112661838531, + 0.046229779720306396, + 0.1281808465719223, + 0.03812706843018532, + -0.06598834693431854, + 0.01704377681016922, + -0.19543801248073578, + 0.02855488657951355, + -0.0012100775493308902, + 0.07636778801679611, + -0.01885504275560379, + 0.15408951044082642, + 0.05460058152675629, + -0.09078076481819153, + -0.08731397986412048, + -0.0178412776440382, + 0.009529132395982742, + -0.02196154184639454, + 0.05682351067662239, + 0.012602673843502998, + 0.03788965195417404, + -0.1945255994796753, + 0.09411356598138809, + -0.2128574103116989, + -0.15137924253940582, + 0.10154969990253448, + 0.0039481851272284985, + -0.0760086178779602, + 0.08458138257265091, + 0.15712343156337738, + -0.15465262532234192, + -0.08671475946903229, + -0.1222393661737442, + -0.08581878989934921, + 0.048469178378582, + -0.03092498704791069, + 0.120295450091362, + 0.019471729174256325, + 0.12445582449436188, + -0.0002412160101812333, + -0.07147208601236343, + 0.025575710460543633, + -0.03206673264503479, + 0.0060219066217541695, + 0.010328374803066254, + 0.031135231256484985, + 0.09292185306549072, + 0.06541694700717926, + -0.07232216745615005, + -0.025525709614157677, + 0.1454779952764511, + 0.009013992734253407, + -0.14307358860969543, + 0.11549556255340576, + -0.17205211520195007, + 0.21330943703651428, + -0.01931619830429554, + -0.05484173819422722, + -0.01539616845548153, + -0.0749770924448967, + -0.017893634736537933, + -0.04368395730853081, + 0.04135642945766449, + -0.04186986759305, + -0.10097698867321014, + 0.05810542404651642, + 0.10213780403137207, + 0.0406399630010128, + -0.0025311154313385487, + -0.24844668805599213, + 0.1340441107749939, + -0.31990283727645874, + 0.004675612319260836, + 0.0164024718105793, + -0.00191535335034132, + 0.006479678675532341, + -0.06633109599351883, + 0.15401610732078552, + -0.02929462119936943, + 0.10599207133054733, + 0.12774427235126495, + -0.02852899394929409, + 0.08546929806470871, + -0.08496031910181046, + -0.003923008218407631, + -0.07987330108880997, + -0.21283338963985443, + -0.18863262236118317, + -0.02976061776280403, + -0.022594358772039413, + 0.024058399721980095, + 0.007697511464357376, + 0.01994980126619339, + -0.19354231655597687, + 0.09053049981594086, + -0.3502095341682434, + -0.1018480509519577, + 0.12027610093355179, + -0.02503804676234722, + -0.09151419997215271, + -0.027944346889853477, + 0.010775971226394176, + 0.16753730177879333, + -0.03857749328017235, + 0.10553496330976486, + 0.11496828496456146, + 0.0340392105281353 + ], + [ + 0.11624166369438171, + -0.01571604236960411, + 0.055150873959064484, + -0.06317383050918579, + -0.08703821152448654, + -0.08720247447490692, + -0.559186577796936, + 0.06004374474287033, + 0.1289820820093155, + 0.004837502725422382, + -0.3999999463558197, + -0.515246570110321, + 0.4138813316822052, + 0.12979130446910858, + -0.005172504112124443, + 0.0681481882929802, + -0.06546269357204437, + 0.3748612701892853, + 0.28702783584594727, + 0.38527384400367737, + 0.43039771914482117, + -0.011079145595431328, + -0.19132556021213531, + -0.1232079267501831, + -0.02443065494298935, + -0.25439900159835815, + -0.04770185425877571, + 0.1642468124628067, + 0.002528670011088252, + -0.12973152101039886, + 0.16487818956375122, + -0.06454241275787354, + 0.10422591120004654, + -0.07241131365299225, + 0.2096889168024063, + 0.0955149456858635, + -0.10111594945192337, + -0.07672996819019318, + -0.4653593897819519, + -0.032145772129297256, + 0.07852419465780258, + 0.04060721397399902, + 0.019509170204401016, + 0.021607957780361176, + 0.13177554309368134, + -0.16385455429553986, + -0.06875932216644287, + -0.04431833326816559, + 0.04098524525761604, + -0.1542675644159317, + 0.15816128253936768, + -0.3721161484718323, + 0.03207479789853096, + -0.13583774864673615, + 0.17195332050323486, + -0.1444127857685089, + 0.030313819646835327, + 0.2632273733615875, + -0.30573147535324097, + 0.15116849541664124, + -0.15042588114738464, + 0.19506770372390747, + -0.12220057100057602, + 0.03104354254901409, + -0.5907121896743774, + 0.01538354717195034, + -0.08873238414525986, + -0.27525201439857483, + -0.0860881358385086, + 0.13073845207691193, + 0.1127089112997055, + 0.007789279334247112, + -0.8492324948310852, + -0.17791880667209625, + -0.1550365835428238, + 0.011879228055477142, + -0.06764309108257294, + 0.23961757123470306, + 0.37707704305648804, + 0.038580797612667084, + -0.05314377695322037, + -0.17047083377838135, + 0.08967919647693634, + 0.28448131680488586, + 0.41862213611602783, + -0.35943540930747986, + 0.07687916606664658, + 0.3043081760406494, + -0.1421555131673813, + -0.008262289687991142, + 0.21802853047847748, + -0.12047397345304489, + -0.5514250993728638, + -0.24352504312992096, + -0.20739121735095978, + 0.0384545773267746, + 0.30992016196250916, + 0.15926837921142578, + -0.1042383462190628, + -0.32362040877342224, + -0.34364497661590576, + 0.43160298466682434, + 0.09140705317258835, + 0.19288015365600586, + -1.0271941423416138, + -0.2545274496078491, + -0.2871151566505432, + -0.04322255775332451, + 0.0037722319830209017, + 0.257635235786438, + 0.3313235938549042, + -0.011471675708889961, + 0.3620432913303375, + 0.1522638499736786, + 0.3640832304954529, + -0.2083352953195572, + -0.2241700440645218, + 0.01692509651184082, + -0.02049091085791588, + 0.083431176841259, + -0.042351651936769485, + -0.08233878761529922, + -0.21785210072994232, + 0.4199874997138977, + 0.006673484109342098, + 0.26084187626838684, + -0.12582415342330933, + -0.045637667179107666 + ], + [ + -0.09767929464578629, + -0.02646580897271633, + -0.02548760548233986, + -0.37020596861839294, + 0.0378107950091362, + 0.16748683154582977, + 0.1685241311788559, + 0.1537238359451294, + -0.14425452053546906, + 0.2700742185115814, + 0.0596657432615757, + 0.029250971972942352, + -0.25435182452201843, + 0.07161706686019897, + -0.16305822134017944, + 0.2718462347984314, + -0.22415445744991302, + -0.06677820533514023, + 0.056159839034080505, + -0.13023880124092102, + -0.032388005405664444, + -0.08899647742509842, + -0.1781005561351776, + 0.19714532792568207, + -0.2311280220746994, + 0.08445937931537628, + -0.045824915170669556, + -0.0017121504060924053, + -0.33260414004325867, + 0.04943729564547539, + -0.07386311888694763, + 0.1724644899368286, + -0.08592069149017334, + 0.17305625975131989, + 0.007955878041684628, + -0.09980817139148712, + 0.03475431725382805, + 0.05254169926047325, + 0.03976953402161598, + 0.09794768691062927, + -0.1415271759033203, + 0.19170264899730682, + -0.03711928427219391, + -0.1178331971168518, + -0.3025088906288147, + -0.054161231964826584, + 0.15518106520175934, + 0.04724365472793579, + 0.12686321139335632, + 0.034326646476984024, + 0.0503666028380394, + 0.007065047975629568, + -0.3584679663181305, + 0.046997759491205215, + -0.10511962324380875, + 0.138271763920784, + 0.004238039255142212, + 0.0754486471414566, + 0.13247737288475037, + 0.2191682606935501, + -0.031811319291591644, + -0.09962992370128632, + 0.11258156597614288, + -0.23694868385791779, + -0.1538933366537094, + -0.17110522091388702, + 0.0240901131182909, + -0.554036021232605, + 0.1116071343421936, + -0.06416866183280945, + 0.10978896915912628, + 0.1295633316040039, + 0.07489017397165298, + -0.03071265108883381, + -0.040281910449266434, + -0.2966133952140808, + 0.06645472347736359, + -0.08449490368366241, + 0.03005966730415821, + -0.06931386888027191, + -0.46020254492759705, + 0.020355116575956345, + -0.08875144273042679, + -0.01933923363685608, + -0.06929413974285126, + 0.005526193417608738, + -0.005028835032135248, + 0.08662579208612442, + -0.044920071959495544, + -0.8463559150695801, + -0.050016578286886215, + 0.5585256814956665, + -0.2755867838859558, + 0.015931742265820503, + -0.07500067353248596, + 0.039979442954063416, + 0.004177835304290056, + -0.39306432008743286, + -0.16676579415798187, + -0.18732644617557526, + -0.000799868896137923, + -0.131780207157135, + 0.10766572505235672, + 0.033867307007312775, + -0.01042663212865591, + 0.08592263609170914, + -0.07332299649715424, + -0.02247733250260353, + -0.06473665684461594, + -0.03810109943151474, + -0.09914673119783401, + 0.11249876022338867, + 0.30195072293281555, + 0.018936045467853546, + 0.015083040110766888, + 0.4338112473487854, + -0.14515697956085205, + -0.24012601375579834, + 0.10155973583459854, + -0.04951704666018486, + -0.1673204004764557, + 0.018459811806678772, + -0.10294149070978165, + -0.2198963165283203, + -0.1505623310804367, + 0.0688878670334816, + 0.018406838178634644, + 0.05594385042786598 + ], + [ + -0.12746946513652802, + 0.024723824113607407, + -0.0376197025179863, + 0.021044239401817322, + 0.0011405112454667687, + -0.04892275482416153, + 0.003858888288959861, + 0.10181489586830139, + 0.10485230386257172, + -0.03406596556305885, + 0.15208466351032257, + 0.12345488369464874, + 0.060594238340854645, + -0.14685428142547607, + 0.028292110189795494, + -0.1468980610370636, + 0.03351476788520813, + 0.006978187244385481, + 0.2556180953979492, + 0.1519002467393875, + 0.10329752415418625, + 0.06649171561002731, + 0.007208199240267277, + 0.1388019174337387, + -0.01975197345018387, + 0.027535369619727135, + 0.10498390346765518, + 0.06343314796686172, + -0.14083239436149597, + 0.13760565221309662, + 0.013929623179137707, + -0.01909775100648403, + -0.19026914238929749, + 0.02120637334883213, + 0.05106830224394798, + -0.14571569859981537, + -0.11374138295650482, + 0.017053967341780663, + 0.11531165987253189, + -0.010985620319843292, + 0.06688769906759262, + 0.01022972259670496, + 0.038309309631586075, + 0.010309632867574692, + -0.07820086181163788, + -0.038133423775434494, + 0.03997375816106796, + -0.06989584863185883, + 0.13131941854953766, + 0.16958771646022797, + -0.03026803582906723, + 0.04541432484984398, + -0.11246780306100845, + -0.01870913989841938, + 0.09264366328716278, + -0.035363294184207916, + -0.11365361511707306, + 0.03595827519893646, + -0.06666871905326843, + 0.06897125393152237, + -0.09781718254089355, + -0.01970064267516136, + -0.05191226676106453, + -0.0029533582273870707, + 0.05745602771639824, + -0.0934310182929039, + 0.0695151537656784, + 0.07759737223386765, + -0.08607800304889679, + 0.017747079953551292, + -0.0304858461022377, + 0.04644801840186119, + -0.017055166885256767, + -0.015288762748241425, + 0.021949302405118942, + -0.08836259692907333, + 0.03136719763278961, + -0.11182188987731934, + 0.05890974774956703, + -0.021940503269433975, + 0.0756509080529213, + -0.06306964159011841, + 0.015943702310323715, + 0.16031193733215332, + 0.07178078591823578, + 0.0294327475130558, + -0.02170618437230587, + -0.040162473917007446, + -0.04722602665424347, + -0.042037613689899445, + 0.06592158228158951, + 0.055321935564279556, + -0.041841473430395126, + -0.03193536773324013, + 0.005528357345610857, + 0.07812253385782242, + 0.04115033894777298, + -0.0454261489212513, + 0.11569187790155411, + -0.048677898943424225, + 0.10013031959533691, + -0.054694224148988724, + -0.052041396498680115, + 0.03838324546813965, + 0.07824435830116272, + -0.10582361370325089, + -0.26646795868873596, + -0.1206146627664566, + 0.03230404853820801, + 0.00307282991707325, + -0.008678714744746685, + 0.22847716510295868, + 0.07282295823097229, + 0.06895799189805984, + 0.03233928605914116, + -0.08143479377031326, + -0.07896750420331955, + 0.09057149291038513, + 0.05636338144540787, + 0.10702451318502426, + -0.011341196484863758, + 0.020170027390122414, + 0.05724503844976425, + 0.1362958550453186, + -0.09090482443571091, + 0.005083740688860416, + 0.08164390921592712, + 0.09439848363399506 + ], + [ + -0.008619899861514568, + 0.02950391359627247, + 0.041573114693164825, + -0.03377021849155426, + -0.22733448445796967, + -0.009479114785790443, + -0.09811480343341827, + 0.09123116731643677, + 0.0176051277667284, + 0.10825192928314209, + 0.14220422506332397, + 0.10346661508083344, + 0.057383328676223755, + -0.09473565965890884, + -0.0729885920882225, + -0.5289965271949768, + 0.017915483564138412, + -0.2828711271286011, + 0.09230373054742813, + 0.08629033714532852, + 0.34277230501174927, + -0.029111536219716072, + -0.32557961344718933, + -0.03589485213160515, + -0.2188313752412796, + -0.0635717585682869, + 0.01615806482732296, + 0.08041473478078842, + 0.11144591122865677, + 0.16756732761859894, + 0.08115367591381073, + 0.03625577688217163, + -0.04937059432268143, + 0.020722832530736923, + 0.007699364330619574, + 0.009437430649995804, + 0.0009414675878360868, + 0.09586779773235321, + 0.037508103996515274, + -0.0634361132979393, + -0.09604517370462418, + -0.14146415889263153, + -0.08024024963378906, + -0.0303496140986681, + -0.07836363464593887, + -0.21103283762931824, + 0.18571089208126068, + -0.06084670498967171, + 0.14082282781600952, + -0.1406082957983017, + -0.09961295872926712, + 0.0821763277053833, + 0.028768833726644516, + -0.034922126680612564, + -0.010712186805903912, + 0.12908026576042175, + 0.22368402779102325, + 0.10746410489082336, + -0.16835224628448486, + 0.1845204383134842, + 0.03438553959131241, + 0.04837709292769432, + 0.04383551701903343, + 0.0748986005783081, + 0.052893031388521194, + 0.03615095093846321, + 0.015149577520787716, + 0.08199726045131683, + -0.17325888574123383, + -0.19667865335941315, + 0.065639927983284, + 0.05473312735557556, + 0.05926407128572464, + -0.10390913486480713, + -0.20850573480129242, + 0.0373510867357254, + 0.005822872743010521, + 0.15684835612773895, + -0.07239682227373123, + -0.1928108185529709, + -0.32712528109550476, + -0.10599428415298462, + 0.08284176886081696, + -0.1319519281387329, + -0.0751095712184906, + 0.0451040156185627, + 0.2729843854904175, + -0.024619217962026596, + -0.012405910529196262, + 0.005724112968891859, + 0.012200315482914448, + 0.4910992681980133, + -0.2716349959373474, + -0.0014599432470276952, + -0.007098309695720673, + -0.029435016214847565, + 0.041705530136823654, + -0.05379387363791466, + 0.09218251705169678, + -0.027666321024298668, + -0.09147574007511139, + -0.11111937463283539, + -0.2074972540140152, + 0.09940126538276672, + -0.1570773869752884, + -0.09040587395429611, + -0.1201348826289177, + 0.14941321313381195, + -0.19362638890743256, + -0.06515025347471237, + 0.016433952376246452, + -0.04331999272108078, + 0.33345547318458557, + -0.03530190885066986, + 0.13463129103183746, + -0.15002213418483734, + 0.3075539767742157, + 0.09617678076028824, + 0.1323065161705017, + 0.0900944322347641, + -0.0746457502245903, + -0.03389609977602959, + 0.006052124314010143, + 0.18295727670192719, + -0.23882946372032166, + -0.1359127312898636, + 0.0661553218960762, + 0.26008161902427673 + ], + [ + 0.275935560464859, + 0.1652606576681137, + -0.19000062346458435, + 0.15897560119628906, + -0.037145063281059265, + 0.11762473732233047, + -0.1431088149547577, + -0.4287289083003998, + -0.023353515192866325, + 0.0029914032202214003, + 0.19956910610198975, + -0.355596661567688, + -0.12636777758598328, + -0.4502655267715454, + -0.09180711954832077, + 0.010518644005060196, + 0.11942804604768753, + 0.034093502908945084, + 0.2288271188735962, + 0.2261287122964859, + 0.2871485650539398, + -0.1853117048740387, + -0.008599755354225636, + -0.18058960139751434, + 0.060403987765312195, + 0.2323015183210373, + -0.38637328147888184, + -0.11449313908815384, + -0.6314042806625366, + 0.22713477909564972, + -0.3727354407310486, + 0.4686967730522156, + -0.04629014432430267, + -0.019582515582442284, + 0.08101990818977356, + 0.04716792702674866, + -0.11164221167564392, + -0.3087153136730194, + -0.33557286858558655, + 0.006678826175630093, + 0.06455597281455994, + -0.3527022898197174, + -0.2719956040382385, + 0.024951523169875145, + -0.14401434361934662, + -0.13791276514530182, + 0.15103012323379517, + 0.11955331265926361, + 0.1766151785850525, + 0.0664343386888504, + -1.0029864311218262, + 0.04064562916755676, + 0.030789313837885857, + 0.06264917552471161, + 0.10574875771999359, + 0.19984391331672668, + 0.11182354390621185, + 0.24388115108013153, + 0.024383431300520897, + 0.07221149653196335, + -0.03310650959610939, + 0.05348244309425354, + -0.021915512159466743, + 0.1808372586965561, + 0.154388889670372, + -0.09497803449630737, + -0.009944372810423374, + 0.013318709097802639, + -0.07390138506889343, + 0.03921384736895561, + 0.100347138941288, + 0.08459543436765671, + 0.15930426120758057, + -0.15386484563350677, + 0.14959901571273804, + -0.14557719230651855, + 0.14275594055652618, + -0.2637668251991272, + -0.0537969172000885, + -0.12310314923524857, + -0.026928899809718132, + -0.018619541078805923, + -0.04153279960155487, + 0.09819190204143524, + 0.0714327022433281, + 0.06667140126228333, + 0.04973718523979187, + 0.11347902566194534, + -0.06612741947174072, + 0.3952409029006958, + 0.38254809379577637, + 0.10156295448541641, + 0.08729171752929688, + 0.0005177009734325111, + -0.06880532205104828, + 0.09628837555646896, + -0.2572055160999298, + 0.07991338521242142, + 0.17876414954662323, + -0.06502752006053925, + -0.05755683779716492, + 0.19448959827423096, + 0.04040355607867241, + -0.3619987666606903, + -0.19416801631450653, + -0.2359343320131302, + -0.34764689207077026, + -0.141602024435997, + -0.08679439127445221, + 0.11009471118450165, + -0.03969990834593773, + -1.770026445388794, + 0.07601990550756454, + -0.1249859556555748, + -0.0063919867388904095, + -0.6881052851676941, + -0.032630015164613724, + -0.11964694410562515, + 0.1878069043159485, + -0.5467861890792847, + -0.26607340574264526, + -0.345798134803772, + 0.03436001017689705, + 0.009500943124294281, + 0.3526684045791626, + -0.38263407349586487, + -0.03845313563942909, + -0.2561958134174347 + ], + [ + 0.02119838073849678, + 0.006122015882283449, + -0.19972746074199677, + 0.0331338495016098, + 0.06285413354635239, + 0.19839166104793549, + -0.08385197073221207, + 0.02966902405023575, + 0.03716616332530975, + 0.24646444618701935, + 0.06929785013198853, + 0.06817226111888885, + 0.08643137663602829, + -0.11162200570106506, + -0.14698950946331024, + 0.07015752792358398, + 0.3245279788970947, + 0.05342041328549385, + -0.18306873738765717, + -0.16903308033943176, + -0.04020591825246811, + 0.007130536716431379, + -0.20360428094863892, + 0.13609719276428223, + 0.13604092597961426, + -0.07449481636285782, + -0.2029234915971756, + 0.03909219428896904, + 0.061778753995895386, + -0.022083034738898277, + -0.005375449080020189, + -0.008014907129108906, + 0.0024300767108798027, + -0.0035792342387139797, + 0.04054637625813484, + 0.06398648023605347, + -0.18860873579978943, + 0.07875309884548187, + -0.052270010113716125, + 0.0835486352443695, + 0.037376563996076584, + -0.11308088153600693, + 0.328698992729187, + 0.11984138935804367, + 0.20148572325706482, + -0.08167455345392227, + 0.019305387511849403, + 0.06239515170454979, + -0.12433987110853195, + 0.1459835022687912, + 0.005868660751730204, + 0.02645665593445301, + 0.10151762515306473, + -0.0014670017408207059, + 0.12013545632362366, + -0.020266445353627205, + -0.20025356113910675, + -0.018666699528694153, + 0.05820460990071297, + -0.07696835696697235, + -0.07306207716464996, + -0.11976257711648941, + -0.0647280365228653, + 0.00804979633539915, + 0.07356103509664536, + 0.028761694207787514, + -0.35542935132980347, + 0.18447689712047577, + 0.08331208676099777, + 0.10473587363958359, + -0.042404115200042725, + -0.05478600412607193, + -0.07303871959447861, + -0.19149042665958405, + -0.13379709422588348, + -0.10448404401540756, + 0.11113845556974411, + -0.01602412573993206, + 0.06736055016517639, + 0.10649147629737854, + 0.02224687859416008, + 0.06182729825377464, + -0.13068877160549164, + -0.04639412835240364, + 0.11108597368001938, + -0.03905295953154564, + -0.06113893911242485, + -0.030884385108947754, + 0.01946406625211239, + -0.03005182556807995, + 0.45347192883491516, + 0.15264509618282318, + -0.11278238892555237, + 0.14507387578487396, + 0.10473841428756714, + -0.3255913257598877, + 0.14445705711841583, + -0.04275968298316002, + -0.17154011130332947, + -0.06325735151767731, + 0.08711835741996765, + -0.06514601409435272, + 0.047180671244859695, + 0.0415475033223629, + 0.06802841275930405, + 0.10720280557870865, + -0.0153557313606143, + -0.1618494987487793, + 0.06409239768981934, + 0.15348462760448456, + 0.12096192687749863, + 0.12055931240320206, + -0.06515578180551529, + 0.08273628354072571, + -0.0974465161561966, + -0.0534808374941349, + -0.23102650046348572, + -0.010547109879553318, + 0.1040920615196228, + -0.19188295304775238, + -0.02869671955704689, + -0.1751348078250885, + 0.17289960384368896, + -0.00567677291110158, + 0.007491199765354395, + 0.014689347706735134, + -0.1377055048942566, + -0.26718634366989136 + ], + [ + -0.032156433910131454, + 0.1121002584695816, + 0.07099778950214386, + -0.26680290699005127, + -0.028496038168668747, + -0.03010357730090618, + 0.019727807492017746, + -0.013099674135446548, + -0.1980319619178772, + 0.04483691602945328, + 0.24775198101997375, + -0.16583071649074554, + -1.2298603057861328, + -0.16207726299762726, + 0.044226083904504776, + 0.1750195175409317, + -0.2546505928039551, + -0.05247771739959717, + 0.1483478546142578, + 0.10165983438491821, + 0.08422192186117172, + -0.05736769735813141, + 0.0764559954404831, + -0.07861950993537903, + 0.1392192542552948, + 0.14048291742801666, + 0.0007231808849610388, + -0.23195523023605347, + -0.4298853278160095, + -0.15191100537776947, + -0.12717485427856445, + 0.1372087001800537, + 0.16509756445884705, + -0.050060782581567764, + 0.06400089710950851, + -0.5898275971412659, + -0.2345123291015625, + -0.1957385241985321, + 0.08917835354804993, + 0.13834910094738007, + -0.08896128833293915, + 0.10082324594259262, + 0.1733614206314087, + 0.07996395975351334, + -0.016322122886776924, + -0.16024146974086761, + -0.2694755792617798, + -0.07095825672149658, + 0.15098927915096283, + 0.1756410002708435, + -0.22151359915733337, + 0.09296814352273941, + -0.13343748450279236, + 0.07943455129861832, + 0.03684939816594124, + 0.17885737121105194, + -0.024877041578292847, + -0.02647467330098152, + -0.06720030307769775, + 0.09838120639324188, + -0.12644055485725403, + 0.04072877764701843, + 0.07018515467643738, + 0.14387819170951843, + -0.08674003183841705, + -0.06945402920246124, + 0.13108867406845093, + -0.1458662450313568, + 0.011609703302383423, + 0.09273232519626617, + 0.17020772397518158, + -0.03558893874287605, + 0.026508856564760208, + -0.031576890498399734, + -0.03739458695054054, + 0.021177448332309723, + -0.029170330613851547, + -0.060975439846515656, + -0.1097940057516098, + -0.10203050076961517, + 0.16018156707286835, + -0.08754288405179977, + 0.008768152445554733, + 0.0825934037566185, + 0.22969521582126617, + 0.07537299394607544, + 0.009448202326893806, + -0.0251519363373518, + -0.06465736776590347, + -0.07919053733348846, + 0.07797374576330185, + 0.13599343597888947, + -0.007772950455546379, + 0.012574764899909496, + 0.01997946761548519, + -0.04317529872059822, + -0.15461091697216034, + 0.003913807682693005, + 0.05646957829594612, + -0.20896732807159424, + -0.013078508898615837, + 0.1651305854320526, + 0.08759237825870514, + 0.190042644739151, + -0.2608363628387451, + -0.11361437290906906, + 0.1781146377325058, + -0.1007465049624443, + 0.023202743381261826, + 0.15325108170509338, + 0.06094760447740555, + -0.30479925870895386, + 0.19822239875793457, + -0.06570911407470703, + -0.05179695039987564, + -0.12916797399520874, + 0.018564024940133095, + -0.00674356147646904, + 0.133587047457695, + 0.16908606886863708, + -0.0008576909312978387, + 0.015966150909662247, + 0.006275718566030264, + 0.07083070278167725, + 0.1428391933441162, + 0.04436357691884041, + 0.11945687234401703, + -0.19663044810295105 + ], + [ + -0.022856557741761208, + 0.04487277567386627, + -0.05677424743771553, + -0.06255180388689041, + -0.02123691327869892, + -0.0012819505063816905, + -0.04396839439868927, + -0.04157068580389023, + 0.09559015929698944, + -0.12035655975341797, + -0.0199015811085701, + 0.0275283120572567, + 0.09828685969114304, + -0.018498536199331284, + 0.06405064463615417, + 0.049634408205747604, + 0.03303837776184082, + 0.027570385485887527, + 0.1688777506351471, + 0.07480121403932571, + 0.04290056973695755, + -0.04273523390293121, + 0.005349800921976566, + -0.024258479475975037, + 0.04584629088640213, + -0.06074584275484085, + 0.07113653421401978, + -0.008897927589714527, + 0.08160443603992462, + 0.127738818526268, + 0.07444062829017639, + -0.04685695841908455, + -0.12552885711193085, + -0.000905435299500823, + 0.05252488702535629, + -0.0008776640170253813, + 0.00025598154752515256, + 0.04184594750404358, + 0.05445915833115578, + -0.029792984947562218, + -0.019995179027318954, + 0.020275073125958443, + -0.0108407661318779, + 0.04892155900597572, + -0.033022619783878326, + -0.02463371679186821, + 0.10228396952152252, + 0.05368858575820923, + 0.11179719865322113, + 0.08611889183521271, + -0.04666087031364441, + -0.0514596551656723, + 0.0202898308634758, + -0.13701915740966797, + -0.00905620027333498, + -0.014576721005141735, + 0.00265760556794703, + -0.015653185546398163, + 0.028510140255093575, + -0.00882194098085165, + -0.12436816841363907, + -0.05838567763566971, + -0.008891049772500992, + -0.0443514809012413, + 0.05052797868847847, + 0.06339982897043228, + 0.11926479637622833, + 0.05656243488192558, + 0.0043640779331326485, + -0.009952029213309288, + 0.06414405256509781, + 0.03272782266139984, + 0.07020559906959534, + -0.05609814077615738, + -0.10609102249145508, + -0.05953008681535721, + 0.10224830359220505, + 0.025717739015817642, + -0.00024869816843420267, + -0.056671902537345886, + -0.047084614634513855, + 0.06979303807020187, + 0.010882535018026829, + 0.008338131941854954, + -0.0077162026427686214, + -0.03440956398844719, + -0.00133825046941638, + -0.07306027412414551, + -0.08648783713579178, + 0.0470319502055645, + 0.044719040393829346, + 0.10532621294260025, + -0.10298075526952744, + 0.10043300688266754, + -0.07484367489814758, + 0.07906537503004074, + -0.061359304934740067, + 0.06187963858246803, + -0.008959894068539143, + 0.10647768527269363, + -0.027124937623739243, + -0.07867374271154404, + 0.03543009236454964, + -0.11053677648305893, + 0.038374435156583786, + 0.01329584326595068, + -0.10386889427900314, + -0.06739332526922226, + -0.003415660234168172, + -0.08784373104572296, + -0.07695187628269196, + 0.037762489169836044, + 0.029262980446219444, + -0.01627308316528797, + 0.07368969172239304, + -0.0014174157986417413, + -0.06133376806974411, + 0.04289627820253372, + 0.03101777844130993, + -0.03650839999318123, + 0.032930418848991394, + 0.013213892467319965, + 0.04695914313197136, + 0.04289361461997032, + -0.027238616719841957, + -0.0064278109930455685, + -0.026891781017184258, + 0.031789012253284454 + ], + [ + -0.004749776795506477, + -5.360155046219006e-05, + -0.0027947176713496447, + -0.0032766100484877825, + -0.0008638365543447435, + -0.0009035306866280735, + -0.00038527260767295957, + 0.001215755706652999, + -0.0017604364547878504, + 0.0024402840062975883, + -0.0004977608332410455, + 1.0542900781729259e-05, + -3.045215635211207e-07, + -2.2582747988053598e-05, + -0.0006901302258484066, + 7.118785106285941e-06, + -2.049406866433401e-09, + -0.00041168846655637026, + 3.724958341777551e-09, + 3.2313307656295365e-06, + -2.2463311779574724e-06, + 0.005032592918723822, + -0.002087303902953863, + -0.00029537369846366346, + 2.938290526799392e-05, + -6.309221589617664e-07, + -0.0015364978462457657, + -0.0023707877844572067, + -0.0012628973927348852, + -7.102676136128139e-06, + -0.00015057598648127168, + -3.30837101500947e-05, + -0.00020321470219641924, + -0.00014515641669277102, + 8.593955863034353e-05, + 3.4707920804066816e-06, + -0.00045834240154363215, + -0.0014739976031705737, + 0.0005632165702991188, + -2.5197548893629573e-05, + -0.0003651680308394134, + -0.0015271867159754038, + 1.8328381656829151e-06, + -0.00024410152400378138, + -0.00035790441324934363, + -6.62324673612602e-05, + -0.00010202339763054624, + -0.0010405067587271333, + -0.00022129612625576556, + 4.61888163272306e-07, + 4.5915225200587884e-05, + -0.0053244782611727715, + 0.00023759211762808263, + -1.293276909564156e-06, + 2.912622585427016e-05, + -0.0008311775745823979, + -0.0005812984309159219, + -0.0008955416851677, + -0.0006573451682925224, + -0.000917591736651957, + -0.0007255348027683794, + -0.001728262985125184, + -0.0025877775624394417, + 1.959160584874553e-07, + -0.00014723208732903004, + 0.0006007168558426201, + -4.986745352653088e-06, + 4.32345335205607e-14, + -0.00032041288795880973, + -0.0009070870000869036, + -0.000769883394241333, + -0.00022777530830353498, + -0.0013800961896777153, + -0.0012436407851055264, + -0.0004152007168158889, + 0.00025631595053710043, + 0.00048309782869182527, + 2.1284629838191904e-05, + -0.0015570248942822218, + 5.9108413552166894e-05, + -0.00030266045359894633, + 0.00013385791680775583, + -0.0007740167784504592, + 7.178285886766389e-05, + -0.00013858955935575068, + -0.00063857133500278, + -0.00721492525190115, + -0.00040520037873648107, + -1.4049944184080232e-05, + 0.00087831070413813, + -0.0005532990908250213, + -0.0006217529298737645, + -0.00035008281702175736, + -2.3029817384667695e-05, + -0.0008767739636823535, + -0.00015826871094759554, + -0.003996041603386402, + -0.0018523423932492733, + -0.0001028352344292216, + -0.0006587653770111501, + -0.002557189203798771, + -7.211663614725694e-05, + -0.00016773022070992738, + -0.00019901136693079025, + -0.0002744705998338759, + -0.0023915800265967846, + 0.0004756658454425633, + -0.0004359026497695595, + -7.605910650454462e-05, + -2.8242680855328217e-05, + -0.00043340426054783165, + 0.0006144908838905394, + 4.494909717323026e-06, + -0.0008624817128293216, + 0.0011404610704630613, + -9.516581485513598e-05, + 6.14068703725934e-05, + -0.005436489824205637, + -0.002297725062817335, + -3.649156860774383e-05, + 0.00029152812203392386, + -0.0003130583500023931, + -0.00029568737954832613, + -1.4830204619897813e-08, + -2.475851488270564e-07, + -0.0012207728577777743, + -0.0004876990569755435, + -0.00011673672997858375 + ], + [ + -0.0009659687639214098, + 0.045937541872262955, + -0.030188847333192825, + -0.051727816462516785, + -0.04918219521641731, + -0.019897419959306717, + 0.006848420947790146, + -0.009278587065637112, + 0.027427490800619125, + -0.07886139303445816, + 0.06559595465660095, + 0.06274643540382385, + 0.01668153516948223, + -0.015285590663552284, + -0.00920771062374115, + 0.0030949260108172894, + 0.00882281269878149, + -0.025673357769846916, + 0.017734529450535774, + -0.0003367047756910324, + -0.018354620784521103, + 0.02070082351565361, + -0.004740834701806307, + -0.011495983228087425, + -0.01224990002810955, + -0.05208698287606239, + 0.0790492370724678, + 0.04094821587204933, + -0.02649003639817238, + -0.009379874914884567, + 0.029234370216727257, + -0.009284519590437412, + 0.011224743910133839, + 0.050322361290454865, + 0.056622836738824844, + 0.011170273646712303, + -0.009114030748605728, + -0.03423908352851868, + -0.0027833774220198393, + 0.06740715354681015, + -0.0718802735209465, + 0.03713907673954964, + -0.011839720420539379, + 0.03924999013543129, + -0.04202329367399216, + -0.01220724731683731, + -0.0021672940347343683, + -0.006062021013349295, + 0.00022312799410428852, + 0.000876422505825758, + -0.05139923095703125, + 0.06723244488239288, + -0.02662120945751667, + -0.007329009938985109, + 0.05603739246726036, + 0.0168553926050663, + -0.008066878654062748, + 0.009327257052063942, + -0.04043477028608322, + -0.003708562580868602, + -0.09111032634973526, + -0.03659063205122948, + -0.04575714096426964, + -0.026567094027996063, + -0.012945499271154404, + 0.04821668565273285, + 0.11087341606616974, + -0.004760699812322855, + -0.011431105434894562, + -0.0019724073354154825, + -0.028743714094161987, + -0.09445206820964813, + -0.016284234821796417, + 0.027125846594572067, + -0.05462346598505974, + -0.09789221733808517, + 0.035306140780448914, + 0.03787245228886604, + -0.03248464688658714, + -0.03321496769785881, + 0.012496530078351498, + 0.041937947273254395, + 0.013050006702542305, + 0.01693713106215, + -0.07966431230306625, + -0.02545466274023056, + -0.012346772477030754, + 0.007580060977488756, + -0.038773342967033386, + 0.0003212405426893383, + -0.006084801163524389, + 0.04436745122075081, + -0.06606435030698776, + -0.03705325722694397, + 0.025862237438559532, + -0.002331853611394763, + -0.045494552701711655, + -0.01780787855386734, + 0.007346542552113533, + 0.05293174088001251, + 0.04766920208930969, + 0.00852229818701744, + -0.022024234756827354, + 0.04152403399348259, + 0.012117693200707436, + -0.02248343639075756, + -0.032710589468479156, + -0.012539537623524666, + -0.036493271589279175, + 0.04236834868788719, + 0.01045633852481842, + -0.02241237461566925, + 0.03259315714240074, + -0.030569640919566154, + 0.0017610013019293547, + -0.05094379186630249, + 0.0385771319270134, + 0.04215108975768089, + -0.00023095292272046208, + 0.032588791102170944, + 0.029889550060033798, + -0.06679310649633408, + 0.059742145240306854, + 0.007210161071270704, + -0.006186027079820633, + 0.013310938142240047, + 0.04370136931538582, + 0.0156700536608696 + ], + [ + -0.05412008613348007, + 0.010381434112787247, + 0.021653344854712486, + -0.04477553069591522, + -0.12003907561302185, + -0.09462250769138336, + -0.0983566865324974, + 0.06516776978969574, + -0.04503883421421051, + -0.06807953864336014, + 0.06831184774637222, + 0.12044505774974823, + 0.0795726627111435, + -0.09406139701604843, + -0.2198992669582367, + 0.0037924384232610464, + 0.13219207525253296, + -0.17990969121456146, + 0.14013062417507172, + 0.06921444088220596, + 0.08685183525085449, + 0.08346769213676453, + -0.05580170080065727, + -0.029459748417139053, + 0.12348329275846481, + -0.13697563111782074, + 0.06457316130399704, + -0.011059180833399296, + -0.060691721737384796, + 0.008451825007796288, + -0.051496777683496475, + 0.01891815848648548, + -0.13862761855125427, + 0.15652123093605042, + 0.13554930686950684, + -0.10610471665859222, + 0.08807627856731415, + 0.05179155617952347, + 0.05665804445743561, + -0.01573575660586357, + 0.005955331493169069, + 0.032564926892519, + -0.1559731662273407, + 0.1065542921423912, + -0.06964106112718582, + -0.08344428986310959, + 0.011269127018749714, + 0.15038123726844788, + -0.013799088075757027, + 0.11423389613628387, + -0.1697530746459961, + -0.11382301151752472, + 0.08237218111753464, + 0.03806707635521889, + -0.13815540075302124, + 0.04390700161457062, + 0.13404731452465057, + 0.0810299962759018, + 0.08329906314611435, + 0.08863252401351929, + -0.13960127532482147, + 0.03546145185828209, + 0.025180377066135406, + 0.056741438806056976, + 0.01410349365323782, + 0.02013334445655346, + 0.0209128949791193, + -0.22141367197036743, + 0.11761308461427689, + -0.07549607008695602, + 0.003845473052933812, + -0.1606534868478775, + 0.021332761272788048, + -0.0760880708694458, + 0.029683755710721016, + -0.09043291211128235, + 0.022210607305169106, + 0.056272026151418686, + 0.020880049094557762, + -0.0040367101319134235, + -0.11569046229124069, + 0.2571037709712982, + -0.09802695363759995, + 0.01624787412583828, + -0.10170961171388626, + 0.03313923999667168, + 0.19340597093105316, + 0.07979143410921097, + -0.054403237998485565, + 0.16575725376605988, + 0.010035901330411434, + -0.0912293940782547, + -0.17739517986774445, + 0.11435428261756897, + 0.012896290048956871, + 0.08155736327171326, + -0.048506274819374084, + 0.017170824110507965, + -0.004670025780797005, + 0.01886621117591858, + 0.061723582446575165, + -0.033185217529535294, + 0.06486264616250992, + 0.04181754216551781, + 0.057110343128442764, + -0.030568929389119148, + -0.12827521562576294, + -0.08688687533140182, + -0.24250738322734833, + 0.04736584052443504, + 0.014085025526583195, + -0.024027179926633835, + 0.12047254294157028, + -0.0742117241024971, + 0.021231280639767647, + 0.09346447885036469, + -0.07762507349252701, + 0.07382959872484207, + 0.03118390403687954, + 0.005068227648735046, + -0.07262168079614639, + -0.20503975450992584, + 0.044183146208524704, + 0.14974060654640198, + -0.0600312240421772, + 0.026143640279769897, + -0.03869996219873428, + 0.035469021648168564 + ], + [ + 0.0783107802271843, + -0.06209395080804825, + 0.0775170773267746, + 0.052745521068573, + -0.05323448404669762, + 0.03723796457052231, + 0.023208189755678177, + -0.046077754348516464, + -0.08712354302406311, + -0.11231788992881775, + 0.12065090984106064, + 0.09735070914030075, + 0.08098666369915009, + 0.0008674478158354759, + -0.07813674956560135, + -0.08195708692073822, + 0.08007963746786118, + -0.08599144965410233, + -0.12421919405460358, + -0.1070384830236435, + -0.18375536799430847, + 0.026169059798121452, + -0.05858877673745155, + 0.11871934682130814, + 0.027422772720456123, + -0.16596676409244537, + 0.1185077503323555, + -0.2931102216243744, + 0.058145955204963684, + 0.03627323731780052, + 0.13171541690826416, + -0.013797903433442116, + -0.2606445848941803, + -0.07792270183563232, + 0.04705105349421501, + -0.046407684683799744, + 0.07301218062639236, + 0.05719660967588425, + 0.025195684283971786, + -0.021518753841519356, + 0.0027664965018630028, + -0.03722113370895386, + -0.06558717787265778, + 0.08700130879878998, + 0.05732503905892372, + -0.1175960823893547, + 0.1751236617565155, + 0.11834891885519028, + 0.045822467654943466, + 0.05800104886293411, + 0.06859605759382248, + -0.040403950959444046, + 0.02516876719892025, + -0.3626827895641327, + 0.03252297267317772, + -0.03225744143128395, + 0.08381500840187073, + 0.07262907177209854, + -0.14457014203071594, + 0.051144469529390335, + -0.0465659573674202, + -0.04613771662116051, + 0.007858566008508205, + 0.04653696343302727, + 0.023363297805190086, + 0.04013524949550629, + 0.20805996656417847, + 0.052848488092422485, + -0.023402636870741844, + -0.0770229697227478, + 0.014178812503814697, + -0.030377550050616264, + 0.13914506137371063, + -0.03710757568478584, + -0.01884017139673233, + 0.06817333400249481, + 0.03372802212834358, + -0.15559056401252747, + -0.09961248189210892, + -0.10863467305898666, + -0.2521604895591736, + 0.058229681104421616, + 0.02746099978685379, + -0.1274789720773697, + -0.10521097481250763, + -0.04122538864612579, + 0.0005927450256422162, + 0.09356043487787247, + -0.005073938053101301, + -0.02273348718881607, + 0.18530170619487762, + 0.01927751488983631, + -0.47309115529060364, + 0.09965679794549942, + -0.09994552284479141, + 0.044199664145708084, + -0.15941093862056732, + 0.07569195330142975, + 0.04863956943154335, + 0.02459474466741085, + 0.04846476390957832, + 0.13527025282382965, + 0.07478143274784088, + 0.11627490073442459, + 0.02294449508190155, + -0.3537912368774414, + -0.06947092711925507, + -0.1707899272441864, + 0.016928566619753838, + 0.11249400675296783, + -0.1328856199979782, + 0.1875341236591339, + 0.1605599969625473, + 0.00577149074524641, + -0.04612089693546295, + 0.160458043217659, + 0.10576420277357101, + 0.08362124860286713, + 0.13664701581001282, + -0.03724243864417076, + -0.02373570203781128, + -0.1694856584072113, + 0.09192536026239395, + 0.047866176813840866, + 0.041556812822818756, + -0.21142099797725677, + -0.013325473293662071, + 0.033590465784072876 + ], + [ + -0.16974706947803497, + -0.013276711106300354, + 0.00870585348457098, + 0.08049459010362625, + 0.008109571412205696, + 0.0025743262376636267, + -0.029012005776166916, + 0.047674939036369324, + 0.00548558821901679, + -0.17715318500995636, + 0.12060627341270447, + 0.06666550040245056, + 0.43342992663383484, + -0.08594294637441635, + -0.5472456216812134, + -0.025757573544979095, + 0.07323829084634781, + -0.09105446934700012, + 0.37387362122535706, + 0.08532585203647614, + -0.679141104221344, + -0.14288429915905, + -0.05902261659502983, + 0.01658516190946102, + 0.04135235771536827, + -0.18881317973136902, + 0.06117340177297592, + 0.10362137109041214, + -0.01725500077009201, + 0.08261173218488693, + -0.015196184627711773, + 0.1715594232082367, + -0.50400310754776, + 0.31396764516830444, + 0.02331027016043663, + -0.00996393896639347, + -0.0873170718550682, + 0.20405033230781555, + 0.05743598937988281, + 0.006898785475641489, + -0.04900485277175903, + -0.11970929801464081, + -0.09672103822231293, + -0.07270237058401108, + -0.02925228700041771, + -0.2118958681821823, + 0.06413758546113968, + 0.08792413026094437, + -0.040877167135477066, + -6.135779403848574e-05, + -0.287894606590271, + -0.21185548603534698, + 0.03082280047237873, + -0.13821503520011902, + -0.003109552199020982, + 0.19534873962402344, + 0.1634657233953476, + 0.051271893084049225, + 0.03925219178199768, + 0.03213047981262207, + -0.10586637258529663, + 0.10121849179267883, + 0.00875455979257822, + -0.10927174240350723, + 0.1436740756034851, + 0.13789226114749908, + 0.2922316789627075, + 0.08311786502599716, + -0.08646401017904282, + 0.023914018645882607, + 0.04494757205247879, + -0.031619228422641754, + 0.02106788381934166, + -0.1977376937866211, + -0.049747005105018616, + 0.06780429929494858, + -0.08405772596597672, + -0.13275672495365143, + -0.3085252642631531, + -0.2482079714536667, + -0.30780038237571716, + 0.14354628324508667, + -0.08113561570644379, + 0.10517571866512299, + -0.12481875717639923, + -0.07294172048568726, + -0.0022403753828257322, + 0.10069069266319275, + -0.0959811806678772, + 0.07175649702548981, + -0.5910592675209045, + -0.02270411141216755, + -0.9116727709770203, + 0.03462376073002815, + -0.28613823652267456, + -0.06896331161260605, + 0.1170155480504036, + 0.0047248732298612595, + 0.04108273610472679, + -0.03805609419941902, + -0.08602793514728546, + 0.01743868924677372, + 0.026034241542220116, + -0.11675702035427094, + -0.08064799755811691, + -0.026928972452878952, + -0.10761459171772003, + -0.7605286836624146, + -0.46380940079689026, + 0.10488393157720566, + 0.01561813335865736, + 0.21165993809700012, + 0.144938662648201, + -0.12368196249008179, + 0.04582733288407326, + 0.23434913158416748, + -0.10771222412586212, + -0.15288381278514862, + 0.11517055332660675, + 0.12524695694446564, + -0.004074688069522381, + -0.14222010970115662, + 0.17129836976528168, + 0.20149309933185577, + -0.26205068826675415, + -0.2507866621017456, + 0.1013573557138443, + -0.20686477422714233 + ], + [ + 0.08675603568553925, + -0.04924555867910385, + -0.15757234394550323, + 0.031558964401483536, + 0.11017536371946335, + 0.06146883964538574, + 0.19680474698543549, + 0.003780987113714218, + -0.2885929048061371, + 0.03709396719932556, + -0.09273272007703781, + -0.012492988258600235, + 0.2135177105665207, + -0.33416685461997986, + -0.16756805777549744, + 0.17318390309810638, + -0.555483877658844, + 0.056309666484594345, + 0.26585260033607483, + 0.08137231320142746, + 0.3254030644893646, + 0.04905780404806137, + -0.0004609464667737484, + -0.11070796847343445, + 0.054153162986040115, + -0.06229635700583458, + -0.10936834663152695, + 0.15657664835453033, + -0.2574399411678314, + -0.0327109694480896, + -0.10009638965129852, + -0.07509712874889374, + -0.2651923894882202, + -0.4021395444869995, + -0.468514621257782, + 0.04949710890650749, + 0.1837141364812851, + -0.2196643203496933, + -0.1571110635995865, + 0.060903795063495636, + 0.025448502972722054, + -0.03401053324341774, + 0.13747890293598175, + -0.08266312628984451, + -0.0903635174036026, + -0.27135568857192993, + -0.15599872171878815, + 0.12481475621461868, + -0.1620606929063797, + 0.05685736984014511, + -0.4011027216911316, + 0.204836905002594, + 0.051946595311164856, + 0.12413392961025238, + 0.16698716580867767, + 0.13195425271987915, + -0.06835778802633286, + -0.034156180918216705, + -0.25071731209754944, + -0.021448714658617973, + -0.016461357474327087, + 0.26151543855667114, + 0.07864474505186081, + 0.17389187216758728, + -0.08629564195871353, + 0.15622662007808685, + -0.11040123552083969, + -0.19204944372177124, + 0.05964328721165657, + 0.14725792407989502, + -0.0252989511936903, + 0.007619159296154976, + -0.1776377111673355, + 0.16713851690292358, + -0.3712519109249115, + 0.1553904265165329, + -0.04910900071263313, + -0.17310303449630737, + 0.08439517766237259, + -0.23322449624538422, + 0.02817469649016857, + 0.0729636549949646, + -0.043519072234630585, + -0.14348860085010529, + 0.19556428492069244, + 0.0017799785127863288, + -0.26780086755752563, + -0.22917942702770233, + -0.22644217312335968, + 0.06633588671684265, + 0.28340741991996765, + -0.23967261612415314, + -0.06867237389087677, + 0.01884148083627224, + -0.11188331991434097, + 0.13153158128261566, + 0.04652686417102814, + 0.18151134252548218, + -0.010962572880089283, + -0.5159135460853577, + -0.03854915872216225, + 0.30726757645606995, + 0.025458725169301033, + 0.21914255619049072, + -0.15601128339767456, + -0.21966613829135895, + -0.18772362172603607, + 0.23973922431468964, + 0.19637024402618408, + -0.009257867000997066, + 0.21424467861652374, + 0.2042698711156845, + 0.25431111454963684, + -0.04386188089847565, + 0.15342675149440765, + -0.023999156430363655, + -0.03818193078041077, + 0.12436921149492264, + 0.11113045364618301, + -0.08261441439390182, + 5.984990275464952e-05, + -0.3166167736053467, + 0.32878413796424866, + -0.15462122857570648, + -0.31561025977134705, + 0.03368854150176048, + 0.03422364592552185, + -0.17395758628845215 + ], + [ + -0.1079932153224945, + -0.058108922094106674, + 0.16107238829135895, + -0.06802112609148026, + -0.08859067410230637, + -0.025067681446671486, + -0.058310288935899734, + 0.13243107497692108, + 0.01723628118634224, + 0.004805194213986397, + 0.03628576546907425, + -0.03461208567023277, + -0.35326382517814636, + 0.204325333237648, + -0.13805431127548218, + -0.00806991383433342, + -0.5502825975418091, + -0.07940246164798737, + 0.04991210624575615, + -0.004542887210845947, + -0.44374772906303406, + -0.040956441313028336, + -0.08269327133893967, + 0.08908300846815109, + 0.04443015903234482, + -0.13246691226959229, + 0.10874763131141663, + -0.09507060050964355, + 0.03444927558302879, + -0.0619351789355278, + -0.003085497999563813, + 0.09234225004911423, + -0.15391896665096283, + 0.14162443578243256, + 0.06882685422897339, + -0.33378738164901733, + 0.0896821990609169, + 0.042083993554115295, + 0.14141498506069183, + 0.056810759007930756, + 0.01242655050009489, + 0.047269534319639206, + -0.20154079794883728, + 0.02460796944797039, + -0.0025588159915059805, + 0.004870925098657608, + -0.024067936465144157, + 0.07062233984470367, + 0.021638451144099236, + 0.04681726172566414, + -0.05278048664331436, + 0.06918779015541077, + -0.11305337399244308, + -0.04187703877687454, + 0.04821775108575821, + -0.14323614537715912, + 0.19046659767627716, + 0.00012921373127028346, + -0.014746185392141342, + -0.06877554208040237, + -0.009217028506100178, + 0.12014289945363998, + 0.0770832970738411, + 0.09677247703075409, + -0.1273730993270874, + -0.0675196424126625, + 0.12332307547330856, + -0.2922936975955963, + 0.00500972056761384, + 0.058786578476428986, + 0.09132737666368484, + -0.06598961353302002, + 0.12078772485256195, + 0.06632108986377716, + -0.04037463292479515, + -0.010475999675691128, + 0.08104552328586578, + -0.15024887025356293, + -0.15263959765434265, + -0.19324056804180145, + 0.11844342201948166, + -0.05861712992191315, + 0.10300588607788086, + 0.10088586062192917, + 0.1492317020893097, + 0.039142362773418427, + 0.08330883085727692, + -0.02019585482776165, + -0.10730573534965515, + -0.046750567853450775, + 0.32330524921417236, + 0.09848538041114807, + -0.6992539763450623, + -0.07044966518878937, + -0.3867523968219757, + 0.13274651765823364, + -0.3288094997406006, + -0.055095214396715164, + -0.010446997359395027, + -0.24840307235717773, + 0.22637607157230377, + 0.17327803373336792, + 0.11393189430236816, + -0.02747414819896221, + -0.11980723589658737, + -0.11430279165506363, + 0.012197989970445633, + 0.0505264587700367, + -0.014160780236124992, + 0.02941620536148548, + -0.28946977853775024, + 0.47213131189346313, + 0.05838055908679962, + -0.032171543687582016, + -0.49156564474105835, + -0.024989230558276176, + 0.13208609819412231, + 0.0715160220861435, + -0.14407067000865936, + 0.1823405921459198, + 0.022447684779763222, + -0.3923014998435974, + 0.02583010494709015, + 0.32148730754852295, + 0.12203289568424225, + 0.03533616289496422, + -0.04628293588757515, + -0.04807164520025253 + ], + [ + -0.19024065136909485, + -0.252533882856369, + -0.019715487957000732, + 0.047169029712677, + -0.057813823223114014, + 0.0335092730820179, + 0.0036097215488553047, + 0.23629534244537354, + 0.04145783931016922, + 0.015565905719995499, + 0.1458183079957962, + -0.0402115099132061, + 0.1835932433605194, + -0.11074043810367584, + -0.26481571793556213, + -0.08152663707733154, + 0.15711240470409393, + -0.17441695928573608, + -0.05699195712804794, + -0.0160861536860466, + 0.008906514383852482, + 0.14310625195503235, + -0.03766690939664841, + -0.04292721301317215, + 0.15639038383960724, + -0.16025248169898987, + 0.060089632868766785, + -0.16941995918750763, + 0.003307783743366599, + -0.08539890497922897, + -0.09507834911346436, + -0.021324526518583298, + -0.005308454856276512, + 0.23059196770191193, + -0.15391209721565247, + 0.09060069173574448, + 0.04788491502404213, + 0.1102127656340599, + 0.05092727392911911, + -0.13365112245082855, + 0.005585954058915377, + -0.07319989800453186, + 0.08416307717561722, + 0.07884838432073593, + -0.1797676533460617, + 0.030152088031172752, + 0.17531463503837585, + -0.10311593860387802, + 0.12146880477666855, + 0.17616918683052063, + -0.0594387985765934, + -0.07066290825605392, + 0.01412491500377655, + 0.09983557462692261, + -0.06846794486045837, + 0.06230270862579346, + 0.20361082255840302, + -0.0585748590528965, + -0.26450350880622864, + 0.0445450097322464, + -0.16132082045078278, + 0.005000120494514704, + 0.03272498771548271, + 0.029690496623516083, + 0.0029387071263045073, + -0.03390410542488098, + 0.053230274468660355, + -0.14193861186504364, + 0.020743142813444138, + -0.14382445812225342, + 0.021842438727617264, + -0.10581634938716888, + -0.004721806384623051, + 0.003347614547237754, + -0.07901641726493835, + -0.1352909654378891, + 0.10122517496347427, + -0.26144030690193176, + -0.09566234797239304, + 0.15630820393562317, + 0.0006697386270388961, + 0.04405131936073303, + 0.1331574022769928, + -0.0244685597717762, + 0.0126058803871274, + 0.03927677869796753, + 0.16056929528713226, + 0.12790364027023315, + 0.026373114436864853, + 0.02760746143758297, + 0.09701279550790787, + 0.0029215740505605936, + -0.3708004653453827, + 0.09096989035606384, + 0.005134083330631256, + -0.5552148222923279, + 0.10908109694719315, + 0.08115455508232117, + 0.19338807463645935, + -0.2545126676559448, + -0.10457564890384674, + -0.09614507108926773, + 0.024394264444708824, + 0.06999236345291138, + -0.08052948862314224, + -0.33623167872428894, + -0.09075400233268738, + -0.10139493644237518, + -0.21180123090744019, + 0.04576632007956505, + 0.046867746859788895, + 0.1231309026479721, + 0.046315208077430725, + -0.08899713307619095, + 0.024158736690878868, + 0.21690280735492706, + 0.24213100969791412, + -0.004678723402321339, + 0.049206893891096115, + 0.41365209221839905, + -0.029656101018190384, + 0.05131585896015167, + 0.1078445166349411, + 0.22551696002483368, + -0.1947326362133026, + -0.20488090813159943, + 0.0723985806107521, + -0.06438877433538437 + ], + [ + 0.01791493408381939, + -0.025942755863070488, + 0.09545334428548813, + 0.046465128660202026, + 0.016201790422201157, + 0.04795854911208153, + 0.049426041543483734, + 0.05556958541274071, + 0.11742531508207321, + -0.10908105224370956, + 0.18711280822753906, + 0.033176667988300323, + 0.08249927312135696, + 0.06045115739107132, + 0.07445468753576279, + 0.07197996973991394, + -0.08960839360952377, + 0.07605665922164917, + 0.04981362819671631, + -0.04324726760387421, + -0.003607548540458083, + 0.07709771394729614, + -0.018393972888588905, + 0.08008398115634918, + -0.014669940806925297, + -0.01782369799911976, + -0.03640350326895714, + 0.08876810222864151, + 0.054390981793403625, + 0.008247011341154575, + -0.07544739544391632, + -0.08353977650403976, + 0.07725229859352112, + 0.05759109929203987, + -0.08736513555049896, + -0.19626383483409882, + -0.12539047002792358, + 0.10644373297691345, + -0.10277232527732849, + 0.06071652099490166, + 0.012111731804907322, + 0.07315482944250107, + -0.13153834640979767, + 0.013529067859053612, + -0.19497279822826385, + 0.12907059490680695, + 0.13801982998847961, + 0.06010165810585022, + 0.029515668749809265, + 0.03725430369377136, + -0.012215410359203815, + -0.04317757487297058, + -0.009204107336699963, + 0.006863452028483152, + -0.09893722087144852, + -0.04463561624288559, + 0.09153374284505844, + -0.0002521635324228555, + 0.03681527078151703, + -0.03201797604560852, + 0.01717298850417137, + -0.027461454272270203, + -0.07738136500120163, + 0.033871155232191086, + 0.13231778144836426, + -0.054085202515125275, + 0.01540937926620245, + -0.19857051968574524, + 0.08374720811843872, + -0.020926279947161674, + 0.08145733177661896, + -0.04677482694387436, + 0.03541560098528862, + 0.09200383722782135, + 0.04398716613650322, + -0.11131878942251205, + 0.05890766903758049, + -0.01778149977326393, + 0.017070239409804344, + -0.1720496416091919, + 0.008870814926922321, + -0.019129304215312004, + -0.07214629650115967, + -0.003646161872893572, + 0.06006057932972908, + 0.10719681531190872, + 0.06815898418426514, + 0.10359420627355576, + -0.13623450696468353, + -0.06407197564840317, + 0.007115744519978762, + 0.00642108591273427, + 0.01056867279112339, + 0.10550247132778168, + 0.01760658249258995, + -0.05009228363633156, + 0.04645248129963875, + -0.08067593723535538, + -0.015521382912993431, + 0.13890522718429565, + 0.006651498842984438, + 0.07805135846138, + 0.06009603664278984, + -0.07019500434398651, + -0.012347945012152195, + 0.005085631273686886, + -0.0323946587741375, + -0.03416810557246208, + -0.10191236436367035, + 0.12254025042057037, + -0.02236918918788433, + 0.059133131057024, + 0.048790909349918365, + 0.07976420223712921, + -0.10350076109170914, + 0.04162734001874924, + -0.22263696789741516, + 0.03198809549212456, + 0.10009842365980148, + 0.12853595614433289, + -0.0868561714887619, + 0.0185559019446373, + -0.003401882713660598, + 0.1320810467004776, + 0.1389266699552536, + -0.08731698244810104, + 0.002416628645732999, + 0.06596992909908295 + ], + [ + 0.03402885049581528, + 0.17184652388095856, + -0.0684080496430397, + -0.08321606367826462, + -0.10243111848831177, + -0.016146551817655563, + -0.23775449395179749, + 0.08948293328285217, + -0.02245282754302025, + 0.22760383784770966, + 0.07137952744960785, + -0.00772787444293499, + 0.14328080415725708, + 0.2795014977455139, + -0.026929821819067, + -0.016401957720518112, + 0.1470419019460678, + -0.04003877937793732, + -0.021982381120324135, + 0.06376465409994125, + 0.034196749329566956, + -0.034421853721141815, + -0.08936117589473724, + -0.05335858091711998, + -0.11902857571840286, + 0.06406857073307037, + 0.09919808804988861, + 0.11816784739494324, + 0.2573908865451813, + -0.14958268404006958, + -0.048101916909217834, + -0.007493417244404554, + -0.08915136009454727, + 0.07983780652284622, + 0.0017997643444687128, + 0.06836969405412674, + 0.043636504560709, + 0.08021841198205948, + 0.0690588727593422, + -0.09686476737260818, + -0.12845516204833984, + -0.04548157751560211, + -0.045027896761894226, + -0.0007180914981290698, + 0.1153668463230133, + -0.12038947641849518, + 0.15076185762882233, + 0.0993744507431984, + -0.013236787170171738, + -0.1732177436351776, + 0.015414605848491192, + -0.23610834777355194, + -0.1269170492887497, + 0.06376733630895615, + -0.1374727040529251, + 0.004723945166915655, + 0.029386013746261597, + 0.033333923667669296, + 0.08234690874814987, + 0.13926872611045837, + 0.04503564164042473, + 0.16013045608997345, + -0.20866484940052032, + -0.04521014913916588, + 0.07477078586816788, + -0.3979688584804535, + -0.028738820925354958, + 0.18040470778942108, + -0.06811711192131042, + -0.1297680139541626, + 0.12433342635631561, + -0.4820062220096588, + 0.07175249606370926, + 0.003000649157911539, + 0.041797488927841187, + 0.14857541024684906, + 0.05472109094262123, + -0.025885380804538727, + 0.0358748659491539, + -0.016018683090806007, + -0.08206749707460403, + -0.10003101080656052, + 0.24121898412704468, + 0.06406819820404053, + -0.16363966464996338, + -0.04091299697756767, + 0.06072753667831421, + 0.07490544021129608, + -0.026494791731238365, + -0.40030384063720703, + 0.03300757706165314, + 0.13088351488113403, + 0.06993871927261353, + 0.05875615403056145, + -0.014963909983634949, + 0.06571130454540253, + 0.13375288248062134, + -0.15387213230133057, + 0.0017725001089274883, + 0.015218920074403286, + 0.041125986725091934, + -0.08208359777927399, + -0.04627269133925438, + -0.005012473091483116, + 0.11098652333021164, + 0.09685841202735901, + -0.17673125863075256, + -0.06659920513629913, + -0.19631990790367126, + 0.0214687529951334, + -0.056450530886650085, + -0.0054071275517344475, + 0.06359260529279709, + 0.0256314966827631, + -0.08834754675626755, + -0.09362724423408508, + -0.07478292286396027, + -0.11418525129556656, + 0.08474377542734146, + 0.18872839212417603, + -0.08264492452144623, + 0.06566280126571655, + -0.12940745055675507, + 0.019459476694464684, + -0.09139396250247955, + -0.09026888757944107, + 0.246625155210495, + 0.07982154190540314 + ], + [ + -0.05507000535726547, + -0.02131926268339157, + 0.04944295436143875, + -0.03573378175497055, + -0.01657203398644924, + 0.14195500314235687, + 0.21404534578323364, + 0.02811877243220806, + -0.0576045885682106, + 0.15966397523880005, + 0.2409980446100235, + 0.026132900267839432, + 0.3758307993412018, + 0.033755313605070114, + -0.18917512893676758, + 0.20075681805610657, + 0.04076554998755455, + -0.1220354214310646, + -0.1138526126742363, + -0.06354916095733643, + -0.059131596237421036, + -0.01199362613260746, + -0.1554720103740692, + 0.07684934884309769, + -0.07363799959421158, + 0.009605887345969677, + 0.07168678194284439, + -0.2739746868610382, + 0.07860474288463593, + 0.1279282569885254, + -0.0882757157087326, + 0.29636430740356445, + -0.21375158429145813, + 0.26944777369499207, + -0.058581311255693436, + 0.32430365681648254, + -0.07423863559961319, + -0.008950176648795605, + -0.32775214314460754, + 0.09635032713413239, + -0.22904905676841736, + -0.08342330902814865, + 0.049132734537124634, + 0.032984450459480286, + -0.017277343198657036, + -0.08202850818634033, + 0.07708396017551422, + -0.10757459700107574, + 0.09509218484163284, + 0.033357180655002594, + -0.19157864153385162, + 0.15730640292167664, + -0.028441214933991432, + 0.19777168333530426, + 0.023929376155138016, + 0.30504804849624634, + 0.1002316027879715, + -0.01444215141236782, + 0.17837172746658325, + -0.2999696731567383, + -0.22165510058403015, + -0.6968523859977722, + -0.13073420524597168, + -0.13355430960655212, + -0.05382949113845825, + -0.22527579963207245, + 0.0012581351911649108, + -0.23846299946308136, + -0.015188734978437424, + 0.03868040814995766, + -0.2551972568035126, + -0.06245913729071617, + -0.17034785449504852, + 0.10582216084003448, + -0.10647819936275482, + -0.255878746509552, + 0.1901082992553711, + 0.12736175954341888, + -0.11881639063358307, + -0.08173978328704834, + 0.1352575421333313, + -0.10771975666284561, + 0.004049294162541628, + -0.9885585308074951, + -0.021124059334397316, + -0.03339037671685219, + -0.11345835030078888, + -0.05960260331630707, + 0.19854438304901123, + 0.017005328088998795, + 0.2673771381378174, + -0.014508014544844627, + -0.4334041476249695, + -0.39906299114227295, + 0.061464689671993256, + -0.1496482938528061, + -0.1355964094400406, + -0.4513859450817108, + -0.03250174596905708, + -0.1912541687488556, + 0.1424100399017334, + 0.0781925693154335, + -0.09312355518341064, + 0.016138849779963493, + -0.1684177815914154, + 0.08388680964708328, + 0.11225790530443192, + -0.04652887582778931, + -0.1853048950433731, + -0.00502705667167902, + -0.12878072261810303, + 0.32022517919540405, + -0.17318204045295715, + 0.02135736495256424, + 0.08441817760467529, + 0.06962086260318756, + -0.08189657330513, + -0.057970549911260605, + 0.2673652470111847, + -0.07123631238937378, + -0.07232522964477539, + 0.011358468793332577, + -0.11902695149183273, + -0.31605780124664307, + 0.04831117019057274, + -0.09163043648004532, + -0.022405127063393593, + 0.08179832249879837 + ], + [ + -0.05075155943632126, + 0.06007346883416176, + 0.04109467193484306, + 0.053282804787158966, + 0.06533806025981903, + 0.05562977120280266, + -0.05099375173449516, + 0.060241568833589554, + 0.020855695009231567, + -0.09233864396810532, + 0.09251739084720612, + 0.08472535014152527, + 0.09527648985385895, + 0.052704520523548126, + 0.033505626022815704, + -0.06626196205615997, + 0.0474107563495636, + 0.017282461747527122, + -0.011970995925366879, + 0.036480773240327835, + 0.06753698736429214, + 0.06463093310594559, + -0.09152448922395706, + 0.07208115607500076, + 0.05212666094303131, + -0.05862032249569893, + 0.11908654123544693, + 0.0022102773655205965, + 0.043653856962919235, + 0.06451453268527985, + -0.0257997065782547, + 0.05256029963493347, + -0.06490683555603027, + 0.08359035849571228, + 0.11670561879873276, + -0.04109185189008713, + 0.06029554829001427, + 0.08626388758420944, + 0.03846277296543121, + 0.027039479464292526, + 0.004472294822335243, + -0.06400847434997559, + -0.02305387146770954, + 0.06068957597017288, + -0.08961204439401627, + -0.006609043106436729, + -0.03764680400490761, + -0.03983108326792717, + 0.049245286732912064, + 0.05488681420683861, + -0.05985971540212631, + 0.06347443163394928, + -0.007649154867976904, + 0.041829366236925125, + -0.06142677739262581, + 0.05242614448070526, + 0.04408152028918266, + 0.0104526923969388, + 0.03687090799212456, + -0.07762142270803452, + 0.02884487994015217, + -0.04635879024863243, + -0.07366127520799637, + -0.0033723588567227125, + 0.01275240071117878, + 0.015148023143410683, + 0.08099985122680664, + 0.03688198700547218, + -0.07628071308135986, + -0.024569496512413025, + -0.032038260251283646, + -0.025578701868653297, + 0.002566736890003085, + 0.08247742801904678, + 0.029258985072374344, + -0.030174406245350838, + 0.021106787025928497, + -0.02103414013981819, + -0.08780112862586975, + -0.04166444018483162, + 0.009409008547663689, + 0.03348240628838539, + -0.10171521455049515, + 0.051232412457466125, + -0.059736382216215134, + 0.0500001460313797, + -0.03418341651558876, + 0.07528172433376312, + 0.027022384107112885, + -0.011322054080665112, + 0.04829498007893562, + 0.0034503985662013292, + -0.0356631800532341, + 0.05397336930036545, + -0.05105278640985489, + -0.07796555757522583, + 0.031243061646819115, + 0.04457807540893555, + 0.03978641331195831, + 0.016067547723650932, + 0.09660463780164719, + -0.039868347346782684, + -0.014092287980020046, + -0.014105375856161118, + -0.014528402127325535, + -0.05022742599248886, + -0.13002842664718628, + -0.028510889038443565, + -0.004857775289565325, + -0.05690135061740875, + -0.042781099677085876, + 0.0017229042714461684, + 0.09719821810722351, + -0.08027160912752151, + 0.052277177572250366, + -0.05458057299256325, + 0.0371289886534214, + 0.07427956908941269, + 0.04087419435381889, + 0.04853630065917969, + 0.031039929017424583, + 0.07483533769845963, + 0.05205433443188667, + 0.00034488539677113295, + -0.03262289986014366, + 0.0077996645122766495, + -0.07109898328781128, + -0.04631292447447777 + ], + [ + -0.034364085644483566, + 0.01366499811410904, + -0.050639212131500244, + 0.02495812438428402, + 0.03625164553523064, + -0.19375380873680115, + 0.024802280589938164, + -0.004905098583549261, + 0.05862438306212425, + -0.06739203631877899, + 0.4541315734386444, + -0.14850732684135437, + -0.29218003153800964, + 0.17652375996112823, + 0.16355639696121216, + 0.0006983880302868783, + -0.5748200416564941, + -0.130961075425148, + -0.03462770953774452, + 0.007168447133153677, + 0.04161759093403816, + 0.05439426377415657, + -0.04206879064440727, + 0.021860796958208084, + -0.005913739558309317, + 0.2500191330909729, + -0.07939167320728302, + -0.2596276104450226, + -0.050456512719392776, + -0.32970717549324036, + -0.11508201062679291, + 0.1354358047246933, + 0.1753389537334442, + -0.21002636849880219, + -0.07071862369775772, + 0.12705105543136597, + 0.09845433384180069, + -0.24003276228904724, + -0.9818633198738098, + 0.13733619451522827, + 0.1237991526722908, + 0.08727428317070007, + 0.5008524656295776, + 0.11503604054450989, + -0.08274747431278229, + -0.07544020563364029, + 0.009651330299675465, + -0.015211090445518494, + -0.09755361825227737, + 0.1361347883939743, + -0.02835853025317192, + -0.019144834950566292, + -0.006387061905115843, + 0.16071385145187378, + 0.03541707992553711, + -0.017365654930472374, + -0.011994481086730957, + -0.07240567356348038, + -0.07341742515563965, + 0.0857895165681839, + -0.15003702044487, + 0.05391468107700348, + 0.019032076001167297, + 0.0538313202559948, + -0.049717579036951065, + 0.04513609781861305, + -0.28474941849708557, + 0.47179755568504333, + 0.03661379963159561, + 0.06483380496501923, + 0.04901105910539627, + -0.02619193121790886, + 0.028165308758616447, + -0.0685148760676384, + -0.0031237995717674494, + 0.14587272703647614, + -0.1276971399784088, + -0.22385139763355255, + 0.0539671964943409, + -0.1270672082901001, + 0.04747120663523674, + -0.09596765786409378, + -0.007317489944398403, + -0.024358101189136505, + 0.16819700598716736, + -0.0407785102725029, + 0.06253518909215927, + -0.07786669582128525, + -0.14297811686992645, + -0.07657699286937714, + 0.050816453993320465, + 0.0432378388941288, + -0.2909793257713318, + -0.004221203271299601, + -0.1737229973077774, + 0.019902151077985764, + -0.016461418941617012, + 0.11222855746746063, + -0.010353737510740757, + -0.29926440119743347, + -0.06961150467395782, + 0.029195809736847878, + 0.07926090061664581, + 0.14165686070919037, + -0.09339983016252518, + -0.004020492546260357, + 0.07114912569522858, + -0.24216331541538239, + 0.15601202845573425, + 0.0038938107900321484, + 0.04824419692158699, + -0.02217330038547516, + 0.1345236450433731, + -0.02145131304860115, + -0.6536331176757812, + -0.32908183336257935, + 0.1301077902317047, + 0.20210956037044525, + -0.3153581917285919, + -0.011250759474933147, + 0.08730414509773254, + -0.06925900280475616, + 0.08352179080247879, + 0.030913259834051132, + 0.14725427329540253, + 0.03529774397611618, + 0.18357326090335846, + -0.24009162187576294 + ], + [ + -0.05785241723060608, + 0.10063207894563675, + 0.009645380079746246, + 0.09382807463407516, + 0.057568032294511795, + -0.04681988060474396, + -0.2814941108226776, + -0.2991574704647064, + 0.13419915735721588, + -0.19655455648899078, + -0.09377829730510712, + -0.046257566660642624, + 0.03976738080382347, + 0.13096971809864044, + 0.16601769626140594, + 0.22202259302139282, + -0.2949269115924835, + -0.12465850263834, + -0.4231935739517212, + -0.006281765177845955, + 0.14321166276931763, + 0.03367907553911209, + -0.1643390953540802, + 0.058880969882011414, + -0.009653732180595398, + -0.23885636031627655, + 0.10869532823562622, + -0.1949620544910431, + -0.022088689729571342, + 0.1815725564956665, + -0.04105697572231293, + 0.23392239212989807, + -0.3140588402748108, + -0.10523411631584167, + 0.09147334098815918, + -0.015120396390557289, + 0.10563974827528, + 0.005437401123344898, + 0.0708921030163765, + 0.08642204105854034, + 0.009408035315573215, + 0.032508734613657, + -0.10628470778465271, + 0.1673254519701004, + -0.1441275030374527, + -0.9983530640602112, + 0.06685927510261536, + -0.03895234316587448, + -0.030156802386045456, + 0.07780111581087112, + -0.041675761342048645, + 0.08189360052347183, + 0.006465703248977661, + -0.2882259786128998, + -0.1616893857717514, + 0.14872929453849792, + 0.027775054797530174, + -0.10623019933700562, + -0.017209434881806374, + 0.17870131134986877, + -0.03869114816188812, + -0.3410245180130005, + -0.030567819252610207, + 0.06180263310670853, + -0.05928216874599457, + -0.28897756338119507, + 0.2604711651802063, + 0.1580100953578949, + 0.17353583872318268, + -0.2424541562795639, + -0.20194730162620544, + 0.032669782638549805, + 0.06093314290046692, + -0.010068221017718315, + -0.051924776285886765, + -0.3538822829723358, + 0.2700287103652954, + -0.21148258447647095, + -0.012954628095030785, + -0.18607397377490997, + -0.09839504212141037, + -0.1803533136844635, + -0.17657765746116638, + 0.21982517838478088, + 0.09631680697202682, + -0.28538814187049866, + 0.04010291025042534, + -0.40274733304977417, + -0.16290006041526794, + -0.07956428825855255, + 0.6290508508682251, + -0.07114563137292862, + -0.2360440343618393, + -0.7841418981552124, + -0.2129071056842804, + -0.05691816285252571, + -0.044528305530548096, + 0.09897283464670181, + -0.22227083146572113, + -0.059639859944581985, + 0.03645295277237892, + 0.3975054621696472, + -0.024859732016921043, + 0.12806645035743713, + -0.05103042349219322, + -0.05628630891442299, + -0.3426257371902466, + -0.00939891766756773, + 0.042580682784318924, + -0.09265851974487305, + 0.07650777697563171, + 0.049619149416685104, + 0.2958213686943054, + 0.011523501016199589, + -0.07654157280921936, + -0.636650562286377, + -0.00765631441026926, + 0.051264118403196335, + 0.026328522711992264, + -0.13748876750469208, + -0.09978058934211731, + -0.07052633911371231, + 0.12946857511997223, + 0.007580659817904234, + 0.31295689940452576, + 0.3850337266921997, + 0.1715293824672699, + 0.2588191032409668 + ], + [ + -0.03500312194228172, + -0.11029358953237534, + 0.058573901653289795, + 0.014679742977023125, + 0.03801311179995537, + -0.08058583736419678, + -0.33443590998649597, + -0.07363328337669373, + -0.11673133075237274, + -0.015089466236531734, + 0.11915460228919983, + -0.19552305340766907, + -0.0018842731369659305, + -0.2225828617811203, + -0.04368438944220543, + -0.18539972603321075, + -0.009713297709822655, + 0.004975649528205395, + -0.03727368637919426, + 0.023286867886781693, + -0.1565008908510208, + -0.011285142041742802, + -0.2231624871492386, + -0.08160023391246796, + -0.023162836208939552, + -0.08662538975477219, + 0.10125204175710678, + 0.13266779482364655, + 0.021796584129333496, + 0.08525101095438004, + 0.07079177349805832, + 0.22965413331985474, + 0.08301083743572235, + 0.07257188111543655, + -0.02253502607345581, + 0.08336064964532852, + 0.19950313866138458, + 0.0030744136311113834, + -0.2167322039604187, + 0.10524797439575195, + -0.08689101040363312, + 0.07861118018627167, + 0.1435534507036209, + -0.013694120571017265, + -0.15481656789779663, + -0.5166780948638916, + 0.12849117815494537, + 0.11689741909503937, + -0.6834136843681335, + -0.04960346221923828, + 0.009049243293702602, + -0.26923486590385437, + 0.05041193217039108, + -0.02689318172633648, + 0.10265225917100906, + -0.15087932348251343, + -0.15728215873241425, + -0.009680625982582569, + -0.11187408864498138, + -0.38680702447891235, + 0.03635323420166969, + 0.19670069217681885, + 0.18773691356182098, + 0.28645437955856323, + 0.2015988826751709, + -0.24749761819839478, + -0.566813051700592, + -0.012071026489138603, + -0.021162858232855797, + 0.11551038175821304, + 0.056844159960746765, + -0.04833778366446495, + 0.051523033529520035, + 0.061391036957502365, + 0.09329529851675034, + -0.528886079788208, + -0.10218077898025513, + -0.010639429092407227, + -0.07837643474340439, + 0.12088513374328613, + 0.014652717858552933, + -0.03144022449851036, + -0.012989030219614506, + 0.1701495349407196, + -0.4619208574295044, + -0.020544061437249184, + 0.05315040796995163, + 0.16402943432331085, + -0.05258168280124664, + -0.2616097629070282, + 0.14146070182323456, + 0.24581041932106018, + -0.31169936060905457, + 0.3033657371997833, + -0.10853741317987442, + 0.05876293405890465, + 0.22122496366500854, + -0.17194312810897827, + -0.24239954352378845, + 0.11911101639270782, + -0.11921755969524384, + -0.13346531987190247, + -0.14232701063156128, + 0.27124354243278503, + -0.40329116582870483, + -0.02210230939090252, + -0.11382360011339188, + 0.12426042556762695, + -0.34048137068748474, + 0.10911496728658676, + 0.08892162144184113, + 0.0016809850931167603, + -0.006928650662302971, + 0.03486957028508186, + -0.3684970736503601, + -0.23897820711135864, + -0.6507090926170349, + -0.08072611689567566, + -0.025833817198872566, + 0.08556969463825226, + 0.18245169520378113, + -0.23255300521850586, + -0.1756393164396286, + 0.0038658350240439177, + 0.1164507195353508, + -0.18601351976394653, + 0.079983651638031, + 0.07902306318283081 + ], + [ + 0.04744051396846771, + 0.3501811623573303, + 0.016084585338830948, + -0.12134507298469543, + -0.06107659265398979, + -0.019069593399763107, + 0.06098470464348793, + -0.09800015389919281, + -0.23486611247062683, + 0.04954270273447037, + -0.13560539484024048, + -0.3029384911060333, + 0.18619607388973236, + 0.16791419684886932, + -0.2335570901632309, + -0.2640669047832489, + 0.19209930300712585, + -0.31711190938949585, + 0.0033899808768182993, + -0.9375644326210022, + -0.1390312910079956, + 0.08762377500534058, + 0.05409293621778488, + -0.08712887018918991, + -0.27749741077423096, + -0.21618731319904327, + -0.22798122465610504, + -0.042443595826625824, + -0.49589747190475464, + -0.4583521783351898, + -0.1777624487876892, + -0.413225919008255, + -0.01658625900745392, + -0.15268462896347046, + 0.21356235444545746, + 0.4105224311351776, + 0.057944849133491516, + -0.015789460390806198, + 0.05805232748389244, + 0.01964305154979229, + 0.04913802072405815, + -0.10555639863014221, + -0.2441979944705963, + -0.024102000519633293, + 0.4596140384674072, + 0.009997056797146797, + 0.33381029963493347, + -0.11775051802396774, + 0.13603340089321136, + 0.5409362316131592, + -0.3091731667518616, + -0.04616522416472435, + -0.06289330869913101, + -0.10406564176082611, + 0.028147879987955093, + 0.0236998051404953, + 0.3681204915046692, + 0.17072725296020508, + -0.14778293669223785, + -0.344341903924942, + -0.03936358168721199, + -0.2473531812429428, + 0.11310060322284698, + -0.613621175289154, + 0.019861998036503792, + 0.35305482149124146, + 0.18740513920783997, + -0.702521026134491, + 0.08046234399080276, + -0.10790092498064041, + 0.10652562975883484, + -0.12191914767026901, + -0.08506406843662262, + -0.1280076652765274, + -0.09068406373262405, + 0.08132967352867126, + -0.22058457136154175, + 0.19185470044612885, + 0.07505033165216446, + -0.012269659899175167, + 0.1336406171321869, + 0.11552256345748901, + -0.10077863931655884, + 0.04646674171090126, + 0.10620252043008804, + 0.01826818287372589, + -0.2421698272228241, + 0.037228140980005264, + 0.07485733926296234, + -0.0035711568780243397, + 0.08291175216436386, + 0.15031857788562775, + -0.06429225951433182, + 0.14790476858615875, + -0.018214279785752296, + -0.47439906001091003, + -0.017914751544594765, + 0.2692652642726898, + 0.1402575820684433, + 0.03970937803387642, + 0.15560896694660187, + -0.3744448721408844, + 0.003873751498758793, + 0.07473213970661163, + -0.13146282732486725, + 0.038064904510974884, + 0.1531292200088501, + -0.10464932024478912, + 0.19341252744197845, + -0.3296719193458557, + -0.1406797468662262, + -0.15250054001808167, + 0.09428688883781433, + -0.019406825304031372, + -0.061579588800668716, + 0.056586526334285736, + 0.03924350067973137, + -0.5412783026695251, + -0.2921083867549896, + 0.4318559467792511, + 0.05303330719470978, + -0.21462926268577576, + -0.2415643334388733, + -0.3421584665775299, + -0.08560625463724136, + -0.011194444261491299, + -0.34886765480041504, + 0.07568749040365219 + ], + [ + 0.008060253225266933, + 0.05988208204507828, + -0.0013654726790264249, + 0.003013470908626914, + -0.2575582265853882, + -0.04816637188196182, + 0.1732870191335678, + 0.05960530415177345, + 0.015651829540729523, + -0.0640391856431961, + -0.22270606458187103, + -0.12955482304096222, + 0.04919688403606415, + 0.06474018096923828, + 0.042336441576480865, + 0.039161317050457, + 0.10421798378229141, + 0.2790203094482422, + 0.21959391236305237, + 0.6378502249717712, + -0.0898100808262825, + 0.005358349531888962, + -0.10286261886358261, + -0.01699739322066307, + 0.1471143364906311, + -0.11436440050601959, + 0.026341823861002922, + 0.06510856002569199, + 0.1601673662662506, + 0.031657010316848755, + 0.016646092757582664, + 0.181640625, + 0.11736728996038437, + -0.021263377740979195, + -0.15383924543857574, + -0.2691207230091095, + 0.011040529236197472, + 0.05587590113282204, + -0.09700872749090195, + 0.27181166410446167, + 0.0019587641581892967, + -0.022556912153959274, + -0.3491414189338684, + -0.22165030241012573, + 0.057059094309806824, + -0.18389983475208282, + -0.03395810350775719, + -0.15415963530540466, + -0.2874647378921509, + 0.0835510715842247, + 0.1143430769443512, + 0.08022210001945496, + 0.04676882550120354, + 0.06359831988811493, + -0.1185191422700882, + 0.029990950599312782, + -0.005116136744618416, + 0.02068069763481617, + -0.1840038299560547, + 0.176298588514328, + -0.24522428214550018, + 0.03290135785937309, + -0.317613810300827, + 0.031289223581552505, + 0.13962461054325104, + -0.013756575994193554, + 0.030770571902394295, + -0.08911740779876709, + -0.012970815412700176, + -0.01519605703651905, + -0.0057386127300560474, + 0.04031286761164665, + -0.13666634261608124, + 0.06901378184556961, + -0.37476232647895813, + 0.05644898861646652, + 0.08669921010732651, + -0.10550033301115036, + -0.06645208597183228, + 0.025189977139234543, + -0.027945224195718765, + 0.08571132272481918, + -0.09880457073450089, + 0.011575276963412762, + -0.3854926526546478, + -0.36543604731559753, + -0.023380184546113014, + 0.24693846702575684, + -0.007882521487772465, + 0.037850890308618546, + -0.10896891355514526, + 0.11888999491930008, + -0.3500766456127167, + 0.07734168320894241, + -0.007458634674549103, + -0.05164363980293274, + -0.06706497073173523, + -0.5805925130844116, + -0.1763964146375656, + -0.07913561165332794, + 0.03218692168593407, + -0.07781650871038437, + -0.25672265887260437, + 0.21471354365348816, + 0.28738072514533997, + -0.00545237772166729, + 0.05537362024188042, + 0.07160620391368866, + -0.03422999754548073, + -0.20350764691829681, + 0.14728330075740814, + 0.020241010934114456, + -0.4721711575984955, + 0.035242244601249695, + 0.10296231508255005, + 0.37980589270591736, + 0.035042062401771545, + 0.014317094348371029, + 0.13781072199344635, + -0.0637299194931984, + -0.17858773469924927, + 0.15325239300727844, + -0.5329996347427368, + 0.15490716695785522, + 0.2627120018005371, + -0.02690720558166504, + -0.18768227100372314, + 0.1807984858751297 + ], + [ + -0.11069995909929276, + -0.0361710749566555, + -0.05023827776312828, + -0.11715874075889587, + 0.006298726890236139, + 0.06394832581281662, + -0.07114605605602264, + 0.04748836159706116, + -0.03151697292923927, + 0.4037519097328186, + 0.07973339408636093, + -0.07463320344686508, + 0.13119105994701385, + 0.10082056373357773, + -0.022978443652391434, + 0.13820843398571014, + 0.08049740642309189, + -0.006477805785834789, + 0.08643132448196411, + 0.11766654253005981, + 0.0637887567281723, + -0.12851934134960175, + 0.11493454873561859, + -0.31300845742225647, + 0.0530414804816246, + 0.033691659569740295, + -0.22755147516727448, + -0.008600138127803802, + -0.10520932823419571, + -0.16335712373256683, + -0.029736001044511795, + 0.19327537715435028, + -0.21595466136932373, + -0.02394651062786579, + 0.04072326049208641, + -0.19098258018493652, + 0.0352352038025856, + 0.049644578248262405, + 0.07671507447957993, + -0.055751193314790726, + -0.3795986771583557, + 0.12021838873624802, + -0.12458398938179016, + 0.016724998131394386, + -0.2290058434009552, + -0.11339280009269714, + -0.002168697537854314, + -0.01945374347269535, + -0.07510098069906235, + -0.10881152749061584, + -0.09698985517024994, + -0.0821429193019867, + -0.024197060614824295, + 0.016772348433732986, + -0.16653260588645935, + 0.1247415840625763, + -0.016146106645464897, + -0.010790119878947735, + 0.20532923936843872, + 0.21497324109077454, + 0.05237189307808876, + -0.24924395978450775, + -0.19208846986293793, + 0.07718594372272491, + 0.04791101813316345, + 0.33555424213409424, + -0.034399282187223434, + 0.1015062928199768, + -0.135126531124115, + -0.11011108011007309, + 0.05888548120856285, + 0.14566509425640106, + -0.0346771739423275, + 0.0819680467247963, + 0.03055502474308014, + -0.00713674072176218, + 0.0984339639544487, + -0.07809858024120331, + 0.0003933811385650188, + -0.007449776399880648, + 0.05727580189704895, + 0.14825616776943207, + -0.6510351896286011, + -0.01617405191063881, + -0.11186500638723373, + 0.015037063509225845, + 0.09380999952554703, + 0.05348784849047661, + -0.07382088154554367, + 0.1783403903245926, + 0.032999929040670395, + 0.0039691682904958725, + -0.0038469270803034306, + -0.22246809303760529, + 0.0575171522796154, + -0.045170124620199203, + 0.006224551238119602, + -0.11150231212377548, + -0.06002156063914299, + -0.09353010356426239, + -0.15149933099746704, + -0.23994587361812592, + -0.06334011256694794, + 0.06319431215524673, + -0.11241863667964935, + 0.02395869977772236, + 0.3657398819923401, + -0.04065369814634323, + -0.11754345148801804, + 0.17338091135025024, + -0.0568859837949276, + 0.05827471986413002, + 0.09721074998378754, + -0.13025976717472076, + 0.10374946892261505, + 0.4384205937385559, + -0.03487764298915863, + -0.03445944935083389, + 0.08922462910413742, + 0.0035328706726431847, + 0.06022754684090614, + 0.1041577085852623, + -0.05793899670243263, + 0.06821019947528839, + -0.08021565526723862, + 0.045155275613069534, + -0.4623643755912781, + -0.04474380239844322 + ], + [ + 0.09019472450017929, + -0.01584523543715477, + -0.0823553130030632, + -0.07583436369895935, + 0.005524053703993559, + -0.04324500262737274, + 0.03214110806584358, + -0.04296056181192398, + -0.04444437474012375, + -0.16958926618099213, + 0.06742851436138153, + 0.09212705492973328, + 0.07566104829311371, + -0.1028389111161232, + 0.10844631493091583, + -0.06225913017988205, + -0.06552150100469589, + -0.34070682525634766, + 0.029548844322562218, + 0.13618813455104828, + 0.1861131340265274, + -0.017207447439432144, + -0.1894988715648651, + 0.009387781843543053, + -0.06112179532647133, + -0.08514562994241714, + 0.1318208873271942, + 0.008560947142541409, + 0.13941338658332825, + 0.12381214648485184, + 0.14116498827934265, + -0.32244759798049927, + -0.22140157222747803, + -0.02758917398750782, + 0.1092623844742775, + -0.11741430312395096, + -0.027313441038131714, + 0.14570213854312897, + 0.014553910121321678, + -0.011813564226031303, + 0.10577317327260971, + -0.03657934442162514, + -0.12064201384782791, + 0.1254260390996933, + -0.24406656622886658, + -0.1361042559146881, + 0.15975819528102875, + -0.24544507265090942, + 0.09915439039468765, + 0.023997463285923004, + 0.019991682842373848, + 0.12410097569227219, + -0.05489063635468483, + -0.06096179783344269, + -0.026923412457108498, + 0.046569451689720154, + -0.029221873730421066, + 0.15861906111240387, + -0.07817312330007553, + 0.015204569324851036, + -0.23029068112373352, + 0.12302616238594055, + -0.09459497779607773, + 0.029163779690861702, + 0.054196301847696304, + 0.07156804203987122, + 0.08272919058799744, + -0.10209823399782181, + 0.06663566827774048, + 0.06485863775014877, + -0.037604253739118576, + -0.046474065631628036, + -0.0028736810199916363, + 0.04942545294761658, + -0.07913847267627716, + 0.04599349945783615, + 0.11606284230947495, + -0.05313688516616821, + 0.024331675842404366, + -0.12469855695962906, + 0.10146526992321014, + 0.11884273588657379, + -0.17550964653491974, + -3.1381605367641896e-05, + 0.002040270483121276, + -0.03323400765657425, + 0.11809074133634567, + 0.09043652564287186, + -0.011575785465538502, + 0.1427130401134491, + 0.16791509091854095, + -0.05635606870055199, + -0.39032429456710815, + 0.1435529887676239, + -0.097979836165905, + -0.2738165557384491, + -0.1712430864572525, + 0.026801003143191338, + 0.02471931464970112, + -0.05322689935564995, + 0.1033157929778099, + 0.04486861452460289, + -0.05226697400212288, + 0.11500038206577301, + 0.043725986033678055, + -0.1764233112335205, + -0.19012592732906342, + -0.13764843344688416, + -0.3133106231689453, + -0.008518649265170097, + -0.0777585357427597, + 0.17646904289722443, + 0.14687032997608185, + -0.09803684800863266, + 0.08861979842185974, + 0.09559087455272675, + 0.3461950421333313, + 0.06764566898345947, + 0.0806322693824768, + 0.05198058858513832, + -0.04391499608755112, + -0.08683855831623077, + -0.025047991424798965, + 0.16992712020874023, + -0.3815115690231323, + -0.14546774327754974, + -0.004296218045055866, + -0.21258191764354706 + ], + [ + 0.07700692862272263, + 0.362967848777771, + 0.037658873945474625, + 0.0919339582324028, + 0.09182237088680267, + 0.03532266616821289, + -0.14692038297653198, + -0.27524110674858093, + -0.03426111489534378, + -0.22764326632022858, + -0.07577305287122726, + 0.13078393042087555, + -0.38927850127220154, + 0.01637921668589115, + 0.13312627375125885, + 0.2689998745918274, + -0.008082779124379158, + -0.184591144323349, + -0.09901832789182663, + 0.32574328780174255, + -0.1569225937128067, + -0.09810412675142288, + 0.0529470220208168, + -0.09569448977708817, + 0.0325014665722847, + -0.02157684788107872, + 0.012269697152078152, + 0.006447982974350452, + 0.1318628042936325, + 0.09955150634050369, + 0.104733407497406, + -0.1557747721672058, + 0.14787371456623077, + -0.11121635138988495, + 0.30065950751304626, + -0.03753340244293213, + 0.10796582698822021, + 0.04024107754230499, + 0.03620349243283272, + 0.0020574594382196665, + 0.088230662047863, + -0.04680425673723221, + -0.13738657534122467, + 0.12510453164577484, + 0.02170502208173275, + 0.10886172950267792, + 0.12985041737556458, + 0.010413152165710926, + -0.14913801848888397, + 0.0025767115876078606, + -0.05778643861413002, + 0.05178946629166603, + 0.06382680684328079, + 0.0368037186563015, + -0.12735480070114136, + -0.008801484480500221, + -0.06999295949935913, + 0.09571041911840439, + 0.06604178249835968, + 0.1366422325372696, + -0.09479035437107086, + -0.15079167485237122, + -0.2791488468647003, + 0.17681241035461426, + -0.11943936347961426, + 0.12223619222640991, + 0.0598648302257061, + 0.3608716130256653, + 0.041329920291900635, + -0.05831841006875038, + -0.0397334098815918, + 0.13715441524982452, + -0.0741693302989006, + 0.16515474021434784, + 0.08594008535146713, + -0.06003338471055031, + 0.07296983152627945, + 0.015117044560611248, + -0.018771087750792503, + 0.08844926953315735, + 0.006341609638184309, + 0.10250641405582428, + 0.03865562006831169, + 0.04381156340241432, + -0.23889409005641937, + -0.07095803320407867, + 0.19262228906154633, + -0.20135721564292908, + 0.004646706394851208, + 0.2518692910671234, + 0.005224279593676329, + -0.010860745795071125, + -0.13003754615783691, + 0.03218717500567436, + 0.05152931064367294, + 0.012877043336629868, + -0.29062163829803467, + -0.19444923102855682, + -0.2987559139728546, + 0.1600905805826187, + -0.012921495363116264, + 0.002897716360166669, + -0.060245633125305176, + 0.12232869863510132, + 0.08183345198631287, + -0.057950105518102646, + -0.029539527371525764, + 0.20980355143547058, + 0.0006047409842722118, + -0.015367820858955383, + -0.11384140700101852, + 0.03260524570941925, + 0.2585211992263794, + 0.017726579681038857, + 0.1078614592552185, + -0.4565546214580536, + 0.04262514039874077, + 0.12797331809997559, + 0.05106595531105995, + -0.1423257440328598, + 0.0029127895832061768, + 0.03996835649013519, + -0.017228877171874046, + -0.1539647877216339, + 0.07608885318040848, + -0.1613522469997406, + -0.17343954741954803, + 0.0684589222073555 + ], + [ + -0.4555928707122803, + -0.03857779502868652, + -0.0338260680437088, + -0.028421912342309952, + -0.15463918447494507, + 0.009381053037941456, + -0.3342217206954956, + 0.10372021049261093, + -0.05657382309436798, + -0.028352094814181328, + -0.3685641884803772, + -0.25119027495384216, + 0.18288768827915192, + -0.007007717154920101, + 0.03491612896323204, + 0.02223595418035984, + 0.056111935526132584, + 0.048045702278614044, + 0.015422199852764606, + -0.3294396698474884, + -0.6259031891822815, + 0.13333939015865326, + -0.1190742552280426, + 0.0735064148902893, + -0.06638183444738388, + 0.023278165608644485, + 0.13946741819381714, + 0.08310502022504807, + -0.054623737931251526, + -0.4734610915184021, + -0.2697869837284088, + -0.1830895096063614, + 0.03143030032515526, + -0.11463603377342224, + -0.03707747161388397, + 0.16919586062431335, + 0.11740085482597351, + 0.028525397181510925, + -0.11406080424785614, + -0.30353981256484985, + 0.04995578154921532, + 0.14487838745117188, + 0.10259928554296494, + 0.06881336122751236, + 0.0775798037648201, + 0.10065710544586182, + -0.125356987118721, + -0.03245577588677406, + -0.06819174438714981, + -0.1470710039138794, + 0.03730865940451622, + -0.09006353467702866, + 0.021707510575652122, + 0.038076866418123245, + -0.0038729910738766193, + 0.03094407171010971, + -0.023129940032958984, + 0.12174216657876968, + -0.03536384925246239, + -0.04608716815710068, + -0.12768273055553436, + 0.17003260552883148, + 0.17700687050819397, + 0.27759116888046265, + -0.18433189392089844, + -0.5560452938079834, + 0.20588411390781403, + 0.07144249975681305, + 0.020802507176995277, + 0.1210118904709816, + 0.01882963627576828, + -0.19240494072437286, + 0.19744153320789337, + -0.04764750599861145, + -0.05234755948185921, + 0.42689597606658936, + -0.05883520841598511, + -0.07991089671850204, + 0.07526478916406631, + 0.03213942423462868, + 0.12761227786540985, + 0.13374973833560944, + -0.04362761601805687, + -0.021113382652401924, + -0.02114981599152088, + -0.04849012941122055, + -0.03374556452035904, + -0.23703891038894653, + 0.06574226170778275, + -0.12947668135166168, + -0.030203619971871376, + -0.006273951381444931, + -0.004531534854322672, + -0.012808607891201973, + -9.947730723069981e-05, + -0.13016155362129211, + -0.09106133133172989, + 0.011237537488341331, + -0.08298385143280029, + 0.016452113166451454, + -0.05583765730261803, + 0.11382216960191727, + -0.07412587851285934, + 0.022424286231398582, + 0.10804887115955353, + 0.06762203574180603, + -0.24973170459270477, + -0.014700664207339287, + 0.1319582313299179, + 0.025003384798765182, + -0.19519846141338348, + -0.03925691545009613, + 0.1835165023803711, + -0.16844400763511658, + -0.14131464064121246, + -1.9416025876998901, + 0.10302198678255081, + 0.2774777114391327, + -0.12134549021720886, + 0.08280422538518906, + -0.1514057219028473, + 0.18511226773262024, + 0.18247590959072113, + 0.12227306514978409, + 0.3694884181022644, + 0.07677261531352997, + 0.27845844626426697, + -0.019107036292552948 + ], + [ + 0.0570477657020092, + 0.02580777369439602, + -0.027045562863349915, + -0.3938111364841461, + -0.12496328353881836, + -0.005740193650126457, + -0.36344215273857117, + 0.24376553297042847, + 0.10221818089485168, + 0.00819211732596159, + -0.10368043184280396, + 0.1426512897014618, + 0.44817373156547546, + 0.03963415324687958, + 0.04962139204144478, + -0.13696862757205963, + -0.3246763050556183, + 0.09797266870737076, + 0.08890107274055481, + 0.0786423310637474, + 0.09095216542482376, + 0.31812459230422974, + -0.3281908333301544, + -0.30132195353507996, + 0.076666921377182, + -0.3348168432712555, + -0.13213732838630676, + -0.08620228618383408, + 0.5397965312004089, + -0.1883048415184021, + -0.12734763324260712, + -0.016609156504273415, + 0.02075969986617565, + 0.22663933038711548, + -0.3172050714492798, + -0.43840011954307556, + -0.1671704649925232, + 0.30818164348602295, + 0.19551801681518555, + -0.07075397670269012, + -0.6091893911361694, + 0.04267750307917595, + -0.1238492950797081, + -0.290554940700531, + 0.32172349095344543, + -0.018335087224841118, + 0.0718098059296608, + -0.018623746931552887, + 0.20343279838562012, + -0.17332495748996735, + -0.37087494134902954, + 0.032574836164712906, + -0.18469935655593872, + 0.06060893088579178, + 0.007792746648192406, + -0.02293357253074646, + -0.02857653982937336, + 0.010247007943689823, + -0.2822771966457367, + -0.3528403341770172, + 0.06564489006996155, + -0.6833745241165161, + 0.12093828618526459, + -0.24923212826251984, + 0.014938391745090485, + -0.14017713069915771, + -0.016300195828080177, + -0.24036595225334167, + -0.03851170837879181, + -0.1761503666639328, + 0.06860928982496262, + 0.0672663003206253, + -0.004755143076181412, + -0.004657530691474676, + -0.2684628665447235, + -0.19284537434577942, + 0.04050600156188011, + -0.29997357726097107, + 0.06807784736156464, + 0.0753079205751419, + -0.14991848170757294, + 0.27532365918159485, + 0.049833036959171295, + 0.29846087098121643, + 0.09443292021751404, + 0.11916235089302063, + 0.0853794738650322, + -0.16858069598674774, + -0.007355846464633942, + -0.017552129924297333, + -0.03511343523859978, + -0.16823948919773102, + -0.6627655029296875, + 0.11420208215713501, + -0.05470462888479233, + 0.03870198875665665, + -0.3663022816181183, + 0.3550664782524109, + 0.2918487787246704, + 0.22520728409290314, + 0.1341996192932129, + -0.11122198402881622, + -0.012851831503212452, + -0.2627066969871521, + -0.10199342668056488, + -0.11768098175525665, + -0.3489589989185333, + -0.006385158747434616, + 0.1747308373451233, + -0.15219472348690033, + 0.41335341334342957, + -0.11679571866989136, + -0.11973913013935089, + 0.12023627758026123, + 0.06902012974023819, + 0.2533632814884186, + -0.14335007965564728, + -0.6772888898849487, + -0.13706187903881073, + -0.15908710658550262, + 0.06809552758932114, + 0.21025623381137848, + -0.24327750504016876, + 0.2716046869754791, + 0.04970308765769005, + 0.039718978106975555, + -0.3893130421638489, + -0.2832285463809967 + ], + [ + -0.07558222860097885, + 0.006723813712596893, + 0.023517411202192307, + -0.01663401909172535, + 0.009884004481136799, + -0.0345214419066906, + -0.014434866607189178, + 0.08104366064071655, + 0.11513692140579224, + 0.011845191940665245, + 0.10644713789224625, + -0.10059388726949692, + -0.0013881268678233027, + 0.03635132685303688, + 0.13399481773376465, + 0.03583725541830063, + 0.09599944204092026, + -0.011792699806392193, + 0.20306260883808136, + -0.08006599545478821, + -0.10591718554496765, + 0.03839271515607834, + -0.1344800889492035, + 0.07541996985673904, + 0.19879339635372162, + -0.009614686481654644, + -0.006225500255823135, + -0.08185599744319916, + 0.009963863529264927, + 0.060309890657663345, + 0.07306483387947083, + 0.07053721696138382, + -0.44253814220428467, + 0.03675201162695885, + -0.007057767827063799, + -0.3239661157131195, + 0.0885327085852623, + 0.187168687582016, + -0.0917423740029335, + -0.07572264969348907, + 0.032630279660224915, + -0.09048868715763092, + 0.014637559652328491, + -0.020168974995613098, + -0.11486317217350006, + -0.29941606521606445, + -0.06866921484470367, + 0.03329930827021599, + 0.1143377348780632, + 0.011615258641541004, + -0.1292702853679657, + -0.18587277829647064, + 0.06681372970342636, + -0.018505943939089775, + 0.012830864638090134, + 0.10734078288078308, + 0.011985646560788155, + 0.06725069135427475, + 0.11573486030101776, + -0.04502275586128235, + 0.014525610953569412, + -0.09427444636821747, + -0.008256297558546066, + -0.030384888872504234, + 0.01906091906130314, + 0.1276632696390152, + 0.11930136382579803, + -0.015630263835191727, + 0.08832965046167374, + -0.06598871946334839, + 0.0797785222530365, + -0.0821489542722702, + 0.11018653213977814, + 0.015655633062124252, + -0.058477360755205154, + -0.019628318026661873, + 0.05653474107384682, + 0.019071504473686218, + -0.044273052364587784, + 0.006489504594355822, + -0.09654926508665085, + 0.10203201323747635, + -0.035749100148677826, + -0.07216256111860275, + -0.008805365301668644, + 0.10112869739532471, + 0.02296634018421173, + -0.010583718307316303, + -0.04380735382437706, + -0.008884803391993046, + -0.18332038819789886, + 0.10686099529266357, + -0.09588604420423508, + 0.08822908997535706, + -0.043758559972047806, + -0.037908557802438736, + -0.03904839977622032, + 0.07938092201948166, + -0.02105756290256977, + 0.13244135677814484, + 0.10143657773733139, + -0.10154028236865997, + 0.024699339643120766, + -0.008070564828813076, + -0.04247472062706947, + -0.03536387160420418, + -0.011009926907718182, + -0.06588172912597656, + -0.09244298189878464, + 0.01887907274067402, + -0.14008377492427826, + 0.05023614317178726, + 0.04619153216481209, + -0.056905340403318405, + -0.012789773754775524, + 0.05880126357078552, + 0.007525037974119186, + -0.07670587301254272, + 0.06436154246330261, + -0.010502236895263195, + -0.07690519094467163, + -0.07466233521699905, + 0.1372014582157135, + 0.11761488020420074, + -0.011373582296073437, + -0.0011860583908855915, + -0.010885713621973991, + -0.1659897118806839 + ], + [ + -0.06940096616744995, + -0.17101828753948212, + -0.1365196257829666, + -0.5359589457511902, + 0.0035101971589028835, + -0.32338643074035645, + -0.0028992046136409044, + -0.4929232895374298, + -0.051545292139053345, + 0.12274091690778732, + 0.12362087517976761, + 0.06940197199583054, + 0.01618785224854946, + 0.17607545852661133, + 0.19521698355674744, + -0.08301877230405807, + 0.30363303422927856, + -0.23456966876983643, + 0.23502959311008453, + 0.06785297393798828, + 0.018351225182414055, + 0.0029580360278487206, + 0.009887988679111004, + -0.13547582924365997, + -0.31451380252838135, + 0.11923503875732422, + -0.11990857124328613, + -0.009594987146556377, + 0.05963154509663582, + -0.09855768829584122, + 0.25284260511398315, + 0.13296480476856232, + -0.17616748809814453, + 0.08547444641590118, + -0.018636619672179222, + -0.003272861009463668, + -0.012520909309387207, + 0.11271005123853683, + -0.0015575947472825646, + 0.0287784431129694, + -0.20700107514858246, + -0.04870426654815674, + 0.13390932977199554, + 0.11894164234399796, + -0.011908152140676975, + -0.09465628862380981, + 0.09135694056749344, + -0.01485937274992466, + -0.005657576024532318, + 0.12360452115535736, + -0.1002417802810669, + -0.4311646819114685, + -0.024340860545635223, + -0.04418100416660309, + 0.06588611751794815, + -0.49956372380256653, + -0.25284457206726074, + 0.029253046959638596, + 0.02947372943162918, + -0.5961841940879822, + -0.0917447879910469, + 0.3095843195915222, + -0.013903687708079815, + 0.029768135398626328, + -0.048705197870731354, + -0.29907041788101196, + 0.21262812614440918, + 0.13538594543933868, + 0.007508663460612297, + 0.19134335219860077, + 0.17261554300785065, + -0.05424884706735611, + -0.21821443736553192, + -0.04494252800941467, + 0.03894960880279541, + -0.021166464313864708, + -0.18331748247146606, + 0.021135590970516205, + -0.034343551844358444, + -0.010908463038504124, + -0.2805461585521698, + 0.23111887276172638, + 0.6385664939880371, + 0.09066811203956604, + -0.19490426778793335, + -0.4323098659515381, + 0.14100602269172668, + 0.017653843387961388, + -0.2232057899236679, + -0.3629726469516754, + -0.07811284065246582, + -0.32620784640312195, + -0.21479575335979462, + -0.053145330399274826, + 0.16462266445159912, + -0.350886732339859, + -0.8266401886940002, + -0.11285221576690674, + -0.3422500491142273, + -0.07900942862033844, + 0.08357035368680954, + -0.5361188650131226, + -0.07456646114587784, + -0.1060861125588417, + -0.04974236339330673, + -0.16316840052604675, + 0.3063100576400757, + -0.08602432161569595, + -0.43071404099464417, + 0.07197558134794235, + 0.3050892651081085, + 0.11921555548906326, + 0.14531128108501434, + -0.05980633944272995, + 0.08422769606113434, + 0.14766906201839447, + 0.13894858956336975, + 0.12595485150814056, + 0.10061817616224289, + 0.49659088253974915, + -0.0019406821811571717, + -0.060929350554943085, + 0.04502672329545021, + 0.21883413195610046, + 0.16355019807815552, + -0.048710405826568604, + 0.016856729984283447, + -0.061476755887269974 + ], + [ + 0.10016962885856628, + -0.010274588130414486, + 0.05182357877492905, + 0.06956535577774048, + -0.0173965934664011, + -0.011244586668908596, + 0.06379945576190948, + 0.052551429718732834, + 0.06531822681427002, + 0.04006089270114899, + -0.10019068419933319, + 0.002959904260933399, + 0.12074568122625351, + -0.007632292341440916, + 0.022090667858719826, + 0.11650747805833817, + 0.006939641200006008, + -0.08818849176168442, + 0.0108944745734334, + -0.06506664305925369, + 0.013764986768364906, + 0.005276576150208712, + -0.08876977860927582, + 0.041864048689603806, + 0.09322181344032288, + -0.004488212056457996, + -0.07307851314544678, + -0.0010372435208410025, + 0.049360815435647964, + 0.0054590716026723385, + 0.006440204102545977, + -0.06820422410964966, + -0.021676786243915558, + 0.0397145114839077, + 0.0012800745898857713, + 0.07386341691017151, + -0.005496046505868435, + 0.03908878564834595, + -0.018884049728512764, + 0.084945447742939, + -0.011544589884579182, + -0.12145821005105972, + -0.06262265145778656, + 0.07857023924589157, + -0.04001670330762863, + 0.0456671267747879, + -0.12914526462554932, + 0.04664640501141548, + 0.017249664291739464, + -0.008213689550757408, + -0.078413225710392, + -0.05715758353471756, + 0.06142508238554001, + 0.02191011980175972, + 0.02089688368141651, + -0.026662923395633698, + 0.002659400925040245, + 0.010687260888516903, + 0.00415431521832943, + 0.017348365858197212, + -0.06585202366113663, + 0.009207702241837978, + 0.08492682129144669, + 0.025843800976872444, + 0.008410198614001274, + -0.040954507887363434, + -0.16241639852523804, + -0.09408893436193466, + 0.030860116705298424, + -0.030448712408542633, + 0.024618906900286674, + -0.07624676823616028, + -0.050029706209897995, + 0.015101027674973011, + 0.008731895126402378, + -0.005163616966456175, + -0.016517074778676033, + 0.013342677615582943, + -0.018305223435163498, + 0.06058698520064354, + 0.02535492368042469, + -0.0014730156399309635, + 0.03812725469470024, + 0.022633083164691925, + 0.026032086461782455, + 0.009453002363443375, + 0.059898823499679565, + -0.05425108224153519, + -0.035754647105932236, + 0.07836917042732239, + 0.038476064801216125, + 0.010618230327963829, + -0.09000751376152039, + -0.043080467730760574, + 0.07129234820604324, + -0.06593001633882523, + -0.005188079085201025, + 0.019621217623353004, + -0.048588015139102936, + -0.06472895294427872, + -0.009012101218104362, + -0.03380448743700981, + 0.08233749866485596, + -0.045430250465869904, + 3.431398363318294e-05, + -0.01813228242099285, + 0.05594703182578087, + 0.06519906967878342, + -0.09853655844926834, + 0.025499727576971054, + -0.05555672198534012, + -0.02468022145330906, + -0.06364111602306366, + -0.037881966680288315, + -0.004600340500473976, + -0.016238372772932053, + -0.010151775553822517, + -0.04727615788578987, + 0.007969299331307411, + -0.04190324619412422, + 0.002025892958045006, + 0.037030212581157684, + -0.01608317904174328, + 0.09813655912876129, + -0.08405668288469315, + 0.016390005126595497, + 0.017167193815112114, + -0.05458718165755272 + ], + [ + 0.04893726482987404, + -0.0058402493596076965, + 0.09677095711231232, + -0.01799270138144493, + -0.0845121443271637, + 0.050097040832042694, + -0.0760682076215744, + 0.047012072056531906, + 0.04017748683691025, + -0.000549644639249891, + -0.008726249448955059, + 0.09820261597633362, + 0.003039821982383728, + 0.0014271332183852792, + -0.02098223753273487, + -0.04054472595453262, + 0.1749071478843689, + -0.20440784096717834, + 0.032307855784893036, + 0.09268529713153839, + -0.3435579240322113, + 0.06814207136631012, + -0.05877280607819557, + 0.1223217099905014, + 0.11179354041814804, + -0.051450274884700775, + 0.025481754913926125, + -0.1819257289171219, + 0.007147911936044693, + 0.08810316771268845, + 0.20382805168628693, + 0.04239678010344505, + -0.1195031926035881, + 0.0032910157460719347, + -0.007354235276579857, + 0.24458040297031403, + -0.0926448255777359, + 0.03889201954007149, + -0.08063124120235443, + 0.02067529410123825, + 0.021831054240465164, + -0.044897451996803284, + -0.0507982112467289, + 0.04813080653548241, + 0.011655500158667564, + -0.1006115972995758, + 0.13817951083183289, + 0.11375591158866882, + -0.029052352532744408, + 0.032122306525707245, + 0.08776398003101349, + -0.1208442971110344, + 0.1185803934931755, + -0.0451594702899456, + 0.06881650537252426, + 0.1314273327589035, + 0.13900017738342285, + 0.1061820238828659, + -0.06934898346662521, + -0.01508370228111744, + -0.16175629198551178, + 0.025260161608457565, + -0.019894346594810486, + 0.1751524955034256, + 0.004936481360346079, + 0.14545002579689026, + 0.19607006013393402, + 0.018146924674510956, + -0.026711300015449524, + -0.025026461109519005, + 0.024112621322274208, + -0.008576999418437481, + 0.08398990333080292, + -0.011081922799348831, + 0.04346594214439392, + 0.07444331794977188, + 0.033239029347896576, + 0.07223405689001083, + -0.1309126615524292, + -0.02131984755396843, + -0.28765928745269775, + 0.12153533101081848, + -0.029027482494711876, + 0.06173262000083923, + -0.26933974027633667, + 0.04371882602572441, + -0.038598477840423584, + 0.1349632889032364, + 0.03874923661351204, + 0.08301515132188797, + 0.1785111278295517, + 0.051854055374860764, + -0.3853868246078491, + 0.05699970945715904, + -0.012921108864247799, + 0.01769828237593174, + -0.03694178909063339, + 0.025612691417336464, + 0.005346054211258888, + -0.1594909131526947, + -0.07150875777006149, + -0.28304287791252136, + 0.05378830432891846, + -0.08613017946481705, + -0.09403800964355469, + -0.2186294049024582, + -0.03819073736667633, + -0.04001442342996597, + 0.06888549029827118, + -0.046198900789022446, + -0.2528606653213501, + 0.17923451960086823, + 0.07434991747140884, + -0.05781762674450874, + -0.0570165291428566, + 0.0757913887500763, + 0.0135014858096838, + -0.0033735090401023626, + 0.11975431442260742, + -0.0023670245427638292, + -0.011395405977964401, + -0.004186677746474743, + 0.03269597142934799, + 0.1403622031211853, + -0.060640741139650345, + -0.14460735023021698, + 0.04573328047990799, + 0.007799024228006601 + ], + [ + -0.009682480245828629, + 0.05292750149965286, + 0.07668240368366241, + 0.10546676069498062, + 0.029432740062475204, + -0.45123785734176636, + 0.09541188925504684, + -0.10465805977582932, + -0.015568483620882034, + -0.17877328395843506, + 0.14573606848716736, + -0.03973155841231346, + -0.12162008881568909, + -0.24564428627490997, + 0.11408292502164841, + 0.06726127862930298, + 0.13195553421974182, + -0.1201321929693222, + 0.0724555030465126, + -0.04764515906572342, + 0.2118200659751892, + -0.35577526688575745, + 0.006964277476072311, + -0.5206752419471741, + 0.16660310328006744, + -0.03923766314983368, + 0.10647612065076828, + -0.1221228763461113, + -0.08642400056123734, + -0.24455711245536804, + -0.24729026854038239, + -0.28766903281211853, + -0.31521761417388916, + 0.02641470730304718, + -0.029588716104626656, + -0.5426087975502014, + -0.11675895750522614, + -0.10410301387310028, + -0.12491942197084427, + 0.1097325012087822, + -0.13869278132915497, + 0.08168550580739975, + -0.018541323021054268, + -0.24245180189609528, + 0.0056050121784210205, + 0.1566111296415329, + 0.08483851701021194, + 0.22938497364521027, + 0.0036835551727563143, + -0.5441039204597473, + 0.09151265025138855, + -0.12439418584108353, + -0.14873455464839935, + 0.09215636551380157, + -0.012051736935973167, + -0.04932255670428276, + 0.21852144598960876, + 0.05425585061311722, + -0.01630050502717495, + 0.09343045949935913, + 0.007835119031369686, + 0.04148249328136444, + 0.08018830418586731, + -0.11759725213050842, + -0.024801168590784073, + -0.04710831493139267, + 0.28018680214881897, + 0.07934600859880447, + -0.08701283484697342, + 0.12618325650691986, + -0.0806952714920044, + -0.28779932856559753, + 0.06395451724529266, + -0.13048182427883148, + -0.059479426592588425, + 0.2232690155506134, + -0.027086252346634865, + -0.16470271348953247, + -0.10676867514848709, + -0.0030674587469547987, + 0.16489183902740479, + 0.177242249250412, + 0.05709878355264664, + -0.13217869400978088, + 0.14346542954444885, + 0.06503988057374954, + 0.03210723027586937, + 0.09677568078041077, + -0.05039878189563751, + 0.027128838002681732, + 0.18376664817333221, + -0.07421406358480453, + -0.32794326543807983, + -0.34613439440727234, + -0.11973515897989273, + -0.03209976106882095, + -0.020840223878622055, + 0.048445168882608414, + 0.10510396957397461, + -0.403597891330719, + -0.11090874671936035, + -0.1491924226284027, + -0.024676864966750145, + -0.022991839796304703, + -0.09472640603780746, + -0.11453399062156677, + -0.08906660228967667, + 0.2052864283323288, + 0.17384682595729828, + -0.07431571930646896, + 0.14708857238292694, + -0.3357168138027191, + 0.021947024390101433, + -0.05447414889931679, + 0.08098731189966202, + 0.11021174490451813, + -0.22608281672000885, + 0.5040015578269958, + -0.25376829504966736, + 0.22929547727108002, + 0.19472669064998627, + -0.22433297336101532, + -0.08627758920192719, + -0.09376882761716843, + 0.011259117163717747, + 0.10272569209337234, + -0.030747821554541588, + -0.0700654610991478 + ], + [ + -0.03373949974775314, + -0.20386609435081482, + 0.028986118733882904, + -0.02541741356253624, + 0.17529776692390442, + 0.06198277696967125, + -0.08386463671922684, + 0.0013539398787543178, + -0.1949346512556076, + 0.1320994645357132, + -0.21428871154785156, + 0.013764902949333191, + 0.07008123397827148, + -0.07240710407495499, + 0.05570218712091446, + 0.11839047819375992, + -0.2439432442188263, + 0.32172784209251404, + 0.0011403111275285482, + -0.433788925409317, + 0.1881006509065628, + 0.051037225872278214, + -0.11586712300777435, + -0.0021518152207136154, + -0.3133847713470459, + 0.07732443511486053, + 0.05773279815912247, + 0.07260143756866455, + -0.04432855173945427, + 0.10585169494152069, + 0.1755530834197998, + -0.007567631546407938, + -0.07691910117864609, + -0.26937299966812134, + -0.0182910468429327, + -0.04958734288811684, + -0.22675833106040955, + 0.0002216265711467713, + 0.02831379696726799, + -0.0055421567521989346, + 0.011302954517304897, + -0.08322001993656158, + 0.08351431041955948, + 0.024850662797689438, + 0.12815693020820618, + -0.2277359664440155, + -0.18226532638072968, + 0.07065649330615997, + 0.07333964109420776, + 0.2076173573732376, + -0.2722102701663971, + -0.03309676796197891, + 0.11944717913866043, + 0.08164827525615692, + 0.029720788821578026, + -0.043797485530376434, + 0.028479482978582382, + 0.020878860726952553, + 0.052128955721855164, + -0.20585131645202637, + 0.20815885066986084, + 0.09836098551750183, + 0.24171386659145355, + -0.008032371290028095, + -0.024199075996875763, + 0.20322568714618683, + 0.06355233490467072, + 0.049235470592975616, + 0.05883649364113808, + 0.16226248443126678, + -0.03820045664906502, + 0.05564669892191887, + -0.17151591181755066, + 0.16064175963401794, + -0.18818634748458862, + 0.16918310523033142, + 0.1387612223625183, + -0.38740164041519165, + -0.0034635458141565323, + -0.1112586036324501, + -0.09161406010389328, + 0.04657602682709694, + 0.13405965268611908, + 0.11244387924671173, + 0.17962154746055603, + 0.03205510601401329, + -0.1620417982339859, + -0.316724568605423, + -0.1300724297761917, + 0.004112797789275646, + -0.3679676949977875, + -0.8888188004493713, + -1.3260128498077393, + -0.03746601939201355, + 0.06930089741945267, + 0.1198146864771843, + -0.06911249458789825, + -0.027608202770352364, + -0.05782115459442139, + -0.07657481729984283, + 0.11506764590740204, + 0.3820888102054596, + 0.1321304887533188, + -0.42697519063949585, + -0.04237403720617294, + -0.034845177084207535, + 0.11277307569980621, + 0.056731946766376495, + 0.08227284252643585, + -0.18221744894981384, + 0.5221775770187378, + 0.16589905321598053, + -0.3070075213909149, + 0.034732233732938766, + 0.03331894800066948, + 0.1380075365304947, + 0.08150234818458557, + 0.12187470495700836, + 0.11502106487751007, + -0.16480767726898193, + -0.0747382789850235, + 0.05263378471136093, + 0.15973956882953644, + -0.11154681444168091, + 0.1031060442328453, + 0.14426474273204803, + 0.013075035065412521, + 0.06027095764875412 + ], + [ + -0.15520255267620087, + 0.0181125458329916, + -0.08428597450256348, + -0.1390082687139511, + -0.18263308703899384, + 0.09813286364078522, + -0.34793365001678467, + 0.10763484984636307, + -0.08782882988452911, + 0.17000305652618408, + 0.1635645478963852, + -0.08807937055826187, + 0.12180091440677643, + 0.07664904743432999, + 0.023484421893954277, + -0.14350254833698273, + -0.07822943478822708, + -0.031011106446385384, + 0.2000948041677475, + -0.7007007002830505, + 0.16365677118301392, + 0.17082816362380981, + -0.21023450791835785, + 0.15482839941978455, + -0.6186422109603882, + 0.09985583275556564, + 0.00916769914329052, + 0.16208937764167786, + 0.19459307193756104, + -0.20068523287773132, + -0.05844015255570412, + 0.07200222462415695, + -0.10007701069116592, + 0.08475957810878754, + -0.12023811787366867, + 0.06343929469585419, + -0.025667887181043625, + -0.0564231313765049, + 0.010185373947024345, + -0.07049667835235596, + -0.20722773671150208, + -0.11022617667913437, + -0.18156608939170837, + 0.09187473356723785, + 0.003936861641705036, + -0.09194739162921906, + -0.017594361677765846, + 0.17291833460330963, + 0.09563695639371872, + 0.1762615293264389, + -0.3952924609184265, + -0.10080292820930481, + -0.107294961810112, + 0.029298121109604836, + -0.0710691586136818, + 0.12434081733226776, + 0.013349381275475025, + -0.046077944338321686, + -0.043741609901189804, + 0.2880513370037079, + 0.0745154395699501, + -0.3252546489238739, + 0.12081035226583481, + -0.28100040555000305, + 0.1265290230512619, + -0.004578909836709499, + -0.01107937190681696, + -0.3420383930206299, + 0.04421592503786087, + -0.04566372558474541, + -0.023252414539456367, + 0.02066107839345932, + -0.09750576317310333, + -0.004938255995512009, + -0.327876478433609, + -0.25856858491897583, + 0.20891813933849335, + -0.08908101171255112, + 0.09061186760663986, + 0.07218626141548157, + 0.08094844967126846, + -0.011012701317667961, + -0.03055514022707939, + -0.15159012377262115, + 0.07817917317152023, + 0.031234128400683403, + -0.12829139828681946, + -0.16767331957817078, + -0.2714453935623169, + -0.25519248843193054, + -0.11575079709291458, + 0.15695978701114655, + 0.0018485760083422065, + 0.14046752452850342, + 0.01577463187277317, + 0.27033159136772156, + -0.0033939408604055643, + -0.3100588917732239, + 0.28145667910575867, + 0.005141402594745159, + -0.02461238205432892, + -0.09548435360193253, + 0.038965512067079544, + -0.03453989326953888, + 0.023588130250573158, + 0.012711389921605587, + -0.2543503940105438, + 0.062455352395772934, + -0.3897725045681, + -0.11031099408864975, + -0.08257236331701279, + 0.10927227139472961, + 0.42813047766685486, + -0.05718206986784935, + 0.0066705429926514626, + 0.18214565515518188, + -0.16828954219818115, + 0.21732397377490997, + -0.07088091224431992, + 0.06865840405225754, + -0.05611380562186241, + 0.15235593914985657, + -0.06764762103557587, + -0.13969795405864716, + -0.19600360095500946, + -0.17516988515853882, + 0.08721411228179932, + 0.12968914210796356 + ], + [ + -0.11238700151443481, + -0.12590478360652924, + -0.018640849739313126, + 0.056609101593494415, + -0.04449351504445076, + -0.0035858822520822287, + -0.04093122109770775, + 0.012282810173928738, + -0.02729523926973343, + -0.08766801655292511, + 0.27297723293304443, + 0.1819225251674652, + 0.011973485350608826, + 0.09232823550701141, + 0.0050602625124156475, + -0.06857647746801376, + -0.15286347270011902, + 0.05194134637713432, + -0.08123817294836044, + -0.022473309189081192, + 0.010499238036572933, + -0.001212665461935103, + -0.18625488877296448, + 0.1599319577217102, + 0.09731491655111313, + -0.14146697521209717, + -0.005108128301799297, + 0.01515810377895832, + 0.18046905100345612, + 0.155790776014328, + -0.22740906476974487, + -0.01863410882651806, + -0.5205853581428528, + 0.02772824838757515, + -0.03363064303994179, + -0.07392145693302155, + 0.09469647705554962, + 0.15424472093582153, + -0.03624097257852554, + -0.07461462169885635, + -0.09659659117460251, + -0.1971689909696579, + -0.056987594813108444, + 0.15038242936134338, + -0.31143951416015625, + -0.2500779628753662, + 0.29711928963661194, + -0.07175704091787338, + 0.1043776273727417, + -0.03851475194096565, + 0.07258779555559158, + -0.08160170912742615, + -0.04302779212594032, + -0.02723030187189579, + -0.11148824542760849, + 0.05932198464870453, + 0.1146557405591011, + 0.12016455829143524, + 0.1304599940776825, + 0.06771352142095566, + -0.0775962620973587, + 0.016576742753386497, + -0.05617036670446396, + -0.09299235045909882, + 0.11756674200296402, + -0.06574629247188568, + 0.1122073158621788, + -0.02751249447464943, + 0.0949266105890274, + 0.016215916723012924, + 0.011630916967988014, + 0.03165903314948082, + 0.029043007642030716, + -0.197708860039711, + -0.19848133623600006, + -0.09184287488460541, + 0.13407044112682343, + 0.12438161671161652, + -0.04411923512816429, + -0.04930974170565605, + -0.20349879562854767, + 0.123837411403656, + -0.021471315994858742, + 0.01303805410861969, + -0.09922424703836441, + 0.015586872585117817, + 0.2197849005460739, + 0.10328707098960876, + 0.0406968779861927, + 0.06670669466257095, + -0.039120446890592575, + 0.16096921265125275, + -0.34162184596061707, + 0.043180983513593674, + -0.1315820962190628, + -0.08134584873914719, + -0.09721112996339798, + 0.14486537873744965, + 0.12716516852378845, + 0.07350900769233704, + 0.05324964597821236, + -0.10450754314661026, + 0.0751427561044693, + 0.027063103392720222, + -0.013825553469359875, + -0.10479243099689484, + -0.2941891849040985, + -0.10762480646371841, + -0.19687223434448242, + -0.0655607283115387, + -0.1385330855846405, + 0.15017810463905334, + 0.1323767900466919, + -0.027664611116051674, + 0.09297416359186172, + -0.010130951181054115, + 0.06616318225860596, + -0.06307036429643631, + 0.1716156005859375, + 0.020379718393087387, + -0.04996771365404129, + -0.05517945811152458, + 0.025452762842178345, + -0.00799772422760725, + -0.246479794383049, + -0.16347448527812958, + -0.045373089611530304, + 0.10530093312263489 + ], + [ + 0.021212151274085045, + -0.014556045643985271, + 0.12308353185653687, + -0.008947973139584064, + -0.02102857455611229, + 0.029185673221945763, + -0.026273446157574654, + 0.09616921842098236, + -0.020904704928398132, + -0.046645063906908035, + 0.0313723087310791, + 0.07961895316839218, + 0.13139225542545319, + -0.036146145313978195, + -0.05767407268285751, + 0.11898039281368256, + -0.07241222262382507, + -0.3726726770401001, + -0.022222647443413734, + 0.10817413777112961, + -0.17358657717704773, + 0.006756107322871685, + -0.33816471695899963, + -0.06903818994760513, + 0.0958629921078682, + 0.03751816228032112, + -0.01229590643197298, + -0.003997483290731907, + 0.09592573344707489, + 0.08838314563035965, + 0.034253329038619995, + -0.005883978679776192, + -0.1767890602350235, + 0.08971678465604782, + 0.17069286108016968, + -0.1699218899011612, + -0.1577530801296234, + 0.15159261226654053, + 0.1092519536614418, + -0.0013490464771166444, + 0.13342469930648804, + -0.08054398745298386, + -0.22331413626670837, + 0.07005267590284348, + -0.05445452407002449, + -0.4209088385105133, + 0.16884927451610565, + 0.11056655645370483, + -0.03872447460889816, + -0.138294979929924, + -0.03449954465031624, + 0.16699829697608948, + 0.025344939902424812, + 0.08181089907884598, + -0.10200044512748718, + 0.08064776659011841, + 0.06253424286842346, + 0.00414982670918107, + -0.12361068278551102, + 0.18191449344158173, + -0.11839795112609863, + 0.17322653532028198, + -0.1238081306219101, + -0.052344612777233124, + 0.10128083825111389, + 0.027662431821227074, + 0.1374344527721405, + -0.1649051457643509, + 0.03373895213007927, + 0.026597490534186363, + 0.0714898332953453, + -0.10654684156179428, + -0.022828616201877594, + -0.08680666983127594, + -0.08026181906461716, + -0.007190864998847246, + -0.02074909582734108, + -0.07425564527511597, + 0.015315448865294456, + -0.22029536962509155, + 0.09127532690763474, + 0.17612291872501373, + -0.03754301741719246, + -0.16699016094207764, + -0.11774273216724396, + -0.08488398790359497, + 0.182804137468338, + 0.08531278371810913, + 0.11736488342285156, + -0.020065894350409508, + 0.2015722543001175, + 0.04994514212012291, + -1.4182758331298828, + 0.04682062938809395, + -0.20435501635074615, + 0.08111504465341568, + -0.3645493984222412, + -0.05393547564744949, + 0.11758356541395187, + -0.2482536882162094, + 0.04504050314426422, + -0.22125330567359924, + 0.02407822012901306, + 0.10014230757951736, + 0.015849582850933075, + -0.012852243147790432, + -0.12249977141618729, + 0.054227109998464584, + -0.05478619784116745, + -0.03176501765847206, + -0.02743583358824253, + 0.1056777760386467, + 0.04198061302304268, + -0.10728026926517487, + 0.1708507239818573, + 0.03860192745923996, + -0.008396417833864689, + -0.070994071662426, + -0.055344562977552414, + -0.03527451679110527, + -0.037409745156764984, + 0.14319907128810883, + 0.012748748064041138, + 0.16019997000694275, + -0.024607589468359947, + -0.14934349060058594, + -0.06920216977596283, + 0.12574462592601776 + ], + [ + 0.07014277577400208, + -0.0351623110473156, + -0.11218171566724777, + 0.11228878051042557, + 0.2416064590215683, + 0.07768618315458298, + 0.20139671862125397, + -0.056351859122514725, + -0.031683556735515594, + -0.0559673011302948, + -0.10940022021532059, + -0.1315704882144928, + 0.1717953085899353, + -0.09507451206445694, + 0.10520726442337036, + 0.055889930576086044, + -0.15574617683887482, + -0.11354203522205353, + -0.04941444844007492, + -0.13760824501514435, + 0.08856821060180664, + -0.026700805872678757, + -0.11374159157276154, + -0.0029887992423027754, + 0.09967483580112457, + 0.028182007372379303, + 0.05805085971951485, + -0.1415824592113495, + 0.014357483945786953, + -0.055257998406887054, + 0.12128321826457977, + -0.11484801024198532, + 0.14399008452892303, + -0.2751915156841278, + 2.6399542548460886e-05, + -0.12891985476016998, + 0.10406123846769333, + -0.0046610720455646515, + 0.09761025011539459, + -0.002737517934292555, + 0.1710057556629181, + 0.07467767596244812, + 0.19904911518096924, + -0.07420102506875992, + 0.1825695037841797, + 0.01794128119945526, + -0.1413174867630005, + -0.1304275542497635, + -0.09736531972885132, + 0.13590827584266663, + 0.004227517172694206, + -0.14674699306488037, + 0.18957200646400452, + 0.05690461024641991, + -0.03822953999042511, + -0.05274684354662895, + 0.12726889550685883, + 0.0888669490814209, + -0.09172869473695755, + -0.04757538437843323, + 0.13331128656864166, + 0.060887549072504044, + 0.12637916207313538, + -0.08851641416549683, + -0.23486490547657013, + 0.04241888225078583, + -0.16076141595840454, + 0.03931017592549324, + 0.11708633601665497, + 0.02282727137207985, + -0.15546077489852905, + 0.07051675766706467, + -0.13935674726963043, + -0.1752493679523468, + -0.01036369614303112, + 0.09951820224523544, + -0.10911285132169724, + -0.09764610230922699, + 0.033752597868442535, + -0.011054752394557, + 0.06407804787158966, + -0.05066635087132454, + -0.07907290011644363, + -0.029791949316859245, + 0.04015176370739937, + -0.12839564681053162, + -0.05495491251349449, + -0.29271063208580017, + 0.13553035259246826, + 0.19222354888916016, + -0.0782637819647789, + -0.06670359522104263, + 0.2584686279296875, + -0.03308732435107231, + 0.10523476451635361, + -0.019808489829301834, + 0.07774040102958679, + 0.14416004717350006, + -0.43290817737579346, + 0.06242425739765167, + 0.003355608321726322, + 0.02894199825823307, + -0.16063717007637024, + 0.007322956342250109, + -0.005649432074278593, + -0.128949835896492, + 0.07308026403188705, + 0.06317023187875748, + -0.04360933601856232, + 0.11624979227781296, + -0.03618346154689789, + 0.08947136253118515, + -0.47764158248901367, + 0.040414854884147644, + 0.04210762307047844, + 0.07939056307077408, + -0.0468658022582531, + -0.030375273898243904, + -0.1277189552783966, + -0.02766875922679901, + 0.06974193453788757, + 0.045762427151203156, + 0.05726540461182594, + -0.06064724177122116, + 0.16445636749267578, + -0.022550180554389954, + -0.08066385239362717, + -0.0916556641459465 + ], + [ + -0.12670359015464783, + 0.05853061005473137, + -0.06844493001699448, + 0.04335508495569229, + -0.15289808809757233, + 0.026114648208022118, + 0.049946464598178864, + 0.07867565751075745, + 0.018592430278658867, + -0.16809295117855072, + 0.04641557112336159, + -0.12483598291873932, + 0.2641320526599884, + -0.3055526912212372, + -0.42058998346328735, + 0.10521021485328674, + 0.2037898302078247, + -0.009504948742687702, + 0.07657673954963684, + 0.04024892300367355, + -0.3284511864185333, + 0.07537226378917694, + -0.008081602863967419, + 0.061946917325258255, + -0.0010998526122421026, + -0.16480925679206848, + 0.17128832638263702, + -0.3784794509410858, + 0.010231069289147854, + -0.04435530677437782, + 0.2619415521621704, + 0.09728822857141495, + -0.06740335375070572, + -0.03171616420149803, + -0.03549680858850479, + 0.020831892266869545, + -0.18521955609321594, + 0.11249279975891113, + 0.1642773300409317, + 0.028309153392910957, + -0.02181088551878929, + -0.11785566806793213, + -0.1720147430896759, + 0.02929362840950489, + -0.05137592926621437, + -0.10623881220817566, + 0.004034942947328091, + -0.005083553958684206, + 0.1392102688550949, + 0.12150391936302185, + 0.014211629517376423, + -0.09720737487077713, + 0.0589771494269371, + -0.15021109580993652, + 0.06645956635475159, + 0.09847874939441681, + 0.0659671202301979, + 0.12213972210884094, + -0.027439456433057785, + -0.016916945576667786, + -0.09185638278722763, + -0.18259376287460327, + 0.030444424599409103, + -0.002183769829571247, + 0.013662177138030529, + 0.1678646057844162, + 0.07505106925964355, + -0.15538452565670013, + 0.1030774861574173, + -0.06060400232672691, + 0.06704123318195343, + -0.010108391754329205, + 0.1497269868850708, + -0.23932306468486786, + -0.05756823718547821, + 0.060190871357917786, + 0.09712830185890198, + 0.012486563064157963, + -0.08697210252285004, + -0.13108906149864197, + -0.22837071120738983, + 0.07913801074028015, + -0.14489814639091492, + -0.022766700014472008, + -0.10219480842351913, + 0.021647289395332336, + 0.11857856065034866, + 0.08575023710727692, + -0.09612056612968445, + 0.17948158085346222, + 0.07525833696126938, + 0.030646294355392456, + -0.19634924829006195, + 0.06541768461465836, + -0.03852180019021034, + -0.1658252626657486, + 0.06698651611804962, + -0.048025552183389664, + 0.10644666105508804, + -0.14985734224319458, + -0.020324628800153732, + -0.25834840536117554, + -0.01859930157661438, + 0.06704728305339813, + 0.0965677872300148, + -0.20235134661197662, + -0.14699392020702362, + 0.24269802868366241, + -0.3027573227882385, + 0.15615732967853546, + 0.0658564418554306, + 0.22233732044696808, + 0.04463149234652519, + 0.05108906701207161, + -0.23091135919094086, + 0.3518728017807007, + 0.06779973208904266, + -0.008436222560703754, + 0.046186599880456924, + 0.09458363801240921, + -0.20586149394512177, + -0.12211515009403229, + 0.06288594007492065, + 0.20402872562408447, + -0.4218448996543884, + -0.03784264624118805, + -0.031425971537828445, + -0.3249821066856384 + ], + [ + -0.058300845324993134, + 0.20745012164115906, + 0.07265610992908478, + 0.026096975430846214, + 0.13887976109981537, + 0.0005489321192726493, + -0.26757189631462097, + -0.1347556710243225, + -0.23581361770629883, + 0.09280536323785782, + -0.27552688121795654, + -0.12768524885177612, + 0.026498083025217056, + -0.44579601287841797, + -0.2752133011817932, + 0.23625190556049347, + -0.033908162266016006, + -1.2662625312805176, + 0.014485793188214302, + 0.29547378420829773, + -0.6393572092056274, + -0.03196083754301071, + 0.17717158794403076, + -0.06059296429157257, + -0.07955514639616013, + 0.18489709496498108, + 0.09252215176820755, + 0.15390756726264954, + -0.1419997215270996, + -0.18443699181079865, + 0.14637082815170288, + 0.026171663776040077, + -0.5015670657157898, + -0.2884386479854584, + 0.1476019322872162, + 0.03005380742251873, + 0.2266792207956314, + 0.11030063778162003, + -0.07772094756364822, + -0.016127651557326317, + 0.011067626997828484, + -0.16511696577072144, + 0.15066871047019958, + -0.010925629176199436, + -0.09620647877454758, + -0.18288780748844147, + -0.06117785722017288, + 0.06310617923736572, + -0.5031008720397949, + 0.3473767936229706, + -0.5198538899421692, + 0.19490426778793335, + 0.15623517334461212, + 0.05985216051340103, + 0.13408464193344116, + 0.06788714975118637, + 0.1250510960817337, + -0.2061993032693863, + -1.4160492420196533, + -0.23088213801383972, + -0.06822527199983597, + -0.002661078004166484, + 0.029115067794919014, + 0.15134410560131073, + -0.03307243809103966, + 0.23254504799842834, + 0.3156445324420929, + -0.16247589886188507, + 0.18690630793571472, + 0.10854858160018921, + -0.16166380047798157, + 0.064368836581707, + -0.33288654685020447, + -0.44020524621009827, + 0.0728880912065506, + 0.07155076414346695, + -0.01470980979502201, + 0.06650067865848541, + -0.12366008013486862, + -0.0830800011754036, + -0.16207584738731384, + -0.4292878210544586, + -0.06562856584787369, + 0.10687728971242905, + 0.2684785723686218, + -0.21883408725261688, + -0.04105547443032265, + -0.1453772634267807, + 0.09407351166009903, + -0.33736559748649597, + 0.25463223457336426, + -0.1384527087211609, + -0.18398812413215637, + -0.22044654190540314, + -0.14390592277050018, + -0.09311831742525101, + 0.08781791478395462, + 0.2504681646823883, + -0.7596649527549744, + -0.40232813358306885, + -0.028742847964167595, + 0.12352072447538376, + 0.05515861511230469, + 0.017746057361364365, + -0.10040784627199173, + 0.2759139835834503, + -0.30584976077079773, + -0.07145065814256668, + -0.30956968665122986, + 0.1862582415342331, + -0.36936235427856445, + 0.16688980162143707, + 0.059454094618558884, + 0.049328893423080444, + -0.0049695889465510845, + 0.08939918130636215, + 0.19247804582118988, + 0.1553768515586853, + -0.051356520503759384, + 0.11763298511505127, + -0.10939280688762665, + -0.035860586911439896, + 0.23290087282657623, + -0.2812832295894623, + -0.0015875404933467507, + 0.8846412301063538, + 0.05173202231526375, + -0.02991451881825924 + ], + [ + -0.2833716571331024, + -0.01045883446931839, + 0.1311863660812378, + 0.023237314075231552, + 0.08414684236049652, + 0.07144024223089218, + 0.10651535540819168, + 0.3003581762313843, + -0.1277167797088623, + 0.042347341775894165, + 0.09138737618923187, + 0.13765038549900055, + 0.4605830907821655, + -0.15266557037830353, + -0.284836083650589, + -0.18694671988487244, + 0.025315390899777412, + -0.13807758688926697, + 0.00750333908945322, + -0.0328548327088356, + -0.3762173354625702, + -0.06670304387807846, + -0.192694753408432, + 0.044747982174158096, + 0.06927310675382614, + -0.3003024160861969, + 0.08969210088253021, + 0.08440666645765305, + -0.018170710653066635, + -0.17766065895557404, + -0.05617024749517441, + 0.16515156626701355, + -0.17289264500141144, + -0.023961931467056274, + -0.2240113765001297, + -0.12013834714889526, + 0.2723321318626404, + 0.08252869546413422, + 0.09141071140766144, + -0.10177671909332275, + -0.0703808143734932, + -0.0003880492295138538, + -0.12967807054519653, + 0.0641188770532608, + 0.006737728137522936, + -0.27119848132133484, + -0.028498778119683266, + 0.10969524830579758, + 0.04731299728155136, + -0.018849927932024002, + -0.2163204699754715, + 0.10685645788908005, + -0.04648631438612938, + -0.018303487449884415, + 0.08888842910528183, + 0.1771603375673294, + 0.18295419216156006, + -0.005444866139441729, + 0.055780455470085144, + 0.08179789036512375, + -0.20270608365535736, + 0.09772735089063644, + 0.03463514894247055, + -0.08333923667669296, + 0.007068773731589317, + 0.0810212716460228, + 0.14951300621032715, + -0.1398766189813614, + 0.015347322449088097, + 0.10847602784633636, + 0.06289854645729065, + -0.407133549451828, + -0.042247530072927475, + -0.3595666289329529, + -0.14160160720348358, + 0.11895439773797989, + 0.15333783626556396, + 0.08144699037075043, + -0.3348076641559601, + 0.07028499245643616, + -0.17219598591327667, + 0.05357527732849121, + -0.1851181834936142, + -0.10151620954275131, + 0.026331702247262, + 0.09880059957504272, + 0.12294364720582962, + 0.07895776629447937, + 0.013659468851983547, + 0.023486172780394554, + 0.07317490130662918, + 0.04738108441233635, + -0.8077264428138733, + 0.05772717297077179, + -0.1070374995470047, + -0.16474947333335876, + 0.040634673088788986, + 0.10228157788515091, + 0.11225728690624237, + -0.025256605818867683, + 0.1587221920490265, + -0.02539418824017048, + 0.05065034702420235, + 0.004962633363902569, + -0.06527451425790787, + 0.12095000594854355, + -0.2241382747888565, + -0.2271765172481537, + -0.20756956934928894, + 0.14052821695804596, + 0.029862336814403534, + -0.08544861525297165, + 0.1612582951784134, + -0.06768684089183807, + 0.003743815468624234, + 0.2268703728914261, + -0.06765150278806686, + -0.3303546905517578, + 0.3167669475078583, + -0.10948602855205536, + -0.19783207774162292, + -0.08800287544727325, + 0.07103893160820007, + 0.34151336550712585, + -0.19146503508090973, + -0.13352634012699127, + 0.059924013912677765, + 0.14139758050441742 + ], + [ + -0.38959962129592896, + -0.08303744345903397, + 0.013090858235955238, + -0.34505975246429443, + -0.010397303849458694, + 0.17019739747047424, + 0.007793452590703964, + 0.202500119805336, + -0.18432430922985077, + 0.022671403363347054, + 0.2562287151813507, + 0.06401509791612625, + 0.19012291729450226, + -0.693723201751709, + 0.09999910742044449, + -0.04594073444604874, + 0.029487164691090584, + -0.2489795833826065, + 0.09767896682024002, + 0.12935370206832886, + -0.14421936869621277, + 0.07965601980686188, + 0.039961256086826324, + -0.011313557624816895, + -0.03947460278868675, + 0.07326416671276093, + 0.32380208373069763, + 0.05146734043955803, + -0.2828916907310486, + 0.060360778123140335, + 0.06715979427099228, + 0.19839811325073242, + -0.449133962392807, + 0.10023324191570282, + -0.12748286128044128, + -0.801084578037262, + -0.37160494923591614, + -0.09470392018556595, + 0.18981191515922546, + 0.045743923634290695, + 0.0369405634701252, + -0.2093656212091446, + -0.07763554155826569, + 0.007479988969862461, + 0.1395556628704071, + -0.8160651326179504, + -0.4891366958618164, + -0.10827787965536118, + 0.08526217192411423, + 0.024748247116804123, + -0.348864883184433, + 0.10248037427663803, + -0.13364684581756592, + 0.0790579542517662, + 0.0881509780883789, + -0.012020205147564411, + 0.1387951821088791, + 0.11384648084640503, + -0.006189262494444847, + 0.11277022212743759, + -0.13678455352783203, + 0.06858977675437927, + -0.08986220508813858, + -0.052851635962724686, + 0.002981176134198904, + 0.21299627423286438, + 0.255474328994751, + -0.19084133207798004, + 0.03599700331687927, + 0.004479550290852785, + 0.1444961577653885, + -0.021434929221868515, + 0.06461448967456818, + -0.12569402158260345, + 0.005367428530007601, + 0.02244952693581581, + 0.26586517691612244, + -0.2926822006702423, + -0.1335180252790451, + -0.056272946298122406, + -0.3134584426879883, + -0.05337870866060257, + -0.2686101496219635, + 0.04500696808099747, + -0.20399774610996246, + -0.017533063888549805, + -0.026522209867835045, + 0.10714105516672134, + 0.10819302499294281, + 0.2323804348707199, + 0.05683912709355354, + 0.04857287555932999, + -1.1003899574279785, + 0.061866044998168945, + -0.23288027942180634, + -0.3471205234527588, + 0.015438929200172424, + -0.03819103538990021, + 0.17148616909980774, + -0.0747537612915039, + -0.14700700342655182, + -0.009657368063926697, + -0.00972847267985344, + 0.07264848053455353, + -0.13252469897270203, + -0.11538960039615631, + -0.5282462239265442, + -0.010666993446648121, + 0.04405824840068817, + 0.05710693076252937, + -0.11731170862913132, + 0.47661086916923523, + 0.07386558502912521, + -0.20288528501987457, + -0.09192929416894913, + 0.44514521956443787, + 0.014272564090788364, + -0.06648489832878113, + 0.10130656510591507, + 0.2070777863264084, + -0.38231736421585083, + -0.05244328826665878, + 0.020609194412827492, + 0.461796373128891, + 0.04598718136548996, + -0.06330186128616333, + 0.15325510501861572, + 0.27057313919067383 + ], + [ + -0.44828030467033386, + 0.048996977508068085, + -0.055692508816719055, + -0.021444624289870262, + 0.0136663056910038, + 0.01632648892700672, + 0.006340031512081623, + 0.2693220376968384, + 0.006907705217599869, + -0.04462500289082527, + 0.10185114294290543, + 0.08448948711156845, + 0.32572734355926514, + -0.467989444732666, + -0.05995290353894234, + -0.02337992563843727, + 0.10729463398456573, + -0.07564529031515121, + 0.31894317269325256, + 0.19312816858291626, + -0.4805547297000885, + 0.11524203419685364, + 0.05771439149975777, + -0.024202896282076836, + -0.10051058232784271, + 0.22599555552005768, + 0.19865544140338898, + -0.0668187290430069, + -0.37262454628944397, + 0.059289492666721344, + -0.006592836696654558, + 0.06093962863087654, + 0.003229717491194606, + -0.0788615494966507, + -0.13608485460281372, + -0.15801580250263214, + -0.08486194908618927, + -0.01817161776125431, + 0.256752073764801, + -0.03345233201980591, + -0.05510847643017769, + -0.06311394274234772, + -0.17559905350208282, + -0.04742007702589035, + -0.012616240419447422, + -0.1665707677602768, + -0.10086043179035187, + -0.10733810812234879, + 0.104757159948349, + 0.11616234481334686, + -0.4474644064903259, + 0.10261689871549606, + -0.09192220866680145, + 0.13350309431552887, + 0.023577097803354263, + 0.08805719763040543, + -0.023995034396648407, + 0.10118794441223145, + -0.0304582342505455, + 0.11441292613744736, + -0.053170111030340195, + -0.02209489420056343, + -0.15139377117156982, + 0.09921064972877502, + 0.0800919458270073, + 0.19017855823040009, + 0.12358066439628601, + 0.04576299712061882, + -0.02178935706615448, + -0.04926731809973717, + 0.035468053072690964, + 0.1750515252351761, + -0.008526111952960491, + -0.13073231279850006, + 0.014108633622527122, + 0.06785829365253448, + 0.15868376195430756, + -0.28627467155456543, + -0.06030423194169998, + -0.004290005192160606, + -0.2238890528678894, + -0.06331953406333923, + -0.08061075210571289, + 0.11651495844125748, + -0.11863988637924194, + -0.09556056559085846, + 0.047146234661340714, + 0.0061664944514632225, + -0.04765507951378822, + -0.048831261694431305, + 0.1393192857503891, + -0.08016173541545868, + -0.6002864241600037, + -0.04565341770648956, + -0.014506650157272816, + -0.22978918254375458, + 0.05320210009813309, + 0.03441409766674042, + 0.03812064230442047, + -0.04341857507824898, + -0.12483333051204681, + -0.3196130394935608, + -0.07220491021871567, + 0.12090760469436646, + -0.12244710326194763, + 0.008286695927381516, + -0.10953690856695175, + -0.11676423251628876, + -0.21673326194286346, + 0.1732134371995926, + 0.061833299696445465, + 0.3622860908508301, + 0.1278005689382553, + -0.09926170855760574, + -0.05644288286566734, + 0.3958446979522705, + 0.13134005665779114, + -0.010907700285315514, + -0.06852703541517258, + 0.21079760789871216, + -0.1440414935350418, + 0.08464924991130829, + 0.10947507619857788, + 0.23124960064888, + 0.035889919847249985, + 0.06840106099843979, + 0.0755625069141388, + 0.26267319917678833 + ], + [ + -0.11242909729480743, + 0.07777813822031021, + 0.017147891223430634, + 0.0762556940317154, + -0.13979555666446686, + -0.017134366557002068, + -0.013230796903371811, + 0.046539243310689926, + 0.09505430608987808, + -0.0414227657020092, + 0.011501535773277283, + 0.0006677822093479335, + 0.03078167326748371, + 0.03196495771408081, + -0.4448053240776062, + 0.05593688413500786, + 0.095420241355896, + -0.017177168279886246, + 0.20258627831935883, + -0.0011969000333920121, + -0.23747749626636505, + 0.10683197528123856, + -0.03323505446314812, + 0.031687263399362564, + 0.046661339700222015, + 0.054656755179166794, + 0.012940018437802792, + 0.0006602873909287155, + -0.00022531057766173035, + 0.012515143491327763, + -0.02224370650947094, + 0.0003869870270136744, + -0.22065208852291107, + 0.09468060731887817, + 0.027235044166445732, + -0.24194775521755219, + -0.006803445052355528, + 0.042202215641736984, + 0.12874245643615723, + -0.011152839288115501, + 0.048327911645174026, + -0.01924109272658825, + 0.05618466064333916, + 0.08392523974180222, + -0.010170466266572475, + -0.20001554489135742, + 0.031138386577367783, + -0.014936487190425396, + -0.009930361062288284, + 0.01906576007604599, + -0.022151919081807137, + -0.1364564746618271, + -0.029475219547748566, + 0.003457417944446206, + -0.0936758816242218, + 0.06645279377698898, + -0.08753884583711624, + 0.09784088283777237, + 0.05310910567641258, + 0.013989313505589962, + -0.05888087674975395, + -0.0767161175608635, + 0.01887543499469757, + 0.03624594211578369, + 0.015229374170303345, + 0.14909999072551727, + 0.1695866733789444, + 0.061133455485105515, + 0.061879713088274, + 0.08529990166425705, + 0.0601227805018425, + 0.017911726608872414, + 0.0371505506336689, + 0.02199270762503147, + 0.028400950133800507, + -0.03420627489686012, + 0.12792915105819702, + -0.023482924327254295, + -0.10907995700836182, + -0.14806093275547028, + 0.06563640385866165, + 0.01166056003421545, + -0.07720408588647842, + 0.044623225927352905, + -0.0638914406299591, + -0.08010472357273102, + 0.00018978943990077823, + 0.018169814720749855, + -0.06458085775375366, + 0.008147467859089375, + 0.1874166876077652, + 0.11788869649171829, + -0.14503361284732819, + -0.056928880512714386, + -0.051586370915174484, + 0.0311379786580801, + -0.029242807999253273, + -0.011479995213449001, + 0.008777651004493237, + 0.016140438616275787, + 0.07853540033102036, + -0.014216870069503784, + -0.06865290552377701, + -0.00562622444704175, + -0.06683144718408585, + 0.03967902064323425, + 0.0025456338189542294, + -0.19979006052017212, + -0.08635608851909637, + 0.019245268777012825, + -0.019028937444090843, + 0.05601559579372406, + 0.07524778693914413, + 0.004251585807651281, + -0.29718995094299316, + 0.05612385645508766, + -0.018215905874967575, + 0.04229329898953438, + -0.08520445972681046, + -0.027483901008963585, + -0.08607099205255508, + -0.07842132449150085, + 0.06527925282716751, + 0.07952331751585007, + -0.07018627226352692, + 0.023047439754009247, + 0.010121683590114117, + 0.06487199664115906 + ], + [ + -0.06850838661193848, + 0.03820861876010895, + -0.04997118189930916, + -0.0558176226913929, + -0.13766519725322723, + -0.11654634028673172, + -0.1751621812582016, + 0.06240612640976906, + -0.029796531423926353, + -0.042663898319005966, + 0.057892680168151855, + -0.06606350839138031, + 0.36279329657554626, + -0.2325775921344757, + -0.43989458680152893, + -0.006151264533400536, + 0.15289334952831268, + -0.03182404488325119, + 0.21477963030338287, + 0.2055479735136032, + -0.12358276546001434, + -0.06091983988881111, + 0.10520388185977936, + -0.04231717064976692, + -0.03316401690244675, + -0.20634320378303528, + 0.1947881430387497, + -0.19119331240653992, + 0.00224330578930676, + 0.1105017215013504, + 0.25372496247291565, + 0.16088958084583282, + -0.12618398666381836, + 0.30473464727401733, + -0.05637894570827484, + -0.15415571630001068, + -0.17848704755306244, + 0.02226218394935131, + 0.3213121294975281, + 0.005376210901886225, + 0.064080610871315, + -0.10318352282047272, + 0.0498216412961483, + 0.06875601410865784, + 0.0332530215382576, + -0.40952959656715393, + -0.2061605155467987, + -0.0011906459694728255, + 0.1559947431087494, + 0.1803823560476303, + 0.03039652481675148, + -0.1341896504163742, + -0.10690904408693314, + 0.016638850793242455, + 0.014905170537531376, + 0.1646748036146164, + 0.04869476333260536, + 0.011145325377583504, + -0.05761881545186043, + 0.06827794015407562, + 0.07074949145317078, + 0.1155596524477005, + -0.02054426446557045, + 0.06492248922586441, + 0.026034777984023094, + 0.05220374837517738, + 0.23937484622001648, + -0.08600922673940659, + -0.10282047837972641, + 0.05017709732055664, + 0.03027898073196411, + -0.21838518977165222, + 0.03674689307808876, + -0.20900288224220276, + 0.07955801486968994, + 0.010319070890545845, + 0.0006021950975991786, + -0.16838690638542175, + -0.19675730168819427, + -0.020879825577139854, + -0.14897336065769196, + -0.11284782737493515, + -0.034122489392757416, + 0.14978983998298645, + -0.1882222294807434, + -0.02910163812339306, + 0.07628294825553894, + -0.12354149669408798, + -0.20605646073818207, + -0.067026287317276, + 0.03651810064911842, + -0.04009218513965607, + -0.4336915910243988, + -0.12370501458644867, + -0.1894737333059311, + -0.1067979484796524, + 0.0005106063326820731, + 0.02266286499798298, + 0.15023422241210938, + 0.07491898536682129, + 0.1738005131483078, + -0.011356878094375134, + -0.07745537161827087, + 0.0018616914749145508, + -0.03903433680534363, + -0.14344757795333862, + -0.25354230403900146, + -0.3587690591812134, + -0.16760607063770294, + 0.07490547746419907, + -0.027816174551844597, + 0.2804313898086548, + 0.13427774608135223, + -0.08475857973098755, + -0.13692010939121246, + 0.0434500016272068, + 0.15848766267299652, + 0.06419291347265244, + 0.13726398348808289, + 0.20845246315002441, + -0.1566987782716751, + -0.13126084208488464, + 0.1149948462843895, + 0.22904621064662933, + -0.5067880153656006, + -0.07808747887611389, + 0.12406840175390244, + -0.10160921514034271 + ] + ], + [ + [ + -0.06756807118654251, + -0.16543987393379211, + -0.3955898582935333, + 0.23137737810611725, + 0.8105812072753906, + -0.7038881182670593, + 0.4241858720779419, + -0.1069340705871582, + 0.00033008589525707066, + -0.000287123752059415, + 0.21891747415065765, + -0.2620095908641815, + 0.21122407913208008, + -0.2604796290397644, + -0.20162317156791687, + 0.33764249086380005, + 0.027461672201752663, + 1.1470284461975098, + -0.6689460873603821, + 0.0030311518348753452, + 0.0007200560066848993, + 0.5650426149368286, + -1.4484511613845825, + 0.7349910736083984, + 0.13127289712429047, + -0.0971437618136406, + -0.20855778455734253, + 1.0889016389846802, + 0.29467228055000305, + 0.02700752764940262, + 0.06916867941617966, + 1.2414883375167847, + -0.2856679856777191, + -0.25423693656921387, + 0.36631569266319275, + -0.3396744132041931, + 0.194773331284523, + 0.005377310793846846, + -0.6050127744674683, + -0.08947154134511948, + -0.6805055141448975, + 0.3207355737686157, + 0.32702335715293884, + -1.0606392621994019, + -1.2447706460952759, + -2.5441553592681885, + 0.3214056193828583, + -0.8217073082923889, + -0.04984506592154503, + 0.048520974814891815, + -0.7850474715232849, + -1.295426368713379, + 0.2141086608171463, + -0.8262404799461365, + -0.12335185706615448, + -0.3200710713863373, + 0.2956101894378662, + 0.006402024067938328, + 0.48091503977775574, + -0.13661351799964905, + 0.5072853565216064, + -1.0244522094726562, + -0.5020338892936707, + -0.34595540165901184, + -0.7613320350646973, + -0.2577553391456604, + 0.5887584090232849, + -0.17392855882644653, + -1.6277570724487305, + 0.026200657710433006, + 0.12160252779722214, + 1.3254919052124023, + 0.13432152569293976, + 0.0013512569712474942, + 0.2175121009349823, + 0.14757132530212402, + 0.22288161516189575, + -0.08641904592514038, + -0.9761590361595154, + -0.22108010947704315, + 0.28592702746391296, + 1.2467024326324463, + -0.5349047780036926, + 0.09560094028711319, + 0.4562901258468628, + -0.5896481275558472, + -0.3397466242313385, + -0.34773266315460205, + 0.027619490399956703, + -0.005026989616453648, + 0.007132960017770529, + 0.13221536576747894, + 0.1813429743051529, + 0.6329764127731323, + -0.6600300073623657, + 0.36737582087516785, + 0.605143666267395, + 0.08125394582748413, + -0.2494572252035141, + -1.0565824508666992, + 0.015786949545145035, + -0.33262452483177185, + -0.7486162185668945, + -0.8038054704666138, + -1.5733354091644287, + -1.061435341835022, + -0.24699997901916504, + 0.28481364250183105, + 0.2269888073205948, + -0.8981099128723145, + -2.031986713409424, + 0.11067937314510345, + -0.42052552103996277, + -0.018095210194587708, + 0.1407410204410553, + -0.7161560654640198, + -0.3761627972126007, + -0.5308826565742493, + 0.3465748429298401, + 0.4759661853313446, + -0.2290668934583664, + 0.5694806575775146, + -1.6017088890075684, + 0.8640625476837158, + 1.2884881496429443, + 1.053236961364746, + 0.0726151391863823, + 0.6022239923477173 + ] + ] + ], + "activationLayers": [ + "reLU", + "reLU", + "reLU" + ], + "biases": [ + [ + 0.41210898756980896, + -0.14466501772403717, + 0.11615071445703506, + 0.15551765263080597, + 0.2896631360054016, + 0.2601502537727356, + -0.09201329946517944, + 0.12317017465829849, + 0.2754036784172058, + 0.1393490433692932, + 0.0106710996478796, + 0.3008906841278076, + -0.3705286979675293, + 0.13894571363925934, + -0.24349340796470642, + 0.11219232529401779, + -0.3475782573223114, + -0.3216690719127655, + -0.4586440920829773, + -0.35091671347618103, + -0.3820383548736572, + 0.23171252012252808, + -0.5182207822799683, + 0.2747291624546051, + 0.10045353323221207, + 0.007530077360570431, + -0.07444372773170471, + 0.12993216514587402, + 0.2992708384990692, + 0.2836220860481262, + -0.24976615607738495, + -0.09116362035274506, + -0.05714938044548035, + -0.2559919059276581, + 0.17496241629123688, + -0.07897314429283142, + -0.012259084731340408, + 0.2332637906074524, + 0.06723882257938385, + 0.05602554231882095, + -0.008035704493522644, + 0.06503932178020477, + -0.1942092925310135, + 0.16313612461090088, + -0.20086807012557983, + -0.4011442959308624, + 0.19558599591255188, + 0.11690598726272583, + 0.08897747099399567, + -0.1906489133834839, + 0.09074617922306061, + -0.022266032174229622, + 0.08257032930850983, + -0.4201367497444153, + 0.025272872298955917, + 0.16209301352500916, + 0.1653354912996292, + 0.06447666138410568, + -0.24351462721824646, + -0.25724470615386963, + -0.1045750230550766, + -0.05668416619300842, + 0.06308846175670624, + -0.0446014478802681, + 0.08223901689052582, + -0.17683956027030945, + -0.049475718289613724, + -0.3542690575122833, + 0.12083445489406586, + -0.08175133913755417, + 0.12638680636882782, + 0.04070816561579704, + 0.15121451020240784, + -0.17749427258968353, + -0.05650431290268898, + 0.089194156229496, + 0.24117672443389893, + -0.03255736455321312, + 0.3707243800163269, + -0.14949753880500793, + 0.2443474531173706, + 0.20650245249271393, + -0.04992556571960449, + -0.3056444823741913, + -0.03732169046998024, + 0.23476454615592957, + 0.3409971296787262, + -0.15662559866905212, + 0.023329952731728554, + 0.29453355073928833, + -0.46265003085136414, + 0.029613692313432693, + -0.6571834087371826, + 0.1144028827548027, + -0.20819827914237976, + -0.03867660462856293, + -0.037245795130729675, + 0.16308486461639404, + -0.36614203453063965, + -0.2943962514400482, + 0.334348201751709, + -0.150777205824852, + 0.2239372432231903, + 0.008847127668559551, + 0.11820665746927261, + 0.09000764787197113, + -0.12177037447690964, + -0.34207475185394287, + 0.07563664019107819, + 0.012970546260476112, + -0.3776833713054657, + -0.008794604800641537, + -0.16827081143856049, + 0.2858964204788208, + -0.019382338970899582, + -0.22749945521354675, + -0.25380027294158936, + 0.029560454189777374, + 0.224834144115448, + 0.04995596036314964, + 0.2540011703968048, + 0.057912442833185196, + 0.0915704295039177, + -0.3275631070137024, + -0.3664524555206299, + -0.04216212034225464, + -0.052519362419843674, + -0.3615948259830475 + ], + [ + 0.047699615359306335, + 0.08498521149158478, + 0.11366501450538635, + 0.20011846721172333, + 0.13601696491241455, + 0.044375352561473846, + 0.10027670860290527, + 0.042436566203832626, + -2.1180344804416773e-13, + -0.0035322783514857292, + 0.08897602558135986, + 0.05478644743561745, + 0.01309268083423376, + -0.031728990375995636, + -0.011171314865350723, + -0.11098471283912659, + 0.02556885965168476, + 0.08012890070676804, + 0.06106296181678772, + 0.007653011474758387, + -0.008623088710010052, + -0.022251330316066742, + 0.02949560061097145, + -0.09171757847070694, + 0.05878021568059921, + 0.0700177252292633, + -0.07448582351207733, + 0.026788588613271713, + -0.1554175615310669, + -1.267052191248827e-13, + 0.06707378476858139, + -0.21733832359313965, + 0.09259239584207535, + 0.04375907778739929, + -0.043065495789051056, + 0.08185916393995285, + -0.06715206056833267, + 0.021116917952895164, + 0.022028082981705666, + 0.01577305793762207, + 0.09392517060041428, + -0.02431216463446617, + 0.015894291922450066, + -0.0474163256585598, + 0.14496028423309326, + 0.027936071157455444, + -0.020667744800448418, + -0.13828492164611816, + 0.011518381536006927, + 0.06043945997953415, + 0.005468614865094423, + 0.11960017681121826, + 0.015274234116077423, + -0.07039181143045425, + 0.07687773555517197, + 0.15402936935424805, + -0.003556318348273635, + 0.027931367978453636, + -0.10420271754264832, + 0.02732524462044239, + 0.06901879608631134, + 0.05905807390809059, + 0.10269398987293243, + -0.11711360514163971, + 0.0024184132926166058, + 0.06942161917686462, + -0.012103397399187088, + -0.045856524258852005, + 0.02954353578388691, + -0.055997584015131, + 0.07168228924274445, + -0.08835671842098236, + 0.0692959725856781, + -0.0004929265123791993, + 0.08187314867973328, + -0.03290141746401787, + 0.04021641984581947, + 0.009694978594779968, + 0.09465997666120529, + -0.06636419892311096, + 0.03090566210448742, + -0.01891789212822914, + 0.09943457692861557, + -0.07068777084350586, + 0.04829065129160881, + 0.10806778073310852, + 0.14855991303920746, + -0.017830660566687584, + 0.0849478542804718, + -0.02673247642815113, + 0.051755715161561966, + 0.09093113243579865, + 0.07427509874105453, + -0.11021300405263901, + 0.08712634444236755, + -0.03183803707361221, + -0.08997230231761932, + 0.06885886192321777, + -0.006107353139668703, + 0.14184601604938507, + -0.02509114518761635, + -0.08376593887805939, + 0.058691930025815964, + 0.024715105071663857, + 0.002094974974170327, + -0.03127538412809372, + 0.024943839758634567, + 0.14382454752922058, + 0.14642073214054108, + -0.010911544784903526, + 0.20058637857437134, + 0.0668783187866211, + 0.049609556794166565, + 0.09508326649665833, + 0.0025026528164744377, + 0.017473747953772545, + 0.017592759802937508, + 0.052776649594306946, + 0.06248925253748894, + 0.06398999691009521, + 0.09754019230604172, + -0.12873417139053345, + 0.003965021576732397, + -0.025616483762860298, + -0.1462949514389038, + -0.34913501143455505, + 0.05132580175995827, + -0.06187589839100838 + ], + [ + 0.026853766292333603 + ] + ] +} \ No newline at end of file diff --git a/models/0/scaler.txt b/models/0/scaler.txt new file mode 100644 index 0000000000..461a1d8f8c --- /dev/null +++ b/models/0/scaler.txt @@ -0,0 +1,2 @@ +2.6720630059068036,1.0017657905428634,1.9156327155670845,9.149623402193956,28.647771666093696,3.7526096196518424,10.19303372191143,1.82985123605338,3.1600228146388725,1.5738584867331313,1.435431446698128,1.2643060286901897,1.1452401787667594 +1.858559757027421,0.4865249978344985,3.162572904879665,25.21040017953888,34.48252849402906,5.320336536404879,25.362484286622546,3.722723690419032,10.46575141988185,2.489423223957629,1.8889113541845977,1.0913557664615596,0.6525565126200781 diff --git a/monitoring/MonitoringSettings.md b/monitoring/MonitoringSettings.md new file mode 100644 index 0000000000..0a9c05e37e --- /dev/null +++ b/monitoring/MonitoringSettings.md @@ -0,0 +1,24 @@ +# Monitoring Settings + +## Configuration files + +There are monitoring configuration files for each project in `project//monitoring.properties`. + +### Monitoring configure +The file `monitoring.properties` is passed as a java property `-Dutbot.monitoring.settings.path`. It configures `org.utbot.monitoring.MonitoringSettings` class. + +#### Properties description: +- `project` is a name of project that will be run in monitoring. +- `classTimeoutSeconds` is a unit-test generation timeout for one class. +- `runTimeoutMinutes` is a timeout for one whole run of the project. +- `fuzzingRatios` is a list of numbers that configure the ratio of fuzzing time to total generation time. + +## Which project can be run? + +### Prerequisites + +Firstly, you should read [this](../utbot-junit-contest/README.md) paper about available projects and how to extend them. + +### How to add projects to monitoring + +To add a project to monitoring you should create a folder with a project name and create a file `monitoring.properties` with needed configurations. diff --git a/monitoring/insert_metadata.py b/monitoring/insert_metadata.py new file mode 100644 index 0000000000..10be3c4b45 --- /dev/null +++ b/monitoring/insert_metadata.py @@ -0,0 +1,281 @@ +import argparse +import json +import re +import subprocess +from collections import OrderedDict +from datetime import datetime +from os import environ +from platform import uname +from time import time +from typing import Optional, List + +from monitoring_settings import JSON_VERSION +from utils import load + + +def try_get_output(args: str) -> Optional[str]: + """ + Try to run subprocess with specified arguments + :param args: arguments for execution + :return: result output of execution or None + """ + try: + return subprocess.check_output(args, stderr=subprocess.STDOUT, shell=True).decode() + except Exception as e: + print(f'Error in command "{args}":\n\t{e}') + return None + + +def parse_gradle_version(s: str) -> Optional[str]: + """ + Parse gradle version from given string + :param s: execution result of gradle --version + :return: parsed gradle version or None + """ + if s is None: + return None + regex = re.compile(r'^\s*(Gradle [.\d]+)\s*$', re.MULTILINE) + result = regex.search(s) + if result is None: + return None + return result.group(1) + + +def build_environment_data() -> dict: + """ + Collect environment data from host + :return: dictionary with environment data + """ + uname_result = uname() + environment = { + 'host': uname_result.node, + 'OS': f'{uname_result.system} version {uname_result.version}', + 'java_version': try_get_output('java -version'), + 'gradle_version': parse_gradle_version(try_get_output('gradle --version')), + 'JAVA_HOME': environ.get('JAVA_HOME'), + 'KOTLIN_HOME': environ.get('KOTLIN_HOME'), + 'PATH': environ.get('PATH'), + } + return environment + + +def build_metadata(args: argparse.Namespace) -> dict: + """ + Collect metadata into dictionary + :param args: parsed program arguments + :return: dictionary with metadata + """ + metadata = { + 'source': { + 'type': args.source_type, + 'id': args.source_id + }, + 'commit_hash': args.commit, + 'branch': args.branch, + 'build_number': args.build, + 'timestamp': args.timestamp, + 'date': datetime.fromtimestamp(args.timestamp).strftime('%Y-%m-%dT%H:%M:%S'), + 'environment': build_environment_data() + } + return metadata + + +def build_target(target_name: str) -> dict: + return { + "target": target_name, + "summarised": [], + "by_class": OrderedDict() + } + + +def transform_metrics(metrics: dict) -> dict: + """ + Transform given metrics with calculation coverage + :param metrics: given metrics + :return: transformed metrics + """ + result = OrderedDict() + + instr_count_prefix = "covered_bytecode_instructions" + total_instr_count_prefix = "total_bytecode_instructions" + + coverage_prefix = "total_bytecode_instruction_coverage" + + total_count = 0 + for metric in metrics: + if metric.startswith(total_instr_count_prefix): + total_count = metrics[metric] + break + + for metric in metrics: + if metric.startswith(total_instr_count_prefix): + continue + if metric.startswith(instr_count_prefix): + coverage = metrics[metric] / total_count if total_count > 0 else 0.0 + result[coverage_prefix + metric.removeprefix(instr_count_prefix)] = coverage + else: + result[metric] = metrics[metric] + + return result + + +def build_data(parameters: dict, metrics: dict) -> dict: + return { + "parameters": { + **parameters + }, + "metrics": { + **transform_metrics(metrics) + } + } + + +def build_by_class(class_name: str) -> dict: + return { + "class_name": class_name, + "data": [] + } + + +def update_from_class(by_class: dict, class_item: dict, parameters: dict): + """ + Update class object using given class_item + :param by_class: dictionary with classname keys + :param class_item: class metrics of current run + :param parameters: parameters of current run + """ + class_name = class_item["class_name"] + if class_name not in by_class: + by_class[class_name] = build_by_class(class_name) + + metrics = class_item["metrics"] + by_class[class_name]["data"].append( + build_data(parameters, metrics) + ) + + +def update_from_target(targets: dict, target_item: dict, parameters: dict): + """ + Update targets using given target_item + :param targets: dictionary with target keys + :param target_item: metrics of current run + :param parameters: parameters of current run + """ + target_name = target_item["target"] + if target_name not in targets: + targets[target_name] = build_target(target_name) + + summarised_metrics = target_item["summarised_metrics"] + targets[target_name]["summarised"].append( + build_data(parameters, summarised_metrics) + ) + + for class_item in target_item["metrics_by_class"]: + update_from_class(targets[target_name]["by_class"], class_item, parameters) + + +def update_from_stats(targets: dict, stats: dict): + """ + Updates targets using given statistics + :param targets: dictionary with target keys + :param stats: target object + """ + parameters = stats["parameters"] + for target_item in stats["targets"]: + update_from_target(targets, target_item, parameters) + + +def postprocess_by_class(by_class: dict) -> List[dict]: + """ + Transform dictionary with classname keys into array with class objects + :param by_class: dictionary with classname keys + :return: array of class objects + """ + return list(by_class.values()) + + +def postprocess_targets(targets: dict) -> List[dict]: + """ + Transform dictionary with target keys into array with target objects + :param targets: dictionary with target keys + :return: array of targets + """ + result = [] + for target in targets.values(): + target["by_class"] = postprocess_by_class(target["by_class"]) + result.append(target) + return result + + +def build_targets(stats_array: List[dict]) -> List[dict]: + """ + Collect and group statistics by target + :param stats_array: list of dictionaries with parameters and metrics + :return: list of metrics and parameters grouped by target + """ + result = OrderedDict() + for stats in stats_array: + update_from_stats(result, stats) + + return postprocess_targets(result) + + +def insert_metadata(args: argparse.Namespace) -> dict: + """ + Collect metadata and statistics from specified files and merge them into result + :param args: parsed program arguments + :return: dictionary with statistics and metadata + """ + stats_array = [item for f in args.stats_file for item in load(f)] + result = { + 'version': JSON_VERSION, + 'targets': build_targets(stats_array), + 'metadata': build_metadata(args) + } + return result + + +def get_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + '--stats_file', required=True, nargs='+', + help='files (one or more) with statistics', type=str + ) + parser.add_argument( + '--commit', help='commit hash', type=str + ) + parser.add_argument( + '--build', help='build number', type=str + ) + parser.add_argument( + '--output_file', required=True, + help='output file', type=str + ) + parser.add_argument( + '--timestamp', help='statistics timestamp', + type=int, default=int(time()) + ) + parser.add_argument( + '--source_type', help='source type of metadata', + type=str, default="Manual" + ) + parser.add_argument( + '--source_id', help='source id of metadata', type=str + ) + parser.add_argument( + '--branch', help='branch name', type=str + ) + + args = parser.parse_args() + return args + + +def main(): + args = get_args() + stats = insert_metadata(args) + with open(args.output_file, "w") as f: + json.dump(stats, f, indent=4) + + +if __name__ == "__main__": + main() diff --git a/monitoring/monitoring_settings.py b/monitoring/monitoring_settings.py new file mode 100644 index 0000000000..8b0fb83a0f --- /dev/null +++ b/monitoring/monitoring_settings.py @@ -0,0 +1,4 @@ +""" +Json format version. +""" +JSON_VERSION = 2 diff --git a/monitoring/prepare_metrics.py b/monitoring/prepare_metrics.py new file mode 100644 index 0000000000..59da56fca0 --- /dev/null +++ b/monitoring/prepare_metrics.py @@ -0,0 +1,150 @@ +import argparse +import json +from typing import List + +from utils import load + + +def remove_in_class(name: str) -> str: + in_class = "_in_class" + idx = name.find(in_class) + if idx == -1: + return name + return name[:idx] + name[idx:].removeprefix(in_class) + + +def update_from_counter_name(key_word: str, name: str, labels: dict) -> str: + if name == f"total_{key_word}": + labels["type"] = "total" + return key_word + if name.startswith(key_word): + labels["type"] = name.removeprefix(f"{key_word}_") + return key_word + return name + + +def update_from_coverage(name: str, labels: dict) -> str: + coverage_key = "bytecode_instruction_coverage" + idx = name.find(coverage_key) + if idx == -1: + return name + labels["type"] = name[:idx - 1] + source = name[idx:].removeprefix(f"{coverage_key}") + if len(source) > 0: + source = source.removeprefix("_by_") + if source == "classes": + labels["type"] = "averaged_by_classes" + else: + labels["source"] = source + if "source" not in labels: + labels["source"] = "all" + return coverage_key + + +def build_metric_struct(name: str, value: any, labels: dict) -> dict: + name = remove_in_class(name) + name = update_from_counter_name("classes", name, labels) + name = update_from_counter_name("methods", name, labels) + name = update_from_coverage(name, labels) + + if type(value) == bool: + value = int(value) + name = f"test_generation_{name}" + elif type(value) == int: + name = f"{name}_total" + + name = f"utbot_{name}" + + return { + "metric": name, + "labels": labels, + "value": value + } + + +def build_metrics_from_data(data: dict, labels: dict) -> List[dict]: + result = [] + fuzzing_ratio = data["parameters"]["fuzzing_ratio"] + new_labels = { + **labels, + "fuzzing_ratio": fuzzing_ratio + } + metrics = data["metrics"] + for metric in metrics: + result.append(build_metric_struct(metric, metrics[metric], new_labels.copy())) + return result + + +def build_metrics_from_data_array(metrics: List[dict], labels: dict) -> List[dict]: + result = [] + for metric in metrics: + result.extend(build_metrics_from_data(metric, labels)) + return result + + +def build_metrics_from_target(target: dict, run_id: str) -> List[dict]: + result = [] + project = target["target"] + + result.extend(build_metrics_from_data_array( + target["summarised"], + { + "run_id": run_id, + "project": project, + "class": "All" + } + )) + + for class_item in target["by_class"]: + class_name = class_item["class_name"] + result.extend(build_metrics_from_data_array( + class_item["data"], + { + "run_id": run_id, + "project": project, + "class": class_name + } + )) + + return result + + +def build_metrics_from_targets(targets: List[dict], run_id: str) -> List[dict]: + metrics = [] + for target in targets: + metrics.extend(build_metrics_from_target(target, run_id)) + return metrics + + +def get_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + '--stats_file', required=True, + help='files with statistics after insertion metadata', type=str + ) + parser.add_argument( + '--output_file', required=True, + help='output file', type=str + ) + + args = parser.parse_args() + return args + + +def extract_run_id(text: str): + idx = text.find('-') + return "run" + text[idx:] + + +def main(): + args = get_args() + stats = load(args.stats_file) + run_id = extract_run_id(stats["metadata"]["source"]["id"]) + metrics = build_metrics_from_targets(stats["targets"], run_id) + metrics.sort(key=lambda x: x["metric"]) + with open(args.output_file, "w") as f: + json.dump(metrics, f, indent=4) + + +if __name__ == "__main__": + main() diff --git a/monitoring/projects/fescar/monitoring.properties b/monitoring/projects/fescar/monitoring.properties new file mode 100644 index 0000000000..cabd34e51e --- /dev/null +++ b/monitoring/projects/fescar/monitoring.properties @@ -0,0 +1,4 @@ +project=fescar +classTimeoutSeconds=20 +runTimeoutMinutes=20 +fuzzingRatios=0.0;0.05;1.0 \ No newline at end of file diff --git a/monitoring/projects/guava/monitoring.properties b/monitoring/projects/guava/monitoring.properties new file mode 100644 index 0000000000..0555a8c44a --- /dev/null +++ b/monitoring/projects/guava/monitoring.properties @@ -0,0 +1,4 @@ +project=guava +classTimeoutSeconds=20 +runTimeoutMinutes=20 +fuzzingRatios=0.0;0.05;1.0 \ No newline at end of file diff --git a/monitoring/projects/pdfbox/monitoring.properties b/monitoring/projects/pdfbox/monitoring.properties new file mode 100644 index 0000000000..3878e706fe --- /dev/null +++ b/monitoring/projects/pdfbox/monitoring.properties @@ -0,0 +1,4 @@ +project=pdfbox +classTimeoutSeconds=20 +runTimeoutMinutes=20 +fuzzingRatios=0.0;0.05;1.0 \ No newline at end of file diff --git a/monitoring/projects/seata/monitoring.properties b/monitoring/projects/seata/monitoring.properties new file mode 100644 index 0000000000..9cb7421c9a --- /dev/null +++ b/monitoring/projects/seata/monitoring.properties @@ -0,0 +1,4 @@ +project=seata +classTimeoutSeconds=20 +runTimeoutMinutes=20 +fuzzingRatios=0.0;0.05;1.0 \ No newline at end of file diff --git a/monitoring/projects/spoon/monitoring.properties b/monitoring/projects/spoon/monitoring.properties new file mode 100644 index 0000000000..41b56c94f0 --- /dev/null +++ b/monitoring/projects/spoon/monitoring.properties @@ -0,0 +1,4 @@ +project=spoon +classTimeoutSeconds=20 +runTimeoutMinutes=20 +fuzzingRatios=0.0;0.05;1.0 \ No newline at end of file diff --git a/monitoring/push_with_rebase.sh b/monitoring/push_with_rebase.sh new file mode 100644 index 0000000000..655b09cb9c --- /dev/null +++ b/monitoring/push_with_rebase.sh @@ -0,0 +1,32 @@ +#!/bin/sh +set -e + +# inputs: target_branch, target_directory, github_token, message + +AUTHOR_EMAIL='github-actions[bot]@users.noreply.github.com' +AUTHOR_NAME='github-actions[bot]' +INPUT_BRANCH=${target_branch:-GITHUB_REF_NAME} +INPUT_DIRECTORY=${target_directory:-'.'} +REPOSITORY=$GITHUB_REPOSITORY + +echo "Push to branch $INPUT_BRANCH"; +[ -z "${github_token}" ] && { + echo 'Missing input "github_token: ${{ secrets.GITHUB_TOKEN }}".'; + exit 1; +}; + +cd "${INPUT_DIRECTORY}" + +remote_repo="https://${GITHUB_ACTOR}:${github_token}@github.com/${REPOSITORY}.git" + +git config http.sslVerify false +git config --local user.email "${AUTHOR_EMAIL}" +git config --local user.name "${AUTHOR_NAME}" + +git add -A +git commit -m "${message}" + +until git push "${remote_repo}" HEAD:"${INPUT_BRANCH}" +do + git pull --rebase || exit 1 +done diff --git a/monitoring/utils.py b/monitoring/utils.py new file mode 100644 index 0000000000..c897f46df6 --- /dev/null +++ b/monitoring/utils.py @@ -0,0 +1,15 @@ +import json +from os.path import exists +from typing import Optional + + +def load(json_file: str) -> Optional[any]: + """ + Try load object from json file + :param json_file: path to json file + :return: object from given json file or None + """ + if exists(json_file): + with open(json_file, "r") as f: + return json.load(f) + return None diff --git a/scripts/codeforces_scrapper/codeforces_scrapper.py b/scripts/ml/codeforces_scrapper/codeforces_scrapper.py similarity index 100% rename from scripts/codeforces_scrapper/codeforces_scrapper.py rename to scripts/ml/codeforces_scrapper/codeforces_scrapper.py diff --git a/scripts/prepare.sh b/scripts/ml/prepare.sh similarity index 100% rename from scripts/prepare.sh rename to scripts/ml/prepare.sh diff --git a/scripts/prog_list b/scripts/ml/prog_list similarity index 100% rename from scripts/prog_list rename to scripts/ml/prog_list diff --git a/scripts/quality_analysis.sh b/scripts/ml/quality_analysis.sh similarity index 100% rename from scripts/quality_analysis.sh rename to scripts/ml/quality_analysis.sh diff --git a/scripts/requirements.txt b/scripts/ml/requirements.txt similarity index 100% rename from scripts/requirements.txt rename to scripts/ml/requirements.txt diff --git a/scripts/run_contest_estimator.sh b/scripts/ml/run_contest_estimator.sh similarity index 100% rename from scripts/run_contest_estimator.sh rename to scripts/ml/run_contest_estimator.sh diff --git a/scripts/run_with_coverage.sh b/scripts/ml/run_with_coverage.sh similarity index 84% rename from scripts/run_with_coverage.sh rename to scripts/ml/run_with_coverage.sh index 29f41ddf20..95ba77eb7b 100644 --- a/scripts/run_with_coverage.sh +++ b/scripts/ml/run_with_coverage.sh @@ -13,7 +13,7 @@ if [[ -n $COVERAGE_PROCESSING ]]; then fi WORKDIR="." -$WORKDIR/scripts/run_contest_estimator.sh $PROJECT $TIME_LIMIT "$PATH_SELECTOR" "" "$COVERAGE_PROCESSING" +$WORKDIR/scripts/ml/run_contest_estimator.sh $PROJECT $TIME_LIMIT "$PATH_SELECTOR" "" "$COVERAGE_PROCESSING" ./gradlew :utbot-junit-contest:test :utbot-junit-contest:jacocoTestReport diff --git a/scripts/selector_list b/scripts/ml/selector_list similarity index 100% rename from scripts/selector_list rename to scripts/ml/selector_list diff --git a/scripts/train.py b/scripts/ml/train.py similarity index 100% rename from scripts/train.py rename to scripts/ml/train.py diff --git a/scripts/ml/train_data.sh b/scripts/ml/train_data.sh new file mode 100644 index 0000000000..25ef4b4709 --- /dev/null +++ b/scripts/ml/train_data.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +WORKDIR="." +TIME_LIMIT=${1} + +while read prog; do + echo "Starting features collection from $prog" + prog="${prog%%[[:cntrl:]]}" + while read selector; do + echo "Starting features collection from $prog with $selector" + selector="${selector%%[[:cntrl:]]}" + $WORKDIR/scripts/ml/run_contest_estimator.sh "$prog" "$TIME_LIMIT" "$selector" true + done <"$WORKDIR/scripts/ml/selector_list" +done <"$WORKDIR/scripts/ml/prog_list" diff --git a/scripts/ml/train_iteratively.sh b/scripts/ml/train_iteratively.sh new file mode 100644 index 0000000000..a2169a9514 --- /dev/null +++ b/scripts/ml/train_iteratively.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +TIME_LIMIT=${1} +ITERATIONS=${2} +OUTPUT_DIR=${3} +PYTHON_COMMAND=${4} + +declare -a models=("linear" "nn16" "nn32" "nn64" "nn128") + +WORKDIR="." + +echo "Start training data on heuristical based selectors" + +$WORKDIR/scripts/ml/train_data.sh $TIME_LIMIT + +echo "Start iterative learning of models" + +for (( i=0; i < $ITERATIONS; i++ )) +do + + echo "Start $i iteration" + + for model in "${models[@]}" + do + EXTRA_ARGS="" + if [[ $model == *"nn"* ]]; then + EXTRA_ARGS="--hidden_dim $(echo $model | cut -c 3-)" + echo "EXTRA_ARGS=$EXTRA_ARGS" + fi + + COMMAND="$PYTHON_COMMAND $WORKDIR/scripts/ml/train.py --features_dir $WORKDIR/eval/features --output_dir $OUTPUT_DIR/$model/$i --prog_list $WORKDIR/scripts/prog_list --model $model $EXTRA_ARGS" + echo "TRAINING COMMAND=$COMMAND" + $COMMAND + done + + while read prog; do + prog="${prog%%[[:cntrl:]]}" + + for model in "${models[@]}" + do + PREDICTOR="BASE" + + if [[ $model == *"linear"* ]]; then + PREDICTOR="LINEAR" + fi + + $WORKDIR/scripts/ml/run_contest_estimator.sh $prog $TIME_LIMIT "NN_REWARD_GUIDED_SELECTOR $OUTPUT_DIR/$model/$i $PREDICTOR" "true eval/features/jlearch/$model$i/$prog" + done + done <"$WORKDIR/scripts/ml/prog_list" +done diff --git a/scripts/project/json_to_prometheus.py b/scripts/project/json_to_prometheus.py new file mode 100644 index 0000000000..0a00270f9b --- /dev/null +++ b/scripts/project/json_to_prometheus.py @@ -0,0 +1,36 @@ +import sys +import json + +with open(sys.argv[1]) as metrics_raw: + metrics_json = json.load(metrics_raw) + +# metrics is a json list e.g.: +# [ +# { +# "metric": "total_classes", +# "labels": { +# "project": "guava", +# "fuzzing_ratio": 0.1 +# }, +# "value": 20 +# }, +# { +# "metric": "testcases_generated", +# "labels": { +# "project": "guava", +# "fuzzing_ratio": 0.1 +# }, +# "value": 1042 +# } +# ] +# +# the loop below iterates over each list item and constructs metrics set +metrics_set_str = "" +for metric in metrics_json: + labels_set_str = "" + comma = "" + for label, value in metric['labels'].items(): + labels_set_str = f'{labels_set_str}{comma}{label}=\"{value}\"' + comma = "," + metrics_set_str += f'{metric["metric"]}{{{labels_set_str}}} {metric["value"]}\n' +print(metrics_set_str) diff --git a/scripts/project/logging.sh b/scripts/project/logging.sh new file mode 100644 index 0000000000..52931a78cc --- /dev/null +++ b/scripts/project/logging.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +FILEBEAT_DIR=${1} +LOGSTASH_HOST=${2} + +cat > ${FILEBEAT_DIR}/filebeat.yml </dev/null + sleep ${SLEEP_TIME_SECONDS} +done & + +# jvm metrics +# +# to enable this part of monitoring you also need to pass -javaagent option to org.gradle.jvmargs of GRADLE_OPTS variable, for example: +# GRADLE_OPTS: "-Dorg.gradle.jvmargs='-XX:MaxHeapSize=2048m -javaagent:/tmp/jmx-exporter.jar=12345:/tmp/jmx-exporter.yml -Dorg.gradle.daemon=false'" +#curl ${JMX_EXPORTER_URL} -o ${JMX_EXPORTER_JAR} +#chmod +x ${JMX_EXPORTER_JAR} +#printf "rules:\n- pattern: \".*\"\n" > ${JMX_EXPORTER_CONFIG} +#while true; do +# curl localhost:${JMX_EXPORTER_PORT} 2>/dev/null | curl -u "${PUSHGATEWAY_USER}":"${PUSHGATEWAY_PASSWORD}" --data-binary @- "https://${PUSHGATEWAY_HOSTNAME}${PUSHGATEWAY_ADDITIONAL_PATH}/metrics/job/pushgateway/instance/${GITHUB_RUN_ID}-${HOSTNAME}${PROM_ADDITIONAL_LABELS}" 2>/dev/null +# sleep ${SLEEP_TIME_SECONDS} +#done & diff --git a/scripts/project/ps_parser.sh b/scripts/project/ps_parser.sh new file mode 100644 index 0000000000..7bdf32b0ca --- /dev/null +++ b/scripts/project/ps_parser.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +while read line; do + #echo $line + PID=$(echo $line | awk '{ print $1 }') + RSS=$(echo $line | awk '{ print $2 * 1024 }') + PID_EXECUTABLE=$(cat /proc/${PID}/stat 2>/dev/null | awk '{ print $2 }' | sed -n 's/^(\(.*\))$/\1/p' ) + DESCRIPTION=$(echo $line | grep -o "Gradle Test Executor [0-9]*") + if [[ "${PID_EXECUTABLE=}" == "java" ]]; then + echo "process_memory_bytes{pid=\"${PID}\",pid_executable=\"${PID_EXECUTABLE}\",description=\"${DESCRIPTION}\"} ${RSS}" + fi +done <<< $(ps -ax --no-headers --format=pid,rss,command --sort=-rss,pid) diff --git a/scripts/train_data.sh b/scripts/train_data.sh deleted file mode 100644 index a31e8609fe..0000000000 --- a/scripts/train_data.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -WORKDIR="." -TIME_LIMIT=${1} - -while read prog; do - echo "Starting features collection from $prog" - prog="${prog%%[[:cntrl:]]}" - while read selector; do - echo "Starting features collection from $prog with $selector" - selector="${selector%%[[:cntrl:]]}" - $WORKDIR/scripts/run_contest_estimator.sh "$prog" "$TIME_LIMIT" "$selector" true - done <"$WORKDIR/scripts/selector_list" -done <"$WORKDIR/scripts/prog_list" diff --git a/scripts/train_iteratively.sh b/scripts/train_iteratively.sh deleted file mode 100644 index 842fd771bd..0000000000 --- a/scripts/train_iteratively.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -TIME_LIMIT=${1} -ITERATIONS=${2} -OUTPUT_DIR=${3} -PYTHON_COMMAND=${4} - -declare -a models=("linear" "nn16" "nn32" "nn64" "nn128") - -WORKDIR="." - -echo "Start training data on heuristical based selectors" - -$WORKDIR/scripts/train_data.sh $TIME_LIMIT - -echo "Start iterative learning of models" - -for (( i=0; i < $ITERATIONS; i++ )) -do - - echo "Start $i iteration" - - for model in "${models[@]}" - do - EXTRA_ARGS="" - if [[ $model == *"nn"* ]]; then - EXTRA_ARGS="--hidden_dim $(echo $model | cut -c 3-)" - echo "EXTRA_ARGS=$EXTRA_ARGS" - fi - - COMMAND="$PYTHON_COMMAND $WORKDIR/scripts/train.py --features_dir $WORKDIR/eval/features --output_dir $OUTPUT_DIR/$model/$i --prog_list $WORKDIR/scripts/prog_list --model $model $EXTRA_ARGS" - echo "TRAINING COMMAND=$COMMAND" - $COMMAND - done - - while read prog; do - prog="${prog%%[[:cntrl:]]}" - - for model in "${models[@]}" - do - PREDICTOR="BASE" - - if [[ $model == *"linear"* ]]; then - PREDICTOR="LINEAR" - fi - - $WORKDIR/scripts/run_contest_estimator.sh $prog $TIME_LIMIT "NN_REWARD_GUIDED_SELECTOR $OUTPUT_DIR/$model/$i $PREDICTOR" "true eval/features/jlearch/$model$i/$prog" - done - done <"$WORKDIR/scripts/prog_list" -done diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index ac2c9d1279..0000000000 --- a/settings.gradle +++ /dev/null @@ -1,20 +0,0 @@ -rootProject.name = 'utbot' - -include 'utbot-core' -include 'utbot-framework' -include 'utbot-framework-api' -include 'utbot-intellij' -include 'utbot-sample' -include 'utbot-fuzzers' -include 'utbot-junit-contest' -include 'utbot-analytics' -include 'utbot-cli' -include 'utbot-api' -include 'utbot-instrumentation' -include 'utbot-instrumentation-tests' - -include 'utbot-summary' -include 'utbot-gradle' -include 'utbot-maven' -include 'utbot-summary-tests' - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000000..dda07b9caf --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,101 @@ +val projectType: String by settings +val communityEdition: String by settings +val ultimateEdition: String by settings + +val ideType: String by settings +val buildType: String by settings +val pycharmIdeType: String by settings + +val javaIde: String by settings +val pythonIde: String by settings +val jsIde: String by settings +val jsBuild: String by settings +val includeRiderInBuild: String by settings +val goIde: String by settings + +pluginManagement { + resolutionStrategy { + eachPlugin { + if (requested.id.name == "rdgen") { + useModule("com.jetbrains.rd:rd-gen:${requested.version}") + } + } + } +} + +rootProject.name = "utbot" + +include("utbot-core") +include("utbot-framework") +include("utbot-framework-api") +include("utbot-modificators-analyzer") +include("utbot-sample") +include("utbot-java-fuzzing") +include("utbot-fuzzing") +include("utbot-junit-contest") +include("utbot-analytics") +include("utbot-analytics-torch") + +include("utbot-usvm") + +include("utbot-cli") + +include("utbot-api") +include("utbot-instrumentation") +include("utbot-instrumentation-tests") + +include("utbot-summary") +include("utbot-gradle") +include("utbot-maven") +include("utbot-summary-tests") +include("utbot-framework-test") +include("utbot-testing") +include("utbot-rd") +include("utbot-android-studio") + +if (includeRiderInBuild.toBoolean()) { + include("utbot-rider") +} + +include("utbot-ui-commons") + +include("utbot-spring-framework") +include("utbot-spring-commons-api") +include("utbot-spring-commons") +include("utbot-spring-analyzer") +include("utbot-spring-sample") +include("utbot-spring-test") + +if (pycharmIdeType.split(",").contains(ideType)) { + include("utbot-python-pycharm") +} else { + include("utbot-intellij-main") +} + +if (javaIde.split(",").contains(ideType)) { + include("utbot-intellij") +} + +if (pythonIde.split(",").contains(ideType)) { + include("utbot-python") + include("utbot-cli-python") + include("utbot-intellij-python") + include("utbot-python-parser") + include("utbot-python-executor") +} + +if (projectType == ultimateEdition) { + if (jsBuild == buildType || jsIde.split(",").contains(ideType)) { + include("utbot-js") + include("utbot-cli-js") + include("utbot-intellij-js") + } + + if (goIde.split(",").contains(ideType)) { + include("utbot-go") + include("utbot-cli-go") + include("utbot-intellij-go") + } +} + +include("utbot-light") diff --git a/utbot-analytics-torch/build.gradle b/utbot-analytics-torch/build.gradle new file mode 100644 index 0000000000..572658ddc7 --- /dev/null +++ b/utbot-analytics-torch/build.gradle @@ -0,0 +1,48 @@ +configurations { + torchmodels +} + +def osName = System.getProperty('os.name').toLowerCase().split()[0] +if (osName == "mac") osName = "macosx" +String classifier = osName + "-x86_64" + +evaluationDependsOn(':utbot-framework') +compileTestJava.dependsOn tasks.getByPath(':utbot-framework:testClasses') + +dependencies { + api project(':utbot-analytics') + testImplementation project(':utbot-sample') + testImplementation group: 'junit', name: 'junit', version: junit4Version + + implementation group: 'org.bytedeco', name: 'javacpp', version: javaCppVersion, classifier: "$classifier" + implementation group: 'org.jsoup', name: 'jsoup', version: jsoupVersion + + implementation "ai.djl:api:$djlApiVersion" + implementation "ai.djl.pytorch:pytorch-engine:$djlApiVersion" + implementation "ai.djl.pytorch:pytorch-native-auto:$pytorchNativeVersion" + + testImplementation project(':utbot-framework').sourceSets.test.output +} + +processResources { + configurations.torchmodels.resolvedConfiguration.resolvedArtifacts.each { artifact -> + from(zipTree(artifact.getFile())) { + into "models" + } + } +} + +jar { + dependsOn classes + manifest { + attributes 'Main-Class': 'org.utbot.QualityAnalysisKt' + } + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + zip64 = true +} \ No newline at end of file diff --git a/utbot-analytics-torch/readme.md b/utbot-analytics-torch/readme.md new file mode 100644 index 0000000000..3238a2c511 --- /dev/null +++ b/utbot-analytics-torch/readme.md @@ -0,0 +1,10 @@ +To enable support of the `utbot-analytics-torch` models in `utbot-intellij` module the following steps should be made: + +- change the row `api project(':utbot-analytics')` to the `api project(':utbot-analytics-torch')` in the `build.gradle` file in the `utbot-intellij` module and uncomment it, if it's commented. +- change the `pathSelectorType` in the `UtSettings.kt` to the `PathSelectorType.TORCH_SELECTOR` +- don't forget the put the Torch model in the path ruled by the setting `modelPath` in the `UtSettings.kt` + +NOTE: for Windows you could obtain the error message related to the "engine not found problem" from DJL library during the Torch model initialization. +The proposed solution from DJL authors includes the installation of the [Microsoft Visual C++ Redistributable.](https://docs.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170) + +But at this moment it doesn't work on Windows at all. \ No newline at end of file diff --git a/utbot-analytics-torch/src/main/kotlin/org/utbot/AnalyticsTorchConfiguration.kt b/utbot-analytics-torch/src/main/kotlin/org/utbot/AnalyticsTorchConfiguration.kt new file mode 100644 index 0000000000..7f70ec31e3 --- /dev/null +++ b/utbot-analytics-torch/src/main/kotlin/org/utbot/AnalyticsTorchConfiguration.kt @@ -0,0 +1,21 @@ +package org.utbot + +import org.utbot.analytics.EngineAnalyticsContext +import org.utbot.features.FeatureExtractorFactoryImpl +import org.utbot.features.FeatureProcessorWithStatesRepetitionFactory +import org.utbot.predictors.TorchPredictorFactoryImpl + +/** + * The basic configuration of the utbot-analytics-torch module used in utbot-intellij and (as planned) in utbot-cli + * to implement the hidden configuration initialization to avoid direct calls of this configuration and usage of utbot-analytics-torch imports. + * + * @see + * Issue: Enable utbot-analytics module in utbot-intellij module + */ +object AnalyticsTorchConfiguration { + init { + EngineAnalyticsContext.featureProcessorFactory = FeatureProcessorWithStatesRepetitionFactory() + EngineAnalyticsContext.featureExtractorFactory = FeatureExtractorFactoryImpl() + EngineAnalyticsContext.mlPredictorFactory = TorchPredictorFactoryImpl() + } +} \ No newline at end of file diff --git a/utbot-analytics-torch/src/main/kotlin/org/utbot/predictors/TorchPredictor.kt b/utbot-analytics-torch/src/main/kotlin/org/utbot/predictors/TorchPredictor.kt new file mode 100644 index 0000000000..5b0dce7047 --- /dev/null +++ b/utbot-analytics-torch/src/main/kotlin/org/utbot/predictors/TorchPredictor.kt @@ -0,0 +1,39 @@ +package org.utbot.predictors + +import ai.djl.Model +import ai.djl.inference.Predictor +import ai.djl.ndarray.NDArray +import ai.djl.ndarray.NDList +import ai.djl.translate.Translator +import ai.djl.translate.TranslatorContext +import org.utbot.analytics.MLPredictor +import org.utbot.framework.UtSettings +import java.io.Closeable +import java.nio.file.Paths + +class TorchPredictor : MLPredictor, Closeable { + val model: Model + + init { + model = Model.newInstance("model") + model.load(Paths.get(UtSettings.modelPath, "model.pt1")) + } + + private val predictor: Predictor, Float> = model.newPredictor(object : Translator, Float> { + override fun processInput(ctx: TranslatorContext, input: List): NDList { + val array: NDArray = ctx.ndManager.create(input.toFloatArray()) + return NDList(array) + } + + override fun processOutput(ctx: TranslatorContext, list: NDList): Float = list[0].getFloat() + }) + + override fun predict(input: List): Double { + val reward: Float = predictor.predict(input.map { it.toFloat() }.toList()) + return reward.toDouble() + } + + override fun close() { + predictor.close() + } +} \ No newline at end of file diff --git a/utbot-analytics-torch/src/main/kotlin/org/utbot/predictors/TorchPredictorFactoryImpl.kt b/utbot-analytics-torch/src/main/kotlin/org/utbot/predictors/TorchPredictorFactoryImpl.kt new file mode 100644 index 0000000000..f61fe0b1a0 --- /dev/null +++ b/utbot-analytics-torch/src/main/kotlin/org/utbot/predictors/TorchPredictorFactoryImpl.kt @@ -0,0 +1,11 @@ +package org.utbot.predictors + +import org.utbot.analytics.MLPredictorFactory +import org.utbot.framework.UtSettings + +/** + * Creates [StateRewardPredictor], by checking the [UtSettings] configuration. + */ +class TorchPredictorFactoryImpl : MLPredictorFactory { + override operator fun invoke() = TorchPredictor() +} \ No newline at end of file diff --git a/utbot-analytics-torch/src/test/kotlin/org/utbot/predictors/TorchPredictorTest.kt b/utbot-analytics-torch/src/test/kotlin/org/utbot/predictors/TorchPredictorTest.kt new file mode 100644 index 0000000000..22d58ca158 --- /dev/null +++ b/utbot-analytics-torch/src/test/kotlin/org/utbot/predictors/TorchPredictorTest.kt @@ -0,0 +1,48 @@ +package org.utbot.predictors + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.analytics.MLPredictor +import org.utbot.testcheckers.withModelPath +import kotlin.system.measureNanoTime + +class TorchPredictorTest { + @Test + @Disabled("Just to see the performance of predictors") + fun simpleTest() { + withModelPath("src/test/resources") { + val pred = TorchPredictor() + + val features = listOf(0.0, 0.0) + + assertEquals(5.0, pred.predict(features)) + } + } + + @Disabled("Just to see the performance of predictors") + @Test + fun performanceTest() { + val features = (1..13).map { 1.0 }.toList() + withModelPath("models") { + val averageTime = calcAverageTimeForModelPredict(::TorchPredictor, 100, features) + println(averageTime) + } + } + + private fun calcAverageTimeForModelPredict( + model: () -> MLPredictor, + iterations: Int, + features: List + ): Double { + val pred = model() + + (1..iterations).map { + pred.predict(features) + } + + return (1..iterations) + .map { measureNanoTime { pred.predict(features) } } + .average() + } +} \ No newline at end of file diff --git a/utbot-analytics-torch/src/test/resources/log4j2.xml b/utbot-analytics-torch/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..7dde3c2fea --- /dev/null +++ b/utbot-analytics-torch/src/test/resources/log4j2.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utbot-analytics/build.gradle b/utbot-analytics/build.gradle index 2757b9f65d..5838ac7591 100644 --- a/utbot-analytics/build.gradle +++ b/utbot-analytics/build.gradle @@ -1,5 +1,3 @@ -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" - configurations { mlmodels } @@ -12,44 +10,31 @@ evaluationDependsOn(':utbot-framework') compileTestJava.dependsOn tasks.getByPath(':utbot-framework:testClasses') dependencies { - implementation(project(":utbot-framework")) - compile(project(':utbot-instrumentation')) - implementation(project(':utbot-summary')) + api project(":utbot-framework") testImplementation project(':utbot-sample') - testImplementation group: 'junit', name: 'junit', version: junit4_version + testImplementation group: 'junit', name: 'junit', version: junit4Version - implementation "com.github.UnitTestBot:soot:${soot_commit_hash}" + implementation("org.unittestbot.soot:soot-utbot-fork:${sootVersion}") { + exclude group:'com.google.guava', module:'guava' + } implementation group: 'com.github.haifengl', name: 'smile-kotlin', version: '2.6.0' implementation group: 'com.github.haifengl', name: 'smile-plot', version: '2.6.0' implementation group: 'com.github.haifengl', name: 'smile-core', version: '2.6.0' implementation group: 'com.github.haifengl', name: 'smile-interpolation', version: '2.6.0' - implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version + implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlinLoggingVersion implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6' - implementation group: 'org.bytedeco', name: 'arpack-ng', version: "3.7.0-1.5.4", classifier: "$classifier" - implementation group: 'org.bytedeco', name: 'openblas', version: "0.3.10-1.5.4", classifier: "$classifier" - implementation group: 'org.bytedeco', name: 'javacpp', version: javacpp_version, classifier: "$classifier" + implementation group: 'org.bytedeco', name: 'arpack-ng', version: arpackNgVersion, classifier: "$classifier" + implementation group: 'org.bytedeco', name: 'openblas', version: openblasVersion, classifier: "$classifier" implementation group: 'tech.tablesaw', name: 'tablesaw-core', version: '0.38.2' implementation group: 'tech.tablesaw', name: 'tablesaw-jsplot', version: '0.38.2' implementation group: 'org.apache.commons', name: 'commons-text', version: '1.9' - implementation group: 'com.github.javaparser', name: 'javaparser-core', version: '3.22.1' - implementation group: 'org.jsoup', name: 'jsoup', version: jsoup_version - - implementation "ai.djl:api:$djl_api_version" - implementation "ai.djl.pytorch:pytorch-engine:$djl_api_version" - implementation "ai.djl.pytorch:pytorch-native-auto:$pytorch_native_version" - - testCompile project(':utbot-framework').sourceSets.test.output -} - -test { - useJUnitPlatform { - excludeTags 'Summary' - } + testImplementation project(':utbot-testing') + testImplementation project(':utbot-framework').sourceSets.test.output } processResources { @@ -60,15 +45,27 @@ processResources { } } -jar { - dependsOn classes - manifest { - attributes 'Main-Class': 'org.utbot.QualityAnalysisKt' - } - - from { - configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } - } - - duplicatesStrategy = DuplicatesStrategy.EXCLUDE -} \ No newline at end of file +// TODO if you need utbot-analytics fat jar, use shadow jar to create a SEPARATE task for a fat jar. +// Do not use main jar for a fat jar, because it breaks Gradle conflict resolution, here's how: +// 1. utbot-analytics depends on library A version 1.0 (and adds it to own main jar) +// 2. utbot-junit-contest depends on utbot-analytics and library A version 1.1 +// 3. Both library A version 1.0 and version 1.1 end up on the classpath and it's a matter of chance which one is earlier +// If utbot-analytics were to only declare its dependency on library A version 1.0 and not force it by adding it to a +// main jar, then Gradle would be able to recognize the conflict of library A version 1.0 and version 1.1 and resolve +// it according to a conflict resolution strategy, which by default picks the latest version, which works in most cases. +// But if you put library A version 1.0 into some fat jar, Gradle will no longer be able to exclude it from the fat jar +// in favor of a newer version when it needs to resolve dependency conflicts. +//jar { +// dependsOn classes +// manifest { +// attributes 'Main-Class': 'org.utbot.QualityAnalysisKt' +// } +// +// dependsOn configurations.runtimeClasspath +// from { +// configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } +// } +// +// duplicatesStrategy = DuplicatesStrategy.EXCLUDE +// zip64 = true +//} \ No newline at end of file diff --git a/utbot-analytics/src/main/kotlin/org/utbot/AnalyticsConfiguration.kt b/utbot-analytics/src/main/kotlin/org/utbot/AnalyticsConfiguration.kt new file mode 100644 index 0000000000..58e0df80b0 --- /dev/null +++ b/utbot-analytics/src/main/kotlin/org/utbot/AnalyticsConfiguration.kt @@ -0,0 +1,21 @@ +package org.utbot + +import org.utbot.analytics.EngineAnalyticsContext +import org.utbot.features.FeatureExtractorFactoryImpl +import org.utbot.features.FeatureProcessorWithStatesRepetitionFactory +import org.utbot.predictors.MLPredictorFactoryImpl + +/** + * The basic configuration of the utbot-analytics module used in utbot-intellij and (as planned) in utbot-cli + * to implement the hidden configuration initialization to avoid direct calls of this configuration and usage of utbot-analytics imports. + * + * @see + * Issue: Enable utbot-analytics module in utbot-intellij module + */ +object AnalyticsConfiguration { + init { + EngineAnalyticsContext.featureProcessorFactory = FeatureProcessorWithStatesRepetitionFactory() + EngineAnalyticsContext.featureExtractorFactory = FeatureExtractorFactoryImpl() + EngineAnalyticsContext.mlPredictorFactory = MLPredictorFactoryImpl() + } +} \ No newline at end of file diff --git a/utbot-analytics/src/main/kotlin/org/utbot/features/FeatureExtractorImpl.kt b/utbot-analytics/src/main/kotlin/org/utbot/features/FeatureExtractorImpl.kt index 6786c9847a..ddf1322659 100644 --- a/utbot-analytics/src/main/kotlin/org/utbot/features/FeatureExtractorImpl.kt +++ b/utbot-analytics/src/main/kotlin/org/utbot/features/FeatureExtractorImpl.kt @@ -1,7 +1,7 @@ package org.utbot.features import org.utbot.analytics.FeatureExtractor -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.InterProceduralUnitGraph import org.utbot.engine.selectors.strategies.StatementsStatistics import org.utbot.engine.selectors.strategies.SubpathStatistics diff --git a/utbot-analytics/src/main/kotlin/org/utbot/features/FeatureProcessorWithStatesRepetition.kt b/utbot-analytics/src/main/kotlin/org/utbot/features/FeatureProcessorWithStatesRepetition.kt index 49f9427678..4c80f18dda 100644 --- a/utbot-analytics/src/main/kotlin/org/utbot/features/FeatureProcessorWithStatesRepetition.kt +++ b/utbot-analytics/src/main/kotlin/org/utbot/features/FeatureProcessorWithStatesRepetition.kt @@ -2,7 +2,7 @@ package org.utbot.features import org.utbot.analytics.EngineAnalyticsContext import org.utbot.analytics.FeatureProcessor -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.InterProceduralUnitGraph import org.utbot.framework.UtSettings import soot.jimple.Stmt @@ -107,7 +107,7 @@ class FeatureProcessorWithStatesRepetition( } } -internal class RewardEstimator { +class RewardEstimator { fun calculateRewards(testCases: List): Map { val rewards = mutableMapOf() diff --git a/utbot-analytics/src/main/kotlin/org/utbot/features/UtExpressionStructureCounter.kt b/utbot-analytics/src/main/kotlin/org/utbot/features/UtExpressionStructureCounter.kt index f6f209875c..b1c5661919 100644 --- a/utbot-analytics/src/main/kotlin/org/utbot/features/UtExpressionStructureCounter.kt +++ b/utbot-analytics/src/main/kotlin/org/utbot/features/UtExpressionStructureCounter.kt @@ -34,21 +34,6 @@ val featureIndex = listOf( UtBoolOpExpression::class.simpleName, UtIsExpression::class.simpleName, UtIteExpression::class.simpleName, - UtStringConst::class.simpleName, - UtConcatExpression::class.simpleName, - UtConvertToString::class.simpleName, - UtStringLength::class.simpleName, - UtStringPositiveLength::class.simpleName, - UtStringCharAt::class.simpleName, - UtStringEq::class.simpleName, - UtSubstringExpression::class.simpleName, - UtReplaceExpression::class.simpleName, - UtStartsWithExpression::class.simpleName, - UtEndsWithExpression::class.simpleName, - UtIndexOfExpression::class.simpleName, - UtContainsExpression::class.simpleName, - UtToStringExpression::class.simpleName, - UtSeqLiteral::class.simpleName, TREES, MAX_NODES, MIN_NODES, @@ -160,6 +145,7 @@ class UtExpressionStructureCounter(private val input: Iterable) : override fun visit(expr: UtAddNoOverflowExpression) = multipleExpressions(expr.left, expr.right) override fun visit(expr: UtSubNoOverflowExpression) = multipleExpressions(expr.left, expr.right) + override fun visit(expr: UtMulNoOverflowExpression)= multipleExpressions(expr.left, expr.right) override fun visit(expr: UtNegExpression): NestStat { val stat = buildState(expr.variable.expr) @@ -168,6 +154,13 @@ class UtExpressionStructureCounter(private val input: Iterable) : return stat } + override fun visit(expr: UtBvNotExpression): NestStat { + val stat = buildState(expr.variable.expr) + stat.level++ + stat.nodes++ + return stat + } + override fun visit(expr: UtCastExpression): NestStat { val stat = buildState(expr.variable.expr) stat.level++ @@ -216,59 +209,6 @@ class UtExpressionStructureCounter(private val input: Iterable) : ) } - //const string value - override fun visit(expr: UtStringConst) = NestStat() - - override fun visit(expr: UtConcatExpression) = multipleExpression(expr.parts) - - override fun visit(expr: UtConvertToString): NestStat { - val stat = buildState(expr.expression) - stat.level++ - stat.nodes++ - return stat - } - - override fun visit(expr: UtStringToInt): NestStat { - val stat = buildState(expr.expression) - stat.level++ - stat.nodes++ - return stat - } - - override fun visit(expr: UtStringLength): NestStat { - val stat = buildState(expr.string) - stat.level++ - stat.nodes++ - return stat - } - - override fun visit(expr: UtStringPositiveLength): NestStat { - val stat = buildState(expr.string) - stat.level++ - stat.nodes++ - return stat - } - - override fun visit(expr: UtStringCharAt) = multipleExpressions(expr.string, expr.index) - - override fun visit(expr: UtStringEq) = multipleExpressions(expr.left, expr.right) - - override fun visit(expr: UtSubstringExpression) = multipleExpressions(expr.string, expr.beginIndex, expr.length) - - override fun visit(expr: UtReplaceExpression) = multipleExpressions(expr.string, expr.regex, expr.replacement) - - override fun visit(expr: UtStartsWithExpression) = multipleExpressions(expr.string, expr.prefix) - - override fun visit(expr: UtEndsWithExpression) = multipleExpressions(expr.string, expr.suffix) - - override fun visit(expr: UtIndexOfExpression) = multipleExpressions(expr.string, expr.substring) - - override fun visit(expr: UtContainsExpression) = multipleExpressions(expr.string, expr.substring) - - override fun visit(expr: UtToStringExpression) = multipleExpressions(expr.notNullExpr, expr.isNull) - - override fun visit(expr: UtSeqLiteral) = NestStat() - private fun multipleExpressions(vararg expressions: UtExpression) = multipleExpression(expressions.toList()) private fun multipleExpression(expressions: List): NestStat { @@ -311,14 +251,6 @@ class UtExpressionStructureCounter(private val input: Iterable) : override fun visit(expr: UtArrayApplyForAll): NestStat { return NestStat() } - - override fun visit(expr: UtStringToArray): NestStat { - return NestStat() - } - - override fun visit(expr: UtArrayToString): NestStat { - return NestStat() - } } data class NestStat(var nodes: Int = 1, var level: Int = 1) diff --git a/utbot-analytics/src/main/kotlin/org/utbot/predictors/LinearRegressionPredictor.kt b/utbot-analytics/src/main/kotlin/org/utbot/predictors/LinearRegressionPredictor.kt new file mode 100644 index 0000000000..731480a8da --- /dev/null +++ b/utbot-analytics/src/main/kotlin/org/utbot/predictors/LinearRegressionPredictor.kt @@ -0,0 +1,72 @@ +package org.utbot.predictors + +import org.utbot.analytics.MLPredictor +import mu.KotlinLogging +import org.utbot.framework.PathSelectorType +import org.utbot.framework.UtSettings +import org.utbot.predictors.util.PredictorLoadingException +import org.utbot.predictors.util.WeightsLoadingException +import org.utbot.predictors.util.splitByCommaIntoDoubleArray +import smile.math.MathEx.dot +import smile.math.matrix.Matrix +import java.io.File + +private const val DEFAULT_WEIGHT_PATH = "linear.txt" + +private val logger = KotlinLogging.logger {} + +/** + * Last weight is bias + */ +private fun loadWeights(path: String): Matrix { + val weightsFile = File("${UtSettings.modelPath}/${path}") + lateinit var weightsArray: DoubleArray + + try { + if (!weightsFile.exists()) { + error("There is no file with weights with path: ${weightsFile.absolutePath}") + } + + weightsArray = weightsFile.readText().splitByCommaIntoDoubleArray() + } catch (e: Exception) { + throw WeightsLoadingException(e) + } + + return Matrix(weightsArray) +} + +class LinearRegressionPredictor(weightsPath: String = DEFAULT_WEIGHT_PATH, scalerPath: String = DEFAULT_SCALER_PATH) : + MLPredictor { + private lateinit var weights: Matrix + private lateinit var scaler: StandardScaler + + init { + try { + weights = loadWeights(weightsPath) + scaler = loadScaler(scalerPath) + } catch (e: PredictorLoadingException) { + logger.info(e) { + "Error while initialization of LinearRegressionPredictor. Changing pathSelectorType on INHERITORS_SELECTOR" + } + UtSettings.pathSelectorType = PathSelectorType.INHERITORS_SELECTOR + } + } + + fun predict(input: List>): List { + // add 1 to each feature vector + val matrixValues = input + .map { (it + 1.0).toDoubleArray() } + .toTypedArray() + + val X = Matrix(matrixValues) + + return X.mm(weights).col(0).toList() + } + + override fun predict(input: List): Double { + var inputArray = Matrix(input.toDoubleArray()).sub(scaler.mean).div(scaler.variance).col(0) + inputArray += 1.0 + + return dot(inputArray, weights.col(0)) + } +} \ No newline at end of file diff --git a/utbot-analytics/src/main/kotlin/org/utbot/predictors/LinearStateRewardPredictor.kt b/utbot-analytics/src/main/kotlin/org/utbot/predictors/LinearStateRewardPredictor.kt deleted file mode 100644 index 2d3dc434de..0000000000 --- a/utbot-analytics/src/main/kotlin/org/utbot/predictors/LinearStateRewardPredictor.kt +++ /dev/null @@ -1,72 +0,0 @@ -package org.utbot.predictors - -import org.utbot.analytics.StateRewardPredictor -import mu.KotlinLogging -import org.utbot.framework.PathSelectorType -import org.utbot.framework.UtSettings -import org.utbot.predictors.util.PredictorLoadingException -import org.utbot.predictors.util.WeightsLoadingException -import org.utbot.predictors.util.splitByCommaIntoDoubleArray -import smile.math.MathEx.dot -import smile.math.matrix.Matrix -import java.io.File - -private const val DEFAULT_WEIGHT_PATH = "linear.txt" - -private val logger = KotlinLogging.logger {} - -/** - * Last weight is bias - */ -private fun loadWeights(path: String): Matrix { - val weightsFile = File("${UtSettings.rewardModelPath}/${path}") - lateinit var weightsArray: DoubleArray - - try { - if (!weightsFile.exists()) { - error("There is no file with weights with path: ${weightsFile.absolutePath}") - } - - weightsArray = weightsFile.readText().splitByCommaIntoDoubleArray() - } catch (e: Exception) { - throw WeightsLoadingException(e) - } - - return Matrix(weightsArray) -} - -class LinearStateRewardPredictor(weightsPath: String = DEFAULT_WEIGHT_PATH, scalerPath: String = DEFAULT_SCALER_PATH) : - StateRewardPredictor { - private lateinit var weights: Matrix - private lateinit var scaler: StandardScaler - - init { - try { - weights = loadWeights(weightsPath) - scaler = loadScaler(scalerPath) - } catch (e: PredictorLoadingException) { - logger.info(e) { - "Error while initialization of LinearStateRewardPredictor. Changing pathSelectorType on INHERITORS_SELECTOR" - } - UtSettings.pathSelectorType = PathSelectorType.INHERITORS_SELECTOR - } - } - - fun predict(input: List>): List { - // add 1 to each feature vector - val matrixValues = input - .map { (it + 1.0).toDoubleArray() } - .toTypedArray() - - val X = Matrix(matrixValues) - - return X.mm(weights).col(0).toList() - } - - override fun predict(input: List): Double { - var inputArray = Matrix(input.toDoubleArray()).sub(scaler.mean).div(scaler.variance).col(0) - inputArray += 1.0 - - return dot(inputArray, weights.col(0)) - } -} \ No newline at end of file diff --git a/utbot-analytics/src/main/kotlin/org/utbot/predictors/MultilayerPerceptronPredictor.kt b/utbot-analytics/src/main/kotlin/org/utbot/predictors/MultilayerPerceptronPredictor.kt new file mode 100644 index 0000000000..978f5b0267 --- /dev/null +++ b/utbot-analytics/src/main/kotlin/org/utbot/predictors/MultilayerPerceptronPredictor.kt @@ -0,0 +1,44 @@ +package org.utbot.predictors + +import mu.KotlinLogging +import org.utbot.analytics.MLPredictor +import org.utbot.framework.PathSelectorType +import org.utbot.framework.UtSettings +import org.utbot.predictors.util.PredictorLoadingException +import smile.math.matrix.Matrix + +private const val DEFAULT_MODEL_PATH = "nn.json" + +private val logger = KotlinLogging.logger {} + +private fun getModel(path: String) = buildModel(loadModel(path)) + +class MultilayerPerceptronPredictor(modelPath: String = DEFAULT_MODEL_PATH, scalerPath: String = DEFAULT_SCALER_PATH) : + MLPredictor { + private lateinit var nn: FeedForwardNetwork + private lateinit var scaler: StandardScaler + + init { + try { + nn = getModel(modelPath) + scaler = loadScaler(scalerPath) + } catch (e: PredictorLoadingException) { + logger.info(e) { + "Error while initialization of MultilayerPerceptronPredictor. Changing pathSelectorType on INHERITORS_SELECTOR" + } + UtSettings.pathSelectorType = PathSelectorType.INHERITORS_SELECTOR + } + } + + override fun predict(input: List): Double { + var inputArray = input.toDoubleArray() + inputArray = Matrix(inputArray).sub(scaler.mean).div(scaler.variance).col(0) + + nn.operations.forEach { + inputArray = it(inputArray) + } + + check(inputArray.size == 1) { "Neural network have several outputs" } + return inputArray[0] + } +} diff --git a/utbot-analytics/src/main/kotlin/org/utbot/predictors/NNJson.kt b/utbot-analytics/src/main/kotlin/org/utbot/predictors/NNJson.kt index d14034e64d..ed0066ec06 100644 --- a/utbot-analytics/src/main/kotlin/org/utbot/predictors/NNJson.kt +++ b/utbot-analytics/src/main/kotlin/org/utbot/predictors/NNJson.kt @@ -33,7 +33,7 @@ data class NNJson( } internal fun loadModel(path: String): NNJson { - val modelFile = Paths.get(UtSettings.rewardModelPath, path).toFile() + val modelFile = Paths.get(UtSettings.modelPath, path).toFile() lateinit var nnJson: NNJson try { diff --git a/utbot-analytics/src/main/kotlin/org/utbot/predictors/NNStateRewardPredictorBase.kt b/utbot-analytics/src/main/kotlin/org/utbot/predictors/NNStateRewardPredictorBase.kt deleted file mode 100644 index d8e77c4d23..0000000000 --- a/utbot-analytics/src/main/kotlin/org/utbot/predictors/NNStateRewardPredictorBase.kt +++ /dev/null @@ -1,44 +0,0 @@ -package org.utbot.predictors - -import mu.KotlinLogging -import org.utbot.analytics.StateRewardPredictor -import org.utbot.framework.PathSelectorType -import org.utbot.framework.UtSettings -import org.utbot.predictors.util.PredictorLoadingException -import smile.math.matrix.Matrix - -private const val DEFAULT_MODEL_PATH = "nn.json" - -private val logger = KotlinLogging.logger {} - -private fun getModel(path: String) = buildModel(loadModel(path)) - -class NNStateRewardPredictorBase(modelPath: String = DEFAULT_MODEL_PATH, scalerPath: String = DEFAULT_SCALER_PATH) : - StateRewardPredictor { - private lateinit var nn: FeedForwardNetwork - private lateinit var scaler: StandardScaler - - init { - try { - nn = getModel(modelPath) - scaler = loadScaler(scalerPath) - } catch (e: PredictorLoadingException) { - logger.info(e) { - "Error while initialization of NNStateRewardPredictorBase. Changing pathSelectorType on INHERITORS_SELECTOR" - } - UtSettings.pathSelectorType = PathSelectorType.INHERITORS_SELECTOR - } - } - - override fun predict(input: List): Double { - var inputArray = input.toDoubleArray() - inputArray = Matrix(inputArray).sub(scaler.mean).div(scaler.variance).col(0) - - nn.operations.forEach { - inputArray = it(inputArray) - } - - check(inputArray.size == 1) { "Neural network have several outputs" } - return inputArray[0] - } -} diff --git a/utbot-analytics/src/main/kotlin/org/utbot/predictors/StateRewardPredictorFactory.kt b/utbot-analytics/src/main/kotlin/org/utbot/predictors/StateRewardPredictorFactory.kt index c0b7dffe7c..0c6d3e12a0 100644 --- a/utbot-analytics/src/main/kotlin/org/utbot/predictors/StateRewardPredictorFactory.kt +++ b/utbot-analytics/src/main/kotlin/org/utbot/predictors/StateRewardPredictorFactory.kt @@ -1,16 +1,16 @@ package org.utbot.predictors -import org.utbot.analytics.StateRewardPredictorFactory -import org.utbot.framework.StateRewardPredictorType +import org.utbot.analytics.MLPredictor +import org.utbot.analytics.MLPredictorFactory +import org.utbot.framework.MLPredictorType import org.utbot.framework.UtSettings /** - * Creates [StateRewardPredictor], by checking the [UtSettings] configuration. + * Creates [MLPredictor], by checking the [UtSettings] configuration. */ -class StateRewardPredictorFactoryImpl : StateRewardPredictorFactory { - override operator fun invoke() = when (UtSettings.stateRewardPredictorType) { - StateRewardPredictorType.BASE -> NNStateRewardPredictorBase() - StateRewardPredictorType.TORCH -> StateRewardPredictorTorch() - StateRewardPredictorType.LINEAR -> LinearStateRewardPredictor() +class MLPredictorFactoryImpl : MLPredictorFactory { + override operator fun invoke() = when (UtSettings.mlPredictorType) { + MLPredictorType.MLP -> MultilayerPerceptronPredictor() + MLPredictorType.LINREG -> LinearRegressionPredictor() } } \ No newline at end of file diff --git a/utbot-analytics/src/main/kotlin/org/utbot/predictors/StateRewardPredictorTorch.kt b/utbot-analytics/src/main/kotlin/org/utbot/predictors/StateRewardPredictorTorch.kt deleted file mode 100644 index f0cfe17571..0000000000 --- a/utbot-analytics/src/main/kotlin/org/utbot/predictors/StateRewardPredictorTorch.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.utbot.predictors - -import ai.djl.Model -import ai.djl.inference.Predictor -import ai.djl.ndarray.NDArray -import ai.djl.ndarray.NDList -import ai.djl.translate.Translator -import ai.djl.translate.TranslatorContext -import org.utbot.analytics.StateRewardPredictor -import org.utbot.framework.UtSettings -import java.io.Closeable -import java.nio.file.Paths - -class StateRewardPredictorTorch : StateRewardPredictor, Closeable { - val model: Model = Model.newInstance("model") - - init { - model.load(Paths.get(UtSettings.rewardModelPath, "model.pt1")) - } - - private val predictor: Predictor, Float> = model.newPredictor(object : Translator, Float> { - override fun processInput(ctx: TranslatorContext, input: List): NDList { - val array: NDArray = ctx.ndManager.create(input.toFloatArray()) - return NDList(array) - } - - override fun processOutput(ctx: TranslatorContext, list: NDList): Float = list[0].getFloat() - }) - - override fun predict(input: List): Double { - val reward: Float = predictor.predict(input.map { it.toFloat() }.toList()) - return reward.toDouble() - } - - override fun close() { - predictor.close() - } -} \ No newline at end of file diff --git a/utbot-analytics/src/main/kotlin/org/utbot/predictors/scalers.kt b/utbot-analytics/src/main/kotlin/org/utbot/predictors/scalers.kt index b65e78d478..db38bc7743 100644 --- a/utbot-analytics/src/main/kotlin/org/utbot/predictors/scalers.kt +++ b/utbot-analytics/src/main/kotlin/org/utbot/predictors/scalers.kt @@ -13,7 +13,7 @@ data class StandardScaler(val mean: Matrix?, val variance: Matrix?) internal fun loadScaler(path: String): StandardScaler = try { - Paths.get(UtSettings.rewardModelPath, path).toFile().bufferedReader().use { + Paths.get(UtSettings.modelPath, path).toFile().bufferedReader().use { val mean = it.readLine()?.splitByCommaIntoDoubleArray() ?: error("There is not mean in $path") val variance = it.readLine()?.splitByCommaIntoDoubleArray() ?: error("There is not variance in $path") StandardScaler(Matrix(mean), Matrix(variance)) diff --git a/utbot-analytics/src/main/kotlin/org/utbot/visual/AbstractHtmlReport.kt b/utbot-analytics/src/main/kotlin/org/utbot/visual/AbstractHtmlReport.kt index 9f1673a57e..ce7ff03e6f 100644 --- a/utbot-analytics/src/main/kotlin/org/utbot/visual/AbstractHtmlReport.kt +++ b/utbot-analytics/src/main/kotlin/org/utbot/visual/AbstractHtmlReport.kt @@ -1,5 +1,6 @@ package org.utbot.visual +import org.utbot.common.dateTimeFormatter import java.time.LocalDateTime import java.time.format.DateTimeFormatter @@ -7,8 +8,6 @@ import java.time.format.DateTimeFormatter abstract class AbstractHtmlReport(bodyWidth: Int = 600) { val builder = HtmlBuilder(bodyMaxWidth = bodyWidth) - private val dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy_HH-mm-ss") - private fun nameWithDate() = "logs/Report_" + dateTimeFormatter.format(LocalDateTime.now()) + ".html" diff --git a/utbot-analytics/src/main/resources/config.properties b/utbot-analytics/src/main/resources/config.properties new file mode 100644 index 0000000000..e71418b7e6 --- /dev/null +++ b/utbot-analytics/src/main/resources/config.properties @@ -0,0 +1,3 @@ +project=antlr +selectors=random_120,cpi_120,fork_120,inheritors_120,random_120 +covStatistics=logs/covStatistics,logs/covStatistics \ No newline at end of file diff --git a/utbot-analytics/src/test/kotlin/org/utbot/analytics/UtBotPredictorTest.kt b/utbot-analytics/src/test/kotlin/org/utbot/analytics/UtBotPredictorTest.kt deleted file mode 100644 index 38e68a8cbb..0000000000 --- a/utbot-analytics/src/test/kotlin/org/utbot/analytics/UtBotPredictorTest.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.utbot.analytics - -import org.junit.jupiter.api.Test - -internal class UtBotPredictorTest { - - @Test - fun predict() { - } -} \ No newline at end of file diff --git a/utbot-analytics/src/test/kotlin/org/utbot/features/FeatureProcessorWithRepetitionTest.kt b/utbot-analytics/src/test/kotlin/org/utbot/features/FeatureProcessorWithRepetitionTest.kt index 3c0176fd3e..b2020d374b 100644 --- a/utbot-analytics/src/test/kotlin/org/utbot/features/FeatureProcessorWithRepetitionTest.kt +++ b/utbot-analytics/src/test/kotlin/org/utbot/features/FeatureProcessorWithRepetitionTest.kt @@ -5,13 +5,13 @@ import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.utbot.analytics.EngineAnalyticsContext -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.utbot.examples.withFeaturePath +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withFeaturePath +import org.utbot.testing.UtValueTestCaseChecker import java.io.File import java.io.FileInputStream -class FeatureProcessorWithRepetitionTest : AbstractTestCaseGeneratorTest(OnePath::class, false) { +class FeatureProcessorWithRepetitionTest: UtValueTestCaseChecker(OnePath::class, false) { companion object { const val featureDir = "src/test/resources/features" fun reward(coverage: Double, time: Double) = RewardEstimator.reward(coverage, time) @@ -96,6 +96,11 @@ class FeatureProcessorWithRepetitionTest : AbstractTestCaseGeneratorTest(OnePath /** * Test, that we correctly add test cases and dump them into file + * + * NOTE: works only if the + * ``` + * UtSettings.pathSelectorType == PathSelectorType.INHERITORS_SELECTOR + * ``` */ @Test fun addTestCaseTest() { diff --git a/utbot-analytics/src/test/kotlin/org/utbot/predictors/LinearRegressionPredictorTest.kt b/utbot-analytics/src/test/kotlin/org/utbot/predictors/LinearRegressionPredictorTest.kt new file mode 100644 index 0000000000..32f36b05b3 --- /dev/null +++ b/utbot-analytics/src/test/kotlin/org/utbot/predictors/LinearRegressionPredictorTest.kt @@ -0,0 +1,45 @@ +package org.utbot.predictors + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.utbot.framework.PathSelectorType +import org.utbot.framework.UtSettings +import org.utbot.testcheckers.withPathSelectorType +import org.utbot.testcheckers.withModelPath + +class LinearRegressionPredictorTest { + @Test + fun simpleTest() { + withModelPath("src/test/resources") { + val pred = LinearRegressionPredictor() + + val features = listOf( + listOf(2.0, 3.0), + listOf(2.0, 3.0) + ) + + assertEquals(listOf(6.0, 6.0), pred.predict(features)) + } + } + + @Test + fun wrongFormatTest() { + withModelPath("src/test/resources") { + withPathSelectorType(PathSelectorType.ML_SELECTOR) { + LinearRegressionPredictor("wrong_format_linear.txt") + assertEquals(PathSelectorType.INHERITORS_SELECTOR, UtSettings.pathSelectorType) + } + } + } + + @Test + fun simpleTestNotBatch() { + withModelPath("src/test/resources") { + val pred = LinearRegressionPredictor() + + val features = listOf(2.0, 3.0) + + assertEquals(6.0, pred.predict(features)) + } + } +} \ No newline at end of file diff --git a/utbot-analytics/src/test/kotlin/org/utbot/predictors/LinearStateRewardPredictorTest.kt b/utbot-analytics/src/test/kotlin/org/utbot/predictors/LinearStateRewardPredictorTest.kt deleted file mode 100644 index 6a68e83212..0000000000 --- a/utbot-analytics/src/test/kotlin/org/utbot/predictors/LinearStateRewardPredictorTest.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.utbot.predictors - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.utbot.examples.withPathSelectorType -import org.utbot.examples.withRewardModelPath -import org.utbot.framework.PathSelectorType -import org.utbot.framework.UtSettings - -class LinearStateRewardPredictorTest { - @Test - fun simpleTest() { - withRewardModelPath("src/test/resources") { - val pred = LinearStateRewardPredictor() - - val features = listOf( - listOf(2.0, 3.0), - listOf(2.0, 3.0) - ) - - assertEquals(listOf(6.0, 6.0), pred.predict(features)) - } - } - - @Test - fun wrongFormatTest() { - withRewardModelPath("src/test/resources") { - withPathSelectorType(PathSelectorType.NN_REWARD_GUIDED_SELECTOR) { - LinearStateRewardPredictor("wrong_format_linear.txt") - assertEquals(PathSelectorType.INHERITORS_SELECTOR, UtSettings.pathSelectorType) - } - } - } - - @Test - fun simpleTestNotBatch() { - withRewardModelPath("src/test/resources") { - val pred = LinearStateRewardPredictor() - - val features = listOf(2.0, 3.0) - - assertEquals(6.0, pred.predict(features)) - } - } -} \ No newline at end of file diff --git a/utbot-analytics/src/test/kotlin/org/utbot/predictors/MultilayerPerceptronPredictorTest.kt b/utbot-analytics/src/test/kotlin/org/utbot/predictors/MultilayerPerceptronPredictorTest.kt new file mode 100644 index 0000000000..c6829c6b56 --- /dev/null +++ b/utbot-analytics/src/test/kotlin/org/utbot/predictors/MultilayerPerceptronPredictorTest.kt @@ -0,0 +1,80 @@ +package org.utbot.predictors + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.analytics.MLPredictor +import org.utbot.framework.PathSelectorType +import org.utbot.framework.UtSettings +import org.utbot.testcheckers.withPathSelectorType +import org.utbot.testcheckers.withModelPath +import kotlin.system.measureNanoTime + +class MultilayerPerceptronPredictorTest { + @Test + fun simpleTest() { + withModelPath("src/test/resources") { + val pred = MultilayerPerceptronPredictor() + + val features = listOf(0.0, 0.0) + + assertEquals(5.0, pred.predict(features)) + } + } + + @Disabled("Just to see the performance of predictors") + @Test + fun performanceTest() { + val features = (1..13).map { 1.0 }.toList() + withModelPath("models\\test\\0") { + val averageTime = calcAverageTimeForModelPredict(::MultilayerPerceptronPredictor, 100, features) + println(averageTime) + } + } + + internal fun calcAverageTimeForModelPredict( + model: () -> MLPredictor, + iterations: Int, + features: List + ): Double { + val pred = model() + + (1..iterations).map { + pred.predict(features) + } + + return (1..iterations) + .map { measureNanoTime { pred.predict(features) } } + .average() + } + + @Test + fun corruptedModelFileTest() { + withModelPath("src/test/resources") { + withPathSelectorType(PathSelectorType.ML_SELECTOR) { + MultilayerPerceptronPredictor(modelPath = "corrupted_nn.json") + assertEquals(PathSelectorType.INHERITORS_SELECTOR, UtSettings.pathSelectorType) + } + } + } + + @Test + fun emptyModelFileTest() { + withModelPath("src/test/resources") { + withPathSelectorType(PathSelectorType.ML_SELECTOR) { + MultilayerPerceptronPredictor(modelPath = "empty_nn.json") + assertEquals(PathSelectorType.INHERITORS_SELECTOR, UtSettings.pathSelectorType) + } + } + } + + @Test + fun corruptedScalerTest() { + withModelPath("src/test/resources") { + withPathSelectorType(PathSelectorType.ML_SELECTOR) { + MultilayerPerceptronPredictor(scalerPath = "corrupted_scaler.txt") + assertEquals(PathSelectorType.INHERITORS_SELECTOR, UtSettings.pathSelectorType) + } + } + } +} \ No newline at end of file diff --git a/utbot-analytics/src/test/kotlin/org/utbot/predictors/NNStateRewardPredictorTest.kt b/utbot-analytics/src/test/kotlin/org/utbot/predictors/NNStateRewardPredictorTest.kt deleted file mode 100644 index 42dd3bac40..0000000000 --- a/utbot-analytics/src/test/kotlin/org/utbot/predictors/NNStateRewardPredictorTest.kt +++ /dev/null @@ -1,86 +0,0 @@ -package org.utbot.predictors - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.utbot.examples.withPathSelectorType -import org.utbot.analytics.StateRewardPredictor -import org.utbot.examples.withRewardModelPath -import org.utbot.framework.PathSelectorType -import org.utbot.framework.UtSettings -import kotlin.system.measureNanoTime - -class NNStateRewardPredictorTest { - @Test - fun simpleTest() { - withRewardModelPath("src/test/resources") { - val pred = NNStateRewardPredictorBase() - - val features = listOf(0.0, 0.0) - - assertEquals(5.0, pred.predict(features)) - } - } - - @Disabled("Just to see the performance of predictors") - @Test - fun performanceTest() { - val features = (1..13).map { 1.0 }.toList() - withRewardModelPath("models\\test\\0") { - val averageTime = calcAverageTimeForModelPredict(::NNStateRewardPredictorBase, 100, features) - println(averageTime) - } - - - withRewardModelPath("models") { - val averageTime = calcAverageTimeForModelPredict(::StateRewardPredictorTorch, 100, features) - println(averageTime) - } - } - - private fun calcAverageTimeForModelPredict( - model: () -> StateRewardPredictor, - iterations: Int, - features: List - ): Double { - val pred = model() - - (1..iterations).map { - pred.predict(features) - } - - return (1..iterations) - .map { measureNanoTime { pred.predict(features) } } - .average() - } - - @Test - fun corruptedModelFileTest() { - withRewardModelPath("src/test/resources") { - withPathSelectorType(PathSelectorType.NN_REWARD_GUIDED_SELECTOR) { - NNStateRewardPredictorBase(modelPath = "corrupted_nn.json") - assertEquals(PathSelectorType.INHERITORS_SELECTOR, UtSettings.pathSelectorType) - } - } - } - - @Test - fun emptyModelFileTest() { - withRewardModelPath("src/test/resources") { - withPathSelectorType(PathSelectorType.NN_REWARD_GUIDED_SELECTOR) { - NNStateRewardPredictorBase(modelPath = "empty_nn.json") - assertEquals(PathSelectorType.INHERITORS_SELECTOR, UtSettings.pathSelectorType) - } - } - } - - @Test - fun corruptedScalerTest() { - withRewardModelPath("src/test/resources") { - withPathSelectorType(PathSelectorType.NN_REWARD_GUIDED_SELECTOR) { - NNStateRewardPredictorBase(scalerPath = "corrupted_scaler.txt") - assertEquals(PathSelectorType.INHERITORS_SELECTOR, UtSettings.pathSelectorType) - } - } - } -} \ No newline at end of file diff --git a/utbot-android-studio/build.gradle.kts b/utbot-android-studio/build.gradle.kts new file mode 100644 index 0000000000..96197ffc94 --- /dev/null +++ b/utbot-android-studio/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + id("org.jetbrains.intellij") version "1.13.1" +} + +intellij { + /* + The list of Android Studio releases can be found here https://plugins.jetbrains.com/docs/intellij/android-studio-releases-list.html + For each release a compatible Intellij Idea version can be found in the right column. Specify it in "version.set("...") + + NOTE!!! + We use Android Studio Chipmunk (2021.2.1), although Android Studio Dolphin (2021.3.1) has been released. + The reason is that a version of Kotlin plugin compatible with Android Studio is required. + The list of Kotlin plugin releases can be found here https://plugins.jetbrains.com/plugin/6954-kotlin/versions/stable + The last compatible with AS plugin version on 19 Oct 2022 is Kotlin 212-1.7.10-release-333-AS5457.46, + it is not compatible with Dolphin release (https://plugins.jetbrains.com/plugin/6954-kotlin/versions/stable/193255). + */ + + val androidPlugins = listOf("org.jetbrains.android") + + val jvmPlugins = listOf( + "java", + "org.jetbrains.kotlin:212-1.7.10-release-333-AS5457.46" + ) + + plugins.set(jvmPlugins + androidPlugins) + + version.set("212.5712.43") + type.set("IC") +} + +project.tasks.asMap["runIde"]?.enabled = false + +tasks { + compileKotlin { + kotlinOptions { + jvmTarget = "11" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class") + allWarningsAsErrors = false + } + } + + java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} \ No newline at end of file diff --git a/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifier.kt b/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifier.kt new file mode 100644 index 0000000000..4b5665d7e2 --- /dev/null +++ b/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifier.kt @@ -0,0 +1,117 @@ +package org.androidstudio.plugin.util + +import com.android.tools.idea.gradle.AndroidGradleJavaProjectModelModifier +import com.android.tools.idea.gradle.dsl.api.GradleBuildModel +import com.android.tools.idea.gradle.dsl.api.dependencies.ArtifactDependencySpec +import com.android.tools.idea.gradle.dsl.api.dependencies.CommonConfigurationNames +import com.android.tools.idea.gradle.project.sync.GradleSyncInvoker +import com.android.tools.idea.gradle.project.sync.GradleSyncListener +import com.android.tools.idea.gradle.project.sync.idea.GradleSyncExecutor +import com.android.tools.idea.gradle.util.GradleUtil +import com.android.tools.idea.project.AndroidProjectInfo +import com.android.tools.idea.projectsystem.TestArtifactSearchScopes +import com.google.wireless.android.sdk.stats.GradleSyncStats +import com.intellij.ide.plugins.PluginManager +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.command.undo.BasicUndoableAction +import com.intellij.openapi.command.undo.UndoManager +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ExternalLibraryDescriptor +import com.intellij.openapi.roots.libraries.Library +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.pom.java.LanguageLevel +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.concurrency.AsyncPromise +import org.jetbrains.concurrency.Promise +import org.jetbrains.concurrency.rejectedPromise + +class UtAndroidGradleJavaProjectModelModifier : AndroidGradleJavaProjectModelModifier() { + override fun addExternalLibraryDependency( + modules: Collection, + descriptor: ExternalLibraryDescriptor, + scope: DependencyScope + ): Promise? { + val module = ContainerUtil.getFirstItem(modules) ?: return null + val dependencySpec = ArtifactDependencySpec.create(descriptor.libraryArtifactId, descriptor.libraryGroupId, descriptor.preferredVersion) + return addExternalLibraryDependency(module, dependencySpec, scope) + } + + private fun addExternalLibraryDependency( + module: Module, + dependencySpec: ArtifactDependencySpec, + scope: DependencyScope, + ): Promise? { + val project = module.project + val openedFile = FileEditorManagerEx.getInstanceEx(project).currentFile + val buildModelsToUpdate: MutableList = ArrayList() + + val buildModel = GradleBuildModel.get(module) ?: return null + val configurationName = getConfigurationName(module, scope, openedFile) + val dependencies = buildModel.dependencies() + dependencies.addArtifact(configurationName, dependencySpec) + buildModelsToUpdate.add(buildModel) + + WriteCommandAction.writeCommandAction(project).withName("Add Gradle Library Dependency").run { + buildModelsToUpdate.forEach { buildModel -> buildModel.applyChanges() } + registerUndoAction(project) + } + + return doAndroidGradleSync(project, GradleSyncStats.Trigger.TRIGGER_MODIFIER_ADD_LIBRARY_DEPENDENCY) + } + + private fun getConfigurationName(module: Module, scope: DependencyScope, openedFile: VirtualFile?): String = + GradleUtil.mapConfigurationName( + getLegacyConfigurationName(module, scope, openedFile), + GradleUtil.getAndroidGradleModelVersionInUse(module), + false + ) + + private fun getLegacyConfigurationName( + module: Module, + scope: DependencyScope, + openedFile: VirtualFile? + ): String { + if (!scope.isForProductionCompile) { + val testScopes = TestArtifactSearchScopes.getInstance(module) + if (testScopes != null && openedFile != null) { + return if (testScopes.isAndroidTestSource(openedFile)) CommonConfigurationNames.ANDROID_TEST_COMPILE else CommonConfigurationNames.TEST_COMPILE + } + } + return CommonConfigurationNames.COMPILE + } + + private fun registerUndoAction(project: Project) { + UndoManager.getInstance(project).undoableActionPerformed(object : BasicUndoableAction() { + + override fun undo() { + doAndroidGradleSync(project, GradleSyncStats.Trigger.TRIGGER_MODIFIER_ACTION_UNDONE) + + } + + override fun redo() { + doAndroidGradleSync(project, GradleSyncStats.Trigger.TRIGGER_MODIFIER_ACTION_REDONE) + } + }) + } + + private fun doAndroidGradleSync(project: Project, trigger: GradleSyncStats.Trigger): AsyncPromise { + val promise = AsyncPromise() + val request = GradleSyncInvoker.Request(trigger) + val listener = object : GradleSyncListener { + override fun syncSucceeded(project: Project) { + promise.setResult(null) + } + + override fun syncFailed(project: Project, errorMessage: String) { + promise.setError(errorMessage) + } + } + GradleSyncExecutor(project).sync(request, listener) + + return promise + } +} \ No newline at end of file diff --git a/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifierWrapper.kt b/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifierWrapper.kt new file mode 100644 index 0000000000..4eba5a7db9 --- /dev/null +++ b/utbot-android-studio/src/main/kotlin/org/androidstudio/plugin/util/UtAndroidGradleJavaProjectModelModifierWrapper.kt @@ -0,0 +1,63 @@ +package org.androidstudio.plugin.util + +import com.android.tools.idea.model.AndroidModel +import com.intellij.facet.ProjectFacetManager +import com.intellij.ide.plugins.PluginManager +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ExternalLibraryDescriptor +import com.intellij.openapi.roots.impl.IdeaProjectModelModifier +import com.intellij.openapi.roots.libraries.Library +import com.intellij.pom.java.LanguageLevel +import org.jetbrains.android.facet.AndroidFacet +import org.jetbrains.concurrency.Promise + +/* +NOTE: this is a wrapper for [UtAndroidGradleJavaProjectModelModifier]. +The purpose of this wrapper is to avoid inheritance of [AndroidGradleJavaProjectModelModifier] +because it leads to crashes when Android plugin is disabled. + */ +class UtAndroidGradleJavaProjectModelModifierWrapper(val project: Project): IdeaProjectModelModifier(project) { + + override fun addExternalLibraryDependency( + modules: Collection, + descriptor: ExternalLibraryDescriptor, + scope: DependencyScope + ): Promise? { + if (!isAndroidGradleProject(project)) { + return null + } + + // NOTE: we use such DependencyScope to obtain `implementation`, not `testImplementation` + // to deal with androidTest modules (there is no way to add `androidTestImplementation` additionally. + return UtAndroidGradleJavaProjectModelModifier().addExternalLibraryDependency(modules, descriptor, DependencyScope.COMPILE) + } + + override fun addModuleDependency( + from: Module, + to: Module, + scope: DependencyScope, + exported: Boolean + ): Promise? = null + + override fun addLibraryDependency( + from: Module, + library: Library, + scope: DependencyScope, + exported: Boolean + ): Promise? = null + + override fun changeLanguageLevel(module: Module, level: LanguageLevel): Promise? = null + + private fun isAndroidGradleProject(project: Project): Boolean { + val pluginId = PluginId.findId("org.jetbrains.android") + if (pluginId == null || PluginManager.getInstance().findEnabledPlugin(pluginId) == null) { + return false + } + + return ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID).stream() + .anyMatch { AndroidModel.isRequired(it) } + } +} \ No newline at end of file diff --git a/utbot-api/build.gradle b/utbot-api/build.gradle deleted file mode 100644 index e99e0d7078..0000000000 --- a/utbot-api/build.gradle +++ /dev/null @@ -1,11 +0,0 @@ -plugins { - id "com.github.johnrengelman.shadow" version "6.1.0" -} - -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" - -shadowJar { - configurations = [project.configurations.compileClasspath] - archiveClassifier.set('') - minimize() -} \ No newline at end of file diff --git a/utbot-api/build.gradle.kts b/utbot-api/build.gradle.kts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-api/src/main/java/org/utbot/api/exception/UtMockAssumptionViolatedException.java b/utbot-api/src/main/java/org/utbot/api/exception/UtMockAssumptionViolatedException.java new file mode 100644 index 0000000000..25a4b58691 --- /dev/null +++ b/utbot-api/src/main/java/org/utbot/api/exception/UtMockAssumptionViolatedException.java @@ -0,0 +1,8 @@ +package org.utbot.api.exception; + +public class UtMockAssumptionViolatedException extends RuntimeException { + @Override + public String getMessage() { + return "UtMock assumption violated"; + } +} diff --git a/utbot-api/src/main/java/org/utbot/api/mock/UtMock.java b/utbot-api/src/main/java/org/utbot/api/mock/UtMock.java index c7f7b2215b..42e25a21f1 100644 --- a/utbot-api/src/main/java/org/utbot/api/mock/UtMock.java +++ b/utbot-api/src/main/java/org/utbot/api/mock/UtMock.java @@ -1,5 +1,7 @@ package org.utbot.api.mock; +import org.utbot.api.exception.UtMockAssumptionViolatedException; + public class UtMock { public static T makeSymbolic() { return makeSymbolic(false); @@ -14,13 +16,16 @@ public static T makeSymbolic(boolean isNullable) { public static void assume(boolean predicate) { // to use compilers checks, i.e. for possible NPE if (!predicate) { - throw new RuntimeException(); + throw new UtMockAssumptionViolatedException(); } } @SuppressWarnings("unused") public static void assumeOrExecuteConcretely(boolean predicate) { // In oppose to assume, we don't have predicate check here - // to avoid RuntimeException during concrete execution + // to avoid UtMockAssumptionViolatedException during concrete execution } + + @SuppressWarnings("unused") + public static void disableClassCastExceptionCheck(Object object) {} } \ No newline at end of file diff --git a/utbot-cli-go/build.gradle b/utbot-cli-go/build.gradle new file mode 100644 index 0000000000..1f0cf22758 --- /dev/null +++ b/utbot-cli-go/build.gradle @@ -0,0 +1,77 @@ +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17 + freeCompilerArgs += ["-Xallow-result-return-type", "-Xsam-conversions=class"] + } +} + +tasks.withType(JavaCompile) { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_17 +} + +configurations { + fetchInstrumentationJar +} + +dependencies { + implementation project(':utbot-framework') + implementation project(':utbot-cli') + implementation project(':utbot-go') + + // Without this dependency testng tests do not run. + implementation group: 'com.beust', name: 'jcommander', version: '1.48' + implementation group: 'org.junit.platform', name: 'junit-platform-console-standalone', version: junit4PlatformVersion + implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlinLoggingVersion + implementation group: 'com.github.ajalt.clikt', name: 'clikt', version: cliktVersion + implementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: junit5Version + implementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junit5Version + implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j2Version + implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4j2Version + implementation group: 'org.jacoco', name: 'org.jacoco.report', version: jacocoVersion + //noinspection GroovyAssignabilityCheck + fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration: 'instrumentationArchive') + + implementation 'com.beust:klaxon:5.5' // to read and write JSON +} + +processResources { + from(configurations.fetchInstrumentationJar) { + into "lib" + } +} + +task createProperties(dependsOn: processResources) { + doLast { + new File("$buildDir/resources/main/version.properties").withWriter { w -> + Properties properties = new Properties() + //noinspection GroovyAssignabilityCheck + properties['version'] = project.version.toString() + properties.store w, null + } + } +} + +classes { + dependsOn createProperties +} + +jar { + manifest { + attributes 'Main-Class': 'org.utbot.cli.go.ApplicationKt' + attributes 'Bundle-SymbolicName': 'org.utbot.cli.go' + attributes 'Bundle-Version': "${project.version}" + attributes 'Implementation-Title': 'UtBot Go CLI' + attributes 'JAR-Type': 'Fat JAR' + } + + archiveVersion.set(project.version as String) + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + diff --git a/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/Application.kt b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/Application.kt new file mode 100644 index 0000000000..2492442c23 --- /dev/null +++ b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/Application.kt @@ -0,0 +1,36 @@ +package org.utbot.cli.go + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.subcommands +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.versionOption +import com.github.ajalt.clikt.parameters.types.enum +import org.slf4j.event.Level +import org.utbot.cli.getVersion +import org.utbot.cli.setVerbosity +import kotlin.system.exitProcess +import org.utbot.cli.go.commands.GenerateGoTestsCommand +import org.utbot.cli.go.commands.RunGoTestsCommand + +class UtBotCli : CliktCommand(name = "UnitTestBot Go Command Line Interface") { + private val verbosity by option("--verbosity", help = "Changes verbosity level, case insensitive") + .enum(ignoreCase = true) + .default(Level.INFO) + + override fun run() = setVerbosity(verbosity) + + init { + versionOption(getVersion()) + } +} + +fun main(args: Array) = try { + UtBotCli().subcommands( + GenerateGoTestsCommand(), + RunGoTestsCommand(), + ).main(args) +} catch (ex: Throwable) { + ex.printStackTrace() + exitProcess(1) +} \ No newline at end of file diff --git a/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/commands/CoverageJsonStructs.kt b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/commands/CoverageJsonStructs.kt new file mode 100644 index 0000000000..55c97cb554 --- /dev/null +++ b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/commands/CoverageJsonStructs.kt @@ -0,0 +1,13 @@ +package org.utbot.cli.go.commands + +import com.beust.klaxon.Json + +internal data class Position(@Json(index = 1) val line: Int, @Json(index = 2) val column: Int) + +internal data class CodeRegion(@Json(index = 1) val start: Position, @Json(index = 2) val end: Position) + +internal data class CoveredSourceFile( + @Json(index = 1) val sourceFileName: String, + @Json(index = 2) val covered: List, + @Json(index = 3) val uncovered: List +) \ No newline at end of file diff --git a/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/commands/GenerateGoTestsCommand.kt b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/commands/GenerateGoTestsCommand.kt new file mode 100644 index 0000000000..b836808e7b --- /dev/null +++ b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/commands/GenerateGoTestsCommand.kt @@ -0,0 +1,154 @@ +package org.utbot.cli.go.commands + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.* +import com.github.ajalt.clikt.parameters.types.int +import com.github.ajalt.clikt.parameters.types.long +import mu.KotlinLogging +import org.utbot.cli.go.logic.CliGoUtTestsGenerationController +import org.utbot.cli.go.util.durationInMillis +import org.utbot.cli.go.util.now +import org.utbot.cli.go.util.toAbsolutePath +import org.utbot.go.logic.GoUtTestsGenerationConfig +import org.utbot.go.logic.TestsGenerationMode +import java.nio.file.Files +import java.nio.file.Paths + +private val logger = KotlinLogging.logger {} + +class GenerateGoTestsCommand : + CliktCommand(name = "generateGo", help = "Generates tests for the specified Go source file") { + + private val sourceFile: String by option( + "-s", "--source", + help = "Specifies Go source file to generate tests for" + ) + .required() + .check("Must exist and ends with *.go suffix") { + it.endsWith(".go") && Files.exists(Paths.get(it)) + } + + private val selectedFunctionNames: List by option( + "-f", "--function", + help = StringBuilder() + .append("Specifies function name to generate tests for. ") + .append("Can be used multiple times to select multiple functions at the same time.") + .toString() + ) + .multiple() + + private val selectedMethodNames: List by option( + "-m", "--method", + help = StringBuilder() + .append("Specifies method name to generate tests for. ") + .append("Can be used multiple times to select multiple methods at the same time.") + .toString() + ) + .multiple() + + private val goExecutablePath: String by option( + "-go", + help = "Specifies path to Go executable. For example, it could be [/usr/local/go/bin/go] for some systems" + ) + .required() // TODO: attempt to find it if not specified + + private val gopath: String by option( + "-gopath", + help = buildString { + appendLine("Specifies path the location of your workspace.") + appendLine("It defaults to a directory named go inside your home directory, so \$HOME/go on Unix, \$home/go on Plan 9, and %USERPROFILE%\\go (usually C:\\Users\\YourName\\go) on Windows.") + } + ).required() // TODO: attempt to find it if not specified + + private val numberOfFuzzingProcesses: Int by option( + "-parallel", + help = "The number of fuzzing processes running at once, default 8." + ) + .int() + .default(8) + .check("Must be positive") { it > 0 } + + private val eachFunctionExecutionTimeoutMillis: Long by option( + "-et", "--each-execution-timeout", + help = StringBuilder() + .append("Specifies a timeout in milliseconds for each fuzzed function execution.") + .append("Default is ${GoUtTestsGenerationConfig.DEFAULT_EACH_EXECUTION_TIMEOUT_MILLIS} ms") + .toString() + ) + .long() + .default(GoUtTestsGenerationConfig.DEFAULT_EACH_EXECUTION_TIMEOUT_MILLIS) + .check("Must be positive") { it > 0 } + + private val allFunctionExecutionTimeoutMillis: Long by option( + "-at", "--all-execution-timeout", + help = StringBuilder() + .append("Specifies a timeout in milliseconds for all fuzzed function execution.") + .append("Default is ${GoUtTestsGenerationConfig.DEFAULT_ALL_EXECUTION_TIMEOUT_MILLIS} ms") + .toString() + ) + .long() + .default(GoUtTestsGenerationConfig.DEFAULT_ALL_EXECUTION_TIMEOUT_MILLIS) + .check("Must be positive") { it > 0 } + + private val printToStdOut: Boolean by option( + "-p", + "--print-test", + help = "Specifies whether a test should be printed out to StdOut. Is disabled by default" + ) + .flag(default = false) + + private val overwriteTestFiles: Boolean by option( + "-w", + "--overwrite", + help = "Specifies whether to overwrite the output test file if it already exists. Is disabled by default" + ) + .flag(default = false) + + private val fuzzingMode: Boolean by option( + "-fm", + "--fuzzing-mode", + help = "Stop test generation when a panic or error occurs (only one test will be generated for one of these cases)" + ) + .flag(default = false) + + override fun run() { + if (selectedFunctionNames.isEmpty() && selectedMethodNames.isEmpty()) { + throw IllegalArgumentException("Functions or methods must be passed") + } + + val sourceFileAbsolutePath = sourceFile.toAbsolutePath() + val goExecutableAbsolutePath = goExecutablePath.toAbsolutePath() + val gopathAbsolutePath = gopath.toAbsolutePath() + val mode = if (fuzzingMode) { + TestsGenerationMode.FUZZING_MODE + } else { + TestsGenerationMode.DEFAULT + } + + val testsGenerationStarted = now() + logger.info { "Test file generation for [$sourceFile] - started" } + try { + CliGoUtTestsGenerationController( + printToStdOut = printToStdOut, + overwriteTestFiles = overwriteTestFiles + ).generateTests( + mapOf(sourceFileAbsolutePath to selectedFunctionNames), + mapOf(sourceFileAbsolutePath to selectedMethodNames), + GoUtTestsGenerationConfig( + goExecutableAbsolutePath, + gopathAbsolutePath, + numberOfFuzzingProcesses, + mode, + eachFunctionExecutionTimeoutMillis, + allFunctionExecutionTimeoutMillis + ), + ) + } catch (t: Throwable) { + logger.error { "An error has occurred while generating test for snippet $sourceFile: $t" } + throw t + } finally { + val duration = durationInMillis(testsGenerationStarted) + logger.info { "Test file generation for [$sourceFile] - completed in [$duration] (ms)" } + } + } +} \ No newline at end of file diff --git a/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/commands/RunGoTestsCommand.kt b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/commands/RunGoTestsCommand.kt new file mode 100644 index 0000000000..b5e5847664 --- /dev/null +++ b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/commands/RunGoTestsCommand.kt @@ -0,0 +1,223 @@ +package org.utbot.cli.go.commands + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.* +import com.github.ajalt.clikt.parameters.types.choice +import mu.KotlinLogging +import org.utbot.cli.go.util.* +import org.utbot.go.util.convertObjectToJsonString +import java.io.File + +private val logger = KotlinLogging.logger {} + +class RunGoTestsCommand : CliktCommand(name = "runGo", help = "Runs tests for the specified Go package") { + + private val packageDirectory: String by option( + "-p", "--package", + help = "Specifies Go package to run tests for" + ) + .required() + .check("Must exist and be directory") { + File(it).let { file -> file.exists() && file.isDirectory } + } + + private val goExecutablePath: String by option( + "-go", "--go-path", + help = "Specifies path to Go executable. For example, it could be [/usr/local/go/bin/go] for some systems" + ) + .required() // TODO: attempt to find it if not specified + + private val verbose: Boolean by option( + "-v", "--verbose", + help = "Specifies whether an output should be verbose. Is disabled by default" + ) + .flag(default = false) + + private val json: Boolean by option( + "-j", "--json", + help = "Specifies whether an output should be in JSON format. Is disabled by default" + ) + .flag(default = false) + + private val output: String? by option( + "-o", "--output", + help = "Specifies output file for tests run report. Prints to StdOut by default" + ) + + private enum class CoverageMode(val displayName: String) { + REGIONS_HTML("html"), PERCENTS_BY_FUNCS("func"), REGIONS_JSON("json"); + + override fun toString(): String = displayName + + val fileExtensionValidator: (String) -> Boolean + get() = when (this) { + REGIONS_HTML -> { + { it.substringAfterLast('.') == "html" } + } + + REGIONS_JSON -> { + { it.substringAfterLast('.') == "json" } + } + + PERCENTS_BY_FUNCS -> { + { true } + } + } + } + + private val coverageMode: CoverageMode? by option( + "-cov-mode", "--coverage-mode", + help = StringBuilder() + .append("Specifies whether a test coverage report should be generated and defines its mode. ") + .append("Coverage report generation is disabled by default") + .toString() + ) + .choice( + CoverageMode.REGIONS_HTML.toString() to CoverageMode.REGIONS_HTML, + CoverageMode.PERCENTS_BY_FUNCS.toString() to CoverageMode.PERCENTS_BY_FUNCS, + CoverageMode.REGIONS_JSON.toString() to CoverageMode.REGIONS_JSON, + ) + .check( + StringBuilder() + .append("Test coverage report output file must be set ") + .append("and have an extension that matches the coverage mode") + .toString() + ) { mode -> + coverageOutput?.let { mode.fileExtensionValidator(it) } ?: false + } + + private val coverageOutput: String? by option( + "-cov-out", "--coverage-output", + help = "Specifies output file for test coverage report. Required if [--coverage-mode] is set" + ) + .check("Test coverage report mode must be specified") { + coverageMode != null + } + + override fun run() { + val runningTestsStarted = now() + try { + logger.debug { "Running tests for [$packageDirectory] - started" } + + /* run tests */ + + val packageDirectoryFile = File(packageDirectory).canonicalFile + + val coverProfileFile = if (coverageMode != null) { + createFile(createCoverProfileFileName()) + } else { + null + } + + try { + val runGoTestCommand = mutableListOf( + goExecutablePath.toAbsolutePath().toString(), + "test", + "./" + ) + if (verbose) { + runGoTestCommand.add("-v") + } + if (json) { + runGoTestCommand.add("-json") + } + if (coverageMode != null) { + runGoTestCommand.add("-coverprofile") + runGoTestCommand.add(coverProfileFile!!.canonicalPath) + } + + val outputStream = if (output == null) { + System.out + } else { + createFile(output!!).outputStream() + } + executeCommandAndRedirectStdoutOrFail(runGoTestCommand, packageDirectoryFile, outputStream) + + /* generate coverage report */ + + val coverageOutputFile = coverageOutput?.let { createFile(it) } ?: return + + when (coverageMode) { + null -> { + return + } + + CoverageMode.REGIONS_HTML, CoverageMode.PERCENTS_BY_FUNCS -> { + val runToolCoverCommand = mutableListOf( + "go", + "tool", + "cover", + "-${coverageMode!!.displayName}", + coverProfileFile!!.canonicalPath, + "-o", + coverageOutputFile.canonicalPath + ) + executeCommandAndRedirectStdoutOrFail(runToolCoverCommand, packageDirectoryFile) + } + + CoverageMode.REGIONS_JSON -> { + val coveredSourceFiles = parseCoverProfile(coverProfileFile!!) + val jsonCoverage = convertObjectToJsonString(coveredSourceFiles) + coverageOutputFile.writeText(jsonCoverage) + } + } + } finally { + coverProfileFile?.delete() + } + } catch (t: Throwable) { + logger.error { "An error has occurred while running tests for [$packageDirectory]: $t" } + throw t + } finally { + val duration = durationInMillis(runningTestsStarted) + logger.debug { "Running tests for [$packageDirectory] - completed in [$duration] (ms)" } + } + } + + private fun createCoverProfileFileName(): String { + return "ut_go_cover_profile.out" + } + + private fun parseCoverProfile(coverProfileFile: File): List { + data class CoverageRegions( + val covered: MutableList, + val uncovered: MutableList + ) + + val coverageRegionsBySourceFilesNames = mutableMapOf() + + coverProfileFile.readLines().asSequence() + .drop(1) // drop "mode" value + .forEach { fullLine -> + val (sourceFileFullName, coverageInfoLine) = fullLine.split(":", limit = 2) + val sourceFileName = sourceFileFullName.substringAfterLast("/") + val (regionString, _, countString) = coverageInfoLine.split(" ", limit = 3) + + fun parsePosition(positionString: String): Position { + val (lineNumber, columnNumber) = positionString.split(".", limit = 2).asSequence() + .map { it.toInt() } + .toList() + return Position(lineNumber, columnNumber) + } + val (startString, endString) = regionString.split(",", limit = 2) + val region = CodeRegion(parsePosition(startString), parsePosition(endString)) + + val regions = coverageRegionsBySourceFilesNames.getOrPut(sourceFileName) { + CoverageRegions( + mutableListOf(), + mutableListOf() + ) + } + // it is called "count" in docs, but in reality it is like boolean for covered / uncovered + val count = countString.toInt() + if (count == 0) { + regions.uncovered.add(region) + } else { + regions.covered.add(region) + } + } + + return coverageRegionsBySourceFilesNames.map { (sourceFileName, regions) -> + CoveredSourceFile(sourceFileName, regions.covered, regions.uncovered) + } + } +} \ No newline at end of file diff --git a/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/logic/CliGoUtTestsGenerationController.kt b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/logic/CliGoUtTestsGenerationController.kt new file mode 100644 index 0000000000..e10f3e9754 --- /dev/null +++ b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/logic/CliGoUtTestsGenerationController.kt @@ -0,0 +1,138 @@ +package org.utbot.cli.go.logic + +import mu.KotlinLogging +import org.utbot.cli.go.util.durationInMillis +import org.utbot.cli.go.util.now +import org.utbot.go.api.GoUtFile +import org.utbot.go.api.GoUtFunction +import org.utbot.go.api.GoUtFuzzedFunctionTestCase +import org.utbot.go.gocodeanalyzer.GoSourceCodeAnalyzer +import org.utbot.go.logic.AbstractGoUtTestsGenerationController +import java.io.File +import java.nio.file.Path +import java.time.LocalDateTime + +private val logger = KotlinLogging.logger {} + +class CliGoUtTestsGenerationController( + private val printToStdOut: Boolean, + private val overwriteTestFiles: Boolean +) : AbstractGoUtTestsGenerationController() { + + private lateinit var currentStageStarted: LocalDateTime + + override fun onSourceCodeAnalysisStart( + targetFunctionNamesBySourceFiles: Map>, + targetMethodNamesBySourceFiles: Map> + ): Boolean { + currentStageStarted = now() + logger.debug { "Source code analysis - started" } + + return true + } + + override fun onSourceCodeAnalysisFinished( + analysisResults: Map + ): Boolean { + val stageDuration = durationInMillis(currentStageStarted) + logger.debug { "Source code analysis - completed in [$stageDuration] (ms)" } + + return handleMissingSelectedFunctions(analysisResults) + } + + override fun onPackageInstrumentationStart(): Boolean { + currentStageStarted = now() + logger.debug { "Package instrumentation - started" } + + return true + } + + override fun onPackageInstrumentationFinished(): Boolean { + val stageDuration = durationInMillis(currentStageStarted) + logger.debug { "Package instrumentation - completed in [$stageDuration] (ms)" } + + return true + } + + override fun onTestCasesGenerationForGoSourceFileFunctionsStart( + sourceFile: GoUtFile, + functions: List + ): Boolean { + currentStageStarted = now() + logger.debug { "Test cases generation for [${sourceFile.fileName}] - started" } + + return true + } + + override fun onTestCasesGenerationForGoSourceFileFunctionsFinished( + sourceFile: GoUtFile, + testCases: List + ): Boolean { + val stageDuration = durationInMillis(currentStageStarted) + logger.debug { + "Test cases generation for [${sourceFile.fileName}] functions - completed in [$stageDuration] (ms)" + } + + return true + } + + override fun onTestCasesFileCodeGenerationStart( + sourceFile: GoUtFile, + testCases: List + ): Boolean { + currentStageStarted = now() + logger.debug { "Test cases file code generation for [${sourceFile.fileName}] - started" } + + return true + } + + override fun onTestCasesFileCodeGenerationFinished(sourceFile: GoUtFile, generatedTestsFileCode: String): Boolean { + if (printToStdOut) { + logger.info { generatedTestsFileCode } + return true + } + writeGeneratedCodeToFile(sourceFile, generatedTestsFileCode) + + val stageDuration = durationInMillis(currentStageStarted) + logger.debug { + "Test cases file code generation for [${sourceFile.fileName}] functions - completed in [$stageDuration] (ms)" + } + + return true + } + + private fun handleMissingSelectedFunctions( + analysisResults: Map + ): Boolean { + val missingSelectedFunctionsListMessage = generateMissingSelectedFunctionsListMessage(analysisResults) + val okSelectedFunctionsArePresent = + analysisResults.any { (_, analysisResult) -> analysisResult.functions.isNotEmpty() } + + if (missingSelectedFunctionsListMessage != null) { + logger.warn { "Some selected functions were skipped during source code analysis.$missingSelectedFunctionsListMessage" } + } + if (!okSelectedFunctionsArePresent) { + throw Exception("Nothing to process. No functions were provided") + } + + return true + } + + private fun writeGeneratedCodeToFile(sourceFile: GoUtFile, generatedTestsFileCode: String) { + val testsFileNameWithExtension = createTestsFileNameWithExtension(sourceFile) + val testFile = File(sourceFile.absoluteDirectoryPath).resolve(testsFileNameWithExtension) + if (testFile.exists()) { + val alreadyExistsMessage = "File [${testFile.absolutePath}] already exists" + if (overwriteTestFiles) { + logger.warn { "$alreadyExistsMessage: it will be overwritten" } + } else { + logger.warn { "$alreadyExistsMessage: skipping test generation for [${sourceFile.fileName}]" } + return + } + } + testFile.writeText(generatedTestsFileCode) + } + + private fun createTestsFileNameWithExtension(sourceFile: GoUtFile) = + sourceFile.fileNameWithoutExtension + "_go_ut_test.go" +} \ No newline at end of file diff --git a/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/util/FileUtils.kt b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/util/FileUtils.kt new file mode 100644 index 0000000000..b3d072fd05 --- /dev/null +++ b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/util/FileUtils.kt @@ -0,0 +1,16 @@ +package org.utbot.cli.go.util + +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths + +fun String.toAbsolutePath(): Path = Paths.get(this).toAbsolutePath() + +fun createFile(filePath: String): File = createFile(File(filePath).canonicalFile) + +fun createFile(file: File): File { + return file.also { + it.parentFile?.mkdirs() + it.createNewFile() + } +} \ No newline at end of file diff --git a/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/util/IoUtils.kt b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/util/IoUtils.kt new file mode 100644 index 0000000000..220b2f0e5f --- /dev/null +++ b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/util/IoUtils.kt @@ -0,0 +1,12 @@ +package org.utbot.cli.go.util + +import java.io.InputStream +import java.io.OutputStream + +fun copy(from: InputStream, to: OutputStream?) { + val buffer = ByteArray(10240) + var len: Int + while (from.read(buffer).also { len = it } != -1) { + to?.write(buffer, 0, len) + } +} \ No newline at end of file diff --git a/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/util/ProcessExecutionUtils.kt b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/util/ProcessExecutionUtils.kt new file mode 100644 index 0000000000..3dbeebd99b --- /dev/null +++ b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/util/ProcessExecutionUtils.kt @@ -0,0 +1,33 @@ +package org.utbot.cli.go.util + +import java.io.File +import java.io.InputStreamReader +import java.io.OutputStream + +fun executeCommandAndRedirectStdoutOrFail( + command: List, + workingDirectory: File? = null, + redirectStdoutToStream: OutputStream? = null // if null, stdout of process is suppressed +) { + val executedProcess = runCatching { + val process = ProcessBuilder(command) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectErrorStream(true) + .directory(workingDirectory) + .start() + copy(process.inputStream, redirectStdoutToStream) + process.waitFor() + process + }.getOrElse { + throw RuntimeException( + "Execution of [${command.joinToString(separator = " ")}] failed with throwable: $it" + ) + } + val exitCode = executedProcess.exitValue() + if (exitCode != 0) { + val processOutput = InputStreamReader(executedProcess.inputStream).readText() + throw RuntimeException( + "Execution of [${command.joinToString(separator = " ")}] failed with non-zero exit code = $exitCode:\n$processOutput" + ) + } +} \ No newline at end of file diff --git a/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/util/TimeMeasureUtils.kt b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/util/TimeMeasureUtils.kt new file mode 100644 index 0000000000..c72601574b --- /dev/null +++ b/utbot-cli-go/src/main/kotlin/org/utbot/cli/go/util/TimeMeasureUtils.kt @@ -0,0 +1,8 @@ +package org.utbot.cli.go.util + +import java.time.LocalDateTime +import java.time.temporal.ChronoUnit + +fun now(): LocalDateTime = LocalDateTime.now() + +fun durationInMillis(started: LocalDateTime): Long = ChronoUnit.MILLIS.between(started, now()) \ No newline at end of file diff --git a/utbot-cli-go/src/main/resources/log4j2.xml b/utbot-cli-go/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..3d6ee82bcf --- /dev/null +++ b/utbot-cli-go/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utbot-cli-go/src/main/resources/version.properties b/utbot-cli-go/src/main/resources/version.properties new file mode 100644 index 0000000000..956d6e337a --- /dev/null +++ b/utbot-cli-go/src/main/resources/version.properties @@ -0,0 +1,2 @@ +#to be populated during the build task +version=N/A \ No newline at end of file diff --git a/utbot-cli-js/Dockerfile b/utbot-cli-js/Dockerfile new file mode 100644 index 0000000000..3dcc611e25 --- /dev/null +++ b/utbot-cli-js/Dockerfile @@ -0,0 +1,22 @@ +FROM azul/zulu-openjdk:11.0.15-11.56.19 + +ARG DEBIAN_FRONTEND=noninteractive + +WORKDIR /usr/src/ + +RUN apt-get update \ + && apt-get install -y -q --no-install-recommends \ + curl \ + && curl -sL https://deb.nodesource.com/setup_18.x -o nodesource_setup.sh \ + && /bin/bash nodesource_setup.sh \ + && apt-get install -y -q --no-install-recommends \ + nodejs \ + && rm -rf /var/lib/apt/lists/* + +# Install UTBot Javascript CLI + +ARG ARTIFACT_PATH +COPY ${ARTIFACT_PATH} . + +RUN UTBOT_JS_CLI_PATH="$(find /usr/src -type f -name 'utbot-cli*')" \ + && ln -s "${UTBOT_JS_CLI_PATH}" /usr/src/utbot-cli.jar \ diff --git a/utbot-cli-js/build.gradle b/utbot-cli-js/build.gradle new file mode 100644 index 0000000000..0248806799 --- /dev/null +++ b/utbot-cli-js/build.gradle @@ -0,0 +1,75 @@ +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17 + freeCompilerArgs += ["-Xallow-result-return-type", "-Xsam-conversions=class"] + } +} + +tasks.withType(JavaCompile) { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_17 +} + +configurations { + fetchInstrumentationJar +} + +dependencies { + implementation project(':utbot-framework') + implementation project(':utbot-cli') + implementation project(':utbot-js') + + // Without this dependency testng tests do not run. + implementation group: 'com.beust', name: 'jcommander', version: '1.48' + implementation group: 'org.junit.platform', name: 'junit-platform-console-standalone', version: junit4PlatformVersion + implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlinLoggingVersion + implementation group: 'com.github.ajalt.clikt', name: 'clikt', version: cliktVersion + implementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: junit5Version + implementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junit5Version + implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j2Version + implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4j2Version + implementation group: 'org.json', name: 'json', version: '20220320' + //noinspection GroovyAssignabilityCheck + fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration: 'instrumentationArchive') +} + +processResources { + from(configurations.fetchInstrumentationJar) { + into "lib" + } +} + +task createProperties(dependsOn: processResources) { + doLast { + new File("$buildDir/resources/main/version.properties").withWriter { w -> + Properties properties = new Properties() + //noinspection GroovyAssignabilityCheck + properties['version'] = project.version.toString() + properties.store w, null + } + } +} + +classes { + dependsOn createProperties +} + +jar { + manifest { + attributes 'Main-Class': 'org.utbot.cli.js.ApplicationKt' + attributes 'Bundle-SymbolicName': 'org.utbot.cli.js' + attributes 'Bundle-Version': "${project.version}" + attributes 'Implementation-Title': 'UtBot JavaScript CLI' + attributes 'JAR-Type': 'Fat JAR' + } + + archiveVersion.set(project.version as String) + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + diff --git a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/Application.kt b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/Application.kt new file mode 100644 index 0000000000..9f1ed44f6c --- /dev/null +++ b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/Application.kt @@ -0,0 +1,35 @@ +package org.utbot.cli.js + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.subcommands +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.versionOption +import com.github.ajalt.clikt.parameters.types.enum +import org.slf4j.event.Level +import org.utbot.cli.getVersion +import org.utbot.cli.setVerbosity +import kotlin.system.exitProcess + +class UtBotJsCli : CliktCommand(name = "UnitTestBot JavaScript Command Line Interface") { + private val verbosity by option("--verbosity", help = "Changes verbosity level, case insensitive") + .enum(ignoreCase = true) + .default(Level.INFO) + + override fun run() = setVerbosity(verbosity) + + init { + versionOption(getVersion()) + } +} + +fun main(args: Array) = try { + UtBotJsCli().subcommands( + JsCoverageCommand(), + JsGenerateTestsCommand(), + JsRunTestsCommand(), + ).main(args) +} catch (ex: Throwable) { + ex.printStackTrace() + exitProcess(1) +} \ No newline at end of file diff --git a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsCoverageCommand.kt b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsCoverageCommand.kt new file mode 100644 index 0000000000..788b63c559 --- /dev/null +++ b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsCoverageCommand.kt @@ -0,0 +1,163 @@ +package org.utbot.cli.js + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.check +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import mu.KotlinLogging +import org.json.JSONArray +import org.json.JSONObject +import org.utbot.cli.js.JsUtils.makeAbsolutePath +import org.w3c.dom.Document +import org.w3c.dom.Element +import utils.JsCmdExec +import java.io.File +import java.nio.file.Files +import java.nio.file.Paths +import javax.xml.parsers.DocumentBuilderFactory + +private val logger = KotlinLogging.logger {} + +class JsCoverageCommand : CliktCommand(name = "coverage_js", help = "Get tests coverage for the specified file.") { + + private val testFile by option( + "-s", "--source", + help = "Target test file path." + ).required() + .check("Must exist and ends with .js suffix") { + it.endsWith(".js") && Files.exists(Paths.get(it)) + } + + private val output by option( + "-o", "--output", + help = "Specifies output .json file for generated tests." + ).check("Must end with .json suffix") { + it.endsWith(".json") + } + + private val pathToNYC by option( + "--path-to-nyc", + help = "Sets path to nyc executable, defaults to \"nyc\" shortcut. " + + "As there are many nyc files in the global npm directory, choose one without file extension." + ).default("nyc") + + override fun run() { + val testFileAbsolutePath = makeAbsolutePath(testFile) + val workingDir = testFileAbsolutePath.substringBeforeLast("/") + val coverageDataPath = "$workingDir/coverage" + val outputAbsolutePath = output?.let { makeAbsolutePath(it) } + JsCmdExec.runCommand( + dir = workingDir, + shouldWait = true, + timeout = 20, + cmd = arrayOf( + "\"$pathToNYC\"", + "--report-dir=\"$coverageDataPath\"", + "--reporter=\"clover\"", + "--temp-dir=\"${workingDir}/cache\"", + "mocha", + "\"$testFileAbsolutePath\"" + ) + ) + val coveredList = mutableListOf() + val partiallyCoveredList = mutableListOf() + val uncoveredList = mutableListOf() + val db = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val xmlFile = File("$coverageDataPath/clover.xml") + val doc = db.parse(xmlFile) + buildCoverageLists( + coveredList, + partiallyCoveredList, + uncoveredList, + doc, + ) + val json = createJson( + coveredList, + partiallyCoveredList, + uncoveredList, + ) + processResult(json, outputAbsolutePath) + } + + private fun buildCoverageLists( + coveredList: MutableList, + partiallyCoveredList: MutableList, + uncoveredList: MutableList, + doc: Document, + ) { + doc.documentElement.normalize() + val lineList = try { + (((doc.getElementsByTagName("project").item(0) as Element) + .getElementsByTagName("package").item(0) as Element) + .getElementsByTagName("file").item(0) as Element) + .getElementsByTagName("line") + } catch (e: Exception) { + ((doc.getElementsByTagName("project").item(0) as Element) + .getElementsByTagName("file").item(0) as Element) + .getElementsByTagName("line") + } + for (i in 0 until lineList.length) { + val lineInfo = lineList.item(i) as Element + val num = lineInfo.getAttribute("num").toInt() + val count = lineInfo.getAttribute("count").toInt() + when (lineInfo.getAttribute("type")) { + "stmt" -> { + if (count > 0) coveredList += num + else uncoveredList += num + } + + "cond" -> { + val trueCount = lineInfo.getAttribute("truecount").toInt() + val falseCount = lineInfo.getAttribute("falsecount").toInt() + when { + trueCount == 2 && falseCount == 0 -> coveredList += num + trueCount == 1 && falseCount == 1 -> partiallyCoveredList += num + trueCount == 0 && falseCount == 2 -> uncoveredList += num + } + } + } + } + } + + private fun createJson( + coveredList: List, + partiallyCoveredList: List, + uncoveredList: List, + ): JSONObject { + val coveredArray = JSONArray() + coveredList.forEach { + val obj = JSONObject() + obj.put("start", it) + obj.put("end", it) + coveredArray.put(obj) + } + val partiallyCoveredArray = JSONArray() + partiallyCoveredList.forEach { + val obj = JSONObject() + obj.put("start", it) + obj.put("end", it) + partiallyCoveredArray.put(obj) + } + val uncoveredArray = JSONArray() + uncoveredList.forEach { + val obj = JSONObject() + obj.put("start", it) + obj.put("end", it) + uncoveredArray.put(obj) + } + val json = JSONObject() + json.put("covered", coveredArray) + json.put("notCovered", uncoveredArray) + json.put("partlyCovered", partiallyCoveredArray) + return json + } + + private fun processResult(json: JSONObject, output: String?) { + output?.let { fileName -> + val file = File(fileName) + file.createNewFile() + file.writeText(json.toString()) + } ?: logger.info { json.toString() } + } +} \ No newline at end of file diff --git a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt new file mode 100644 index 0000000000..2a0e043216 --- /dev/null +++ b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsGenerateTestsCommand.kt @@ -0,0 +1,149 @@ +package org.utbot.cli.js + +import api.JsTestGenerator +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.* +import com.github.ajalt.clikt.parameters.types.choice +import mu.KotlinLogging +import org.utbot.cli.js.JsUtils.makeAbsolutePath +import service.coverage.CoverageMode +import settings.JsDynamicSettings +import settings.JsExportsSettings.endComment +import settings.JsExportsSettings.startComment +import settings.JsTestGenerationSettings.defaultTimeout +import java.io.File +import java.nio.file.Files +import java.nio.file.Paths +import java.time.LocalDateTime +import java.time.temporal.ChronoUnit + +private val logger = KotlinLogging.logger {} + + +class JsGenerateTestsCommand : + CliktCommand(name = "generate_js", help = "Generates tests for the specified class or toplevel functions.") { + + private val sourceCodeFile by option( + "-s", "--source", + help = "Specifies source code file for a generated test." + ) + .required() + .check("Must exist and ends with .js suffix") { + it.endsWith(".js") && Files.exists(Paths.get(it)) + } + + private val targetClass by option("-c", "--class", help = "Specifies target class to generate tests for.") + + private val output by option("-o", "--output", help = "Specifies output file for generated tests.") + .check("Must end with .js suffix") { + it.endsWith(".js") + } + + private val printToStdOut by option( + "-p", + "--print-test", + help = "Specifies whether test should be printed out to StdOut." + ) + .flag(default = false) + + private val timeout by option( + "-t", + "--timeout", + help = "Timeout for Node.js to run scripts in seconds." + ).default("$defaultTimeout") + + private val coverageMode by option( + "--coverage-mode", + help = "Specifies the coverage mode for test generation. Check docs for more info." + ).choice( + CoverageMode.BASIC.toString() to CoverageMode.BASIC, + CoverageMode.FAST.toString() to CoverageMode.FAST + ).default(CoverageMode.FAST) + + private val pathToNode by option( + "--path-to-node", + help = "Sets path to Node.js executable, defaults to \"node\" shortcut." + ).default("node") + + private val pathToNYC by option( + "--path-to-nyc", + help = "Sets path to nyc executable, defaults to \"nyc\" shortcut. " + + "As there are many nyc files in the global npm directory, choose one without file extension." + ).default("nyc") + + private val pathToNPM by option( + "--path-to-npm", + help = "Sets path to npm executable, defaults to \"npm\" shortcut." + ).default("npm") + + override fun run() { + val started = LocalDateTime.now() + try { + val sourceFileAbsolutePath = makeAbsolutePath(sourceCodeFile) + logger.info { "Generating tests for [$sourceFileAbsolutePath] - started" } + val fileText = File(sourceCodeFile).readText() + currentFileText = fileText + val outputAbsolutePath = output?.let { makeAbsolutePath(it) } + val testGenerator = JsTestGenerator( + fileText = fileText, + sourceFilePath = sourceFileAbsolutePath, + parentClassName = targetClass, + outputFilePath = outputAbsolutePath, + exportsManager = ::manageExports, + settings = JsDynamicSettings( + pathToNode = pathToNode, + pathToNYC = pathToNYC, + pathToNPM = pathToNPM, + timeout = timeout.toLong(), + coverageMode = coverageMode, + + ) + ) + val testCode = testGenerator.run() + + if (printToStdOut || (outputAbsolutePath == null && !printToStdOut)) { + logger.info { "\n$testCode" } + } + outputAbsolutePath?.let { filePath -> + val outputFile = File(filePath) + outputFile.createNewFile() + outputFile.writeText(testCode) + } + + } catch (t: Throwable) { + logger.error { "An error has occurred while generating tests for file $sourceCodeFile : $t" } + throw t + } finally { + val duration = ChronoUnit.MILLIS.between(started, LocalDateTime.now()) + logger.debug { "Generating test for [$sourceCodeFile] - completed in [$duration] (ms)" } + } + } + + // Needed for continuous exports managing + private var currentFileText = "" + + private fun manageExports(swappedText: (String?, String) -> String) { + val file = File(sourceCodeFile) + when { + + currentFileText.contains(startComment) -> { + val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") + regex.find(currentFileText)?.groups?.get(1)?.value?.let { existingSection -> + val newText = swappedText(existingSection, currentFileText) + file.writeText(newText) + currentFileText = newText + } + } + + else -> { + val line = buildString { + append("\n$startComment") + append(swappedText(null, currentFileText)) + append(endComment) + } + file.appendText(line) + currentFileText = file.readText() + } + } + } +} diff --git a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsRunTestsCommand.kt b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsRunTestsCommand.kt new file mode 100644 index 0000000000..60d96fc374 --- /dev/null +++ b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsRunTestsCommand.kt @@ -0,0 +1,59 @@ +package org.utbot.cli.js + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.check +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import com.github.ajalt.clikt.parameters.types.choice +import mu.KotlinLogging +import org.utbot.cli.js.JsUtils.makeAbsolutePath +import utils.JsCmdExec +import java.io.File + +private val logger = KotlinLogging.logger {} + +class JsRunTestsCommand : CliktCommand(name = "run_js", help = "Runs tests for the specified file or directory.") { + + private val fileWithTests by option( + "--fileOrDir", "-f", + help = "Specifies a file or directory with tests." + ).required() + + private val output by option( + "-o", "--output", + help = "Specifies an output .txt file for test framework result." + ).check("Must end with .txt suffix") { + it.endsWith(".txt") + } + + private val testFramework by option("--test-framework", "-t", help = "Test framework to be used.") + .choice("mocha") + .default("mocha") + + + override fun run() { + val fileWithTestsAbsolutePath = makeAbsolutePath(fileWithTests) + val dir = if (fileWithTestsAbsolutePath.endsWith(".js")) + fileWithTestsAbsolutePath.substringBeforeLast("/") else fileWithTestsAbsolutePath + val outputAbsolutePath = output?.let { makeAbsolutePath(it) } + when (testFramework) { + "mocha" -> { + val (inputText, errorText) = JsCmdExec.runCommand( + dir = dir, + shouldWait = true, + cmd = arrayOf("mocha", "\"$fileWithTestsAbsolutePath\"") + ) + if (errorText.isNotEmpty()) { + logger.error { "An error has occurred while running tests for $fileWithTests: $errorText" } + } else { + outputAbsolutePath?.let { + val file = File(it) + file.createNewFile() + file.writeText(inputText) + } ?: logger.info { "Output absolute path is null with text: $inputText" } + } + } + } + } +} diff --git a/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsUtils.kt b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsUtils.kt new file mode 100644 index 0000000000..87b135a11a --- /dev/null +++ b/utbot-cli-js/src/main/kotlin/org/utbot/cli/js/JsUtils.kt @@ -0,0 +1,14 @@ +package org.utbot.cli.js + +import java.io.File + +internal object JsUtils { + + fun makeAbsolutePath(path: String): String { + val rawPath = when { + File(path).isAbsolute -> path + else -> System.getProperty("user.dir") + "/" + path + } + return rawPath.replace("\\", "/") + } +} \ No newline at end of file diff --git a/utbot-cli-js/src/main/resources/log4j2.xml b/utbot-cli-js/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..d0f20b10bc --- /dev/null +++ b/utbot-cli-js/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utbot-cli-js/src/main/resources/version.properties b/utbot-cli-js/src/main/resources/version.properties new file mode 100644 index 0000000000..956d6e337a --- /dev/null +++ b/utbot-cli-js/src/main/resources/version.properties @@ -0,0 +1,2 @@ +#to be populated during the build task +version=N/A \ No newline at end of file diff --git a/utbot-cli-python/Dockerfile b/utbot-cli-python/Dockerfile new file mode 100644 index 0000000000..6a76e29d21 --- /dev/null +++ b/utbot-cli-python/Dockerfile @@ -0,0 +1,24 @@ +FROM azul/zulu-openjdk:11.0.15-11.56.19 + +ARG DEBIAN_FRONTEND=noninteractive + +WORKDIR /usr/src/ + +RUN apt-get update \ + && apt-get install -y -q --no-install-recommends \ + curl \ + python3.9 \ + python3.9-distutils \ + && rm -rf /var/lib/apt/lists/* \ + && curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py \ + && python3.9 get-pip.py \ + && pip install -U \ + pytest + +# Install UTBot Python CLI + +ARG ARTIFACT_PATH +COPY ${ARTIFACT_PATH} . + +RUN UTBOT_PYTHON_CLI_PATH="$(find /usr/src -type f -name 'utbot-cli*')" \ + && ln -s "${UTBOT_PYTHON_CLI_PATH}" /usr/src/utbot-cli.jar diff --git a/utbot-cli-python/build.gradle b/utbot-cli-python/build.gradle new file mode 100644 index 0000000000..fbfa2bdd9d --- /dev/null +++ b/utbot-cli-python/build.gradle @@ -0,0 +1,70 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +tasks.withType(KotlinCompile).configureEach { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17 + freeCompilerArgs += ["-Xallow-result-return-type", "-Xsam-conversions=class"] + } +} + +tasks.withType(JavaCompile).configureEach { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_17 +} + +configurations { + fetchInstrumentationJar +} + +dependencies { + implementation project(':utbot-framework') + implementation project(':utbot-python') + + implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlinLoggingVersion + implementation group: 'com.github.ajalt.clikt', name: 'clikt', version: cliktVersion + implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j2Version + implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4j2Version + implementation group: 'com.github.UnitTestBot', name: 'PythonTypesAPI', version: pythonTypesAPIHash +} + +processResources { + from(configurations.fetchInstrumentationJar) { + into "lib" + } +} + +tasks.register('createProperties') { + dependsOn processResources + doLast { + new File("$buildDir/resources/main/version.properties").withWriter { w -> + Properties properties = new Properties() + //noinspection GroovyAssignabilityCheck + properties['version'] = project.version.toString() + properties.store w, null + } + } +} + +classes { + dependsOn createProperties +} + +jar { + manifest { + attributes 'Main-Class': 'org.utbot.cli.language.python.ApplicationKt' + attributes 'Bundle-SymbolicName': 'org.utbot.cli.language.python' + attributes 'Bundle-Version': "${project.version}" + attributes 'Implementation-Title': 'UtBot Python CLI' + attributes 'JAR-Type': 'Fat JAR' + } + + archiveVersion.set(project.version as String) + + dependsOn configurations.runtimeClasspath + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + diff --git a/utbot-cli-python/src/README.md b/utbot-cli-python/src/README.md new file mode 100644 index 0000000000..29873d592c --- /dev/null +++ b/utbot-cli-python/src/README.md @@ -0,0 +1,105 @@ +## Build + +.jar file can be built in Github Actions with script `publish-plugin-and-cli-from-branch`. + +## Requirements + + - Required Java version: 11. + + - Preferred Python version: 3.10-3.11 (3.9 also supported but with limited functionality). + + Make sure that your Python has `pip` installed (this is usually the case). [Read more about pip installation](https://pip.pypa.io/en/stable/installation/). + + Before running utbot install pip requirements (or use `--install-requirements` flag in `generate_python` command): + + python -m pip install mypy==1.0 utbot_executor==0.9.19 utbot_mypy_runner==0.2.16 + +## Basic usage + +Generate tests: + + java -jar utbot-cli-python.jar generate_python dir/file_with_sources.py -p -o generated_tests.py -s dir + +This will generate tests for top-level functions from `file_with_sources.py`. + +Run generated tests: + + java -jar utbot-cli-python.jar run_python generated_tests.py -p + +### `generate_python` options + +- `-s, --sys-path ,` + + (required) Directories to add to `sys.path`. One of directories must contain the file with the methods under test. + + `sys.path` is a list of strings that specifies the search path for modules. It must include paths for all user modules that are used in imports. + +- `-p, --python-path ` + + (required) Path to Python interpreter. + +- `-o, --output ` + + (required) File for generated tests. + +- `--coverage ` + + File to write coverage report. + +- `-c, --class ` + + Specify top-level (ordinary, not nested) class under test. Without this option tests will be generated for top-level functions. + +- `-m, --methods ,` + + Specify methods under test. + +- `--install-requirements` + + Install Python requirements if missing. + +- `--do-not-minimize` + + Turn off minimization of the number of generated tests. + +- `--do-not-check-requirements` + + Turn off Python requirements check (to speed up). + +- `-t, --timeout INT` + + Specify the maximum time in milliseconds to spend on generating tests (60000 by default). + +- `--timeout-for-run INT` + + Specify the maximum time in milliseconds to spend on one function run (2000 by default). + +- `--test-framework [pytest|Unittest]` + + Test framework to be used. + +- `--runtime-exception-behaviour [PASS|FAIL]` + + Expected behaviour for runtime exception. + +- `--do-not-generate-regression-suite` + + Generate regression test suite or not. Regression suite and error suite generation is active by default. + +### `run_python` options + +- `-p, --python-path ` + + (required) Path to Python interpreter. + +- `--test-framework [pytest|Unittest]` + + Test framework of tests to run. + +- `-o, --output ` + + Specify file for report. + +## Problems + +- Unittest can not run tests from parent directories diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/Application.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/Application.kt new file mode 100644 index 0000000000..0cad4e6264 --- /dev/null +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/Application.kt @@ -0,0 +1,55 @@ +package org.utbot.cli.language.python + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.subcommands +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.versionOption +import com.github.ajalt.clikt.parameters.types.enum +import mu.KotlinLogging +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.core.config.Configurator +import org.slf4j.event.Level +import java.util.* +import kotlin.system.exitProcess + +private val logger = KotlinLogging.logger {} + +class UtBotPythonCli : CliktCommand(name = "UnitTestBot Python Command Line Interface") { + private val verbosity by option("--verbosity", help = "Changes verbosity level, case insensitive") + .enum(ignoreCase = true) + .default(Level.INFO) + + override fun run() = setVerbosity(verbosity) + + init { + versionOption(getVersion()) + } +} + +fun main(args: Array) = try { + UtBotPythonCli().subcommands( + PythonGenerateTestsCommand(), + PythonRunTestsCommand(), + PythonTypeInferenceCommand() + ).main(args) +} catch (ex: Throwable) { + ex.printStackTrace() + exitProcess(1) +} + +fun setVerbosity(verbosity: Level) { + Configurator.setAllLevels(LogManager.getRootLogger().name, level(verbosity)) + logger.debug { "Log Level changed to [$verbosity]" } +} + +private fun level(level: Level) = org.apache.logging.log4j.Level.toLevel(level.name) + +fun getVersion(): String { + val prop = Properties() + Thread.currentThread().contextClassLoader.getResourceAsStream("version.properties") + .use { stream -> + prop.load(stream) + } + return prop.getProperty("version") +} diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/CliRequirementsInstaller.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/CliRequirementsInstaller.kt new file mode 100644 index 0000000000..8653520a30 --- /dev/null +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/CliRequirementsInstaller.kt @@ -0,0 +1,27 @@ +package org.utbot.cli.language.python + +import mu.KLogger +import org.utbot.python.utils.RequirementsInstaller +import org.utbot.python.utils.RequirementsUtils + +class CliRequirementsInstaller( + private val installRequirementsIfMissing: Boolean, + private val logger: KLogger, +) : RequirementsInstaller { + override fun checkRequirements(pythonPath: String, requirements: List): Boolean { + return RequirementsUtils.requirementsAreInstalled(pythonPath, requirements) + } + + override fun installRequirements(pythonPath: String, requirements: List) { + if (installRequirementsIfMissing) { + val result = RequirementsUtils.installRequirements(pythonPath, requirements) + if (result.exitValue != 0) { + System.err.println(result.stderr) + logger.error("Failed to install requirements.") + } + } else { + logger.error("Missing some requirements. Please add --install-requirements flag or install them manually.") + } + logger.info("Requirements: ${requirements.joinToString()}") + } +} \ No newline at end of file diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonCliProcessor.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonCliProcessor.kt new file mode 100644 index 0000000000..58aefea470 --- /dev/null +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonCliProcessor.kt @@ -0,0 +1,42 @@ +package org.utbot.cli.language.python + +import mu.KotlinLogging +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.PythonTestGenerationProcessor +import org.utbot.python.PythonTestSet + +private val logger = KotlinLogging.logger {} + +class PythonCliProcessor( + override val configuration: PythonTestGenerationConfig, + private val testWriter: TestWriter, + private val coverageOutput: String?, + private val executionCounterOutput: String?, +) : PythonTestGenerationProcessor() { + + override fun saveTests(testsCode: String) { + testWriter.addTestCode(testsCode) +// writeToFileAndSave(output, testsCode) + } + + override fun notGeneratedTestsAction(testedFunctions: List) { + logger.error( + "Couldn't generate tests for the following functions: ${testedFunctions.joinToString()}" + ) + } + + private fun getExecutionsNumber(testSets: List): Int { + return testSets.sumOf { it.executionsNumber } + } + + override fun processCoverageInfo(testSets: List) { + coverageOutput?.let { output -> + val coverageReport = getStringCoverageInfo(testSets) + writeToFileAndSave(output, coverageReport) + } + executionCounterOutput?.let { executionOutput -> + val executionReport = "{\"executions\": ${getExecutionsNumber(testSets)}}" + writeToFileAndSave(executionOutput, executionReport) + } + } +} \ No newline at end of file diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt new file mode 100644 index 0000000000..a9e2242829 --- /dev/null +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt @@ -0,0 +1,380 @@ +package org.utbot.cli.language.python + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import com.github.ajalt.clikt.parameters.options.split +import com.github.ajalt.clikt.parameters.types.choice +import com.github.ajalt.clikt.parameters.types.long +import mu.KotlinLogging +import org.parsers.python.PythonParser +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.python.MypyConfig +import org.utbot.python.PythonMethodHeader +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.PythonTestGenerationProcessor +import org.utbot.python.PythonTestSet +import org.utbot.python.TestFileInformation +import org.utbot.python.code.PythonCode +import org.utbot.python.coverage.CoverageOutputFormat +import org.utbot.python.coverage.PythonCoverageMode +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.pythonBuiltinsModuleName +import org.utbot.python.framework.codegen.model.Pytest +import org.utbot.python.framework.codegen.model.Unittest +import org.utbot.python.newtyping.ast.parseClassDefinition +import org.utbot.python.newtyping.ast.parseFunctionDefinition +import org.utbot.python.newtyping.mypy.dropInitFile +import org.utbot.python.utils.Cleaner +import org.utbot.python.utils.Fail +import org.utbot.python.utils.RequirementsInstaller +import org.utbot.python.utils.Success +import org.utbot.python.utils.separateTimeout +import java.io.File +import java.nio.file.Paths +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.system.exitProcess +import kotlin.system.measureTimeMillis + +private const val DEFAULT_TIMEOUT_IN_MILLIS = 60000L +private const val DEFAULT_TIMEOUT_FOR_ONE_RUN_IN_MILLIS = 2000L + +private val logger = KotlinLogging.logger {} + +class PythonGenerateTestsCommand : CliktCommand( + name = "generate_python", + help = "Generate tests for a specified Python class or top-level functions from a specified file." +) { + private val sourceFile by argument( + help = "File with Python code to generate tests for." + ) + + private lateinit var absPathToSourceFile: String + private lateinit var sourceFileContent: String + + private val classesToTest by option( + "-c", "--classes", + help = "Generate tests for all methods of selected classes. Use '-' to specify top-level functions group." + ) + .split(",") + private fun classesToTest() = classesToTest?.map { if (it == "-") null else it } + + private val methodsToTest by option( + "-m", "--methods", + help = "Specify methods under test using full path (use qualified name: only name for top-level function and . for methods. Use '-' to skip test generation for top-level functions" + ) + .split(",") + + private val directoriesForSysPath by option( + "-s", "--sys-path", + help = "(required) Directories to add to sys.path. " + + "One of directories must contain the file with the methods under test." + ).split(",").required() + + private val pythonPath by option( + "-p", "--python-path", + help = "(required) Path to Python interpreter." + ).required() + + private val output by option( + "-o", "--output", help = "(required) File for generated tests." + ).required() + + private val coverageOutput by option( + "--coverage", + help = "File to write coverage report." + ) + + private val executionCounterOutput by option( + "--executions-counter", + help = "File to write number of executions." + ) + + private val installRequirementsIfMissing by option( + "--install-requirements", + help = "Install Python requirements if missing." + ).flag(default = false) + + private val doNotMinimize by option( + "--do-not-minimize", + help = "Turn off minimization of the number of generated tests." + ).flag(default = false) + + private val timeout by option( + "-t", "--timeout", + help = "Specify the maximum time in milliseconds to spend on generating tests ($DEFAULT_TIMEOUT_IN_MILLIS by default)." + ).long().default(DEFAULT_TIMEOUT_IN_MILLIS) + + private val timeoutForRun by option( + "--timeout-for-run", + help = "Specify the maximum time in milliseconds to spend on one function run ($DEFAULT_TIMEOUT_FOR_ONE_RUN_IN_MILLIS by default)." + ).long().default(DEFAULT_TIMEOUT_FOR_ONE_RUN_IN_MILLIS) + + private val includeMypyAnalysisTime by option( + "--include-mypy-analysis-time", + help = "Include mypy static analysis time in the total timeout." + ).flag(default = false) + + private val testFrameworkAsString by option("--test-framework", help = "Test framework to be used.") + .choice(Pytest.toString(), Unittest.toString()) + .default(Unittest.toString()) + + private val runtimeExceptionTestsBehaviour by option("--runtime-exception-behaviour", help = "PASS or FAIL") + .choice("PASS", "FAIL") + .default("FAIL") + + private val doNotGenerateRegressionSuite by option("--do-not-generate-regression-suite", help = "Do not generate regression test suite.") + .flag(default = false) + + private val coverageMeasureMode by option("--coverage-measure-mode", help = "Use LINES or INSTRUCTIONS for coverage measurement.") + .choice("INSTRUCTIONS", "LINES") + .default("INSTRUCTIONS") + + private val doNotSendCoverageContinuously by option("--do-not-send-coverage-continuously", help = "Do not send coverage during execution.") + .flag(default = false) + + private val prohibitedExceptions by option( + "--prohibited-exceptions", + help = "Do not generate tests with these exceptions. Set '-' to generate tests for all exceptions." + ) + .split(",") + .default(PythonTestGenerationConfig.defaultProhibitedExceptions) + + private val testFramework: TestFramework + get() = + when (testFrameworkAsString) { + Unittest.toString() -> Unittest + Pytest.toString() -> Pytest + else -> error("Not reachable") + } + + private val forbiddenMethods = listOf("__init__", "__new__") + + private fun getPythonMethods(): List> { + val parsedModule = PythonParser(sourceFileContent).Module() + + val topLevelFunctions = PythonCode.getTopLevelFunctions(parsedModule) + val topLevelClasses = PythonCode.getTopLevelClasses(parsedModule) + + val functions = topLevelFunctions + .mapNotNull { parseFunctionDefinition(it) } + .map { PythonMethodHeader(it.name.toString(), absPathToSourceFile, null) } + val methods = topLevelClasses + .mapNotNull { cls -> + val parsedClass = parseClassDefinition(cls) ?: return@mapNotNull null + val innerClasses = PythonCode.getInnerClasses(cls) + (listOf(parsedClass to null) + innerClasses.mapNotNull { innerClass -> parseClassDefinition(innerClass)?.let { it to parsedClass } }).map { (cls, parent) -> + PythonCode.getClassMethods(cls.body) + .mapNotNull { parseFunctionDefinition(it) } + .map { function -> + val clsName = (parent?.let { "${it.name}." } ?: "") + cls.name.toString() + val parsedClassName = PythonClassId(pythonBuiltinsModuleName, clsName) + PythonMethodHeader(function.name.toString(), absPathToSourceFile, parsedClassName) + } + } + } + .flatten() + + fun functionsFilter(group: List, methodFilter: List? = methodsToTest): List { + return methodFilter?.let { + if (it.isEmpty()) group + else group.filter { method -> method.fullname in it } + } ?: group + } + + fun methodsFilter(group: List, containingClass: PythonClassId): List { + val localMethodFilter = methodsToTest?.let { it.filter { name -> name.startsWith(containingClass.typeName) } } + return functionsFilter(group, localMethodFilter) + } + + fun groupFilter(group: List, classFilter: List?): List { + if (group.isEmpty()) return emptyList() + val groupClass = group.first().containingPythonClassId + if (classFilter != null && groupClass?.typeName !in classFilter) return emptyList() + return if (groupClass == null) functionsFilter(group) + else methodsFilter(group, groupClass) + } + + val methodGroups = (methods + listOf(functions)) + .map { groupFilter(it, classesToTest()) } + .map { + it.filter { forbiddenMethods.all { name -> !it.name.endsWith(name) } } + } + .filter { it.isNotEmpty() } + + methodsToTest?.forEach { name -> + require(methodGroups.flatten().any { it.fullname == name }) { "Cannot find function '$name' in file '$absPathToSourceFile'" } + } + classesToTest()?.forEach { name -> + require(methodGroups.flatten().any { it.containingPythonClassId?.typeName == name }) { "Cannot find class '$name' or methods in file '$absPathToSourceFile'" } + } + return methodGroups + } + + private val shutdown: AtomicBoolean = AtomicBoolean(false) + private val alreadySaved: AtomicBoolean = AtomicBoolean(false) + + private val shutdownThread = + object : Thread() { + override fun run() { + shutdown.set(true) + try { + if (!alreadySaved.get()) { + saveTests() + } + } catch (_: InterruptedException) { + logger.warn { "Interrupted exception" } + } + } + } + + private fun addShutdownHook() { + Runtime.getRuntime().addShutdownHook(shutdownThread) + } + + private fun removeShutdownHook() { + Runtime.getRuntime().removeShutdownHook(shutdownThread) + } + + private val testWriter = TestWriter() + + private fun saveTests() { + logger.info("Saving tests...") + val testCode = testWriter.generateTestCode() + writeToFileAndSave(output, testCode) + + Cleaner.doCleaning() + alreadySaved.set(true) + } + + private fun initialize() { + absPathToSourceFile = sourceFile.toAbsolutePath() + sourceFileContent = File(absPathToSourceFile).readText() + } + + override fun run() { + initialize() + + val sysPathDirectories = directoriesForSysPath.map { it.toAbsolutePath() }.toSet() + val currentPythonModule = when (val module = findCurrentPythonModule(sysPathDirectories, absPathToSourceFile)) { + is Success -> { + module.value + } + + is Fail -> { + logger.error { module.message } + return + } + } + + logger.info("Checking requirements...") + val installer = CliRequirementsInstaller(installRequirementsIfMissing, logger) + val requirementsAreInstalled = RequirementsInstaller.checkRequirements( + installer, + pythonPath, + if (testFramework.isInstalled) emptyList() else listOf(testFramework.mainPackage) + ) + if (!requirementsAreInstalled) { + return + } + + val pythonMethodGroups = getPythonMethods() + + val testFile = TestFileInformation(absPathToSourceFile, sourceFileContent, currentPythonModule.dropInitFile()) + + val mypyConfig: MypyConfig + val mypyTime = measureTimeMillis { + logger.info("Loading information about Python types...") + mypyConfig = PythonTestGenerationProcessor.sourceCodeAnalyze( + sysPathDirectories, + pythonPath, + testFile, + ) + } + logger.info { "Mypy time: $mypyTime" } + + addShutdownHook() + + val startTime = System.currentTimeMillis() + val countOfFunctions = pythonMethodGroups.sumOf { it.size } + val timeoutAfterMypy = if (includeMypyAnalysisTime) timeout - mypyTime else timeout + val oneFunctionTimeout = separateTimeout(timeoutAfterMypy, countOfFunctions) + logger.info { "One function timeout: ${oneFunctionTimeout}ms. x${countOfFunctions}" } + pythonMethodGroups.forEachIndexed { index, pythonMethods -> + val usedTime = System.currentTimeMillis() - startTime + val countOfTestedFunctions = pythonMethodGroups.take(index).sumOf { it.size } + val expectedTime = countOfTestedFunctions * oneFunctionTimeout + val localOneFunctionTimeout = if (usedTime < expectedTime) { + separateTimeout(timeoutAfterMypy - usedTime, countOfFunctions - countOfTestedFunctions) + } else { + oneFunctionTimeout + } + val localTimeout = pythonMethods.size * localOneFunctionTimeout + logger.info { "Timeout for current group: ${localTimeout}ms" } + + val config = PythonTestGenerationConfig( + pythonPath = pythonPath, + testFileInformation = testFile, + sysPathDirectories = sysPathDirectories, + testedMethods = pythonMethods, + timeout = localTimeout, + timeoutForRun = timeoutForRun, + testFramework = testFramework, + testSourceRootPath = Paths.get(output.toAbsolutePath()).parent.toAbsolutePath(), + withMinimization = !doNotMinimize, + isCanceled = { shutdown.get() }, + runtimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.valueOf(runtimeExceptionTestsBehaviour), + coverageMeasureMode = PythonCoverageMode.parse(coverageMeasureMode), + sendCoverageContinuously = !doNotSendCoverageContinuously, + coverageOutputFormat = CoverageOutputFormat.Lines, + prohibitedExceptions = if (prohibitedExceptions == listOf("-")) emptyList() else prohibitedExceptions, + ) + val processor = PythonCliProcessor( + config, + testWriter, + coverageOutput, + executionCounterOutput, + ) + + logger.info("Generating tests...") + val testSets = processor.testGenerate(mypyConfig).let { + return@let if (doNotGenerateRegressionSuite) { + it.map { testSet -> + PythonTestSet( + testSet.method, + testSet.executions.filterNot { execution -> execution.result is UtExecutionSuccess }, + testSet.errors, + testSet.executionsNumber, + testSet.clustersInfo, + ) + } + } else { + it + } + } + if (testSets.isNotEmpty()) { + logger.info("Saving tests...") + val testCode = processor.testCodeGenerate(testSets) + processor.saveTests(testCode) + + + logger.info("Saving coverage report...") + processor.processCoverageInfo(testSets) + + logger.info( + "Finished test generation for the following functions: ${ + testSets.joinToString { it.method.name } + }" + ) + } + } + saveTests() + removeShutdownHook() + exitProcess(0) + } +} diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonRunTestsCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonRunTestsCommand.kt new file mode 100644 index 0000000000..ffa9d08462 --- /dev/null +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonRunTestsCommand.kt @@ -0,0 +1,82 @@ +package org.utbot.cli.language.python + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import com.github.ajalt.clikt.parameters.types.choice +import org.utbot.python.framework.codegen.model.Pytest +import org.utbot.python.framework.codegen.model.Unittest +import org.utbot.python.utils.CmdResult +import org.utbot.python.utils.runCommand +import java.io.File +import java.nio.file.Paths + +class PythonRunTestsCommand : CliktCommand(name = "run_python", help = "Run tests in the specified file") { + + private val sourceFile by argument( + help = "File with Python tests to run." + ) + + private val pythonPath by option( + "-p", "--python-path", + help = "Path to Python interpreter." + ).required() + + private val output by option( + "-o", "--output", + help = "Specify file for report." + ) + + private val testFrameworkAsString by option("--test-framework", help = "Test framework of tests to run") + .choice(Pytest.toString(), Unittest.toString()) + .default(Unittest.toString()) + + private fun runUnittest(): CmdResult { + val currentPath = Paths.get("").toAbsolutePath().toString() + val sourceFilePath = Paths.get(sourceFile).toAbsolutePath().toString() + return if (sourceFilePath.startsWith(currentPath)) { + runCommand( + listOf( + pythonPath, + "-m", + "unittest", + sourceFile + ) + ) + } else CmdResult( + "", + "File $sourceFile can not be imported from Unittest. Move test file to child directory or use pytest.", + 1 + ) + } + + private fun runPytest(): CmdResult = + runCommand( + listOf( + pythonPath, + "-m", + "pytest", + sourceFile + ) + ) + + override fun run() { + val result = + when (testFrameworkAsString) { + Unittest.toString() -> runUnittest() + Pytest.toString() -> runPytest() + else -> error("Not reachable") + } + + output?.let { + val file = File(it) + file.writeText(result.stderr + result.stdout) + file.parentFile?.mkdirs() + file.createNewFile() + } + println(result.stderr) + println(result.stdout) + } +} \ No newline at end of file diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonTypeInferenceCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonTypeInferenceCommand.kt new file mode 100644 index 0000000000..124cd6a36e --- /dev/null +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonTypeInferenceCommand.kt @@ -0,0 +1,89 @@ +package org.utbot.cli.language.python + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.arguments.argument +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import com.github.ajalt.clikt.parameters.options.split +import com.github.ajalt.clikt.parameters.types.long +import mu.KotlinLogging +import org.utbot.python.newtyping.inference.TypeInferenceProcessor +import org.utpython.types.pythonTypeRepresentation +import org.utbot.python.utils.Fail +import org.utbot.python.utils.RequirementsUtils.requirements +import org.utbot.python.utils.Success + +private val logger = KotlinLogging.logger {} + +class PythonTypeInferenceCommand : CliktCommand( + name = "infer_types", + help = "Infer types for the specified Python top-level function." +) { + private val sourceFile by argument( + help = "File with Python code." + ) + + private val function by argument( + help = "Function to infer types for." + ) + + private val className by option( + "-c", "--class", + help = "Class of the function" + ) + + private val pythonPath by option( + "-p", "--python-path", + help = "(required) Path to Python interpreter. Use only UNC format on Windows." + ).required() + + private val timeout by option( + "-t", "--timout", + help = "(required) Timeout in milliseconds for type inference." + ).long().required() + + private val directoriesForSysPath by option( + "-s", "--sys-path", + help = "(required) Directories to add to sys.path. Use only UNC format on Windows. " + + "One of directories must contain the file with the methods under test." + ).split(",").required() + + private var startTime: Long = 0 + + override fun run() { + val moduleOpt = findCurrentPythonModule( + directoriesForSysPath.map { it.toAbsolutePath() }, + sourceFile.toAbsolutePath() + ) + if (moduleOpt is Fail) { + logger.error(moduleOpt.message) + } + val module = (moduleOpt as Success).value + + val result = TypeInferenceProcessor( + pythonPath, + directoriesForSysPath.map{ it.toAbsolutePath() }.toSet(), + sourceFile, + module, + function, + className + ).inferTypes( + startingTypeInferenceAction = { + startTime = System.currentTimeMillis() + logger.info("Starting type inference...") + }, + processSignature = { logger.info("Found signature: " + it.pythonTypeRepresentation()) }, + cancel = { System.currentTimeMillis() - startTime > timeout }, + checkRequirementsAction = { logger.info("Checking Python requirements...") }, + missingRequirementsAction = { + logger.error("Some of the following Python requirements are missing: " + + "${requirements.joinToString()}. Please install them.") + }, + loadingInfoAboutTypesAction = { logger.info("Loading information about types...") }, + analyzingCodeAction = { logger.info("Analyzing code...") }, + pythonMethodExtractionFailAction = { logger.error(it) } + ) + + result.forEach { println(it.pythonTypeRepresentation()) } + } +} \ No newline at end of file diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/TestWriter.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/TestWriter.kt new file mode 100644 index 0000000000..9857fdbe3e --- /dev/null +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/TestWriter.kt @@ -0,0 +1,28 @@ +package org.utbot.cli.language.python + +class TestWriter { + private val testCode: MutableList = mutableListOf() + + fun addTestCode(code: String) { + testCode.add(code) + } + + fun generateTestCode(): String { + val (importLines, code) = testCode.fold(mutableListOf() to StringBuilder()) { acc, s -> +// val lines = s.split(System.lineSeparator()) + val lines = s.split("(\\r\\n|\\r|\\n)".toRegex()) + val firstClassIndex = lines.indexOfFirst { it.startsWith("class") } + lines.take(firstClassIndex).forEach { line -> if (line !in acc.first) acc.first.add(line) } + lines.drop(firstClassIndex).forEach { line -> acc.second.append(line + System.lineSeparator()) } + acc.first to acc.second + } + val codeBuilder = StringBuilder() + importLines.filter { it.isNotEmpty() }.forEach { + codeBuilder.append(it) + codeBuilder.append(System.lineSeparator()) + } + codeBuilder.append(System.lineSeparator()) + codeBuilder.append(code) + return codeBuilder.toString() + } +} \ No newline at end of file diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/Utils.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/Utils.kt new file mode 100644 index 0000000000..708cfa6131 --- /dev/null +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/Utils.kt @@ -0,0 +1,28 @@ +package org.utbot.cli.language.python + +import org.utbot.python.utils.Fail +import org.utbot.python.utils.Optional +import org.utbot.python.utils.Success +import org.utbot.python.utils.getModuleName +import java.io.File + +fun findCurrentPythonModule( + directoriesForSysPath: Collection, + sourceFile: String +): Optional { + directoriesForSysPath.forEach { path -> + val module = getModuleName(path.toAbsolutePath(), sourceFile.toAbsolutePath()) + if (module != null) + return Success(module) + } + return Fail("Couldn't find path for $sourceFile in --sys-path option. Please, specify it.") +} + +fun String.toAbsolutePath(): String = + File(this).canonicalPath + +fun writeToFileAndSave(filename: String, fileContent: String) { + val file = File(filename) + file.parentFile?.mkdirs() + file.writeText(fileContent) +} diff --git a/utbot-cli-python/src/main/resources/log4j2.xml b/utbot-cli-python/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..3d6ee82bcf --- /dev/null +++ b/utbot-cli-python/src/main/resources/log4j2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utbot-cli-python/src/main/resources/version.properties b/utbot-cli-python/src/main/resources/version.properties new file mode 100644 index 0000000000..956d6e337a --- /dev/null +++ b/utbot-cli-python/src/main/resources/version.properties @@ -0,0 +1,2 @@ +#to be populated during the build task +version=N/A \ No newline at end of file diff --git a/utbot-cli/Dockerfile b/utbot-cli/Dockerfile new file mode 100644 index 0000000000..9e4e97e1b8 --- /dev/null +++ b/utbot-cli/Dockerfile @@ -0,0 +1,28 @@ +FROM azul/zulu-openjdk:11.0.15-11.56.19 + +ARG DEBIAN_FRONTEND=noninteractive + +WORKDIR /usr/src/ + +RUN apt-get update \ + && apt-get install -y -q --no-install-recommends \ + wget \ + unzip \ + && rm -rf /var/lib/apt/lists/* + +# Install Kotlin compiler + +ENV KOTLIN_COMPILER_VERSION=1.8.0 +ENV KOTLIN_HOME="/opt/kotlin/kotlinc" +ENV PATH="${KOTLIN_HOME}/bin:${PATH}" + +RUN wget --no-verbose https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_COMPILER_VERSION}/kotlin-compiler-${KOTLIN_COMPILER_VERSION}.zip -O /tmp/${KOTLIN_COMPILER_VERSION}.zip \ + && unzip -q -d /opt/kotlin /tmp/${KOTLIN_COMPILER_VERSION}.zip + +# Install UTBot Java CLI + +ARG ARTIFACT_PATH +COPY ${ARTIFACT_PATH} . + +RUN UTBOT_JAVA_CLI_PATH="$(find /usr/src -type f -name 'utbot-cli*')" \ + && ln -s "${UTBOT_JAVA_CLI_PATH}" /usr/src/utbot-cli.jar diff --git a/utbot-cli/build.gradle b/utbot-cli/build.gradle index 9e2106f3e7..139bc81c4c 100644 --- a/utbot-cli/build.gradle +++ b/utbot-cli/build.gradle @@ -1,33 +1,36 @@ -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17 + freeCompilerArgs += ["-Xallow-result-return-type", "-Xsam-conversions=class"] + } +} -configurations { - fetchInstrumentationJar +tasks.withType(JavaCompile) { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_17 } -compileKotlin { - kotlinOptions { - allWarningsAsErrors = false - } +configurations { + fetchInstrumentationJar } dependencies { - api project(":utbot-framework") - api project(':utbot-summary') + implementation project(':utbot-framework') - implementation group: 'org.mockito', name: 'mockito-core', version: mockito_version + implementation group: 'org.mockito', name: 'mockito-core', version: mockitoVersion // Without this dependency testng tests do not run. implementation group: 'com.beust', name: 'jcommander', version: '1.48' - implementation group: 'org.testng', name: 'testng', version: testng_version - implementation group: 'junit', name: 'junit', version: junit4_version - implementation group: 'org.junit.platform', name: 'junit-platform-console-standalone', version: junit4_platform_version - implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version - implementation group: 'com.github.ajalt.clikt', name: 'clikt', version: clikt_version - implementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: junit5_version - implementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junit5_version - compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j2_version - compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4j2_version - implementation group: 'org.jacoco', name: 'org.jacoco.report', version: jacoco_version - fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration:'instrumentationArchive') + implementation group: 'org.testng', name: 'testng', version: testNgVersion + implementation group: 'junit', name: 'junit', version: junit4Version + implementation group: 'org.junit.platform', name: 'junit-platform-console-standalone', version: junit4PlatformVersion + implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlinLoggingVersion + implementation group: 'com.github.ajalt.clikt', name: 'clikt', version: cliktVersion + implementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: junit5Version + implementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junit5Version + implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j2Version + implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4j2Version + implementation group: 'org.jacoco', name: 'org.jacoco.report', version: jacocoVersion + fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration: 'instrumentationArchive') } processResources { @@ -40,6 +43,7 @@ task createProperties(dependsOn: processResources) { doLast { new File("$buildDir/resources/main/version.properties").withWriter { w -> Properties properties = new Properties() + //noinspection GroovyAssignabilityCheck properties['version'] = project.version.toString() properties.store w, null } @@ -59,8 +63,9 @@ jar { attributes 'JAR-Type': 'Fat JAR' } - version project.version + archiveVersion.set(project.version as String) + dependsOn configurations.runtimeClasspath from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } diff --git a/utbot-cli/src/main/kotlin/org/utbot/cli/BunchTestGeneratorCommand.kt b/utbot-cli/src/main/kotlin/org/utbot/cli/BunchTestGeneratorCommand.kt index f4b8e2e852..6b54026aa7 100644 --- a/utbot-cli/src/main/kotlin/org/utbot/cli/BunchTestGeneratorCommand.kt +++ b/utbot-cli/src/main/kotlin/org/utbot/cli/BunchTestGeneratorCommand.kt @@ -4,6 +4,7 @@ import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.options.default import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.choice +import mu.KotlinLogging import org.utbot.cli.util.createClassLoader import org.utbot.engine.Mocker import org.utbot.framework.plugin.api.ClassId @@ -14,9 +15,6 @@ import java.io.File import java.net.URLClassLoader import java.nio.file.Paths import java.time.temporal.ChronoUnit -import kotlin.reflect.KClass -import kotlin.reflect.jvm.jvmName -import mu.KotlinLogging private val logger = KotlinLogging.logger {} @@ -93,25 +91,25 @@ class BunchTestGeneratorCommand : GenerateTestsAbstractCommand( logger.debug { "Generating test for [$targetClassFqn] - started" } logger.debug { "Classpath to be used: ${newline()} $classPath ${newline()}" } - val classUnderTest: KClass<*> = loadClassBySpecifiedFqn(targetClassFqn) - val targetMethods = classUnderTest.targetMethods() - if (targetMethods.isEmpty()) return - - initializeEngine(workingDirectory) - - // utContext is used in `generateTestCases`, `generateTest`, `generateReport` + // utContext is used in `targetMethods`, `generate`, `generateTest`, `generateReport` withUtContext(UtContext(classLoader)) { + val classIdUnderTest = ClassId(targetClassFqn) + val targetMethods = classIdUnderTest.targetMethods() + if (targetMethods.isEmpty()) return + + val testCaseGenerator = initializeGenerator(workingDirectory) - val testClassName = "${classUnderTest.simpleName}Test" + val testClassName = "${classIdUnderTest.simpleName}Test" - val testCases = generateTestCases( + val testSets = generateTestSets( + testCaseGenerator, targetMethods, searchDirectory = workingDirectory, chosenClassesToMockAlways = (Mocker.defaultSuperClassesToMockAlwaysNames + classesToMockAlways) .mapTo(mutableSetOf()) { ClassId(it) } ) - val testClassBody = generateTest(classUnderTest, testClassName, testCases) + val testClassBody = generateTest(classIdUnderTest, testClassName, testSets) val outputArgAsFile = File(output ?: "") if (!outputArgAsFile.exists()) { @@ -120,7 +118,7 @@ class BunchTestGeneratorCommand : GenerateTestsAbstractCommand( val outputDir = "$outputArgAsFile${File.separator}" - val packageNameAsList = classUnderTest.jvmName.split('.').dropLast(1) + val packageNameAsList = classIdUnderTest.jvmName.split('.').dropLast(1) val path = Paths.get("${outputDir}${packageNameAsList.joinToString(separator = File.separator)}") path.toFile().mkdirs() diff --git a/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt b/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt index 3db74fb999..4d39fd2c1c 100644 --- a/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt +++ b/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt @@ -8,6 +8,7 @@ import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.unique import com.github.ajalt.clikt.parameters.types.choice import com.github.ajalt.clikt.parameters.types.long +import mu.KotlinLogging import org.utbot.common.PathUtil.classFqnToPath import org.utbot.common.PathUtil.replaceSeparator import org.utbot.common.PathUtil.toPath @@ -15,33 +16,32 @@ import org.utbot.common.PathUtil.toURL import org.utbot.common.toPath import org.utbot.engine.Mocker import org.utbot.framework.UtSettings -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.MockitoStaticMocking -import org.utbot.framework.codegen.NoStaticMocking -import org.utbot.framework.codegen.StaticsMocking -import org.utbot.framework.codegen.model.ModelBasedTestCodeGenerator -import org.utbot.framework.codegen.testFrameworkByName +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.MockitoStaticMocking +import org.utbot.framework.codegen.domain.NoStaticMocking +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.testFrameworkByName +import org.utbot.framework.codegen.generator.CodeGenerator +import org.utbot.framework.codegen.generator.CodeGeneratorParams +import org.utbot.framework.codegen.services.language.CgLanguageAssistant import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockFramework +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.TestCaseGenerator import org.utbot.framework.plugin.api.TreatOverflowAsError -import org.utbot.framework.plugin.api.UtBotTestCaseGenerator -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.summary.summarize +import org.utbot.framework.plugin.api.UtMethodTestSet +import org.utbot.framework.plugin.services.JdkInfoDefaultProvider +import org.utbot.summary.summarizeAll import java.io.File -import java.lang.reflect.Method import java.net.URLClassLoader import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths import java.time.LocalDateTime import java.time.temporal.ChronoUnit -import kotlin.reflect.KCallable -import kotlin.reflect.KClass -import kotlin.reflect.jvm.kotlinFunction -import mu.KotlinLogging private const val LONG_GENERATION_TIMEOUT = 1_200_000L @@ -50,7 +50,7 @@ private val logger = KotlinLogging.logger {} abstract class GenerateTestsAbstractCommand(name: String, help: String) : CliktCommand(name = name, help = help) { - abstract val classPath:String? + abstract val classPath: String? private val mockStrategy by option("-m", "--mock-strategy", help = "Defines the mock strategy") .choice( @@ -151,22 +151,20 @@ abstract class GenerateTestsAbstractCommand(name: String, help: String) : return Paths.get(classAbsolutePath) } - protected fun loadClassBySpecifiedFqn(classFqn: String): KClass<*> = - classLoader.loadClass(classFqn).kotlin - - protected fun generateTestCases( - targetMethods: List>, + protected fun generateTestSets( + testCaseGenerator: TestCaseGenerator, + targetMethods: List, sourceCodeFile: Path? = null, searchDirectory: Path, chosenClassesToMockAlways: Set - ): List = - UtBotTestCaseGenerator.generateForSeveralMethods( + ): List = + testCaseGenerator.generate( targetMethods, mockStrategy, chosenClassesToMockAlways, generationTimeout - ).map { - if (sourceCodeFile != null) it.summarize(sourceCodeFile.toFile(), searchDirectory) else it + ).let { + if (sourceCodeFile != null) it.summarizeAll(searchDirectory, sourceCodeFile.toFile()) else it } @@ -185,51 +183,50 @@ abstract class GenerateTestsAbstractCommand(name: String, help: String) : } } - protected fun generateTest(classUnderTest: KClass<*>, testClassname: String, testCases: List): String = + protected fun generateTest( + classUnderTest: ClassId, + testClassname: String, + testSets: List + ): String = initializeCodeGenerator( testFramework, classUnderTest - ).generateAsString(testCases, testClassname) + ).generateAsString(testSets, testClassname) - protected fun initializeEngine(workingDirectory: Path) { + protected fun initializeGenerator(workingDirectory: Path): TestCaseGenerator { val classPathNormalized = classLoader.urLs.joinToString(separator = File.pathSeparator) { it.toPath().absolutePath } - - // TODO: SAT-1566 - // Set UtSettings parameters. + // TODO: SAT-1566 Set UtSettings parameters. UtSettings.treatOverflowAsError = treatOverflowAsError == TreatOverflowAsError.AS_ERROR - UtBotTestCaseGenerator.init(workingDirectory, classPathNormalized, System.getProperty("java.class.path")) { false } + return TestCaseGenerator( + listOf(workingDirectory), + classPathNormalized, + System.getProperty("java.class.path"), + JdkInfoDefaultProvider().info + ) } - private fun initializeCodeGenerator(testFramework: String, classUnderTest: KClass<*>): ModelBasedTestCodeGenerator { + private fun initializeCodeGenerator(testFramework: String, classUnderTest: ClassId): CodeGenerator { val generateWarningsForStaticMocking = forceStaticMocking == ForceStaticMocking.FORCE && staticsMocking is NoStaticMocking - return ModelBasedTestCodeGenerator().apply { - init( + return CodeGenerator( + CodeGeneratorParams( testFramework = testFrameworkByName(testFramework), - classUnderTest = classUnderTest.java, - mockFramework = MockFramework.MOCKITO, // TODO: rewrite default mock framework system + classUnderTest = classUnderTest, + //TODO: Support Spring projects in utbot-cli if requested + projectType = ProjectType.PureJvm, codegenLanguage = codegenLanguage, + cgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage(codegenLanguage), staticsMocking = staticsMocking, forceStaticMocking = forceStaticMocking, - generateWarningsForStaticMocking = generateWarningsForStaticMocking + generateWarningsForStaticMocking = generateWarningsForStaticMocking, ) - } + ) } - protected fun KClass<*>.targetMethods() = - this.java.declaredMethods.mapNotNull { - toUtMethod(it, kClass = this) - } - - private fun toUtMethod(method: Method, kClass: KClass<*>): UtMethod<*>? = - method.kotlinFunction?.let { - UtMethod(it as KCallable<*>, kClass) - } ?: run { - logger.info("Method does not have a kotlin function: $method") - null - } + protected fun ClassId.targetMethods(): List = + allMethods.filter { it.classId == this }.toList() // only declared methods protected fun saveToFile(snippet: String, outputPath: String?) = outputPath?.let { diff --git a/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsCommand.kt b/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsCommand.kt index 0380efaa7b..a6837ca80f 100644 --- a/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsCommand.kt +++ b/utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsCommand.kt @@ -7,20 +7,24 @@ import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.required import com.github.ajalt.clikt.parameters.types.choice +import mu.KotlinLogging import org.utbot.common.PathUtil.toPath +import org.utbot.common.filterWhen import org.utbot.engine.Mocker +import org.utbot.framework.UtSettings import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.UtTestCase +import org.utbot.framework.plugin.api.UtMethodTestSet import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.isAbstract +import org.utbot.framework.plugin.api.util.isSynthetic import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.framework.util.isKnownImplicitlyDeclaredMethod import org.utbot.sarif.SarifReport import org.utbot.sarif.SourceFindingStrategyDefault import java.nio.file.Files import java.nio.file.Paths import java.time.temporal.ChronoUnit -import kotlin.reflect.KClass -import mu.KotlinLogging private val logger = KotlinLogging.logger {} @@ -47,8 +51,8 @@ class GenerateTestsCommand : help = "Specifies source code file for a generated test" ) .required() - .check("Must exist and ends with *.java suffix") { - it.endsWith(".java") && Files.exists(Paths.get(it)) + .check("Must exist and end with .java or .kt suffix") { + (it.endsWith(".java") || it.endsWith(".kt")) && Files.exists(Paths.get(it)) } private val projectRoot by option( @@ -90,32 +94,37 @@ class GenerateTestsCommand : logger.debug { "Generating test for [$targetClassFqn] - started" } logger.debug { "Classpath to be used: ${newline()} $classPath ${newline()}" } - val classUnderTest: KClass<*> = loadClassBySpecifiedFqn(targetClassFqn) - val targetMethods = classUnderTest.targetMethods() - initializeEngine(workingDirectory) - - if (targetMethods.isEmpty()) { - throw Exception("Nothing to process. No methods were provided") - } - // utContext is used in `generateTestCases`, `generateTest`, `generateReport` - withUtContext(UtContext(targetMethods.first().clazz.java.classLoader)) { + // utContext is used in `targetMethods`, `generate`, `generateTest`, `generateReport` + withUtContext(UtContext(classLoader)) { + val classIdUnderTest = ClassId(targetClassFqn) + val targetMethods = classIdUnderTest.targetMethods() + .filterWhen(UtSettings.skipTestGenerationForSyntheticAndImplicitlyDeclaredMethods) { + !it.isSynthetic && !it.isKnownImplicitlyDeclaredMethod + } + .filterNot { it.isAbstract } + val testCaseGenerator = initializeGenerator(workingDirectory) + + if (targetMethods.isEmpty()) { + throw Exception("Nothing to process. No methods were provided") + } val testClassName = output?.toPath()?.toFile()?.nameWithoutExtension - ?: "${classUnderTest.simpleName}Test" - val testCases = generateTestCases( + ?: "${classIdUnderTest.simpleName}Test" + val testSets = generateTestSets( + testCaseGenerator, targetMethods, Paths.get(sourceCodeFile), searchDirectory = workingDirectory, chosenClassesToMockAlways = (Mocker.defaultSuperClassesToMockAlwaysNames + classesToMockAlways) .mapTo(mutableSetOf()) { ClassId(it) } ) - val testClassBody = generateTest(classUnderTest, testClassName, testCases) + val testClassBody = generateTest(classIdUnderTest, testClassName, testSets) if (printToStdOut) { logger.info { testClassBody } } if (sarifReport != null) { - generateReport(targetClassFqn, testCases, testClassBody) + generateReport(targetClassFqn, testSets, testClassBody) } saveToFile(testClassBody, output) } @@ -128,7 +137,7 @@ class GenerateTestsCommand : } } - private fun generateReport(classFqn: String, testCases: List, testClassBody: String) = try { + private fun generateReport(classFqn: String, testSets: List, testClassBody: String) = try { // reassignments for smart casts val testsFilePath = output val projectRootPath = projectRoot @@ -143,9 +152,9 @@ class GenerateTestsCommand : else -> { val sourceFinding = SourceFindingStrategyDefault(classFqn, sourceCodeFile, testsFilePath, projectRootPath) - val report = SarifReport(testCases, testClassBody, sourceFinding).createReport() + val report = SarifReport(testSets, testClassBody, sourceFinding).createReport().toJson() saveToFile(report, sarifReport) - println("The report was saved to \"$sarifReport\". You can open it using the VS Code extension \"Sarif Viewer\".") + println("The report was saved to \"$sarifReport\".") } } } catch (t: Throwable) { diff --git a/utbot-core/build.gradle b/utbot-core/build.gradle deleted file mode 100644 index 271a7a5651..0000000000 --- a/utbot-core/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - id "com.github.johnrengelman.shadow" version "6.1.0" -} - -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" - -dependencies { - implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version - implementation group: 'net.java.dev.jna', name: 'jna-platform', version: '5.5.0' -} - -shadowJar { - configurations = [project.configurations.compileClasspath] - archiveClassifier.set('') - minimize() -} \ No newline at end of file diff --git a/utbot-core/build.gradle.kts b/utbot-core/build.gradle.kts new file mode 100644 index 0000000000..faada50cce --- /dev/null +++ b/utbot-core/build.gradle.kts @@ -0,0 +1,13 @@ +val kotlinLoggingVersion: String by rootProject +val junit4Version: String by rootProject + +plugins { + id("com.github.johnrengelman.shadow") version "7.1.2" +} + +dependencies { + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) + implementation(group = "net.java.dev.jna", name = "jna-platform", version = "5.5.0") + + testImplementation(group = "junit", name = "junit", version = junit4Version) +} \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/AbstractSettings.kt b/utbot-core/src/main/kotlin/org/utbot/common/AbstractSettings.kt new file mode 100644 index 0000000000..3c5e4bb78f --- /dev/null +++ b/utbot-core/src/main/kotlin/org/utbot/common/AbstractSettings.kt @@ -0,0 +1,182 @@ +package org.utbot.common + +import java.io.FileInputStream +import java.io.IOException +import java.io.InputStream +import java.util.* +import kotlin.Comparator +import mu.KLogger +import org.utbot.common.PathUtil.toPath +import kotlin.properties.PropertyDelegateProvider +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +interface SettingsContainer { + fun settingFor( + defaultValue: T, + range : Triple>? = null, + converter: (String) -> T + ): PropertyDelegateProvider> + + fun getInputStream() : InputStream? = null + + // Returns true iff some properties have non-default values + fun isCustomized() = false +} + +interface SettingsContainerFactory { + fun createSettingsContainer( + logger: KLogger, + defaultKeyForSettingsPath: String, + defaultSettingsPath: String? = null) : SettingsContainer +} + +internal open class PropertiesSettingsContainer( + private val logger: KLogger, + val defaultKeyForSettingsPath: String, + val defaultSettingsPath: String? = null): SettingsContainer { + companion object: SettingsContainerFactory { + override fun createSettingsContainer( + logger: KLogger, + defaultKeyForSettingsPath: String, + defaultSettingsPath: String? + ): SettingsContainer = PropertiesSettingsContainer(logger, defaultKeyForSettingsPath, defaultSettingsPath) + } + + private val properties = Properties().also { props -> + try { + getInputStream()?.use { + props.load(it) + } + } catch (e: IOException) { + logger.info(e) { e.message } + } + } + + override fun getInputStream() : InputStream? { + val settingsPath = System.getProperty(defaultKeyForSettingsPath) ?: defaultSettingsPath + val settingsPathFile = settingsPath?.toPath()?.toFile() + return if (settingsPathFile?.exists() == true) FileInputStream(settingsPathFile) else null + } + + private val settingsValues: MutableMap, Any?> = mutableMapOf() + private var customized: Boolean = false + + inner class SettingDelegate(val property: KProperty<*>, val initializer: () -> T): ReadWriteProperty { + private var value = initializer() + + init { + updateSettingValue() + } + + override operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value + + override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + this.value = value + updateSettingValue() + } + + private fun updateSettingValue() { + settingsValues[property] = value + } + } + + override fun settingFor( + defaultValue: T, + range : Triple>?, + converter: (String) -> T + ): PropertyDelegateProvider> { + return PropertyDelegateProvider { _, property -> + SettingDelegate(property) { + try { + properties.getProperty(property.name)?.let { + var parsedValue = converter.invoke(it) + range?.let { + // Coerce parsed value into the specified range + parsedValue = maxOf(parsedValue, range.first, range.third) + parsedValue = minOf(parsedValue, range.second, range.third) + } + customized = customized or (parsedValue != defaultValue) + return@SettingDelegate parsedValue + } + defaultValue + } catch (e: Throwable) { + logger.warn("Cannot parse value for ${property.name}, default value [$defaultValue] will be used instead") { e } + defaultValue + } + } + } + } + + override fun isCustomized(): Boolean { + return customized + } + + override fun toString(): String = + settingsValues + .mapKeys { it.key.name } + .entries + .sortedBy { it.key } + .joinToString(separator = System.lineSeparator()) { "\t${it.key}=${it.value}" } +} + +abstract class AbstractSettings( + logger: KLogger, + defaultKeyForSettingsPath: String, + defaultSettingsPath: String? = null) { + private val container: SettingsContainer = createSettingsContainer(logger, defaultKeyForSettingsPath, defaultSettingsPath) + init { + allSettings[defaultKeyForSettingsPath] = this + } + companion object : SettingsContainerFactory { + val allSettings = mutableMapOf() + private var factory: SettingsContainerFactory? = null + override fun createSettingsContainer( + logger: KLogger, + defaultKeyForSettingsPath: String, + defaultSettingsPath: String? + ): SettingsContainer { + return (factory ?: PropertiesSettingsContainer).createSettingsContainer(logger, defaultKeyForSettingsPath, defaultSettingsPath) + } + + fun setupFactory(factory: SettingsContainerFactory) { + this.factory = factory + } + } + + fun areCustomized(): Boolean = container.isCustomized() + + protected fun getProperty( + defaultValue: T, + range : Triple>?, + converter: (String) -> T + ): PropertyDelegateProvider> where T: Comparable { + return container.settingFor(defaultValue, range, converter) + } + + protected fun getProperty( + defaultValue: T, + converter: (String) -> T + ): PropertyDelegateProvider> { + return container.settingFor(defaultValue, null, converter) + } + + protected fun getBooleanProperty(defaultValue: Boolean) = getProperty(defaultValue, converter = { + //Invalid values shouldn't be parsed as "false" + if (it.equals("true", true)) true + else if (it.equals("false", true)) false + else defaultValue + }) + protected fun getIntProperty(defaultValue: Int) = getProperty(defaultValue, converter = String::toInt) + protected fun getIntProperty(defaultValue: Int, minValue: Int, maxValue: Int) = getProperty(defaultValue, Triple(minValue, maxValue, Comparator(Integer::compare)), String::toInt) + protected fun getLongProperty(defaultValue: Long) = getProperty(defaultValue, converter = String::toLong) + protected fun getLongProperty(defaultValue: Long, minValue: Long, maxValue: Long) = getProperty(defaultValue, Triple(minValue, maxValue, Comparator(Long::compareTo)), String::toLong) + protected fun getStringProperty(defaultValue: String) = getProperty(defaultValue) { it } + protected inline fun > getEnumProperty(defaultValue: T) = + getProperty(defaultValue) { enumValueOf(it) } + protected fun getListProperty(defaultValue: List) = + getProperty(defaultValue) { it.split(';') } + protected inline fun getListProperty(defaultValue: List, crossinline elementTransform: (String) -> T) = + getProperty(defaultValue) { it.split(';').map(elementTransform) } + override fun toString(): String = container.toString() +} \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/AnnotationUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/AnnotationUtil.kt new file mode 100644 index 0000000000..e396e00d28 --- /dev/null +++ b/utbot-core/src/main/kotlin/org/utbot/common/AnnotationUtil.kt @@ -0,0 +1,33 @@ +package org.utbot.common + +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Proxy + +/** + * Assigns [newValue] to specified [property] of [annotation]. + * + * NOTE! [annotation] instance is expected to be a [Proxy] + * using [sun.reflect.annotation.AnnotationInvocationHandler] + * making this function depend on JDK vendor and version. + * + * Example: `@ImportResource -> @ImportResource(value = "classpath:shark-config.xml")` + */ +fun patchAnnotation( + annotation: Annotation, + property: String, + newValue: Any? +) { + val proxyClass = Proxy::class.java + val hField = proxyClass.getDeclaredField("h") + hField.isAccessible = true + + val invocationHandler = hField[annotation] as InvocationHandler + + val annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler") + val memberValuesField = annotationInvocationHandlerClass.getDeclaredField("memberValues") + memberValuesField.isAccessible = true + + @Suppress("UNCHECKED_CAST") // unavoidable because of reflection + val memberValues = memberValuesField[invocationHandler] as MutableMap + memberValues[property] = newValue +} diff --git a/utbot-core/src/main/kotlin/org/utbot/common/ClassLoaderUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/ClassLoaderUtil.kt new file mode 100644 index 0000000000..ce7652a48a --- /dev/null +++ b/utbot-core/src/main/kotlin/org/utbot/common/ClassLoaderUtil.kt @@ -0,0 +1,14 @@ +package org.utbot.common + +import java.net.URLClassLoader + +/** + * Checks that the class given by its binary name is on classpath of this classloader. + * + * Note: if the specified class is on classpath, `true` is returned even when + * superclass (or implemented interfaces) aren't on the classpath. + */ +fun URLClassLoader.hasOnClasspath(classBinaryName: String): Boolean { + val classFqn = classBinaryName.replace('.', '/').plus(".class") + return this.findResource(classFqn) != null +} \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/DynamicProperties.kt b/utbot-core/src/main/kotlin/org/utbot/common/DynamicProperties.kt new file mode 100644 index 0000000000..666929bbbb --- /dev/null +++ b/utbot-core/src/main/kotlin/org/utbot/common/DynamicProperties.kt @@ -0,0 +1,110 @@ +package org.utbot.common + +import java.util.* + +/** + * @param TOwner used purely to make type system enforce the use of properties with correct receiver, + * e.g. if property `NotEmptyTypeFlag` is defined for `FuzzedType` it can't be used on `CgContext`. + * + * **See also:** [this post](https://stackoverflow.com/a/58219723/10536125). + */ +interface DynamicProperty + +data class InitialisedDynamicProperty( + val property: DynamicProperty, + val value: T +) + +fun DynamicProperty.withValue(value: T) = + InitialisedDynamicProperty(this, value) + +interface DynamicProperties { + val entries: Set> + + operator fun get(property: DynamicProperty): T? + fun getValue(property: DynamicProperty): T + operator fun contains(property: DynamicProperty): Boolean + + /** + * Two instances of [DynamicProperties] implementations are equal iff their [entries] are equal + */ + override fun equals(other: Any?): Boolean + override fun hashCode(): Int +} + +interface MutableDynamicProperties : DynamicProperties { + operator fun set(property: DynamicProperty, value: T) +} + +fun MutableDynamicProperties.getOrPut(property: DynamicProperty, default: () -> T): T { + if (property !in this) set(property, default()) + return getValue(property) +} + +fun Iterable>.toMutableDynamicProperties(): MutableDynamicProperties = + DynamicPropertiesImpl().also { properties -> + forEach { properties.add(it) } + } + +fun Iterable>.toDynamicProperties(): DynamicProperties = + toMutableDynamicProperties() + +fun mutableDynamicPropertiesOf( + vararg initialisedDynamicProperties: InitialisedDynamicProperty +): MutableDynamicProperties = initialisedDynamicProperties.asIterable().toMutableDynamicProperties() + +fun dynamicPropertiesOf( + vararg initialisedDynamicProperties: InitialisedDynamicProperty +): DynamicProperties = initialisedDynamicProperties.asIterable().toDynamicProperties() + +fun DynamicProperties.withoutProperty(property: DynamicProperty): DynamicProperties = + entries.filterNot { it.property == property }.toMutableDynamicProperties() + +operator fun DynamicProperties.plus(other: DynamicProperties): DynamicProperties = + (entries + other.entries).toMutableDynamicProperties() + +class DynamicPropertiesImpl : MutableDynamicProperties { + /** + * Actual type of [properties] should be `Map, T>`, but + * such type is not representable withing kotlin type system, hence unchecked casts are + * used later. + */ + private val properties = IdentityHashMap, Any?>() + override val entries: Set> + get() = properties.mapTo(mutableSetOf()) { unsafeInitializedDynamicProperty(it.key, it.value) } + + @Suppress("UNCHECKED_CAST") + private fun unsafeInitializedDynamicProperty(property: DynamicProperty, value: Any?) = + InitialisedDynamicProperty(property, value as T) + + @Suppress("UNCHECKED_CAST") + override fun get(property: DynamicProperty): T? = + properties[property] as T? + + @Suppress("UNCHECKED_CAST") + override fun getValue(property: DynamicProperty): T = + properties.getValue(property) as T + + override fun set(property: DynamicProperty, value: T) { + properties[property] = value + } + + override fun contains(property: DynamicProperty): Boolean = + property in properties + + fun add(initialisedDynamicProperty: InitialisedDynamicProperty) = + set(initialisedDynamicProperty.property, initialisedDynamicProperty.value) + + override fun toString(): String { + return "DynamicPropertiesImpl(properties=$properties)" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is DynamicProperties<*>) return false + return entries == other.entries + } + + override fun hashCode(): Int = + properties.hashCode() +} diff --git a/utbot-core/src/main/kotlin/org/utbot/common/FallbackClassLoader.kt b/utbot-core/src/main/kotlin/org/utbot/common/FallbackClassLoader.kt new file mode 100644 index 0000000000..044bdb93e2 --- /dev/null +++ b/utbot-core/src/main/kotlin/org/utbot/common/FallbackClassLoader.kt @@ -0,0 +1,99 @@ +package org.utbot.common + +import java.io.IOException +import java.net.URL +import java.net.URLClassLoader +import java.util.* + +/** + * [ClassLoader] implementation, that + * - first, attempts to load class/resource with [commonParent] class loader + * - next, attempts to load class/resource from `urls` + * - finally, attempts to load class/resource with `fallback` class loader + * + * More details can be found in [this post](https://medium.com/@isuru89/java-a-child-first-class-loader-cbd9c3d0305). + */ +class FallbackClassLoader( + urls: Array, + fallback: ClassLoader, + private val commonParent: ClassLoader = fallback.parent, +) : URLClassLoader(urls, fallback) { + + @Throws(ClassNotFoundException::class) + override fun loadClass(name: String, resolve: Boolean): Class<*>? { + // has the class loaded already? + var loadedClass = findLoadedClass(name) + if (loadedClass == null) { + try { + loadedClass = commonParent.loadClass(name) + } catch (ex: ClassNotFoundException) { + // class not found in common parent loader... silently skipping + } + try { + // find the class from given jar urls as in first constructor parameter. + if (loadedClass == null) { + loadedClass = findClass(name) + } + } catch (e: ClassNotFoundException) { + // class is not found in the given urls. + // Let's try it in fallback classloader. + // If class is still not found, then this method will throw class not found ex. + loadedClass = super.loadClass(name, resolve) + } + } + if (resolve) { // marked to resolve + resolveClass(loadedClass) + } + return loadedClass + } + + @Throws(IOException::class) + override fun getResources(name: String): Enumeration { + val allRes: MutableList = LinkedList() + + // load resources from common parent loader + val commonParentResources: Enumeration? = commonParent.getResources(name) + if (commonParentResources != null) { + while (commonParentResources.hasMoreElements()) { + allRes.add(commonParentResources.nextElement()) + } + } + + // load resource from this classloader + val thisRes: Enumeration? = findResources(name) + if (thisRes != null) { + while (thisRes.hasMoreElements()) { + allRes.add(thisRes.nextElement()) + } + } + + // then try finding resources from fallback classloaders + val parentRes: Enumeration? = super.findResources(name) + if (parentRes != null) { + while (parentRes.hasMoreElements()) { + allRes.add(parentRes.nextElement()) + } + } + return object : Enumeration { + var it: Iterator = allRes.iterator() + override fun hasMoreElements(): Boolean { + return it.hasNext() + } + + override fun nextElement(): URL { + return it.next() + } + } + } + + override fun getResource(name: String): URL? { + var res: URL? = commonParent.getResource(name) + if (res === null) { + res = findResource(name) + } + if (res === null) { + res = super.getResource(name) + } + return res + } +} \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/FileUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/FileUtil.kt index cab4e7ff33..84ce9f0f0a 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/FileUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/FileUtil.kt @@ -15,12 +15,12 @@ import java.time.Duration import java.util.concurrent.TimeUnit import java.util.zip.ZipFile import kotlin.concurrent.thread -import kotlin.reflect.KClass import kotlin.streams.asSequence import mu.KotlinLogging +import java.util.zip.ZipEntry -fun KClass<*>.toClassFilePath(): String { - val name = requireNotNull(java.name) { "Class is local or anonymous" } +fun Class<*>.toClassFilePath(): String { + val name = requireNotNull(name) { "Class is local or anonymous" } return "${name.replace('.', '/')}.class" } @@ -31,12 +31,14 @@ object FileUtil { fun extractArchive( archiveFile: Path, destPath: Path, - vararg options: CopyOption = arrayOf(StandardCopyOption.REPLACE_EXISTING) + vararg options: CopyOption = arrayOf(StandardCopyOption.REPLACE_EXISTING), + extractOnlySuchEntriesPredicate: (ZipEntry) -> Boolean = { true } ) { Files.createDirectories(destPath) ZipFile(archiveFile.toFile()).use { archive -> val entries = archive.stream().asSequence() + .filter(extractOnlySuchEntriesPredicate) .sortedBy { it.name } .toList() @@ -65,30 +67,33 @@ object FileUtil { * Deletes all the files and folders from the java unit-test temp directory that are older than [daysLimit]. */ fun clearTempDirectory(daysLimit: Int) { - val currentTimeInMillis = System.currentTimeMillis() - - val files = utBotTempDirectory.toFile().listFiles() ?: return + (utBotTempDirectory.toFile().listFiles() ?: return).filter { isOld(it, daysLimit) } + .forEach { it.deleteRecursively() } + } - files.filter { - val creationTime = Files.readAttributes(it.toPath(), BasicFileAttributes::class.java).creationTime() - TimeUnit.MILLISECONDS.toDays(currentTimeInMillis - creationTime.toMillis()) > daysLimit - }.forEach { it.deleteRecursively() } + private fun isOld(it: File, daysLimit: Int): Boolean { + val creationTime = Files.readAttributes(it.toPath(), BasicFileAttributes::class.java).creationTime() + return TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis() - creationTime.toMillis()) > daysLimit } fun createTempDirectory(prefix: String): Path { return createTempDirectory(utBotTempDirectory, prefix) } + fun createTempFile(prefix: String, suffix: String): Path { + return Files.createTempFile(utBotTempDirectory, prefix, suffix) + } + /** * Copy the class file for given [classes] to temporary folder. * It can be used for Soot analysis. */ - fun isolateClassFiles(vararg classes: KClass<*>): File { + fun isolateClassFiles(vararg classes: Class<*>): File { val tempDir = createTempDirectory("generated-").toFile() for (clazz in classes) { val path = clazz.toClassFilePath() - val resource = clazz.java.classLoader.getResource(path) ?: error("No such file: $path") + val resource = clazz.classLoader.getResource(path) ?: error("No such file: $path") if (resource.toURI().scheme == "jar") { val jarLocation = resource.toURI().extractJarName() @@ -105,9 +110,9 @@ object FileUtil { return tempDir } - private fun URI.extractJarName(): URI = URI(this.schemeSpecificPart.substringBefore("!")) + private fun URI.extractJarName(): URI = URI(this.schemeSpecificPart.substringBefore("!").replace(" ", "%20")) - private fun extractClassFromArchive(archiveFile: Path, clazz: KClass<*>, destPath: Path) { + private fun extractClassFromArchive(archiveFile: Path, clazz: Class<*>, destPath: Path) { val classFilePath = clazz.toClassFilePath() ZipFile(archiveFile.toFile()).use { archive -> val entry = archive.stream().asSequence().filter { it.name.normalizePath() == classFilePath }.single() @@ -121,9 +126,9 @@ object FileUtil { * Locates class path by class. * It can be used for Soot analysis. */ - fun locateClassPath(clazz: KClass<*>): File? { + fun locateClassPath(clazz: Class<*>): File? { val path = clazz.toClassFilePath() - val resource = requireNotNull(clazz.java.classLoader.getResource(path)) { "No such file: $path" } + val resource = requireNotNull(clazz.classLoader.getResource(path)) { "No such file: $path" } if (resource.toURI().scheme == "jar") return null val fullPath = resource.path.removeSuffix(path) return File(fullPath) @@ -132,9 +137,9 @@ object FileUtil { /** * Locates class and returns class location. Could be directory or jar based. */ - fun locateClass(clazz: KClass<*>): ClassLocation { + fun locateClass(clazz: Class<*>): ClassLocation { val path = clazz.toClassFilePath() - val resource = requireNotNull(clazz.java.classLoader.getResource(path)) { "No such file: $path" } + val resource = requireNotNull(clazz.classLoader.getResource(path)) { "No such file: $path" } return if (resource.toURI().scheme == "jar") { val jarLocation = resource.toURI().extractJarName() JarClassLocation(Paths.get(jarLocation)) @@ -173,12 +178,26 @@ object FileUtil { /** * Extracts archive to temp directory and returns path to directory. */ - fun extractArchive(archiveFile: Path): Path { + fun extractArchive(archiveFile: Path, extractOnlySuchEntriesPredicate: (ZipEntry) -> Boolean = { true }): Path { val tempDir = createTempDirectory(TEMP_DIR_NAME).toFile().apply { deleteOnExit() } - extractArchive(archiveFile, tempDir.toPath()) + extractArchive(archiveFile, tempDir.toPath(), extractOnlySuchEntriesPredicate = extractOnlySuchEntriesPredicate) return tempDir.toPath() } + /** + * Extracts specified directory (with all contents) from archive to temp directory and returns path to it. + */ + fun extractDirectoryFromArchive(archiveFile: Path, directoryName: String): Path? { + val extractedJarDirectory = extractArchive(archiveFile) { entry -> + entry.name.normalizePath().startsWith(directoryName) + } + val extractedTargetDirectoryPath = extractedJarDirectory.resolve(directoryName) + if (!extractedTargetDirectoryPath.toFile().exists()) { + return null + } + return extractedTargetDirectoryPath + } + /** * Returns the path to the class files for the given ClassLocation. */ @@ -223,6 +242,18 @@ object FileUtil { this.parentFile.mkdirs() this.createNewFile() } + + // https://stackoverflow.com/a/68822715 + fun byteCountToDisplaySize(bytes: Long): String { + val bytesInDouble = bytes.toDouble() + + return when { + bytesInDouble >= 1 shl 30 -> "%.1f GB".format(bytesInDouble / (1 shl 30)) + bytesInDouble >= 1 shl 20 -> "%.1f MB".format(bytesInDouble / (1 shl 20)) + bytesInDouble >= 1 shl 10 -> "%.0f kB".format(bytesInDouble / (1 shl 10)) + else -> "$bytes bytes" + } + } } /** diff --git a/utbot-core/src/main/kotlin/org/utbot/common/FilterWhen.kt b/utbot-core/src/main/kotlin/org/utbot/common/FilterWhen.kt new file mode 100644 index 0000000000..e85e1afa78 --- /dev/null +++ b/utbot-core/src/main/kotlin/org/utbot/common/FilterWhen.kt @@ -0,0 +1,21 @@ +package org.utbot.common + +/** + * If [condition] is true, returns a list containing only elements matching [predicate]. + * Otherwise, returns list with all elements of collection + */ +inline fun Iterable.filterWhen(condition: Boolean, predicate: (T) -> Boolean): List = + if (condition) + this.filter(predicate) + else + this.toList() + +/** + * If [condition] is true, returns a sequence containing only elements matching [predicate]. + * Otherwise, leaves sequence unchanged + */ +fun Sequence.filterWhen(condition: Boolean, predicate: (T) -> Boolean): Sequence = + if (condition) + this.filter(predicate) + else + this \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/HackUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/HackUtil.kt index 3b9eb2e827..e375e2ee0b 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/HackUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/HackUtil.kt @@ -25,26 +25,55 @@ inline fun heuristic(reason: WorkaroundReason, block: () -> T): T = block() /** * Explains reason for applied workaround. - * - * Workarounds are: - * - HACK - hacks behaviour for contest. Shall be removed sometimes - * - MAKE_SYMBOLIC - Returns a new symbolic value with proper type instead of function result (i.e. for wrappers) - * - IGNORE_SORT_INEQUALITY -- Ignores pairs of particular sorts in stores and selects - * - RUN_CONCRETE -- Runs something concretely instead of symbolic run - * - REMOVE_ANONYMOUS_CLASSES -- Remove anonymous classes from the results passed to the code generation till it doesn't support their generation - * - IGNORE_MODEL_TYPES_INEQUALITY -- Ignore the fact that model before and model after have different types - * - LONG_CODE_FRAGMENTS -- Comment too long blocks of code due to JVM restrictions [65536 bytes](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3) - * - ARRAY_ELEMENT_TYPES_ALWAYS_NULLABLE -- Can't infer nullability for array elements from allocation statement, so make them nullable - * Note: - * - MAKE_SYMBOLIC can lose additional path constraints or branches from function call */ enum class WorkaroundReason { + /** + * Hacks behaviour for contest. Shall be removed sometimes + */ HACK, + /** + * Returns a new symbolic value with proper type instead of function result (i.e. for wrappers) + * + * [MAKE_SYMBOLIC] can lose additional path constraints or branches from function call + */ MAKE_SYMBOLIC, + /** + * Ignores pairs of particular sorts in stores and selects + */ IGNORE_SORT_INEQUALITY, + /** + * Runs something concretely instead of symbolic run + */ RUN_CONCRETE, + /** + * Remove anonymous classes from the results passed to the code generation till it doesn't support their generation + */ REMOVE_ANONYMOUS_CLASSES, + /** + * Ignore the fact that model before and model after have different types + */ IGNORE_MODEL_TYPES_INEQUALITY, + /** + * Comment too long blocks of code due to JVM restrictions [65536 bytes](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3) + */ LONG_CODE_FRAGMENTS, + /** + * Can't infer nullability for array elements from allocation statement, so make them nullable + */ ARRAY_ELEMENT_TYPES_ALWAYS_NULLABLE, + /** + * We won't branch on static field from trusted libraries and won't add them to staticsBefore. For now, it saves us + * from setting [SecurityManager] and other suspicious stuff, but it can lead to coverage regression and thus it + * requires thorough [investigation](https://github.com/UnitTestBot/UTBotJava/issues/716). + */ + IGNORE_STATICS_FROM_TRUSTED_LIBRARIES, + /** + * Methods that return [java.util.stream.BaseStream] as a result, can return them ”dirty” - consuming of them lead to the exception. + * The symbolic engine and concrete execution create UtStreamConsumingFailure executions in such cases. To warn a + * user about unsafety of using such “dirty” streams, code generation consumes them (mostly with `toArray` methods) + * and asserts exception. Unfortunately, it doesn't work well for parametrized tests - they create assertions relying on + * such-called “generic execution”, so resulted tests always contain `deepEquals` for streams, and we cannot easily + * construct `toArray` invocation (because streams cannot be consumed twice). + */ + CONSUME_DIRTY_STREAMS, } \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/JarUtils.kt b/utbot-core/src/main/kotlin/org/utbot/common/JarUtils.kt new file mode 100644 index 0000000000..765d520514 --- /dev/null +++ b/utbot-core/src/main/kotlin/org/utbot/common/JarUtils.kt @@ -0,0 +1,46 @@ +package org.utbot.common + +import java.io.File +import java.net.URL +import java.nio.file.Files +import java.nio.file.StandardCopyOption +import java.util.* + +object JarUtils { + private const val UNKNOWN_MODIFICATION_TIME = 0L + + fun extractJarFileFromResources(jarFileName: String, jarResourcePath: String, targetDirectoryName: String): File { + val resource = this::class.java.classLoader.getResource(jarResourcePath) + ?: error("Unable to find \"$jarResourcePath\" in resources, make sure it's on the classpath") + + val targetDirectory = utBotTempDirectory.toFile().resolve(targetDirectoryName).toPath() + fun extractToSubDir(subDir: String) = + Files.createDirectories(targetDirectory.resolve(subDir)).toFile().resolve(jarFileName).also { jarFile -> + updateJarIfRequired(jarFile, resource) + } + + // We attempt to always extract jars to same locations, to avoid eating up drive space with + // every UtBot launch, but we may fail to do so if multiple processes are running in parallel. + repeat(10) { i -> + runCatching { + return extractToSubDir(i.toString()) + } + } + return extractToSubDir(UUID.randomUUID().toString()) + } + + private fun updateJarIfRequired(jarFile: File, resource: URL) { + val resourceConnection = resource.openConnection() + resourceConnection.getInputStream().use { inputStream -> + val lastResourceModification = resourceConnection.lastModified + if ( + !jarFile.exists() || + jarFile.lastModified() == UNKNOWN_MODIFICATION_TIME || + lastResourceModification == UNKNOWN_MODIFICATION_TIME || + jarFile.lastModified() < lastResourceModification + ) { + Files.copy(inputStream, jarFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + } + } + } +} \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/JvmUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/JvmUtil.kt new file mode 100644 index 0000000000..86868349ec --- /dev/null +++ b/utbot-core/src/main/kotlin/org/utbot/common/JvmUtil.kt @@ -0,0 +1,7 @@ +package org.utbot.common + +private val javaSpecificationVersion = System.getProperty("java.specification.version") +val isJvm8 = javaSpecificationVersion.equals("1.8") +val isJvm9Plus = !javaSpecificationVersion.contains(".") && javaSpecificationVersion.toInt() >= 9 + +fun osSpecificJavaExecutable() = if (isWindows) "javaw" else "java" \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt index 1f6bb848a5..92e4d8d398 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/KClassUtil.kt @@ -1,31 +1,16 @@ package org.utbot.common -import java.lang.reflect.Field import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method - -val Class<*>.packageName: String get() = `package`?.name?:"" - -fun Class<*>.findField(name: String): Field = - findFieldOrNull(name) ?: error("Can't find field $name in $this") - -fun Class<*>.findFieldOrNull(name: String): Field? = generateSequence(this) { it.superclass } - .mapNotNull { - try { - it.getField(name) - } catch (e: NoSuchFieldException) { - try { - it.getDeclaredField(name) - } catch (e: NoSuchFieldException) { - null - } - } - } - .firstOrNull() - +/** + * Invokes [this] method of passed [obj] instance (null for static methods) with the passed [args] arguments. + * NOTE: vararg parameters must be passed as an array of the corresponding type. + */ fun Method.invokeCatching(obj: Any?, args: List) = try { - Result.success(invoke(obj, *args.toTypedArray())) + val invocation = invoke(obj, *args.toTypedArray()) + + Result.success(invocation) } catch (e: InvocationTargetException) { Result.failure(e.targetException) -} +} \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/Logging.kt b/utbot-core/src/main/kotlin/org/utbot/common/Logging.kt index eb6a5a7a98..bb24c9142c 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/Logging.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/Logging.kt @@ -1,26 +1,23 @@ package org.utbot.common import mu.KLogger +import java.time.format.DateTimeFormatter +val timeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS") +val dateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy_HH-mm-ss") -class LoggerWithLogMethod(val logger: KLogger, val logMethod: (() -> Any?) -> Unit) +class LoggerWithLogMethod(val logMethod: (() -> Any?) -> Unit) -fun KLogger.info(): LoggerWithLogMethod = LoggerWithLogMethod(this, this::info) -fun KLogger.debug(): LoggerWithLogMethod = LoggerWithLogMethod(this, this::debug) -fun KLogger.trace(): LoggerWithLogMethod = LoggerWithLogMethod(this, this::trace) +fun KLogger.info(): LoggerWithLogMethod = LoggerWithLogMethod(this::info) +fun KLogger.debug(): LoggerWithLogMethod = LoggerWithLogMethod(this::debug) +fun KLogger.trace(): LoggerWithLogMethod = LoggerWithLogMethod(this::trace) - -/** - * - */ fun elapsedSecFrom(startNano: Long): String { val elapsedNano = System.nanoTime() - startNano val elapsedS = elapsedNano.toDouble() / 1_000_000_000 return String.format("%.3f", elapsedS) + " sec" } - - /** * Structured logging. * @@ -38,16 +35,22 @@ fun elapsedSecFrom(startNano: Long): String { * You can use [closingComment] to add some result-depending comment to "Finished:" message. Special "" comment * is added if non local return happened in [block] */ -inline fun LoggerWithLogMethod.bracket( - msg: String, +inline fun LoggerWithLogMethod.measureTime( + crossinline msgBlock: () -> String, crossinline closingComment: (Result) -> Any? = { "" }, block: () -> T ): T { - logMethod { "Started: $msg" } + var msg = "" + + logMethod { + msg = msgBlock() + "Started: $msg" + } + val startNano = System.nanoTime() var alreadyLogged = false - var res : Maybe = Maybe.empty() + var res: Maybe = Maybe.empty() try { // Note: don't replace this one with runCatching, otherwise return from lambda breaks "finished" logging. res = Maybe(block()) @@ -66,11 +69,28 @@ inline fun LoggerWithLogMethod.bracket( } } -inline fun KLogger.catch(block: () -> T): T? { +inline fun KLogger.catchException(message: String = "Isolated", block: () -> T): T? { return try { block() } catch (e: Throwable) { - this.error(e) { "Isolated" } + this.error(message, e) null } } + +inline fun KLogger.logException(message: String = "Exception occurred", block: () -> T): T { + return try { + block() + } catch (e: Throwable) { + this.error(message, e) + throw e + } +} + +inline fun silent(block: () -> T): T? { + return try { + block() + } catch (_: Throwable) { + null + } +} \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/OsUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/OsUtil.kt new file mode 100644 index 0000000000..3c8a1b53d6 --- /dev/null +++ b/utbot-core/src/main/kotlin/org/utbot/common/OsUtil.kt @@ -0,0 +1,8 @@ +package org.utbot.common + +import java.util.* + +private val os = System.getProperty("os.name").lowercase(Locale.getDefault()) +val isWindows = os.startsWith("windows") +val isUnix = !isWindows +val isMac = os.startsWith("mac") \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/PathUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/PathUtil.kt index 3f5f0070e8..b74cb39a56 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/PathUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/PathUtil.kt @@ -1,20 +1,30 @@ package org.utbot.common +import java.io.File +import java.net.MalformedURLException import java.net.URL +import java.net.URLClassLoader import java.nio.file.Path import java.nio.file.Paths +import java.util.* object PathUtil { + fun String.toPathOrNull(): Path? = try { + Paths.get(this) + } catch (e: Throwable) { + null + } + /** - * Creates a Path from the String + * Creates a Path from the String. */ fun String.toPath(): Path = Paths.get(this) /** - * Finds a path for the [other] relative to the [root] if it is possible + * Finds a path for the [other] relative to the [root] if it is possible. * - * Example: safeRelativize("C:/project/", "C:/project/src/Main.java") = "src/Main.java" + * Example: safeRelativize("C:/project/", "C:/project/src/Main.java") = "src/Main.java". */ fun safeRelativize(root: String?, other: String?): String? { if (root == null || other == null) @@ -28,9 +38,9 @@ object PathUtil { } /** - * Removes class fully qualified name from absolute path to the file if it is possible + * Removes class fully qualified name from absolute path to the file if it is possible. * - * Example: removeClassFqnFromPath("C:/project/src/com/Main.java", "com.Main") = "C:/project/src/" + * Example: removeClassFqnFromPath("C:/project/src/com/Main.java", "com.Main") = "C:/project/src/". */ fun removeClassFqnFromPath(sourceAbsolutePath: String?, classFqn: String?): String? { if (sourceAbsolutePath == null || classFqn == null) @@ -48,9 +58,9 @@ object PathUtil { } /** - * Resolves `pathToResolve` against `absolutePath` and checks if a resolved path exists + * Resolves [toResolve] against [absolute] and checks if a resolved path exists. * - * Example: resolveIfExists("C:/project/src/", "Main.java") = "C:/project/src/Main.java" + * Example: resolveIfExists("C:/project/src/", "Main.java") = "C:/project/src/Main.java". */ fun resolveIfExists(absolute: String, toResolve: String): String? { val absolutePath = absolute.toPath() @@ -64,19 +74,19 @@ object PathUtil { } /** - * Replaces '\\' in the [path] with '/' + * Replaces '\\' in the [path] with '/'. */ fun replaceSeparator(path: String): String = path.replace('\\', '/') /** - * Replaces '.' in the [classFqn] with '/' + * Replaces '.' in the [classFqn] with '/'. */ fun classFqnToPath(classFqn: String): String = classFqn.replace('.', '/') /** - * Returns a URL to represent this path + * Returns a URL to represent this path. */ fun Path.toURL(): URL = this.toUri().toURL() @@ -88,9 +98,18 @@ object PathUtil { """${fileName}""" /** - * Returns the extension of this file (including the dot) + * Returns the extension of this file (including the dot). */ val Path.fileExtension: String get() = "." + this.toFile().extension + @JvmStatic + fun getUrlsFromClassLoader(contextClassLoader: ClassLoader): Array { + return if (contextClassLoader is URLClassLoader) { + contextClassLoader.urLs + } else { + System.getProperty("java.class.path").split(File.pathSeparator).dropLastWhile { it.isEmpty() } + .map { File(it).toURL() }.toTypedArray() + } + } } \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/ProcessUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/ProcessUtil.kt index fb6e0b4dbd..3371fe29c8 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/ProcessUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/ProcessUtil.kt @@ -1,35 +1,69 @@ package org.utbot.common +import com.sun.jna.Library +import com.sun.jna.Native import com.sun.jna.Pointer import com.sun.jna.platform.win32.Kernel32 import com.sun.jna.platform.win32.WinNT -import java.lang.management.ManagementFactory -val Process.pid : Long get() = try { - when (javaClass.name) { - "java.lang.UNIXProcess" -> { - val fPid = javaClass.getDeclaredField("pid") - fPid.withAccessibility { fPid.getLong(this) } +/** + * working pid for jvm 8 and 9+ + */ +val Process.getPid: Long + get() = try { + if (isJvm9Plus) { + // because we cannot reference Java9+ API here + ClassLoader.getSystemClassLoader().loadClass("java.lang.Process").getDeclaredMethod("pid").invoke(this) as Long + } else { + when (javaClass.name) { + "java.lang.UNIXProcess" -> { + val fPid = javaClass.getDeclaredField("pid") + fPid.withAccessibility { fPid.getLong(this) } - } - "java.lang.Win32Process", "java.lang.ProcessImpl" -> { - val fHandle = javaClass.getDeclaredField("handle") - fHandle.withAccessibility { - val handle = fHandle.getLong(this) - val winntHandle = WinNT.HANDLE() - winntHandle.pointer = Pointer.createConstant(handle) - Kernel32.INSTANCE.GetProcessId(winntHandle).toLong() + } + + "java.lang.Win32Process", "java.lang.ProcessImpl" -> { + val fHandle = javaClass.getDeclaredField("handle") + fHandle.withAccessibility { + val handle = fHandle.getLong(this) + val winntHandle = WinNT.HANDLE() + winntHandle.pointer = Pointer.createConstant(handle) + Kernel32.INSTANCE.GetProcessId(winntHandle).toLong() + } + } + + else -> -2 } } - else -> -1 - } -} catch (e: Exception) { -2 } - -fun getCurrentProcessId() = - try { - ManagementFactory.getRuntimeMXBean()?.let { - it.name.split("@")[0].toLong() - } ?: -1 - } catch (t: Throwable) { + } catch (e: Exception) { -1 - } \ No newline at end of file + } + +private interface CLibrary : Library { + fun getpid(): Int + + companion object { + val INSTANCE = Native.load("c", CLibrary::class.java) as CLibrary + } +} + +/** + * working for jvm 8 and 9+ + */ +val currentProcessPid: Long + get() = + try { + if (isJvm9Plus) { + val handleClass = ClassLoader.getSystemClassLoader().loadClass("java.lang.ProcessHandle") + val handle = handleClass.getDeclaredMethod("current").invoke(handleClass) + handleClass.getDeclaredMethod("pid").invoke(handle) as Long + } else { + if (isWindows) { + Kernel32.INSTANCE.GetCurrentProcessId() + } else { + CLibrary.INSTANCE.getpid() + }.toLong() + } + } catch (e: Throwable) { + -1 + } \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/ReflectionUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/ReflectionUtil.kt index 160b0adf3f..211e13a037 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/ReflectionUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/ReflectionUtil.kt @@ -1,10 +1,12 @@ package org.utbot.common import org.utbot.common.Reflection.setModifiers +import sun.misc.Unsafe import java.lang.reflect.AccessibleObject import java.lang.reflect.Field +import java.lang.reflect.Member +import java.lang.reflect.Method import java.lang.reflect.Modifier -import sun.misc.Unsafe object Reflection { val unsafe: Unsafe @@ -15,7 +17,19 @@ object Reflection { unsafe = f.get(null) as Unsafe } - private val modifiersField: Field = Field::class.java.getDeclaredField("modifiers") + val getDeclaredFields0Method: Method = + Class::class.java.getDeclaredMethod("getDeclaredFields0", Boolean::class.java).apply { + isAccessible = true + } + + + @Suppress("UNCHECKED_CAST") + val fields: Array = + getDeclaredFields0Method.invoke(Field::class.java, false) as Array + + // TODO: works on JDK 8-17. Doesn't work on JDK 18 + val modifiersField: Field = + fields.first { it.name == "modifiers" } init { modifiersField.isAccessible = true @@ -24,12 +38,17 @@ object Reflection { fun setModifiers(field: Field, modifiers: Int) { modifiersField.set(field, modifiers) } + + fun isModifiersAccessible(): Boolean { + return modifiersField.isAccessible + } } -inline fun AccessibleObject.withAccessibility(block: () -> R): R { +inline fun T.withAccessibility(block: T.() -> R): R { val prevAccessibility = isAccessible + isAccessible = true + try { - isAccessible = true return block() } finally { isAccessible = prevAccessibility @@ -39,15 +58,68 @@ inline fun AccessibleObject.withAccessibility(block: () -> R): R { /** * Executes given [block] with removed final modifier and restores it back after execution. * Also sets `isAccessible` to `true` and restores it back. + * + * Please note, that this function doesn't guarantee that reflection calls in the [block] always succeed. The problem is + * that prior calls to reflection may result in caching internal FieldAccessor field which is not suitable for setting + * [this]. But if you didn't call reflection previously, this function should help. + * + * Also note, that primitive static final fields may be inlined, so may not be possible to change. */ -inline fun Field.withRemovedFinalModifier(block: () -> R): R { +inline fun Field.withAccessibility(block: Field.() -> R): R { val prevModifiers = modifiers + val prevAccessibility = isAccessible + + isAccessible = true setModifiers(this, modifiers and Modifier.FINAL.inv()) - this.withAccessibility { - try { - return block() - } finally { - setModifiers(this, prevModifiers) - } + + try { + return block() + } finally { + isAccessible = prevAccessibility + setModifiers(this, prevModifiers) } -} \ No newline at end of file +} + +// utility properties + +val Member.isAbstract + get() = Modifier.isAbstract(modifiers) + +val Member.isStatic + get() = Modifier.isStatic(modifiers) + +val Member.isPrivate + get() = Modifier.isPrivate(modifiers) + +val Member.isPublic + get() = Modifier.isPublic(modifiers) + +val Member.isFinal + get() = Modifier.isFinal(modifiers) + +val Member.isProtected + get() = Modifier.isProtected(modifiers) + +val Class<*>.isAbstract + get() = Modifier.isAbstract(modifiers) + +val Class<*>.isStatic + get() = Modifier.isStatic(modifiers) + +val Class<*>.isPrivate + get() = Modifier.isPrivate(modifiers) + +val Class<*>.isPublic + get() = Modifier.isPublic(modifiers) + +val Class<*>.isFinal + get() = Modifier.isFinal(modifiers) + +val Class<*>.isProtected + get() = Modifier.isProtected(modifiers) + +val Class<*>.nameOfPackage: String + get() = `package`?.name?:"" + +val Class<*>.allNestedClasses: List> + get() = listOf(this) + this.declaredClasses.flatMap { it.allNestedClasses } \ No newline at end of file diff --git a/utbot-core/src/main/kotlin/org/utbot/common/StopWatch.kt b/utbot-core/src/main/kotlin/org/utbot/common/StopWatch.kt index 83ec7bb00c..1ff1b149fa 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/StopWatch.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/StopWatch.kt @@ -28,12 +28,19 @@ class StopWatch { startTime = System.currentTimeMillis() } } - - fun stop() { + + /** + * @param compensationMillis the duration in millis that should be subtracted from [elapsedMillis] to compensate + * for stopping and restarting [StopWatch] taking some time, can also be used to compensate for some activities, + * that are hard to directly detect (e.g. class loading). + * + * NOTE: [compensationMillis] will never cause [elapsedMillis] become negative. + */ + fun stop(compensationMillis: Long = 0) { lock.withLockInterruptibly { - startTime?.let { - elapsedMillis += (System.currentTimeMillis() - it) - startTime = null + startTime?.let { startTime -> + elapsedMillis += ((System.currentTimeMillis() - startTime) - compensationMillis).coerceAtLeast(0) + this.startTime = null } } } @@ -52,7 +59,7 @@ class StopWatch { } } - fun get(unit: TimeUnit) = lock.withLockInterruptibly { + fun get(unit: TimeUnit = TimeUnit.MILLISECONDS) = lock.withLockInterruptibly { unsafeUpdate() unit.convert(elapsedMillis, TimeUnit.MILLISECONDS) } diff --git a/utbot-core/src/main/kotlin/org/utbot/common/ThreadUtil.kt b/utbot-core/src/main/kotlin/org/utbot/common/ThreadUtil.kt index 453f2d2468..2dd7fb9969 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/ThreadUtil.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/ThreadUtil.kt @@ -2,8 +2,11 @@ package org.utbot.common import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.thread +import kotlin.concurrent.withLock import kotlin.properties.ReadOnlyProperty +import kotlin.random.Random import kotlin.reflect.KProperty @@ -24,31 +27,47 @@ class ThreadBasedExecutor { val threadLocal by threadLocalLazy { ThreadBasedExecutor() } } + /** + * Used to avoid calling [Thread.stop] during clean up. + * + * @see runCleanUpIfTimedOut + */ + private val timeOutCleanUpLock = ReentrantLock() + + /** + * `null` when either: + * - no tasks have yet been run + * - current task timed out, and we are waiting for its thread to die + */ + @Volatile private var thread: Thread? = null private var requestQueue = ArrayBlockingQueue<() -> Any?>(1) private var responseQueue = ArrayBlockingQueue>(1) + /** + * Can be called from lambda passed to [invokeWithTimeout]. + * [ThreadBasedExecutor] guarantees that it won't attempt to terminate [cleanUpBlock] with [Thread.stop]. + */ + fun runCleanUpIfTimedOut(cleanUpBlock: () -> Unit) { + timeOutCleanUpLock.withLock { + if (thread == null) + cleanUpBlock() + } + } /** * Invoke [action] with timeout. * * [stopWatch] is used to respect specific situations (such as class loading and transforming) while invoking. */ - fun invokeWithTimeout(timeoutMillis: Long, stopWatch: StopWatch? = null, action:() -> Any?) : Result? { - if (thread?.isAlive != true) { - requestQueue = ArrayBlockingQueue<() -> Any?>(1) - responseQueue = ArrayBlockingQueue>(1) - - thread = thread(name = "executor", isDaemon = true) { - try { - while (true) { - val next = requestQueue.take() - responseQueue.offer(kotlin.runCatching { next() }) - } - } catch (_: InterruptedException) {} - } - } + fun invokeWithTimeout( + timeoutMillis: Long, + stopWatch: StopWatch? = null, + threadDeathThrowPeriodMillis: Long = 10_000, + action:() -> Any? + ) : Result? { + ensureThreadIsAlive() requestQueue.offer { try { @@ -71,16 +90,46 @@ class ThreadBasedExecutor { if (res == null) { try { val t = thread ?: return res + thread = null t.interrupt() t.join(10) - if (t.isAlive) - @Suppress("DEPRECATION") - t.stop() + // to avoid race condition we need to wait for `t` to die + while (t.isAlive) { + timeOutCleanUpLock.withLock { + @Suppress("DEPRECATION") + t.stop() + } + // If somebody catches `ThreadDeath`, for now we + // just wait for [threadDeathThrowPeriod] and throw another one. + // + // A better approach may be to kill instrumented process. + t.join(threadDeathThrowPeriodMillis) + } } catch (_: Throwable) {} + } + return res + } + + fun invokeWithoutTimeout(action:() -> Any?) : Result { + ensureThreadIsAlive() - thread = null + requestQueue.offer(action) + return responseQueue.take() + } + private fun ensureThreadIsAlive() { + if (thread?.isAlive != true) { + requestQueue = ArrayBlockingQueue<() -> Any?>(1) + responseQueue = ArrayBlockingQueue>(1) + + thread = thread(name = "executor @${Random.nextInt(10_000)}", isDaemon = true) { + try { + while (thread === Thread.currentThread()) { + val next = requestQueue.take() + responseQueue.offer(kotlin.runCatching { next() }) + } + } catch (_: InterruptedException) {} + } } - return res } } \ No newline at end of file diff --git a/utbot-framework/src/test/java/org/utbot/examples/StaticInitializerExample.java b/utbot-core/src/test/java/org/utbot/examples/StaticInitializerExample.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/StaticInitializerExample.java rename to utbot-core/src/test/java/org/utbot/examples/StaticInitializerExample.java diff --git a/utbot-core/src/test/java/org/utbot/examples/reflection/ClassWithDifferentModifiers.java b/utbot-core/src/test/java/org/utbot/examples/reflection/ClassWithDifferentModifiers.java new file mode 100644 index 0000000000..1520c271b7 --- /dev/null +++ b/utbot-core/src/test/java/org/utbot/examples/reflection/ClassWithDifferentModifiers.java @@ -0,0 +1,27 @@ +package org.utbot.examples.reflection; + +public class ClassWithDifferentModifiers { + @SuppressWarnings({"All"}) + private int privateField; + + private static final Wrapper privateStaticFinalField = new Wrapper(1); + + public ClassWithDifferentModifiers() { + privateField = 0; + } + + int packagePrivateMethod() { + return 1; + } + + private int privateMethod() { + return 1; + } + + public static class Wrapper { + public int x; + public Wrapper(int x) { + this.x = x; + } + } +} diff --git a/utbot-core/src/test/kotlin/org/utbot/common/AbstractSettingsTest.kt b/utbot-core/src/test/kotlin/org/utbot/common/AbstractSettingsTest.kt new file mode 100644 index 0000000000..8290771693 --- /dev/null +++ b/utbot-core/src/test/kotlin/org/utbot/common/AbstractSettingsTest.kt @@ -0,0 +1,51 @@ +package org.utbot.common + +import java.io.InputStream +import mu.KLogger +import mu.KotlinLogging +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + + +internal class AbstractSettingsTest { + @Test + fun testParsing() { + var settings = createSettings("testBoolean=false\ntestInt=3\ntestIntRange=2\ntestLong=333\ntestLongRange=222") + Assertions.assertFalse(settings.testBoolean) + Assertions.assertEquals(3, settings.testInt) + Assertions.assertEquals(2, settings.testIntRange) + Assertions.assertEquals(333, settings.testLong) + Assertions.assertEquals(222, settings.testLongRange) + + settings = createSettings("testBoolean=invalid\ntestInt=-1\ntestIntRange=-1\ntestLong=-111\ntestLongRange=-111") + Assertions.assertTrue(settings.testBoolean) + Assertions.assertEquals(-1, settings.testInt) + Assertions.assertEquals(1, settings.testIntRange) + Assertions.assertEquals(-111, settings.testLong) + Assertions.assertEquals(111, settings.testLongRange) + + settings = createSettings("") + Assertions.assertTrue(settings.testBoolean) + Assertions.assertEquals(3, settings.testInt) + Assertions.assertEquals(333, settings.testLongRange) + } + + private fun createSettings(propertiesText: String): TestSettings { + AbstractSettings.setupFactory(object : SettingsContainerFactory { + override fun createSettingsContainer( + logger: KLogger, defaultKeyForSettingsPath: String, defaultSettingsPath: String? + ) = object : PropertiesSettingsContainer(logger, defaultKeyForSettingsPath, defaultSettingsPath) { + override fun getInputStream(): InputStream = propertiesText.byteInputStream() + } + }) + return TestSettings() + } + + internal class TestSettings : AbstractSettings(KotlinLogging.logger {}, "") { + val testBoolean: Boolean by getBooleanProperty(true) + val testInt: Int by getIntProperty(3) + val testIntRange: Int by getIntProperty(3, 1, 5) + val testLong: Long by getLongProperty(333) + val testLongRange: Long by getLongProperty(333, 111, 555) + } +} \ No newline at end of file diff --git a/utbot-core/src/test/kotlin/org/utbot/common/ReflectionUtilTest.kt b/utbot-core/src/test/kotlin/org/utbot/common/ReflectionUtilTest.kt new file mode 100644 index 0000000000..3452ced216 --- /dev/null +++ b/utbot-core/src/test/kotlin/org/utbot/common/ReflectionUtilTest.kt @@ -0,0 +1,117 @@ +package org.utbot.common + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import org.utbot.examples.reflection.ClassWithDifferentModifiers +import org.utbot.examples.reflection.ClassWithDifferentModifiers.Wrapper + +class ReflectionUtilTest { + private val testedClass = ClassWithDifferentModifiers::class.java + + @Test + fun testPackagePrivateInvoke() { + val method = testedClass.declaredMethods.first { it.name == "packagePrivateMethod"} + val instance = ClassWithDifferentModifiers() + + method.apply { + withAccessibility { + assertEquals(1, invoke(instance)) + } + } + } + + @Test + fun testPrivateInvoke() { + val method = testedClass.declaredMethods.first { it.name == "privateMethod"} + val instance = ClassWithDifferentModifiers() + + method.apply { + withAccessibility { + assertEquals(1, invoke(instance)) + } + } + + } + + @Test + fun testPrivateFieldSetting() { + val field = testedClass.declaredFields.first { it.name == "privateField" } + val instance = ClassWithDifferentModifiers() + + field.apply { + withAccessibility { + set(instance, 0) + } + } + } + + @Test + fun testPrivateFieldGetting() { + val field = testedClass.declaredFields.first { it.name == "privateField" } + val instance = ClassWithDifferentModifiers() + + field.apply { + withAccessibility { + assertEquals(0, get(instance)) + } + } + + } + + @Test + fun testPrivateFieldGettingAfterSetting() { + val field = testedClass.declaredFields.first { it.name == "privateField" } + val instance = ClassWithDifferentModifiers() + + + field.apply { + withAccessibility { + set(instance, 1) + } + + withAccessibility { + assertEquals(1, get(instance)) + } + } + } + + @Test + fun testPrivateStaticFinalFieldSetting() { + val field = testedClass.declaredFields.first { it.name == "privateStaticFinalField" } + + field.apply { + withAccessibility { + set(null, Wrapper(2)) + } + } + } + + @Test + fun testPrivateStaticFinalFieldGetting() { + val field = testedClass.declaredFields.first { it.name == "privateStaticFinalField" } + + field.apply { + withAccessibility { + val value = get(null) as? Wrapper + assertNotNull(value) + } + } + } + + @Test + fun testPrivateStaticFinalFieldGettingAfterSetting() { + val field = testedClass.declaredFields.first { it.name == "privateStaticFinalField" } + + field.apply { + withAccessibility { + set(null, Wrapper(3)) + } + + withAccessibility { + val value = (get(null) as? Wrapper)?.x + assertEquals(3, value) + } + } + } +} \ No newline at end of file diff --git a/utbot-framework-api/build.gradle b/utbot-framework-api/build.gradle deleted file mode 100644 index d922375eef..0000000000 --- a/utbot-framework-api/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - id "com.github.johnrengelman.shadow" version "6.1.0" -} - -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" - -dependencies { - api project(':utbot-core') - api project(':utbot-api') - implementation "com.github.UnitTestBot:soot:${soot_commit_hash}" - - // TODO do we really need apache commons? - implementation group: 'org.apache.commons', name: 'commons-lang3', version: commons_lang_version - implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version -} - -shadowJar { - configurations = [project.configurations.compileClasspath] - archiveClassifier.set('') - minimize() -} \ No newline at end of file diff --git a/utbot-framework-api/build.gradle.kts b/utbot-framework-api/build.gradle.kts new file mode 100644 index 0000000000..f94f8a1b7f --- /dev/null +++ b/utbot-framework-api/build.gradle.kts @@ -0,0 +1,37 @@ +val junit4Version: String by rootProject +val sootVersion: String by rootProject +val commonsLangVersion: String by rootProject +val kotlinLoggingVersion: String? by rootProject +val rdVersion: String? by rootProject +val kryoVersion: String? by rootProject +val kryoSerializersVersion: String? by rootProject + +dependencies { + api(project(":utbot-core")) + api(project(":utbot-api")) + api(project(":utbot-rd")) + implementation(group ="com.jetbrains.rd", name = "rd-framework", version = rdVersion) + implementation(group ="com.jetbrains.rd", name = "rd-core", version = rdVersion) + implementation("org.unittestbot.soot:soot-utbot-fork:${sootVersion}") { + exclude(group="com.google.guava", module="guava") + } + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) + // TODO do we really need apache commons? + implementation(group = "org.apache.commons", name = "commons-lang3", version = commonsLangVersion) + implementation(group = "com.esotericsoftware.kryo", name = "kryo5", version = kryoVersion) + // this is necessary for serialization of some collections + implementation(group = "de.javakaffee", name = "kryo-serializers", version = kryoSerializersVersion) + testImplementation(group = "junit", name = "junit", version = junit4Version) +} + +java { + withSourcesJar() +} + +tasks { + compileKotlin { + kotlinOptions { + freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" + } + } +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathDefaultProvider.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathDefaultProvider.kt deleted file mode 100644 index 9764f00961..0000000000 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathDefaultProvider.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.utbot.framework - -import java.nio.file.Path -import java.nio.file.Paths - -open class JdkPathDefaultProvider: JdkPathProvider() { - override val jdkPath: Path - get() = Paths.get(System.getProperty("java.home")) -} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathProvider.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathProvider.kt deleted file mode 100644 index b46903c571..0000000000 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathProvider.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.utbot.framework - -import java.nio.file.Path -import kotlin.reflect.KProperty - -abstract class JdkPathProvider { - operator fun getValue(service: JdkPathService, property: KProperty<*>): Path { - return jdkPath - } - - abstract val jdkPath: Path -} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathService.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathService.kt deleted file mode 100644 index d9e321a718..0000000000 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/JdkPathService.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.utbot.framework - -import java.nio.file.Path - -/** - * Singleton to enable abstract access to path to JDK. - * - * Used in [org.utbot.instrumentation.process.ChildProcessRunner]. - * The purpose is to use the same JDK in [org.utbot.instrumentation.ConcreteExecutor] - * and in the test runs. - * This is necessary because the engine can be run from the various starting points, like IDEA plugin, CLI, etc. - */ -object JdkPathService { - var jdkPathProvider: JdkPathProvider = JdkPathDefaultProvider() - - // Kotlin delegates do not support changing in runtime, so use simple getter - val jdkPath: Path - get() = jdkPathProvider.jdkPath -} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/TrustedLibraries.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/TrustedLibraries.kt new file mode 100644 index 0000000000..f0bd75cb03 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/TrustedLibraries.kt @@ -0,0 +1,49 @@ +package org.utbot.framework + +import mu.KotlinLogging +import org.utbot.common.PathUtil.toPath +import java.io.IOException + +private val logger = KotlinLogging.logger {} + +private val defaultUserTrustedLibrariesPath: String = "${utbotHomePath}/trustedLibraries.txt" +private const val userTrustedLibrariesKey: String = "utbot.settings.trusted.libraries.path" + +object TrustedLibraries { + /** + * Always "trust" JDK. + */ + private val defaultTrustedLibraries: List = listOf( + "java", + "sun", + "javax", + "com.sun", + "org.omg", + "org.xml", + "org.w3c.dom", + ) + + private val userTrustedLibraries: List + get() { + val userTrustedLibrariesPath = System.getProperty(userTrustedLibrariesKey) ?: defaultUserTrustedLibrariesPath + val userTrustedLibrariesFile = userTrustedLibrariesPath.toPath().toFile() + + if (!userTrustedLibrariesFile.exists()) { + return emptyList() + } + + return try { + userTrustedLibrariesFile.readLines() + } catch (e: IOException) { + logger.info { e.message } + + emptyList() + } + } + + /** + * Represents prefixes of packages for trusted libraries - + * as the union of [defaultTrustedLibraries] and [userTrustedLibraries]. + */ + val trustedLibraries: Set by lazy { (defaultTrustedLibraries + userTrustedLibraries).toSet() } +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/TrustedPackages.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/TrustedPackages.kt new file mode 100644 index 0000000000..45da218a54 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/TrustedPackages.kt @@ -0,0 +1,32 @@ +package org.utbot.framework + +import org.utbot.framework.plugin.api.ClassId +import soot.SootClass + +/** + * Cache for already discovered trusted/untrusted packages. + */ +private val isPackageTrusted: MutableMap = mutableMapOf() + +/** + * Determines whether [this] class is from trusted libraries as defined in [TrustedLibraries]. + */ +fun SootClass.isFromTrustedLibrary(): Boolean = isFromTrustedLibrary(packageName) + +/** + * Determines whether [this] class is from trusted libraries as defined in [TrustedLibraries]. + */ +fun ClassId.isFromTrustedLibrary(): Boolean = isFromTrustedLibrary(packageName) + +/** + * Determines whether [packageName] is from trusted libraries as defined in [TrustedLibraries]. + */ +fun isFromTrustedLibrary(packageName: String): Boolean { + isPackageTrusted[packageName]?.let { + return it + } + + val isTrusted = TrustedLibraries.trustedLibraries.any { packageName.startsWith(it, ignoreCase = false) } + + return isTrusted.also { isPackageTrusted[packageName] = it } +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt index 6eee9a2d39..112c5bfb7e 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt @@ -1,81 +1,38 @@ package org.utbot.framework +import java.io.File import mu.KotlinLogging -import org.utbot.common.PathUtil.toPath -import java.io.FileInputStream -import java.io.IOException -import java.util.Properties -import kotlin.properties.PropertyDelegateProvider -import kotlin.reflect.KProperty - +import org.utbot.common.AbstractSettings +import java.lang.reflect.Executable private val logger = KotlinLogging.logger {} /** - * Default path for properties file + * Path to the utbot home folder. */ -internal val defaultSettingsPath = "${System.getProperty("user.home")}/.utbot/settings.properties" -internal const val defaultKeyForSettingsPath = "utbot.settings.path" - -internal class SettingDelegate(val initializer: () -> T) { - private var value = initializer() - - operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value +internal val utbotHomePath = "${System.getProperty("user.home")}${File.separatorChar}.utbot" - operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { - this.value = value - } -} +/** + * Default path for properties file + */ +private val defaultSettingsPath = "$utbotHomePath${File.separatorChar}settings.properties" +private const val defaultKeyForSettingsPath = "utbot.settings.path" /** * Default concrete execution timeout (in milliseconds). */ -const val DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS = 1000L - -object UtSettings { - private val properties = Properties().also { props -> - val settingsPath = System.getProperty(defaultKeyForSettingsPath) ?: defaultSettingsPath - val settingsPathFile = settingsPath.toPath().toFile() - if (settingsPathFile.exists()) { - try { - FileInputStream(settingsPathFile).use { reader -> - props.load(reader) - } - } catch (e: IOException) { - logger.info(e) { e.message } - } - } - } +const val DEFAULT_EXECUTION_TIMEOUT_IN_INSTRUMENTED_PROCESS_MS = 1000L - private fun getProperty( - defaultValue: T, - converter: (String) -> T - ): PropertyDelegateProvider> { - return PropertyDelegateProvider { _, prop -> - SettingDelegate { - try { - properties.getProperty(prop.name)?.let(converter) ?: defaultValue - } catch (e: Throwable) { - logger.info(e) { e.message } - defaultValue - } finally { - properties.putIfAbsent(prop.name, defaultValue.toString()) - } - } - } - } +object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultSettingsPath) { - private fun getBooleanProperty(defaultValue: Boolean) = getProperty(defaultValue, String::toBoolean) - private fun getIntProperty(defaultValue: Int) = getProperty(defaultValue, String::toInt) - private fun getLongProperty(defaultValue: Long) = getProperty(defaultValue, String::toLong) - private fun getStringProperty(defaultValue: String) = getProperty(defaultValue) { it } - private inline fun > getEnumProperty(defaultValue: T) = - getProperty(defaultValue) { enumValueOf(it) } + fun defaultSettingsPath() = defaultSettingsPath + @JvmStatic + fun getPath(): String = System.getProperty(defaultKeyForSettingsPath)?: defaultSettingsPath /** * Setting to disable coroutines debug explicitly. * - * True by default, set it to false if debug info is required. + * Set it to false if debug info is required. */ var disableCoroutinesDebug: Boolean by getBooleanProperty(true) @@ -89,13 +46,13 @@ object UtSettings { * * Set it to 0 to disable timeout. */ - var checkSolverTimeoutMillis: Int by getIntProperty(1000) + var checkSolverTimeoutMillis: Int by getIntProperty(1000, 0, Int.MAX_VALUE) /** * Timeout for symbolic execution * */ - var utBotGenerationTimeoutInMillis by getLongProperty(60000L) + var utBotGenerationTimeoutInMillis by getLongProperty(60000L, 1000L, Int.MAX_VALUE.toLong()) /** * Random seed in path selector. @@ -105,19 +62,19 @@ object UtSettings { var seedInPathSelector: Int? by getProperty(42, String::toInt) /** - * Type of path selector + * Type of path selector. */ var pathSelectorType: PathSelectorType by getEnumProperty(PathSelectorType.INHERITORS_SELECTOR) /** - * Type of nnRewardGuidedSelector + * Type of MLSelector recalculation. */ - var nnRewardGuidedSelectorType: NNRewardGuidedSelectorType by getEnumProperty(NNRewardGuidedSelectorType.WITHOUT_RECALCULATION) + var mlSelectorRecalculationType: MLSelectorRecalculationType by getEnumProperty(MLSelectorRecalculationType.WITHOUT_RECALCULATION) /** - * Type of [StateRewardPredictor] + * Type of [MLPredictor]. */ - var stateRewardPredictorType: StateRewardPredictorType by getEnumProperty(StateRewardPredictorType.BASE) + var mlPredictorType: MLPredictorType by getEnumProperty(MLPredictorType.MLP) /** * Steps limit for path selector. @@ -133,7 +90,7 @@ object UtSettings { /** * Use debug visualization. * - * False by default, set it to true if debug visualization is needed. + * Set it to true if debug visualization is needed. */ var useDebugVisualization by getBooleanProperty(false) @@ -146,78 +103,101 @@ object UtSettings { val copyVisualizationPathToClipboard get() = useDebugVisualization /** - * Method is paused after this timeout to give an opportunity other methods - * to work + * Set the value to true to show library classes' graphs in visualization. */ - var timeslotForOneToplevelMethodTraversalMs by getIntProperty(2000) + val showLibraryClassesInVisualization by getBooleanProperty(false) /** * Use simplification of UtExpressions. * - * True by default, set it to false to disable expression simplification. - * @see - * UtBot Expression Optimizations + * Set it to false to disable expression simplification. + * @see UtBot Expression Optimizations */ var useExpressionSimplification by getBooleanProperty(true) - /* - * Activate or deactivate tests on comments && names/displayNames - * */ - var testSummary by getBooleanProperty(true) - var testName by getBooleanProperty(true) - var testDisplayName by getBooleanProperty(true) - /** - * Enable the machine learning module to generate summaries for methods under test. - * True by default. + * Enable the Summarization module to generate summaries for methods under test. * - * Note: if it is false, all the execution for a particular method will be stored at the same nameless region. + * Note: if it is [SummariesGenerationType.NONE], + * all the execution for a particular method will be stored at the same nameless region. + */ + var summaryGenerationType by getEnumProperty(SummariesGenerationType.FULL) + + /** + * If True test comments will be generated. + */ + var enableJavaDocGeneration by getBooleanProperty(true) + + /** + * If True cluster comments will be generated. + */ + var enableClusterCommentsGeneration by getBooleanProperty(true) + + /** + * If True names for tests will be generated. + */ + var enableTestNamesGeneration by getBooleanProperty(true) + + /** + * If True display names for tests will be generated. + */ + var enableDisplayNameGeneration by getBooleanProperty(true) + + /** + * If True display name in from -> to style will be generated. + */ + var useDisplayNameArrowStyle by getBooleanProperty(true) + + /** + * Generate summaries using plugin's custom JavaDoc tags. */ - var enableMachineLearningModule by getBooleanProperty(true) + var useCustomJavaDocTags by getBooleanProperty(true) /** - * Options below regulate which NullPointerExceptions check should be performed. + * This option regulates which [NullPointerException] check should be performed for nested methods. * * Set an option in true if you want to perform NPE check in the corresponding situations, otherwise set false. */ var checkNpeInNestedMethods by getBooleanProperty(true) + + /** + * This option regulates which [NullPointerException] check should be performed for nested not private methods. + * + * Set an option in true if you want to perform NPE check in the corresponding situations, otherwise set false. + */ var checkNpeInNestedNotPrivateMethods by getBooleanProperty(false) - var checkNpeForFinalFields by getBooleanProperty(false) + + /** + * This option determines whether we should generate [NullPointerException] checks for final or non-public fields + * in non-application classes. Set by true, this option highly decreases test's readability in some cases + * because of using reflection API for setting final/non-public fields in non-application classes. + * + * NOTE: With false value loses some executions with NPE in system classes, but often most of these executions + * are not expected by user. + */ + var maximizeCoverageUsingReflection by getBooleanProperty(false) /** * Activate or deactivate substituting static fields values set in static initializer * with symbolic variable to try to set them another value than in initializer. - * - * We should not try to substitute in parametrized tests, for example */ var substituteStaticsWithSymbolicVariable by getBooleanProperty(true) - /** * Use concrete execution. - * - * True by default. */ var useConcreteExecution by getBooleanProperty(true) - /** - * Enable check of full coverage for methods with code generations tests. - * - * TODO doesn't work for now JIRA:1407 - */ - var checkCoverageInCodeGenerationTests by getBooleanProperty(true) - /** * Enable code generation tests with every possible configuration * for every method in samples. * - * Important: disabled by default. This check requires enormous amount of time. + * Important: is enabled generation requires enormous amount of time. */ var checkAllCombinationsForEveryTestInSamples by getBooleanProperty(false) /** * Enable transformation UtCompositeModels into UtAssembleModels using AssembleModelGenerator. - * True by default. * * Note: false doesn't mean that there will be no assemble models, it means that the generator will be turned off. * Assemble models will present for lists, sets, etc. @@ -228,12 +208,10 @@ object UtSettings { * Test related files from the temp directory that are older than [daysLimitForTempFiles] * will be removed at the beginning of the test run. */ - var daysLimitForTempFiles by getIntProperty(3) + var daysLimitForTempFiles by getIntProperty(3, 0, 30) /** * Enables soft constraints in the engine. - * - * True by default. */ var preferredCexOption by getBooleanProperty(true) @@ -251,21 +229,34 @@ object UtSettings { /** * Set the total attempts to improve coverage by fuzzer. */ - var fuzzingMaxAttempts: Int by getIntProperty(Int.MAX_VALUE) + var fuzzingMaxAttempts: Int by getIntProperty(Int.MAX_VALUE, 0, Int.MAX_VALUE) /** * Fuzzer tries to generate and run tests during this time. */ - var fuzzingTimeoutInMillis: Int by getIntProperty(3_000) + var fuzzingTimeoutInMillis: Long by getLongProperty(3_000L, 0, Long.MAX_VALUE) + + /** + * Find implementations of interfaces and abstract classes to fuzz. + */ + var fuzzingImplementationOfAbstractClasses: Boolean by getBooleanProperty(true) + + /** + * Use methods to mutate fields of classes different from class under test or not. + */ + var tryMutateOtherClassesFieldsWithMethods: Boolean by getBooleanProperty(false) /** * Generate tests that treat possible overflows in arithmetic operations as errors * that throw Arithmetic Exception. - * - * False by default. */ var treatOverflowAsError: Boolean by getBooleanProperty(false) + /** + * Generate tests that treat assertions as error suits. + */ + var treatAssertAsErrorSuite: Boolean by getBooleanProperty(true) + /** * Instrument all classes before start */ @@ -280,17 +271,117 @@ object UtSettings { /** * Timeout for specific concrete execution (in milliseconds). */ - var concreteExecutionTimeoutInChildProcess: Long by getLongProperty( - DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS + var concreteExecutionDefaultTimeoutInInstrumentedProcessMillis: Long by getLongProperty( + DEFAULT_EXECUTION_TIMEOUT_IN_INSTRUMENTED_PROCESS_MS ) /** - * Determines whether should errors from a child process be written to a log file or suppressed. - * Note: being enabled, this option can highly increase disk usage when using ContestEstimator. + * Enable taint analysis or not. + */ + var useTaintAnalysis by getBooleanProperty(false) + + /** + * If it is true, [Traverser] forks the state and creates checks for each taint mark separately, + * otherwise, it processes all available taint marks in one [Traverser.implicitlyThrowException] request. + * + * @see [org.utbot.engine.Traverser.processTaintSink] + */ + var throwTaintErrorForEachMarkSeparately = true + + /** + * How deep we should analyze the throwables. + */ + val exploreThrowableDepth: ExploreThrowableDepth + get() = + if (useTaintAnalysis) { + ExploreThrowableDepth.EXPLORE_ALL_STATEMENTS + } else { + ExploreThrowableDepth.SKIP_ALL_STATEMENTS + } + +// region engine process debug + + /** + * Path to custom log4j2 configuration file for EngineProcess. + * By default utbot-intellij/src/main/resources/log4j2.xml is used. + * Also default value is used if provided value is not a file. + */ + var engineProcessLogConfigFile by getStringProperty("") + + /** + * The property is useful only for the IntelliJ IDEs. + * If the property is set in true the engine process opens a debug port. + * @see runInstrumentedProcessWithDebug + * @see org.utbot.intellij.plugin.process.EngineProcess + */ + var runEngineProcessWithDebug by getBooleanProperty(false) + + /** + * The engine process JDWP agent's port of the engine process. + * A debugger attaches to the port in order to debug the process. + */ + var engineProcessDebugPort by getIntProperty(5005) + + /** + * Value of the suspend mode for the JDWP agent of the engine process. + * If the value is true, the engine process will suspend until a debugger attaches to it. + */ + var suspendEngineProcessExecutionInDebugMode by getBooleanProperty(true) + +// endregion + +// region spring analyzer process debug + /** + * The property is useful only for the IntelliJ IDEs. + * If the property is set in true the spring analyzer process opens a debug port. + * @see runInstrumentedProcessWithDebug + * @see org.utbot.spring.process.SpringAnalyzerProcess + */ + var runSpringAnalyzerProcessWithDebug by getBooleanProperty(false) + + /** + * The spring analyzer process JDWP agent's port. + * A debugger attaches to the port in order to debug the process. + */ + var springAnalyzerProcessDebugPort by getIntProperty(5007) + + /** + * Value of the suspend mode for the JDWP agent of the spring analyzer process. + * If the value is true, the spring analyzer process will suspend until a debugger attaches to it. + */ + var suspendSpringAnalyzerProcessExecutionInDebugMode by getBooleanProperty(true) + +// endregion + +// region instrumented process debug + /** + * The instrumented process JDWP agent's port of the instrumented process. + * A debugger attaches to the port in order to debug the process. + */ + var instrumentedProcessDebugPort by getIntProperty(5006, 0, 65535) + + /** + * Value of the suspend mode for the JDWP agent of the instrumented process. + * If the value is true, the instrumented process will suspend until a debugger attaches to it. + */ + var suspendInstrumentedProcessExecutionInDebugMode by getBooleanProperty(true) + + /** + * If true, runs the instrumented process with the ability to attach a debugger. + * + * To debug the instrumented process, set the breakpoint in the + * [org.utbot.instrumentation.rd.InstrumentedProcess.Companion.invoke] + * and in the instrumented process's main function and run the main process. + * Then run the remote JVM debug configuration in IDEA. + * If you see the message in console about successful connection, then + * the debugger is attached successfully. + * Now you can put the breakpoints in the instrumented process and debug + * both processes simultaneously. * - * False by default (for saving disk space). + * @see [org.utbot.instrumentation.rd.InstrumentedProcess.Companion.invoke] */ - var logConcreteExecutionErrors by getBooleanProperty(false) + var runInstrumentedProcessWithDebug by getBooleanProperty(false) +// endregion /** * Number of branch instructions using for clustering executions in the test minimization phase. @@ -302,21 +393,23 @@ object UtSettings { */ var minimizeCrashExecutions by getBooleanProperty(true) + /** + * Determines maximum number of executions with unknown coverage per method per result type. + * In [ContestUsvm] it is useful if concrete fails, so we use symbolic execution result without trace. + */ + var maxUnknownCoverageExecutionsPerMethodPerResultType by getIntProperty(10) + /** * Enable it to calculate unsat cores for hard constraints as well. * It may be usefull during debug. * * Note: it might highly impact performance, so do not enable it in release mode. - * - * False by default. */ var enableUnsatCoreCalculationForHardConstraints by getBooleanProperty(false) /** * Enable it to process states with unknown solver status * from the queue to concrete execution. - * - * True by default. */ var processUnknownStatesDuringConcreteExecution by getBooleanProperty(true) @@ -327,19 +420,24 @@ object UtSettings { var subpathGuidedSelectorIndex by getIntProperty(1) /** - * Set of indexes, which will use [SubpathGuidedSelector] in not single mode + * Flag that indicates whether feature processing for execution states enabled or not */ - var subpathGuidedSelectorIndexes = listOf(0, 1, 2, 3) + var enableFeatureProcess by getBooleanProperty(false) /** - * Flag that indicates whether feature processing for execution states enabled or not + * Path to deserialized ML models */ - var enableFeatureProcess by getBooleanProperty(false) + var modelPath by getStringProperty("../models/0") + + /** + * Full class name of the class containing the configuration for the ML models to solve path selection task. + */ + var analyticsConfigurationClassPath by getStringProperty("org.utbot.AnalyticsConfiguration") /** - * Path to deserialized reward models + * Full class name of the class containing the configuration for the ML models exported from the PyTorch to solve path selection task. */ - var rewardModelPath by getStringProperty("../models/0") + var analyticsTorchConfigurationClassPath by getStringProperty("org.utbot.AnalyticsTorchConfiguration") /** * Number of model iterations that will be used during ContestEstimator @@ -357,15 +455,179 @@ object UtSettings { var testCounter by getIntProperty(0) /** - * Flag for Subpath and NN selectors whether they are combined (Subpath use several indexes, NN use several models) + * Flag that indicates whether tests for synthetic (see [Executable.isSynthetic]) and implicitly declared methods (like values, valueOf in enums) should be generated, or not + */ + var skipTestGenerationForSyntheticAndImplicitlyDeclaredMethods by getBooleanProperty(true) + + /** + * Flag that indicates whether should we branch on and set static fields from trusted libraries or not. + * + * @see [org.utbot.common.WorkaroundReason.IGNORE_STATICS_FROM_TRUSTED_LIBRARIES] + */ + var ignoreStaticsFromTrustedLibraries by getBooleanProperty(true) + + /** + * Use the sandbox in the instrumented process. + * + * If true, the sandbox will prevent potentially dangerous calls, e.g., file access, reading + * or modifying the environment, calls to `Unsafe` methods etc. + * + * If false, all these operations will be enabled and may lead to data loss during code analysis + * and test generation. + */ + var useSandbox by getBooleanProperty(true) + + /** + * Transform bytecode in the instrumented process. + * + * If true, bytecode transformation will help fuzzing to find interesting input data, but the size of bytecode can increase. + * + * If false, bytecode won`t be changed. + */ + var useBytecodeTransformation by getBooleanProperty(false) + + /** + * Limit for number of generated tests per method (in each region) + */ + var maxTestsPerMethodInRegion by getIntProperty(50, 1, Integer.MAX_VALUE) + + /** + * Max file length for generated test file + */ + const val DEFAULT_MAX_FILE_SIZE = 1000000 + var maxTestFileSize by getProperty(DEFAULT_MAX_FILE_SIZE, ::parseFileSize) + + + fun parseFileSize(s: String): Int { + val suffix = StringBuilder() + var value = 0 + for (ch in s) { + (ch - '0').let { + if (it in 0..9) { + value = value * 10 + it + } else suffix.append(ch) + } + } + when (suffix.toString().trim().lowercase()) { + "k", "kb" -> value *= 1000 + "m", "mb" -> value *= 1000000 + } + return if (value > 0) value else DEFAULT_MAX_FILE_SIZE // fallback for incorrect value + } + + /** + * If this options set in true, all soot classes will be removed from a Soot Scene, + * therefore, you will be unable to test soot classes. + */ + var removeSootClassesFromHierarchy by getBooleanProperty(true) + + /** + * If this options set in true, all UtBot classes will be removed from a Soot Scene, + * therefore, you will be unable to test UtBot classes. + */ + var removeUtBotClassesFromHierarchy by getBooleanProperty(true) + + /** + * Use this option to enable calculation and logging of MD5 for dropped states by statistics. + * Example of such logging: + * Dropping state (lastStatus=UNDEFINED) by the distance statistics. MD5: 5d0bccc242e87d53578ca0ef64aa5864 + */ + var enableLoggingForDroppedStates by getBooleanProperty(false) + + /** + * If this option set in true, depending on the number of possible types for + * a particular object will be used either type system based on conjunction + * or on bit vectors. + * + * @see useBitVecBasedTypeSystem + */ + var useBitVecBasedTypeSystem by getBooleanProperty(true) + + /** + * The number of types on which the choice of the type system depends. + */ + var maxTypeNumberForEnumeration by getIntProperty(64) + + /** + * The threshold for numbers of types for which they will be encoded into solver. + * It is used to do not encode big type storages due to significand performance degradation. + */ + var maxNumberOfTypesToEncode by getIntProperty(512) + + /** + * The behaviour of further analysis if tests generation cancellation is requested. + */ + var cancellationStrategyType by getEnumProperty(CancellationStrategyType.SAVE_PROCESSED_RESULTS) + + /** + * Depending on this option, sections might be analyzed or not. + * Note that some clinit sections still will be initialized using runtime information. + */ + var enableClinitSectionsAnalysis by getBooleanProperty(true) + + /** + * Process all clinit sections concretely. + * + * If [enableClinitSectionsAnalysis] is false, it disables effect of this option as well. + * Note that values processed concretely won't be replaced with unbounded symbolic variables. + */ + var processAllClinitSectionsConcretely by getBooleanProperty(false) + + /** + * In cases where we don't have a body for a method, we can either throw an exception + * or treat this a method as a source of an unbounded symbolic variable returned as a result. + * + * If this option is set in true, instead of analysis we will return an unbounded symbolic + * variable with a corresponding type. Otherwise, an exception will be thrown. + * + * Default value is false since it is not a common situation when you cannot retrieve a body + * from a regular method. Setting this option in true might be suitable in situations when + * it is more important not to fall at all rather than work precisely. + */ + var treatAbsentMethodsAsUnboundedValue by getBooleanProperty(false) + + // region preferred size options + // Changes in this region might severe influence on the performance of symbolic execution. + + /** + * A maximum size for any array in the program. Note that input arrays might be less than this value + * due to the symbolic engine limitation, see `org.utbot.engine.Traverser.softMaxArraySize`. + */ + var maxArraySize by getIntProperty(1024) + + // endregion + + // region UTBot light related options + // Changes to improve symbolic engine for light version + + var disableUnsatChecking by getBooleanProperty(false) + + // endregion + + // region Spring-related options + + /** + * When generating integration tests we only partially reset context in between executions to save time. + * For example, entity id generators do not get reset. It may lead to non-reproduceable results if + * IDs leak to the output of the method under test. + * + * To cope with that, we rerun executions that are left after minimization, fully resetting Spring context + * between executions. However, full context reset is slow, so we use this setting to limit number of + * tests per method that are rerun with full context reset in case minimization outputs too many tests. + */ + var maxSpringContextResetsPerMethod by getIntProperty(25, 0, Int.MAX_VALUE) + + // endregion + + // region codegen options + + /** + * Add "test method start marker" and "test method end marker" around each test, can be used to + * detect uncompilable tests and remove them. */ - var singleSelector by getBooleanProperty(true) + var addTestMethodMarkers by getBooleanProperty(false) - override fun toString(): String = - properties - .entries - .sortedBy { it.key.toString() } - .joinToString(separator = System.lineSeparator()) { "\t${it.key}=${it.value}" } + // endregion } /** @@ -382,6 +644,11 @@ enum class PathSelectorType { */ INHERITORS_SELECTOR, + /** + * [BFSSelector] + */ + BFS_SELECTOR, + /** * [SubpathGuidedSelector] */ @@ -398,9 +665,14 @@ enum class PathSelectorType { FORK_DEPTH_SELECTOR, /** - * [NNRewardGuidedSelector] + * [MLSelector] + */ + ML_SELECTOR, + + /** + * [TorchSelector] */ - NN_REWARD_GUIDED_SELECTOR, + TORCH_SELECTOR, /** * [RandomSelector] @@ -414,41 +686,104 @@ enum class PathSelectorType { } enum class TestSelectionStrategyType { - DO_NOT_MINIMIZE_STRATEGY, // Always adds new test - COVERAGE_STRATEGY // Adds new test only if it increases coverage + /** + * Always adds new test + */ + DO_NOT_MINIMIZE_STRATEGY, + + /** + * Adds new test only if it increases coverage + */ + COVERAGE_STRATEGY, +} + +/** + * Describes the behaviour if test generation is canceled. + */ +enum class CancellationStrategyType { + /** + * Do not react on cancellation + */ + NONE, + + /** + * Clear all generated test classes + */ + CANCEL_EVERYTHING, + + /** + * Show already processed test classes + */ + SAVE_PROCESSED_RESULTS } /** - * Enum to specify [NNRewardGuidedSelector], see implementations for more details + * Enum to specify [MLSelector], see implementations for more details */ -enum class NNRewardGuidedSelectorType { +enum class MLSelectorRecalculationType { /** - * [NNRewardGuidedSelectorWithRecalculation] + * [MLSelectorWithRecalculation] */ WITH_RECALCULATION, /** - * [NNRewardGuidedSelectorWithoutRecalculation] + * [MLSelectorWithoutRecalculation] */ WITHOUT_RECALCULATION } /** - * Enum to specify [StateRewardPredictor], see implementations for details + * Enum to specify [MLPredictor], see implementations for details + */ +enum class MLPredictorType { + /** + * [MultilayerPerceptronPredictor] + */ + MLP, + + /** + * [LinearRegressionPredictor] + */ + LINREG +} + +/** + * Enum to describe how we analyze code to obtain summaries. */ -enum class StateRewardPredictorType { +enum class SummariesGenerationType { + /** + * All possible analysis actions are taken + */ + FULL, + + /** + * Analysis actions based on sources are NOT taken + */ + LIGHT, + + /** + * No summaries are generated + */ + NONE, +} + +/** + * Enum to describe how deep we should analyze the throwables. + */ +enum class ExploreThrowableDepth { + /** - * [NNStateRewardPredictorBase] + * Skip all statements between throwable's `new` and `` statements. */ - BASE, + SKIP_ALL_STATEMENTS, /** - * [StateRewardPredictorTorch] + * Skip only throwable's statement. */ - TORCH, + SKIP_INIT_STATEMENT, /** - * [NNStateRewardPredictorBase] + * Do not skip statements. */ - LINEAR + EXPLORE_ALL_STATEMENTS } diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/fuzzer/IdGenerator.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/fuzzer/IdGenerator.kt new file mode 100644 index 0000000000..1447694569 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/fuzzer/IdGenerator.kt @@ -0,0 +1,81 @@ +package org.utbot.framework.fuzzer + +import java.util.* +import java.util.concurrent.atomic.AtomicInteger + +/** + * Identifier generator interface for fuzzer model providers. + * + * Provides fresh identifiers for generated models. + * + * Warning: specific generators are not guaranteed to be thread-safe. + * + * @param Id the identifier type (e.g., [Int] for [UtReferenceModel] providers) + */ +interface IdGenerator { + /** + * Create a fresh identifier. Each subsequent call should return a different value. + * + * The method is not guaranteed to be thread-safe, unless an implementation makes such a guarantee. + */ + fun createId(): Id +} + +/** + * Identity preserving identifier generator interface. + * + * It allows to optionally save identifiers assigned to specific objects and later get the same identifiers + * for these objects instead of fresh identifiers. This feature is necessary, for example, to implement reference + * equality for enum models. + * + * Warning: specific generators are not guaranteed to be thread-safe. + * + * @param Id the identifier type (e.g., [Int] for [UtReferenceModel] providers) + */ +interface IdentityPreservingIdGenerator : IdGenerator { + /** + * Return an identifier for a specified non-null object. If an identifier has already been assigned + * to an object, subsequent calls should return the same identifier for this object. + * + * Note: the interface does not specify whether reference equality or regular `equals`/`compareTo` equality + * will be used to compare objects. Each implementation may provide these guarantees by itself. + * + * The method is not guaranteed to be thread-safe, unless an implementation makes such a guarantee. + */ + fun getOrCreateIdForValue(value: Any): Id +} + +/** + * An identity preserving id generator for fuzzer value providers that returns identifiers of type [Int]. + * + * When identity-preserving identifier is requested, objects are compared by reference. + * The generator is not thread-safe. + * + * @param lowerBound an integer value so that any generated identifier is strictly greater than it. + * + * Warning: when generating [UtReferenceModel] identifiers, no identifier should be equal to zero, + * as this value is reserved for [UtNullModel]. To guarantee it, [lowerBound] should never be negative. + * Avoid using custom lower bounds (maybe except fuzzer unit tests), use the predefined default value instead. + */ +class ReferencePreservingIntIdGenerator(lowerBound: Int = DEFAULT_LOWER_BOUND) : IdentityPreservingIdGenerator { + private val lastId: AtomicInteger = AtomicInteger(lowerBound) + private val cache: IdentityHashMap = IdentityHashMap() + + override fun getOrCreateIdForValue(value: Any): Int { + return cache.getOrPut(value) { createId() } + } + + override fun createId(): Int { + return lastId.incrementAndGet() + } + + companion object { + /** + * The default lower bound (all generated integer identifiers will be greater than it). + * + * It is defined as a large value because all synthetic [UtModel] instances + * must have greater identifiers than the real models. + */ + const val DEFAULT_LOWER_BOUND: Int = 1500_000_000 + } +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index be42ca8e4b..ec289f2b23 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -8,41 +8,34 @@ package org.utbot.framework.plugin.api +import org.utbot.common.FileUtil import org.utbot.common.isDefaultValue import org.utbot.common.withToStringThreadLocalReentrancyGuard import org.utbot.framework.UtSettings -import org.utbot.framework.plugin.api.MockFramework.MOCKITO -import org.utbot.framework.plugin.api.impl.FieldIdReflectionStrategy -import org.utbot.framework.plugin.api.impl.FieldIdSootStrategy +import org.utbot.framework.plugin.api.util.ModifierFactory import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.byteClassId import org.utbot.framework.plugin.api.util.charClassId import org.utbot.framework.plugin.api.util.constructor import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.executableId -import org.utbot.framework.plugin.api.util.findFieldOrNull import org.utbot.framework.plugin.api.util.floatClassId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.isArray import org.utbot.framework.plugin.api.util.isPrimitive +import org.utbot.framework.plugin.api.util.isStatic import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.jField +import org.utbot.framework.plugin.api.util.kClass import org.utbot.framework.plugin.api.util.longClassId import org.utbot.framework.plugin.api.util.method import org.utbot.framework.plugin.api.util.primitiveTypeJvmNameOrNull +import org.utbot.framework.plugin.api.util.safeJField import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.supertypeOfAnonymousClass import org.utbot.framework.plugin.api.util.toReferenceTypeBytecodeSignature import org.utbot.framework.plugin.api.util.voidClassId -import java.io.File -import java.lang.reflect.Modifier -import java.nio.file.Path -import kotlin.jvm.internal.CallableReference -import kotlin.reflect.KCallable -import kotlin.reflect.KClass -import kotlin.reflect.KFunction -import kotlin.reflect.full.instanceParameter -import kotlin.reflect.jvm.javaConstructor -import kotlin.reflect.jvm.javaType import soot.ArrayType import soot.BooleanType import soot.ByteType @@ -58,67 +51,27 @@ import soot.Type import soot.VoidType import soot.jimple.JimpleBody import soot.jimple.Stmt - -data class UtMethod( - val callable: KCallable, - val clazz: KClass<*> -) { - companion object { - fun from(function: KCallable): UtMethod { - val kClass = when (function) { - is CallableReference -> function.owner as? KClass<*> - else -> function.instanceParameter?.type?.classifier as? KClass<*> - } ?: tryConstructor(function) ?: error("Can't get parent class for $function") - - return UtMethod(function, kClass) - } - - /** - * Workaround for constructors from tests. - */ - private fun tryConstructor(function: KCallable): KClass? { - val declaringClass: Class<*>? = (function as? KFunction<*>)?.javaConstructor?.declaringClass - return declaringClass?.kotlin - } - } - - override fun toString(): String { - return "${clazz.qualifiedName}.${callable.name}" + - callable.parameters.drop(if (callable.instanceParameter != null) 1 else 0) - .joinToString(", ", "(", ")") { - it.type.javaType.typeName.substringBefore('<').substringAfterLast(".") - } - } -} - -/** - * Test case. - * - * Note: it should not be transformed into data class since it is used as a key in maps. - * The clusters in it are mutable objects, therefore, we might have problems with hash because of it. - */ -@Suppress("unused") -class UtTestCase( - val method: UtMethod<*>, +import java.io.File +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract +import org.utbot.common.isAbstract +import org.utbot.framework.plugin.api.mapper.UtModelMapper +import org.utbot.framework.plugin.api.mapper.map +import org.utbot.framework.plugin.api.mapper.mapPreservingType +import org.utbot.framework.plugin.api.util.SpringModelUtils +import org.utbot.framework.process.OpenModulesContainer +import soot.SootMethod + +const val SYMBOLIC_NULL_ADDR: Int = 0 + +data class UtMethodTestSet( + val method: ExecutableId, val executions: List = emptyList(), + // in idea process this property is null val jimpleBody: JimpleBody? = null, val errors: Map = emptyMap(), - private val clustersInfo: List> = listOf(null to executions.indices) -) { - operator fun component1() = method - operator fun component2() = executions - operator fun component3() = jimpleBody - operator fun component4() = errors - operator fun component5() = clustersInfo - - fun copy( - method: UtMethod<*> = this.method, - executions: List = this.executions, - jimpleBody: JimpleBody? = this.jimpleBody, - errors: Map = this.errors, - clustersInfo: List> = this.clustersInfo - ) = UtTestCase(method, executions, jimpleBody, errors, clustersInfo) -} + val clustersInfo: List> = listOf(null to executions.indices) +) data class Step( val stmt: Stmt, @@ -144,6 +97,17 @@ data class Step( } } +/** + * One symbolic step. + * + * @see UtSymbolicExecution.symbolicSteps + */ +data class SymbolicStep( + val method: SootMethod, + val lineNumber: Int, + val callDepth: Int, +) + /** * Traverse result. @@ -152,28 +116,66 @@ data class Step( */ sealed class UtResult + /** * Execution. * * Contains: * - execution parameters, including thisInstance; * - result; - * - static fields changed during execution; - * - required instrumentation details (such as randoms, time, static methods). * - coverage information (instructions) if this execution was obtained from the concrete execution. + * - comments, method names and display names created by utbot-summary module. */ -data class UtExecution( +abstract class UtExecution( val stateBefore: EnvironmentModels, val stateAfter: EnvironmentModels, val result: UtExecutionResult, - val instrumentation: List, - val path: MutableList, - val fullPath: List, val coverage: Coverage? = null, var summary: List? = null, var testMethodName: String? = null, - var displayName: String? = null, + var displayName: String? = null ) : UtResult() { + abstract fun copy( + stateBefore: EnvironmentModels = this.stateBefore, + stateAfter: EnvironmentModels = this.stateAfter, + result: UtExecutionResult = this.result, + coverage: Coverage? = this.coverage, + summary: List? = this.summary, + testMethodName: String? = this.testMethodName, + displayName: String? = this.displayName, + ): UtExecution + + val executableToCall get() = stateBefore.executableToCall +} + +interface UtExecutionWithInstrumentation { + val instrumentation: List +} + +/** + * Symbolic execution. + * + * Contains: + * - execution parameters, including thisInstance; + * - result; + * - static fields changed during execution; + * - required instrumentation details (such as randoms, time, static methods). + * - coverage information (instructions) if this execution was obtained from the concrete execution. + * - comments, method names and display names created by utbot-summary module. + */ +class UtSymbolicExecution( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + override val instrumentation: List, + val path: MutableList, + val fullPath: List, + coverage: Coverage? = null, + summary: List? = null, + testMethodName: String? = null, + displayName: String? = null, + /** Convenient view of the full symbolic path */ val symbolicSteps: List = listOf(), +) : UtExecution(stateBefore, stateAfter, result, coverage, summary, testMethodName, displayName), UtExecutionWithInstrumentation { /** * By design the 'before' and 'after' states contain info about the same fields. * It means that it is not possible for a field to be present at 'before' and to be absent at 'after'. @@ -182,8 +184,31 @@ data class UtExecution( val staticFields: Set get() = stateBefore.statics.keys + var containsMocking: Boolean = false + + override fun copy( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage?, + summary: List?, + testMethodName: String?, + displayName: String? + ): UtExecution = UtSymbolicExecution( + stateBefore = stateBefore, + stateAfter = stateAfter, + result = result, + instrumentation = instrumentation, + path = path, + fullPath = fullPath, + coverage = coverage, + summary = summary, + testMethodName = testMethodName, + displayName = displayName + ) + override fun toString(): String = buildString { - append("UtExecution(") + append("UtSymbolicExecution(") appendLine() append(":") @@ -204,13 +229,99 @@ data class UtExecution( appendOptional("instrumentation", instrumentation) append(")") } + + fun copy( + stateBefore: EnvironmentModels = this.stateBefore, + stateAfter: EnvironmentModels = this.stateAfter, + result: UtExecutionResult = this.result, + coverage: Coverage? = this.coverage, + summary: List? = this.summary, + testMethodName: String? = this.testMethodName, + displayName: String? = this.displayName, + instrumentation: List = this.instrumentation, + path: MutableList = this.path, + fullPath: List = this.fullPath + ): UtExecution = UtSymbolicExecution( + stateBefore = stateBefore, + stateAfter = stateAfter, + result = result, + instrumentation = instrumentation, + path = path, + fullPath = fullPath, + coverage = coverage, + summary = summary, + testMethodName = testMethodName, + displayName = displayName + ) +} + +/** + * Execution that result in an error (e.g., JVM crash or another concrete execution error). + * + * Contains: + * - state before the execution; + * - result (a [UtExecutionFailure] or its subclass); + * - coverage information (instructions) if this execution was obtained from the concrete execution. + * - comments, method names and display names created by utbot-summary module. + * + * This execution does not contain any "after" state, as it is generally impossible to obtain + * in case of failure. [MissingState] is used instead. + */ +class UtFailedExecution( + stateBefore: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage? = null, + summary: List? = null, + testMethodName: String? = null, + displayName: String? = null +) : UtExecution(stateBefore, MissingState, result, coverage, summary, testMethodName, displayName) { + override fun copy( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage?, + summary: List?, + testMethodName: String?, + displayName: String? + ): UtExecution { + return UtFailedExecution( + stateBefore, + result, + coverage, + summary, + testMethodName, + displayName + ) + } } +/** + * @property executableToCall executable that is called in the test body and whose result is used in asserts as `actual`. + * + * Most often [executableToCall] is just method under test, but it may be changed to different executable if more + * appropriate way of calling specific method is found (for example, Spring controller methods are called via `MockMvc`). + * + * `null` value of [executableToCall] indicates that method under test should be called in the test body. + */ open class EnvironmentModels( val thisInstance: UtModel?, val parameters: List, - val statics: Map + val statics: Map, + val executableToCall: ExecutableId?, ) { + @Deprecated("Now `executableToCall` also need to be passed to `EnvironmentModels` constructor " + + "(see more details in `EnvironmentModels` class documentation)", level = DeprecationLevel.ERROR) + constructor( + thisInstance: UtModel?, + parameters: List, + statics: Map, + ) : this( + thisInstance = thisInstance, + parameters = parameters, + statics = statics, + executableToCall = null, + ) + override fun toString() = buildString { append("this=$thisInstance") appendOptional("parameters", parameters) @@ -220,19 +331,27 @@ open class EnvironmentModels( operator fun component1(): UtModel? = thisInstance operator fun component2(): List = parameters operator fun component3(): Map = statics + + fun copy( + thisInstance: UtModel? = this.thisInstance, + parameters: List = this.parameters, + statics: Map = this.statics, + executableToCall: ExecutableId? = this.executableToCall, + ) = EnvironmentModels(thisInstance, parameters, statics, executableToCall) } /** - * Represents missing state. Useful for [UtConcreteExecutionFailure] because it does not have [UtExecution.stateAfter] + * Represents missing state. Useful for [UtConcreteExecutionFailure] because it does not have [UtSymbolicExecution.stateAfter] */ object MissingState : EnvironmentModels( thisInstance = null, parameters = emptyList(), - statics = emptyMap() + statics = emptyMap(), + executableToCall = null, ) /** - * Error happened in traverse. + * Error happened during test cases generation. */ data class UtError( val description: String, @@ -244,7 +363,7 @@ data class UtError( * * UtNullModel represents nulls, other models represent not-nullable entities. */ -sealed class UtModel( +open class UtModel( open val classId: ClassId ) @@ -264,12 +383,12 @@ sealed class UtReferenceModel( ) : UtModel(classId) /** - * Checks if [UtModel] is a null. + * Checks if [UtModel] is a [UtNullModel]. */ fun UtModel.isNull() = this is UtNullModel /** - * Checks if [UtModel] is not a null. + * Checks if [UtModel] is not a [UtNullModel]. */ fun UtModel.isNotNull() = !isNull() @@ -285,6 +404,36 @@ fun UtModel.hasDefaultValue() = */ fun UtModel.isMockModel() = this is UtCompositeModel && isMock +/** + * Checks that this [UtModel] must be constructed with @Spy annotation in generated tests. + * Used only for construct variables with @Spy annotation. + */ +fun UtModel.canBeSpied(): Boolean = + this is UtAssembleModel && spiedTypes.any { type -> type.isAssignableFrom(this.classId.jClass)} + +val spiedTypes = setOf(Collection::class.java, Map::class.java) + +/** + * Get model id (symbolic null value for UtNullModel) + * or null if model has no id (e.g., a primitive model) or the id is null. + */ +fun UtModel.idOrNull(): Int? = when (this) { + is UtNullModel -> SYMBOLIC_NULL_ADDR + is UtReferenceModel -> id + else -> null +} + +/** + * Returns the model id if it is available, or throws an [IllegalStateException]. + */ +@OptIn(ExperimentalContracts::class) +fun UtModel?.getIdOrThrow(): Int { + contract { + returns() implies (this@getIdOrThrow != null) + } + return this?.idOrNull() ?: throw IllegalStateException("Model id must not be null: $this") +} + /** * Model for nulls. */ @@ -326,20 +475,24 @@ object UtVoidModel : UtModel(voidClassId) * Model for enum constant */ data class UtEnumConstantModel( + override val id: Int, override val classId: ClassId, val value: Enum<*> -) : UtModel(classId) { - override fun toString(): String = "$value" +) : UtReferenceModel(id, classId) { + // Model id is included for debugging purposes + override fun toString(): String = "$value@$id" } /** * Model for class reference */ data class UtClassRefModel( + override val id: Int, override val classId: ClassId, - val value: Class<*> -) : UtModel(classId) { - override fun toString(): String = "$value" + val value: ClassId +) : UtReferenceModel(id, classId) { + // Model id is included for debugging purposes + override fun toString(): String = "$value@$id" } /** @@ -349,6 +502,12 @@ data class UtClassRefModel( * - isMock flag * - calculated field values (models) * - mocks for methods with return values + * - [canHaveRedundantOrMissingMocks] flag, which is set to `true` for mocks + * created by: + * - fuzzer which doesn't know which methods will actually be called + * - engine which also doesn't know which methods will actually be + * called during concrete execution that may be only **partially** + * backed up by the symbolic analysis * * [fields] contains non-static fields */ @@ -358,6 +517,7 @@ data class UtCompositeModel( val isMock: Boolean, val fields: MutableMap = mutableMapOf(), val mocks: MutableMap> = mutableMapOf(), + val canHaveRedundantOrMissingMocks: Boolean = true, ) : UtReferenceModel(id, classId) { //TODO: SAT-891 - rewrite toString() method override fun toString() = withToStringThreadLocalReentrancyGuard { @@ -369,7 +529,7 @@ data class UtCompositeModel( if (fields.isNotEmpty()) { append(" ") append(fields.entries.joinToString(", ", "{", "}") { (field, value) -> - if (value.classId != classId || value.isNull()) "${field.name}: $value" else "${field.name}: not evaluated" + if (value.classId != classId || value.isNull()) "(${field.declaringClass}) ${field.name}: $value" else "${field.name}: not evaluated" }) // TODO: here we can get an infinite recursion if we have cyclic dependencies. } if (mocks.isNotEmpty()) { @@ -397,10 +557,7 @@ data class UtCompositeModel( other as UtCompositeModel - if (id != other.id) return false - if (classId != other.classId) return false - - return true + return id == other.id } override fun hashCode(): Int { @@ -438,34 +595,73 @@ data class UtArrayModel( return true } - override fun hashCode(): Int { - return id - } + override fun hashCode(): Int = id } +/** + * Wrapper of [origin] model, that can be handled in a different + * way in some situations (e.g. during value construction). + */ +sealed class UtModelWithCompositeOrigin( + id: Int?, + classId: ClassId, + modelName: String = id.toString(), + open val origin: UtCompositeModel?, +) : UtReferenceModel( + id = id, + classId = classId, + modelName = modelName +) + /** * Model for complex objects with assemble instructions. * - * @param instantiationChain is a chain of [UtStatementModel] to instantiate represented object - * @param modificationsChain is a chain of [UtStatementModel] to construct object state + * The default constructor is made private to enforce using a safe constructor. + * + * @param instantiationCall is an [UtExecutableCallModel] to instantiate represented object. It **must** not return `null`. + * @param modificationsChain is a chain of [UtStatementModel] to construct object state. */ -data class UtAssembleModel( +data class UtAssembleModel private constructor( override val id: Int?, override val classId: ClassId, override val modelName: String, - val instantiationChain: List = emptyList(), - val modificationsChain: List = emptyList(), - val origin: UtCompositeModel? = null -) : UtReferenceModel(id, classId, modelName) { - val allStatementsChain - get() = instantiationChain + modificationsChain - val finalInstantiationModel - get() = instantiationChain.lastOrNull() + val instantiationCall: UtStatementCallModel, + val modificationsChain: List, + override val origin: UtCompositeModel? +) : UtModelWithCompositeOrigin(id, classId, modelName, origin) { + + /** + * Creates a new [UtAssembleModel]. + * + * Please note, that it's the caller responsibility to properly cache [UtModel]s to prevent an infinite recursion. + * The order of the calling: + * 1. [instantiationCall] + * 2. [constructor] + * 3. [modificationsChainProvider]. Possible caching should be made at the beginning of this method. + * + * @param instantiationCall defines the single instruction, which provides a [UtAssembleModel]. It could be a + * constructor or a method of another class, which returns the object of the [classId] type. + * + * @param modificationsChainProvider used for creating modifying statements. Its receiver corresponds to newly + * created [UtAssembleModel], so you can use it for caching and for creating [UtExecutableCallModel]s with it + * as [UtExecutableCallModel.instance]. + */ + constructor( + id: Int?, + classId: ClassId, + modelName: String, + instantiationCall: UtStatementCallModel, + origin: UtCompositeModel? = null, + modificationsChainProvider: UtAssembleModel.() -> List = { emptyList() } + ) : this(id, classId, modelName, instantiationCall, mutableListOf(), origin) { + val modificationChainStatements = modificationsChainProvider() + (modificationsChain as MutableList).addAll(modificationChainStatements) + } override fun toString() = withToStringThreadLocalReentrancyGuard { buildString { append("UtAssembleModel(${classId.simpleName} $modelName) ") - append(instantiationChain.joinToString(" ")) + append(instantiationCall) if (modificationsChain.isNotEmpty()) { append(" ") append(modificationsChain.joinToString(" ")) @@ -481,11 +677,163 @@ data class UtAssembleModel( return id == other.id } - override fun hashCode(): Int { - return id ?: 0 + override fun hashCode(): Int = id ?: 0 +} + +/** + * Model for lambdas. + * + * Lambdas in Java represent the implementation of a single abstract method (SAM) of a functional interface. + * They can be used to create an instance of said functional interface, but **they are not classes**. + * In Java lambdas are compiled into synthetic methods of a class they are declared in. + * Depending on the captured variables, this method will be either static or non-static. + * + * Since lambdas are not classes we cannot use a class loader to get info about them as we can do for other models. + * Hence, the necessity for this specific lambda model that will be processed differently: + * instead of working with a class we will be working with the synthetic method that represents our lambda. + * + * @property id see documentation on [UtReferenceModel.id] + * @property samType the type of functional interface that this lambda will be used for (e.g. [java.util.function.Predicate]). + * `sam` means single abstract method. See https://kotlinlang.org/docs/fun-interfaces.html for more details about it in Kotlin. + * In Java it means the same. + * @property declaringClass a class where the lambda is located. + * We need this class, because the synthetic method the lambda is compiled into will be located in it. + * @property lambdaName the name of synthetic method the lambda is compiled into. + * We need it to find this method in the [declaringClass] + * @property capturedValues models of values captured by lambda. + * Lambdas can capture local variables, method arguments, static and non-static fields. + */ +// TODO: what about support for Kotlin lambdas and function types? See https://github.com/UnitTestBot/UTBotJava/issues/852 +class UtLambdaModel( + override val id: Int, + val samType: ClassId, + val declaringClass: ClassId, + val lambdaName: String, + val capturedValues: MutableList = mutableListOf(), +) : UtReferenceModel(id, samType) { + + val isFake: Boolean = lambdaName == fakeName + + val lambdaMethodId: MethodId + get() { + if (isFake) { + val targetMethod = samType.jClass.declaredMethods.single { it.isAbstract } + return object : MethodId( + declaringClass, + fakeName, + targetMethod.returnType.id, + targetMethod.parameterTypes.map { it.id } + ) { + override val modifiers: Int = ModifierFactory.invoke { + public = true + static = true + final = true + } + } + } + return declaringClass.jClass + .declaredMethods + .singleOrNull { it.name == lambdaName } + ?.executableId // synthetic lambda methods should not have overloads, so we always expect there to be only one method with the given name + ?: error("More than one method with name $lambdaName found in class: ${declaringClass.canonicalName}") + } + + override fun toString(): String = "Anonymous function $lambdaName implementing functional interface $declaringClass" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UtLambdaModel + + if (id != other.id) return false + + return true + } + + override fun hashCode(): Int = id + + companion object { + private const val fakeName = "" + + /** + * Create a non-existent lambda with fake method. + * + * That's temporary solution for building lambdas from concrete values. + */ + fun createFake(id: Int, samType: ClassId, declaringClass: ClassId) = + UtLambdaModel(id, samType, declaringClass, fakeName) } } +/** + * Common parent of all framework-specific models (e.g. Spring-specific models) + */ +abstract class UtCustomModel( + id: Int?, + classId: ClassId, + modelName: String = id.toString(), + override val origin: UtCompositeModel? = null, +) : UtModelWithCompositeOrigin(id, classId, modelName, origin) { + abstract fun shallowMap(mapper: UtModelMapper): UtCustomModel +} + +object UtSpringContextModel : UtCustomModel( + id = null, + classId = SpringModelUtils.applicationContextClassId, + modelName = "applicationContext" +) { + override fun shallowMap(mapper: UtModelMapper) = this + + // NOTE that overriding equals is required just because without it + // we will lose equality for objects after deserialization + override fun equals(other: Any?): Boolean = other is UtSpringContextModel + + override fun hashCode(): Int = 0 +} + +class UtSpringEntityManagerModel : UtCustomModel( + id = null, + classId = SpringModelUtils.entityManagerClassIds.first(), + modelName = "entityManager" +) { + override fun shallowMap(mapper: UtModelMapper) = this + + // NOTE that overriding equals is required just because without it + // we will lose equality for objects after deserialization + override fun equals(other: Any?): Boolean = other is UtSpringEntityManagerModel + + override fun hashCode(): Int = 0 +} + +data class SpringRepositoryId( + val repositoryBeanName: String, + val repositoryClassId: ClassId, + val entityClassId: ClassId, +) + +data class UtSpringMockMvcResultActionsModel( + override val id: Int?, + override val origin: UtCompositeModel?, + val status: Int, + val errorMessage: String?, + val contentAsString: String, + val viewName: String?, + // model for mvcResult.modelAndView?.model + val model: UtModel? + // TODO add headers and other data +) : UtCustomModel( + origin = origin, + classId = SpringModelUtils.resultActionsClassId, + id = id, + modelName = "mockMvcResultActions@$id" +) { + override fun shallowMap(mapper: UtModelMapper) = copy( + origin = origin?.mapPreservingType(mapper), + model = model?.map(mapper) + ) +} + /** * Model for a step to obtain [UtAssembleModel]. */ @@ -494,30 +842,24 @@ sealed class UtStatementModel( ) /** - * Step of assemble instruction that calls executable. - * - * Contains executable to call, call parameters and an instance model before call. - * Return value is used for tracking objects and call others methods with these tracking objects as parameters. + * Step of assemble instruction that calls executable or accesses the field. */ -data class UtExecutableCallModel( +sealed class UtStatementCallModel( override val instance: UtReferenceModel?, - val executable: ExecutableId, - val params: List, - val returnValue: UtReferenceModel? = null, -) : UtStatementModel(instance) { + open val statement: StatementId, + open val params: List, + var thrownConcreteException: ClassId? = null +): UtStatementModel(instance) { override fun toString() = withToStringThreadLocalReentrancyGuard { buildString { - val executableName = when (executable) { - is ConstructorId -> executable.classId.name - is MethodId -> executable.name + val executableName = when (statement) { + is ConstructorId -> statement.classId.name + is DirectFieldAccessId -> statement.name + is MethodId -> statement.name } - if (returnValue != null) { - append("val ${returnValue.modelName} = ") - } else if (instance != null) { - append("${instance.modelName}.") - } + instance?.let { append("${it.modelName}.") } append(executableName) val paramsRepresentation = params.map { param -> @@ -531,6 +873,32 @@ data class UtExecutableCallModel( } } +/** + * Step of assemble instruction that calls executable. + * + * Contains executable to call, call parameters and an instance model before call. + * @param [instance] **must be** `null` for static methods and constructors + */ +data class UtExecutableCallModel( + override val instance: UtReferenceModel?, + val executable: ExecutableId, + override val params: List, +) : UtStatementCallModel(instance, executable, params) { + override fun toString(): String = super.toString() +} + +/** + * Step of assemble instruction that directly accesses a field. + * + * Contains parameter value to set and an instance model before call. + */ +data class UtDirectGetFieldModel( + override val instance: UtReferenceModel, + val fieldAccess: DirectFieldAccessId, +) : UtStatementCallModel(instance, fieldAccess, emptyList()) { + override fun toString(): String = super.toString() +} + /** * Step of assemble instruction that sets public field with direct setter. * @@ -542,12 +910,12 @@ data class UtDirectSetFieldModel( val fieldModel: UtModel, ) : UtStatementModel(instance) { override fun toString(): String = withToStringThreadLocalReentrancyGuard { - val modelRepresentation = when (fieldModel) { - is UtAssembleModel -> fieldModel.modelName - else -> fieldModel.toString() - } - "${instance.modelName}.${fieldId.name} = $modelRepresentation" + val modelRepresentation = when (fieldModel) { + is UtAssembleModel -> fieldModel.modelName + else -> fieldModel.toString() } + "${instance.modelName}.${fieldId.name} = $modelRepresentation" + } } @@ -632,51 +1000,48 @@ val Type.classId: ClassId * [elementClassId] if this class id represents an array class, then this property * represents the class id of the array's elements. Otherwise, this property is null. */ -open class ClassId( +open class ClassId @JvmOverloads constructor( val name: String, - val elementClassId: ClassId? = null + val elementClassId: ClassId? = null, + // Treat simple class ids as non-nullable + open val isNullable: Boolean = false ) { + open val modifiers: Int + get() = jClass.modifiers + open val canonicalName: String get() = jClass.canonicalName ?: error("ClassId $name does not have canonical name") open val simpleName: String get() = jClass.simpleName /** - * For regular classes this is just a simple name - * For anonymous classes this includes the containing class and numeric indices of the anonymous class + * For regular classes this is just a simple name. + * For anonymous classes this includes the containing class and numeric indices of the anonymous class. + * + * Note: according to [java.lang.Class.getCanonicalName] documentation, local and anonymous classes + * do not have canonical names, as well as arrays whose elements don't have canonical classes themselves. + * In these cases prettified names are constructed using [ClassId.name] instead of [ClassId.canonicalName]. */ val prettifiedName: String - get() = canonicalName - .substringAfterLast(".") - .replace(Regex("[^a-zA-Z0-9]"), "") - .let { if (this.isArray) it + "Array" else it } + get() { + val baseName = when { + // anonymous classes have empty simpleName and their canonicalName is null, + // so we create a specific name for them + isAnonymous -> "Anonymous${supertypeOfAnonymousClass.prettifiedName}" + // in other cases where canonical name is still null, we use ClassId.name instead + else -> runCatching { canonicalName }.getOrElse { name } + } + return baseName + .substringAfterLast(".") + .replace(Regex("[^a-zA-Z0-9]"), "") + .let { if (this.isArray) it + "Array" else it } + } open val packageName: String get() = jClass.`package`?.name ?: "" // empty package for primitives open val isInDefaultPackage: Boolean get() = packageName.isEmpty() - open val isPublic: Boolean - get() = Modifier.isPublic(jClass.modifiers) - - open val isProtected: Boolean - get() = Modifier.isProtected(jClass.modifiers) - - open val isPrivate: Boolean - get() = Modifier.isPrivate(jClass.modifiers) - - val isPackagePrivate: Boolean - get() = !(isPublic || isProtected || isPrivate) - - open val isFinal: Boolean - get() = Modifier.isFinal(jClass.modifiers) - - open val isStatic: Boolean - get() = Modifier.isStatic(jClass.modifiers) - - open val isAbstract: Boolean - get() = Modifier.isAbstract(jClass.modifiers) - open val isAnonymous: Boolean get() = jClass.isAnonymousClass @@ -692,16 +1057,15 @@ open class ClassId( open val isSynthetic: Boolean get() = jClass.isSynthetic - open val isNullable: Boolean - // Treat simple class ids as non-nullable - get() = false + open val isKotlinObject: Boolean + get() = kClass.objectInstance != null /** * Collects all declared methods (including private and protected) from class and all its superclasses to sequence */ - // TODO for now it duplicates overridden methods JIRA:1458 open val allMethods: Sequence get() = generateSequence(jClass) { it.superclass } + .flatMap { it.interfaces.toMutableList() + it } .mapNotNull { it.declaredMethods } .flatMap { it.toList() } .map { it.executableId } @@ -718,6 +1082,12 @@ open class ClassId( open val outerClass: Class<*>? get() = jClass.enclosingClass + open val superclass: Class<*>? + get() = jClass.superclass + + open val interfaces: Array> + get() = jClass.interfaces + /** * For member classes returns a name including * enclosing classes' simple names e.g. `A.B`. @@ -727,11 +1097,11 @@ open class ClassId( * It is needed because [simpleName] for inner classes does not * take into account enclosing classes' names. */ - open val simpleNameWithEnclosings: String + open val simpleNameWithEnclosingClasses: String get() { val clazz = jClass return if (clazz.isMemberClass) { - "${clazz.enclosingClass.id.simpleNameWithEnclosings}.$simpleName" + "${clazz.enclosingClass.id.simpleNameWithEnclosingClasses}.$simpleName" } else { simpleName } @@ -766,31 +1136,53 @@ open class ClassId( * (it is important because name for nested classes contains $ as a delimiter between nested and outer classes) */ class BuiltinClassId( - name: String, + elementClassId: ClassId? = null, override val canonicalName: String, override val simpleName: String, - // by default we assume that the class is not a member class - override val simpleNameWithEnclosings: String = simpleName, - override val isPublic: Boolean = true, - override val isProtected: Boolean = false, - override val isPrivate: Boolean = false, - override val isFinal: Boolean = false, - override val isStatic: Boolean = false, - override val isAbstract: Boolean = false, + // set name manually only if it differs from canonical (e.g. for nested classes) + name: String = canonicalName, + // by default, we assume that the class is not a member class + override val simpleNameWithEnclosingClasses: String = simpleName, + override val isNullable: Boolean = false, + isPublic: Boolean = true, + isProtected: Boolean = false, + isPrivate: Boolean = false, + isFinal: Boolean = false, + isStatic: Boolean = false, + isAbstract: Boolean = false, override val isAnonymous: Boolean = false, override val isLocal: Boolean = false, override val isInner: Boolean = false, override val isNested: Boolean = false, override val isSynthetic: Boolean = false, + override val isKotlinObject: Boolean = false, + override val typeParameters: TypeParameters = TypeParameters(), override val allMethods: Sequence = emptySequence(), override val allConstructors: Sequence = emptySequence(), override val outerClass: Class<*>? = null, + // by default, we assume that the class does not have a superclass (other than Object) + override val superclass: Class<*>? = java.lang.Object::class.java, + // by default, we assume that the class does not implement any interfaces + override val interfaces: Array> = emptyArray(), override val packageName: String = when (val index = canonicalName.lastIndexOf('.')) { -1, 0 -> "" else -> canonicalName.substring(0, index) }, -) : ClassId(name) { +) : ClassId( + name = name, + elementClassId = elementClassId, + isNullable = isNullable, +) { + override val modifiers: Int = ModifierFactory { + public = isPublic + protected = isProtected + private = isPrivate + final = isFinal + static = isStatic + abstract = isAbstract + } + init { BUILTIN_CLASSES_BY_NAMES[name] = this } @@ -809,9 +1201,17 @@ class BuiltinClassId( } } -enum class FieldIdStrategyValues { - Reflection, - Soot +class CgClassId( + name: String, + elementClassId: ClassId? = null, + override val typeParameters: TypeParameters = TypeParameters(), + override val isNullable: Boolean = true, +) : ClassId(name, elementClassId) { + constructor( + classId: ClassId, + typeParameters: TypeParameters = TypeParameters(), + isNullable: Boolean = true, + ) : this(classId.name, classId.elementClassId, typeParameters, isNullable) } /** @@ -820,38 +1220,14 @@ enum class FieldIdStrategyValues { * Created to avoid usage String objects as a key. */ open class FieldId(val declaringClass: ClassId, val name: String) { - - object Strategy { - var value: FieldIdStrategyValues = FieldIdStrategyValues.Soot - } - - private val strategy - get() = if (Strategy.value == FieldIdStrategyValues.Soot) - FieldIdSootStrategy(declaringClass, this) else FieldIdReflectionStrategy(this) - - open val isPublic: Boolean - get() = strategy.isPublic - - open val isProtected: Boolean - get() = strategy.isProtected - - open val isPrivate: Boolean - get() = strategy.isPrivate - - open val isPackagePrivate: Boolean - get() = strategy.isPackagePrivate - - open val isFinal: Boolean - get() = strategy.isFinal - - open val isStatic: Boolean - get() = strategy.isStatic + open val modifiers: Int + get() = jField.modifiers open val isSynthetic: Boolean - get() = strategy.isSynthetic + get() = jField.isSynthetic open val type: ClassId - get() = strategy.type + get() = jField.type.id override fun equals(other: Any?): Boolean { if (this === other) return true @@ -871,17 +1247,7 @@ open class FieldId(val declaringClass: ClassId, val name: String) { return result } - override fun toString() = declaringClass.findFieldOrNull(name).toString() -} - -inline fun withReflection(block: () -> T): T { - val prevStrategy = FieldId.Strategy.value - try { - FieldId.Strategy.value = FieldIdStrategyValues.Reflection - return block() - } finally { - FieldId.Strategy.value = prevStrategy - } + override fun toString() = safeJField?.toString() ?: "[unresolved] $declaringClass.$name" } /** @@ -897,11 +1263,18 @@ class BuiltinFieldId( name: String, override val type: ClassId, // by default we assume that the builtin field is public and non-final - override val isPublic: Boolean = true, - override val isPrivate: Boolean = false, - override val isFinal: Boolean = false, + isPublic: Boolean = true, + isPrivate: Boolean = false, + isFinal: Boolean = false, override val isSynthetic: Boolean = false, -) : FieldId(declaringClass, name) +) : FieldId(declaringClass, name) { + override val modifiers = ModifierFactory { + public = isPublic + private = isPrivate + final = isFinal + } + +} sealed class StatementId { abstract val classId: ClassId @@ -924,12 +1297,15 @@ sealed class ExecutableId : StatementId() { abstract val returnType: ClassId abstract val parameters: List - abstract val isPublic: Boolean - abstract val isProtected: Boolean - abstract val isPrivate: Boolean + /** + * Normally during concrete execution every executable is executed in a + * [sandbox](https://github.com/UnitTestBot/UTBotJava/blob/main/docs/Sandboxing.md). + * + * However, if this flag is set to `true`, then `this` particular executable is executed without a sandbox. + */ + abstract val bypassesSandbox: Boolean - val isPackagePrivate: Boolean - get() = !(isPublic || isProtected || isPrivate) + abstract val modifiers: Int val signature: String get() { @@ -938,16 +1314,15 @@ sealed class ExecutableId : StatementId() { return "$name($args)$retType" } + fun describesSameMethodAs(other: ExecutableId): Boolean { + return classId == other.classId && signature == other.signature + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false - other as ExecutableId - - if (classId != other.classId) return false - if (signature != other.signature) return false - - return true + return describesSameMethodAs(other as ExecutableId) } override fun hashCode(): Int { @@ -969,36 +1344,24 @@ open class MethodId( override val classId: ClassId, override val name: String, override val returnType: ClassId, - override val parameters: List + override val parameters: List, + override val bypassesSandbox: Boolean = false, ) : ExecutableId() { - open val isStatic: Boolean - get() = Modifier.isStatic(method.modifiers) - - override val isPublic: Boolean - get() = Modifier.isPublic(method.modifiers) - - override val isProtected: Boolean - get() = Modifier.isProtected(method.modifiers) - - override val isPrivate: Boolean - get() = Modifier.isPrivate(method.modifiers) + override val modifiers: Int + get() = method.modifiers } -class ConstructorId( +open class ConstructorId( override val classId: ClassId, - override val parameters: List + override val parameters: List, + override val bypassesSandbox: Boolean = false, ) : ExecutableId() { - override val name: String = "" - override val returnType: ClassId = voidClassId + final override val name: String = "" + final override val returnType: ClassId = voidClassId - override val isPublic: Boolean - get() = Modifier.isPublic(constructor.modifiers) + override val modifiers: Int + get() = constructor.modifiers - override val isProtected: Boolean - get() = Modifier.isProtected(constructor.modifiers) - - override val isPrivate: Boolean - get() = Modifier.isPrivate(constructor.modifiers) } class BuiltinMethodId( @@ -1006,29 +1369,218 @@ class BuiltinMethodId( name: String, returnType: ClassId, parameters: List, + bypassesSandbox: Boolean = false, // by default we assume that the builtin method is non-static and public - override val isStatic: Boolean = false, - override val isPublic: Boolean = true, - override val isProtected: Boolean = false, - override val isPrivate: Boolean = false -) : MethodId(classId, name, returnType, parameters) - -open class TypeParameters(val parameters: List = emptyList()) - -class WildcardTypeParameter: TypeParameters(emptyList()) - -interface TestCaseGenerator { - fun init( - buildDir: Path, - classpath: String? = null, - dependencyPaths: String, - isCanceled: () -> Boolean = { false } - ) + isStatic: Boolean = false, + isPublic: Boolean = true, + isProtected: Boolean = false, + isPrivate: Boolean = false +) : MethodId(classId, name, returnType, parameters, bypassesSandbox) { + override val modifiers: Int = ModifierFactory { + static = isStatic + public = isPublic + private = isPrivate + protected = isProtected + } +} + +class BuiltinConstructorId( + classId: ClassId, + parameters: List, + bypassesSandbox: Boolean = false, + // by default, we assume that the builtin constructor is public + isPublic: Boolean = true, + isProtected: Boolean = false, + isPrivate: Boolean = false +) : ConstructorId(classId, parameters, bypassesSandbox) { + override val modifiers: Int = ModifierFactory { + public = isPublic + private = isPrivate + protected = isProtected + } +} + +open class TypeParameters(val parameters: List = emptyList()) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as TypeParameters - fun generate(method: UtMethod<*>, mockStrategy: MockStrategyApi): UtTestCase + if (parameters != other.parameters) return false + + return true + } + + override fun hashCode(): Int { + return parameters.hashCode() + } } +object WildcardTypeParameter : TypeParameters(emptyList()) + +/** + * Describes the way to replace abstract types with concrete implementors. + */ +enum class TypeReplacementMode { + /** + * Any possible implementor (that is preferred by solver) may be used. + */ + AnyImplementor, + + /** + * There is a known implementor to be used. + * For example, it is obtained from bean definitions in Spring application. + */ + KnownImplementor, + + /** + * Using implementors is not allowed. + * If mocking is allowed, mock of this type will be used. + * Otherwise, branch will be pruned as unsatisfiable. + */ + NoImplementors, +} + +sealed class SpringConfiguration(val fullDisplayName: String) { + abstract class JavaBasedConfiguration(open val configBinaryName: String) : SpringConfiguration(configBinaryName) + + class JavaConfiguration(override val configBinaryName: String) : JavaBasedConfiguration(configBinaryName) + + class SpringBootConfiguration( + override val configBinaryName: String, + val isDefinitelyUnique: Boolean + ) : JavaBasedConfiguration(configBinaryName) + + class XMLConfiguration(val absolutePath: String) : SpringConfiguration(absolutePath) +} + +sealed interface SpringSettings { + companion object AbsentSpringSettings : SpringSettings { + // NOTE that overriding equals is required just because without it + // we will lose equality for objects after deserialization + override fun equals(other: Any?): Boolean = other is AbsentSpringSettings + + override fun hashCode(): Int = 0 + } + + data class PresentSpringSettings( + val configuration: SpringConfiguration, + val profiles: List + ) : SpringSettings +} + +/** + * Result of loading concrete execution context (e.g. Spring application context). + * + * [contextLoaded] can be `true` while [exceptions] is not empty. For example, we may fail + * to load context with most specific SpringApi available (e.g. SpringBoot), + * but successfully fall back to less specific SpringApi (e.g. PureSpring). + */ +class ConcreteContextLoadingResult( + val contextLoaded: Boolean, + val exceptions: List +) { + val utErrors: List get() = + exceptions.map { UtError(it.message ?: "Concrete context loading failed", it) } + + fun andThen(onSuccess: () -> ConcreteContextLoadingResult) = + if (contextLoaded) { + val otherResult = onSuccess() + ConcreteContextLoadingResult( + contextLoaded = otherResult.contextLoaded, + exceptions = exceptions + otherResult.exceptions + ) + } else this + + companion object { + fun successWithoutExceptions() = ConcreteContextLoadingResult( + contextLoaded = true, + exceptions = emptyList() + ) + } +} + +enum class SpringTestType( + override val id: String, + override val displayName: String, + override val description: String, +) : CodeGenerationSettingItem { + UNIT_TEST( + "Unit tests", + "Unit tests", + "Generate unit tests mocking other classes" + ), + INTEGRATION_TEST( + "Integration tests", + "Integration tests", + "Generate integration tests autowiring real instance" + ); + + override fun toString() = id + + companion object : CodeGenerationSettingBox { + override val defaultItem = UNIT_TEST + override val allItems: List = values().toList() + } +} + +class SpringProfileNames( + override val defaultItem: String, +) : CodeGenerationSettingTextField { + + override fun toString() = defaultItem + + companion object : CodeGenerationSettingTextField { + override val defaultItem = "default" + } +} + +const val NO_SPRING_CONFIGURATION_OPTION = "No configuration" + +class SpringConfig( + override val defaultItem: String, +) : CodeGenerationSettingTextField { + + override fun toString() = defaultItem + + companion object : CodeGenerationSettingTextField { + override val defaultItem = NO_SPRING_CONFIGURATION_OPTION + } +} + +/** + * Describes information about beans obtained from Spring analysis process. + * + * Contains the name of the bean, its type (class or interface) and optional additional data. + * + * @param beanTypeName a name in a form obtained by [java.lang.Class.getName] method. + */ +data class BeanDefinitionData( + val beanName: String, + val beanTypeName: String, + val additionalData: BeanAdditionalData?, +) + +/** + * Describes some additional information about beans obtained from Spring analysis process. + * + * Sometimes the actual type of the bean can not be obtained from bean definition. + * Then we try to recover it by method and class defining bean (e.g. using Psi elements). + * + * @param configClassName a name in a form obtained by [java.lang.Class.getName] method. + */ +data class BeanAdditionalData( + val factoryMethodName: String, + val parameterTypes: List, + val configClassName: String, +) + +val RefType.isAbstractType + get() = this.sootClass.isAbstract || this.sootClass.isInterface + interface CodeGenerationSettingItem { + val id: String val displayName: String val description: String } @@ -1040,45 +1592,62 @@ interface CodeGenerationSettingBox { fun labels(): Array = allItems.map { it.displayName }.toTypedArray() } +interface CodeGenerationSettingTextField { + val defaultItem: String +} + enum class MockStrategyApi( + override val id: String, override val displayName: String, override val description: String ) : CodeGenerationSettingItem { - NO_MOCKS("No mocks", "Do not use mock frameworks at all"), + NO_MOCKS("No mocks", "Do not mock", "Do not use mock frameworks at all"), OTHER_PACKAGES( - "Other packages: $MOCKITO", + "Other packages: Mockito", + "Mock everything outside the package", "Mock all classes outside the current package except system ones" ), OTHER_CLASSES( - "Other classes: $MOCKITO", + "Other classes: Mockito", + "Mock everything outside the class", "Mock all classes outside the class under test except system ones" ); - override fun toString() = displayName + override fun toString() = id // Get is mandatory because of the initialization order of the inheritors. // Otherwise, in some cases we could get an incorrect value companion object : CodeGenerationSettingBox { override val defaultItem = OTHER_PACKAGES override val allItems: List = values().toList() + + // Mock strategy gains more meaning in Spring Projects. + // We use OTHER_CLASSES strategy as default one in `No configuration` mode + // and as unique acceptable in other modes (combined with type replacement). + val springDefaultItem = OTHER_CLASSES + // We use NO_MOCKS strategy in integration tests because they are based on fuzzer that is not compatible with mocks + val springIntegrationTestItem = NO_MOCKS } } enum class TreatOverflowAsError( + override val id: String, override val displayName: String, override val description: String, ) : CodeGenerationSettingItem { AS_ERROR( + id = "Treat overflows as errors", displayName = "Treat overflows as errors", description = "Generate tests that treat possible overflows in arithmetic operations as errors " + "that throw Arithmetic Exception", ), IGNORE( + id = "Ignore overflows", displayName = "Ignore overflows", description = "Ignore possible overflows in arithmetic operations", ); - override fun toString(): String = displayName + override fun toString(): String = id // Get is mandatory because of the initialization order of the inheritors. // Otherwise, in some cases we could get an incorrect value @@ -1088,14 +1657,39 @@ enum class TreatOverflowAsError( } } +enum class JavaDocCommentStyle( + override val id: String, + override val displayName: String, + override val description: String, +) : CodeGenerationSettingItem { + CUSTOM_JAVADOC_TAGS( + id = "Structured via custom Javadoc tags", + displayName = "Structured via custom Javadoc tags", + description = "Uses custom Javadoc tags to describe test's execution path." + ), + FULL_SENTENCE_WRITTEN( + id = "Plain text", + displayName = "Plain text", + description = "Uses plain text to describe test's execution path." + ); + + override fun toString(): String = displayName + + companion object : CodeGenerationSettingBox { + override val defaultItem = if (UtSettings.useCustomJavaDocTags) CUSTOM_JAVADOC_TAGS else FULL_SENTENCE_WRITTEN + override val allItems = JavaDocCommentStyle.values().toList() + } +} + enum class MockFramework( + override val id: String = "Mockito", override val displayName: String, override val description: String = "Use $displayName as mock framework", var isInstalled: Boolean = false ) : CodeGenerationSettingItem { - MOCKITO("Mockito"); + MOCKITO(displayName = "Mockito"); - override fun toString() = displayName + override fun toString() = id companion object : CodeGenerationSettingBox { override val defaultItem: MockFramework = MOCKITO @@ -1104,11 +1698,12 @@ enum class MockFramework( } enum class CodegenLanguage( + override val id: String, override val displayName: String, @Suppress("unused") override val description: String = "Generate unit tests in $displayName" ) : CodeGenerationSettingItem { - JAVA(displayName = "Java"), - KOTLIN(displayName = "Kotlin"); + JAVA(id = "Java", displayName = "Java"), + KOTLIN(id = "Kotlin", displayName = "Kotlin (experimental)"); enum class OperatingSystem { WINDOWS, @@ -1147,16 +1742,21 @@ enum class CodegenLanguage( KOTLIN -> listOf(System.getenv("JAVA_HOME"), "bin", "java") }.joinToString(File.separator) - override fun toString(): String = displayName + override fun toString(): String = id fun getCompilationCommand(buildDirectory: String, classPath: String, sourcesFiles: List): List { val arguments = when (this) { JAVA -> listOf( "-d", buildDirectory, "-cp", classPath, - "-XDignore.symbol.file" // to let javac use classes from rt.jar - ).plus(sourcesFiles) - KOTLIN -> listOf("-d", buildDirectory, "-jvm-target", jvmTarget, "-cp", classPath).plus(sourcesFiles) + "-XDignore.symbol.file", // to let javac use classes from rt.jar + ).plus(OpenModulesContainer.javaVersionSpecificArguments.toMutableList().apply { + if (last().contains("illegal")) + removeLast() + }).plus(sourcesFiles) + + // TODO: -Xskip-prerelease-check is needed to handle #1262, check if this is good enough solution + KOTLIN -> listOf("-d", buildDirectory, "-jvm-target", jvmTarget, "-cp", classPath, "-Xskip-prerelease-check").plus(sourcesFiles) } if (this == KOTLIN && System.getenv("KOTLIN_HOME") == null) { throw RuntimeException("'KOTLIN_HOME' environment variable is not defined. Standard location is {IDEA installation dir}/plugins/Kotlin/kotlinc") @@ -1172,10 +1772,12 @@ enum class CodegenLanguage( override val allItems: List = values().toList() } } +//TODO #1279 +fun CodegenLanguage?.isSummarizationCompatible() = this == CodegenLanguage.JAVA // https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javac.html#commandlineargfile fun isolateCommandLineArgumentsToArgumentFile(arguments: List): String { - val argumentFile = File.createTempFile("cmd-args", "") + val argumentFile = FileUtil.createTempFile("cmd-args", "").toFile() argumentFile.writeText( arguments.joinToString(" ") { // If a filename contains embedded spaces, put the whole filename in double quotes, @@ -1186,13 +1788,6 @@ fun isolateCommandLineArgumentsToArgumentFile(arguments: List): String { return argumentFile.absolutePath.let { "@$it" } } -interface UtService { - val displayName: String - val serviceProvider: T -} - -interface TestGeneratorService : UtService - private fun StringBuilder.appendOptional(name: String, value: Collection<*>) { if (value.isNotEmpty()) { append(", $name=$value") @@ -1206,7 +1801,7 @@ private fun StringBuilder.appendOptional(name: String, value: Map<*, *>) { } /** - * Entity that represents cluster information that should appear in the comment + * Entity that represents cluster information that should appear in the comment. */ data class UtClusterInfo( val header: String? = null, @@ -1214,13 +1809,12 @@ data class UtClusterInfo( ) /** - * Entity that represents cluster of executions + * Entity that represents cluster of executions. */ data class UtExecutionCluster(val clusterInfo: UtClusterInfo, val executions: List) - /** - * Entity that represents various types of statements in comments + * Entity that represents various types of statements in comments. */ sealed class DocStatement @@ -1235,6 +1829,9 @@ class DocPreTagStatement(content: List) : DocTagStatement(content) override fun hashCode(): Int = content.hashCode() } +data class DocCustomTagStatement(val statements: List) : DocTagStatement(statements) { + override fun toString(): String = content.joinToString(separator = "") +} open class DocClassLinkStmt(val className: String) : DocStatement() { override fun toString(): String = className @@ -1275,3 +1872,12 @@ class DocRegularStmt(val stmt: String) : DocStatement() { override fun hashCode(): Int = stmt.hashCode() } + +class DocRegularLineStmt(val stmt: String) : DocStatement() { + override fun toString(): String = stmt + + override fun equals(other: Any?): Boolean = + if (other is DocRegularLineStmt) this.hashCode() == other.hashCode() else false + + override fun hashCode(): Int = stmt.hashCode() +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/ArtificialErrors.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/ArtificialErrors.kt new file mode 100644 index 0000000000..f1578afd76 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/ArtificialErrors.kt @@ -0,0 +1,31 @@ +package org.utbot.framework.plugin.api + +/** + * Represents an error that may be detected or not + * during analysis in accordance with custom settings. + * + * Usually execution may be continued somehow after such error, + * but the result may be different from basic expectations. + */ +sealed class ArtificialError(message: String): Error(message) + +/** + * Represents overflow detection errors in symbolic engine, + * if a mode to detect them is turned on. + * + * See [TraversalContext.intOverflowCheck] for more details. + */ +class OverflowDetectionError(message: String): ArtificialError(message) + +/** + * An artificial error that could be implicitly thrown by the symbolic engine during taint sink processing. + */ +class TaintAnalysisError( + /** Sink method name: "${classId.simpleName}.${methodId.name}". */ + val sinkName: String, + /** Some information about a tainted var, for example, its type. */ + val taintedVar: String, + /** Name of the taint mark. */ + val taintMark: String, + message: String = "'$taintedVar' marked '$taintMark' was passed into '$sinkName' method" +) : ArtificialError(message) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/CoverageApi.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/CoverageApi.kt index 95085c3c04..3c3f31cb08 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/CoverageApi.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/CoverageApi.kt @@ -3,19 +3,21 @@ package org.utbot.framework.plugin.api /** * Represents a covered bytecode instruction. * - * @param className a fqn of the class. + * @param internalName the fqn in internal form, i.e. com/rest/order/services/OrderService$InnerClass. * @param methodSignature the signature of the method. * @param lineNumber a number of the line in the source file. * @param id a unique identifier among all instructions in all classes. * * @see Test minimization */ -data class Instruction( - val className: String, +open class Instruction( + val internalName: String, val methodSignature: String, val lineNumber: Int, val id: Long -) +) { + val className: String get() = internalName.replace('/', '.') +} /** * Represents coverage information. Some other @@ -23,8 +25,12 @@ data class Instruction( * Some other useful information (e.g., covered branches, etc.) may be added in the future. * * @param coveredInstructions a list of the covered instructions in the order they are visited. + * @param instructionsCount a number of all instructions in the current class. + * @param missedInstructions a list of the missed instructions. * */ -class Coverage( - val coveredInstructions: List = emptyList() +data class Coverage( + val coveredInstructions: List = emptyList(), + val instructionsCount: Long? = null, + val missedInstructions: List = emptyList(), ) \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt index 4b41a6dd88..3c8c9612df 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt @@ -1,95 +1,110 @@ -package org.utbot.framework.plugin.api - -import java.io.File -import java.util.LinkedList - -sealed class UtExecutionResult - -data class UtExecutionSuccess(val model: UtModel) : UtExecutionResult() { - override fun toString() = "$model" -} - -sealed class UtExecutionFailure : UtExecutionResult() { - abstract val exception: Throwable - val isCheckedException get() = !(exception is RuntimeException || exception is Error) -} - -data class UtOverflowFailure( - override val exception: Throwable, -) : UtExecutionFailure() - -/** - * unexpectedFail (when exceptions such as NPE, IOBE, etc. appear, but not thrown by a user, applies both for function under test and nested calls ) - * expectedCheckedThrow (when function under test or nested call explicitly says that checked exception could be thrown and throws it) - * expectedUncheckedThrow (when there is a throw statement for unchecked exception inside of function under test) - * unexpectedUncheckedThrow (in case when there is unchecked exception thrown from nested call) - */ -data class UtExplicitlyThrownException( - override val exception: Throwable, - val fromNestedMethod: Boolean -) : UtExecutionFailure() - -data class UtImplicitlyThrownException( - override val exception: Throwable, - val fromNestedMethod: Boolean -) : UtExecutionFailure() - -class TimeoutException(s: String) : Exception(s) - -data class UtTimeoutException(override val exception: TimeoutException) : UtExecutionFailure() - -/** - * Indicates failure in concrete execution. - * For now it is explicitly throwing by ConcreteExecutor in case child process death. - */ -class ConcreteExecutionFailureException(cause: Throwable, errorFile: File, val processStdout: List) : - Exception( - buildString { - appendLine() - appendLine("----------------------------------------") - appendLine("The child process is dead") - appendLine("Cause:\n${cause.message}") - appendLine("Last 20 lines of the error log ${errorFile.absolutePath}:") - appendLine("----------------------------------------") - errorFile.useLines { lines -> - val lastLines = LinkedList() - for (line in lines) { - lastLines.add(line) - if (lastLines.size > 20) { - lastLines.removeFirst() - } - } - lastLines.forEach { appendLine(it) } - } - appendLine("----------------------------------------") - }, - cause - ) - -data class UtConcreteExecutionFailure(override val exception: ConcreteExecutionFailureException) : UtExecutionFailure() - -val UtExecutionResult.isSuccess: Boolean - get() = this is UtExecutionSuccess - -val UtExecutionResult.isFailure: Boolean - get() = this is UtExecutionFailure - -inline fun UtExecutionResult.onSuccess(action: (model: UtModel) -> Unit): UtExecutionResult { - if (this is UtExecutionSuccess) action(model) - return this -} - -inline fun UtExecutionResult.onFailure(action: (exception: Throwable) -> Unit): UtExecutionResult { - if (this is UtExecutionFailure) action(exception) - return this -} - -fun UtExecutionResult.getOrThrow(): UtModel = when (this) { - is UtExecutionSuccess -> model - is UtExecutionFailure -> throw exception -} - -fun UtExecutionResult.exceptionOrNull(): Throwable? = when (this) { - is UtExecutionFailure -> exception - is UtExecutionSuccess -> null -} +package org.utbot.framework.plugin.api + +import org.utbot.framework.plugin.api.visible.UtStreamConsumingException + +sealed class UtExecutionResult + +data class UtExecutionSuccess(val model: UtModel) : UtExecutionResult() { + override fun toString() = "$model" +} + +sealed class UtExecutionFailure : UtExecutionResult() { + abstract val exception: Throwable + + /** + * Represents the most inner exception in the failure. + * Often equals to [exception], but is wrapped exception in [UtStreamConsumingException]. + */ + open val rootCauseException: Throwable + get() = exception +} + +data class UtOverflowFailure( + override val exception: Throwable, +) : UtExecutionFailure() + +data class UtTaintAnalysisFailure( + override val exception: Throwable +) : UtExecutionFailure() + +data class UtSandboxFailure( + override val exception: Throwable +) : UtExecutionFailure() + +data class UtStreamConsumingFailure( + override val exception: UtStreamConsumingException, +) : UtExecutionFailure() { + override val rootCauseException: Throwable + get() = exception.innerExceptionOrAny +} + +/** + * unexpectedFail (when exceptions such as NPE, IOBE, etc. appear, but not thrown by a user, applies both for function under test and nested calls ) + * + * expectedCheckedThrow (when function under test or nested call explicitly says that checked exception could be thrown and throws it) + * + * expectedUncheckedThrow (when there is a throw statement for unchecked exception inside of function under test) + * + * unexpectedUncheckedThrow (in case when there is unchecked exception thrown from nested call) + */ +data class UtExplicitlyThrownException( + override val exception: Throwable, + val fromNestedMethod: Boolean +) : UtExecutionFailure() + +data class UtImplicitlyThrownException( + override val exception: Throwable, + val fromNestedMethod: Boolean +) : UtExecutionFailure() + +class TimeoutException(s: String) : Exception(s) + +data class UtTimeoutException(override val exception: TimeoutException) : UtExecutionFailure() + +/** + * Indicates failure in concrete execution. + * For now it is explicitly throwing by ConcreteExecutor in case instrumented process death. + */ +class InstrumentedProcessDeathException(cause: Throwable) : + Exception( + buildString { + appendLine() + appendLine("----------------------------------------") + appendLine("The instrumented process is dead") + appendLine("Cause:\n${cause.message}") + appendLine("----------------------------------------") + }, + cause + ) + +data class UtConcreteExecutionFailure(override val exception: Throwable) : UtExecutionFailure() + +/** + * Represents a failure in instrumented process + * that is not actually caused by concrete method under test call. + * + * For example, failure may have occurred during method arguments preparation + * or statics initializers processing during object instance creation. + */ +data class UtConcreteExecutionProcessedFailure(override val exception: Throwable) : UtExecutionFailure() + +val UtExecutionResult.isSuccess: Boolean + get() = this is UtExecutionSuccess + +val UtExecutionResult.isFailure: Boolean + get() = this is UtExecutionFailure + +inline fun UtExecutionResult.onSuccess(action: (model: UtModel) -> Unit): UtExecutionResult { + if (this is UtExecutionSuccess) action(model) + return this +} + +inline fun UtExecutionResult.onFailure(action: (exception: Throwable) -> Unit): UtExecutionResult { + if (this is UtExecutionFailure) action(rootCauseException) + return this +} + +fun UtExecutionResult.exceptionOrNull(): Throwable? = when (this) { + is UtExecutionFailure -> rootCauseException + is UtExecutionSuccess -> null +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/ValueBasedApi.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/ValueBasedApi.kt index 84341b008e..97a0fe74d2 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/ValueBasedApi.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/ValueBasedApi.kt @@ -6,15 +6,12 @@ package org.utbot.framework.plugin.api -import kotlin.reflect.KClass import org.apache.commons.lang3.builder.RecursiveToStringStyle import org.apache.commons.lang3.builder.ReflectionToStringBuilder -import soot.jimple.JimpleBody -data class UtValueTestCase( - val method: UtMethod, +data class UtMethodValueTestSet( + val method: ExecutableId, val executions: List> = emptyList(), - val jimpleBody: JimpleBody? = null, val errors: Map = emptyMap(), ) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/impl/FieldIdStrategies.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/impl/FieldIdStrategies.kt deleted file mode 100644 index 8ce32fdbc2..0000000000 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/impl/FieldIdStrategies.kt +++ /dev/null @@ -1,90 +0,0 @@ -package org.utbot.framework.plugin.api.impl - -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.classId -import org.utbot.framework.plugin.api.util.field -import org.utbot.framework.plugin.api.util.id -import java.lang.reflect.Modifier -import soot.Scene -import soot.SootClass - -interface FieldIdStrategy { - - val isPublic: Boolean - - val isProtected: Boolean - - val isPrivate: Boolean - - val isPackagePrivate: Boolean - get() = !(isPublic || isProtected || isPrivate) - - val isFinal: Boolean - - val isStatic: Boolean - - val isSynthetic: Boolean - - val type: ClassId -} - -class FieldIdReflectionStrategy(val fieldId: FieldId) : FieldIdStrategy { - - override val isPublic: Boolean - get() = Modifier.isPublic(fieldId.field.modifiers) - - override val isProtected: Boolean - get() = Modifier.isProtected(fieldId.field.modifiers) - - override val isPrivate: Boolean - get() = Modifier.isPrivate(fieldId.field.modifiers) - - override val isFinal: Boolean - get() = Modifier.isFinal(fieldId.field.modifiers) - - override val isStatic: Boolean - get() = Modifier.isStatic(fieldId.field.modifiers) - - override val isSynthetic: Boolean - get() = fieldId.field.isSynthetic - - override val type: ClassId - get() = fieldId.field.type.id -} - -class FieldIdSootStrategy(val declaringClass: ClassId, val fieldId: FieldId) : FieldIdStrategy { - - private val declaringSootClass: SootClass - get() = Scene.v().getSootClass(declaringClass.name) - - /** - * For hidden field (fields with the same names but different types in one class) produces RuntimeException. - * [SAT-315](JIRA:315) - */ - private val modifiers: Int - get() = declaringSootClass.getFieldByName(fieldId.name).modifiers - - - override val isPublic: Boolean - get() = soot.Modifier.isPublic(modifiers) - - override val isProtected: Boolean - get() = soot.Modifier.isProtected(modifiers) - - override val isPrivate: Boolean - get() = soot.Modifier.isPrivate(modifiers) - - override val isFinal: Boolean - get() = soot.Modifier.isFinal(modifiers) - - override val isStatic: Boolean - get() = soot.Modifier.isStatic(modifiers) - - override val isSynthetic: Boolean - get() = soot.Modifier.isSynthetic(modifiers) - - override val type: ClassId - get() = declaringSootClass.getFieldByName(fieldId.name).type.classId - -} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelDeepMapper.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelDeepMapper.kt new file mode 100644 index 0000000000..1d207f118d --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelDeepMapper.kt @@ -0,0 +1,124 @@ +package org.utbot.framework.plugin.api.mapper + +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtClassRefModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtCustomModel +import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtVoidModel +import java.util.IdentityHashMap + +/** + * Performs deep mapping of [UtModel]s. + * + * NOTE: + * - [shallowMapper] is invoked on models **before** mapping their sub models. + * - [shallowMapper] is responsible for caching own results (it may be called repeatedly on same models). + */ +class UtModelDeepMapper private constructor( + private val shallowMapper: UtModelMapper +) : UtModelMapper { + constructor(shallowMapper: (UtModel) -> UtModel) : this(UtModelSafeCastingCachingShallowMapper(shallowMapper)) + + /** + * Keys are models that have been shallowly mapped by [shallowMapper]. + * Values are models that have been deeply mapped by this [UtModelDeepMapper]. + * Models are only associated with models of the same type (i.e. the cache type is actually `MutableMap`) + */ + private val cache = IdentityHashMap() + + private val allInputtedModels get() = cache.keys + private val allOutputtedModels get() = cache.values + + override fun map(model: T, clazz: Class): T = + clazz.cast(mapNestedModels(shallowMapper.map(model, clazz))) + + /** + * Maps models contained inside [model], but not the [model] itself. + */ + private fun mapNestedModels(model: UtModel): UtModel = cache.getOrPut(model) { + when (model) { + is UtNullModel, + is UtPrimitiveModel, + is UtEnumConstantModel, + is UtClassRefModel, + is UtVoidModel -> model + is UtArrayModel -> mapNestedModels(model) + is UtCompositeModel -> mapNestedModels(model) + is UtLambdaModel -> mapNestedModels(model) + is UtAssembleModel -> mapNestedModels(model) + is UtCustomModel -> mapNestedModels(model) + + // PythonModel, JsUtModel may be here + else -> throw UnsupportedOperationException("UtModel $this cannot be mapped") + } + } + + private fun mapNestedModels(model: UtArrayModel): UtReferenceModel { + val mappedModel = UtArrayModel( + id = model.id, + classId = model.classId, + length = model.length, + constModel = model.constModel, + stores = model.stores, + ) + cache[model] = mappedModel + + mappedModel.constModel = model.constModel.map(this) + mappedModel.stores.putAll(model.stores.mapModelValues(this)) + + return mappedModel + } + + private fun mapNestedModels(model: UtCompositeModel): UtCompositeModel { + val mappedModel = UtCompositeModel( + id = model.id, + classId = model.classId, + isMock = model.isMock, + ) + cache[model] = mappedModel + + mappedModel.fields.putAll(model.fields.mapModelValues(this)) + mappedModel.mocks.putAll(model.mocks.mapValuesTo(mutableMapOf()) { it.value.mapModels(this@UtModelDeepMapper) }) + + return mappedModel + } + + private fun mapNestedModels(model: UtLambdaModel): UtReferenceModel = UtLambdaModel( + id = model.id, + samType = model.samType, + declaringClass = model.declaringClass, + lambdaName = model.lambdaName, + capturedValues = model.capturedValues.mapModels(this@UtModelDeepMapper).toMutableList() + ) + + private fun mapNestedModels(model: UtAssembleModel): UtReferenceModel = UtAssembleModel( + id = model.id, + classId = model.classId, + modelName = model.modelName, + instantiationCall = model.instantiationCall.mapModels(this), + modificationsChainProvider = { + cache[model] = this@UtAssembleModel + model.modificationsChain.map { it.mapModels(this@UtModelDeepMapper) } + }, + origin = model.origin?.mapPreservingType(this) + ) + + private fun mapNestedModels(model: UtCustomModel): UtReferenceModel = + model.shallowMap(this) + + companion object { + /** + * Creates identity deep mapper, runs [block] on it, and returns the set of all models that + * were mapped (i.e. deeply collects all models reachable from models passed to `collector`). + */ + fun collectAllModels(block: (collector: UtModelDeepMapper) -> Unit): Set = + UtModelDeepMapper(UtModelNoopMapper).also(block).allInputtedModels + } +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelMapper.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelMapper.kt new file mode 100644 index 0000000000..8db21f8baf --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelMapper.kt @@ -0,0 +1,18 @@ +package org.utbot.framework.plugin.api.mapper + +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtModelWithCompositeOrigin + +interface UtModelMapper { + /** + * Performs depending on the implementation deep or shallow mapping of the [model]. + * + * In some cases (e.g. when mapping [UtModelWithCompositeOrigin.origin]) you may want to get result + * of some specific type (e.g. [UtCompositeModel]), only then you should specify specific value for [clazz]. + * + * NOTE: if you are fine with result model and [model] having different types, then you should + * use `UtModel::class.java` as a value for [clazz] or just use [UtModel.map]. + */ + fun map(model: T, clazz: Class): T +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelNoopMapper.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelNoopMapper.kt new file mode 100644 index 0000000000..0325b52343 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelNoopMapper.kt @@ -0,0 +1,7 @@ +package org.utbot.framework.plugin.api.mapper + +import org.utbot.framework.plugin.api.UtModel + +object UtModelNoopMapper : UtModelMapper { + override fun map(model: T, clazz: Class): T = model +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelSafeCastingCachingShallowMapper.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelSafeCastingCachingShallowMapper.kt new file mode 100644 index 0000000000..e511450550 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/UtModelSafeCastingCachingShallowMapper.kt @@ -0,0 +1,16 @@ +package org.utbot.framework.plugin.api.mapper + +import org.utbot.framework.plugin.api.UtModel +import java.util.IdentityHashMap + +class UtModelSafeCastingCachingShallowMapper( + val mapper: (UtModel) -> UtModel +) : UtModelMapper { + private val cache = IdentityHashMap() + + override fun map(model: T, clazz: Class): T { + val mapped = cache.getOrPut(model) { mapper(model) } + return if (clazz.isInstance(mapped)) clazz.cast(mapped) + else model + } +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/Utils.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/Utils.kt new file mode 100644 index 0000000000..9c8ed80a8a --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/Utils.kt @@ -0,0 +1,75 @@ +package org.utbot.framework.plugin.api.mapper + +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.UtDirectGetFieldModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtExecutionResult +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtInstrumentation +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtStatementCallModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation +import org.utbot.framework.plugin.api.isSuccess + +inline fun T.mapPreservingType(mapper: UtModelMapper): T = + mapper.map(this, T::class.java) + +fun UtModel.map(mapper: UtModelMapper) = mapPreservingType(mapper) + +fun List.mapModels(mapper: UtModelMapper): List = + map { model -> model.map(mapper) } + +fun Map.mapModelValues(mapper: UtModelMapper): Map = + mapValues { (_, model) -> model.map(mapper) } + +fun UtStatementModel.mapModels(mapper: UtModelMapper): UtStatementModel = + when(this) { + is UtStatementCallModel -> mapModels(mapper) + is UtDirectSetFieldModel -> UtDirectSetFieldModel( + instance = instance.mapPreservingType(mapper), + fieldId = fieldId, + fieldModel = fieldModel.map(mapper) + ) + } + +fun UtStatementCallModel.mapModels(mapper: UtModelMapper): UtStatementCallModel = + when(this) { + is UtDirectGetFieldModel -> UtDirectGetFieldModel( + instance = instance.mapPreservingType(mapper), + fieldAccess = fieldAccess, + ) + is UtExecutableCallModel -> UtExecutableCallModel( + instance = instance?.mapPreservingType(mapper), + executable = executable, + params = params.mapModels(mapper) + ) + } + +fun EnvironmentModels.mapModels(mapper: UtModelMapper) = EnvironmentModels( + thisInstance = thisInstance?.map(mapper), + statics = statics.mapModelValues(mapper), + parameters = parameters.mapModels(mapper), + executableToCall = executableToCall, +) + +fun UtExecutionResult.mapModelIfExists(mapper: UtModelMapper) = if (this.isSuccess) { + val successResult = this as UtExecutionSuccess + UtExecutionSuccess(successResult.model.map(mapper)) +} else { + this +} + + +fun UtInstrumentation.mapModels(mapper: UtModelMapper) = when (this) { + is UtNewInstanceInstrumentation -> copy(instances = instances.mapModels(mapper)) + is UtStaticMethodInstrumentation -> copy(values = values.mapModels(mapper)) +} + +fun UtExecution.mapStateBeforeModels(mapper: UtModelMapper) = copy( + stateBefore = stateBefore.mapModels(mapper) +) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt index 9d2173115b..908e2a8ca5 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IdUtil.kt @@ -1,7 +1,7 @@ package org.utbot.framework.plugin.api.util -import org.utbot.common.findFieldOrNull import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.BuiltinConstructorId import org.utbot.framework.plugin.api.BuiltinMethodId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConstructorId @@ -11,24 +11,53 @@ import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.framework.plugin.api.id +import soot.SootField import java.lang.reflect.Constructor import java.lang.reflect.Executable import java.lang.reflect.Field import java.lang.reflect.Method +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type import java.util.concurrent.atomic.AtomicInteger -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.contract +import kotlin.jvm.internal.CallableReference import kotlin.reflect.KCallable import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KProperty +import kotlin.reflect.full.extensionReceiverParameter +import kotlin.reflect.full.instanceParameter +import kotlin.reflect.jvm.internal.impl.load.kotlin.header.KotlinClassHeader import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaGetter import kotlin.reflect.jvm.javaMethod +import kotlin.reflect.jvm.kotlinFunction // ClassId utils +/** + * A type is called **non-denotable** if its name cannot be used in the source code. + * For example, anonymous classes **are** non-denotable types. + * On the other hand, [java.lang.Integer], for example, **is** denotable. + * + * This property returns the same type for denotable types, + * and it returns the supertype when given an anonymous class. + * + * **NOTE** that in Java there are non-denotable types other than anonymous classes. + * For example, null-type, intersection types, capture types. + * But [ClassId] cannot contain any of these (at least at the moment). + * So we only consider the case of anonymous classes. + */ +val ClassId.denotableType: ClassId + get() { + return when { + this.isAnonymous -> this.supertypeOfAnonymousClass + else -> this + } + } + @Suppress("unused") val ClassId.enclosingClass: ClassId? get() = jClass.enclosingClass?.id @@ -91,24 +120,66 @@ infix fun ClassId.isSubtypeOf(type: ClassId): Boolean { // unwrap primitive wrappers val left = primitiveByWrapper[this] ?: this val right = primitiveByWrapper[type] ?: type + if (left == right) { return true } - val leftClass = this.jClass + + val leftClass = this + val superTypes = leftClass.allSuperTypes() + + return right in superTypes +} + +fun ClassId.allSuperTypes(): Sequence { val interfaces = sequence { - var types = listOf(leftClass) + var types = listOf(this@allSuperTypes) while (types.isNotEmpty()) { yieldAll(types) - types = types.map { it.interfaces }.flatMap { it.toList() } + types = types + .flatMap { it.interfaces.toList() } + .map { it.id } } } - val superclasses = generateSequence(leftClass) { it.superclass } - val superTypes = interfaces + superclasses - return right in superTypes.map { it.id } + + val superclasses = generateSequence(this) { it.superclass?.id } + + return interfaces + superclasses } infix fun ClassId.isNotSubtypeOf(type: ClassId): Boolean = !(this isSubtypeOf type) +/** + * - Anonymous class that extends a class will have this class as its superclass and no interfaces. + * - Anonymous class that implements an interface, will have the only interface + * and [java.lang.Object] as its superclass. + * + * @return [ClassId] of a type that the given anonymous class inherits + */ +val ClassId.supertypeOfAnonymousClass: ClassId + get() { + if (this is BuiltinClassId) error("Cannot obtain info about supertypes of BuiltinClassId $canonicalName") + require(isAnonymous) { "An anonymous class expected, but got $canonicalName" } + + val clazz = jClass + val superclass = clazz.superclass.id + val interfaces = clazz.interfaces.map { it.id } + + return when (superclass) { + objectClassId -> { + // anonymous class actually inherits from Object, e.g. Object obj = new Object() { ... }; + if (interfaces.isEmpty()) { + objectClassId + } else { + // anonymous class implements some interface + interfaces.singleOrNull() ?: error("Anonymous class can have no more than one interface") + } + } + // anonymous class inherits from some class other than java.lang.Object + else -> superclass + } + } + val ClassId.kClass: KClass<*> get() = jClass.kotlin @@ -118,6 +189,32 @@ val ClassId.isFloatType: Boolean val ClassId.isDoubleType: Boolean get() = this == doubleClassId || this == doubleWrapperClassId +val ClassId.isClassType: Boolean + get() = this == classClassId + +/** + * Returns [Metadata] annotation if this is a Kotlin class, null otherwise + */ +val ClassId.kotlinMetadata: Metadata? + get() = jClass.annotations.filterIsInstance().singleOrNull() + +val ClassId.isFromKotlin: Boolean + get() = kotlinMetadata != null + +/** + * Checks if the class is a Kotlin class with kind File (see [Metadata.kind] for more details) + */ +val ClassId.isKotlinFile: Boolean + get() = kotlinMetadata?.let { + KotlinClassHeader.Kind.getById(it.kind) == KotlinClassHeader.Kind.FILE_FACADE + } ?: false + +/** + * Returns [ClassId.simpleNameWithEnclosingClasses] with '.' replaced with '_' - to use it as a class name. + */ +val ClassId.nameWithEnclosingClassesAsContigousString: String + get() = simpleNameWithEnclosingClasses.replace('.', '_') + val voidClassId = ClassId("void") val booleanClassId = ClassId("boolean") val byteClassId = ClassId("byte") @@ -139,6 +236,11 @@ val longWrapperClassId = java.lang.Long::class.id val floatWrapperClassId = java.lang.Float::class.id val doubleWrapperClassId = java.lang.Double::class.id +val classClassId = java.lang.Class::class.id +val fieldClassId = java.lang.reflect.Field::class.id +val methodClassId = java.lang.reflect.Method::class.id +val constructorClassId = java.lang.reflect.Constructor::class.id + // We consider void wrapper as primitive wrapper // because voidClassId is considered primitive here val primitiveWrappers = setOf( @@ -166,6 +268,8 @@ val primitiveByWrapper = mapOf( val wrapperByPrimitive = primitiveByWrapper.entries.associateBy({ it.value }) { it.key } +fun replaceWithWrapperIfPrimitive(classId: ClassId): ClassId = wrapperByPrimitive[classId] ?: classId + // We consider void primitive here // It is sometimes useful even if void is not technically a primitive type val primitives = setOf( @@ -202,6 +306,28 @@ val atomicIntegerGetAndIncrement = MethodId(atomicIntegerClassId, "getAndIncreme val iterableClassId = java.lang.Iterable::class.id val mapClassId = java.util.Map::class.id +val collectionClassId = java.util.Collection::class.id + +val listClassId = List::class.id +val setClassId = Set::class.id + +val baseStreamClassId = java.util.stream.BaseStream::class.id +val streamClassId = java.util.stream.Stream::class.id +val intStreamClassId = java.util.stream.IntStream::class.id +val longStreamClassId = java.util.stream.LongStream::class.id +val doubleStreamClassId = java.util.stream.DoubleStream::class.id + +val intStreamToArrayMethodId = methodId(intStreamClassId, "toArray", intArrayClassId) +val longStreamToArrayMethodId = methodId(longStreamClassId, "toArray", longArrayClassId) +val doubleStreamToArrayMethodId = methodId(doubleStreamClassId, "toArray", doubleArrayClassId) +val streamToArrayMethodId = methodId(streamClassId, "toArray", objectArrayClassId) + +val dateClassId = java.util.Date::class.id + +fun getArrayClassIdByElementClassId(elementClassId: ClassId): ClassId{ + val elementClass = utContext.classLoader.loadClass(elementClassId.name) + return java.lang.reflect.Array.newInstance(elementClass,0)::class.java.id +} @Suppress("RemoveRedundantQualifierName") val primitiveToId: Map, ClassId> = mapOf( @@ -236,6 +362,21 @@ val idToPrimitive: Map> = mapOf( */ fun isPrimitiveWrapperOrString(type: ClassId): Boolean = (type in primitiveWrappers) || (type == stringClassId) +/** + * Returns a wrapper of a given type if it is primitive or a type itself otherwise. + */ +fun wrapIfPrimitive(type: ClassId): ClassId = when (type) { + booleanClassId -> booleanWrapperClassId + byteClassId -> byteWrapperClassId + charClassId -> charWrapperClassId + shortClassId -> shortWrapperClassId + intClassId -> intWrapperClassId + longClassId -> longWrapperClassId + floatClassId -> floatWrapperClassId + doubleClassId -> doubleWrapperClassId + else -> type +} + /** * Note: currently uses class$innerClass form to load classes with classloader. */ @@ -247,6 +388,13 @@ val Class<*>.id: ClassId else -> ClassId(name) } +/** + * We should specially handle the case of a generic type that is a [Type] and not a [Class]. + * Returns a [ClassId] for the corresponding raw type. + */ +val ParameterizedType.id: ClassId + get() = ClassId(this.rawType.typeName) + val KClass<*>.id: ClassId get() = java.id @@ -271,9 +419,32 @@ val ClassId.isMap: Boolean val ClassId.isIterableOrMap: Boolean get() = isIterable || isMap -fun ClassId.findFieldOrNull(fieldName: String): Field? = jClass.findFieldOrNull(fieldName) +val ClassId.isCollection: Boolean + get() = isSubtypeOf(collectionClassId) + +val ClassId.isCollectionOrMap: Boolean + get() = isCollection || isMap + +val ClassId.isEnum: Boolean + get() = jClass.isEnum -fun ClassId.hasField(fieldName: String): Boolean = findFieldOrNull(fieldName) != null +val ClassId.isData: Boolean + get() = kClass.isData + +val ClassId.enumConstants: List>? + get() = jClass.enumConstants?.filterIsInstance>() + +fun ClassId.findFieldByIdOrNull(fieldId: FieldId): Field? { + if (isNotSubtypeOf(fieldId.declaringClass)) { + return null + } + + return fieldId.safeJField +} + +fun ClassId.hasField(fieldId: FieldId): Boolean { + return findFieldByIdOrNull(fieldId) != null +} fun ClassId.defaultValueModel(): UtModel = when (this) { intClassId -> UtPrimitiveModel(0) @@ -284,15 +455,33 @@ fun ClassId.defaultValueModel(): UtModel = when (this) { doubleClassId -> UtPrimitiveModel(0.0) booleanClassId -> UtPrimitiveModel(false) charClassId -> UtPrimitiveModel('\u0000') + voidClassId -> UtVoidModel else -> UtNullModel(this) } +val ClassId.allDeclaredFieldIds: Sequence + get() = + generateSequence(this.jClass) { it.superclass } + .flatMap { it.declaredFields.asSequence() } + .map { it.fieldId } + +val SootField.fieldId: FieldId + get() = FieldId(declaringClass.id, name) + +/** + * For some lambdas class names in byte code and in Soot don't match, so we may fail + * to convert some soot fields to Java fields, in such case `null` is returned. + */ +val SootField.jFieldOrNull: Field? + get() = runCatching { fieldId.jField }.getOrNull() + // FieldId utils +val FieldId.safeJField: Field? + get() = declaringClass.jClass.declaredFields.firstOrNull { it.name == name } // TODO: maybe cache it somehow in the future -val FieldId.field: Field - get() = declaringClass.jClass.declaredFields.firstOrNull { it.name == name } - ?: error("Field $name is not found in class ${declaringClass.jClass.name}") +val FieldId.jField: Field + get() = safeJField ?: error("Field $name is not declared in class ${declaringClass.jClass.name}") // https://docstore.mik.ua/orelly/java-ent/jnut/ch03_13.htm val FieldId.isInnerClassEnclosingClassReference: Boolean @@ -323,6 +512,12 @@ val MethodId.method: Method ?: error("Can't find method $signature in ${declaringClass.name}") } +/** + * See [KCallable.extensionReceiverParameter] for more details + */ +val MethodId.extensionReceiverParameterIndex: Int? + get() = this.method.kotlinFunction?.extensionReceiverParameter?.index + // TODO: maybe cache it somehow in the future val ConstructorId.constructor: Constructor<*> get() { @@ -340,6 +535,13 @@ val KCallable<*>.executableId: ExecutableId else -> error("Unknown KCallable type: ${this::class}") } +val Executable.executableId: ExecutableId + get() = when (this) { + is Method -> executableId + is Constructor<*> -> executableId + else -> error("Unknown Executable type: ${this::class}") + } + val Method.executableId: MethodId get() { val classId = declaringClass.id @@ -355,22 +557,53 @@ val Constructor<*>.executableId: ConstructorId return constructorId(classId, *arguments) } -@ExperimentalContracts -fun ExecutableId.isMethod(): Boolean { - contract { - returns(true) implies (this@isMethod is MethodId) - returns(false) implies (this@isMethod is ConstructorId) +val ExecutableId.humanReadableName: String + get() { + val executableName = this.name + val parameters = this.parameters.joinToString(separator = ", ") { it.name } + return "$executableName($parameters)" } - return this is MethodId -} -@ExperimentalContracts -fun ExecutableId.isConstructor(): Boolean { - contract { - returns(true) implies (this@isConstructor is ConstructorId) - returns(false) implies (this@isConstructor is MethodId) +val ExecutableId.simpleNameWithClass: String + get() = "${classId.simpleName}.${name}" + +val Constructor<*>.displayName: String + get() = executableId.humanReadableName + +val Method.displayName: String + get() = executableId.humanReadableName + +val KCallable<*>.declaringClazz: Class<*> + get() = when (this) { + is KFunction<*> -> javaMethod?.declaringClass?.kotlin + is CallableReference -> owner as? KClass<*> + else -> instanceParameter?.type?.classifier as? KClass<*> + }?.java ?: tryConstructor(this) ?: error("Can't get parent class for $this") + +private fun tryConstructor(function: KCallable): Class<*>? = + (function as? KFunction<*>)?.javaConstructor?.declaringClass + +val ExecutableId.isMethod: Boolean + get() = this is MethodId + +val ExecutableId.isConstructor: Boolean + get() = this is ConstructorId + +fun arrayTypeOf(elementType: ClassId, isNullable: Boolean = false): ClassId { + val arrayIdName = "[${elementType.arrayLikeName}" + return when (elementType) { + is BuiltinClassId -> BuiltinClassId( + canonicalName = "${elementType.canonicalName}[]", + simpleName = "${elementType.simpleName}[]", + elementClassId = elementType, + isNullable = isNullable + ) + else -> ClassId( + name = arrayIdName, + elementClassId = elementType, + isNullable = isNullable + ) } - return this is ConstructorId } /** @@ -391,6 +624,10 @@ fun builtinMethodId(classId: BuiltinClassId, name: String, returnType: ClassId, return BuiltinMethodId(classId, name, returnType, arguments.toList()) } +fun builtinConstructorId(classId: BuiltinClassId, vararg arguments: ClassId): BuiltinConstructorId { + return BuiltinConstructorId(classId, arguments.toList()) +} + fun builtinStaticMethodId(classId: ClassId, name: String, returnType: ClassId, vararg arguments: ClassId): BuiltinMethodId { return BuiltinMethodId(classId, name, returnType, arguments.toList(), isStatic = true) -} +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IndentUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IndentUtil.kt new file mode 100644 index 0000000000..5e6bcc57a0 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/IndentUtil.kt @@ -0,0 +1,5 @@ +package org.utbot.framework.plugin.api.util + +object IndentUtil { + const val TAB = " " +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/LockFile.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/LockFile.kt new file mode 100644 index 0000000000..3f5fc0a9e4 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/LockFile.kt @@ -0,0 +1,52 @@ +package org.utbot.framework.plugin.api.util + +import java.io.OutputStream +import java.nio.file.Paths +import java.nio.file.StandardOpenOption +import java.text.DateFormat +import kotlin.io.path.deleteIfExists +import kotlin.io.path.outputStream +import mu.KotlinLogging +import org.utbot.framework.utbotHomePath + +private val lockFilePath = "$utbotHomePath/utbot.lock" +private var currentLock : OutputStream? = null +private val logger = KotlinLogging.logger {} + +object LockFile { + @Synchronized + fun isLocked() = currentLock != null + + @Synchronized + fun lock(): Boolean { + if (currentLock != null) return false + return try { + Paths.get(utbotHomePath).toFile().mkdirs() + currentLock = Paths.get(lockFilePath).outputStream(StandardOpenOption.CREATE, StandardOpenOption.DELETE_ON_CLOSE).also { + it.write(DateFormat.getDateTimeInstance().format(System.currentTimeMillis()).toByteArray()) + } + logger.debug("Locked") + true + } catch (e: Exception) { + logger.error("Failed to lock") + false + } + } + + @Synchronized + fun unlock(): Boolean { + try { + val tmp = currentLock + if (tmp != null) { + tmp.close() + Paths.get(lockFilePath).deleteIfExists() + logger.debug("Unlocked") + currentLock = null + return true + } + } catch (ignored: Exception) { + logger.error("Failed to unlock") + } + return false + } +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/ModifierUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/ModifierUtil.kt new file mode 100644 index 0000000000..bb40f5b7a4 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/ModifierUtil.kt @@ -0,0 +1,102 @@ +package org.utbot.framework.plugin.api.util + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import java.lang.reflect.Modifier + +class ModifierFactory private constructor( + configure: ModifierFactory.() -> Unit = {} +) { + var public: Boolean = true + var protected: Boolean = false + var private: Boolean = false + var final: Boolean = false + var static: Boolean = false + var abstract: Boolean = false + + init { + this.configure() + } + + private val modifiers: Int = + (Modifier.PUBLIC.takeIf { public } ?: 0) or + (Modifier.PRIVATE.takeIf { private } ?: 0) or + (Modifier.PROTECTED.takeIf { protected } ?: 0) or + (Modifier.FINAL.takeIf { final } ?: 0) or + (Modifier.STATIC.takeIf { static } ?: 0) or + (Modifier.ABSTRACT.takeIf { abstract } ?: 0) + + companion object { + operator fun invoke(configure: ModifierFactory.() -> Unit): Int { + return ModifierFactory(configure).modifiers + } + } +} + +// ClassIds + +val ClassId.isAbstract: Boolean + get() = Modifier.isAbstract(modifiers) + +val ClassId.isPrivate: Boolean + get() = Modifier.isPrivate(modifiers) + +val ClassId.isPackagePrivate: Boolean + get() = !(isPublic || isProtected || isPrivate) + +val ClassId.isStatic: Boolean + get() = Modifier.isStatic(modifiers) + +val ClassId.isFinal: Boolean + get() = Modifier.isFinal(modifiers) + +val ClassId.isProtected: Boolean + get() = Modifier.isProtected(modifiers) + +val ClassId.isPublic: Boolean + get() = Modifier.isPublic(modifiers) + +// ExecutableIds + +val ExecutableId.isPublic: Boolean + get() = Modifier.isPublic(modifiers) + +val ExecutableId.isProtected: Boolean + get() = Modifier.isProtected(modifiers) + +val ExecutableId.isPrivate: Boolean + get() = Modifier.isPrivate(modifiers) + +val ExecutableId.isStatic: Boolean + get() = Modifier.isStatic(modifiers) + +val ExecutableId.isPackagePrivate: Boolean + get() = !(isPublic || isProtected || isPrivate) + +val ExecutableId.isAbstract: Boolean + get() = Modifier.isAbstract(modifiers) + +val ExecutableId.isSynthetic: Boolean + get() = (this is MethodId) && method.isSynthetic + +// FieldIds + +val FieldId.isStatic: Boolean + get() = Modifier.isStatic(modifiers) + +val FieldId.isFinal: Boolean + get() = Modifier.isFinal(modifiers) + +val FieldId.isPackagePrivate: Boolean + get() = !(isPublic || isProtected || isPrivate) + +val FieldId.isProtected: Boolean + get() = Modifier.isProtected(modifiers) + +val FieldId.isPrivate + get() = Modifier.isPrivate(modifiers) + +val FieldId.isPublic: Boolean + get() = Modifier.isPublic(modifiers) \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/ReflectionUtils.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/ReflectionUtils.kt new file mode 100644 index 0000000000..5cfa8ab8f3 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/ReflectionUtils.kt @@ -0,0 +1,63 @@ +package org.utbot.framework.plugin.api.util + +import org.utbot.common.Reflection +import org.utbot.framework.plugin.api.FieldId +import soot.RefType + +/** + * Several fields are inaccessible in runtime even via reflection + */ +val FieldId.isInaccessibleViaReflection: Boolean + get() { + val declaringClassName = declaringClass.name + return declaringClassName in inaccessibleViaReflectionClasses || + (name to declaringClassName) in inaccessibleViaReflectionFields + } + +val RefType.isInaccessibleViaReflection: Boolean + get() { + return className in inaccessibleViaReflectionClasses + } + +private val inaccessibleViaReflectionClasses = setOf( + "jdk.internal.reflect.ReflectionFactory", + "jdk.internal.reflect.Reflection", + "jdk.internal.loader.ClassLoaderValue", + "sun.reflect.Reflection", +) + +private val inaccessibleViaReflectionFields = setOf( + "security" to "java.lang.System", +) + +@Suppress("DEPRECATION") +val Class<*>.anyInstance: Any + get() { +// val defaultCtor = declaredConstructors.singleOrNull { it.parameterCount == 0} +// if (defaultCtor != null) { +// try { +// defaultCtor.isAccessible = true +// return defaultCtor.newInstance() +// } catch (e : Throwable) { +// logger.warn(e) { "Can't create object with default ctor. Fallback to Unsafe." } +// } +// } + return Reflection.unsafe.allocateInstance(this) + +// val constructors = runCatching { +// arrayOf(getDeclaredConstructor()) +// }.getOrElse { declaredConstructors } +// +// return constructors.asSequence().mapNotNull { constructor -> +// runCatching { +// val parameters = constructor.parameterTypes.map { defaultParameterValue(it) } +// val isAccessible = constructor.isAccessible +// try { +// constructor.isAccessible = true +// constructor.newInstance(*parameters.toTypedArray()) +// } finally { +// constructor.isAccessible = isAccessible +// } +// }.getOrNull() +// }.firstOrNull() ?: error("Failed to create instance of $this") + } \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SignatureUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SignatureUtil.kt index 6f0649a41c..9a7c1b4cc9 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SignatureUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SignatureUtil.kt @@ -1,5 +1,6 @@ package org.utbot.framework.plugin.api.util +import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ExecutableId import java.lang.reflect.Constructor import java.lang.reflect.Executable @@ -39,6 +40,23 @@ fun Constructor<*>.bytecodeSignature() = buildString { fun Class<*>.bytecodeSignature(): String = id.jvmName +/** + * Method [Class.getName] works differently for arrays than for other types. + * - When an element of an array is a reference type (e.g. `java.lang.Object`), + * the array of `java.lang.Object` will have name `[Ljava.lang.Object;`. + * - When an element of an array is a primitive type (e.g. `int`), + * the array of `int` will have name `[I`. + * + * So, this property returns the name of the given class in the format of an array element type name. + * Basically, it performs conversions for primitives and reference types (e.g. `int` -> `I`, `java.lang.Object` -> `Ljava.lang.Object;`. + */ +val ClassId.arrayLikeName: String + get() = when { + isPrimitive -> primitiveTypeJvmNameOrNull()!! + isRefType -> "L$name;" + else -> name + } + fun String.toReferenceTypeBytecodeSignature(): String { val packageName = this .takeIf { "." in this } @@ -58,7 +76,7 @@ fun Class<*>.singleConstructor(signature: String): Constructor<*> = fun Class<*>.singleMethodOrNull(signature: String): Method? = generateSequence(this) { it.superclass }.mapNotNull { clazz -> - clazz.declaredMethods.firstOrNull { it.signature == signature } + (clazz.methods + clazz.declaredMethods).firstOrNull { it.signature == signature } }.firstOrNull() /** @@ -84,10 +102,4 @@ fun Class<*>.singleExecutableIdOrNull(signature: String) = if (isConstructorSign singleMethodOrNull(signature)?.executableId } -private fun isConstructorSignature(signature: String): Boolean = signature.startsWith("") - -private val Constructor<*>.isPrivate: Boolean - get() = Modifier.isPrivate(modifiers) - -private val Constructor<*>.isProtected: Boolean - get() = Modifier.isProtected(modifiers) +private fun isConstructorSignature(signature: String): Boolean = signature.startsWith("") \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt new file mode 100644 index 0000000000..2e468b3322 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/SpringModelUtils.kt @@ -0,0 +1,982 @@ +package org.utbot.framework.plugin.api.util + +import mu.KotlinLogging +import org.utbot.common.tryLoadClass +import org.utbot.common.withToStringThreadLocalReentrancyGuard +import org.utbot.framework.plugin.api.isNotNull +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtSpringContextModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper +import org.utbot.framework.plugin.api.mapper.mapModels +import java.util.Optional + +private val logger = KotlinLogging.logger {} + +object SpringModelUtils { + val autowiredClassId = ClassId("org.springframework.beans.factory.annotation.Autowired") + val injectClassIds = getClassIdFromEachAvailablePackage( + packages = listOf("javax", "jakarta"), + classNameFromPackage = "inject.Inject" + ) + val componentClassId = ClassId("org.springframework.stereotype.Component") + + val applicationContextClassId = ClassId("org.springframework.context.ApplicationContext") + val repositoryClassId = ClassId("org.springframework.data.repository.Repository") + + val springBootTestClassId = ClassId("org.springframework.boot.test.context.SpringBootTest") + + val dirtiesContextClassId = ClassId("org.springframework.test.annotation.DirtiesContext") + val dirtiesContextClassModeClassId = ClassId("org.springframework.test.annotation.DirtiesContext\$ClassMode") + val transactionalClassId = ClassId("org.springframework.transaction.annotation.Transactional") + val autoConfigureTestDbClassId = ClassId("org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase") + val autoConfigureMockMvcClassId = ClassId("org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc") + val withMockUserClassId = ClassId("org.springframework.security.test.context.support.WithMockUser") + + val runWithClassId = ClassId("org.junit.runner.RunWith") + val springRunnerClassId = ClassId("org.springframework.test.context.junit4.SpringRunner") + + val extendWithClassId = ClassId("org.junit.jupiter.api.extension.ExtendWith") + val springExtensionClassId = ClassId("org.springframework.test.context.junit.jupiter.SpringExtension") + + val bootstrapWithClassId = ClassId("org.springframework.test.context.BootstrapWith") + val springBootTestContextBootstrapperClassId = + ClassId("org.springframework.boot.test.context.SpringBootTestContextBootstrapper") + + val activeProfilesClassId = ClassId("org.springframework.test.context.ActiveProfiles") + val contextConfigurationClassId = ClassId("org.springframework.test.context.ContextConfiguration") + + private fun getClassIdFromEachAvailablePackage( + packages: List, + classNameFromPackage: String + ): List = packages.map { ClassId("$it.$classNameFromPackage") } + .filter { utContext.classLoader.tryLoadClass(it.name) != null } + + // most likely only one persistent library is on the classpath, but we need to be able to work with either of them + private val persistentLibraries = listOf("javax.persistence", "jakarta.persistence") + private fun persistentClassIds(simpleName: String) = getClassIdFromEachAvailablePackage(persistentLibraries, simpleName) + + // the library in which Cookie is stored depends on the version of Spring + private val cookiesLibraries = listOf("javax.servlet.http", "jakarta.servlet.http") + private val cookieClassIds = getClassIdFromEachAvailablePackage(cookiesLibraries, "Cookie") + + val entityClassIds get() = persistentClassIds("Entity") + val generatedValueClassIds get() = persistentClassIds("GeneratedValue") + val idClassIds get() = persistentClassIds("Id") + val persistenceContextClassIds get() = persistentClassIds("PersistenceContext") + val entityManagerClassIds get() = persistentClassIds("EntityManager") + + val persistMethodIdOrNull: MethodId? + get() { + return MethodId( + classId = entityManagerClassIds.firstOrNull() ?: return null, + name = "persist", + returnType = voidClassId, + parameters = listOf(objectClassId), + bypassesSandbox = true // TODO may be we can use some alternative sandbox that has more permissions + ) + } + + val flushMethodIdOrNull: MethodId? + get() { + return MethodId( + classId = entityManagerClassIds.firstOrNull() ?: return null, + name = "flush", + returnType = voidClassId, + parameters = listOf(), + bypassesSandbox = true // TODO may be we can use some alternative sandbox that has more permissions + ) + } + + val detachMethodIdOrNull: MethodId? + get() { + return MethodId( + classId = entityManagerClassIds.firstOrNull() ?: return null, + name = "detach", + returnType = voidClassId, + parameters = listOf(objectClassId), + bypassesSandbox = true // TODO may be we can use some alternative sandbox that has more permissions + ) + } + + private val validationLibraries = listOf("javax.validation.constraints", "jakarta.validation.constraints") + private fun validationClassIds(simpleName: String) = getClassIdFromEachAvailablePackage(validationLibraries, simpleName) + .filter { utContext.classLoader.tryLoadClass(it.name) != null } + + val notEmptyClassIds get() = validationClassIds("NotEmpty") + val notBlankClassIds get() = validationClassIds("NotBlank") + val emailClassIds get() = validationClassIds("Email") + + + private val getBeanMethodId = MethodId( + classId = applicationContextClassId, + name = "getBean", + returnType = Any::class.id, + parameters = listOf(String::class.id), + bypassesSandbox = true // TODO may be we can use some alternative sandbox that has more permissions + ) + + fun createBeanModel( + beanName: String, + id: Int, + classId: ClassId, + modificationChainProvider: UtAssembleModel.() -> List = { mutableListOf() }, + ) = UtAssembleModel( + id = id, + classId = classId, + modelName = "@Autowired $beanName", + instantiationCall = UtExecutableCallModel( + instance = UtSpringContextModel, + executable = getBeanMethodId, + params = listOf(UtPrimitiveModel(beanName)) + ), + modificationsChainProvider = modificationChainProvider + ) + + fun UtModel.isAutowiredFromContext(): Boolean = + this is UtAssembleModel && this.instantiationCall.instance is UtSpringContextModel + + fun UtModel.getBeanNameOrNull(): String? = if (isAutowiredFromContext()) { + this as UtAssembleModel + val beanNameParam = this.instantiationCall.params.single() + val paramValue = (beanNameParam as? UtPrimitiveModel)?.value + paramValue.toString() + } else { + null + } + + ///region spring-web + private val requestMappingClassId = ClassId("org.springframework.web.bind.annotation.RequestMapping") + private val pathVariableClassId = ClassId("org.springframework.web.bind.annotation.PathVariable") + private val requestHeaderClassId = ClassId("org.springframework.web.bind.annotation.RequestHeader") + private val cookieValueClassId = ClassId("org.springframework.web.bind.annotation.CookieValue") + private val requestAttributesClassId = ClassId("org.springframework.web.bind.annotation.RequestAttribute") + private val sessionAttributesClassId = ClassId("org.springframework.web.bind.annotation.SessionAttribute") + private val modelAttributesClassId = ClassId("org.springframework.web.bind.annotation.ModelAttribute") + private val requestBodyClassId = ClassId("org.springframework.web.bind.annotation.RequestBody") + private val requestParamClassId = ClassId("org.springframework.web.bind.annotation.RequestParam") + private val uriComponentsBuilderClassId = ClassId("org.springframework.web.util.UriComponentsBuilder") + private val mediaTypeClassId = ClassId("org.springframework.http.MediaType") + private val mockHttpServletResponseClassId = ClassId("org.springframework.mock.web.MockHttpServletResponse") + + private val mockMvcRequestBuildersClassId = ClassId("org.springframework.test.web.servlet.request.MockMvcRequestBuilders") + private val requestBuilderClassId = ClassId("org.springframework.test.web.servlet.RequestBuilder") + val resultActionsClassId = ClassId("org.springframework.test.web.servlet.ResultActions") + val mockMvcClassId = ClassId("org.springframework.test.web.servlet.MockMvc") + private val mvcResultClassId = ClassId("org.springframework.test.web.servlet.MvcResult") + private val resultHandlerClassId = ClassId("org.springframework.test.web.servlet.ResultHandler") + val mockMvcResultHandlersClassId = ClassId("org.springframework.test.web.servlet.result.MockMvcResultHandlers") + private val resultMatcherClassId = ClassId("org.springframework.test.web.servlet.ResultMatcher") + val mockMvcResultMatchersClassId = ClassId("org.springframework.test.web.servlet.result.MockMvcResultMatchers") + private val statusResultMatchersClassId = ClassId("org.springframework.test.web.servlet.result.StatusResultMatchers") + private val contentResultMatchersClassId = ClassId("org.springframework.test.web.servlet.result.ContentResultMatchers") + private val viewResultMatchersClassId = ClassId("org.springframework.test.web.servlet.result.ViewResultMatchers") + private val mockHttpServletRequestBuilderClassId = ClassId("org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder") + private val modelAndViewClassId = ClassId("org.springframework.web.servlet.ModelAndView") + private val httpHeaderClassId = ClassId("org.springframework.http.HttpHeaders") + + private val objectMapperClassId = ClassId("com.fasterxml.jackson.databind.ObjectMapper") + + // as of Spring 6.0 `NestedServletException` is deprecated in favor of standard `ServletException` nesting + val nestedServletExceptionClassIds = listOf( + ClassId("org.springframework.web.util.NestedServletException"), + ClassId("jakarta.servlet.ServletException") + ) + + private val requestAttributesMethodId = MethodId( + classId = mockHttpServletRequestBuilderClassId, + name = "requestAttr", + returnType = mockHttpServletRequestBuilderClassId, + parameters = listOf(stringClassId, objectClassId) + ) + + private val sessionAttributesMethodId = MethodId( + classId = mockHttpServletRequestBuilderClassId, + name = "sessionAttr", + returnType = mockHttpServletRequestBuilderClassId, + parameters = listOf(stringClassId, objectClassId) + ) + + private val modelAttributesMethodId = MethodId( + classId = mockHttpServletRequestBuilderClassId, + name = "flashAttr", + returnType = mockHttpServletRequestBuilderClassId, + parameters = listOf(stringClassId, objectClassId) + ) + + private val mockHttpServletHeadersMethodId = MethodId( + classId = mockHttpServletRequestBuilderClassId, + name = "headers", + returnType = mockHttpServletRequestBuilderClassId, + parameters = listOf(httpHeaderClassId) + ) + + private fun mockHttpServletCookieMethodId(cookieClassId: ClassId) = MethodId( + classId = mockHttpServletRequestBuilderClassId, + name = "cookie", + returnType = mockHttpServletRequestBuilderClassId, + parameters = listOf(getArrayClassIdByElementClassId(cookieClassId)) + ) + + private val mockHttpServletContentTypeMethodId = MethodId( + classId = mockHttpServletRequestBuilderClassId, + name = "contentType", + returnType = mockHttpServletRequestBuilderClassId, + parameters = listOf(mediaTypeClassId) + ) + + private val mockHttpServletContentMethodId = MethodId( + classId = mockHttpServletRequestBuilderClassId, + name = "content", + returnType = mockHttpServletRequestBuilderClassId, + parameters = listOf(stringClassId) + ) + + val mockMvcPerformMethodId = MethodId( + classId = mockMvcClassId, + name = "perform", + parameters = listOf(requestBuilderClassId), + returnType = resultActionsClassId + ) + + val resultActionsAndReturnMethodId = MethodId( + classId = resultActionsClassId, + name = "andReturn", + parameters = listOf(), + returnType = mvcResultClassId + ) + + val mvcResultGetResponseMethodId = MethodId( + classId = mvcResultClassId, + name = "getResponse", + parameters = listOf(), + returnType = mockHttpServletResponseClassId + ) + + val responseGetStatusMethodId = MethodId( + classId = mockHttpServletResponseClassId, + name = "getStatus", + parameters = listOf(), + returnType = intClassId + ) + + val responseGetErrorMessageMethodId = MethodId( + classId = mockHttpServletResponseClassId, + name = "getErrorMessage", + parameters = listOf(), + returnType = stringClassId + ) + + val responseGetContentAsStringMethodId = MethodId( + classId = mockHttpServletResponseClassId, + name = "getContentAsString", + parameters = listOf(), + returnType = stringClassId + ) + + val mvcResultGetModelAndViewMethodId = MethodId( + classId = mvcResultClassId, + name = "getModelAndView", + parameters = listOf(), + returnType = modelAndViewClassId + ) + + val modelAndViewGetModelMethodId = MethodId( + classId = modelAndViewClassId, + name = "getModel", + parameters = listOf(), + returnType = mapClassId + ) + + val modelAndViewGetViewNameMethodId = MethodId( + classId = modelAndViewClassId, + name = "getViewName", + parameters = listOf(), + returnType = stringClassId + ) + + val resultActionsAndDoMethodId = MethodId( + classId = resultActionsClassId, + name = "andDo", + parameters = listOf(resultHandlerClassId), + returnType = resultActionsClassId + ) + + val resultHandlersPrintMethodId = MethodId( + classId = mockMvcResultHandlersClassId, + name = "print", + parameters = listOf(), + returnType = resultHandlerClassId + ) + + val resultActionsAndExpectMethodId = MethodId( + classId = resultActionsClassId, + name = "andExpect", + parameters = listOf(resultMatcherClassId), + returnType = resultActionsClassId + ) + + val resultMatchersStatusMethodId = MethodId( + classId = mockMvcResultMatchersClassId, + name = "status", + parameters = listOf(), + returnType = statusResultMatchersClassId + ) + + val statusMatchersIsMethodId = MethodId( + classId = statusResultMatchersClassId, + name = "is", + parameters = listOf(intClassId), + returnType = resultMatcherClassId + ) + + val resultMatchersContentMethodId = MethodId( + classId = mockMvcResultMatchersClassId, + name = "content", + parameters = listOf(), + returnType = contentResultMatchersClassId + ) + + val contentMatchersStringMethodId = MethodId( + classId = contentResultMatchersClassId, + name = "string", + parameters = listOf(stringClassId), + returnType = resultMatcherClassId + ) + + val resultMatchersViewMethodId = MethodId( + classId = mockMvcResultMatchersClassId, + name = "view", + parameters = listOf(), + returnType = viewResultMatchersClassId + ) + + val viewMatchersNameMethodId = MethodId( + classId = viewResultMatchersClassId, + name = "name", + parameters = listOf(stringClassId), + returnType = resultMatcherClassId + ) + + private val supportedControllerParameterAnnotations = setOf( + pathVariableClassId, + requestParamClassId, + requestHeaderClassId, + cookieValueClassId, + requestAttributesClassId, + sessionAttributesClassId, + modelAttributesClassId, + requestBodyClassId, + ) + + /** + * If a controller method has a parameter of one of these types, then we don't fully support conversion + * of direct call of that controller method call to a request that can be done via `mockMvc` even if + * said parameter is annotated with one of [supportedControllerParameterAnnotations]. + */ + private val unsupportedControllerParameterTypes = setOf( + dateClassId, // see #2505 + mapClassId, // e.g. `@RequestParam Map` is not yet properly handled + ) + + /** + * Returns `true` if for every parameter of [methodId] we have a mechanism of registering said + * parameter in `requestBuilder` when calling controller method via `mockMvc.perform(requestBuilder)`. + */ + fun allControllerParametersAreSupported(methodId: MethodId): Boolean = + methodId.parameters.none { it in unsupportedControllerParameterTypes } && + methodId.method.parameters.all { param -> + param.annotations.any { annotation -> + annotation.annotationClass.id in supportedControllerParameterAnnotations + } + } + + fun createMockMvcModel(controller: UtModel?, idGenerator: () -> Int) = + createBeanModel("mockMvc", idGenerator(), mockMvcClassId, modificationChainProvider = { + // we need to keep controller modifications if there are any, so we add them to mockMvc + (controller as? UtAssembleModel)?.let { assembledController -> + val controllerModificationRemover = UtModelDeepMapper { model -> + if (model == assembledController) assembledController.copy(modificationsChain = emptyList()) + else model + } + // modificationsChain may mention controller, causing controller modifications to evaluate twice: + // once for mockMvc and once for controller itself, to avoid that we remove modifications from controller + assembledController.modificationsChain.map { it.mapModels(controllerModificationRemover) } + } ?: mutableListOf() + }) + + fun createRequestBuilderModelOrNull(methodId: MethodId, arguments: List, idGenerator: () -> Int): UtModel? { + check(methodId.parameters.size == arguments.size) + + if (methodId.isStatic) return null + + val requestMappingAnnotation = getRequestMappingAnnotationOrNull(methodId) ?: return null + val requestMethod = getRequestMethodOrNull(requestMappingAnnotation) ?: return null + + @Suppress("UNCHECKED_CAST") + val classRequestMappingAnnotation: Annotation? = + methodId.classId.jClass.getAnnotation(requestMappingClassId.jClass as Class) + val classRequestPath = classRequestMappingAnnotation?.let { getRequestPathOrNull(it) }.orEmpty() + + val requestPath = classRequestPath + (getRequestPathOrNull(requestMappingAnnotation) ?: return null) + + val pathVariablesModel = createPathVariablesModel(methodId, arguments, idGenerator) + + val requestParamsModel = createRequestParamsModel(methodId, arguments, idGenerator) + + val urlTemplateModel = createUrlTemplateModel(requestPath, pathVariablesModel, requestParamsModel, idGenerator) + + var requestBuilderModel = UtAssembleModel( + id = idGenerator(), + classId = mockHttpServletRequestBuilderClassId, + modelName = "requestBuilder", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = requestMethod.requestBuilderMethodId, + params = listOf( + urlTemplateModel, + UtArrayModel( + id = idGenerator(), + classId = objectArrayClassId, + length = 0, + constModel = UtNullModel(objectClassId), + stores = mutableMapOf() + ) + ) + ) + ) + + val headersContentModel = createHeadersContentModel(methodId, arguments, idGenerator) + requestBuilderModel = addHeadersToRequestBuilderModel(headersContentModel, requestBuilderModel, idGenerator) + + cookieClassIds.singleOrNull()?.let { cookieClassId -> + val cookieValuesModel = createCookieValuesModel(cookieClassId, methodId, arguments, idGenerator) + requestBuilderModel = + addCookiesToRequestBuilderModel(cookieClassId, cookieValuesModel, requestBuilderModel, idGenerator) + } ?: logger.warn { "Cookie library not found" } + + val requestAttributes = collectArgumentsWithAnnotationModels(methodId, requestAttributesClassId, arguments) + requestBuilderModel = + addRequestAttributesToRequestModelBuilder(requestAttributes, requestBuilderModel, idGenerator) + + val sessionAttributes = collectArgumentsWithAnnotationModels(methodId, sessionAttributesClassId, arguments) + requestBuilderModel = + addSessionAttributesToRequestModelBuilder(sessionAttributes, requestBuilderModel, idGenerator) + + val modelAttributes = collectArgumentsWithAnnotationModels(methodId, modelAttributesClassId, arguments) + requestBuilderModel = + addModelAttributesToRequestModelBuilder(modelAttributes, requestBuilderModel, idGenerator) + + return addContentToRequestBuilderModel(methodId, arguments, requestBuilderModel, idGenerator) + } + + private fun addRequestAttributesToRequestModelBuilder( + requestAttributes: Map, + requestBuilderModel: UtAssembleModel, + idGenerator: () -> Int + ): UtAssembleModel = addAttributesToRequestBuilderModel( + requestAttributes, + requestAttributesMethodId, + requestBuilderModel, + idGenerator + ) + + + private fun addSessionAttributesToRequestModelBuilder( + sessionAttributes: Map, + requestBuilderModel: UtAssembleModel, + idGenerator: () -> Int + ): UtAssembleModel = addAttributesToRequestBuilderModel( + sessionAttributes, + sessionAttributesMethodId, + requestBuilderModel, + idGenerator + ) + + private fun addModelAttributesToRequestModelBuilder( + modelAttributes: Map, + requestBuilderModel: UtAssembleModel, + idGenerator: () -> Int + ): UtAssembleModel = addAttributesToRequestBuilderModel( + modelAttributes, + modelAttributesMethodId, + requestBuilderModel, + idGenerator + ) + + + private fun addAttributesToRequestBuilderModel( + attributes: Map, + addAttributesMethodId: MethodId, + requestBuilderModel: UtAssembleModel, + idGenerator: () -> Int + ): UtAssembleModel{ + @Suppress("NAME_SHADOWING") + var requestBuilderModel = requestBuilderModel + + attributes.forEach { (name, model) -> + requestBuilderModel = UtAssembleModel( + id = idGenerator(), + classId = mockHttpServletRequestBuilderClassId, + modelName = "requestBuilder", + instantiationCall = UtExecutableCallModel( + instance = requestBuilderModel, + executable = addAttributesMethodId, + params = listOf(UtPrimitiveModel(name), model) + ) + ) + } + + return requestBuilderModel + } + + private fun addCookiesToRequestBuilderModel( + cookieClassId: ClassId, + cookieValuesModel: UtArrayModel, + requestBuilderModel: UtAssembleModel, + idGenerator: () -> Int + ): UtAssembleModel { + @Suppress("NAME_SHADOWING") + var requestBuilderModel = requestBuilderModel + + if(cookieValuesModel.length > 0) { + requestBuilderModel = UtAssembleModel( + id = idGenerator(), + classId = mockHttpServletRequestBuilderClassId, + modelName = "requestBuilder", + instantiationCall = UtExecutableCallModel( + instance = requestBuilderModel, + executable = mockHttpServletCookieMethodId(cookieClassId), + params = listOf(cookieValuesModel) + ) + ) + } + return requestBuilderModel + } + + private fun addHeadersToRequestBuilderModel( + headersContentModel: UtAssembleModel, + requestBuilderModel: UtAssembleModel, + idGenerator: () -> Int + ): UtAssembleModel { + @Suppress("NAME_SHADOWING") + var requestBuilderModel = requestBuilderModel + + if (headersContentModel.modificationsChain.isEmpty()) { + return requestBuilderModel + } + + val headers = UtAssembleModel( + id = idGenerator(), + classId = httpHeaderClassId, + modelName = "headers", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = constructorId(httpHeaderClassId), + params = emptyList(), + ), + modificationsChainProvider = { + listOf( + UtExecutableCallModel( + instance = this, + executable = methodId( + classId = httpHeaderClassId, + name = "setAll", + returnType = voidClassId, + arguments = arrayOf(Map::class.java.id), + ), + params = listOf(headersContentModel) + ) + ) + } + ) + + requestBuilderModel = UtAssembleModel( + id = idGenerator(), + classId = mockHttpServletRequestBuilderClassId, + modelName = "requestBuilder", + instantiationCall = UtExecutableCallModel( + instance = requestBuilderModel, + executable = mockHttpServletHeadersMethodId, + params = listOf(headers) + ) + ) + + return requestBuilderModel + } + + private fun addContentToRequestBuilderModel( + methodId: MethodId, + arguments: List, + requestBuilderModel: UtAssembleModel, + idGenerator: () -> Int + ): UtAssembleModel? { + @Suppress("NAME_SHADOWING") + var requestBuilderModel = requestBuilderModel + methodId.method.parameters.zip(arguments).forEach { (param, arg) -> + @Suppress("UNCHECKED_CAST") + param.getAnnotation(requestBodyClassId.jClass as Class) ?: return@forEach + + val mediaTypeModel = UtAssembleModel( + id = idGenerator(), + classId = mediaTypeClassId, + modelName = "mediaType", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = MethodId( + classId = mediaTypeClassId, + name = "valueOf", + returnType = mediaTypeClassId, + parameters = listOf(stringClassId) + ), + // TODO detect actual media type ("application/json" is very common default) + params = listOf(UtPrimitiveModel("application/json")) + ), + ) + requestBuilderModel = UtAssembleModel( + id = idGenerator(), + classId = mockHttpServletRequestBuilderClassId, + modelName = "requestBuilder", + instantiationCall = UtExecutableCallModel( + instance = requestBuilderModel, + executable = mockHttpServletContentTypeMethodId, + params = listOf( + mediaTypeModel + ) + ) + ) + val content = UtAssembleModel( + id = idGenerator(), + classId = stringClassId, + modelName = "content", + instantiationCall = UtExecutableCallModel( + instance = + // TODO support libraries other than Jackson + if (utContext.classLoader.tryLoadClass(objectMapperClassId.name) == null) + return@addContentToRequestBuilderModel null + // TODO `getBean(ObjectMapper.class)`, name may change depending on Spring version + else createBeanModel("jacksonObjectMapper", idGenerator(), objectMapperClassId), + executable = MethodId( + classId = objectMapperClassId, + name = "writeValueAsString", + returnType = stringClassId, + parameters = listOf(objectClassId) + ), + params = listOf(arg) + ) + ) + requestBuilderModel = UtAssembleModel( + id = idGenerator(), + classId = mockHttpServletRequestBuilderClassId, + modelName = "requestBuilder", + instantiationCall = UtExecutableCallModel( + instance = requestBuilderModel, + executable = mockHttpServletContentMethodId, + params = listOf(content) + ) + ) + } + return requestBuilderModel + } + + private fun createCookieValuesModel( + cookieClassId: ClassId, + methodId: MethodId, + arguments: List, + idGenerator: () -> Int, + ): UtArrayModel { + val cookieValues = collectArgumentsWithAnnotationModels(methodId, cookieValueClassId, arguments) + .mapValues { (_, model) -> convertModelValueToString(model) }.toList() + + // Creating an indexed Map for `UtArrayModel.stores` + val indexedCookieValues = HashMap() + cookieValues.indices.forEach { ind -> + indexedCookieValues[ind] = UtAssembleModel( + id = idGenerator(), + classId = cookieClassId, + modelName = "cookie", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = constructorId(cookieClassId, stringClassId, stringClassId), + params = listOf(UtPrimitiveModel(cookieValues[ind].first), cookieValues[ind].second), + ) + ) + } + + return UtArrayModel( + id = idGenerator(), + classId = getArrayClassIdByElementClassId(cookieClassId), + length = cookieValues.size, + constModel = UtNullModel(cookieClassId), + stores = indexedCookieValues, + ) + } + + private fun createHeadersContentModel( + methodId: MethodId, + arguments: List, + idGenerator: () -> Int, + ): UtAssembleModel { + // Converts Map models values to String because `HttpHeaders.setAll(...)` method takes `Map` + val headersContent = collectArgumentsWithAnnotationModels(methodId, requestHeaderClassId, arguments) + .mapValues { (_, model) -> convertModelValueToString(model) } + + return UtAssembleModel( + id = idGenerator(), + classId = Map::class.java.id, + modelName = "headersContent", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = HashMap::class.java.getConstructor().executableId, + params = emptyList() + ), + modificationsChainProvider = { + headersContent.map { (name, value) -> + UtExecutableCallModel( + instance = this, + // Actually it is a `Map`, but we use `Object::class.java` to avoid concrete failures + executable = Map::class.java.getMethod( + "put", + Object::class.java, + Object::class.java + ).executableId, + params = listOf(UtPrimitiveModel(name), value) + ) + } + } + ) + } + + private fun createPathVariablesModel( + methodId: MethodId, + arguments: List, + idGenerator: () -> Int + ): UtAssembleModel { + val pathVariables = collectArgumentsWithAnnotationModels(methodId, pathVariableClassId, arguments) + + return UtAssembleModel( + id = idGenerator(), + classId = Map::class.java.id, + modelName = "pathVariables", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = HashMap::class.java.getConstructor().executableId, + params = emptyList() + ), + modificationsChainProvider = { + pathVariables.map { (name, value) -> + UtExecutableCallModel( + instance = this, + executable = Map::class.java.getMethod( + "put", + Object::class.java, + Object::class.java + ).executableId, + params = listOf(UtPrimitiveModel(name), value) + ) + } + } + ) + } + + private fun createRequestParamsModel( + methodId: MethodId, + arguments: List, + idGenerator: () -> Int + ): List> { + val requestParams = collectArgumentsWithAnnotationModels(methodId, requestParamClassId, arguments) + + return requestParams.map { (name, value) -> + Pair(UtPrimitiveModel(name), + UtArrayModel( + id = idGenerator(), + classId = getArrayClassIdByElementClassId(objectClassId), + length = 1, + constModel = UtNullModel(objectClassId), + stores = mutableMapOf(0 to value), + ) + ) + } + } + + private fun collectArgumentsWithAnnotationModels( + methodId: MethodId, + annotationClassId: ClassId, + arguments: List + ): MutableMap { + fun UtModel.isEmptyOptional(): Boolean { + return classId == Optional::class.java.id && this is UtAssembleModel && + instantiationCall is UtExecutableCallModel && instantiationCall.executable.name == "empty" + } + + val argumentsModels = mutableMapOf() + methodId.method.parameters.zip(arguments).forEach { (param, arg) -> + @Suppress("UNCHECKED_CAST") val paramAnnotation = + param.getAnnotation(annotationClassId.jClass as Class) ?: return@forEach + val name = (annotationClassId.jClass.getMethod("name").invoke(paramAnnotation) as? String).orEmpty() + .ifEmpty { annotationClassId.jClass.getMethod("value").invoke(paramAnnotation) as? String }.orEmpty() + .ifEmpty { param.name } + + if (arg.isNotNull() && !arg.isEmptyOptional()) { + argumentsModels[name] = arg + } + } + + return argumentsModels + } + + /** + * Converts the model into a form that is understandable for annotations. + * Example: UtArrayModel([UtPrimitiveModel("a"), UtPrimitiveModel("b"), UtPrimitiveModel("c")]) -> UtPrimitiveModel("a, b, c") + * + * There is known issue when using `model.toString()` is not reliable: + * https://github.com/UnitTestBot/UTBotJava/issues/2505 + * This issue may be improved in the future. + */ + private fun convertModelValueToString(model: UtModel): UtModel { + return UtPrimitiveModel( + when(model){ + is UtArrayModel -> withToStringThreadLocalReentrancyGuard { + (0 until model.length).map { model.stores[it] ?: model.constModel }.joinToString(", ") + } + else -> model.toString() + } + ) + } + + private fun createUrlTemplateModel( + requestPath: String, + pathVariablesModel: UtAssembleModel, + requestParamModel: List>, + idGenerator: () -> Int + ): UtModel { + val requestPathModel = UtPrimitiveModel(requestPath) + return if (pathVariablesModel.modificationsChain.isEmpty() && requestParamModel.isEmpty()) requestPathModel + else { + var uriBuilderFromPath = UtAssembleModel( + id = idGenerator(), + classId = uriComponentsBuilderClassId, + modelName = "uriBuilderFromPath", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = MethodId( + classId = uriComponentsBuilderClassId, + name = "fromPath", + parameters = listOf(stringClassId), + returnType = uriComponentsBuilderClassId + ), + params = listOf(requestPathModel), + ) + ) + + if(pathVariablesModel.modificationsChain.isNotEmpty()) { + uriBuilderFromPath = UtAssembleModel( + id = idGenerator(), + classId = uriComponentsBuilderClassId, + modelName = "uriBuilderWithPathVariables", + instantiationCall = UtExecutableCallModel( + instance = uriBuilderFromPath, + executable = MethodId( + classId = uriComponentsBuilderClassId, + name = "uriVariables", + parameters = listOf(Map::class.java.id), + returnType = uriComponentsBuilderClassId + ), + params = listOf(pathVariablesModel), + ) + ) + } + + requestParamModel.forEach { (name, value) -> + uriBuilderFromPath = UtAssembleModel( + id = idGenerator(), + classId = uriComponentsBuilderClassId, + modelName = "uriBuilderWithRequestParam", + instantiationCall = UtExecutableCallModel( + instance = uriBuilderFromPath, + executable = MethodId( + classId = uriComponentsBuilderClassId, + name = "queryParam", + parameters = listOf(stringClassId, getArrayClassIdByElementClassId(objectClassId)), + returnType = uriComponentsBuilderClassId + ), + params = listOf(name, value), + ) + ) + } + + return UtAssembleModel( + id = idGenerator(), + classId = stringClassId, + modelName = "uriString", + instantiationCall = UtExecutableCallModel( + instance = uriBuilderFromPath, + executable = MethodId( + classId = uriComponentsBuilderClassId, + name = "toUriString", + parameters = emptyList(), + returnType = stringClassId + ), + params = emptyList(), + ) + ) + } + } + + // TODO handle multiple annotations on one method + private fun getRequestMappingAnnotationOrNull(methodId: MethodId): Annotation? = + methodId.method.annotations + .firstOrNull { it.annotationClass.id in UtRequestMethod.annotationClassIds } + + // TODO support placeholders (e.g. "/${profile_path}"). + private fun getRequestPathOrNull(requestMappingAnnotation: Annotation): String? = + ((requestMappingAnnotation.annotationClass.java.getMethod("path") + .invoke(requestMappingAnnotation) as Array<*>) + .getOrNull(0) ?: + (requestMappingAnnotation.annotationClass.java.getMethod("value") // TODO separate support for @AliasFor + .invoke(requestMappingAnnotation) as Array<*>) + .getOrNull(0)) as? String // TODO support multiple paths + + private fun getRequestMethodOrNull(requestMappingAnnotation: Annotation): UtRequestMethod? = + UtRequestMethod.values().firstOrNull { requestMappingAnnotation.annotationClass.id == it.annotationClassId } ?: + (requestMappingClassId.jClass.getMethod("method").invoke(requestMappingAnnotation) as Array<*>) + .getOrNull(0)?.let { + UtRequestMethod.valueOf(it.toString()) + } + + private enum class UtRequestMethod { + GET, + HEAD, + POST, + PUT, + PATCH, + DELETE, + OPTIONS, + TRACE; + + val annotationClassId get() = ClassId( + "org.springframework.web.bind.annotation.${name.lowercase().capitalize()}Mapping" + ) + + val requestBuilderMethodId = MethodId( + classId = mockMvcRequestBuildersClassId, + name = name.lowercase(), + returnType = mockHttpServletRequestBuilderClassId, + parameters = listOf(stringClassId, objectArrayClassId) + ) + + companion object { + val annotationClassIds = values().map { it.annotationClassId } + + requestMappingClassId + } + } + + ///endregion +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/ThrowableUtils.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/ThrowableUtils.kt new file mode 100644 index 0000000000..30f49c0542 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/ThrowableUtils.kt @@ -0,0 +1,20 @@ +package org.utbot.framework.plugin.api.util + +import org.utbot.framework.plugin.api.OverflowDetectionError +import org.utbot.framework.plugin.api.TimeoutException + +val Throwable.description + get() = message?.replace('\n', '\t') ?: "" + +val Throwable.isCheckedException + get() = !(this is RuntimeException || this is Error) + +val Throwable.prettyName + get() = when (this) { + is OverflowDetectionError -> "Overflow" + is TimeoutException -> "Timeout" + else -> this::class.simpleName + } + +val Class<*>.isCheckedException + get() = !(RuntimeException::class.java.isAssignableFrom(this) || Error::class.java.isAssignableFrom(this)) \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtContext.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtContext.kt index af2e887bb0..9152d986c3 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtContext.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtContext.kt @@ -5,6 +5,7 @@ import org.utbot.common.currentThreadInfo import org.utbot.framework.plugin.api.util.UtContext.Companion.setUtContext import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.ThreadContextElement +import mu.KotlinLogging val utContext: UtContext get() = UtContext.currentContext() @@ -70,4 +71,11 @@ class UtContext(val classLoader: ClassLoader) : ThreadContextElement } } -inline fun withUtContext(context: UtContext, block: () -> T): T = setUtContext(context).use { block() } \ No newline at end of file +inline fun withUtContext(context: UtContext, block: () -> T): T = setUtContext(context).use { + try { + return@use block.invoke() + } catch (e: Exception) { + KotlinLogging.logger("withUtContext").error { e } + throw e + } +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtSettingsUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtSettingsUtil.kt index c6e4f928b7..a0585ca383 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtSettingsUtil.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/UtSettingsUtil.kt @@ -6,7 +6,7 @@ import org.utbot.framework.UtSettings * Runs [block] with [UtSettings.substituteStaticsWithSymbolicVariable] value * modified in accordance with given [condition]. */ -inline fun withSubstitutionCondition(condition: Boolean, block: () -> T) { +inline fun withStaticsSubstitutionRequired(condition: Boolean, block: () -> T) { val standardSubstitutionSetting = UtSettings.substituteStaticsWithSymbolicVariable UtSettings.substituteStaticsWithSymbolicVariable = standardSubstitutionSetting && condition try { @@ -14,4 +14,4 @@ inline fun withSubstitutionCondition(condition: Boolean, block: () -> T) { } finally { UtSettings.substituteStaticsWithSymbolicVariable = standardSubstitutionSetting } -} \ No newline at end of file +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/constructor/LambdaConstructionUtils.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/constructor/LambdaConstructionUtils.kt new file mode 100644 index 0000000000..f2e16807f8 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/constructor/LambdaConstructionUtils.kt @@ -0,0 +1,195 @@ +package org.utbot.framework.plugin.api.util.constructor + +import java.lang.invoke.LambdaMetafactory +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodHandles.Lookup +import java.lang.invoke.MethodType +import java.lang.reflect.Field +import java.lang.reflect.Method +import java.lang.reflect.Modifier +import org.utbot.common.Reflection + +/** + * This class represents the `type` and `value` of a value captured by lambda. + * Captured values are represented as arguments of a synthetic method that lambda is compiled into, + * hence the name of the class. + */ +data class CapturedArgument(val type: Class<*>, val value: Any?) + +/** + * @param clazz a class to create lookup instance for. + * @return [MethodHandles.Lookup] instance for the given [clazz]. + * It can be used, for example, to search methods of this [clazz], even the `private` ones. + */ +@Suppress("UNCHECKED_CAST") +private fun getLookupIn(clazz: Class<*>): Lookup { + val lookup = MethodHandles.lookup().`in`(clazz) + + // Allow lookup to access all members of declaringClass, including the private ones. + // For example, it is useful to access private synthetic methods representing lambdas. + val fields = Reflection.getDeclaredFields0Method.invoke(Lookup::class.java, false) as Array + val allowedModes = fields.single { it.name == "allowedModes" } + val allModesField = fields.single { it.name == "ALL_MODES" } + allowedModes.isAccessible = true + allModesField.isAccessible = true + allowedModes.setInt(lookup, allModesField.get(null) as Int) + + return lookup +} + +/** + * @param lambdaMethod [Method] that represents a synthetic method for lambda. + * @param capturedArgumentTypes types of values captured by lambda. + * @return [MethodType] that represents the value of argument `instantiatedMethodType` + * of method [LambdaMetafactory.metafactory]. + */ +private fun getInstantiatedMethodType( + lambdaMethod: Method, + capturedArgumentTypes: Array> +): MethodType { + // Types of arguments of synthetic method (representing lambda) without the types of captured values. + val instantiatedMethodParamTypes = lambdaMethod.parameterTypes + .drop(capturedArgumentTypes.size) + .toTypedArray() + + return MethodType.methodType(lambdaMethod.returnType, instantiatedMethodParamTypes) +} + +/** + * @param declaringClass class where a lambda is declared. + * @param lambdaName name of synthetic method that represents a lambda. + * @return [Method] instance for the synthetic method that represent a lambda. + */ +private fun getLambdaMethod(declaringClass: Class<*>, lambdaName: String): Method { + return declaringClass.declaredMethods.firstOrNull { it.name == lambdaName } + ?: throw IllegalArgumentException("No lambda method named $lambdaName was found in class: ${declaringClass.canonicalName}") +} + +/** + * This class contains some info that is needed by both [constructLambda] and [constructStaticLambda]. + * We obtain this info in [prepareLambdaInfo] to avoid duplicated code in [constructLambda] and [constructStaticLambda]. + */ +private data class LambdaMetafactoryInfo( + val caller: Lookup, + val invokedName: String, + val samMethodType: MethodType, + val lambdaMethod: Method, + val lambdaMethodType: MethodType +) + +/** + * Obtain and prepare [LambdaMetafactoryInfo] that is needed by [constructLambda] and [constructStaticLambda]. + */ +private fun prepareLambdaInfo( + samType: Class<*>, + declaringClass: Class<*>, + lambdaName: String, +): LambdaMetafactoryInfo { + // Create lookup for class where the lambda is declared in. + val caller = getLookupIn(declaringClass) + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + val singleAbstractMethod = getSingleAbstractMethod(samType) + + val invokedName = singleAbstractMethod.name + + // Method type of single abstract method of the target functional interface. + val samMethodType = MethodType.methodType(singleAbstractMethod.returnType, singleAbstractMethod.parameterTypes) + + val lambdaMethod = getLambdaMethod(declaringClass, lambdaName) + lambdaMethod.isAccessible = true + val lambdaMethodType = MethodType.methodType(lambdaMethod.returnType, lambdaMethod.parameterTypes) + + return LambdaMetafactoryInfo(caller, invokedName, samMethodType, lambdaMethod, lambdaMethodType) +} + +/** + * @param clazz functional interface + * @return a [Method] for the single abstract method of the given functional interface `clazz`. + */ +private fun getSingleAbstractMethod(clazz: Class<*>): Method { + val abstractMethods = clazz.methods.filter { Modifier.isAbstract(it.modifiers) } + require(abstractMethods.isNotEmpty()) { "No abstract methods found in class: " + clazz.canonicalName } + require(abstractMethods.size <= 1) { "More than one abstract method found in class: " + clazz.canonicalName } + return abstractMethods[0] +} + +/** + * @return an [Any] that represents an instance of the given functional interface `samType` + * and implements its single abstract method with the behavior of the given lambda. + */ +fun constructStaticLambda( + samType: Class<*>, + declaringClass: Class<*>, + lambdaName: String, + vararg capturedArguments: CapturedArgument +): Any { + val (caller, invokedName, samMethodType, lambdaMethod, lambdaMethodType) = + prepareLambdaInfo(samType, declaringClass, lambdaName) + + val lambdaMethodHandle = caller.findStatic(declaringClass, lambdaName, lambdaMethodType) + + val capturedArgumentTypes = capturedArguments.map { it.type }.toTypedArray() + val invokedType = MethodType.methodType(samType, capturedArgumentTypes) + val instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes) + + // Create a CallSite for the given lambda. + val site = LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType + ) + val capturedValues = capturedArguments.map { it.value }.toTypedArray() + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + val handle = site.target + return handle.invokeWithArguments(*capturedValues) +} + +/** + * @return an [Any] that represents an instance of the given functional interface `samType` + * and implements its single abstract method with the behavior of the given lambda. + */ +fun constructLambda( + samType: Class<*>, + declaringClass: Class<*>, + lambdaName: String, + capturedReceiver: Any?, + vararg capturedArguments: CapturedArgument +): Any { + val (caller, invokedName, samMethodType, lambdaMethod, lambdaMethodType) = + prepareLambdaInfo(samType, declaringClass, lambdaName) + + val lambdaMethodHandle = caller.findVirtual(declaringClass, lambdaName, lambdaMethodType) + + val capturedArgumentTypes = capturedArguments.map { it.type }.toTypedArray() + val invokedType = MethodType.methodType(samType, declaringClass, *capturedArgumentTypes) + val instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes) + + // Create a CallSite for the given lambda. + val site = LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType + ) + val capturedValues = mutableListOf() + .apply { + add(capturedReceiver) + val capturedArgumentValues = capturedArguments.map { it.value } + addAll(capturedArgumentValues) + }.toTypedArray() + + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + val handle = site.target + return handle.invokeWithArguments(*capturedValues) +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/constructor/ValueConstructor.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/constructor/ValueConstructor.kt new file mode 100644 index 0000000000..c2fa773df7 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/util/constructor/ValueConstructor.kt @@ -0,0 +1,525 @@ +package org.utbot.framework.plugin.api.util.constructor + +import org.utbot.common.Reflection +import org.utbot.common.invokeCatching +import org.utbot.common.withAccessibility +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.DirectFieldAccessId +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.FieldMockTarget +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.MockId +import org.utbot.framework.plugin.api.MockInfo +import org.utbot.framework.plugin.api.MockTarget +import org.utbot.framework.plugin.api.ObjectMockTarget +import org.utbot.framework.plugin.api.ParameterMockTarget +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtClassRefModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtConcreteValue +import org.utbot.framework.plugin.api.UtCustomModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtDirectGetFieldModel +import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtExecutionFailure +import org.utbot.framework.plugin.api.UtExecutionResult +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.UtMockValue +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtStatementCallModel +import org.utbot.framework.plugin.api.UtSymbolicExecution +import org.utbot.framework.plugin.api.UtValueExecution +import org.utbot.framework.plugin.api.UtValueExecutionState +import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.framework.plugin.api.isMockModel +import org.utbot.framework.plugin.api.util.anyInstance +import org.utbot.framework.plugin.api.util.constructor +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.jField +import org.utbot.framework.plugin.api.util.method +import org.utbot.framework.plugin.api.util.utContext +import java.lang.reflect.Modifier +import kotlin.reflect.KClass + +/** + * Constructs values from models. + * + * Uses model->constructed object reference-equality cache. + */ +// TODO: JIRA:1379 -- Refactor ValueConstructor and MockValueConstructor +class ValueConstructor { + private val classLoader: ClassLoader + get() = utContext.classLoader + + // TODO: JIRA:1379 -- replace UtReferenceModel with Int + private val constructedObjects = HashMap() + private val mockInfo = mutableListOf() + private var mockTarget: MockTarget? = null + private var mockCounter = 0 + + private fun clearState() { + constructedObjects.clear() + mockInfo.clear() + mockTarget = null + mockCounter = 0 + } + + /** + * Clears state before and after block execution. State contains caches for reference equality. + */ + private inline fun withCleanState(block: () -> T): T { + try { + clearState() + return block() + } finally { + clearState() + } + } + + /** + * Sets mock context (possible mock target) before block execution and restores previous one after block execution. + */ + private inline fun withMockTarget(target: MockTarget?, block: () -> T): T { + val old = mockTarget + try { + mockTarget = target + return block() + } finally { + mockTarget = old + } + } + + /** + * Constructs values from models. + */ + fun construct(models: List): List> = + withCleanState { models.map { construct(it, null) } } + + private fun constructState(state: EnvironmentModels): Pair> { + val (thisInstance, params, statics) = state + val allParams = listOfNotNull(thisInstance) + params + + val (valuesBefore, mocks, staticValues) = constructParamsAndMocks(allParams, statics) + + val (caller, paramsValues) = if (thisInstance != null) { + valuesBefore.first() to valuesBefore.drop(1) + } else { + null to valuesBefore + } + + return UtValueExecutionState(caller, paramsValues, staticValues) to mocks + } + + /** + * Constructs value based execution from model based. + */ + fun construct(execution: UtExecution): UtValueExecution<*> { + val (stateBefore, mocks) = constructState(execution.stateBefore) + val (stateAfter, _) = constructState(execution.stateAfter) + val returnValue = execution.result.map { construct(listOf(it)).single().value } + + if (execution is UtSymbolicExecution) { + return UtValueExecution( + stateBefore, + stateAfter, + returnValue, + execution.path, + mocks, + execution.instrumentation, + execution.summary, + execution.testMethodName, + execution.displayName + ) + } else { + return UtValueExecution( + stateBefore, + stateAfter, + returnValue, + emptyList(), + mocks, + emptyList(), + execution.summary, + execution.testMethodName, + execution.displayName + ) + } + } + + private fun constructParamsAndMocks( + models: List, + statics: Map + ): ConstructedValues = + withCleanState { + val values = models.mapIndexed { index, model -> + val target = mockTarget(model) { ParameterMockTarget(model.classId.name, index) } + construct(model, target) + } + + val staticValues = mutableMapOf>() + + statics.forEach { (field, model) -> + val target = FieldMockTarget(model.classId.name, field.declaringClass.name, owner = null, field.name) + staticValues += field to construct(model, target) + } + + ConstructedValues(values, mockInfo.toList(), staticValues) + } + + /** + * Main construction method. + * + * Takes care of nulls. Does not use cache, instead construct(Object/Array/List/FromAssembleModel) method + * uses cache directly. + * + * Takes mock creation context (possible mock target) to create mock if required. + */ + private fun construct(model: UtModel, target: MockTarget?): UtConcreteValue<*> = withMockTarget(target) { + when (model) { + is UtNullModel -> UtConcreteValue(null, model.classId.jClass) + is UtPrimitiveModel -> UtConcreteValue(model.value, model.classId.jClass) + is UtEnumConstantModel -> UtConcreteValue(model.value) + is UtClassRefModel -> UtConcreteValue(model.value.jClass) + is UtCompositeModel -> UtConcreteValue(constructObject(model), model.classId.jClass) + is UtArrayModel -> UtConcreteValue(constructArray(model)) + is UtAssembleModel -> UtConcreteValue(constructFromAssembleModel(model)) + is UtLambdaModel -> UtConcreteValue(constructFromLambdaModel(model)) + is UtVoidModel -> UtConcreteValue(Unit) + is UtCustomModel -> UtConcreteValue( + constructObject(model.origin ?: error("Can't construct value for custom model without origin [$model]")), + model.classId.jClass + ) + // Python, JavaScript are supposed to be here as well + else -> throw UnsupportedOperationException("UtModel $model cannot construct UtConcreteValue") + } + } + + /** + * Constructs object by model, uses reference-equality cache. + * + * Returns null for mock cause cannot instantiate it. + */ + private fun constructObject(model: UtCompositeModel): Any? { + val constructed = constructedObjects[model] + if (constructed != null) { + return constructed + } + + this.mockTarget?.let { mockTarget -> + model.mocks.forEach { (methodId, models) -> + mockInfo += MockInfo(mockTarget, methodId, models.map { model -> + if (model.isMockModel()) { + val mockId = MockId("mock${++mockCounter}") + // Call to "construct" method still required to collect mock interaction + construct(model, ObjectMockTarget(model.classId.name, mockId)) + UtMockValue(mockId, model.classId.name) + } else { + construct(model, null) + } + }) + } + } + + if (model.isMock) { + return null + } + + val javaClass = javaClass(model.classId) + val classInstance = javaClass.anyInstance + constructedObjects[model] = classInstance + + model.fields.forEach { (fieldId, fieldModel) -> + val declaredField = fieldId.jField + val accessible = declaredField.isAccessible + + try { + declaredField.isAccessible = true + + check(Reflection.isModifiersAccessible()) + + val target = mockTarget(fieldModel) { + FieldMockTarget( + fieldModel.classId.name, + model.classId.name, + UtConcreteValue(classInstance), + fieldId.name + ) + } + val value = construct(fieldModel, target).value + val instance = if (Modifier.isStatic(declaredField.modifiers)) null else classInstance + declaredField.set(instance, value) + } finally { + declaredField.isAccessible = accessible + } + } + + return classInstance + } + + /** + * Constructs array by model. + * + * Supports arrays of primitive, arrays of arrays and arrays of objects. + * + * Note: does not check isNull, but if isNull set returns empty array because for null array length set to 0. + */ + private fun constructArray(model: UtArrayModel): Any { + val constructed = constructedObjects[model] + if (constructed != null) { + return constructed + } + + with(model) { + val elementClassId = classId.elementClassId!! + return when (elementClassId.jvmName) { + "B" -> ByteArray(length) { primitive(constModel) }.apply { + constructedObjects[model] = this + stores.forEach { (index, model) -> this[index] = primitive(model) } + } + "S" -> ShortArray(length) { primitive(constModel) }.apply { + constructedObjects[model] = this + stores.forEach { (index, model) -> this[index] = primitive(model) } + } + "C" -> CharArray(length) { primitive(constModel) }.apply { + constructedObjects[model] = this + stores.forEach { (index, model) -> this[index] = primitive(model) } + } + "I" -> IntArray(length) { primitive(constModel) }.apply { + constructedObjects[model] = this + stores.forEach { (index, model) -> this[index] = primitive(model) } + } + "J" -> LongArray(length) { primitive(constModel) }.apply { + constructedObjects[model] = this + stores.forEach { (index, model) -> this[index] = primitive(model) } + } + "F" -> FloatArray(length) { primitive(constModel) }.apply { + constructedObjects[model] = this + stores.forEach { (index, model) -> this[index] = primitive(model) } + } + "D" -> DoubleArray(length) { primitive(constModel) }.apply { + constructedObjects[model] = this + stores.forEach { (index, model) -> this[index] = primitive(model) } + } + "Z" -> BooleanArray(length) { primitive(constModel) }.apply { + constructedObjects[model] = this + stores.forEach { (index, model) -> this[index] = primitive(model) } + } + else -> { + val javaClass = javaClass(elementClassId) + val instance = java.lang.reflect.Array.newInstance(javaClass, length) as Array<*> + constructedObjects[model] = instance + for (i in instance.indices) { + val elementModel = stores[i] ?: constModel + val value = construct(elementModel, null).value + java.lang.reflect.Array.set(instance, i, value) + } + instance + } + } + } + } + + /** + * Constructs object with [UtAssembleModel]. + */ + private fun constructFromAssembleModel(assembleModel: UtAssembleModel): Any { + constructedObjects[assembleModel]?.let { return it } + + val instantiationExecutableCall = assembleModel.instantiationCall + val result = updateWithStatementCallModel(instantiationExecutableCall) + checkNotNull(result) { + "Tracked instance can't be null for call ${instantiationExecutableCall.statement} in model $assembleModel" + } + constructedObjects[assembleModel] = result + + assembleModel.modificationsChain.forEach { statementModel -> + when (statementModel) { + is UtStatementCallModel -> updateWithStatementCallModel(statementModel) + is UtDirectSetFieldModel -> updateWithDirectSetFieldModel(statementModel) + } + } + + return constructedObjects[assembleModel] ?: error("Can't assemble model: $assembleModel") + } + + private fun constructFromLambdaModel(lambdaModel: UtLambdaModel): Any { + // A class representing a functional interface. + val samType: Class<*> = lambdaModel.samType.jClass + // A class where the lambda is declared. + val declaringClass: Class<*> = lambdaModel.declaringClass.jClass + // A name of the synthetic method that represents a lambda. + val lambdaName = lambdaModel.lambdaName + + return if (lambdaModel.lambdaMethodId.isStatic) { + val capturedArguments = lambdaModel.capturedValues + .map { model -> CapturedArgument(type = model.classId.jClass, value = value(model)) } + .toTypedArray() + constructStaticLambda(samType, declaringClass, lambdaName, *capturedArguments) + } else { + val capturedReceiverModel = lambdaModel.capturedValues.firstOrNull() + ?: error("Non-static lambda must capture `this` instance, so there must be at least one captured value") + + // Values that the given lambda has captured. + val capturedReceiver = value(capturedReceiverModel) ?: error("Captured receiver of lambda must not be null") + val capturedArguments = lambdaModel.capturedValues.subList(1, lambdaModel.capturedValues.size) + .map { model -> CapturedArgument(type = model.classId.jClass, value = value(model)) } + .toTypedArray() + constructLambda(samType, declaringClass, lambdaName, capturedReceiver, *capturedArguments) + } + } + + /** + * Updates instance state with [callModel] invocation. + * + * @return the result of [callModel] invocation + */ + private fun updateWithStatementCallModel(callModel: UtStatementCallModel, ): Any? { + when (callModel) { + is UtExecutableCallModel -> { + val executable = callModel.executable + val instanceValue = callModel.instance?.let { value(it) } + val params = callModel.params.map { value(it) } + + return when (executable) { + is MethodId -> executable.call(params, instanceValue) + is ConstructorId -> executable.call(params) + } + } + is UtDirectGetFieldModel -> { + val fieldAccess = callModel.fieldAccess + val instanceValue = value(callModel.instance) + + return fieldAccess.get(instanceValue) + } + } + } + + /** + * Updates instance with [UtDirectSetFieldModel] execution. + */ + private fun updateWithDirectSetFieldModel(directSetterModel: UtDirectSetFieldModel) { + val instanceModel = directSetterModel.instance + val instance = constructedObjects[instanceModel] ?: error("Model $instanceModel is not instantiated") + + val instanceClassId = instanceModel.classId + val fieldModel = directSetterModel.fieldModel + + val field = directSetterModel.fieldId.jField + val isAccessible = field.isAccessible + + try { + //set field accessible to support protected or package-private direct setters + field.isAccessible = true + + //prepare mockTarget for field if it is a mock + val mockTarget = mockTarget(fieldModel) { + FieldMockTarget( + fieldModel.classId.name, + instanceClassId.name, + UtConcreteValue(javaClass(instanceClassId).anyInstance), + field.name + ) + } + + //construct and set the value + val fieldValue = construct(fieldModel, mockTarget).value + field.set(instance, fieldValue) + } finally { + //restore accessibility property of the field + field.isAccessible = isAccessible + } + } + + /** + * Constructs value from [UtModel]. + */ + private fun value(model: UtModel) = construct(model, null).value + + private fun MethodId.call(args: List, instance: Any?): Any? = + method.withAccessibility { + method.invokeCatching(obj = instance, args = args).getOrThrow() + } + + private fun ConstructorId.call(args: List): Any? = + constructor.withAccessibility { + newInstance(*args.toTypedArray()) + } + + private fun DirectFieldAccessId.get(instance: Any?): Any { + val field = fieldId.jField + return field.withAccessibility { + field.get(instance) + } + } + + /** + * Fetches primitive value from NutsModel to create array of primitives. + */ + private inline fun primitive(model: UtModel): T = (model as UtPrimitiveModel).value as T + + private fun javaClass(id: ClassId) = kClass(id).java + + private fun kClass(id: ClassId) = + if (id.elementClassId != null) { + arrayClassOf(id.elementClassId!!) + } else { + when (id.jvmName) { + "B" -> Byte::class + "S" -> Short::class + "C" -> Char::class + "I" -> Int::class + "J" -> Long::class + "F" -> Float::class + "D" -> Double::class + "Z" -> Boolean::class + else -> classLoader.loadClass(id.name).kotlin + } + } + + private fun arrayClassOf(elementClassId: ClassId): KClass<*> = + if (elementClassId.elementClassId != null) { + val elementClass = arrayClassOf(elementClassId.elementClassId!!) + java.lang.reflect.Array.newInstance(elementClass.java, 0)::class + } else { + when (elementClassId.jvmName) { + "B" -> ByteArray::class + "S" -> ShortArray::class + "C" -> CharArray::class + "I" -> IntArray::class + "J" -> LongArray::class + "F" -> FloatArray::class + "D" -> DoubleArray::class + "Z" -> BooleanArray::class + else -> { + val elementClass = classLoader.loadClass(elementClassId.name) + java.lang.reflect.Array.newInstance(elementClass, 0)::class + } + } + } +} + +private fun UtExecutionResult.map(transform: (model: UtModel) -> R): Result = when (this) { + is UtExecutionSuccess -> Result.success(transform(model)) + is UtExecutionFailure -> Result.failure(exception) +} + +/** + * Creates mock target using init lambda if model represents mock or null otherwise. + */ +private fun mockTarget(model: UtModel, init: () -> MockTarget): MockTarget? = + if (model.isMockModel()) init() else null + +data class ConstructedValues( + val values: List>, + val mocks: List, + val statics: Map> +) \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/visible/UtStreamConsumingException.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/visible/UtStreamConsumingException.kt new file mode 100644 index 0000000000..522a73b88f --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/visible/UtStreamConsumingException.kt @@ -0,0 +1,18 @@ +package org.utbot.framework.plugin.api.visible + +/** + * An artificial exception that stores an exception that would be thrown in case of consuming stream by invoking terminal operations. + * [innerException] stores this possible exception (null if [UtStreamConsumingException] was constructed by the engine). + * + * NOTE: this class should be visible in almost all parts of the tool - Soot, engine, concrete execution and code generation, + * that's the reason why this class is placed in this module and this package is called `visible`. + */ +data class UtStreamConsumingException(private val innerException: Exception?) : RuntimeException() { + /** + * Returns the original exception [innerException] if possible, and any [RuntimeException] otherwise. + */ + val innerExceptionOrAny: Throwable + get() = innerException ?: RuntimeException("Unknown runtime exception during consuming stream") + + override fun toString(): String = innerExceptionOrAny.toString() +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/JdkInfoService.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/JdkInfoService.kt new file mode 100644 index 0000000000..08d18c1034 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/JdkInfoService.kt @@ -0,0 +1,39 @@ +package org.utbot.framework.plugin.services + +import java.nio.file.Path +import java.nio.file.Paths + +data class JdkInfo( + val path: Path, + val version: Int +) + +/** + * Singleton to enable abstract access to path to JDK. + + * Used in [org.utbot.framework.process.AbstractRDProcessCompanion]. + * The purpose is to use the same JDK in [org.utbot.instrumentation.ConcreteExecutor] and in the test runs. + * This is necessary because the engine can be run from the various starting points, like IDEA plugin, CLI, etc. + */ +object JdkInfoService : PluginService { + var jdkInfoProvider: JdkInfoProvider = JdkInfoDefaultProvider() + + override fun provide(): JdkInfo = jdkInfoProvider.info +} + +interface JdkInfoProvider { + val info: JdkInfo +} + +/** + * Gets [JdkInfo] from the current process. + */ +open class JdkInfoDefaultProvider : JdkInfoProvider { + override val info: JdkInfo = + JdkInfo(Paths.get(System.getProperty("java.home")), fetchJavaVersion(System.getProperty("java.version"))) +} + +fun fetchJavaVersion(javaVersion: String): Int { + val matcher = "(1\\.)?(\\d+)".toRegex() + return Integer.parseInt(matcher.find(javaVersion)?.groupValues?.getOrNull(2)!!) +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/PluginService.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/PluginService.kt new file mode 100644 index 0000000000..2b4796117a --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/PluginService.kt @@ -0,0 +1,5 @@ +package org.utbot.framework.plugin.services + +interface PluginService { + fun provide(): T +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/WorkingDirService.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/WorkingDirService.kt new file mode 100644 index 0000000000..e448a21a76 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/services/WorkingDirService.kt @@ -0,0 +1,26 @@ +package org.utbot.framework.plugin.services + +import java.nio.file.Path +import java.nio.file.Paths + +/** + * Singleton to enable abstract access to the working directory. + * + * Used in [org.utbot.instrumentation.rd.InstrumentedProcess]. + * The purpose is to use the same working directory in [org.utbot.instrumentation.ConcreteExecutor] + * and in the test runs. + */ +object WorkingDirService : PluginService { + var workingDirProvider: WorkingDirProvider = WorkingDirDefaultProvider() + + override fun provide(): Path = workingDirProvider.workingDir +} + +abstract class WorkingDirProvider { + abstract val workingDir: Path +} + +open class WorkingDirDefaultProvider : WorkingDirProvider() { + override val workingDir: Path + get() = Paths.get(System.getProperty("user.dir")) +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/AbstractRDProcessCompanion.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/AbstractRDProcessCompanion.kt new file mode 100644 index 0000000000..41b0499cae --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/AbstractRDProcessCompanion.kt @@ -0,0 +1,37 @@ +package org.utbot.framework.process + +import org.utbot.common.osSpecificJavaExecutable +import org.utbot.framework.plugin.services.JdkInfoService +import org.utbot.rd.rdPortArgument +import java.io.File +import kotlin.io.path.pathString + +abstract class AbstractRDProcessCompanion( + private val debugPort: Int, + private val runWithDebug: Boolean, + private val suspendExecutionInDebugMode: Boolean, + private val processSpecificCommandLineArgs: () -> List +) { + private val javaExecutablePathString get() = + JdkInfoService.provide().path.resolve("bin${File.separatorChar}${osSpecificJavaExecutable()}") + + protected fun obtainProcessCommandLine(port: Int): List = buildList { + addAll(obtainCommonProcessCommandLineArgs()) + addAll(processSpecificCommandLineArgs()) + add(rdPortArgument(port)) + } + + private fun obtainCommonProcessCommandLineArgs(): List = buildList { + val suspendValue = if (suspendExecutionInDebugMode) "y" else "n" + val debugArgument = + "-agentlib:jdwp=transport=dt_socket,server=n,suspend=${suspendValue},quiet=y,address=$debugPort" + .takeIf { runWithDebug } + + add(javaExecutablePathString.pathString) + val javaVersionSpecificArgs = OpenModulesContainer.javaVersionSpecificArguments + if (javaVersionSpecificArgs.isNotEmpty()) { + addAll(javaVersionSpecificArgs) + } + debugArgument?.let { add(it) } + } +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/OpenModulesContainer.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/OpenModulesContainer.kt new file mode 100644 index 0000000000..2d95c4669c --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/OpenModulesContainer.kt @@ -0,0 +1,72 @@ +package org.utbot.framework.process + +import org.utbot.framework.plugin.services.JdkInfoService + +object OpenModulesContainer { + private val modulesContainer: List + val javaVersionSpecificArguments: List + get() = modulesContainer + .takeIf { JdkInfoService.provide().version > 8 } ?: emptyList() + + init { + modulesContainer = buildList { + openPackage("java.base", "java.util.concurrent.atomic") + openPackage("java.base", "sun.security.util") + openPackage("java.base", "sun.reflect.annotation") + openPackage("java.base", "java.time") + openPackage("java.base", "java.text") + openPackage("java.base", "java.lang.invoke") + openPackage("java.base", "jdk.internal.misc") + openPackage("java.base", "sun.reflect.generics.repository") + openPackage("java.base", "java.io") + openPackage("java.base", "java.nio") + openPackage("java.base", "java.nio.file") + openPackage("java.base", "java.net") + openPackage("java.base", "java.lang") + openPackage("java.base", "java.security") + openPackage("java.base", "java.util") + openPackage("java.base", "java.util.stream") + openPackage("java.base", "java.util.concurrent") + openPackage("java.base", "java.util.concurrent.locks") + openPackage("java.base", "java.math") + openPackage("java.base", "java.lang.ref") + openPackage("java.base", "java.lang.reflect") + openPackage("java.base", "sun.security.provider") + openPackage("java.base", "sun.net.util") + openPackage("java.base", "sun.net.fs") + openPackage("java.base", "jdk.internal.event") + openPackage("java.base", "jdk.internal.jimage") + openPackage("java.base", "jdk.internal.jimage.decompressor") + openPackage("java.base", "jdk.internal.jmod") + openPackage("java.base", "jdk.internal.jtrfs") + openPackage("java.base", "jdk.internal.loader") + openPackage("java.base", "jdk.internal.logger") + openPackage("java.base", "jdk.internal.math") + openPackage("java.base", "jdk.internal.misc") + openPackage("java.base", "jdk.internal.module") + openPackage("java.base", "jdk.internal.org.objectweb.asm.commons") + openPackage("java.base", "jdk.internal.org.objectweb.asm.signature") + openPackage("java.base", "jdk.internal.org.objectweb.asm.tree") + openPackage("java.base", "jdk.internal.org.objectweb.asm.tree.analysis") + openPackage("java.base", "jdk.internal.org.objectweb.asm.util") + openPackage("java.base", "jdk.internal.org.xml.sax") + openPackage("java.base", "jdk.internal.org.xml.sax.helpers") + openPackage("java.base", "jdk.internal.perf") + openPackage("java.base", "jdk.internal.platform") + openPackage("java.base", "jdk.internal.ref") + openPackage("java.base", "jdk.internal.reflect") + openPackage("java.base", "jdk.internal.util") + openPackage("java.base", "jdk.internal.util.jar") + openPackage("java.base", "jdk.internal.util.xml") + openPackage("java.base", "jdk.internal.util.xml.impl") + openPackage("java.base", "jdk.internal.vm") + openPackage("java.base", "jdk.internal.vm.annotation") + add("--illegal-access=warn") + } + } + + private fun MutableList.openPackage(module: String, pakage: String) { + add("--add-opens") + add("$module/$pakage=ALL-UNNAMED") + } +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/kryo/KryoHelper.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/kryo/KryoHelper.kt new file mode 100644 index 0000000000..3fb47e2558 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/kryo/KryoHelper.kt @@ -0,0 +1,121 @@ +package org.utbot.framework.process.kryo + +import com.esotericsoftware.kryo.kryo5.Kryo +import com.esotericsoftware.kryo.kryo5.SerializerFactory +import com.esotericsoftware.kryo.kryo5.io.Input +import com.esotericsoftware.kryo.kryo5.io.Output +import com.esotericsoftware.kryo.kryo5.objenesis.instantiator.ObjectInstantiator +import com.esotericsoftware.kryo.kryo5.objenesis.strategy.StdInstantiatorStrategy +import com.esotericsoftware.kryo.kryo5.serializers.JavaSerializer +import com.esotericsoftware.kryo.kryo5.util.DefaultInstantiatorStrategy +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.lifetime.throwIfNotAlive +import java.io.ByteArrayOutputStream +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock + +/** + * Helpful class for working with the kryo. + */ +class KryoHelper constructor( + private val lifetime: Lifetime +) { + private val outputBuffer = ByteArrayOutputStream() + private val kryoOutput = Output(outputBuffer) + private val kryoInput= Input() + private val sendKryo: Kryo = TunedKryo() + private val receiveKryo: Kryo = TunedKryo() + private val myLockObject = ReentrantLock() + + init { + sendKryo.setAutoReset(true) + receiveKryo.setAutoReset(true) + lifetime.onTermination { + kryoInput.close() + kryoOutput.close() + } + } + + fun setKryoClassLoader(classLoader: ClassLoader) { + sendKryo.classLoader = classLoader + receiveKryo.classLoader = classLoader + } + + /** + * Serializes object to ByteArray + * + * @throws WritingToKryoException wraps all exceptions + */ + fun writeObject(obj: T): ByteArray = myLockObject.withLock { + lifetime.throwIfNotAlive() + try { + sendKryo.writeClassAndObject(kryoOutput, obj) + kryoOutput.flush() + + return outputBuffer.toByteArray() + } catch (e: Exception) { + throw WritingToKryoException(e) + } finally { + kryoOutput.reset() + outputBuffer.reset() + } + } + + /** + * Deserializes object form ByteArray + * + * @throws ReadingFromKryoException wraps all exceptions + */ + fun readObject(byteArray: ByteArray): T = myLockObject.withLock { + lifetime.throwIfNotAlive() + return try { + kryoInput.buffer = byteArray + receiveKryo.readClassAndObject(kryoInput) as T + } catch (e: Exception) { + throw ReadingFromKryoException(e) + } + finally { + receiveKryo.reset() + } + } +} + +// This kryo is used to initialize collections properly. +internal class TunedKryo : Kryo() { + init { + this.references = true + this.isRegistrationRequired = false + + this.instantiatorStrategy = object : StdInstantiatorStrategy() { + // workaround for Collections as they cannot be correctly deserialized without calling constructor + val default = DefaultInstantiatorStrategy() + val classesBadlyDeserialized = listOf( + java.util.Queue::class.java, + java.util.HashSet::class.java + ) + + override fun newInstantiatorOf(type: Class): ObjectInstantiator { + return if (classesBadlyDeserialized.any { it.isAssignableFrom(type) }) { + @Suppress("UNCHECKED_CAST") + default.newInstantiatorOf(type) as ObjectInstantiator + } else { + super.newInstantiatorOf(type) + } + } + } + + this.setOptimizedGenerics(false) + + // Kryo cannot (at least, the current used version) deserialize stacktraces that are required for SARIF reports. + // TODO: JIRA:1492 + addDefaultSerializer(java.lang.Throwable::class.java, ThrowableSerializer()) + + addDefaultSerializer(java.lang.StackTraceElement::class.java, JavaSerializer()) + + val factory = object : SerializerFactory.FieldSerializerFactory() {} + factory.config.ignoreSyntheticFields = true + factory.config.serializeTransient = false + factory.config.fieldsCanBeNull = true + this.setDefaultSerializer(factory) + } +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/kryo/ProcessCommunicationException.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/kryo/ProcessCommunicationException.kt new file mode 100644 index 0000000000..53dc6bd623 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/kryo/ProcessCommunicationException.kt @@ -0,0 +1,9 @@ +package org.utbot.framework.process.kryo + +open class ProcessCommunicationException(msg: String?, cause: Throwable? = null) : Exception(msg, cause) + +class ReadingFromKryoException(e: Throwable) : + ProcessCommunicationException("Reading from Kryo exception |> ${e.stackTraceToString()}", e) + +class WritingToKryoException(e: Throwable) : + ProcessCommunicationException("Writing to Kryo exception |> ${e.stackTraceToString()}", e) \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/kryo/ThrowableSerializer.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/kryo/ThrowableSerializer.kt new file mode 100644 index 0000000000..821ed28563 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/process/kryo/ThrowableSerializer.kt @@ -0,0 +1,144 @@ +package org.utbot.framework.process.kryo + +import com.esotericsoftware.kryo.kryo5.Kryo +import com.esotericsoftware.kryo.kryo5.KryoException +import com.esotericsoftware.kryo.kryo5.Serializer +import com.esotericsoftware.kryo.kryo5.io.Input +import com.esotericsoftware.kryo.kryo5.io.Output +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.warn +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jClass +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.ObjectInputStream +import java.io.ObjectOutputStream +import java.io.ObjectStreamClass + +class ThrowableSerializer : Serializer() { + companion object { + private val loggedUnserializableExceptionClassIds = mutableSetOf() + private val logger = getLogger() + } + + private class ThrowableModel( + val classId: ClassId, + val message: String?, + val stackTrace: Array, + val cause: ThrowableModel?, + val serializedExceptionBytes: ByteArray?, + ) + + override fun write(kryo: Kryo, output: Output, throwable: Throwable?) { + fun Throwable.toModel(): ThrowableModel = ThrowableModel( + classId = this::class.java.id, + message = message, + stackTrace = stackTrace, + cause = cause?.toModel(), + serializedExceptionBytes = try { + ByteArrayOutputStream().use { byteOutputStream -> + val objectOutputStream = ObjectOutputStream(byteOutputStream) + objectOutputStream.writeObject(this) + objectOutputStream.flush() + byteOutputStream.toByteArray() + } + } catch (e: Throwable) { + if (loggedUnserializableExceptionClassIds.add(this::class.java.id)) { + logger.warn { "Failed to serialize ${this::class.java.id} to bytes, cause: $e" } + logger.warn { "Constructing ThrowableModel with serializedExceptionBytes = null" } + } + null + } + ) + kryo.writeObject(output, throwable?.toModel()) + } + + override fun read(kryo: Kryo, input: Input, type: Class): Throwable? { + fun ThrowableModel.toThrowable(): Throwable { + this.serializedExceptionBytes?.let { bytes -> + try { + return@toThrowable ByteArrayInputStream(bytes).use { byteInputStream -> + val objectInputStream = IgnoringUidWrappingObjectInputStream(byteInputStream, kryo.classLoader) + objectInputStream.readObject() as Throwable + } + } catch (e: Throwable) { + if (loggedUnserializableExceptionClassIds.add(this.classId)) { + logger.warn { "Failed to deserialize ${this.classId} from bytes, cause: $e" } + logger.warn { "Falling back to constructing throwable instance from ThrowableModel" } + } + } + } + + val cause = cause?.toThrowable() + + val messageCauseConstructor = runCatching { classId.jClass.getConstructor(String::class.java, Throwable::class.java) }.getOrNull() + val causeOnlyConstructor = runCatching { classId.jClass.getConstructor(Throwable::class.java) }.getOrNull() + val messageOnlyConstructor = runCatching { classId.jClass.getConstructor(String::class.java) }.getOrNull() + + val throwableFromConstructor = runCatching { + when { + messageCauseConstructor != null && message != null && cause != null -> + messageCauseConstructor.newInstance(message, cause) + + causeOnlyConstructor != null && cause != null -> causeOnlyConstructor.newInstance(cause) + messageOnlyConstructor != null && message != null -> messageOnlyConstructor.newInstance(message) + else -> null + } + }.getOrNull() as Throwable? + + return (throwableFromConstructor ?: when { + RuntimeException::class.java.isAssignableFrom(classId.jClass) -> RuntimeException(message, cause) + Error::class.java.isAssignableFrom(classId.jClass) -> Error(message, cause) + else -> Exception(message, cause) + }).also { + it.stackTrace = stackTrace + } + } + + return kryo.readObject(input, ThrowableModel::class.java)?.toThrowable() + } +} + +class IgnoringUidWrappingObjectInputStream(iss : InputStream?, private val classLoader: ClassLoader) : ObjectInputStream(iss) { + override fun resolveClass(type: ObjectStreamClass): Class<*>? { + return try { + Class.forName(type.name, false, classLoader) + } catch (ex: ClassNotFoundException) { + try { + return Kryo::class.java.classLoader.loadClass(type.name) + } catch (e: ClassNotFoundException) { + try { + return super.resolveClass(type); + } catch (e: ClassNotFoundException) { + throw KryoException("Class not found: " + type.name, e) + } + } + } + } + + // This overriding allows to ignore serialVersionUID during deserialization. + // For more info, see https://stackoverflow.com/a/1816711 + override fun readClassDescriptor(): ObjectStreamClass { + var resultClassDescriptor = super.readClassDescriptor() // initially streams descriptor + + // the class in the local JVM that this descriptor represents. + val localClass: Class<*> = try { + classLoader.loadClass(resultClassDescriptor.name) + } catch (e: ClassNotFoundException) { + return resultClassDescriptor + } + + val localClassDescriptor = ObjectStreamClass.lookup(localClass) ?: return resultClassDescriptor + + // only if class implements serializable + val localSUID = localClassDescriptor.serialVersionUID + val streamSUID = resultClassDescriptor.serialVersionUID + if (streamSUID != localSUID) { // check for serialVersionUID mismatch. + resultClassDescriptor = localClassDescriptor // Use local class descriptor for deserialization + } + + return resultClassDescriptor + } +} diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/util/SootUtil.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/util/SootUtil.kt new file mode 100644 index 0000000000..9817e5f921 --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/util/SootUtil.kt @@ -0,0 +1,25 @@ +package org.utbot.framework.util + +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.classId +import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.constructorId +import org.utbot.framework.plugin.api.util.methodId +import soot.SootMethod + +/** + * Gets method or constructor id of SootMethod. + */ +val SootMethod.executableId: ExecutableId + get() = when { + isConstructor -> constructorId( + classId = declaringClass.id, + arguments = parameterTypes.map { it.classId }.toTypedArray() + ) + else -> methodId( + classId = declaringClass.id, + name = name, + returnType = returnType.classId, + arguments = parameterTypes.map { it.classId }.toTypedArray() + ) + } diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/utils/UtilMethodProvider.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/utils/UtilMethodProvider.kt new file mode 100644 index 0000000000..dbe881be8f --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/utils/UtilMethodProvider.kt @@ -0,0 +1,258 @@ +package org.utbot.framework.utils + +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.BuiltinConstructorId +import org.utbot.framework.plugin.api.BuiltinMethodId +import org.utbot.framework.plugin.api.CgClassId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.arrayTypeOf +import org.utbot.framework.plugin.api.util.baseStreamClassId +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.builtinConstructorId +import org.utbot.framework.plugin.api.util.classClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.objectArrayClassId +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.plugin.api.util.voidClassId +import sun.misc.Unsafe +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType +import java.lang.reflect.Method + +/** + * Set of ids of all possible util methods for a given class. + * + * The class may actually not have some of these methods if they + * are not required in the process of code generation (this is the case for [TestClassUtilMethodProvider]). + */ +abstract class UtilMethodProvider(val utilClassId: ClassId) { + val utilMethodIds: Set + get() = setOf( + getUnsafeInstanceMethodId, + createInstanceMethodId, + createArrayMethodId, + setFieldMethodId, + setStaticFieldMethodId, + getFieldValueMethodId, + getStaticFieldValueMethodId, + getEnumConstantByNameMethodId, + deepEqualsMethodId, + arraysDeepEqualsMethodId, + iterablesDeepEqualsMethodId, + streamsDeepEqualsMethodId, + mapsDeepEqualsMethodId, + hasCustomEqualsMethodId, + getArrayLengthMethodId, + consumeBaseStreamMethodId, + buildStaticLambdaMethodId, + buildLambdaMethodId, + getLookupInMethodId, + getLambdaCapturedArgumentTypesMethodId, + getLambdaCapturedArgumentValuesMethodId, + getInstantiatedMethodTypeMethodId, + getLambdaMethodMethodId, + getSingleAbstractMethodMethodId + ) + + val getUnsafeInstanceMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getUnsafeInstance", + returnType = Unsafe::class.id, + ) + + /** + * Method that creates instance using Unsafe + */ + val createInstanceMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "createInstance", + returnType = CgClassId(objectClassId, isNullable = true), + arguments = arrayOf(stringClassId) + ) + + val createArrayMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "createArray", + returnType = Array::class.id, + arguments = arrayOf(stringClassId, intClassId, Array::class.id) + ) + + val setFieldMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "setField", + returnType = voidClassId, + arguments = arrayOf(objectClassId, stringClassId, stringClassId, objectClassId) + ) + + val setStaticFieldMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "setStaticField", + returnType = voidClassId, + arguments = arrayOf(Class::class.id, stringClassId, objectClassId) + ) + + val getFieldValueMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getFieldValue", + returnType = objectClassId, + arguments = arrayOf(objectClassId, stringClassId, stringClassId) + ) + + val getStaticFieldValueMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getStaticFieldValue", + returnType = objectClassId, + arguments = arrayOf(Class::class.id, stringClassId) + ) + + val getEnumConstantByNameMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getEnumConstantByName", + returnType = objectClassId, + arguments = arrayOf(Class::class.id, stringClassId) + ) + + val deepEqualsMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "deepEquals", + returnType = booleanClassId, + arguments = arrayOf(objectClassId, objectClassId) + ) + + val arraysDeepEqualsMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "arraysDeepEquals", + returnType = booleanClassId, + arguments = arrayOf(objectClassId, objectClassId) + ) + + val iterablesDeepEqualsMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "iterablesDeepEquals", + returnType = booleanClassId, + arguments = arrayOf(java.lang.Iterable::class.id, java.lang.Iterable::class.id) + ) + + val streamsDeepEqualsMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "streamsDeepEquals", + returnType = booleanClassId, + arguments = arrayOf(java.util.stream.BaseStream::class.id, java.util.stream.BaseStream::class.id) + ) + + val mapsDeepEqualsMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "mapsDeepEquals", + returnType = booleanClassId, + arguments = arrayOf(java.util.Map::class.id, java.util.Map::class.id) + ) + + val hasCustomEqualsMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "hasCustomEquals", + returnType = booleanClassId, + arguments = arrayOf(Class::class.id) + ) + + val getArrayLengthMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getArrayLength", + returnType = intClassId, + arguments = arrayOf(objectClassId) + ) + + val consumeBaseStreamMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "consumeBaseStream", + returnType = voidClassId, + arguments = arrayOf(baseStreamClassId) + ) + + val buildStaticLambdaMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "buildStaticLambda", + returnType = objectClassId, + arguments = arrayOf( + classClassId, + classClassId, + stringClassId, + arrayTypeOf(capturedArgumentClassId) + ) + ) + + val buildLambdaMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "buildLambda", + returnType = objectClassId, + arguments = arrayOf( + classClassId, + classClassId, + stringClassId, + objectClassId, + arrayTypeOf(capturedArgumentClassId) + ) + ) + + val getLookupInMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getLookupIn", + returnType = MethodHandles.Lookup::class.id, + arguments = arrayOf(classClassId) + ) + + val getLambdaCapturedArgumentTypesMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getLambdaCapturedArgumentTypes", + returnType = arrayTypeOf(classClassId), + arguments = arrayOf(arrayTypeOf(capturedArgumentClassId)) + ) + + val getLambdaCapturedArgumentValuesMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getLambdaCapturedArgumentValues", + returnType = objectArrayClassId, + arguments = arrayOf(arrayTypeOf(capturedArgumentClassId)) + ) + + val getInstantiatedMethodTypeMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getInstantiatedMethodType", + returnType = MethodType::class.id, + arguments = arrayOf(Method::class.id, arrayTypeOf(classClassId)) + ) + + val getLambdaMethodMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getLambdaMethod", + returnType = Method::class.id, + arguments = arrayOf(classClassId, stringClassId) + ) + + val getSingleAbstractMethodMethodId: MethodId + get() = utilClassId.utilMethodId( + name = "getSingleAbstractMethod", + returnType = java.lang.reflect.Method::class.id, + arguments = arrayOf(classClassId) + ) + + val capturedArgumentClassId: BuiltinClassId + get() = BuiltinClassId( + canonicalName = "${utilClassId.name}.CapturedArgument", + simpleName = "CapturedArgument" + ) + + val capturedArgumentConstructorId: BuiltinConstructorId + get() = builtinConstructorId(capturedArgumentClassId, classClassId, objectClassId) +} + +internal fun ClassId.utilMethodId( + name: String, + returnType: ClassId, + vararg arguments: ClassId, + // usually util methods are static, so this argument is true by default + isStatic: Boolean = true +): MethodId = + BuiltinMethodId(this, name, returnType, arguments.toList(), isStatic = isStatic) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/testcheckers/MatcherUtils.kt b/utbot-framework-api/src/main/kotlin/org/utbot/testcheckers/MatcherUtils.kt new file mode 100644 index 0000000000..3e94b92c2e --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/testcheckers/MatcherUtils.kt @@ -0,0 +1,12 @@ +package org.utbot.testcheckers + +fun ge(count: Int) = ExecutionsNumberMatcher("ge $count") { it >= count } + +fun eq(count: Int) = ExecutionsNumberMatcher("eq $count") { it == count } + +fun between(bounds: IntRange) = ExecutionsNumberMatcher("$bounds") { it in bounds } + +class ExecutionsNumberMatcher(private val description: String, private val cmp: (Int) -> Boolean) { + operator fun invoke(x: Int) = cmp(x) + override fun toString() = description +} \ No newline at end of file diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/testcheckers/SettingsModificators.kt b/utbot-framework-api/src/main/kotlin/org/utbot/testcheckers/SettingsModificators.kt new file mode 100644 index 0000000000..ae571e273b --- /dev/null +++ b/utbot-framework-api/src/main/kotlin/org/utbot/testcheckers/SettingsModificators.kt @@ -0,0 +1,164 @@ +package org.utbot.testcheckers + +import org.utbot.framework.PathSelectorType +import org.utbot.framework.TestSelectionStrategyType +import org.utbot.framework.UtSettings + +inline fun withFeaturePath(featurePath: String, block: () -> T): T { + val prevFeaturePath = UtSettings.featurePath + val prevEnableFeatureProcess = UtSettings.enableFeatureProcess + + UtSettings.featurePath = featurePath + UtSettings.enableFeatureProcess = true + + try { + return block() + } finally { + UtSettings.featurePath = prevFeaturePath + UtSettings.enableFeatureProcess = prevEnableFeatureProcess + } +} + +inline fun withUsingReflectionForMaximizingCoverage(maximizeCoverage: Boolean, block: () -> T): T { + val prev = UtSettings.maximizeCoverageUsingReflection + UtSettings.maximizeCoverageUsingReflection = maximizeCoverage + try { + return block() + } finally { + UtSettings.maximizeCoverageUsingReflection = prev + } +} + +inline fun withPathSelectorType(pathSelectorType: PathSelectorType, block: () -> T): T { + val prev = UtSettings.pathSelectorType + UtSettings.pathSelectorType = pathSelectorType + try { + return block() + } finally { + UtSettings.pathSelectorType = prev + } +} + +inline fun withModelPath(modelPath: String, block: () -> T): T { + val prev = UtSettings.modelPath + UtSettings.modelPath = modelPath + try { + return block() + } finally { + UtSettings.modelPath = prev + } +} + +inline fun withTreatingOverflowAsError(block: () -> T): T { + val prev = UtSettings.treatOverflowAsError + UtSettings.treatOverflowAsError = true + try { + return block() + } finally { + UtSettings.treatOverflowAsError = prev + } +} + +inline fun withoutThrowTaintErrorForEachMarkSeparately(block: () -> T): T { + val prev = UtSettings.throwTaintErrorForEachMarkSeparately + UtSettings.throwTaintErrorForEachMarkSeparately = false + try { + return block() + } finally { + UtSettings.throwTaintErrorForEachMarkSeparately = prev + } +} + +inline fun withPushingStateFromPathSelectorForConcrete(block: () -> T): T { + val prev = UtSettings.saveRemainingStatesForConcreteExecution + UtSettings.saveRemainingStatesForConcreteExecution = true + try { + return block() + } finally { + UtSettings.saveRemainingStatesForConcreteExecution = prev + } +} + +inline fun withoutSubstituteStaticsWithSymbolicVariable(block: () -> T) { + val substituteStaticsWithSymbolicVariable = UtSettings.substituteStaticsWithSymbolicVariable + UtSettings.substituteStaticsWithSymbolicVariable = false + try { + block() + } finally { + UtSettings.substituteStaticsWithSymbolicVariable = substituteStaticsWithSymbolicVariable + } +} + +inline fun withoutMinimization(block: () -> T): T { + val prev = UtSettings.testMinimizationStrategyType + UtSettings.testMinimizationStrategyType = TestSelectionStrategyType.DO_NOT_MINIMIZE_STRATEGY + try { + return block() + } finally { + UtSettings.testMinimizationStrategyType = prev + } +} + +inline fun withSolverTimeoutInMillis(timeoutInMillis: Int, block: () -> T): T { + val prev = UtSettings.checkSolverTimeoutMillis + UtSettings.checkSolverTimeoutMillis = timeoutInMillis + try { + return block() + } finally { + UtSettings.checkSolverTimeoutMillis = prev + } +} + +inline fun withoutConcrete(block: () -> T): T { + val prev = UtSettings.useConcreteExecution + UtSettings.useConcreteExecution = false + try { + return block() + } finally { + UtSettings.useConcreteExecution = prev + } +} + +inline fun withProcessingClinitSections(value: Boolean, block: () -> T): T { + val prev = UtSettings.enableClinitSectionsAnalysis + UtSettings.enableClinitSectionsAnalysis = value + try { + return block() + } finally { + UtSettings.enableClinitSectionsAnalysis = prev + } +} + +inline fun withProcessingAllClinitSectionsConcretely(value: Boolean, block: () -> T): T { + val prev = UtSettings.processAllClinitSectionsConcretely + UtSettings.processAllClinitSectionsConcretely = value + try { + return block() + } finally { + UtSettings.processAllClinitSectionsConcretely = prev + } +} + + +/** + * Run [block] with disabled sandbox in the concrete executor + */ +inline fun withoutSandbox(block: () -> T): T { + val prev = UtSettings.useSandbox + UtSettings.useSandbox = false + try { + return block() + } finally { + UtSettings.useSandbox = prev + } +} + +inline fun withPathSelectorStepsLimit(stepsLimit: Int, block: () -> T): T { + val prev = UtSettings.pathSelectorStepsLimit + UtSettings.pathSelectorStepsLimit = stepsLimit + try { + return block() + } finally { + UtSettings.pathSelectorStepsLimit = prev + } +} diff --git a/utbot-framework-api/src/test/kotlin/org/utbot/framework/UtSettingsTest.kt b/utbot-framework-api/src/test/kotlin/org/utbot/framework/UtSettingsTest.kt new file mode 100644 index 0000000000..a05c3cab21 --- /dev/null +++ b/utbot-framework-api/src/test/kotlin/org/utbot/framework/UtSettingsTest.kt @@ -0,0 +1,24 @@ +package org.utbot.framework + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.assertEquals + +internal class UtSettingsTest { + @Test + fun testMaxTestFileSize() { + assertEquals(1, UtSettings.parseFileSize("1")) + assertEquals(12, UtSettings.parseFileSize("12")) + assertEquals(123, UtSettings.parseFileSize("123")) + assertEquals(30000, UtSettings.parseFileSize("30K")) + assertEquals(30000, UtSettings.parseFileSize("30Kb")) + assertEquals(30000, UtSettings.parseFileSize("30kB")) + assertEquals(30000, UtSettings.parseFileSize("30kb")) + assertEquals(7000000, UtSettings.parseFileSize("7MB")) + assertEquals(7000000, UtSettings.parseFileSize("7Mb")) + assertEquals(7000000, UtSettings.parseFileSize("7M")) + assertEquals(7, UtSettings.parseFileSize("7abc")) + assertEquals(UtSettings.DEFAULT_MAX_FILE_SIZE, UtSettings.parseFileSize("qwerty")) + assertEquals(UtSettings.DEFAULT_MAX_FILE_SIZE, UtSettings.parseFileSize("MB")) + assertEquals(UtSettings.DEFAULT_MAX_FILE_SIZE, UtSettings.parseFileSize("1000000")) + } +} \ No newline at end of file diff --git a/utbot-framework-test/build.gradle b/utbot-framework-test/build.gradle new file mode 100644 index 0000000000..5c4bc02926 --- /dev/null +++ b/utbot-framework-test/build.gradle @@ -0,0 +1,56 @@ +dependencies { + api project(':utbot-framework-api') + testImplementation project(':utbot-testing') + + api project(':utbot-java-fuzzing') + api project(':utbot-instrumentation') + api project(':utbot-summary') + + implementation(project(":utbot-framework")) + testImplementation project(':utbot-sample') + testImplementation project(":utbot-framework").sourceSets.test.output + testImplementation project(":utbot-core").sourceSets.test.output + + implementation("org.unittestbot.soot:soot-utbot-fork:${sootVersion}") { + exclude group:'com.google.guava', module:'guava' + } + + implementation group: 'com.charleskorn.kaml', name: 'kaml', version: kamlVersion + implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: jacksonVersion + implementation group: 'com.github.curious-odd-man', name: 'rgxgen', version: rgxgenVersion + implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: log4j2Version + implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlinLoggingVersion + implementation group: 'org.jacoco', name: 'org.jacoco.report', version: jacocoVersion + implementation group: 'org.apache.commons', name: 'commons-text', version: apacheCommonsTextVersion + // we need this for construction mocks from composite models + implementation group: 'org.mockito', name: 'mockito-core', version: mockitoVersion + + // To use JUnit4, comment out JUnit5 and uncomment JUnit4 dependencies here. Please also check "test" section + // testImplementation group: 'junit', name: 'junit', version: '4.13.1' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.8.1' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.1' + + // used for testing code generation + testImplementation group: 'commons-io', name: 'commons-io', version: commonsIoVersion + testImplementation group: 'junit', name: 'junit', version: junit4Version + testImplementation group: 'org.junit.platform', name: 'junit-platform-console-standalone', version: junit4PlatformVersion + testImplementation group: 'org.antlr', name: 'antlr4', version: antlrVersion + testImplementation group: 'org.mockito', name: 'mockito-core', version: mockitoVersion + testImplementation group: 'org.mockito', name: 'mockito-inline', version: mockitoInlineVersion + testImplementation group: 'com.google.guava', name: 'guava', version: guavaVersion + + testImplementation group: 'org.mockito', name: 'mockito-inline', version: mockitoInlineVersion + testImplementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j2Version + + // TODO sbft-usvm-merge: UtBot engine expects `com.github.UnitTestBot.ksmt` here + implementation group: 'io.ksmt', name: 'ksmt-core', version: ksmtVersion + implementation group: 'io.ksmt', name: 'ksmt-z3', version: ksmtVersion +} + +// This is required to avoid conflict between SpringBoot standard logger and the logger of our project. +// See https://stackoverflow.com/a/28735604 for more details. +test { + if (System.getProperty('DEBUG', 'false') == 'true') { + jvmArgs '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=9009' + } +} diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/ArrayOfComplexArrays.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ArrayOfComplexArrays.java similarity index 78% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/ArrayOfComplexArrays.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/ArrayOfComplexArrays.java index 2feaaeac2a..6c3936004c 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/ArrayOfComplexArrays.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ArrayOfComplexArrays.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.arrays; +package org.utbot.examples.assemble; /** * A class with array of objects that are arrays of complex fields themselves. diff --git a/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ArrayOfPrimitiveArrays.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ArrayOfPrimitiveArrays.java new file mode 100644 index 0000000000..de7061c090 --- /dev/null +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ArrayOfPrimitiveArrays.java @@ -0,0 +1,8 @@ +package org.utbot.examples.assemble; + +/** + * A class with a two-dimensional array field. + */ +public class ArrayOfPrimitiveArrays { + public int[][] array; +} diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/AssembleTestUtils.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/AssembleTestUtils.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/AssembleTestUtils.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/AssembleTestUtils.java diff --git a/utbot-framework-test/src/main/java/org/utbot/examples/assemble/AssignedArray.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/AssignedArray.java new file mode 100644 index 0000000000..701f90f318 --- /dev/null +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/AssignedArray.java @@ -0,0 +1,8 @@ +package org.utbot.examples.assemble; + +/** + * A class with an array with a default value. + */ +public class AssignedArray { + public int[] array = new int[10]; +} diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/ComplexArray.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ComplexArray.java similarity index 82% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/ComplexArray.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/ComplexArray.java index 7f60a0377f..f1634add0a 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/ComplexArray.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ComplexArray.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.arrays; +package org.utbot.examples.assemble; import org.utbot.examples.assemble.PrimitiveFields; diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/ComplexConstructor.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ComplexConstructor.java similarity index 85% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/ComplexConstructor.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/ComplexConstructor.java index c6e45ff0ea..27f68f07d7 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/ComplexConstructor.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ComplexConstructor.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.constructors; +package org.utbot.examples.assemble; /** * A class without default constructor and with complex one. diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/ComplexConstructorWithSetter.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ComplexConstructorWithSetter.java similarity index 88% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/ComplexConstructorWithSetter.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/ComplexConstructorWithSetter.java index 72111a23e6..b4b83c4137 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/ComplexConstructorWithSetter.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ComplexConstructorWithSetter.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.constructors; +package org.utbot.examples.assemble; /** * A class without default constructor and with complex one, diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/ComplexField.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ComplexField.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/ComplexField.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/ComplexField.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/ConstructorModifyingStatic.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ConstructorModifyingStatic.java similarity index 76% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/ConstructorModifyingStatic.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/ConstructorModifyingStatic.java index eba2e01328..00a567ab52 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/ConstructorModifyingStatic.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ConstructorModifyingStatic.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.constructors; +package org.utbot.examples.assemble; public class ConstructorModifyingStatic { diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultField.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultField.java similarity index 75% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultField.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultField.java index 396f262b5f..09ef14422a 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultField.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultField.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.defaults; +package org.utbot.examples.assemble; /** * A class with a field with default value that is not a default value of type. diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultFieldModifiedInConstructor.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultFieldModifiedInConstructor.java similarity index 75% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultFieldModifiedInConstructor.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultFieldModifiedInConstructor.java index d96a23d005..d11d42fc14 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultFieldModifiedInConstructor.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultFieldModifiedInConstructor.java @@ -1,7 +1,7 @@ -package org.utbot.examples.assemble.defaults; +package org.utbot.examples.assemble; public class DefaultFieldModifiedInConstructor { - int z; + public int z; @SuppressWarnings("Unused") DefaultFieldModifiedInConstructor(int z_) { diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultFieldWithDirectAccessor.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultFieldWithDirectAccessor.java similarity index 77% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultFieldWithDirectAccessor.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultFieldWithDirectAccessor.java index e6ad1e2f92..160b116d0b 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultFieldWithDirectAccessor.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultFieldWithDirectAccessor.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.defaults; +package org.utbot.examples.assemble; /** * A class with a field with default value that is not a default value of type. diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultFieldWithSetter.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultFieldWithSetter.java similarity index 82% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultFieldWithSetter.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultFieldWithSetter.java index 65896c382a..d1ec15f20c 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultFieldWithSetter.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultFieldWithSetter.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.defaults; +package org.utbot.examples.assemble; /** * A class with a field with setter default value that is not a default value of type. diff --git a/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultPackagePrivateField.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultPackagePrivateField.java new file mode 100644 index 0000000000..43d9b7ad25 --- /dev/null +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DefaultPackagePrivateField.java @@ -0,0 +1,9 @@ +package org.utbot.examples.assemble; + +/** + * Need to be located at the same package as [AssembleTestUtils] + * because requires a setter for package-private field. + */ +public class DefaultPackagePrivateField { + int z = 10; +} diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/DirectAccess.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DirectAccess.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/DirectAccess.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/DirectAccess.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/DirectAccessAndSetter.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DirectAccessAndSetter.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/DirectAccessAndSetter.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/DirectAccessAndSetter.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/DirectAccessFinal.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/DirectAccessFinal.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/DirectAccessFinal.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/DirectAccessFinal.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/InheritComplexConstructor.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/InheritComplexConstructor.java similarity index 87% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/InheritComplexConstructor.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/InheritComplexConstructor.java index 417c686742..c3521d8e04 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/InheritComplexConstructor.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/InheritComplexConstructor.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.constructors; +package org.utbot.examples.assemble; /** * A class with a primitive constructor that inherits a complex constructor. diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/InheritPrimitiveConstructor.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/InheritPrimitiveConstructor.java similarity index 87% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/InheritPrimitiveConstructor.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/InheritPrimitiveConstructor.java index 835e5e991c..c5fc633d45 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/InheritPrimitiveConstructor.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/InheritPrimitiveConstructor.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.constructors; +package org.utbot.examples.assemble; /** * A class with a primitive constructor that inherits another primitive constructor. diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/InheritedField.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/InheritedField.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/InheritedField.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/InheritedField.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/ListItem.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/ListItem.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/ListItem.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/ListItem.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/NoModifier.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/NoModifier.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/NoModifier.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/NoModifier.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/PackagePrivateFields.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PackagePrivateFields.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/PackagePrivateFields.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/PackagePrivateFields.java diff --git a/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrimitiveArray.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrimitiveArray.java new file mode 100644 index 0000000000..a07b5aa7a4 --- /dev/null +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrimitiveArray.java @@ -0,0 +1,8 @@ +package org.utbot.examples.assemble; + +/** + * A class with an array with elements of primitive type. + */ +public class PrimitiveArray { + public int[] array; +} diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/PrimitiveConstructor.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrimitiveConstructor.java similarity index 85% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/PrimitiveConstructor.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrimitiveConstructor.java index 4750277578..9d7c3595e4 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/PrimitiveConstructor.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrimitiveConstructor.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.constructors; +package org.utbot.examples.assemble; /** * A class without default constructor and with primitive one. diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/PrimitiveConstructorWithDefaultField.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrimitiveConstructorWithDefaultField.java similarity index 84% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/PrimitiveConstructorWithDefaultField.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrimitiveConstructorWithDefaultField.java index 34d257cee4..b39adb250f 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/PrimitiveConstructorWithDefaultField.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrimitiveConstructorWithDefaultField.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.constructors; +package org.utbot.examples.assemble; /** * A class without default constructor and with another one with default field diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/PrimitiveFields.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrimitiveFields.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/PrimitiveFields.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrimitiveFields.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/PrivateConstructor.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrivateConstructor.java similarity index 76% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/PrivateConstructor.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrivateConstructor.java index 4f0d1438f4..2e4d765c92 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/PrivateConstructor.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PrivateConstructor.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.constructors; +package org.utbot.examples.assemble; /** * A class with private constructor. diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/PseudoComplexConstructor.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PseudoComplexConstructor.java similarity index 84% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/PseudoComplexConstructor.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/PseudoComplexConstructor.java index f0a14772e3..4569566bc5 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/constructors/PseudoComplexConstructor.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/PseudoComplexConstructor.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.constructors; +package org.utbot.examples.assemble; /** * A class with a constructor that seems to be complex diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/statics/StaticField.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/StaticField.java similarity index 86% rename from utbot-framework/src/test/java/org/utbot/examples/assemble/statics/StaticField.java rename to utbot-framework-test/src/main/java/org/utbot/examples/assemble/StaticField.java index 4019957f5d..1653a6d3fd 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/statics/StaticField.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/StaticField.java @@ -1,4 +1,4 @@ -package org.utbot.examples.assemble.statics; +package org.utbot.examples.assemble; /** * A class with primitive constructor and static field diff --git a/utbot-framework-test/src/main/java/org/utbot/examples/assemble/another/MethodUnderTest.java b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/another/MethodUnderTest.java new file mode 100644 index 0000000000..6848f644dc --- /dev/null +++ b/utbot-framework-test/src/main/java/org/utbot/examples/assemble/another/MethodUnderTest.java @@ -0,0 +1,10 @@ +package org.utbot.examples.assemble.another; + +/** + * A test class with fake method under test. + */ +public class MethodUnderTest { + + public void methodUnderTest() { + } +} diff --git a/utbot-framework-test/src/main/java/org/utbot/examples/manual/KotlinWrappers.kt b/utbot-framework-test/src/main/java/org/utbot/examples/manual/KotlinWrappers.kt new file mode 100644 index 0000000000..72c99d7446 --- /dev/null +++ b/utbot-framework-test/src/main/java/org/utbot/examples/manual/KotlinWrappers.kt @@ -0,0 +1,22 @@ +package org.utbot.examples.manual + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtPrimitiveModel + +fun fields( + classId: ClassId, + vararg fields: Pair +): MutableMap { + return fields + .associate { + val fieldId = FieldId(classId, it.first) + val fieldValue = when (val value = it.second) { + is UtModel -> value + else -> UtPrimitiveModel(value) + } + fieldId to fieldValue + } + .toMutableMap() +} \ No newline at end of file diff --git a/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/ArrayOfComplexArraysExample.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/ArrayOfComplexArraysExample.java new file mode 100644 index 0000000000..12d8240f08 --- /dev/null +++ b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/ArrayOfComplexArraysExample.java @@ -0,0 +1,9 @@ +package org.utbot.examples.manual.examples; + +import org.utbot.examples.assemble.ArrayOfComplexArrays; + +public class ArrayOfComplexArraysExample { + public int getValue(ArrayOfComplexArrays a) { + return a.array[0].array[0].getB(); + } +} diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/ArrayOfPrimitiveArraysExample.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/ArrayOfPrimitiveArraysExample.java similarity index 75% rename from utbot-framework/src/test/java/org/utbot/examples/manual/examples/ArrayOfPrimitiveArraysExample.java rename to utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/ArrayOfPrimitiveArraysExample.java index 0ca098705f..189a25de6e 100644 --- a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/ArrayOfPrimitiveArraysExample.java +++ b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/ArrayOfPrimitiveArraysExample.java @@ -1,6 +1,6 @@ package org.utbot.examples.manual.examples; -import org.utbot.examples.assemble.arrays.ArrayOfPrimitiveArrays; +import org.utbot.examples.assemble.ArrayOfPrimitiveArrays; public class ArrayOfPrimitiveArraysExample { int assign10(ArrayOfPrimitiveArrays a) { diff --git a/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/AssignedArrayExample.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/AssignedArrayExample.java new file mode 100644 index 0000000000..45e142df56 --- /dev/null +++ b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/AssignedArrayExample.java @@ -0,0 +1,9 @@ +package org.utbot.examples.manual.examples; + +import org.utbot.examples.assemble.AssignedArray; + +public class AssignedArrayExample { + public void foo(AssignedArray aa) { + aa.array[9] = 42; + } +} diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/ClassRefExample.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/ClassRefExample.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/manual/examples/ClassRefExample.java rename to utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/ClassRefExample.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/ComplexArraysExample.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/ComplexArraysExample.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/manual/examples/ComplexArraysExample.java rename to utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/ComplexArraysExample.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/DirectAccessExample.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/DirectAccessExample.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/manual/examples/DirectAccessExample.java rename to utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/DirectAccessExample.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/MultiMethodExample.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/MultiMethodExample.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/manual/examples/MultiMethodExample.java rename to utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/MultiMethodExample.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/ProvidedExample.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/ProvidedExample.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/manual/examples/ProvidedExample.java rename to utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/ProvidedExample.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/StringSwitchExample.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/StringSwitchExample.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/manual/examples/StringSwitchExample.java rename to utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/StringSwitchExample.java diff --git a/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/Trivial.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/Trivial.java new file mode 100644 index 0000000000..d5aa823325 --- /dev/null +++ b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/Trivial.java @@ -0,0 +1,11 @@ +package org.utbot.examples.manual.examples; + +public class Trivial { + public int aMethod(int a) { + String s = "a"; + if (a > 1) { + return s.length(); + } + return s.length() + 1; + } +} diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/customer/A.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/customer/A.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/manual/examples/customer/A.java rename to utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/customer/A.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/customer/B.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/customer/B.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/manual/examples/customer/B.java rename to utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/customer/B.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/customer/C.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/customer/C.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/manual/examples/customer/C.java rename to utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/customer/C.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/customer/Demo9.java b/utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/customer/Demo9.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/manual/examples/customer/Demo9.java rename to utbot-framework-test/src/main/java/org/utbot/examples/manual/examples/customer/Demo9.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/ConstructorsAndSetters.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/ConstructorsAndSetters.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/ConstructorsAndSetters.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/ConstructorsAndSetters.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/DefaultField.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/DefaultField.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/DefaultField.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/DefaultField.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/InvokeInAssignment.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/InvokeInAssignment.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/InvokeInAssignment.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/InvokeInAssignment.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/NoFields.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/NoFields.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/NoFields.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/NoFields.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/NoMethods.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/NoMethods.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/NoMethods.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/NoMethods.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/PrimitiveModifications.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/PrimitiveModifications.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/PrimitiveModifications.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/PrimitiveModifications.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/RecursiveAndCrossCalls.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/RecursiveAndCrossCalls.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/RecursiveAndCrossCalls.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/RecursiveAndCrossCalls.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/StronglyConnectedComponent.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/StronglyConnectedComponent.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/StronglyConnectedComponent.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/StronglyConnectedComponent.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/StronglyConnectedComponents.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/StronglyConnectedComponents.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/StronglyConnectedComponents.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/StronglyConnectedComponents.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/coupling/ClassA.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/coupling/ClassA.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/coupling/ClassA.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/coupling/ClassA.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/coupling/ClassB.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/coupling/ClassB.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/coupling/ClassB.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/coupling/ClassB.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/hierarchy/BaseModifications.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/hierarchy/BaseModifications.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/hierarchy/BaseModifications.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/hierarchy/BaseModifications.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/hierarchy/InheritedModifications.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/hierarchy/InheritedModifications.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/hierarchy/InheritedModifications.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/hierarchy/InheritedModifications.java diff --git a/utbot-framework/src/test/java/org/utbot/examples/modificators/hierarchy/InterfaceModifications.java b/utbot-framework-test/src/main/java/org/utbot/examples/modificators/hierarchy/InterfaceModifications.java similarity index 100% rename from utbot-framework/src/test/java/org/utbot/examples/modificators/hierarchy/InterfaceModifications.java rename to utbot-framework-test/src/main/java/org/utbot/examples/modificators/hierarchy/InterfaceModifications.java diff --git a/utbot-framework-test/src/main/java/org/utbot/examples/strings11/StringConcat.java b/utbot-framework-test/src/main/java/org/utbot/examples/strings11/StringConcat.java new file mode 100644 index 0000000000..78552f701d --- /dev/null +++ b/utbot-framework-test/src/main/java/org/utbot/examples/strings11/StringConcat.java @@ -0,0 +1,80 @@ +package org.utbot.examples.strings11; + +import org.utbot.api.mock.UtMock; + + +public class StringConcat { + public static class Test { + public int x; + + @Override + public String toString() { + if (x == 42) { + throw new IllegalArgumentException(); + } + return "x = " + x; + } + } + + String str; + public String concatArguments(String a, String b, String c) { + return a + b + c; + } + + public int concatWithConstants(String a) { + String res = '<' + a + '>'; + + if (res.equals("")) { + return 1; + } + + if (res.equals("")) { + return 2; + } + + if (a == null) { + return 3; + } + + return 4; + } + + public String concatWithPrimitives(String a) { + return a + '#' + 42 + 53.0; + } + + public String exceptionInToString(Test t) { + return "Test: " + t + "!"; + } + + public String concatWithField(String a) { + return a + str + '#'; + } + + public int concatWithPrimitiveWrappers(Integer b, char c) { + String res = "" + b + c; + + if (res.endsWith("42")) { + return 1; + } + return 2; + } + + public int sameConcat(String a, String b) { + UtMock.assume(a != null && b != null); + + String res1 = '!' + a + '#'; + String res2 = '!' + b + '#'; + + if (res1.equals(res2)) { + return 0; + } else { + return 1; + } + } + + public String concatStrangeSymbols() { + return "\u0000" + '#' + '\u0001' + "!\u0002" + "@\u0012\t"; + } +} + diff --git a/utbot-framework-test/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java b/utbot-framework-test/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java new file mode 100644 index 0000000000..c8e0213371 --- /dev/null +++ b/utbot-framework-test/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java @@ -0,0 +1,1163 @@ +package org.utbot.examples.manual; + +import kotlin.Pair; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.utbot.api.java.AbstractUtBotJavaApiTest; +import org.utbot.examples.assemble.DirectAccess; +import org.utbot.examples.assemble.PrimitiveFields; +import org.utbot.examples.assemble.ArrayOfComplexArrays; +import org.utbot.examples.assemble.ArrayOfPrimitiveArrays; +import org.utbot.examples.assemble.AssignedArray; +import org.utbot.examples.assemble.ComplexArray; +import org.utbot.examples.manual.examples.*; +import org.utbot.examples.manual.examples.customer.B; +import org.utbot.examples.manual.examples.customer.C; +import org.utbot.examples.manual.examples.customer.Demo9; +import org.utbot.external.api.TestMethodInfo; +import org.utbot.external.api.UnitTestBotLight; +import org.utbot.external.api.UtBotJavaApi; +import org.utbot.framework.codegen.domain.*; +import org.utbot.framework.context.simple.SimpleApplicationContext; +import org.utbot.framework.plugin.api.*; +import org.utbot.framework.plugin.services.JdkInfoDefaultProvider; +import org.utbot.framework.util.Snippet; +import org.utbot.framework.util.SootUtils; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.*; +import static org.utbot.external.api.UtModelFactoryKt.classIdForType; +import static org.utbot.framework.plugin.api.MockFramework.MOCKITO; +import static org.utbot.framework.plugin.api.util.IdUtilKt.*; +import static org.utbot.framework.util.TestUtilsKt.compileClassAndGetClassPath; +import static org.utbot.framework.util.TestUtilsKt.compileClassFile; + +/** + * Tests for UnitTestBot Java API (Examples at the same time) + */ +public class UtBotJavaApiTest extends AbstractUtBotJavaApiTest { + + + /** Uses {@link MultiMethodExample} as a class under test. Demonstrates how to gather information for multiple + * methods analysis and pass it to {@link UtBotJavaApi#generateTestSetsForMethods} in order to produce set + * of information needed for the test generation. After that shows how to use {@link UtBotJavaApi#generateTestCode} + * in order to generate tests code. + *
+ * The tests are generated with and without concrete execution. + *
+ * Note, that you can use the {@code Snippet} instance in order to evaluate the test generation result. + */ + @Test + public void testMultiMethodClass() { + + UtBotJavaApi.setStopConcreteExecutorOnExit(false); + + String classpath = getClassPath(MultiMethodExample.class); + String dependencyClassPath = getDependencyClassPath(); + + UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( + classIdForType(MultiMethodExample.class) + ); + + Method firstMethodUnderTest = getMethodByName(MultiMethodExample.class, "firstMethod"); + Method secondMethodUnderTest = getMethodByName(MultiMethodExample.class, "secondMethod"); + Method thirdMethodUnderTest = getMethodByName(MultiMethodExample.class, "thirdMethod", String.class); + + // To find test sets, you need only {@link Method} instances + // and some configuration + + List testSets = UtBotJavaApi.generateTestSetsForMethods( + Arrays.asList( + firstMethodUnderTest, + secondMethodUnderTest, + thirdMethodUnderTest + ), + MultiMethodExample.class, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + new SimpleApplicationContext() + ); + + String generationResult = UtBotJavaApi.generateTestCode( + emptyList(), + testSets, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + MultiMethodExample.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + MultiMethodExample.class.getPackage().getName(), + new SimpleApplicationContext() + ); + + TestMethodInfo firstTestMethodInfo = buildTestMethodInfo( + firstMethodUnderTest, + classUnderTestModel, + emptyList(), + Collections.emptyMap() + ); + + TestMethodInfo secondTestMethodInfo = buildTestMethodInfo( + firstMethodUnderTest, + classUnderTestModel, + emptyList(), + Collections.emptyMap() + ); + + TestMethodInfo thirdTestMethodInfo = buildTestMethodInfo( + thirdMethodUnderTest, + classUnderTestModel, + Collections.singletonList(new UtPrimitiveModel("some")), + Collections.emptyMap() + ); + + Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet1); + String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generateTestCode( + Arrays.asList( + firstTestMethodInfo, + secondTestMethodInfo, + thirdTestMethodInfo + ), + emptyList(), + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + MultiMethodExample.class, + new SimpleApplicationContext() + ); + + Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet2); + } + + /** Uses {@link ClassRefExample} as a class under test. Demonstrates how to specify custom package name for the + * generate tests. + *
+ * The tests are generated with and without concrete execution. + *
+ * Note, that you can use the {@code Snippet} instance in order to evaluate the test generation result. + */ + @Test + public void testCustomPackage() { + + UtBotJavaApi.setStopConcreteExecutorOnExit(false); + + String classpath = getClassPath(ClassRefExample.class); + String dependencyClassPath = getDependencyClassPath(); + + HashMap fields = new HashMap<>(); + fields.put("stringClass", modelFactory.produceClassRefModel(String.class)); + + UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( + classIdForType(ClassRefExample.class), + fields + ); + + UtClassRefModel classRefModel = modelFactory.produceClassRefModel(Class.class); + + Method methodUnderTest = getMethodByName( + ClassRefExample.class, + "assertInstance", + Class.class); + + List testSets = UtBotJavaApi.generateTestSetsForMethods( + Collections.singletonList(methodUnderTest), + ClassRefExample.class, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + new SimpleApplicationContext() + ); + + String generationResult = UtBotJavaApi.generateTestCode( + emptyList(), + testSets, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + ClassRefExample.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + "some.custom.name", + new SimpleApplicationContext() + ); + + Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet1); + + TestMethodInfo testMethodInfo = buildTestMethodInfo( + methodUnderTest, + classUnderTestModel, + Collections.singletonList(classRefModel), + Collections.emptyMap() + ); + + String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generateTestCode( + Collections.singletonList(testMethodInfo), + emptyList(), + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + ClassRefExample.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + "some.custom.name", + new SimpleApplicationContext() + ); + + Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet2); + } + + /** Uses {@link AssignedArrayExample} as a class under test. Demonstrates how to specify custom package name for the + * generate tests. + *
+ * The tests are generated with and without concrete execution. + *
+ * Note, that you can use the {@code Snippet} instance in order to evaluate the test generation result. + */ + @Test + public void testOnObjectWithAssignedArrayField() { + + UtBotJavaApi.setStopConcreteExecutorOnExit(false); + + String classpath = getClassPath(AssignedArray.class); + String dependencyClassPath = getDependencyClassPath(); + + ClassId classIdAssignedArray = classIdForType(AssignedArray.class); + + HashMap values = new HashMap<>(); + values.put(0, new UtPrimitiveModel(1)); + values.put(1, new UtPrimitiveModel(2)); + + UtArrayModel utArrayModel = modelFactory.produceArrayModel( + getIntArrayClassId(), + 10, + new UtPrimitiveModel(0), + values + ); + + UtCompositeModel compositeModel = modelFactory.produceCompositeModel( + classIdAssignedArray, + Collections.singletonMap("array", utArrayModel) + ); + + UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( + classIdForType(AssignedArrayExample.class) + ); + + Method methodUnderTest = getMethodByName( + AssignedArrayExample.class, + "foo", + AssignedArray.class + ); + + List testSets = UtBotJavaApi.generateTestSetsForMethods( + Collections.singletonList(methodUnderTest), + AssignedArrayExample.class, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + new SimpleApplicationContext() + ); + + String generationResult = UtBotJavaApi.generateTestCode( + emptyList(), + testSets, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + AssignedArrayExample.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet1); + + TestMethodInfo methodInfo = buildTestMethodInfo( + methodUnderTest, + classUnderTestModel, + Collections.singletonList(compositeModel), + Collections.emptyMap() + ); + + String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generateTestCode( + Collections.singletonList(methodInfo), + emptyList(), + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + AssignedArrayExample.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet2); + } + + /** Uses {@link DirectAccessExample} as a class under test. Demonstrates how to test objects with public fields. + *
+ * The tests are generated with and without concrete execution. + *
+ * Note, that you can use the {@code Snippet} instance in order to evaluate the test generation result. + */ + @Test + public void testObjectWithPublicFields() { + + UtBotJavaApi.setStopConcreteExecutorOnExit(false); + + String classpath = getClassPath(DirectAccessExample.class); + String dependencyClassPath = getDependencyClassPath(); + + ClassId testClassId = classIdForType(DirectAccess.class); + ClassId innerClassId = classIdForType(PrimitiveFields.class); + + HashMap primitiveFields = new HashMap<>(); + primitiveFields.put("a", new UtPrimitiveModel(2)); + primitiveFields.put("b", new UtPrimitiveModel(4)); + + HashMap fields = new HashMap<>(); + fields.put("a", new UtPrimitiveModel(2)); + fields.put("b", new UtPrimitiveModel(4)); + fields.put("s", + modelFactory.produceCompositeModel( + innerClassId, + primitiveFields, + Collections.emptyMap() + ) + ); + + UtCompositeModel compositeModel = modelFactory.produceCompositeModel( + testClassId, + fields, + Collections.emptyMap() + ); + + // This class does not contain any fields. Using overloads + UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( + classIdForType(DirectAccessExample.class) + ); + + Method methodUnderTest = getMethodByName( + DirectAccessExample.class, + "foo", + DirectAccess.class + ); + + List testSets = UtBotJavaApi.generateTestSetsForMethods( + Collections.singletonList(methodUnderTest), + DirectAccessExample.class, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + new SimpleApplicationContext() + ); + + String generationResult = UtBotJavaApi.generateTestCode( + emptyList(), + testSets, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + DirectAccessExample.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet1); + + TestMethodInfo methodInfo = buildTestMethodInfo( + methodUnderTest, + classUnderTestModel, + Collections.singletonList(compositeModel), + Collections.emptyMap() + ); + + String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generateTestCode( + Collections.singletonList(methodInfo), + emptyList(), + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + DirectAccessExample.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet2); + } + + /** Uses {@link ArrayOfPrimitiveArraysExample} as a class under test. Demonstrates how to test code that uses arrays of primitives + * inside arrays. + *
+ * The tests are generated with and without concrete execution. + *
+ * Note, that you can use the {@code Snippet} instance in order to evaluate the test generation result. + */ + @Test + public void testOnObjectWithArrayOfPrimitiveArrays() { + + UtBotJavaApi.setStopConcreteExecutorOnExit(false); + + String classpath = getClassPath(ArrayOfPrimitiveArraysExample.class); + String dependencyClassPath = getDependencyClassPath(); + + ClassId cidPrimitiveArrays = getIntArrayClassId(); + ClassId cidPrimitiveArraysOuter = new ClassId("[[I", cidPrimitiveArrays); + ClassId classIdArrayOfPrimitiveArraysClass = classIdForType(ArrayOfPrimitiveArrays.class); + ClassId cidArrayOfPrimitiveArraysTest = classIdForType(ArrayOfPrimitiveArraysExample.class); + + HashMap arrayParameters = new HashMap<>(); + arrayParameters.put(0, new UtPrimitiveModel(88)); + arrayParameters.put(1, new UtPrimitiveModel(42)); + + UtArrayModel innerArrayOfPrimitiveArrayModel = modelFactory.produceArrayModel( + cidPrimitiveArrays, + 2, + new UtPrimitiveModel(0), + arrayParameters + ); + + HashMap enclosingArrayParameters = new HashMap<>(); + enclosingArrayParameters.put(0, innerArrayOfPrimitiveArrayModel); + enclosingArrayParameters.put(1, innerArrayOfPrimitiveArrayModel); + + UtArrayModel enclosingArrayOfPrimitiveArrayModel = modelFactory.produceArrayModel( + cidPrimitiveArraysOuter, + 1, + new UtNullModel(getIntArrayClassId()), + enclosingArrayParameters + ); + + UtCompositeModel compositeModelArrayOfPrimitiveArrays = modelFactory.produceCompositeModel( + classIdArrayOfPrimitiveArraysClass, + Collections.singletonMap("array", enclosingArrayOfPrimitiveArrayModel) + ); + + UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( + cidArrayOfPrimitiveArraysTest + ); + + Method methodUnderTest = getMethodByName( + ArrayOfPrimitiveArraysExample.class, + "assign10", + ArrayOfPrimitiveArrays.class + ); + + List testSets = UtBotJavaApi.generateTestSetsForMethods( + Collections.singletonList(methodUnderTest), + ArrayOfPrimitiveArraysExample.class, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + new SimpleApplicationContext() + ); + + String generationResult = UtBotJavaApi.generateTestCode( + emptyList(), + testSets, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + ArrayOfPrimitiveArraysExample.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet = new Snippet(CodegenLanguage.JAVA, generationResult); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet); + + TestMethodInfo methodInfo = buildTestMethodInfo( + methodUnderTest, + classUnderTestModel, + Collections.singletonList(compositeModelArrayOfPrimitiveArrays), + Collections.emptyMap() + ); + + String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generateTestCode( + Collections.singletonList(methodInfo), + emptyList(), + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + ArrayOfPrimitiveArraysExample.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet2); + } + + /** Example provided by customers. + *
+ * The tests are generated with and without concrete execution. + *
+ * Note, that you can use the {@code Snippet} instance in order to evaluate the test generation result. + */ + @Test + public void testProvided3() { + + UtBotJavaApi.setStopConcreteExecutorOnExit(false); + + String classpath = getClassPath(ArrayOfComplexArraysExample.class); + String dependencyClassPath = getDependencyClassPath(); + + UtCompositeModel cClassModel = modelFactory. + produceCompositeModel( + classIdForType(C.class), + Collections.singletonMap("integer", new UtPrimitiveModel(1)) + ); + + UtCompositeModel bClassModel = modelFactory + .produceCompositeModel( + classIdForType(B.class), + Collections.singletonMap("c", cClassModel) + ); + + UtCompositeModel demo9Model = modelFactory. + produceCompositeModel( + classIdForType(Demo9.class), + Collections.singletonMap("b0", bClassModel) + ); + + Method methodUnderTest = getMethodByName( + Demo9.class, + "test", + B.class + ); + + + List testSets = UtBotJavaApi.generateTestSetsForMethods( + singletonList(methodUnderTest), + Demo9.class, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + new SimpleApplicationContext() + ); + + String generationResult = UtBotJavaApi.generateTestCode( + emptyList(), + testSets, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + Demo9.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet1); + + TestMethodInfo methodInfo = buildTestMethodInfo( + methodUnderTest, + demo9Model, + Collections.singletonList(bClassModel), + Collections.emptyMap() + ); + + + String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generateTestCode( + Collections.singletonList(methodInfo), + emptyList(), + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + Demo9.class, + new SimpleApplicationContext() + ); + + Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet2); + } + + /** Trivial example. + *
+ * The tests are generated with and without concrete execution. + *
+ * Note, that you can use the {@code Snippet} instance in order to evaluate the test generation result. + */ + @Test + public void testCustomAssertion() { + + String classpath = getClassPath(Trivial.class); + String dependencyClassPath = getDependencyClassPath(); + + UtCompositeModel trivialModel = modelFactory. + produceCompositeModel( + classIdForType(Trivial.class) + ); + + Method methodUnderTest = getMethodByName( + Trivial.class, + "aMethod", + int.class + ); + + List testSets = UtBotJavaApi.generateTestSetsForMethods( + Collections.singletonList(methodUnderTest), + Trivial.class, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + new SimpleApplicationContext() + ); + + String generationResult = UtBotJavaApi.generateTestCode( + emptyList(), + testSets, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + Trivial.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet1); + + TestMethodInfo methodInfo = buildTestMethodInfo( + methodUnderTest, + trivialModel, + Collections.singletonList(new UtPrimitiveModel(2)), + Collections.emptyMap() + ); + + String generationResult2 = UtBotJavaApi.generateTestCode( + Collections.singletonList(methodInfo), + emptyList(), + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + Trivial.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResult2); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet2); + } + + /** + * The test is inspired by the API customers + */ + @Test + public void testProvided3Reused() { + + UtBotJavaApi.setStopConcreteExecutorOnExit(true); + + String dependencyClassPath = getDependencyClassPath(); + + // The method in the class contains only one parameter + String classSourceAsString = + "public class Demo9Recompiled {" + + " public int test(int b1) {" + + " return 0;" + + " }" + + "}"; + + String demo9RecompiledClassName = "Demo9Recompiled"; + Pair classpathToClassLoader = + compileClassAndGetClassPath(new Pair<>(demo9RecompiledClassName, classSourceAsString)); + + + String classpath = classpathToClassLoader.getFirst(); + ClassLoader classLoader = classpathToClassLoader.getSecond(); + + Class compiledClass = null; + + try { + compiledClass = classLoader.loadClass(demo9RecompiledClassName); + } catch (ClassNotFoundException e) { + Assertions.fail("Failed to load a class; Classpath: " + classpathToClassLoader.getFirst()); + } + + if (compiledClass == null) { + Assertions.fail("Failed to load the class"); + } + + Method methodUderTest = getMethodByName( + compiledClass, + "test", + int.class + ); + + List testSets = UtBotJavaApi.generateTestSetsForMethods( + Collections.singletonList(methodUderTest), + compiledClass, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + new SimpleApplicationContext() + ); + + String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generateTestCode( + emptyList(), + testSets, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + compiledClass, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet); + + // The test compiles and everything goes well. + // Let's recompile the initial clas file + + // The method below has an extra parameter + classSourceAsString = + "public class Demo9Recompiled {" + + " public int test(int b1, String s) {" + + " return 0;" + + " }" + + "}"; + + Pair stringClassLoaderPair = + compileClassAndGetClassPath(new Pair<>(demo9RecompiledClassName, classSourceAsString), classpath); + + ClassLoader reloadedClassLoader = stringClassLoaderPair.getSecond(); + + Class recompiledClass = null; + + try { + recompiledClass = reloadedClassLoader.loadClass(demo9RecompiledClassName); + } catch (ClassNotFoundException e) { + Assertions.fail("Failed to load the class after recompilation; classpath: " + stringClassLoaderPair.getFirst()); + } + + if (recompiledClass == null) { + Assertions.fail("Failed to load the class after recompilation"); + } + + Method recompiledMethod = getMethodByName( + recompiledClass, + "test", + int.class, String.class + ); + + List testSets1 = UtBotJavaApi.generateTestSetsForMethods( + Collections.singletonList(recompiledMethod), + recompiledClass, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + new SimpleApplicationContext() + ); + + String generationResultWithConcreteExecutionOnly2 = UtBotJavaApi.generateTestCode( + emptyList(), + testSets1, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + recompiledClass, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly2); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet2); + } + + /** A test provided by our customer. Demonstrates how to use the API in + *
+ * The tests are generated with and without concrete execution. + *
+ * Note, that you can use the {@code Snippet} instance in order to evaluate the test generation result. + */ + @Test + public void testProvided1() { + + UtBotJavaApi.setStopConcreteExecutorOnExit(false); + + String classpath = getClassPath(ArrayOfComplexArraysExample.class); + String dependencyClassPath = getDependencyClassPath(); + + UtCompositeModel providedTestModel = modelFactory.produceCompositeModel( + classIdForType(ProvidedExample.class), + Collections.emptyMap(), + Collections.emptyMap() + ); + + List parameters = Arrays.asList( + new UtPrimitiveModel(5), + new UtPrimitiveModel(9), + new UtPrimitiveModel("Some Text") + ); + + Method methodUnderTest = getMethodByName( + ProvidedExample.class, + "test0", + int.class, int.class, String.class + ); + + List testSets = UtBotJavaApi.generateTestSetsForMethods( + Collections.singletonList(methodUnderTest), + ProvidedExample.class, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + new SimpleApplicationContext() + ); + + String generationResult = UtBotJavaApi.generateTestCode( + emptyList(), + testSets, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + ProvidedExample.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet1); + + TestMethodInfo methodInfo = buildTestMethodInfo( + methodUnderTest, + providedTestModel, + parameters, + Collections.emptyMap() + ); + + String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generateTestCode( + Collections.singletonList(methodInfo), + emptyList(), + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + ProvidedExample.class, + new SimpleApplicationContext() + ); + + Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet2); + } + + @Test + public void testOnObjectWithArrayOfComplexArrays() { + + UtBotJavaApi.setStopConcreteExecutorOnExit(false); + + String classpath = getClassPath(ArrayOfComplexArraysExample.class); + String dependencyClassPath = getDependencyClassPath(); + + UtCompositeModel cmArrayOfComplexArrays = createArrayOfComplexArraysModel(); + + UtCompositeModel testClassCompositeModel = modelFactory.produceCompositeModel( + classIdForType(ArrayOfComplexArraysExample.class) + ); + + Method methodUnderTest = getMethodByName( + ArrayOfComplexArraysExample.class, + "getValue", + ArrayOfComplexArrays.class + ); + + List testSets = UtBotJavaApi.generateTestSetsForMethods( + Collections.singletonList(methodUnderTest), + ArrayOfComplexArraysExample.class, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + new SimpleApplicationContext() + ); + + String generationResult = UtBotJavaApi.generateTestCode( + emptyList(), + testSets, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + ArrayOfComplexArraysExample.class, + ProjectType.PureJvm, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + new SimpleApplicationContext() + ); + + Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet1); + + + TestMethodInfo methodInfo = buildTestMethodInfo( + methodUnderTest, + testClassCompositeModel, + Collections.singletonList(cmArrayOfComplexArrays), + Collections.emptyMap() + ); + + String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generateTestCode( + Collections.singletonList(methodInfo), + emptyList(), + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + ArrayOfComplexArraysExample.class, + new SimpleApplicationContext() + ); + + Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet2); + } + + @Test + public void testFuzzingSimple() { + SootUtils.INSTANCE.runSoot(StringSwitchExample.class, false, new JdkInfoDefaultProvider().getInfo()); + UtBotJavaApi.setStopConcreteExecutorOnExit(false); + + String classpath = getClassPath(StringSwitchExample.class); + String dependencyClassPath = getDependencyClassPath(); + + UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( + classIdForType(StringSwitchExample.class) + ); + + Method methodUnderTest = getMethodByName( + StringSwitchExample.class, + "validate", + String.class, int.class, int.class + ); + + List testSets1 = UtBotJavaApi.fuzzingTestSets( + Collections.singletonList(methodUnderTest), + StringSwitchExample.class, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 3000L, + (type) -> { + if (int.class.equals(type) || Integer.class.equals(type)) { + return Arrays.asList(0, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + return null; + }, + new SimpleApplicationContext() + ); + + TestMethodInfo methodInfo = buildTestMethodInfo( + methodUnderTest, + classUnderTestModel, + Arrays.asList(new UtPrimitiveModel("Some"), new UtPrimitiveModel(-10), new UtPrimitiveModel(0)), + Collections.emptyMap() + ); + + String generate = UtBotJavaApi.generateTestCode( + Collections.singletonList(methodInfo), + testSets1, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + StringSwitchExample.class, + new SimpleApplicationContext() + ); + + Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generate); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet2); + } + + public UtCompositeModel createArrayOfComplexArraysModel() { + ClassId classIdOfArrayOfComplexArraysClass = classIdForType(ArrayOfComplexArrays.class); + ClassId classIdOfComplexArray = classIdForType(ComplexArray.class); + ClassId classIdOfPrimitiveFieldsClass = classIdForType(PrimitiveFields.class); + + ClassId classIdOfArrayOfPrimitiveFieldsClass = new ClassId("[L" + classIdOfPrimitiveFieldsClass.getCanonicalName() + ";", classIdOfPrimitiveFieldsClass); + + Map elementsOfComplexArrayArray = new HashMap<>(); + + Map elementsWithPrimitiveFieldsClasses = new HashMap<>(); + + elementsWithPrimitiveFieldsClasses.put(0, modelFactory.produceCompositeModel( + classIdOfPrimitiveFieldsClass, + Collections.singletonMap("a", new UtPrimitiveModel(5)), + Collections.emptyMap() + )); + + elementsWithPrimitiveFieldsClasses.put(1, modelFactory.produceCompositeModel( + classIdOfPrimitiveFieldsClass, + Collections.singletonMap("b", new UtPrimitiveModel(4)), + Collections.emptyMap() + )); + + UtArrayModel arrayOfPrimitiveFieldsModel = modelFactory.produceArrayModel( + classIdOfArrayOfPrimitiveFieldsClass, + 2, + new UtNullModel(classIdOfPrimitiveFieldsClass), + elementsWithPrimitiveFieldsClasses + ); + + UtCompositeModel complexArrayClassModel = modelFactory.produceCompositeModel( + classIdOfComplexArray, + Collections.singletonMap("array", arrayOfPrimitiveFieldsModel) + ); + + elementsOfComplexArrayArray.put(1, complexArrayClassModel); + + ClassId classIdOfArraysOfComplexArrayClass = new ClassId("[L" + classIdOfComplexArray.getCanonicalName() + ";", classIdOfComplexArray); + + UtArrayModel arrayOfComplexArrayClasses = modelFactory.produceArrayModel( + classIdOfArraysOfComplexArrayClass, + 2, + new UtNullModel(classIdOfComplexArray), + elementsOfComplexArrayArray + ); + + return modelFactory.produceCompositeModel( + classIdOfArrayOfComplexArraysClass, + Collections.singletonMap("array", arrayOfComplexArrayClasses)); + } + + @Test + public void testUnitTestBotLight() { + String classpath = getClassPath(Trivial.class); + String dependencyClassPath = getDependencyClassPath(); + + UtCompositeModel model = modelFactory. + produceCompositeModel( + classIdForType(Trivial.class) + ); + + Method methodUnderTest = getMethodByName( + Trivial.class, + "aMethod", + int.class + ); + + TestMethodInfo methodInfo = buildTestMethodInfo( + methodUnderTest, + model, + Collections.singletonList(new UtPrimitiveModel(2)), + Collections.emptyMap() + ); + + UnitTestBotLight.run( + (engine, state) -> System.err.println("Got a call:" + state.getStmt()), + methodInfo, + classpath, + dependencyClassPath + ); + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/bytecode/versions/SootTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/bytecode/versions/SootTest.kt new file mode 100644 index 0000000000..e988274ce0 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/bytecode/versions/SootTest.kt @@ -0,0 +1,69 @@ +package org.utbot.bytecode.versions + +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.examples.objects.SimpleDataClass +import org.utbot.framework.util.SootUtils.runSoot +import soot.Scene + +@Suppress("UNREACHABLE_CODE") +@Disabled("TODO: https://github.com/UnitTestBot/UTBotJava/issues/891") +class SootTest { + @Test + fun `no method isBlank in JDK 8`() { + runSoot( + SimpleDataClass::class.java, + forceReload = true, + TODO("Get JDK 8") + ) + + val stringClass = Scene.v().getSootClass("java.lang.String") + assertFalse(stringClass.isPhantomClass) + + val isBlankMethod = stringClass.getMethodByNameUnsafe("isBlank") // no such method in JDK 8 + assertNull(isBlankMethod) + } + + @Test + fun `method isBlank exists in JDK 11`() { + runSoot( + SimpleDataClass::class.java, + forceReload = true, + TODO("Get JDK 11") + ) + + val stringClass = Scene.v().getSootClass("java.lang.String") + assertFalse(stringClass.isPhantomClass) + + val isBlankMethod = stringClass.getMethodByNameUnsafe("isBlank") // there is such method in JDK 11 + assertNotNull(isBlankMethod) + } + + @Test + fun `no records in JDK 11`() { + runSoot( + SimpleDataClass::class.java, + forceReload = true, + TODO("Get JDK 11") + ) + + val stringClass = Scene.v().getSootClass("java.lang.Record") // must not exist in JDK 11 + assertTrue(stringClass.isPhantomClass) + } + + @Test + fun `records exists in JDK 17`() { + runSoot( + SimpleDataClass::class.java, + forceReload = true, + TODO("Get JDK 17") + ) + + val stringClass = Scene.v().getSootClass("java.lang.Record") // must exist in JDK 17 + assertFalse(stringClass.isPhantomClass) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/engine/pc/QueryOptimizationsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/engine/pc/QueryOptimizationsTest.kt similarity index 99% rename from utbot-framework/src/test/kotlin/org/utbot/engine/pc/QueryOptimizationsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/engine/pc/QueryOptimizationsTest.kt index 43dcb3a39c..498c3ee2e5 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/engine/pc/QueryOptimizationsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/engine/pc/QueryOptimizationsTest.kt @@ -51,7 +51,7 @@ fun afterAll() { class QueryOptimizationsTest { private fun BaseQuery.check(vararg exprs: UtBoolExpression, checker: (BaseQuery) -> Unit = {}): BaseQuery = - this.with(hard = exprs.toList(), soft = emptyList()).also { + this.with(hard = exprs.toList(), soft = emptyList(), assumptions = emptyList()).also { checker(it) } diff --git a/utbot-framework/src/test/kotlin/org/utbot/engine/z3/ExtensionsKtTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/engine/z3/ExtensionsKtTest.kt similarity index 99% rename from utbot-framework/src/test/kotlin/org/utbot/engine/z3/ExtensionsKtTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/engine/z3/ExtensionsKtTest.kt index 91afc1a1c5..6beffa785e 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/engine/z3/ExtensionsKtTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/engine/z3/ExtensionsKtTest.kt @@ -12,6 +12,7 @@ import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments.arguments import org.junit.jupiter.params.provider.MethodSource +import org.utbot.engine.z3.ExtensionsKtTest.Companion.toSort import soot.BooleanType import soot.ByteType import soot.CharType diff --git a/utbot-framework/src/test/kotlin/org/utbot/engine/z3/OperatorsKtTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/engine/z3/OperatorsKtTest.kt similarity index 100% rename from utbot-framework/src/test/kotlin/org/utbot/engine/z3/OperatorsKtTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/engine/z3/OperatorsKtTest.kt diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/algorithms/BinarySearchTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/algorithms/BinarySearchTest.kt new file mode 100644 index 0000000000..b2a0a82c2b --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/algorithms/BinarySearchTest.kt @@ -0,0 +1,47 @@ +package org.utbot.examples.algorithms + +import org.junit.jupiter.api.Test +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException + +class BinarySearchTest : UtValueTestCaseChecker(testClass = BinarySearch::class,) { + @Test + fun testLeftBinarySearch() { + checkWithException( + BinarySearch::leftBinSearch, + ignoreExecutionsNumber, + { a, _, r -> a == null && r.isException() }, + { a, _, r -> a.size >= 2 && a[0] > a[1] && r.isException() }, + { a, _, r -> a.isEmpty() && r.getOrNull() == -1 }, + { a, key, r -> a.isNotEmpty() && key >= a[(a.size - 1) / 2] && key !in a && r.getOrNull() == -1 }, + { a, key, r -> a.isNotEmpty() && key in a && r.getOrNull() == a.indexOfFirst { it == key } + 1 } + ) + } + + @Test + fun testRightBinarySearch() { + checkWithException( + BinarySearch::rightBinSearch, + ignoreExecutionsNumber, + { a, _, r -> a == null && r.isException() }, + { a, _, r -> a.isEmpty() && r.getOrNull() == -1 }, + { a, _, r -> a.size >= 2 && a[0] > a[1] && r.isException() }, + { a, key, r -> a.isNotEmpty() && key !in a && r.getOrNull() == -1 }, + { a, key, r -> a.isNotEmpty() && key in a && r.getOrNull() == a.indexOfLast { it == key } + 1 } + ) + } + + @Test + fun testDefaultBinarySearch() { + checkWithException( + BinarySearch::defaultBinarySearch, + ignoreExecutionsNumber, + { a, _, r -> a == null && r.isException() }, + { a, _, r -> a.isEmpty() && r.getOrNull() == -1 }, + { a, _, r -> a.size >= 2 && a[0] > a[1] && r.isException() }, + { a, key, r -> a.isNotEmpty() && key < a.first() && r.getOrNull() == a.binarySearch(key) }, + { a, key, r -> a.isNotEmpty() && key == a.first() && r.getOrNull() == a.binarySearch(key) }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/algorithms/CorrectBracketSequencesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/algorithms/CorrectBracketSequencesTest.kt new file mode 100644 index 0000000000..9e00ce7f15 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/algorithms/CorrectBracketSequencesTest.kt @@ -0,0 +1,77 @@ +package org.utbot.examples.algorithms + +import org.junit.jupiter.api.Test +import org.utbot.examples.algorithms.CorrectBracketSequences.isBracket +import org.utbot.examples.algorithms.CorrectBracketSequences.isOpen +import org.utbot.testcheckers.eq +import org.utbot.testing.* + +internal class CorrectBracketSequencesTest : UtValueTestCaseChecker( + testClass = CorrectBracketSequences::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testIsOpen() { + checkStaticMethod( + CorrectBracketSequences::isOpen, + eq(4), + { c, r -> c == '(' && r == true }, + { c, r -> c == '{' && r == true }, + { c, r -> c == '[' && r == true }, + { c, r -> c !in "({[".toList() && r == false } + ) + } + + @Test + fun testIsBracket() { + checkStaticMethod( + CorrectBracketSequences::isBracket, + eq(7), + { c, r -> c == '(' && r == true }, + { c, r -> c == '{' && r == true }, + { c, r -> c == '[' && r == true }, + { c, r -> c == ')' && r == true }, + { c, r -> c == '}' && r == true }, + { c, r -> c == ']' && r == true }, + { c, r -> c !in "(){}[]".toList() && r == false } + ) + } + + @Test + fun testIsTheSameType() { + checkStaticMethod( + CorrectBracketSequences::isTheSameType, + ignoreExecutionsNumber, + { a, b, r -> a == '(' && b == ')' && r == true }, + { a, b, r -> a == '{' && b == '}' && r == true }, + { a, b, r -> a == '[' && b == ']' && r == true }, + { a, b, r -> a == '(' && b != ')' && r == false }, + { a, b, r -> a == '{' && b != '}' && r == false }, + { a, b, r -> a == '[' && b != ']' && r == false }, + { a, b, r -> (a != '(' || b != ')') && (a != '{' || b != '}') && (a != '[' || b != ']') && r == false } + ) + } + + @Test + fun testIsCbs() { + val method = CorrectBracketSequences::isCbs + checkStaticMethodWithException( + method, + ignoreExecutionsNumber, + { chars, r -> chars == null && r.isException() }, + { chars, r -> chars != null && chars.isEmpty() && r.getOrNull() == true }, + { chars, r -> chars.any { it == null } && r.isException() }, + { chars, r -> !isBracket(chars.first()) && r.getOrNull() == false }, + { chars, r -> !isOpen(chars.first()) && r.getOrNull() == false }, + { chars, _ -> isOpen(chars.first()) }, + { chars, r -> chars.all { isOpen(it) } && r.getOrNull() == false }, + { chars, _ -> + val charsWithoutFirstOpenBrackets = chars.dropWhile { isOpen(it) } + val firstNotOpenBracketChar = charsWithoutFirstOpenBrackets.first() + + isBracket(firstNotOpenBracketChar) && !isOpen(firstNotOpenBracketChar) + }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/algorithms/GraphTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/algorithms/GraphTest.kt new file mode 100644 index 0000000000..bf669b9c1e --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/algorithms/GraphTest.kt @@ -0,0 +1,53 @@ +package org.utbot.examples.algorithms + +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException + +internal class GraphTest : UtValueTestCaseChecker(testClass = GraphExample::class) { + @Test + @Tag("slow") + fun testRunFindCycle() { + checkWithException( + GraphExample::runFindCycle, + ignoreExecutionsNumber, + { e, r -> e == null && r.isException() }, + { e, r -> e != null && e.contains(null) && r.isException() }, + { e, r -> e != null && e.any { it.first < 0 || it.first >= 10 } && r.isException() }, + { e, r -> e != null && e.any { it.second < 0 || it.second >= 10 } && r.isException() }, + { e, r -> e != null && e.all { it != null } && r.isSuccess } + ) + } + + @Test + fun testDijkstra() { + // The graph is fixed, there should be exactly one execution path, so no matchers are necessary + check( + GraphExample::runDijkstra, + eq(1) + ) + } + + /** + * TODO: fix Dijkstra algorithm. + */ + @Test + fun testRunDijkstraWithParameter() { + checkWithException( + GraphExample::runDijkstraWithParameter, + ignoreExecutionsNumber, + { g, r -> g == null && r.isException() }, + { g, r -> g.isEmpty() && r.isException() }, + { g, r -> g.size == 1 && r.getOrNull()?.size == 1 && r.getOrNull()?.first() == 0 }, + { g, r -> g.size > 1 && g[1] == null && r.isException() }, + { g, r -> g.isNotEmpty() && g.size != g.first().size && r.isException() }, + { g, r -> + val concreteResult = GraphExample().runDijkstraWithParameter(g) + g.isNotEmpty() && r.getOrNull().contentEquals(concreteResult) + } + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/algorithms/SortTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/algorithms/SortTest.kt new file mode 100644 index 0000000000..174b63e7d2 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/algorithms/SortTest.kt @@ -0,0 +1,122 @@ +package org.utbot.examples.algorithms + +import org.utbot.framework.plugin.api.MockStrategyApi +import org.junit.jupiter.api.Test +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testing.* + +// TODO Kotlin mocks generics https://github.com/UnitTestBot/UTBotJava/issues/88 +internal class SortTest : UtValueTestCaseChecker( + testClass = Sort::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testQuickSort() { + check( + Sort::quickSort, + ignoreExecutionsNumber, + mockStrategy = MockStrategyApi.OTHER_PACKAGES + ) + } + + @Test + fun testSwap() { + checkWithException( + Sort::swap, + ge(4), + { a, _, _, r -> a == null && r.isException() }, + { a, i, _, r -> a != null && (i < 0 || i >= a.size) && r.isException() }, + { a, i, j, r -> a != null && i in a.indices && (j < 0 || j >= a.size) && r.isException() }, + { a, i, j, _ -> a != null && i in a.indices && j in a.indices } + ) + } + + @Test + fun testArrayCopy() { + check( + Sort::arrayCopy, + eq(1), + { r -> r contentEquals intArrayOf(1, 2, 3) } + ) + } + + @Test + fun testMergeSort() { + check( + Sort::mergeSort, + eq(4), + { a, r -> a == null && r == null }, + { a, r -> a != null && r != null && a.size < 2 }, + { a, r -> + require(a is IntArray && r is IntArray) + + val sortedConstraint = a.size >= 2 && a.sorted() == r.toList() + + val maxInLeftHalf = a.slice(0 until a.size / 2).maxOrNull()!! + val maxInRightHalf = a.slice(a.size / 2 until a.size).maxOrNull()!! + + sortedConstraint && maxInLeftHalf >= maxInRightHalf + }, + { a, r -> + require(a is IntArray && r is IntArray) + + val sortedConstraint = a.size >= 2 && a.sorted() == r.toList() + + val maxInLeftHalf = a.slice(0 until a.size / 2).maxOrNull()!! + val maxInRightHalf = a.slice(a.size / 2 until a.size).maxOrNull()!! + + sortedConstraint && maxInLeftHalf < maxInRightHalf + }, + ) + } + + @Test + fun testMerge() { + checkWithException( + Sort::merge, + eq(6), + { lhs, _, r -> lhs == null && r.isException() }, + { lhs, rhs, r -> lhs != null && lhs.isEmpty() && r.getOrNull() contentEquals rhs }, + { lhs, rhs, _ -> lhs != null && lhs.isNotEmpty() && rhs == null }, + { lhs, rhs, r -> + val lhsCondition = lhs != null && lhs.isNotEmpty() + val rhsCondition = rhs != null && rhs.isEmpty() + val connection = r.getOrNull() contentEquals lhs + + lhsCondition && rhsCondition && connection + }, + { lhs, rhs, r -> + val lhsCondition = lhs != null && lhs.isNotEmpty() + val rhsCondition = rhs != null && rhs.isNotEmpty() + val connection = lhs.last() < rhs.last() && r.getOrNull()?.toList() == (lhs + rhs).sorted() + + lhsCondition && rhsCondition && connection + }, + { lhs, rhs, r -> + val lhsCondition = lhs != null && lhs.isNotEmpty() + val rhsCondition = rhs != null && rhs.isNotEmpty() + val connection = lhs.last() >= rhs.last() && r.getOrNull()?.toList() == (lhs + rhs).sorted() + + lhsCondition && rhsCondition && connection + } + ) + } + + @Test + fun testDefaultSort() { + checkWithException( + Sort::defaultSort, + eq(3), + { a, r -> a == null && r.isException() }, + { a, r -> a != null && a.size < 4 && r.isException() }, + { a, r -> + val resultArray = intArrayOf(-100, 0, 100, 200) + a != null && r.getOrNull()!!.size >= 4 && r.getOrNull() contentEquals resultArray + } + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/NotNullAnnotationTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/NotNullAnnotationTest.kt similarity index 89% rename from utbot-framework/src/test/kotlin/org/utbot/examples/annotations/NotNullAnnotationTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/NotNullAnnotationTest.kt index b22b46285a..6bb162add4 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/NotNullAnnotationTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/NotNullAnnotationTest.kt @@ -1,11 +1,12 @@ package org.utbot.examples.annotations -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.AtLeast +import org.utbot.testing.UtValueTestCaseChecker -internal class NotNullAnnotationTest : AbstractTestCaseGeneratorTest(testClass = NotNullAnnotation::class) { +internal class NotNullAnnotationTest : UtValueTestCaseChecker(testClass = NotNullAnnotation::class) { @Test fun testDoesNotThrowNPE() { check( @@ -68,7 +69,8 @@ internal class NotNullAnnotationTest : AbstractTestCaseGeneratorTest(testClass = checkStatics( NotNullAnnotation::notNullStaticField, eq(1), - { statics, result -> result == statics.values.single().value } + { statics, result -> result == statics.values.single().value }, + coverage = AtLeast(66) ) } diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithAnnotationsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithAnnotationsTest.kt new file mode 100644 index 0000000000..b4c8097e10 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithAnnotationsTest.kt @@ -0,0 +1,25 @@ +package org.utbot.examples.annotations.lombok + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +/** + * Tests for Lombok annotations + * + * We do not calculate coverage here as Lombok always make it pure + * (see, i.e. https://stackoverflow.com/questions/44584487/improve-lombok-data-code-coverage) + * and Lombok code is considered to be already tested itself. + */ +internal class EnumWithAnnotationsTest : UtValueTestCaseChecker(testClass = EnumWithAnnotations::class) { + @Test + fun testGetterWithAnnotations() { + check( + EnumWithAnnotations::getConstant, + eq(1), + { r -> r == "Constant_1" }, + coverage = DoNotCalculate, + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithoutAnnotationsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithoutAnnotationsTest.kt new file mode 100644 index 0000000000..c9977326d6 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/lombok/EnumWithoutAnnotationsTest.kt @@ -0,0 +1,16 @@ +package org.utbot.examples.annotations.lombok + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +internal class EnumWithoutAnnotationsTest : UtValueTestCaseChecker(testClass = EnumWithoutAnnotations::class) { + @Test + fun testGetterWithoutAnnotations() { + check( + EnumWithoutAnnotations::getConstant, + eq(1), + { r -> r == "Constant_1" }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/lombok/NotNullAnnotationsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/lombok/NotNullAnnotationsTest.kt new file mode 100644 index 0000000000..1d3e95b33c --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/annotations/lombok/NotNullAnnotationsTest.kt @@ -0,0 +1,25 @@ +package org.utbot.examples.annotations.lombok + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +/** + * Tests for Lombok NonNull annotation + * + * We do not calculate coverage here as Lombok always make it pure + * (see, i.e. https://stackoverflow.com/questions/44584487/improve-lombok-data-code-coverage) + * and Lombok code is considered to be already tested itself. + */ +internal class NotNullAnnotationsTest : UtValueTestCaseChecker(testClass = NotNullAnnotations::class) { + @Test + fun testNonNullAnnotations() { + check( + NotNullAnnotations::lombokNonNull, + eq(1), + { value, r -> value == r }, + coverage = DoNotCalculate, + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArrayOfArraysTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArrayOfArraysTest.kt similarity index 93% rename from utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArrayOfArraysTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArrayOfArraysTest.kt index e7c660e426..db375097eb 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArrayOfArraysTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArrayOfArraysTest.kt @@ -1,17 +1,18 @@ package org.utbot.examples.arrays -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.atLeast +import org.junit.jupiter.api.Disabled import org.utbot.examples.casts.ColoredPoint import org.utbot.examples.casts.Point -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.withoutMinimization import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutMinimization +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast +import org.utbot.testing.ignoreExecutionsNumber @Suppress("NestedLambdaShadowedImplicitParameter") -internal class ArrayOfArraysTest : AbstractTestCaseGeneratorTest(testClass = ArrayOfArrays::class) { +internal class ArrayOfArraysTest : UtValueTestCaseChecker(testClass = ArrayOfArrays::class) { @Test fun testDefaultValues() { check( @@ -178,6 +179,7 @@ internal class ArrayOfArraysTest : AbstractTestCaseGeneratorTest(testClass = Arr } @Test + @Disabled("Seems not aligning with ksmt version used in USVM") fun testReallyMultiDimensionalArray() { check( ArrayOfArrays::reallyMultiDimensionalArray, @@ -204,6 +206,7 @@ internal class ArrayOfArraysTest : AbstractTestCaseGeneratorTest(testClass = Arr } @Test + @Disabled("Seems not aligning with ksmt version used in USVM") fun testReallyMultiDimensionalArrayMutation() { checkParamsMutations( ArrayOfArrays::reallyMultiDimensionalArray, @@ -268,10 +271,13 @@ internal class ArrayOfArraysTest : AbstractTestCaseGeneratorTest(testClass = Arr } @Test + @Disabled("https://github.com/UnitTestBot/UTBotJava/issues/1267") fun testArrayWithItselfAnAsElement() { check( ArrayOfArrays::arrayWithItselfAnAsElement, eq(2), + { a, r -> a !== a[0] && r == null }, + { a, r -> a === a[0] && a.contentDeepEquals(r) }, coverage = atLeast(percents = 94) // because of the assumption ) diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArrayOfObjectsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArrayOfObjectsTest.kt new file mode 100644 index 0000000000..1b1c3a28a9 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArrayOfObjectsTest.kt @@ -0,0 +1,108 @@ +package org.utbot.examples.arrays + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testing.* + +// TODO failed Kotlin compilation SAT-1332 +internal class ArrayOfObjectsTest : UtValueTestCaseChecker( + testClass = ArrayOfObjects::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testDefaultValues() { + check( + ArrayOfObjects::defaultValues, + eq(1), + { r -> r != null && r.single() == null }, + coverage = atLeast(50) + ) + } + + @Test + fun testCreateArray() { + check( + ArrayOfObjects::createArray, + eq(2), + { _, _, length, _ -> length < 3 }, + { x, y, length, r -> + require(r != null) + + val sizeConstraint = length >= 3 && r.size == length + val contentConstraint = r.mapIndexed { i, elem -> elem.x == x + i && elem.y == y + i }.all { it } + + sizeConstraint && contentConstraint + } + ) + } + + @Test + fun testCopyArray() { + checkWithException( + ArrayOfObjects::copyArray, + ge(4), + { a, r -> a == null && r.isException() }, + { a, r -> a.size < 3 && r.isException() }, + { a, r -> a.size >= 3 && null in a && r.isException() }, + { a, r -> a.size >= 3 && r.getOrThrow().all { it.x == -1 && it.y == 1 } }, + ) + } + + @Test + fun testCopyArrayMutation() { + checkParamsMutations( + ArrayOfObjects::copyArray, + ignoreExecutionsNumber, + { _, arrayAfter -> arrayAfter.all { it.x == -1 && it.y == 1 } } + ) + } + + @Test + fun testArrayWithSucc() { + check( + ArrayOfObjects::arrayWithSucc, + eq(3), + { length, _ -> length < 0 }, + { length, r -> length < 2 && r != null && r.size == length && r.all { it == null } }, + { length, r -> + require(r != null) + + val sizeConstraint = length >= 2 && r.size == length + val zeroElementConstraint = r[0] is ObjectWithPrimitivesClass && r[0].x == 2 && r[0].y == 4 + val firstElementConstraint = r[1] is ObjectWithPrimitivesClassSucc && r[1].x == 3 + + sizeConstraint && zeroElementConstraint && firstElementConstraint + }, + ) + } + + @Test + fun testObjectArray() { + check( + ArrayOfObjects::objectArray, + eq(5), + { a, _, _ -> a == null }, + { a, _, r -> a != null && a.size != 2 && r == -1 }, + { a, o, _ -> a != null && a.size == 2 && o == null }, + { a, p, r -> a != null && a.size == 2 && p != null && p.x + 5 > 20 && r == 1 }, + { a, o, r -> a != null && a.size == 2 && o != null && o.x + 5 <= 20 && r == 0 }, + ) + } + + @Test + fun testArrayOfArrays() { + withEnabledTestingCodeGeneration(testCodeGeneration = false) { + check( + ArrayOfObjects::arrayOfArrays, + between(4..5), // might be two ClassCastExceptions + { a, _ -> a.any { it == null } }, + { a, _ -> a.any { it != null && it !is IntArray } }, + { a, r -> (a.all { it != null && it is IntArray && it.isEmpty() } || a.isEmpty()) && r == 0 }, + { a, r -> a.all { it is IntArray } && r == a.sumBy { (it as IntArray).sum() } }, + coverage = DoNotCalculate + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArrayStoreExceptionExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArrayStoreExceptionExamplesTest.kt new file mode 100644 index 0000000000..63c14376ff --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArrayStoreExceptionExamplesTest.kt @@ -0,0 +1,209 @@ +package org.utbot.examples.arrays + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.AtLeast +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException + +class ArrayStoreExceptionExamplesTest : UtValueTestCaseChecker( + testClass = ArrayStoreExceptionExamples::class, + // Type inference errors in generated Kotlin code + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testCorrectAssignmentSamePrimitiveType() { + checkWithException( + ArrayStoreExceptionExamples::correctAssignmentSamePrimitiveType, + eq(3), + { data, result -> result.isSuccess && result.getOrNull() == data?.isNotEmpty() } + ) + } + + @Test + fun testCorrectAssignmentIntToIntegerArray() { + checkWithException( + ArrayStoreExceptionExamples::correctAssignmentIntToIntegerArray, + eq(3), + { data, result -> result.isSuccess && result.getOrNull() == data?.isNotEmpty() } + ) + } + + @Test + fun testCorrectAssignmentSubtype() { + checkWithException( + ArrayStoreExceptionExamples::correctAssignmentSubtype, + eq(3), + { data, result -> result.isSuccess && result.getOrNull() == data?.isNotEmpty() } + ) + } + + @Test + fun testCorrectAssignmentToObjectArray() { + checkWithException( + ArrayStoreExceptionExamples::correctAssignmentToObjectArray, + eq(3), + { data, result -> result.isSuccess && result.getOrNull() == data?.isNotEmpty() } + ) + } + + @Test + fun testWrongAssignmentUnrelatedType() { + checkWithException( + ArrayStoreExceptionExamples::wrongAssignmentUnrelatedType, + eq(3), + { data, result -> data == null && result.isSuccess }, + { data, result -> data.isEmpty() && result.isSuccess }, + { data, result -> data.isNotEmpty() && result.isException() }, + coverage = AtLeast(91) // TODO: investigate + ) + } + + @Test + fun testCheckGenericAssignmentWithCorrectCast() { + checkWithException( + ArrayStoreExceptionExamples::checkGenericAssignmentWithCorrectCast, + eq(1), + { result -> result.isSuccess } + ) + } + + @Test + fun testCheckGenericAssignmentWithWrongCast() { + checkWithException( + ArrayStoreExceptionExamples::checkGenericAssignmentWithWrongCast, + eq(1), + { result -> result.isException() }, + coverage = AtLeast(87) // TODO: investigate + ) + } + + @Test + fun testCheckGenericAssignmentWithExtendsSubtype() { + checkWithException( + ArrayStoreExceptionExamples::checkGenericAssignmentWithExtendsSubtype, + eq(1), + { result -> result.isSuccess } + ) + } + + @Test + fun testCheckGenericAssignmentWithExtendsUnrelated() { + checkWithException( + ArrayStoreExceptionExamples::checkGenericAssignmentWithExtendsUnrelated, + eq(1), + { result -> result.isException() }, + coverage = AtLeast(87) // TODO: investigate + ) + } + + @Test + fun testCheckObjectAssignment() { + checkWithException( + ArrayStoreExceptionExamples::checkObjectAssignment, + eq(1), + { result -> result.isSuccess } + ) + } + + // Should this be allowed at all? + @Test + fun testCheckWrongAssignmentOfItself() { + checkWithException( + ArrayStoreExceptionExamples::checkWrongAssignmentOfItself, + eq(1), + { result -> result.isException() }, + coverage = AtLeast(87) + ) + } + + @Test + fun testCheckGoodAssignmentOfItself() { + checkWithException( + ArrayStoreExceptionExamples::checkGoodAssignmentOfItself, + eq(1), + { result -> result.isSuccess } + ) + } + + @Test + fun testCheckAssignmentToObjectArray() { + checkWithException( + ArrayStoreExceptionExamples::checkAssignmentToObjectArray, + eq(1), + { result -> result.isSuccess } + ) + } + + @Test + fun testArrayCopyForIncompatiblePrimitiveTypes() { + checkWithException( + ArrayStoreExceptionExamples::arrayCopyForIncompatiblePrimitiveTypes, + eq(3), + { data, result -> data == null && result.isSuccess && result.getOrNull() == null }, + { data, result -> data != null && data.isEmpty() && result.isSuccess && result.getOrNull()?.size == 0 }, + { data, result -> data != null && data.isNotEmpty() && result.isException() } + ) + } + + @Test + fun testFill2DPrimitiveArray() { + checkWithException( + ArrayStoreExceptionExamples::fill2DPrimitiveArray, + eq(1), + { result -> result.isSuccess } + ) + } + + @Test + fun testFillObjectArrayWithList() { + check( + ArrayStoreExceptionExamples::fillObjectArrayWithList, + eq(2), + { list, result -> list != null && result != null && result[0] != null }, + { list, result -> list == null && result == null } + ) + } + + @Test + fun testFillWithTreeSet() { + check( + ArrayStoreExceptionExamples::fillWithTreeSet, + eq(2), + { treeSet, result -> treeSet != null && result != null && result[0] != null }, + { treeSet, result -> treeSet == null && result == null } + ) + } + + @Test + fun testFillSomeInterfaceArrayWithSomeInterface() { + check( + ArrayStoreExceptionExamples::fillSomeInterfaceArrayWithSomeInterface, + eq(2), + { impl, result -> impl == null && result == null }, + { impl, result -> impl != null && result != null && result[0] != null } + ) + } + + @Test + @Disabled("TODO: Not null path is not found, need to investigate") + fun testFillObjectArrayWithSomeInterface() { + check( + ArrayStoreExceptionExamples::fillObjectArrayWithSomeInterface, + eq(2), + { impl, result -> impl == null && result == null }, + { impl, result -> impl != null && result != null && result[0] != null } + ) + } + + @Test + fun testFillWithSomeImplementation() { + check( + ArrayStoreExceptionExamples::fillWithSomeImplementation, + eq(2), + { impl, result -> impl == null && result == null }, + { impl, result -> impl != null && result != null && result[0] != null } + ) + } +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArraysOverwriteValueTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArraysOverwriteValueTest.kt similarity index 92% rename from utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArraysOverwriteValueTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArraysOverwriteValueTest.kt index d73246a0c5..72c7472581 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArraysOverwriteValueTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/ArraysOverwriteValueTest.kt @@ -1,19 +1,14 @@ package org.utbot.examples.arrays -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker // TODO failed Kotlin compilation SAT-1332 -class ArraysOverwriteValueTest : AbstractTestCaseGeneratorTest( +class ArraysOverwriteValueTest : UtValueTestCaseChecker( testClass = ArraysOverwriteValue::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testByteArray() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/CopyOfExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/CopyOfExampleTest.kt new file mode 100644 index 0000000000..16f691b3e9 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/CopyOfExampleTest.kt @@ -0,0 +1,34 @@ +package org.utbot.examples.arrays + +import org.junit.jupiter.api.Test +import org.utbot.testing.AtLeast +import org.utbot.testing.FullWithAssumptions +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException + +class CopyOfExampleTest : UtValueTestCaseChecker(testClass = CopyOfExample::class) { + @Test + fun testCopyOf() { + checkWithException( + CopyOfExample::copyOfExample, + ignoreExecutionsNumber, + { _, l, r -> l < 0 && r.isException() }, + { arr, l, r -> arr.copyOf(l).contentEquals(r.getOrThrow()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testCopyOfRange() { + checkWithException( + CopyOfExample::copyOfRangeExample, + ignoreExecutionsNumber, + { _, from, _, r -> from < 0 && r.isException() }, + { arr, from, _, r -> from > arr.size && r.isException() }, + { _, from, to, r -> from > to && r.isException() }, + { arr, from, to, r -> arr.copyOfRange(from, to).contentEquals(r.getOrThrow()) }, + coverage = AtLeast(82) + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/FinalStaticFieldArrayTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/FinalStaticFieldArrayTest.kt new file mode 100644 index 0000000000..55b0d37eed --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/FinalStaticFieldArrayTest.kt @@ -0,0 +1,21 @@ +package org.utbot.examples.arrays + +import org.junit.jupiter.api.Test +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber + +internal class FinalStaticFieldArrayTest : UtValueTestCaseChecker(testClass = FinalStaticFieldArray::class) { + + @Test + fun testFactorial() { + checkStaticMethod( + FinalStaticFieldArray::factorial, + ignoreExecutionsNumber, + { n, r -> + (n as Int) >= 0 && n < FinalStaticFieldArray.MAX_FACTORIAL && r == FinalStaticFieldArray.factorial(n) + }, + { n, _ -> (n as Int) < 0 }, + { n, r -> (n as Int) > FinalStaticFieldArray.MAX_FACTORIAL && r == FinalStaticFieldArray.factorial(n) }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/IntArrayBasicsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/IntArrayBasicsTest.kt similarity index 93% rename from utbot-framework/src/test/kotlin/org/utbot/examples/arrays/IntArrayBasicsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/IntArrayBasicsTest.kt index 5d32297515..9c7cffb161 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/IntArrayBasicsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/IntArrayBasicsTest.kt @@ -1,22 +1,18 @@ package org.utbot.examples.arrays -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException // TODO failed Kotlin compilation SAT-1332 -internal class IntArrayBasicsTest : AbstractTestCaseGeneratorTest( +internal class IntArrayBasicsTest : UtValueTestCaseChecker( testClass = IntArrayBasics::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testIntArrayWithAssumeOrExecuteConcretely() { @@ -219,6 +215,7 @@ internal class IntArrayBasicsTest : AbstractTestCaseGeneratorTest( } @Test + @Disabled("Java 11 transition -- Sergei is looking into it") fun testArraysEqualsExample() { check( IntArrayBasics::arrayEqualsExample, diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/PrimitiveArraysTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/PrimitiveArraysTest.kt similarity index 93% rename from utbot-framework/src/test/kotlin/org/utbot/examples/arrays/PrimitiveArraysTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/PrimitiveArraysTest.kt index afee906efa..3fdb46617b 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/PrimitiveArraysTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/arrays/PrimitiveArraysTest.kt @@ -1,21 +1,16 @@ package org.utbot.examples.arrays -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.atLeast -import org.utbot.examples.eq -import org.utbot.examples.isException -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast +import org.utbot.testing.isException // TODO failed Kotlin compilation SAT-1332 -internal class PrimitiveArraysTest : AbstractTestCaseGeneratorTest( +internal class PrimitiveArraysTest : UtValueTestCaseChecker( testClass = PrimitiveArrays::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testDefaultIntValues() { diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/casts/ArrayCastExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/ArrayCastExampleTest.kt similarity index 92% rename from utbot-framework/src/test/kotlin/org/utbot/examples/casts/ArrayCastExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/ArrayCastExampleTest.kt index dfacfbea4b..6356b765e9 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/casts/ArrayCastExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/ArrayCastExampleTest.kt @@ -1,21 +1,17 @@ package org.utbot.examples.casts -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker // TODO failed Kotlin compilation (generics) SAT-1332 //TODO: SAT-1487 calculate coverage for all methods of this test class -internal class ArrayCastExampleTest : AbstractTestCaseGeneratorTest( +internal class ArrayCastExampleTest : UtValueTestCaseChecker( testClass = ArrayCastExample::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testCastToAncestor() { @@ -160,6 +156,7 @@ internal class ArrayCastExampleTest : AbstractTestCaseGeneratorTest( } @Test + @Disabled("Fix Traverser.findInvocationTargets to exclude non-exported classes / provide good classes") fun testCastFromIterableToCollection() { check( ArrayCastExample::castFromIterableToCollection, diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/CastClassTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/CastClassTest.kt new file mode 100644 index 0000000000..0c65e94758 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/CastClassTest.kt @@ -0,0 +1,21 @@ +package org.utbot.examples.casts + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +internal class CastClassTest : UtValueTestCaseChecker( + testClass = CastClass::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testThisTypeChoice() { + check( + CastClass::castToInheritor, + eq(0), + coverage = DoNotCalculate + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/CastExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/CastExampleTest.kt new file mode 100644 index 0000000000..43728dc5d2 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/CastExampleTest.kt @@ -0,0 +1,88 @@ +package org.utbot.examples.casts + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException + +// TODO failed Kotlin compilation SAT-1332 +internal class CastExampleTest : UtValueTestCaseChecker( + testClass = CastExample::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testSimpleCast() { + check( + CastExample::simpleCast, + eq(3), + { o, _ -> o != null && o !is CastClassFirstSucc }, + { o, r -> o != null && r is CastClassFirstSucc }, + { o, r -> o == null && r == null }, + ) + } + + @Test + fun testClassCastException() { + checkWithException( + CastExample::castClassException, + eq(3), + { o, r -> o == null && r.isException() }, + { o, r -> o != null && o !is CastClassFirstSucc && r.isException() }, + { o, r -> o != null && o is CastClassFirstSucc && r.isException() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testCastUp() { + check( + CastExample::castUp, + eq(1) + ) + } + + @Test + fun testCastNullToDifferentTypes() { + check( + CastExample::castNullToDifferentTypes, + eq(1) + ) + } + + @Test + fun testFromObjectToPrimitive() { + check( + CastExample::fromObjectToPrimitive, + eq(3), + { obj, _ -> obj == null }, + { obj, _ -> obj != null && obj !is Int }, + { obj, r -> obj != null && obj is Int && r == obj } + ) + } + + @Test + fun testCastFromObjectToInterface() { + check( + CastExample::castFromObjectToInterface, + eq(2), + { obj, _ -> obj != null && obj !is Colorable }, + { obj, r -> obj != null && obj is Colorable && r == obj }, + coverage = DoNotCalculate + ) + } + + @Test + fun testComplicatedCast() { + withEnabledTestingCodeGeneration(testCodeGeneration = false) { // error: package sun.text is not visible + check( + CastExample::complicatedCast, + eq(2), + { i, a, _ -> i == 0 && a != null && a[i] != null && a[i] !is CastClassFirstSucc }, + { i, a, r -> i == 0 && a != null && a[i] != null && a[i] is CastClassFirstSucc && r is CastClassFirstSucc }, + coverage = DoNotCalculate + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/casts/GenericCastExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/GenericCastExampleTest.kt similarity index 81% rename from utbot-framework/src/test/kotlin/org/utbot/examples/casts/GenericCastExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/GenericCastExampleTest.kt index 20311105d4..1f86663719 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/casts/GenericCastExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/GenericCastExampleTest.kt @@ -1,21 +1,16 @@ package org.utbot.examples.casts -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between // TODO failed Kotlin compilation SAT-1332 -internal class GenericCastExampleTest : AbstractTestCaseGeneratorTest( +internal class GenericCastExampleTest : UtValueTestCaseChecker( testClass = GenericCastExample::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testCompareTwoNumbers() { diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/casts/InstanceOfExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/InstanceOfExampleTest.kt similarity index 96% rename from utbot-framework/src/test/kotlin/org/utbot/examples/casts/InstanceOfExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/InstanceOfExampleTest.kt index 0972bdabb1..cf9cadef4c 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/casts/InstanceOfExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/casts/InstanceOfExampleTest.kt @@ -1,23 +1,18 @@ package org.utbot.examples.casts -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber // TODO failed Kotlin compilation SAT-1332 -internal class InstanceOfExampleTest : AbstractTestCaseGeneratorTest( +internal class InstanceOfExampleTest : UtValueTestCaseChecker( testClass = InstanceOfExample::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testSimpleInstanceOf() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/ClassWithStaticAndInnerClassesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/ClassWithStaticAndInnerClassesTest.kt new file mode 100644 index 0000000000..ab50b27ee7 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/ClassWithStaticAndInnerClassesTest.kt @@ -0,0 +1,127 @@ +package org.utbot.examples.codegen + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +@Suppress("INACCESSIBLE_TYPE") +internal class ClassWithStaticAndInnerClassesTest : UtValueTestCaseChecker( + testClass = ClassWithStaticAndInnerClasses::class, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testUsePrivateStaticClassWithPrivateField() { + check( + ClassWithStaticAndInnerClasses::usePrivateStaticClassWithPrivateField, + eq(2), + coverage = DoNotCalculate + ) + } + + @Test + fun testUsePrivateStaticClassWithPublicField() { + check( + ClassWithStaticAndInnerClasses::usePrivateStaticClassWithPublicField, + eq(2), + coverage = DoNotCalculate + ) + } + + @Test + fun testUsePublicStaticClassWithPrivateField() { + check( + ClassWithStaticAndInnerClasses::usePublicStaticClassWithPrivateField, + eq(2), + coverage = DoNotCalculate + ) + } + + @Test + fun testUsePublicStaticClassWithPublicField() { + check( + ClassWithStaticAndInnerClasses::usePublicStaticClassWithPublicField, + eq(2), + coverage = DoNotCalculate + ) + } + + @Test + fun testUsePrivateInnerClassWithPrivateField() { + check( + ClassWithStaticAndInnerClasses::usePrivateInnerClassWithPrivateField, + eq(2), + coverage = DoNotCalculate + ) + } + + @Test + fun testUsePrivateInnerClassWithPublicField() { + check( + ClassWithStaticAndInnerClasses::usePrivateInnerClassWithPublicField, + eq(2), + coverage = DoNotCalculate + ) + } + + @Test + fun testUsePublicInnerClassWithPrivateField() { + check( + ClassWithStaticAndInnerClasses::usePublicInnerClassWithPrivateField, + eq(2), + coverage = DoNotCalculate + ) + } + + @Test + fun testUsePublicInnerClassWithPublicField() { + check( + ClassWithStaticAndInnerClasses::usePublicInnerClassWithPublicField, + eq(2), + coverage = DoNotCalculate + ) + } + + @Test + fun testUsePackagePrivateFinalStaticClassWithPackagePrivateField() { + check( + ClassWithStaticAndInnerClasses::usePackagePrivateFinalStaticClassWithPackagePrivateField, + eq(2), + coverage = DoNotCalculate + ) + } + + @Test + fun testUsePackagePrivateFinalInnerClassWithPackagePrivateField() { + check( + ClassWithStaticAndInnerClasses::usePackagePrivateFinalInnerClassWithPackagePrivateField, + eq(2), + coverage = DoNotCalculate + ) + } + + @Test + fun testGetValueFromPublicFieldWithPrivateType() { + check( + ClassWithStaticAndInnerClasses::getValueFromPublicFieldWithPrivateType, + eq(2), + coverage = DoNotCalculate + ) + } + + @Test + fun testPublicStaticClassWithPrivateField_DeepNestedStatic_g() { + checkAllCombinations( + ClassWithStaticAndInnerClasses.PublicStaticClassWithPrivateField.DeepNestedStatic::g, + generateWithNested = true + ) + } + + @Test + fun testPublicStaticClassWithPrivateField_DeepNested_h() { + checkAllCombinations( + ClassWithStaticAndInnerClasses.PublicStaticClassWithPrivateField.DeepNested::h, + generateWithNested = true + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/CodegenExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/CodegenExampleTest.kt similarity index 87% rename from utbot-framework/src/test/kotlin/org/utbot/examples/codegen/CodegenExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/CodegenExampleTest.kt index cb15ba4995..bd07565c1e 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/CodegenExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/CodegenExampleTest.kt @@ -1,13 +1,13 @@ package org.utbot.examples.codegen -import org.utbot.examples.AbstractTestCaseGeneratorTest import org.utbot.examples.mock.MockRandomExamples -import org.utbot.examples.withoutConcrete import kotlin.reflect.full.functions import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.UtValueTestCaseChecker -internal class CodegenExampleTest : AbstractTestCaseGeneratorTest(testClass = CodegenExample::class) { +internal class CodegenExampleTest : UtValueTestCaseChecker(testClass = CodegenExample::class) { @Test fun firstExampleTest() { withoutConcrete { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctionsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctionsTest.kt new file mode 100644 index 0000000000..6af41eb6d4 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctionsTest.kt @@ -0,0 +1,43 @@ +package org.utbot.examples.codegen + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import kotlin.reflect.KFunction3 + +@Suppress("UNCHECKED_CAST") +internal class FileWithTopLevelFunctionsTest : UtValueTestCaseChecker(testClass = FileWithTopLevelFunctionsReflectHelper.clazz.kotlin) { + @Test + fun topLevelSumTest() { + check( + ::topLevelSum, + eq(1), + ) + } + + @Test + fun extensionOnBasicTypeTest() { + check( + Int::extensionOnBasicType, + eq(1), + ) + } + + @Test + fun extensionOnCustomClassTest() { + check( + // NB: cast is important here because we need to treat receiver as an argument to be able to check its content in matchers + CustomClass::extensionOnCustomClass as KFunction3<*, CustomClass, CustomClass, Boolean>, + eq(2), + { receiver, argument, result -> receiver === argument && result == true }, + { receiver, argument, result -> receiver !== argument && result == false }, + additionalDependencies = dependenciesForClassExtensions + ) + } + + companion object { + // Compilation of extension methods for ref objects produces call to + // `kotlin.jvm.internal.Intrinsics::checkNotNullParameter`, so we need to add it to dependencies + val dependenciesForClassExtensions = arrayOf>(kotlin.jvm.internal.Intrinsics::class.java) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/JavaAssertTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/JavaAssertTest.kt new file mode 100644 index 0000000000..2133cf2ef8 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/JavaAssertTest.kt @@ -0,0 +1,21 @@ +package org.utbot.examples.codegen + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException + +class JavaAssertTest : UtValueTestCaseChecker( + testClass = JavaAssert::class, + testCodeGeneration = false +) { + @Test + fun testAssertPositive() { + checkWithException( + JavaAssert::assertPositive, + eq(2), + { value, result -> value > 0 && result.isSuccess && result.getOrNull() == value }, + { value, result -> value <= 0 && result.isException() } + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/VoidStaticMethodsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/VoidStaticMethodsTest.kt similarity index 75% rename from utbot-framework/src/test/kotlin/org/utbot/examples/codegen/VoidStaticMethodsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/VoidStaticMethodsTest.kt index 1dfe2526f3..42af7ff925 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/VoidStaticMethodsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/VoidStaticMethodsTest.kt @@ -1,11 +1,12 @@ package org.utbot.examples.codegen -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker -class VoidStaticMethodsTest : AbstractTestCaseGeneratorTest(testClass = VoidStaticMethodsTestingClass::class) { +class VoidStaticMethodsTest : UtValueTestCaseChecker( + testClass = VoidStaticMethodsTestingClass::class) { @Test fun testInvokeChangeStaticFieldMethod() { check( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithCrossReferenceRelationshipTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithCrossReferenceRelationshipTest.kt new file mode 100644 index 0000000000..e87b3e4c7e --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithCrossReferenceRelationshipTest.kt @@ -0,0 +1,21 @@ +package org.utbot.examples.codegen.deepequals + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +class ClassWithCrossReferenceRelationshipTest : UtValueTestCaseChecker( + testClass = ClassWithCrossReferenceRelationship::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testClassWithCrossReferenceRelationship() { + check( + ClassWithCrossReferenceRelationship::returnFirstClass, + eq(2), + coverage = DoNotCalculate + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithNullableFieldTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithNullableFieldTest.kt new file mode 100644 index 0000000000..0026204d6c --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/deepequals/ClassWithNullableFieldTest.kt @@ -0,0 +1,30 @@ +package org.utbot.examples.codegen.deepequals + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +class ClassWithNullableFieldTest : UtValueTestCaseChecker( + testClass = ClassWithNullableField::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testClassWithNullableFieldInCompound() { + check( + ClassWithNullableField::returnCompoundWithNullableField, + eq(2), + coverage = DoNotCalculate + ) + } + + @Test + fun testClassWithNullableFieldInGreatCompound() { + check( + ClassWithNullableField::returnGreatCompoundWithNullableField, + eq(3), + coverage = DoNotCalculate + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/DeepEqualsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/deepequals/DeepEqualsTest.kt similarity index 88% rename from utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/DeepEqualsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/deepequals/DeepEqualsTest.kt index e98dbc640b..af46240390 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/deepequals/DeepEqualsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/deepequals/DeepEqualsTest.kt @@ -1,21 +1,16 @@ package org.utbot.examples.codegen.deepequals -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker // TODO failed Kotlin compilation (generics) SAT-1332 -class DeepEqualsTest : AbstractTestCaseGeneratorTest( +class DeepEqualsTest : UtValueTestCaseChecker( testClass = DeepEqualsTestingClass::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testReturnList() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/modifiers/ClassWithPrivateMutableFieldOfPrivateTypeTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/modifiers/ClassWithPrivateMutableFieldOfPrivateTypeTest.kt new file mode 100644 index 0000000000..1a5ce264ab --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/codegen/modifiers/ClassWithPrivateMutableFieldOfPrivateTypeTest.kt @@ -0,0 +1,36 @@ +package org.utbot.examples.codegen.modifiers + +import org.junit.jupiter.api.Test +import org.utbot.common.withAccessibility +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jField +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +// TODO failed Kotlin tests execution with non-nullable expected field +class ClassWithPrivateMutableFieldOfPrivateTypeTest : UtValueTestCaseChecker( + testClass = ClassWithPrivateMutableFieldOfPrivateType::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testChangePrivateMutableFieldWithPrivateType() { + checkAllMutationsWithThis( + ClassWithPrivateMutableFieldOfPrivateType::changePrivateMutableFieldWithPrivateType, + eq(1), + { thisBefore, _, thisAfter, _, r -> + val privateMutableField = FieldId( + ClassWithPrivateMutableFieldOfPrivateType::class.id, + "privateMutableField" + ).jField + + val (privateFieldBeforeValue, privateFieldAfterValue) = privateMutableField.withAccessibility { + privateMutableField.get(thisBefore) to privateMutableField.get(thisAfter) + } + + privateFieldBeforeValue == null && privateFieldAfterValue != null && r == 0 + } + ) + } +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/CustomerExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/CustomerExamplesTest.kt similarity index 82% rename from utbot-framework/src/test/kotlin/org/utbot/examples/collections/CustomerExamplesTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/CustomerExamplesTest.kt index 421268728e..4d114a5e02 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/CustomerExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/CustomerExamplesTest.kt @@ -1,22 +1,17 @@ package org.utbot.examples.collections -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.UtConcreteValue import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber -internal class CustomerExamplesTest: AbstractTestCaseGeneratorTest( +internal class CustomerExamplesTest: UtValueTestCaseChecker( testClass = CustomerExamples::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testSimpleExample() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/GenericListsExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/GenericListsExampleTest.kt new file mode 100644 index 0000000000..809b2a4222 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/GenericListsExampleTest.kt @@ -0,0 +1,158 @@ +package org.utbot.examples.collections + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +// TODO disabled tests should be fixes with SAT-1441 +internal class GenericListsExampleTest : UtValueTestCaseChecker( + testClass = GenericListsExample::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + @Disabled("Doesn't find branches without NPE") + fun testListOfListsOfT() { + check( + GenericListsExample::listOfListsOfT, + eq(-1) + ) + } + + @Test + @Disabled("Problems with memory") + fun testListOfComparable() { + check( + GenericListsExample::listOfComparable, + eq(1), + { v, r -> v != null && v.size > 1 && v[0] != null && v.all { it is Comparable<*> || it == null } && v == r }, + coverage = DoNotCalculate + ) + } + + @Test + fun testListOfT() { + check( + GenericListsExample::listOfT, + eq(1), + { v, r -> v != null && v.size >= 2 && v[0] != null && v == r }, + coverage = DoNotCalculate + ) + } + + @Test + @Disabled("Wrong number of matchers") + fun testListOfTArray() { + check( + GenericListsExample::listOfTArray, + eq(1) + ) + } + + @Test + @Disabled("JIRA:1446") + fun testListOfExtendsTArray() { + check( + GenericListsExample::listOfExtendsTArray, + eq(-1) + ) + } + + @Test + @Disabled("java.lang.ClassCastException: java.util.ArraysParallelSortHelpers\$FJShort\$Merger cannot be cast to [I") + fun testListOfPrimitiveArrayInheritors() { + check( + GenericListsExample::listOfPrimitiveArrayInheritors, + eq(-1) + ) + } + + @Test + @Disabled("JIRA:1620") + fun createWildcard() { + check( + GenericListsExample<*>::wildcard, + eq(4), + { v, r -> v == null && r?.isEmpty() == true }, + { v, r -> v != null && v.size == 1 && v[0] != null && v == r && v.all { it is Number || it == null } }, + { v, r -> v != null && (v.size != 1 || v[0] == null) && v == r && v.all { it is Number || it == null } }, + coverage = DoNotCalculate + ) + } + + @Suppress("NestedLambdaShadowedImplicitParameter") + @Test + @Disabled("unexpected empty nested list") + fun createListOfLists() { + check( + GenericListsExample<*>::listOfLists, + eq(1), + { v, r -> + val valueCondition = v != null && v[0] != null && v[0].isNotEmpty() + val typeCondition = v.all { (it is List<*> && it.all { it is Int || it == null }) || it == null } + + valueCondition && typeCondition && v == r + }, + coverage = DoNotCalculate + ) + } + + @Test + fun createWildcardWithOnlyQuestionMark() { + check( + GenericListsExample<*>::wildcardWithOnlyQuestionMark, + eq(3), + { v, r -> v == null && r?.isEmpty() == true }, + { v, r -> v.size == 1 && v == r }, + { v, r -> v.size != 1 && v == r }, + coverage = DoNotCalculate + ) + } + + + @Test + fun testGenericWithArrayOfPrimitives() { + check( + GenericListsExample<*>::genericWithArrayOfPrimitives, + eq(1), + { v, _ -> + val valueCondition = v != null && v.size >= 2 && v[0] != null && v[0].isNotEmpty() && v[0][0] != 0L + val typeCondition = v.all { it is LongArray || it == null } + + valueCondition && typeCondition + }, + coverage = DoNotCalculate + ) + } + + + @Test + fun testGenericWithObject() { + check( + GenericListsExample<*>::genericWithObject, + eq(1), + { v, r -> v != null && v.size >= 2 && v[0] != null && v[0] is Long && v == r }, + coverage = DoNotCalculate + ) + } + + + @Test + fun testGenericWithArrayOfArrays() { + check( + GenericListsExample<*>::genericWithArrayOfArrays, + eq(1), + { v, _ -> + val valueCondition = v != null && v.size >= 2 && v[0] != null && v[0].isNotEmpty() && v[0][0] != null + val typeCondition = v.all { + (it is Array<*> && it.isArrayOf>() && it.all { it.isArrayOf() || it == null}) || it == null + } + + valueCondition && typeCondition + }, + coverage = DoNotCalculate + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/LinkedListsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/LinkedListsTest.kt similarity index 94% rename from utbot-framework/src/test/kotlin/org/utbot/examples/collections/LinkedListsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/LinkedListsTest.kt index fab796ada3..645e69c68a 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/LinkedListsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/LinkedListsTest.kt @@ -1,21 +1,16 @@ package org.utbot.examples.collections -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.isException -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException // TODO failed Kotlin compilation (generics) SAT-1332 -internal class LinkedListsTest : AbstractTestCaseGeneratorTest( +internal class LinkedListsTest : UtValueTestCaseChecker( testClass = LinkedLists::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListAlgorithmsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListAlgorithmsTest.kt new file mode 100644 index 0000000000..a9b83353ae --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListAlgorithmsTest.kt @@ -0,0 +1,27 @@ +package org.utbot.examples.collections + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast + +// TODO failed Kotlin compilation SAT-1332 +class ListAlgorithmsTest : UtValueTestCaseChecker( + testClass = ListAlgorithms::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + + @Test + fun testMergeLists() { + check( + ListAlgorithms::mergeListsInplace, + eq(4), + { a, b, r -> b.subList(0, b.size - 1).any { a.last() < it } && r != null && r == r.sorted() }, + { a, b, r -> (a.subList(0, a.size - 1).any { b.last() <= it } || a.any { ai -> b.any { ai < it } }) && r != null && r == r.sorted() }, + { a, b, r -> a[0] < b[0] && r != null && r == r.sorted() }, + { a, b, r -> a[0] >= b[0] && r != null && r == r.sorted() }, + coverage = atLeast(94) + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListIteratorsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListIteratorsTest.kt new file mode 100644 index 0000000000..f27688be08 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListIteratorsTest.kt @@ -0,0 +1,134 @@ +package org.utbot.examples.collections + +import org.junit.jupiter.api.Disabled +import kotlin.math.min +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.FullWithAssumptions +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber + +// TODO failed Kotlin compilation (generics) SAT-1332 +internal class ListIteratorsTest : UtValueTestCaseChecker( + testClass = ListIterators::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testReturnIterator() { + withoutConcrete { // We need to check that a real class is returned but not `Ut` one + check( + ListIterators::returnIterator, + ignoreExecutionsNumber, + { l, r -> l.isEmpty() && r!!.asSequence().toList().isEmpty() }, + { l, r -> l.isNotEmpty() && r!!.asSequence().toList() == l }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testReturnListIterator() { + withoutConcrete { // We need to check that a real class is returned but not `Ut` one + check( + ListIterators::returnListIterator, + ignoreExecutionsNumber, + { l, r -> l.isEmpty() && r!!.asSequence().toList().isEmpty() }, + { l, r -> l.isNotEmpty() && r!!.asSequence().toList() == l }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testIterate() { + check( + ListIterators::iterate, + eq(3), + { l, _ -> l == null }, + { l, result -> l.isEmpty() && result == l }, + { l, result -> l.isNotEmpty() && result == l }, + coverage = DoNotCalculate + ) + } + + @Test + fun testIterateReversed() { + check( + ListIterators::iterateReversed, + eq(3), + { l, _ -> l == null }, + { l, result -> l.isEmpty() && result == l }, + { l, result -> l.isNotEmpty() && result == l.reversed() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testIterateForEach() { + check( + ListIterators::iterateForEach, + eq(4), + { l, _ -> l == null }, + { l, result -> l.isEmpty() && result == 0 }, + { l, _ -> l.isNotEmpty() && l.any { it == null } }, + { l, result -> l.isNotEmpty() && result == l.sum() }, + coverage = DoNotCalculate + ) + } + + @Test + @Disabled("Java 11 transition") + fun testAddElements() { + check( + ListIterators::addElements, + eq(5), + { l, _, _ -> l == null }, + { l, _, result -> l != null && l.isEmpty() && result == l }, + { l, arr, _ -> l != null && l.size > 0 && arr == null }, + { l, arr, _ -> l != null && arr != null && l.isNotEmpty() && arr.isEmpty() }, + { l, arr, _ -> l != null && arr != null && l.size > arr.size }, + coverage = DoNotCalculate + ) + } + + @Test + fun testSetElements() { + check( + ListIterators::setElements, + eq(5), + { l, _, _ -> l == null }, + { l, _, result -> l != null && l.isEmpty() && result == l }, + { l, arr, _ -> l != null && arr != null && l.size > arr.size }, + { l, arr, _ -> l != null && l.size > 0 && arr == null }, + { l, arr, result -> l != null && arr != null && l.size <= arr.size && result == arr.asList().take(l.size) }, + coverage = DoNotCalculate + ) + } + + @Test + fun testRemoveElements() { + check( + ListIterators::removeElements, + ignoreExecutionsNumber, // the exact number of the executions depends on the decisions made by PathSelector + // so we can have either six results or seven, depending on the [pathSelectorType] + // from UtSettings + { l, _, _ -> l == null }, + { l, i, _ -> l != null && i <= 0 }, + { l, i, _ -> l != null && l.isEmpty() && i > 0 }, + { l, i, _ -> l != null && i > 0 && l.subList(0, min(i, l.size)).any { it !is Int } }, + { l, i, _ -> l != null && i > 0 && l.subList(0, min(i, l.size)).any { it == null } }, + { l, i, _ -> l != null && l.isNotEmpty() && i > 0 }, + { l, i, result -> + require(l != null) + + val precondition = l.isNotEmpty() && i > 0 && l.subList(0, i).all { it is Int } + val postcondition = result == (l.subList(0, i - 1) + l.subList(min(l.size, i), l.size)) + + precondition && postcondition + }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/ListWrapperReturnsVoidTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListWrapperReturnsVoidTest.kt similarity index 75% rename from utbot-framework/src/test/kotlin/org/utbot/examples/collections/ListWrapperReturnsVoidTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListWrapperReturnsVoidTest.kt index ebbb7a8c18..462131d110 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/ListWrapperReturnsVoidTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListWrapperReturnsVoidTest.kt @@ -1,21 +1,20 @@ package org.utbot.examples.collections -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.isException -import org.utbot.framework.codegen.CodeGeneration +import org.junit.jupiter.api.Disabled import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.CodeGeneration +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException // TODO failed Kotlin compilation ($ in function names, generics) SAT-1220 SAT-1332 -internal class ListWrapperReturnsVoidTest : AbstractTestCaseGeneratorTest( +@Disabled("Java 11 transition") +internal class ListWrapperReturnsVoidTest : UtValueTestCaseChecker( testClass = ListWrapperReturnsVoidExample::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testRunForEach() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListsPart1Test.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListsPart1Test.kt new file mode 100644 index 0000000000..3562d5f572 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListsPart1Test.kt @@ -0,0 +1,25 @@ +package org.utbot.examples.collections + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber + +// TODO failed Kotlin compilation SAT-1332 +@Disabled +internal class ListsPart1Test : UtValueTestCaseChecker( + testClass = Lists::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testIterableContains() { + check( + Lists::iterableContains, + ignoreExecutionsNumber, + { iterable, _ -> iterable == null }, + { iterable, r -> 1 in iterable && r == true }, + { iterable, r -> 1 !in iterable && r == false }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListsPart2Test.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListsPart2Test.kt new file mode 100644 index 0000000000..d328160986 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListsPart2Test.kt @@ -0,0 +1,25 @@ +package org.utbot.examples.collections + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber + +// TODO failed Kotlin compilation SAT-1332 +@Disabled +internal class ListsPart2Test : UtValueTestCaseChecker( + testClass = Lists::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testCollectionContains() { + check( + Lists::collectionContains, + ignoreExecutionsNumber, + { collection, _ -> collection == null }, + { collection, r -> 1 in collection && r == true }, + { collection, r -> 1 !in collection && r == false }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListsPart3Test.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListsPart3Test.kt new file mode 100644 index 0000000000..d0b6d1f18f --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/ListsPart3Test.kt @@ -0,0 +1,263 @@ +package org.utbot.examples.collections + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.FullWithAssumptions +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between +import org.utbot.testing.isException + +// TODO failed Kotlin compilation SAT-1332 +internal class ListsPart3Test : UtValueTestCaseChecker( + testClass = Lists::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun createTest() { + check( + Lists::create, + eq(3), + { a, _ -> a == null }, + { a, r -> a != null && a.isEmpty() && r!!.isEmpty() }, + { a, r -> a != null && a.isNotEmpty() && r != null && r.isNotEmpty() && a.toList() == r.also { println(r) } }, + coverage = DoNotCalculate + ) + } + + @Test + fun testBigListFromParameters() { + check( + Lists::bigListFromParameters, + eq(1), + { list, r -> list.size == r && list.size == 11 }, + coverage = DoNotCalculate + ) + } + + @Test + fun testGetNonEmptyCollection() { + check( + Lists::getNonEmptyCollection, + eq(3), + { collection, _ -> collection == null }, + { collection, r -> collection.isEmpty() && r == null }, + { collection, r -> collection.isNotEmpty() && collection == r }, + coverage = DoNotCalculate + ) + } + + @Test + fun testGetFromAnotherListToArray() { + check( + Lists::getFromAnotherListToArray, + eq(4), + { l, _ -> l == null }, + { l, _ -> l.isEmpty() }, + { l, r -> l[0] == null && r == null }, + { l, r -> l[0] != null && r is Array<*> && r.isArrayOf() && r.size == 1 && r[0] == l[0] }, + coverage = DoNotCalculate + ) + } + + @Test + fun addElementsTest() { + check( + Lists::addElements, + eq(5), + { list, _, _ -> list == null }, + { list, a, _ -> list != null && list.size >= 2 && a == null }, + { list, _, r -> list.size < 2 && r == list }, + { list, a, r -> list.size >= 2 && a.size < 2 && r == list }, + { list, a, r -> + require(r != null) + + val sizeConstraint = list.size >= 2 && a.size >= 2 && r.size == list.size + a.size + val content = r.mapIndexed { i, it -> if (i < r.size) it == r[i] else it == a[i - r.size] }.all { it } + + sizeConstraint && content + }, + coverage = DoNotCalculate + ) + } + + @Test + fun removeElementsTest() { + checkWithException( + Lists::removeElements, + between(7..8), + { list, _, _, r -> list == null && r.isException() }, + { list, i, _, r -> list != null && i < 0 && r.isException() }, + { list, i, _, r -> list != null && i >= 0 && list.size > i && list[i] == null && r.isException() }, + { list, i, j, r -> + require(list != null && list[i] != null) + + val listConstraints = i >= 0 && list.size > i && (list.size <= j + 1 || j < 0) + val resultConstraint = r.isException() + + listConstraints && resultConstraint + }, + { list, i, j, r -> + require(list != null && list[i] != null) + + val k = j + if (i <= j) 1 else 0 + val indicesConstraint = i >= 0 && list.size > i && j >= 0 && list.size > j + 1 + val contentConstraint = list[i] != null && list[k] == null + val resultConstraint = r.isException() + + indicesConstraint && contentConstraint && resultConstraint + }, + { list, i, j, r -> + require(list != null) + + val k = j + if (i <= j) 1 else 0 + + val precondition = i >= 0 && list.size > i && j >= 0 && list.size > j + 1 && list[i] < list[k] + val postcondition = r.getOrNull() == list[i] + + precondition && postcondition + }, + { list, i, j, r -> + require(list != null) + + val k = j + if (i <= j) 1 else 0 + + val precondition = i >= 0 && list.size > i && j >= 0 && list.size > j + 1 && list[i] >= list[k] + val postcondition = r.getOrNull() == list[k] + + precondition && postcondition + }, + coverage = DoNotCalculate + ) + } + + @Test + fun createArrayWithDifferentTypeTest() { + check( + Lists::createWithDifferentType, + eq(2), + { x, r -> x % 2 != 0 && r is java.util.LinkedList && r == List(4) { it } }, + { x, r -> x % 2 == 0 && r is java.util.ArrayList && r == List(4) { it } }, + coverage = DoNotCalculate + ) + } + + @Test + fun getElementsTest() { + check( + Lists::getElements, + eq(4), + { x, _ -> x == null }, + { x, r -> x != null && x.isEmpty() && r!!.isEmpty() }, + { x, _ -> x != null && x.isNotEmpty() && x.any { it == null } }, + { x, r -> x != null && x.isNotEmpty() && x.all { it is Int } && r!!.toList() == x }, + coverage = DoNotCalculate + ) + } + + @Test + fun setElementsTest() { + check( + Lists::setElements, + eq(3), + { x, _ -> x == null }, + { x, r -> x != null && x.isEmpty() && r!!.isEmpty() }, + { x, r -> x != null && x.isNotEmpty() && r!!.containsAll(x.toList()) && r.size == x.size }, + coverage = DoNotCalculate + ) + } + + @Test + fun testClear() { + check( + Lists::clear, + eq(3), + { list, _ -> list == null }, + { list, r -> list.size >= 2 && r == emptyList() }, + { list, r -> list.size < 2 && r == emptyList() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testAddAll() { + check( + Lists::addAll, + eq(3), + { list, _, _ -> list == null }, + { list, i, r -> + list != null && list.isEmpty() && r != null && r.size == 1 && r[0] == i + }, + { list, i, r -> + list != null && list.isNotEmpty() && r != null && r.size == 1 + list.size && r == listOf(i) + list + }, + coverage = DoNotCalculate + ) + } + + @Test + fun testAddAllInIndex() { + check( + Lists::addAllByIndex, + eq(4), + { list, i, _ -> list == null && i >= 0 }, + { list, i, _ -> list == null && i < 0 }, + { list, i, r -> list != null && i >= list.size && r == list }, + { list, i, r -> + list != null && i in 0..list.lastIndex && r == list.toMutableList().apply { addAll(i, listOf(0, 1)) } + }, + coverage = DoNotCalculate + ) + } + + @Test + fun testAsListExample() { + withEnabledTestingCodeGeneration(testCodeGeneration = false) { // TODO Assemble model for java.util.ArrayList is returned, but actual type is java.util.Arrays.ArrayList https://github.com/UnitTestBot/UTBotJava/issues/398 + withoutConcrete { // TODO Concrete fail matchers with "Cannot show class" error + check( + Lists::asListExample, + eq(2), + { arr, r -> arr.isEmpty() && r!!.isEmpty() }, + { arr, r -> arr.isNotEmpty() && arr.contentEquals(r!!.toTypedArray()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + } + + @Test + @Disabled("TODO: add choosing proper type in list wrapper") + fun testRemoveFromList() { + checkWithException( + Lists::removeFromList, + ge(4), + { list, _, r -> list == null && r.isException() }, + { list, _, r -> list != null && list.isEmpty() && r.isException() }, + { list, i, r -> + require(list != null && list.lastOrNull() != null) + + list.isNotEmpty() && (i < 0 || i >= list.size) && r.isException() + }, + { list, i, r -> + require(list != null && list.lastOrNull() != null) + + val changedList = list.toMutableList().apply { + set(i, last()) + removeLast() + } + + val precondition = list.isNotEmpty() && i >= 0 && i < list.size + val postcondition = changedList == r.getOrNull() + + precondition && postcondition + }, + // TODO: add branches with conditions (list is LinkedList) and (list !is ArrayList && list !is LinkedList) + coverage = DoNotCalculate + ) + } + +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/MapEntrySetTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapEntrySetTest.kt similarity index 91% rename from utbot-framework/src/test/kotlin/org/utbot/examples/collections/MapEntrySetTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapEntrySetTest.kt index 30e50444ec..43ddf5218c 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/MapEntrySetTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapEntrySetTest.kt @@ -1,25 +1,20 @@ package org.utbot.examples.collections -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.isException -import org.utbot.examples.withPushingStateFromPathSelectorForConcrete -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.ge +import org.utbot.testcheckers.withPushingStateFromPathSelectorForConcrete +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException // TODO failed Kotlin compilation SAT-1332 -class MapEntrySetTest : AbstractTestCaseGeneratorTest( +class MapEntrySetTest : UtValueTestCaseChecker( testClass = MapEntrySet::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test @Disabled("JIRA:1443") @@ -148,11 +143,12 @@ class MapEntrySetTest : AbstractTestCaseGeneratorTest( @Test + @Disabled("Seems not aligning with ksmt version used in USVM") fun testIterateWithIterator() { withPushingStateFromPathSelectorForConcrete { checkWithException( MapEntrySet::iterateWithIterator, - eq(6), + ignoreExecutionsNumber, { map, result -> map == null && result.isException() }, { map, result -> map.isEmpty() && result.getOrThrow().contentEquals(intArrayOf(0, 0)) }, { map, result -> map.size % 2 == 1 && result.isException() }, diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/MapKeySetTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapKeySetTest.kt similarity index 88% rename from utbot-framework/src/test/kotlin/org/utbot/examples/collections/MapKeySetTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapKeySetTest.kt index 48172ee2d0..2ade7deaaa 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/MapKeySetTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapKeySetTest.kt @@ -1,27 +1,22 @@ package org.utbot.examples.collections -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.AtLeast -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.examples.withPushingStateFromPathSelectorForConcrete -import org.utbot.examples.withoutMinimization -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testcheckers.withPushingStateFromPathSelectorForConcrete +import org.utbot.testcheckers.withoutMinimization +import org.utbot.testing.AtLeast +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException // TODO failed Kotlin compilation SAT-1332 -class MapKeySetTest : AbstractTestCaseGeneratorTest( +class MapKeySetTest : UtValueTestCaseChecker( testClass = MapKeySet::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testRemoveFromKeySet() { diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/MapValuesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapValuesTest.kt similarity index 91% rename from utbot-framework/src/test/kotlin/org/utbot/examples/collections/MapValuesTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapValuesTest.kt index 45a78bea1c..41787b2a55 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/MapValuesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapValuesTest.kt @@ -1,26 +1,20 @@ package org.utbot.examples.collections -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.AtLeast -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.examples.withoutMinimization -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Test +import org.utbot.testcheckers.ge +import org.utbot.testcheckers.withoutMinimization +import org.utbot.testing.AtLeast +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException // TODO failed Kotlin compilation SAT-1332 -class MapValuesTest : AbstractTestCaseGeneratorTest( +class MapValuesTest : UtValueTestCaseChecker( testClass = MapValues::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testRemoveFromValues() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapsPart1Test.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapsPart1Test.kt new file mode 100644 index 0000000000..3ad15b9b7c --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapsPart1Test.kt @@ -0,0 +1,383 @@ +package org.utbot.examples.collections + +import org.junit.jupiter.api.Tag +import org.utbot.framework.plugin.api.MockStrategyApi +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testcheckers.withPushingStateFromPathSelectorForConcrete +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testcheckers.withoutMinimization +import org.utbot.testing.AtLeast +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between +import org.utbot.testing.ignoreExecutionsNumber + +// TODO failed Kotlin compilation ($ in names, generics) SAT-1220 SAT-1332 +internal class MapsPart1Test : UtValueTestCaseChecker( + testClass = Maps::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testPutElementIfAbsent() { + withoutMinimization { // TODO: JIRA:1506 + check( + Maps::putElementIfAbsent, + ignoreExecutionsNumber, + { map, _, _, _ -> map == null }, + { map, key, _, result -> map != null && key in map && result == map }, + { map, key, value, result -> + val valueWasPut = result!![key] == value && result.size == map.size + 1 + val otherValuesWerentTouched = result.entries.containsAll(map.entries) + key !in map && valueWasPut && otherValuesWerentTouched + }, + coverage = AtLeast(90) // unreachable else branch in MUT + ) + } + } + + @Test + fun testReplaceEntry() { + check( + Maps::replaceEntry, + between(3..6), + { map, _, _, _ -> map == null }, + { map, key, _, result -> key !in map && result == map }, + { map, key, value, result -> + val valueWasReplaced = result!![key] == value + val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } + key in map && valueWasReplaced && otherValuesWerentTouched + }, + coverage = DoNotCalculate + ) + } + + @Test + fun createTest() { + check( + Maps::create, + eq(5), + { keys, _, _ -> keys == null }, + { keys, _, result -> keys.isEmpty() && result!!.isEmpty() }, + { keys, values, result -> keys.isNotEmpty() && values == null }, + { keys, values, result -> keys.isNotEmpty() && values.size < keys.size }, + { keys, values, result -> + keys.isNotEmpty() && values.size >= keys.size && + result!!.size == keys.size && keys.indices.all { result[keys[it]] == values[it] } + }, + coverage = DoNotCalculate + ) + } + + @Test + fun testToString() { + check( + Maps::mapToString, + eq(1), + { a, b, c, r -> r == Maps().mapToString(a, b, c) } + ) + } + + @Test + fun testMapPutAndGet() { + withoutConcrete { + check( + Maps::mapPutAndGet, + eq(1), + { r -> r == 3 } + ) + } + } + + @Test + fun testPutInMapFromParameters() { + withoutConcrete { + check( + Maps::putInMapFromParameters, + ignoreExecutionsNumber, + { values, _ -> values == null }, + { values, r -> 1 in values.keys && r == 3 }, + { values, r -> 1 !in values.keys && r == 3 }, + ) + } + } + + // This test doesn't check anything specific, but the code from MUT + // caused errors with NPE as results while debugging `testPutInMapFromParameters`. + @Test + fun testContainsKeyAndPuts() { + withoutConcrete { + check( + Maps::containsKeyAndPuts, + ignoreExecutionsNumber, + { values, _ -> values == null }, + { values, r -> 1 !in values.keys && r == 3 }, + coverage = DoNotCalculate + ) + } + } + + @Test + fun testFindAllChars() { + check( + Maps::countChars, + eq(3), + { s, _ -> s == null }, + { s, result -> s == "" && result!!.isEmpty() }, + { s, result -> s != "" && result == s.groupingBy { it }.eachCount() }, + coverage = DoNotCalculate + ) + } + + @Test + fun putElementsTest() { + check( + Maps::putElements, + ge(5), + { map, _, _ -> map == null }, + { map, array, _ -> map != null && map.isNotEmpty() && array == null }, + { map, _, result -> map.isEmpty() && result == map }, + { map, array, result -> map.isNotEmpty() && array.isEmpty() && result == map }, + { map, array, result -> + map.size >= 1 && array.isNotEmpty() + && result == map.toMutableMap().apply { putAll(array.map { it to it }) } + }, + coverage = DoNotCalculate + ) + } + + @Test + fun removeEntries() { + check( + Maps::removeElements, + ge(6), + { map, _, _, _ -> map == null }, + { map, i, j, res -> map != null && (i !in map || map[i] == null) && (j !in map || map[j] == null) && res == -1 }, + { map, i, j, res -> map != null && map.isNotEmpty() && i !in map && j in map && res == 4 }, + { map, i, j, res -> map != null && map.isNotEmpty() && i in map && (j !in map || j == i) && res == 3 }, + { map, i, j, res -> map != null && map.size >= 2 && i in map && j in map && i > j && res == 2 }, + { map, i, j, res -> map != null && map.size >= 2 && i in map && j in map && i < j && res == 1 }, + coverage = DoNotCalculate + ) + } + + @Test + fun createWithDifferentTypeTest() { + check( + Maps::createWithDifferentType, + eq(2), + { seed, result -> seed % 2 != 0 && result is java.util.LinkedHashMap }, + { seed, result -> seed % 2 == 0 && result !is java.util.LinkedHashMap && result is java.util.HashMap }, + coverage = DoNotCalculate + ) + } + + @Test + fun removeCustomObjectTest() { + check( + Maps::removeCustomObject, + ge(3), + { map, _, _ -> map == null }, + { map, i, result -> (map.isEmpty() || CustomClass(i) !in map) && result == null }, + { map, i, result -> map.isNotEmpty() && CustomClass(i) in map && result == map[CustomClass(i)] }, + coverage = DoNotCalculate + ) + } + + @Test + @Tag("slow") // it takes about 20 minutes to execute this test + fun testMapOperator() { + withPushingStateFromPathSelectorForConcrete { + check( + Maps::mapOperator, + ignoreExecutionsNumber + ) + } + } + + @Test + fun testComputeValue() { + check( + Maps::computeValue, + between(3..5), + { map, _, _ -> map == null }, + { map, key, result -> + val valueWasUpdated = result!![key] == key + 1 + val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } + map[key] == null && valueWasUpdated && otherValuesWerentTouched + }, + { map, key, result -> + val valueWasUpdated = result!![key] == map[key]!! + 1 + val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } + map[key] != null && valueWasUpdated && otherValuesWerentTouched + }, + coverage = DoNotCalculate + ) + } + + @Test + fun testComputeValueWithMocks() { + check( + Maps::computeValue, + between(3..5), + { map, _, _ -> map == null }, + { map, key, result -> + val valueWasUpdated = result!![key] == key + 1 + val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } + map[key] == null && valueWasUpdated && otherValuesWerentTouched + }, + { map, key, result -> + val valueWasUpdated = result!![key] == map[key]!! + 1 + val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } + map[key] != null && valueWasUpdated && otherValuesWerentTouched + }, + mockStrategy = MockStrategyApi.OTHER_PACKAGES, // checks that we do not generate mocks for lambda classes + coverage = DoNotCalculate + ) + } + + @Test + fun testComputeValueIfAbsent() { + check( + Maps::computeValueIfAbsent, + between(3..5), + { map, _, _ -> map == null }, + { map, key, result -> map[key] != null && result == map }, + { map, key, result -> + val valueWasUpdated = result!![key] == key + 1 + val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } + map[key] == null && valueWasUpdated && otherValuesWerentTouched + }, + coverage = DoNotCalculate + ) + } + + @Test + fun testComputeValueIfPresent() { + check( + Maps::computeValueIfPresent, + between(3..5), + { map, _, _ -> map == null }, + { map, key, result -> map[key] == null && result == map }, + { map, key, result -> + val valueWasUpdated = result!![key] == map[key]!! + 1 + val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } + map[key] != null && valueWasUpdated && otherValuesWerentTouched + }, + coverage = DoNotCalculate + ) + } + + @Test + fun testClearEntries() { + check( + Maps::clearEntries, + between(3..4), + { map, _ -> map == null }, + { map, result -> map.isEmpty() && result == 0 }, + { map, result -> map.isNotEmpty() && result == 1 }, + coverage = DoNotCalculate + ) + } + + @Test + fun testContainsKey() { + check( + Maps::containsKey, + between(3..5), + { map, _, _ -> map == null }, + { map, key, result -> key !in map && result == 0 }, + { map, key, result -> key in map && result == 1 }, + coverage = DoNotCalculate + ) + } + + @Test + fun testContainsValue() { + check( + Maps::containsValue, + between(3..6), + { map, _, _ -> map == null }, + { map, value, result -> value !in map.values && result == 0 }, + { map, value, result -> value in map.values && result == 1 }, + coverage = DoNotCalculate + ) + } + + @Test + fun testGetOrDefaultElement() { + check( + Maps::getOrDefaultElement, + between(4..6), + { map, _, _ -> map == null }, + { map, i, result -> i !in map && result == 1 }, + { map, i, result -> i in map && map[i] == null && result == 0 }, + { map, i, result -> i in map && map[i] != null && result == map[i] }, + coverage = DoNotCalculate + ) + } + + @Test + fun testRemoveKeyWithValue() { + check( + Maps::removeKeyWithValue, + ge(6), + { map, _, _, _ -> map == null }, + { map, key, value, result -> key !in map && value !in map.values && result == 0 }, + { map, key, value, result -> key in map && value !in map.values && result == -1 }, + { map, key, value, result -> key !in map && value in map.values && result == -2 }, + { map, key, value, result -> key in map && map[key] == value && result == 3 }, + { map, key, value, result -> key in map && value in map.values && map[key] != value && result == -3 }, + coverage = DoNotCalculate + ) + } + + @Test + fun testReplaceAllEntries() { + check( + Maps::replaceAllEntries, + between(5..6), + { map, _ -> map == null }, + { map, result -> map.isEmpty() && result == null }, + { map, _ -> map.isNotEmpty() && map.containsValue(null) }, + { map, result -> + val precondition = map.isNotEmpty() && !map.containsValue(null) + val firstBranchInLambdaExists = map.entries.any { it.key > it.value } + val valuesWereReplaced = + result == map.mapValues { if (it.key > it.value) it.value + 1 else it.value - 1 } + precondition && firstBranchInLambdaExists && valuesWereReplaced + }, + { map, result -> + val precondition = map.isNotEmpty() && !map.containsValue(null) + val secondBranchInLambdaExists = map.entries.any { it.key <= it.value } + val valuesWereReplaced = + result == map.mapValues { if (it.key > it.value) it.value + 1 else it.value - 1 } + precondition && secondBranchInLambdaExists && valuesWereReplaced + }, + coverage = DoNotCalculate + ) + } + + @Test + fun testCreateMapWithString() { + withoutConcrete { + check( + Maps::createMapWithString, + eq(1), + { r -> r!!.isEmpty() } + ) + } + } + @Test + fun testCreateMapWithEnum() { + withoutConcrete { + check( + Maps::createMapWithEnum, + eq(1), + { r -> r != null && r.size == 2 && r[Maps.WorkDays.Monday] == 112 && r[Maps.WorkDays.Friday] == 567 } + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapsPart2Test.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapsPart2Test.kt new file mode 100644 index 0000000000..3e62c790b6 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/MapsPart2Test.kt @@ -0,0 +1,87 @@ +package org.utbot.examples.collections + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.ge +import org.utbot.testcheckers.withPushingStateFromPathSelectorForConcrete +import org.utbot.testcheckers.withoutMinimization +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException + +// TODO failed Kotlin compilation ($ in names, generics) SAT-1220 SAT-1332 +internal class MapsPart2Test : UtValueTestCaseChecker( + testClass = Maps::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testReplaceEntryWithValue() { + withPushingStateFromPathSelectorForConcrete { + check( + Maps::replaceEntryWithValue, + ge(6), + { map, _, _, _ -> map == null }, + { map, key, value, result -> key !in map && value !in map.values && result == 0 }, + { map, key, value, result -> key in map && value !in map.values && result == -1 }, + { map, key, value, result -> key !in map && value in map.values && result == -2 }, + { map, key, value, result -> key in map && map[key] == value && result == 3 }, + { map, key, value, result -> key in map && value in map.values && map[key] != value && result == -3 }, + coverage = DoNotCalculate + ) + } + } + + @Test + fun testMerge() { + withoutMinimization { // TODO: JIRA:1506 + checkWithException( + Maps::merge, + ge(5), + { map, _, _, result -> map == null && result.isException() }, + { map, _, value, result -> map != null && value == null && result.isException() }, + { map, key, value, result -> + val resultMap = result.getOrNull()!! + val entryWasPut = resultMap.entries.all { it.key == key && it.value == value || it in map.entries } + key !in map && value != null && entryWasPut + }, + { map, key, value, result -> + val resultMap = result.getOrNull()!! + val valueInMapIsNull = key in map && map[key] == null + val valueWasReplaced = resultMap[key] == value + val otherValuesWerentTouched = resultMap.entries.all { it.key == key || it in map.entries } + value != null && valueInMapIsNull && valueWasReplaced && otherValuesWerentTouched + }, + { map, key, value, result -> + val resultMap = result.getOrNull()!! + val valueInMapIsNotNull = map[key] != null + val valueWasMerged = resultMap[key] == map[key]!! + value + val otherValuesWerentTouched = resultMap.entries.all { it.key == key || it in map.entries } + value != null && valueInMapIsNotNull && valueWasMerged && otherValuesWerentTouched + }, + coverage = DoNotCalculate + ) + } + } + + @Disabled("Flaky https://github.com/UnitTestBot/UTBotJava/issues/1695") + fun testPutAllEntries() { + withPushingStateFromPathSelectorForConcrete { + check( + Maps::putAllEntries, + ge(5), + { map, _, _ -> map == null }, + { map, other, _ -> map != null && other == null }, + { map, other, result -> map != null && other != null && map.keys.containsAll(other.keys) && result == 0 }, + { map, other, result -> map != null && other != null && other.keys.all { it !in map.keys } && result == 1 }, + { map, other, result -> + val notNull = map != null && other != null + val mapContainsAtLeastOneKeyOfOther = other.keys.any { it in map.keys } + val mapDoesNotContainAllKeysOfOther = !map.keys.containsAll(other.keys) + notNull && mapContainsAtLeastOneKeyOfOther && mapDoesNotContainAllKeysOfOther && result == 2 + }, + coverage = DoNotCalculate + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/OptionalsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/OptionalsTest.kt similarity index 76% rename from utbot-framework/src/test/kotlin/org/utbot/examples/collections/OptionalsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/OptionalsTest.kt index 0203e3ef6a..3b9f1139ec 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/OptionalsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/OptionalsTest.kt @@ -1,23 +1,18 @@ package org.utbot.examples.collections -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.examples.singleValue -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Test - -class OptionalsTest : AbstractTestCaseGeneratorTest( +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException +import java.util.* + +class OptionalsTest : UtValueTestCaseChecker( Optionals::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @@ -67,7 +62,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( checkStatics( Optionals::createNullable, eq(2), - { value, statics, result -> value == null && result === statics.singleValue() }, + { value, _, result -> value == null && result === Optional.empty() }, { value, _, result -> value != null && result!!.get() == value }, coverage = DoNotCalculate ) @@ -78,7 +73,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( checkStatics( Optionals::createEmpty, eq(1), - { statics, result -> result === statics.singleValue() }, + { _, result -> result === Optional.empty() }, coverage = DoNotCalculate ) } @@ -88,7 +83,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( checkStatics( Optionals::createIntEmpty, eq(1), - { statics, result -> result === statics.singleValue() }, + { _, result -> result === OptionalInt.empty() }, coverage = DoNotCalculate ) } @@ -98,7 +93,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( checkStatics( Optionals::createLongEmpty, eq(1), - { statics, result -> result === statics.singleValue() }, + { _, result -> result === OptionalLong.empty() }, coverage = DoNotCalculate ) } @@ -108,7 +103,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( checkStatics( Optionals::createDoubleEmpty, eq(1), - { statics, result -> result === statics.singleValue() }, + { _, result -> result === OptionalDouble.empty() }, coverage = DoNotCalculate ) } @@ -119,7 +114,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::getValue, eq(3), { optional, _, _ -> optional == null }, - { optional, statics, result -> optional != null && optional === statics.singleValue() && result == null }, + { optional, _, result -> optional != null && optional === Optional.empty() && result == null }, { optional, _, result -> optional != null && result == optional.get() }, coverage = DoNotCalculate ) @@ -131,7 +126,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::getIntValue, eq(3), { optional, _, _ -> optional == null }, - { optional, statics, result -> optional != null && optional === statics.singleValue() && result == null }, + { optional, _, result -> optional != null && optional === OptionalInt.empty() && result == null }, { optional, _, result -> optional != null && result == optional.asInt }, coverage = DoNotCalculate ) @@ -143,7 +138,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::getLongValue, eq(3), { optional, _, _ -> optional == null }, - { optional, statics, result -> optional != null && optional === statics.singleValue() && result == null }, + { optional, _, result -> optional != null && optional === OptionalLong.empty() && result == null }, { optional, _, result -> optional != null && result == optional.asLong }, coverage = DoNotCalculate ) @@ -155,7 +150,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::getDoubleValue, eq(3), { optional, _, _ -> optional == null }, - { optional, statics, result -> optional != null && optional === statics.singleValue() && result == null }, + { optional, _, result -> optional != null && optional === OptionalDouble.empty() && result == null }, { optional, _, result -> optional != null && result == optional.asDouble }, coverage = DoNotCalculate ) @@ -167,7 +162,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::getWithIsPresent, eq(3), { optional, _, _ -> optional == null }, - { optional, statics, result -> optional === statics.singleValue() && result == null }, + { optional, _, result -> optional === Optional.empty() && result == null }, { optional, _, result -> optional.get() == result }, coverage = DoNotCalculate ) @@ -179,7 +174,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::countIfPresent, eq(3), { optional, _, _ -> optional == null }, - { optional, statics, result -> optional === statics.singleValue() && result == 0 }, + { optional, _, result -> optional === Optional.empty() && result == 0 }, { optional, _, result -> optional.get() == result }, coverage = DoNotCalculate ) @@ -191,7 +186,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::countIntIfPresent, ignoreExecutionsNumber, { optional, _, _ -> optional == null }, - { optional, statics, result -> optional === statics.singleValue() && result == 0 }, + { optional, _, result -> optional === OptionalInt.empty() && result == 0 }, { optional, _, result -> optional.asInt == result }, ) } @@ -202,7 +197,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::countLongIfPresent, ignoreExecutionsNumber, { optional, _, _ -> optional == null }, - { optional, statics, result -> optional === statics.singleValue() && result == 0L }, + { optional, _, result -> optional === OptionalLong.empty() && result == 0L }, { optional, _, result -> optional.asLong == result }, ) } @@ -213,7 +208,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::countDoubleIfPresent, ignoreExecutionsNumber, { optional, _, _ -> optional == null }, - { optional, statics, result -> optional === statics.singleValue() && result == 0.0 }, + { optional, _, result -> optional === OptionalDouble.empty() && result == 0.0 }, { optional, _, result -> optional.asDouble == result }, ) } @@ -224,9 +219,9 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::filterLessThanZero, eq(4), { optional, _, _ -> optional == null }, - { optional, statics, result -> optional === statics.singleValue() && result === optional }, + { optional, _, result -> optional === Optional.empty() && result === optional }, { optional, _, result -> optional.get() >= 0 && result == optional }, - { optional, statics, result -> optional.get() < 0 && result === statics.singleValue() }, + { optional, _, result -> optional.get() < 0 && result === Optional.empty() }, coverage = DoNotCalculate ) } @@ -237,7 +232,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::absNotNull, eq(4), { optional, _, _ -> optional == null }, - { optional, statics, result -> optional === statics.singleValue() && result === optional }, + { optional, _, result -> optional === Optional.empty() && result === optional }, { optional, _, result -> optional.get() < 0 && result!!.get() == -optional.get() }, { optional, _, result -> optional.get() >= 0 && result == optional }, coverage = DoNotCalculate @@ -250,8 +245,8 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::mapLessThanZeroToNull, eq(4), { optional, _, _ -> optional == null }, - { optional, statics, result -> optional === statics.singleValue() && result === optional }, - { optional, statics, result -> optional.get() < 0 && result === statics.singleValue() }, + { optional, _, result -> optional === Optional.empty() && result === optional }, + { optional, _, result -> optional.get() < 0 && result === Optional.empty() }, { optional, _, result -> optional.get() >= 0 && result == optional }, coverage = DoNotCalculate ) @@ -263,7 +258,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::flatAbsNotNull, eq(4), { optional, _, _ -> optional == null }, - { optional, statics, result -> optional === statics.singleValue() && result === optional }, + { optional, _, result -> optional === Optional.empty() && result === optional }, { optional, _, result -> optional.get() < 0 && result!!.get() == -optional.get() }, { optional, _, result -> optional.get() >= 0 && result == optional }, coverage = DoNotCalculate @@ -276,8 +271,8 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::flatMapWithNull, eq(5), { optional, _, _ -> optional == null }, - { optional, statics, result -> optional === statics.singleValue() && result === optional }, - { optional, statics, result -> optional.get() < 0 && result === statics.singleValue() }, + { optional, _, result -> optional === Optional.empty() && result === optional }, + { optional, _, result -> optional.get() < 0 && result === Optional.empty() }, { optional, _, result -> optional.get() > 0 && result == optional }, { optional, _, result -> optional.get() == 0 && result == null }, coverage = DoNotCalculate @@ -290,7 +285,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::leftOrElseRight, eq(3), { left, _, _, _ -> left == null }, - { left, right, statics, result -> left === statics.singleValue() && result == right }, + { left, right, _, result -> left === Optional.empty() && result == right }, { left, _, _, result -> left.isPresent && result == left.get() }, coverage = DoNotCalculate ) @@ -302,7 +297,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::leftIntOrElseRight, eq(3), { left, _, _, _ -> left == null }, - { left, right, statics, result -> left === statics.singleValue() && result == right }, + { left, right, _, result -> left === OptionalInt.empty() && result == right }, { left, _, _, result -> left.isPresent && result == left.asInt }, coverage = DoNotCalculate ) @@ -315,7 +310,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::leftLongOrElseRight, eq(3), { left, _, _, _ -> left == null }, - { left, right, statics, result -> left === statics.singleValue() && result == right }, + { left, right, _, result -> left === OptionalLong.empty() && result == right }, { left, _, _, result -> left.isPresent && result == left.asLong }, coverage = DoNotCalculate ) @@ -328,7 +323,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::leftDoubleOrElseRight, eq(3), { left, _, _, _ -> left == null }, - { left, right, statics, result -> left === statics.singleValue() && (result == right || result!!.isNaN() && right.isNaN()) }, + { left, right, _, result -> left === OptionalDouble.empty() && (result == right || result!!.isNaN() && right.isNaN()) }, { left, _, _, result -> left.isPresent && (result == left.asDouble || result!!.isNaN() && left.asDouble.isNaN()) }, coverage = DoNotCalculate ) @@ -341,7 +336,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::leftOrElseGetOne, eq(3), { left, _, _ -> left == null }, - { left, statics, result -> left === statics.singleValue() && result == 1 }, + { left, _, result -> left === Optional.empty() && result == 1 }, { left, _, result -> left.isPresent && result == left.get() }, coverage = DoNotCalculate ) @@ -353,7 +348,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::leftIntOrElseGetOne, eq(3), { left, _, _ -> left == null }, - { left, statics, result -> left === statics.singleValue() && result == 1 }, + { left, _, result -> left === OptionalInt.empty() && result == 1 }, { left, _, result -> left.isPresent && result == left.asInt }, coverage = DoNotCalculate ) @@ -365,7 +360,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::leftLongOrElseGetOne, eq(3), { left, _, _ -> left == null }, - { left, statics, result -> left === statics.singleValue() && result == 1L }, + { left, _, result -> left === OptionalLong.empty() && result == 1L }, { left, _, result -> left.isPresent && result == left.asLong }, coverage = DoNotCalculate ) @@ -377,7 +372,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::leftDoubleOrElseGetOne, eq(3), { left, _, _ -> left == null }, - { left, statics, result -> left === statics.singleValue() && result == 1.0 }, + { left, _, result -> left === OptionalDouble.empty() && result == 1.0 }, { left, _, result -> left.isPresent && result == left.asDouble }, coverage = DoNotCalculate ) @@ -389,7 +384,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::leftOrElseThrow, eq(3), { left, _, _ -> left == null }, - { left, statics, result -> left === statics.singleValue() && result == null }, + { left, _, result -> left === Optional.empty() && result == null }, { left, _, result -> left.isPresent && result == left.get() }, coverage = DoNotCalculate ) @@ -401,7 +396,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::leftIntOrElseThrow, eq(3), { left, _, _ -> left == null }, - { left, statics, result -> left === statics.singleValue() && result == null }, + { left, _, result -> left === OptionalInt.empty() && result == null }, { left, _, result -> left.isPresent && result == left.asInt }, coverage = DoNotCalculate ) @@ -413,7 +408,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::leftLongOrElseThrow, eq(3), { left, _, _ -> left == null }, - { left, statics, result -> left === statics.singleValue() && result == null }, + { left, _, result -> left === OptionalLong.empty() && result == null }, { left, _, result -> left.isPresent && result == left.asLong }, coverage = DoNotCalculate ) @@ -425,7 +420,7 @@ class OptionalsTest : AbstractTestCaseGeneratorTest( Optionals::leftDoubleOrElseThrow, eq(3), { left, _, _ -> left == null }, - { left, statics, result -> left === statics.singleValue() && result == null }, + { left, _, result -> left === OptionalDouble.empty() && result == null }, { left, _, result -> left.isPresent && result == left.asDouble }, coverage = DoNotCalculate ) diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/QueueUsagesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/QueueUsagesTest.kt new file mode 100644 index 0000000000..69a38564ab --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/QueueUsagesTest.kt @@ -0,0 +1,121 @@ +package org.utbot.examples.collections + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException + +class QueueUsagesTest : UtValueTestCaseChecker( + testClass = QueueUsages::class, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testCreateArrayDeque() { + checkWithException( + QueueUsages::createArrayDeque, + eq(3), + { init, next, r -> init == null && next == null && r.isException() }, + { init, next, r -> init != null && next == null && r.isException() }, + { init, next, r -> init != null && next != null && r.getOrNull() == 2 }, + ) + } + + @Test + fun testCreateLinkedList() { + checkWithException( + QueueUsages::createLinkedList, + eq(1), + { _, _, r -> r.getOrNull()!! == 2 }, + ) + } + + @Test + fun testCreateLinkedBlockingDeque() { + checkWithException( + QueueUsages::createLinkedBlockingDeque, + eq(3), + { init, next, r -> init == null && next == null && r.isException() }, + { init, next, r -> init != null && next == null && r.isException() }, + { init, next, r -> init != null && next != null && r.getOrNull() == 2 }, + ) + } + + @Test + fun testContainsQueue() { + checkWithException( + QueueUsages::containsQueue, + eq(3), + { q, _, r -> q == null && r.isException() }, + { q, x, r -> x in q && r.getOrNull() == 1 }, + { q, x, r -> x !in q && r.getOrNull() == 0 }, + ) + } + + @Test + fun testAddQueue() { + checkWithException( + QueueUsages::addQueue, + eq(3), + { q, _, r -> q == null && r.isException() }, + { q, x, r -> q != null && x in r.getOrNull()!! }, + { q, x, r -> q != null && x == null && r.isException() }, ) + } + + @Test + fun testAddAllQueue() { + checkWithException( + QueueUsages::addAllQueue, + eq(3), + { q, _, r -> q == null && r.isException() }, + { q, x, r -> q != null && x in r.getOrNull()!! }, // we can cover this line with x == null or x != null + { q, x, r -> q != null && x == null && r.isException() }, + ) + } + + @Test + fun testCastQueueToDeque() { + check( + QueueUsages::castQueueToDeque, + eq(2), + { q, r -> q !is java.util.Deque<*> && r == null }, + { q, r -> q is java.util.Deque<*> && r is java.util.Deque<*> }, + ) + } + + @Test + fun testCheckSubtypesOfQueue() { + check( + QueueUsages::checkSubtypesOfQueue, + eq(4), + { q, r -> q == null && r == 0 }, + { q, r -> q is java.util.LinkedList<*> && r == 1 }, + { q, r -> q is java.util.ArrayDeque<*> && r == 2 }, + { q, r -> q !is java.util.LinkedList<*> && q !is java.util.ArrayDeque && r == 3 } + ) + } + + @Test + @Disabled("TODO: Related to https://github.com/UnitTestBot/UTBotJava/issues/820") + fun testCheckSubtypesOfQueueWithUsage() { + check( + QueueUsages::checkSubtypesOfQueueWithUsage, + eq(4), + { q, r -> q == null && r == 0 }, + { q, r -> q is java.util.LinkedList<*> && r == 1 }, + { q, r -> q is java.util.ArrayDeque<*> && r == 2 }, + { q, r -> q !is java.util.LinkedList<*> && q !is java.util.ArrayDeque && r == 3 } // this is uncovered + ) + } + + @Test + fun testAddConcurrentLinkedQueue() { + checkWithException( + QueueUsages::addConcurrentLinkedQueue, + eq(3), + { q, _, r -> q == null && r.isException() }, + { q, x, r -> q != null && x != null && x in r.getOrNull()!! }, + { q, x, r -> q != null && x == null && r.isException() }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/SetIteratorsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/SetIteratorsTest.kt similarity index 79% rename from utbot-framework/src/test/kotlin/org/utbot/examples/collections/SetIteratorsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/SetIteratorsTest.kt index 89f05bb409..e5d1916058 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/SetIteratorsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/SetIteratorsTest.kt @@ -1,24 +1,33 @@ package org.utbot.examples.collections -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.between -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Test +import org.utbot.testcheckers.ge +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.FullWithAssumptions +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException // TODO failed Kotlin compilation SAT-1332 -class SetIteratorsTest : AbstractTestCaseGeneratorTest( +class SetIteratorsTest : UtValueTestCaseChecker( testClass = SetIterators::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { + @Test + fun testReturnIterator() { + withoutConcrete { // We need to check that a real class is returned but not `Ut` one + check( + SetIterators::returnIterator, + ignoreExecutionsNumber, + { s, r -> s.isEmpty() && r!!.asSequence().toSet().isEmpty() }, + { s, r -> s.isNotEmpty() && r!!.asSequence().toSet() == s }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + @Test fun testIteratorHasNext() { check( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/SetsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/SetsTest.kt similarity index 91% rename from utbot-framework/src/test/kotlin/org/utbot/examples/collections/SetsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/SetsTest.kt index 4ac6861aef..3f65f2676f 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/SetsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/collections/SetsTest.kt @@ -1,27 +1,22 @@ package org.utbot.examples.collections -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.AtLeast -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.withPushingStateFromPathSelectorForConcrete -import org.utbot.examples.withoutMinimization -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testcheckers.withPushingStateFromPathSelectorForConcrete +import org.utbot.testcheckers.withoutMinimization +import org.utbot.testing.AtLeast +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between +import org.utbot.testing.ignoreExecutionsNumber // TODO failed Kotlin compilation SAT-1332 -internal class SetsTest : AbstractTestCaseGeneratorTest( +internal class SetsTest : UtValueTestCaseChecker( testClass = Sets::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun createTest() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/controlflow/ConditionsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/controlflow/ConditionsTest.kt new file mode 100644 index 0000000000..91eac95e15 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/controlflow/ConditionsTest.kt @@ -0,0 +1,26 @@ +package org.utbot.examples.controlflow + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber + +internal class ConditionsTest : UtValueTestCaseChecker(testClass = Conditions::class) { + @Test + fun testSimpleCondition() { + check( + Conditions::simpleCondition, + eq(2), + { condition, r -> !condition && r == 0 }, + { condition, r -> condition && r == 1 } + ) + } + + @Test + fun testIfLastStatement() { + checkWithException( + Conditions::emptyBranches, + ignoreExecutionsNumber, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/controlflow/CycleDependedConditionTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/controlflow/CycleDependedConditionTest.kt new file mode 100644 index 0000000000..23f64d33d3 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/controlflow/CycleDependedConditionTest.kt @@ -0,0 +1,55 @@ +package org.utbot.examples.controlflow + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +internal class CycleDependedConditionTest : UtValueTestCaseChecker(testClass = CycleDependedCondition::class) { + @Test + fun testCycleDependedOneCondition() { + check( + CycleDependedCondition::oneCondition, + eq(3), + { x, r -> x <= 0 && r == 0 }, + { x, r -> x in 1..2 && r == 0 }, + { x, r -> x > 2 && r == 1 } + ) + } + + @Test + fun testCycleDependedTwoCondition() { + check( + CycleDependedCondition::twoCondition, + eq(4), + { x, r -> x <= 0 && r == 0 }, + { x, r -> x in 1..3 && r == 0 }, + { x, r -> x == 4 && r == 1 }, + { x, r -> x >= 5 && r == 0 } + ) + } + + + @Test + fun testCycleDependedThreeCondition() { + check( + CycleDependedCondition::threeCondition, + eq(4), + { x, r -> x <= 0 && r == 0 }, + { x, r -> x in 1..5 && r == 0 }, + { x, r -> x == 6 || x > 8 && r == 1 }, + { x, r -> x == 7 && r == 0 } + ) + } + + + @Test + fun testCycleDependedOneConditionHigherNumber() { + check( + CycleDependedCondition::oneConditionHigherNumber, + eq(3), + { x, r -> x <= 0 && r == 0 }, + { x, r -> x in 1..100 && r == 0 }, + { x, r -> x > 100 && r == 1 && r == 1 } + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/controlflow/CyclesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/controlflow/CyclesTest.kt new file mode 100644 index 0000000000..ab293e1e9e --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/controlflow/CyclesTest.kt @@ -0,0 +1,106 @@ +package org.utbot.examples.controlflow + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast +import org.utbot.testing.between +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException + +internal class CyclesTest : UtValueTestCaseChecker(testClass = Cycles::class) { + @Test + fun testForCycle() { + check( + Cycles::forCycle, + eq(3), + { x, r -> x <= 0 && r == -1 }, + { x, r -> x in 1..5 && r == -1 }, + { x, r -> x > 5 && r == 1 } + ) + } + + @Test + fun testForCycleFour() { + check( + Cycles::forCycleFour, + eq(3), + { x, r -> x <= 0 && r == -1 }, + { x, r -> x in 1..4 && r == -1 }, + { x, r -> x > 4 && r == 1 } + ) + } + + @Test + fun testForCycleJayHorn() { + check( + Cycles::forCycleFromJayHorn, + eq(2), + { x, r -> x <= 0 && r == 0 }, + { x, r -> x > 0 && r == 2 * x } + ) + } + + @Test + fun testFiniteCycle() { + check( + Cycles::finiteCycle, + eq(2), + { x, r -> x % 519 == 0 && (r as Int) % 519 == 0 }, + { x, r -> x % 519 != 0 && (r as Int) % 519 == 0 } + ) + } + + @Test + fun testWhileCycle() { + check( + Cycles::whileCycle, + eq(2), + { x, r -> x <= 0 && r == 0 }, + { x, r -> x > 0 && r == (0 until x).sum() } + ) + } + + @Test + fun testCallInnerWhile() { + check( + Cycles::callInnerWhile, + between(1..2), + { x, r -> x >= 42 && r == Cycles().callInnerWhile(x) } + ) + } + + @Test + fun testInnerLoop() { + check( + Cycles::innerLoop, + ignoreExecutionsNumber, + { x, r -> x in 1..3 && r == 0 }, + { x, r -> x == 4 && r == 1 }, + { x, r -> x >= 5 && r == 0 } + ) + } + + @Test + fun testDivideByZeroCheckWithCycles() { + checkWithException( + Cycles::divideByZeroCheckWithCycles, + eq(3), + { n, _, r -> n < 5 && r.isException() }, + { n, x, r -> n >= 5 && x == 0 && r.isException() }, + { n, x, r -> n >= 5 && x != 0 && r.getOrNull() == Cycles().divideByZeroCheckWithCycles(n, x) } + ) + } + + @Test + fun moveToExceptionTest() { + checkWithException( + Cycles::moveToException, + eq(3), + { x, r -> x < 400 && r.isException() }, + { x, r -> x > 400 && r.isException() }, + { x, r -> x == 400 && r.isException() }, + coverage = atLeast(85) + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/controlflow/SwitchTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/controlflow/SwitchTest.kt new file mode 100644 index 0000000000..0163d095d0 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/controlflow/SwitchTest.kt @@ -0,0 +1,55 @@ +package org.utbot.examples.controlflow + +import java.math.RoundingMode.CEILING +import java.math.RoundingMode.DOWN +import java.math.RoundingMode.HALF_DOWN +import java.math.RoundingMode.HALF_EVEN +import java.math.RoundingMode.HALF_UP +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testcheckers.withoutMinimization +import org.utbot.testing.UtValueTestCaseChecker + +internal class SwitchTest : UtValueTestCaseChecker(testClass = Switch::class) { + @Test + fun testSimpleSwitch() { + check( + Switch::simpleSwitch, + ge(4), + { x, r -> x == 10 && r == 10 }, + { x, r -> (x == 11 || x == 12) && r == 12 }, // fall-through has it's own branch + { x, r -> x == 13 && r == 13 }, + { x, r -> x !in 10..13 && r == -1 }, // one for default is enough + ) + } + + @Test + fun testLookupSwitch() { + check( + Switch::lookupSwitch, + ge(4), + { x, r -> x == 0 && r == 0 }, + { x, r -> (x == 10 || x == 20) && r == 20 }, // fall-through has it's own branch + { x, r -> x == 30 && r == 30 }, + { x, r -> x !in setOf(0, 10, 20, 30) && r == -1 } // one for default is enough + ) + } + + @Test + fun testEnumSwitch() { + withoutMinimization { // TODO: JIRA:1506 + check( + Switch::enumSwitch, + eq(7), + { m, r -> m == null && r == null }, // NPE + { m, r -> m == HALF_DOWN && r == 1 }, // We will minimize two of these branches + { m, r -> m == HALF_EVEN && r == 1 }, // We will minimize two of these branches + { m, r -> m == HALF_UP && r == 1 }, // We will minimize two of these branches + { m, r -> m == DOWN && r == 2 }, + { m, r -> m == CEILING && r == 3 }, + { m, r -> m !in setOf(HALF_DOWN, HALF_EVEN, HALF_UP, DOWN, CEILING) && r == -1 }, + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt new file mode 100644 index 0000000000..4019cd0168 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt @@ -0,0 +1,208 @@ +package org.utbot.examples.enums + +import org.junit.jupiter.api.Disabled +import org.utbot.examples.enums.ClassWithEnum.StatusEnum.ERROR +import org.utbot.examples.enums.ClassWithEnum.StatusEnum.READY +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.util.id +import org.junit.jupiter.api.Test +import org.utbot.examples.enums.ClassWithEnum.StatusEnum +import org.utbot.framework.plugin.api.util.jField +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withPushingStateFromPathSelectorForConcrete +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException + +class ClassWithEnumTest : UtValueTestCaseChecker(testClass = ClassWithEnum::class) { + @Test + fun testOrdinal() { + withoutConcrete { + checkAllCombinations(ClassWithEnum::useOrdinal) + } + } + + @Test + fun testGetter() { + check( + ClassWithEnum::useGetter, + eq(2), + { s, r -> s == null && r == -1 }, + { s, r -> s != null && r == 0 }, + ) + } + + @Test + fun testDifficultIfBranch() { + check( + ClassWithEnum::useEnumInDifficultIf, + ignoreExecutionsNumber, + { s, r -> s.equals("TRYIF", ignoreCase = true) && r == 1 }, + { s, r -> !s.equals("TRYIF", ignoreCase = true) && r == 2 }, + ) + } + + @Test + @Disabled("Seems not aligning with ksmt version used in USVM") + fun testNullParameter() { + check( + ClassWithEnum::nullEnumAsParameter, + between(2..3), + { e, _ -> e == null }, + { e, r -> e == READY && r == 0 || e == ERROR && r == -1 }, + ) + } + + @Test + fun testNullField() { + checkWithException( + ClassWithEnum::nullField, + eq(3), + { e, r -> e == null && r.isException() }, + { e, r -> e == ERROR && r.isException() }, + { e, r -> e == READY && r.getOrNull()!! == 3 && READY.s.length == 3 }, + ) + } + + @Suppress("KotlinConstantConditions") + @Test + fun testChangeEnum() { + checkWithException( + ClassWithEnum::changeEnum, + eq(2), + { e, r -> e == READY && r.getOrNull()!! == ERROR.ordinal }, + { e, r -> (e == ERROR || e == null) && r.getOrNull()!! == READY.ordinal }, + ) + } + + @Test + fun testChangeMutableField() { + // TODO testing code generation for this method is disabled because we need to restore original field state + // should be enabled after solving https://github.com/UnitTestBot/UTBotJava/issues/80 + withEnabledTestingCodeGeneration(testCodeGeneration = false) { + checkWithException( + ClassWithEnum::changeMutableField, + eq(2), + { e, r -> e == READY && r.getOrNull()!! == 2 }, + { e, r -> (e == null || e == ERROR) && r.getOrNull()!! == -2 }, + ) + } + } + + @Test + @Disabled("https://github.com/UnitTestBot/UTBotJava/issues/1745") + fun testCheckName() { + check( + ClassWithEnum::checkName, + eq(3), + { s, _ -> s == null }, + { s, r -> s == READY.name && r == ERROR.name }, + { s, r -> s != READY.name && r == READY.name }, + ) + } + + @Test + fun testChangingStaticWithEnumInit() { + checkThisAndStaticsAfter( + ClassWithEnum::changingStaticWithEnumInit, + eq(1), + { t, staticsAfter, r -> + // We cannot check `x` since it is not a meaningful value since + // it is accessed only in a static initializer. + + // For some reasons x is inaccessible + // val x = FieldId(t.javaClass.id, "x").jField.get(t) as Int + + val y = staticsAfter[FieldId(ClassWithEnum.ClassWithStaticField::class.id, "y")]!!.value as Int + + val areStaticsCorrect = /*x == 1 &&*/ y == 11 + areStaticsCorrect && r == true + } + ) + } + + @Test + fun testVirtualFunction() { + check( + ClassWithEnum::virtualFunction, + eq(3), + { parameter, _ -> parameter == null }, + { parameter, r -> r == 1 && parameter == ERROR }, + { parameter, r -> r == 0 && parameter == READY }, + ) + } + + @Test + fun testEnumValues() { + checkStaticMethod( + StatusEnum::values, + eq(1), + { r -> r.contentEquals(arrayOf(READY, ERROR)) }, + ) + } + + @Test + fun testFromCode() { + checkStaticMethod( + StatusEnum::fromCode, + eq(3), + { code, r -> code == 10 && r == READY }, + { code, r -> code == -10 && r == ERROR }, + { code, r -> code !in setOf(10, -10) && r == null }, // IllegalArgumentException + ) + } + + @Test + fun testFromIsReady() { + checkStaticMethod( + StatusEnum::fromIsReady, + eq(2), + { isFirst, r -> isFirst && r == READY }, + { isFirst, r -> !isFirst && r == ERROR }, + ) + } + + @Test + fun testPublicGetCodeMethod() { + checkWithThis( + StatusEnum::publicGetCode, + between(1..2), + { enumInstance, r -> enumInstance == READY && r == 10 || enumInstance == ERROR && r == -10 }, + ) + } + + @Test + fun testImplementingInterfaceEnumInDifficultBranch() { + withPushingStateFromPathSelectorForConcrete { + check( + ClassWithEnum::implementingInterfaceEnumInDifficultBranch, + ignoreExecutionsNumber, + { s, r -> s.equals("SUCCESS", ignoreCase = true) && r == 0 }, + { s, r -> !s.equals("SUCCESS", ignoreCase = true) && r == 2 }, + ) + } + } + + @Test + fun testAffectSystemStaticAndUseInitEnumFromIt() { + check( + ClassWithEnum::affectSystemStaticAndInitEnumFromItAndReturnField, + eq(1), + { r -> r == true }, + coverage = DoNotCalculate + ) + } + + @Test + fun testAffectSystemStaticAndInitEnumFromItAndGetItFromEnumFun() { + check( + ClassWithEnum::affectSystemStaticAndInitEnumFromItAndGetItFromEnumFun, + eq(1), + { r -> r == true }, + coverage = DoNotCalculate + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ComplexEnumExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ComplexEnumExamplesTest.kt new file mode 100644 index 0000000000..1a3a258d82 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/enums/ComplexEnumExamplesTest.kt @@ -0,0 +1,102 @@ +package org.utbot.examples.enums + +import org.junit.jupiter.api.Test +import org.utbot.examples.enums.ComplexEnumExamples.Color +import org.utbot.examples.enums.ComplexEnumExamples.Color.BLUE +import org.utbot.examples.enums.ComplexEnumExamples.Color.GREEN +import org.utbot.examples.enums.ComplexEnumExamples.Color.RED +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber + +class ComplexEnumExamplesTest : UtValueTestCaseChecker( + testClass = ComplexEnumExamples::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testEnumToEnumMapCountValues() { + check( + ComplexEnumExamples::enumToEnumMapCountValues, + ignoreExecutionsNumber, + { m, r -> m.isEmpty() && r == 0 }, + { m, r -> m.isNotEmpty() && !m.values.contains(RED) && r == 0 }, + { m, r -> m.isNotEmpty() && m.values.contains(RED) && m.values.count { it == RED } == r } + ) + } + + @Test + fun testEnumToEnumMapCountKeys() { + check( + ComplexEnumExamples::enumToEnumMapCountKeys, + ignoreExecutionsNumber, + { m, r -> m.isEmpty() && r == 0 }, + { m, r -> m.isNotEmpty() && !m.keys.contains(GREEN) && !m.keys.contains(BLUE) && r == 0 }, + { m, r -> m.isNotEmpty() && m.keys.intersect(setOf(BLUE, GREEN)).isNotEmpty() && m.keys.count { it == BLUE || it == GREEN } == r } + ) + } + + @Test + fun testEnumToEnumMapCountMatches() { + check( + ComplexEnumExamples::enumToEnumMapCountMatches, + ignoreExecutionsNumber, + { m, r -> m.isEmpty() && r == 0 }, + { m, r -> m.entries.count { it.key == it.value } == r } + ) + } + + @Test + fun testCountEqualColors() { + check( + ComplexEnumExamples::countEqualColors, + ignoreExecutionsNumber, + { a, b, c, r -> a == b && a == c && r == 3 }, + { a, b, c, r -> setOf(a, b, c).size == 2 && r == 2 }, + { a, b, c, r -> a != b && b != c && a != c && r == 1 } + ) + } + + @Test + fun testCountNullColors() { + check( + ComplexEnumExamples::countNullColors, + eq(3), + { a, b, r -> a == null && b == null && r == 2 }, + { a, b, r -> (a == null) != (b == null) && r == 1 }, + { a, b, r -> a != null && b != null && r == 0 }, + ) + } + + @Test + fun testFindState() { + check( + ComplexEnumExamples::findState, + ignoreExecutionsNumber, + { c, r -> c in setOf(0, 127, 255) && r != null && r.code == c } + ) + } + + @Test + fun testCountValuesInArray() { + fun Color.isCorrectlyCounted(inputs: Array, counts: Map): Boolean = + inputs.count { it == this } == (counts[this] ?: 0) + + check( + ComplexEnumExamples::countValuesInArray, + ignoreExecutionsNumber, + { cs, r -> cs.isEmpty() && r != null && r.isEmpty() }, + { cs, r -> cs.toList().isEmpty() && r != null && r.isEmpty() }, + { cs, r -> cs.toList().isNotEmpty() && r != null && Color.values().all { it.isCorrectlyCounted(cs, r) } } + ) + } + + @Test + fun testCountRedInArray() { + check( + ComplexEnumExamples::countRedInArray, + eq(3), + { colors, result -> colors.count { it == RED } == result } + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/exceptions/ExceptionClusteringChecker.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/exceptions/ExceptionClusteringChecker.kt new file mode 100644 index 0000000000..4e67505768 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/exceptions/ExceptionClusteringChecker.kt @@ -0,0 +1,57 @@ +package org.utbot.examples.exceptions + +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtExplicitlyThrownException +import org.utbot.framework.plugin.api.UtImplicitlyThrownException +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtTimeoutException +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.ge +import org.utbot.testing.UtModelTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.primitiveValue + +internal class ExceptionClusteringChecker : + UtModelTestCaseChecker(testClass = ExceptionClusteringExamples::class) { + /** + * Difference is in throwing unchecked exceptions - for method under test is [UtExpectedCheckedThrow]. + */ + @Test + fun testDifferentExceptions() { + check( + ExceptionClusteringExamples::differentExceptions, + ignoreExecutionsNumber, + { i, r -> i.int() == 0 && r is UtImplicitlyThrownException && r.exception is ArithmeticException }, + { i, r -> i.int() == 1 && r is UtExplicitlyThrownException && r.exception is MyCheckedException }, + { i, r -> i.int() == 2 && r is UtExplicitlyThrownException && r.exception is IllegalArgumentException }, + { i, r -> i.int() !in 0..2 && r is UtExecutionSuccess && r.model.int() == 2 * i.int() }, + ) + } + + /** + * Difference is in throwing unchecked exceptions - for nested call it is [UtUnexpectedUncheckedThrow]. + */ + @Test + fun testDifferentExceptionsInNestedCall() { + check( + ExceptionClusteringExamples::differentExceptionsInNestedCall, + ignoreExecutionsNumber, + { i, r -> i.int() == 0 && r is UtImplicitlyThrownException && r.exception is ArithmeticException }, + { i, r -> i.int() == 1 && r is UtExplicitlyThrownException && r.exception is MyCheckedException }, + { i, r -> i.int() == 2 && r is UtExplicitlyThrownException && r.exception is IllegalArgumentException }, + { i, r -> i.int() !in 0..2 && r is UtExecutionSuccess && r.model.int() == 2 * i.int() }, + ) + } + + @Test + fun testSleepingMoreThanDefaultTimeout() { + check( + ExceptionClusteringExamples::sleepingMoreThanDefaultTimeout, + ge(1), + { _, r -> r is UtTimeoutException }, // we will minimize one of these: i <= 0 or i > 0 + ) + } +} + +private fun UtModel.int(): Int = this.primitiveValue() + diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/exceptions/ExceptionExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/exceptions/ExceptionExamplesTest.kt new file mode 100644 index 0000000000..efef1f5990 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/exceptions/ExceptionExamplesTest.kt @@ -0,0 +1,136 @@ +package org.utbot.examples.exceptions + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException + +internal class ExceptionExamplesTest : UtValueTestCaseChecker( + testClass = ExceptionExamples::class, + testCodeGeneration = true, + // TODO: Kotlin code generation fails because we construct lists with generics + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testInitAnArray() { + check( + ExceptionExamples::initAnArray, + ignoreExecutionsNumber, + { n, r -> n < 0 && r == -2 }, + { n, r -> n == 0 || n == 1 && r == -3 }, + { n, r -> n > 1 && r == 2 * n + 3 }, + coverage = atLeast(80) + ) + } + + @Test + fun testNestedExceptions() { + check( + ExceptionExamples::nestedExceptions, + eq(3), + { i, r -> i < 0 && r == -100 }, + { i, r -> i > 0 && r == 100 }, + { i, r -> i == 0 && r == 0 }, + ) + } + + @Test + fun testDoNotCatchNested() { + checkWithException( + ExceptionExamples::doNotCatchNested, + eq(3), + { i, r -> i < 0 && r.isException() }, + { i, r -> i > 0 && r.isException() }, + { i, r -> i == 0 && r.getOrThrow() == 0 }, + ) + } + + @Test + fun testFinallyThrowing() { + checkWithException( + ExceptionExamples::finallyThrowing, + eq(2), + { i, r -> i <= 0 && r.isException() }, + { i, r -> i > 0 && r.isException() }, + ) + } + + @Test + fun testFinallyChanging() { + check( + ExceptionExamples::finallyChanging, + eq(2), + { i, r -> i * 2 <= 0 && r == i * 2 + 10 }, + { i, r -> i * 2 > 0 && r == i * 2 + 110 }, + coverage = atLeast(80) // differs from JaCoCo + ) + } + + @Test + fun testThrowException() { + checkWithException( + ExceptionExamples::throwException, + eq(2), + { i, r -> i <= 0 && r.getOrNull() == 101 }, + { i, r -> i > 0 && r.isException() }, + coverage = atLeast(66) // because of unexpected exception thrown + ) + } + + @Test + fun testCreateException() { + check( + ExceptionExamples::createException, + eq(1), + { r -> r is java.lang.IllegalArgumentException }, + ) + } + + /** + * Used for path generation check in [org.utbot.engine.Traverser.fullPath] + */ + @Test + fun testCatchDeepNestedThrow() { + checkWithException( + ExceptionExamples::catchDeepNestedThrow, + eq(2), + { i, r -> i < 0 && r.isException() }, + { i, r -> i >= 0 && r.getOrThrow() == i }, + coverage = atLeast(66) // because of unexpected exception thrown + ) + } + + /** + * Covers [#656](https://github.com/UnitTestBot/UTBotJava/issues/656). + */ + @Test + fun testCatchExceptionAfterOtherPossibleException() { + withoutConcrete { + checkWithException( + ExceptionExamples::catchExceptionAfterOtherPossibleException, + eq(3), + { i, r -> i == -1 && r.isException() }, + { i, r -> i == 0 && r.getOrThrow() == 2 }, + { i, r -> r.getOrThrow() == 1 }, + coverage = atLeast(100) + ) + } + } + + /** + * Used for path generation check in [org.utbot.engine.Traverser.fullPath] + */ + @Test + fun testDontCatchDeepNestedThrow() { + checkWithException( + ExceptionExamples::dontCatchDeepNestedThrow, + eq(2), + { i, r -> i < 0 && r.isException() }, + { i, r -> i >= 0 && r.getOrThrow() == i }, + coverage = atLeast(66) // because of unexpected exception thrown + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/exceptions/JvmCrashExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/exceptions/JvmCrashExamplesTest.kt new file mode 100644 index 0000000000..6a9449d4f7 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/exceptions/JvmCrashExamplesTest.kt @@ -0,0 +1,41 @@ +package org.utbot.examples.exceptions + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutSandbox +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +internal class JvmCrashExamplesTest : UtValueTestCaseChecker(testClass = JvmCrashExamples::class) { + @Test + @Disabled("JIRA:1527") + fun testExit() { + check( + JvmCrashExamples::exit, + eq(2) + ) + } + + @Test + fun testCrash() { + withoutSandbox { + check( + JvmCrashExamples::crash, + eq(1), // we expect only one execution after minimization + // It seems that we can't calculate coverage when the child JVM has crashed + coverage = DoNotCalculate + ) + } + } + + @Test + fun testCrashPrivileged() { + check( + JvmCrashExamples::crashPrivileged, + eq(1), // we expect only one execution after minimization + // It seems that we can't calculate coverage when the child JVM has crashed + coverage = DoNotCalculate + ) + } +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/invokes/InvokeExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/InvokeExampleTest.kt similarity index 95% rename from utbot-framework/src/test/kotlin/org/utbot/examples/invokes/InvokeExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/InvokeExampleTest.kt index 1fdf6aab18..5abc14bd7d 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/invokes/InvokeExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/InvokeExampleTest.kt @@ -1,13 +1,13 @@ package org.utbot.examples.invokes -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException -internal class InvokeExampleTest : AbstractTestCaseGeneratorTest(testClass = InvokeExample::class) { +internal class InvokeExampleTest : UtValueTestCaseChecker(testClass = InvokeExample::class) { @Test fun testSimpleFormula() { check( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/invokes/NativeExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/NativeExampleTest.kt similarity index 79% rename from utbot-framework/src/test/kotlin/org/utbot/examples/invokes/NativeExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/NativeExampleTest.kt index 4ce3b78c28..f437801c68 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/invokes/NativeExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/NativeExampleTest.kt @@ -1,16 +1,16 @@ package org.utbot.examples.invokes -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.atLeast -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber import kotlin.math.ln import kotlin.math.sqrt import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast +import org.utbot.testing.ignoreExecutionsNumber -internal class NativeExampleTest : AbstractTestCaseGeneratorTest(testClass = NativeExample::class) { +internal class NativeExampleTest : UtValueTestCaseChecker(testClass = NativeExample::class) { @Test fun testPartialEx() { check( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/invokes/SimpleInterfaceExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/SimpleInterfaceExampleTest.kt similarity index 85% rename from utbot-framework/src/test/kotlin/org/utbot/examples/invokes/SimpleInterfaceExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/SimpleInterfaceExampleTest.kt index 5394703eaa..98e19a19b8 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/invokes/SimpleInterfaceExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/SimpleInterfaceExampleTest.kt @@ -1,10 +1,10 @@ package org.utbot.examples.invokes -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker -internal class SimpleInterfaceExampleTest : AbstractTestCaseGeneratorTest( +internal class SimpleInterfaceExampleTest : UtValueTestCaseChecker( testClass = SimpleInterfaceExample::class ) { @Test diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/StaticInvokeExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/StaticInvokeExampleTest.kt new file mode 100644 index 0000000000..5c75b0f0cb --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/StaticInvokeExampleTest.kt @@ -0,0 +1,22 @@ +package org.utbot.examples.invokes + +import kotlin.math.max +import org.junit.jupiter.api.Test +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between + +internal class StaticInvokeExampleTest : UtValueTestCaseChecker(testClass = StaticInvokeExample::class) { + // TODO: inline local variables when types inference bug in Kotlin fixed + @Test + fun testMaxForThree() { + val method = StaticInvokeExample::maxForThree + checkStaticMethod( + method, + between(2..3), // two executions can cover all branches + { x, y, _, _ -> x > y }, + { x, y, _, _ -> x <= y }, + { x, y, z, _ -> max(x, y.toInt()) > z }, + { x, y, z, _ -> max(x, y.toInt()) <= z }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/invokes/VirtualInvokeExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/VirtualInvokeExampleTest.kt similarity index 93% rename from utbot-framework/src/test/kotlin/org/utbot/examples/invokes/VirtualInvokeExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/VirtualInvokeExampleTest.kt index 7acbb9f503..57536896dc 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/invokes/VirtualInvokeExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/invokes/VirtualInvokeExampleTest.kt @@ -2,15 +2,14 @@ package org.utbot.examples.invokes -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.isException import java.lang.Boolean -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException -internal class VirtualInvokeExampleTest : AbstractTestCaseGeneratorTest(testClass = VirtualInvokeExample::class) { +internal class VirtualInvokeExampleTest : UtValueTestCaseChecker(testClass = VirtualInvokeExample::class) { @Test fun testSimpleVirtualInvoke() { checkWithException( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt new file mode 100644 index 0000000000..39c88329e8 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/CustomPredicateExampleTest.kt @@ -0,0 +1,78 @@ +package org.utbot.examples.lambda + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.testcheckers.eq +import org.utbot.testing.CodeGeneration +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException + +class CustomPredicateExampleTest : UtValueTestCaseChecker( + testClass = CustomPredicateExample::class, + // TODO: https://github.com/UnitTestBot/UTBotJava/issues/88 (generics in Kotlin) + // At the moment, when we create an instance of a functional interface via lambda (through reflection), + // we need to do a type cast (e.g. `obj as Predicate`), but since generics are not supported yet, + // we use a raw type (e.g. `Predicate`) instead (which is not allowed in Kotlin). + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testNoCapturedValuesPredicateCheck() { + checkWithException( + CustomPredicateExample::noCapturedValuesPredicateCheck, + eq(3), + { predicate, x, r -> !predicate.test(x) && r.getOrNull() == false }, + { predicate, x, r -> predicate.test(x) && r.getOrNull() == true }, + { predicate, _, r -> predicate == null && r.isException() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testCapturedLocalVariablePredicateCheck() { + checkWithException( + CustomPredicateExample::capturedLocalVariablePredicateCheck, + eq(3), + { predicate, x, r -> !predicate.test(x) && r.getOrNull() == false }, + { predicate, x, r -> predicate.test(x) && r.getOrNull() == true }, + { predicate, _, r -> predicate == null && r.isException() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testCapturedParameterPredicateCheck() { + checkWithException( + CustomPredicateExample::capturedParameterPredicateCheck, + eq(3), + { predicate, x, r -> !predicate.test(x) && r.getOrNull() == false }, + { predicate, x, r -> predicate.test(x) && r.getOrNull() == true }, + { predicate, _, r -> predicate == null && r.isException() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testCapturedStaticFieldPredicateCheck() { + checkWithException( + CustomPredicateExample::capturedStaticFieldPredicateCheck, + eq(3), + { predicate, x, r -> !predicate.test(x) && r.getOrNull() == false }, + { predicate, x, r -> predicate.test(x) && r.getOrNull() == true }, + { predicate, _, r -> predicate == null && r.isException() }, + coverage = DoNotCalculate + ) + } + + @Test + fun testCapturedNonStaticFieldPredicateCheck() { + checkWithException( + CustomPredicateExample::capturedNonStaticFieldPredicateCheck, + eq(3), + { predicate, x, r -> !predicate.test(x) && r.getOrNull() == false }, + { predicate, x, r -> predicate.test(x) && r.getOrNull() == true }, + { predicate, _, r -> predicate == null && r.isException() }, + coverage = DoNotCalculate + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/PredicateNotExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/PredicateNotExampleTest.kt new file mode 100644 index 0000000000..739cff5964 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/PredicateNotExampleTest.kt @@ -0,0 +1,19 @@ +package org.utbot.examples.lambda + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +class PredicateNotExampleTest : UtValueTestCaseChecker(testClass = PredicateNotExample::class) { + @Test + @Disabled("TODO flaky 0 executions at GitHub runners https://github.com/UnitTestBot/UTBotJava/issues/999") + fun testPredicateNotExample() { + check( + PredicateNotExample::predicateNotExample, + eq(2), + { a, r -> a == 5 && r == false }, + { a, r -> a != 5 && r == true }, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt new file mode 100644 index 0000000000..251619631a --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt @@ -0,0 +1,34 @@ +package org.utbot.examples.lambda + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException + +// TODO failed Kotlin compilation (generics) SAT-1332 +class SimpleLambdaExamplesTest : UtValueTestCaseChecker( + testClass = SimpleLambdaExamples::class, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testBiFunctionLambdaExample() { + checkWithException( + SimpleLambdaExamples::biFunctionLambdaExample, + eq(2), + { _, b, r -> b == 0 && r.isException() }, + { a, b, r -> b != 0 && r.getOrThrow() == a / b }, + ) + } + + @Test + fun testChoosePredicate() { + check( + SimpleLambdaExamples::choosePredicate, + eq(2), + { b, r -> b && !r!!.test(null) && r.test(0) }, + { b, r -> !b && r!!.test(null) && !r.test(0) }, + coverage = DoNotCalculate // coverage could not be calculated since method result is lambda + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/ThrowingWithLambdaExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/ThrowingWithLambdaExampleTest.kt new file mode 100644 index 0000000000..fb338e5e1d --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/lambda/ThrowingWithLambdaExampleTest.kt @@ -0,0 +1,21 @@ +package org.utbot.examples.lambda + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +class ThrowingWithLambdaExampleTest : UtValueTestCaseChecker(testClass = ThrowingWithLambdaExample::class) { + @Test + fun testAnyExample() { + check( + ThrowingWithLambdaExample::anyExample, + eq(4), + { l, _, _ -> l == null }, + { l, _, r -> l.isEmpty() && r == false }, + { l, _, r -> l.isNotEmpty() && 42 in l && r == true }, + { l, _, r -> l.isNotEmpty() && 42 !in l && r == false }, + coverage = DoNotCalculate // TODO failed coverage calculation + ) + } +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/make/symbolic/ClassWithComplicatedMethodsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/make/symbolic/ClassWithComplicatedMethodsTest.kt similarity index 81% rename from utbot-framework/src/test/kotlin/org/utbot/examples/make/symbolic/ClassWithComplicatedMethodsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/make/symbolic/ClassWithComplicatedMethodsTest.kt index b7279478c8..e2c81d6913 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/make/symbolic/ClassWithComplicatedMethodsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/make/symbolic/ClassWithComplicatedMethodsTest.kt @@ -1,27 +1,30 @@ package org.utbot.examples.make.symbolic -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.withoutConcrete -import org.utbot.framework.codegen.Compilation -import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.MockStrategyApi import kotlin.math.abs import kotlin.math.sqrt import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.Compilation +import org.utbot.testing.Configuration +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker // This class is substituted with ComplicatedMethodsSubstitutionsStorage // but we cannot do in code generation. // For this reason code generation executions are disabled -internal class ClassWithComplicatedMethodsTest : AbstractTestCaseGeneratorTest( +internal class ClassWithComplicatedMethodsTest : UtValueTestCaseChecker( testClass = ClassWithComplicatedMethods::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA, Compilation), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, Compilation) - ) + configurations = listOf( + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.DO_NOT_PARAMETRIZE, Compilation), + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.PARAMETRIZE, Compilation), + Configuration(CodegenLanguage.KOTLIN, ParametrizedTestSource.DO_NOT_PARAMETRIZE, Compilation), + ), ) { @Test @Disabled("[SAT-1419]") diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/math/BitOperatorsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/math/BitOperatorsTest.kt similarity index 93% rename from utbot-framework/src/test/kotlin/org/utbot/examples/math/BitOperatorsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/math/BitOperatorsTest.kt index 0f21c4e0b3..a40a390fef 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/math/BitOperatorsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/math/BitOperatorsTest.kt @@ -1,11 +1,11 @@ package org.utbot.examples.math -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.atLeast -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast -internal class BitOperatorsTest : AbstractTestCaseGeneratorTest(testClass = BitOperators::class) { +internal class BitOperatorsTest : UtValueTestCaseChecker(testClass = BitOperators::class) { @Test fun testComplement() { check( @@ -37,7 +37,7 @@ internal class BitOperatorsTest : AbstractTestCaseGeneratorTest(testClass = BitO } @Test - @kotlin.ExperimentalStdlibApi + @ExperimentalStdlibApi fun testAnd() { check( BitOperators::and, diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/math/DivRemExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/math/DivRemExamplesTest.kt similarity index 90% rename from utbot-framework/src/test/kotlin/org/utbot/examples/math/DivRemExamplesTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/math/DivRemExamplesTest.kt index 2f2d936fe7..acd9124d5d 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/math/DivRemExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/math/DivRemExamplesTest.kt @@ -1,11 +1,11 @@ package org.utbot.examples.math -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.utbot.examples.isException import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException -internal class DivRemExamplesTest : AbstractTestCaseGeneratorTest(testClass = DivRemExamples::class) { +internal class DivRemExamplesTest : UtValueTestCaseChecker(testClass = DivRemExamples::class) { @Test fun testDiv() { checkWithException( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/math/DoubleFunctionsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/math/DoubleFunctionsTest.kt similarity index 87% rename from utbot-framework/src/test/kotlin/org/utbot/examples/math/DoubleFunctionsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/math/DoubleFunctionsTest.kt index c54d15346d..a69f6565f2 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/math/DoubleFunctionsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/math/DoubleFunctionsTest.kt @@ -1,16 +1,16 @@ package org.utbot.examples.math -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.isException import kotlin.math.abs import kotlin.math.hypot import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException @Suppress("SimplifyNegatedBinaryExpression") -internal class DoubleFunctionsTest : AbstractTestCaseGeneratorTest(testClass = DoubleFunctions::class) { +internal class DoubleFunctionsTest : UtValueTestCaseChecker(testClass = DoubleFunctions::class) { @Test @Tag("slow") fun testHypo() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/math/OverflowAsErrorTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/math/OverflowAsErrorTest.kt new file mode 100644 index 0000000000..4120937cd0 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/math/OverflowAsErrorTest.kt @@ -0,0 +1,313 @@ +package org.utbot.examples.math + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.OverflowDetectionError +import org.utbot.examples.algorithms.Sort +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withSolverTimeoutInMillis +import org.utbot.testcheckers.withTreatingOverflowAsError +import org.utbot.testing.* +import kotlin.math.floor +import kotlin.math.sqrt + +internal class OverflowAsErrorTest : UtValueTestCaseChecker( + testClass = OverflowExamples::class, + testCodeGeneration = true, + // Don't launch tests, because ArithmeticException will be expected, but it is not supposed to be actually thrown. + // ArithmeticException acts as a sign of Overflow. + listOf( + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.DO_NOT_PARAMETRIZE, Compilation), + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.PARAMETRIZE, Compilation), + Configuration(CodegenLanguage.KOTLIN, ParametrizedTestSource.DO_NOT_PARAMETRIZE, Compilation), + ) +) { + @Test + fun testIntOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::intOverflow, + eq(5), + { x, _, r -> + val overflowOccurred = kotlin.runCatching { + Math.multiplyExact(x, x) + }.isFailure + overflowOccurred && r.isException() + }, // through overflow + { x, _, r -> + val twoMul = Math.multiplyExact(x, x) + val overflowOccurred = kotlin.runCatching { + Math.multiplyExact(twoMul, x) + }.isFailure + overflowOccurred && r.isException() + }, // through overflow (2nd '*') + { x, _, r -> x * x * x >= 0 && x >= 0 && r.getOrNull() == 0 }, + { x, y, r -> x * x * x > 0 && x > 0 && y == 10 && r.getOrNull() == 1 }, + { x, y, r -> x * x * x > 0 && x > 0 && y != 10 && r.getOrNull() == 0 }, + coverage = DoNotCalculate + ) + } + } + + @Test + fun testByteAddOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::byteAddOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x + y).toByte() >= 0 && x < 0 && y < 0) + val posOverflow = ((x + y).toByte() <= 0 && x > 0 && y > 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } + } + + @Test + fun testByteWithIntOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::byteWithIntOverflow, + eq(2), + { x, y, r -> + runCatching { + Math.addExact(x.toInt(), y) + }.isFailure && r.isException() + }, + { x, y, r -> Math.addExact(x.toInt(), y).toByte() == r.getOrThrow() } + ) + } + } + + @Test + fun testByteSubOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::byteSubOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x - y).toByte() >= 0 && x < 0 && y > 0) + val posOverflow = ((x - y).toByte() <= 0 && x > 0 && y < 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } + } + + @Test + fun testByteMulOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::byteMulOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { _, _, r -> r.isException() }, // through overflow + ) + } + } + + @Test + fun testShortAddOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::shortAddOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x + y).toShort() >= 0 && x < 0 && y < 0) + val posOverflow = ((x + y).toShort() <= 0 && x > 0 && y > 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } + } + + @Test + fun testShortSubOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::shortSubOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x - y).toShort() >= 0 && x < 0 && y > 0) + val posOverflow = ((x - y).toShort() <= 0 && x > 0 && y < 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } + } + + @Test + fun testShortMulOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::shortMulOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { _, _, r -> r.isException() }, // through overflow + ) + } + } + + @Test + fun testIntAddOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::intAddOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x + y) >= 0 && x < 0 && y < 0) + val posOverflow = ((x + y) <= 0 && x > 0 && y > 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } + } + + @Test + fun testIntSubOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::intSubOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x - y) >= 0 && x < 0 && y > 0) + val posOverflow = ((x - y) <= 0 && x > 0 && y < 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } + } + + @Test + fun testIntMulOverflow() { + // This test has solver timeout. + // Reason: softConstraints, containing limits for Int values, hang solver. + // With solver timeout softConstraints are dropped and hard constraints are SAT for overflow. + withSolverTimeoutInMillis(timeoutInMillis = 1000) { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::intMulOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { _, _, r -> r.isException() }, // through overflow + ) + } + } + } + + @Test + fun testLongAddOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::longAddOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x + y) >= 0 && x < 0 && y < 0) + val posOverflow = ((x + y) <= 0 && x > 0 && y > 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } + } + + @Test + fun testLongSubOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::longSubOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { x, y, r -> + val negOverflow = ((x - y) >= 0 && x < 0 && y > 0) + val posOverflow = ((x - y) <= 0 && x > 0 && y < 0) + (negOverflow || posOverflow) && r.isException() + }, // through overflow + ) + } + } + + @Test + @Disabled("Flaky branch count mismatch (1 instead of 2)") + fun testLongMulOverflow() { + // This test has solver timeout. + // Reason: softConstraints, containing limits for Int values, hang solver. + // With solver timeout softConstraints are dropped and hard constraints are SAT for overflow. + withSolverTimeoutInMillis(timeoutInMillis = 2000) { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::longMulOverflow, + eq(2), + { _, _, r -> !r.isException() }, + { _, _, r -> r.isException() }, // through overflow + ) + } + } + } + + @Test + fun testIncOverflow() { + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::incOverflow, + eq(2), + { _, r -> !r.isException() }, + { _, r -> r.isException() }, // through overflow + ) + } + } + + @Test + fun testIntCubeOverflow() { + val sqrtIntMax = floor(sqrt(Int.MAX_VALUE.toDouble())).toInt() + withTreatingOverflowAsError { + checkWithException( + OverflowExamples::intCubeOverflow, + eq(3), + { _, r -> !r.isException() }, + // Can't use abs(x) below, because abs(Int.MIN_VALUE) == Int.MIN_VALUE. + // (Int.MAX_VALUE shr 16) is the border of square overflow and cube overflow. + // Int.MAX_VALUE.toDouble().pow(1/3.toDouble()) + { x, r -> (x > -sqrtIntMax && x < sqrtIntMax) && r.isException() }, // through overflow + { x, r -> (x <= -sqrtIntMax || x >= sqrtIntMax) && r.isException() }, // through overflow + ) + } + } + + // Generated Kotlin code does not compile, so disabled for now + @Test + @Disabled + fun testQuickSort() { + withTreatingOverflowAsError { + checkWithException( + Sort::quickSort, + ignoreExecutionsNumber, + { _, _, _, r -> !r.isException() }, + { _, _, _, r -> r.isException() }, // through overflow + ) + } + } + + @Test + fun testIntOverflowWithoutError() { + check( + OverflowExamples::intOverflow, + eq(6), + { x, _, r -> x * x * x <= 0 && x <= 0 && r == 0 }, + { x, _, r -> x * x * x > 0 && x <= 0 && r == 0 }, // through overflow + { x, y, r -> x * x * x > 0 && x > 0 && y != 10 && r == 0 }, + { x, y, r -> x * x * x > 0 && x > 0 && y == 10 && r == 1 }, + { x, y, r -> x * x * x <= 0 && x > 0 && y != 20 && r == 0 }, // through overflow + { x, y, r -> x * x * x <= 0 && x > 0 && y == 20 && r == 2 } // through overflow + ) + } +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/LoggerExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/LoggerExampleTest.kt similarity index 78% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mixed/LoggerExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/LoggerExampleTest.kt index 7d23115a66..c60e740445 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/LoggerExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/LoggerExampleTest.kt @@ -1,24 +1,19 @@ package org.utbot.examples.mixed -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.UtConcreteValue import org.utbot.framework.plugin.api.UtInstrumentation import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation import org.utbot.framework.plugin.api.isNull import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker -internal class LoggerExampleTest : AbstractTestCaseGeneratorTest( +internal class LoggerExampleTest : UtValueTestCaseChecker( testClass = LoggerExample::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testExample() { @@ -27,7 +22,7 @@ internal class LoggerExampleTest : AbstractTestCaseGeneratorTest( eq(2), { _, instrumentation, _ -> theOnlyStaticMockValue(instrumentation).isNull() }, { mocks, instrumentation, r -> mocks.size == 3 && instrumentation.size == 1 && r == 15 }, - additionalDependencies = arrayOf(org.slf4j.Logger::class), + additionalDependencies = arrayOf(org.slf4j.Logger::class.java), coverage = DoNotCalculate ) } @@ -44,7 +39,7 @@ internal class LoggerExampleTest : AbstractTestCaseGeneratorTest( { mocks, instrumentation, r -> (mocks.single().values.single() as UtConcreteValue<*>).value == true && instrumentation.size == 1 && r == 1 }, - additionalDependencies = arrayOf(org.slf4j.Logger::class), + additionalDependencies = arrayOf(org.slf4j.Logger::class.java), coverage = DoNotCalculate ) } diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/MonitorUsageTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/MonitorUsageTest.kt new file mode 100644 index 0000000000..09974c3c22 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/MonitorUsageTest.kt @@ -0,0 +1,19 @@ +package org.utbot.examples.mixed + +import org.junit.jupiter.api.Test +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast +import org.utbot.testing.ignoreExecutionsNumber + +internal class MonitorUsageTest : UtValueTestCaseChecker(testClass = MonitorUsage::class) { + @Test + fun testSimpleMonitor() { + check( + MonitorUsage::simpleMonitor, + ignoreExecutionsNumber, + { x, r -> x <= 0 && r == 0 }, + { x, r -> x > 0 && x <= Int.MAX_VALUE - 1 && r == 1 }, + coverage = atLeast(81) // differs from JaCoCo + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/OverloadTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/OverloadTest.kt similarity index 76% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mixed/OverloadTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/OverloadTest.kt index b8417f4ae3..b6012d8d8b 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/OverloadTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/OverloadTest.kt @@ -1,10 +1,10 @@ package org.utbot.examples.mixed -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker -internal class OverloadTest : AbstractTestCaseGeneratorTest(testClass = Overload::class) { +internal class OverloadTest : UtValueTestCaseChecker(testClass = Overload::class) { @Test fun testSignOneParam() { check( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/PrivateConstructorExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/PrivateConstructorExampleTest.kt new file mode 100644 index 0000000000..bcf8230e6c --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/PrivateConstructorExampleTest.kt @@ -0,0 +1,30 @@ +package org.utbot.examples.mixed + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +internal class PrivateConstructorExampleTest : UtValueTestCaseChecker( + testClass = PrivateConstructorExample::class, +) { + + /** + * Two branches need to be covered: + * 1. argument must be <= a - b, + * 2. argument must be > a - b + * + * a and b are fields of the class under test + */ + @Test + fun testLimitedSub() { + checkWithThis( + PrivateConstructorExample::limitedSub, + eq(2), + { caller, limit, r -> caller.a - caller.b >= limit && r == caller.a - caller.b }, + { caller, limit, r -> caller.a - caller.b < limit && r == limit }, + coverage = DoNotCalculate // TODO: Method coverage with `this` parameter isn't supported + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/SerializableExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/SerializableExampleTest.kt new file mode 100644 index 0000000000..1897093f61 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/SerializableExampleTest.kt @@ -0,0 +1,18 @@ +package org.utbot.examples.mixed + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +internal class SerializableExampleTest : UtValueTestCaseChecker(testClass = SerializableExample::class) { + + @Test + fun testExample() { + check( + SerializableExample::example, + eq(1), + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/SimpleNoConditionTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/SimpleNoConditionTest.kt new file mode 100644 index 0000000000..8bddaa3e66 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/SimpleNoConditionTest.kt @@ -0,0 +1,32 @@ +package org.utbot.examples.mixed + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +internal class SimpleNoConditionTest : UtValueTestCaseChecker(testClass = SimpleNoCondition::class) { + + @Test + fun testNoConditionAdd() { + check( + SimpleNoCondition::basicAdd, + eq(1) + ) + } + + @Test + fun testNoConditionPow() { + check( + SimpleNoCondition::basicXorInt, + eq(1) + ) + } + + @Test + fun testNoConditionPowBoolean() { + check( + SimpleNoCondition::basicXorBoolean, + eq(1) + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/SimplifierTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/SimplifierTest.kt new file mode 100644 index 0000000000..0c6b13aacd --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/SimplifierTest.kt @@ -0,0 +1,18 @@ +package org.utbot.examples.mixed + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +internal class SimplifierTest: UtValueTestCaseChecker(testClass = Simplifier::class) { + @Test + fun testSimplifyAdditionWithZero() { + check( + Simplifier::simplifyAdditionWithZero, + eq(1), + { fst, r -> r != null && r.x == fst.shortValue.toInt() }, + coverage = DoNotCalculate // because of assumes + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/StaticInitializerExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/StaticInitializerExampleTest.kt similarity index 75% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mixed/StaticInitializerExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/StaticInitializerExampleTest.kt index 59e552f95e..fd31bb20fe 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/StaticInitializerExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/StaticInitializerExampleTest.kt @@ -2,12 +2,12 @@ package org.utbot.examples.mixed import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test -import org.utbot.examples.AbstractTestCaseGeneratorTest import org.utbot.examples.StaticInitializerExample -import org.utbot.examples.eq +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker @Disabled("Unknown build failure") -internal class StaticInitializerExampleTest : AbstractTestCaseGeneratorTest(testClass = StaticInitializerExample::class) { +internal class StaticInitializerExampleTest : UtValueTestCaseChecker(testClass = StaticInitializerExample::class) { @Test fun testPositive() { check( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/StaticMethodExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/StaticMethodExamplesTest.kt similarity index 84% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mixed/StaticMethodExamplesTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/StaticMethodExamplesTest.kt index 4c7020fbc0..7e71562964 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/StaticMethodExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mixed/StaticMethodExamplesTest.kt @@ -1,10 +1,10 @@ package org.utbot.examples.mixed -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker -internal class StaticMethodExamplesTest : AbstractTestCaseGeneratorTest(testClass = StaticMethodExamples::class) { +internal class StaticMethodExamplesTest : UtValueTestCaseChecker(testClass = StaticMethodExamples::class) { // TODO: inline local variables when types inference bug in Kotlin fixed @Test fun testComplement() { diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/ArgumentsMockTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/ArgumentsMockTest.kt similarity index 97% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mock/ArgumentsMockTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/ArgumentsMockTest.kt index 0f8ddcbce9..00c1bcfd13 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/ArgumentsMockTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/ArgumentsMockTest.kt @@ -1,19 +1,19 @@ package org.utbot.examples.mock -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.examples.isParameter import org.utbot.examples.mock.provider.Provider import org.utbot.examples.mock.service.impl.ExampleClass import org.utbot.examples.mock.service.impl.ServiceWithArguments -import org.utbot.examples.mocksMethod -import org.utbot.examples.value import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_PACKAGES import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between +import org.utbot.testing.isParameter +import org.utbot.testing.mocksMethod +import org.utbot.testing.value -internal class ArgumentsMockTest : AbstractTestCaseGeneratorTest(testClass = ServiceWithArguments::class) { +internal class ArgumentsMockTest : UtValueTestCaseChecker(testClass = ServiceWithArguments::class) { @Test fun testMockForArguments_callMultipleMethods() { checkMocksAndInstrumentation( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/CommonMocksExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/CommonMocksExampleTest.kt new file mode 100644 index 0000000000..ae2609d787 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/CommonMocksExampleTest.kt @@ -0,0 +1,84 @@ +package org.utbot.examples.mock + +import org.utbot.framework.plugin.api.MockStrategyApi +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast + +internal class CommonMocksExampleTest: UtValueTestCaseChecker(testClass = CommonMocksExample::class) { + + //TODO: coverage values here require further investigation by experts + + @Test + fun testMockInterfaceWithoutImplementorsWithNoMocksStrategy() { + checkMocks( + CommonMocksExample::mockInterfaceWithoutImplementors, + eq(1), + { v, mocks, _ -> v == null && mocks.isEmpty() }, + mockStrategy = MockStrategyApi.NO_MOCKS, + coverage = atLeast(75), + ) + } + + @Test + fun testMockInterfaceWithoutImplementorsWithMockingStrategy() { + checkMocks( + CommonMocksExample::mockInterfaceWithoutImplementors, + eq(2), + { v, mocks, _ -> v == null && mocks.isEmpty() }, + { _, mocks, _ -> mocks.singleOrNull() != null }, + mockStrategy = MockStrategyApi.OTHER_CLASSES, + coverage = atLeast(75), + ) + } + + // TODO JIRA:1449 + @Test + fun testDoNotMockEquals() { + checkMocks( + CommonMocksExample::doNotMockEquals, + eq(2), + { fst, _, mocks, _ -> fst == null && mocks.isEmpty() }, + { _, _, mocks, _ -> mocks.isEmpty() }, // should be changed to not null fst when 1449 will be finished + mockStrategy = MockStrategyApi.OTHER_PACKAGES, + coverage = atLeast(75) + ) + } + + // TODO JIRA:1449 + @Test + fun testNextValue() { + checkMocks( + CommonMocksExample::nextValue, + eq(4), + // node == null -> NPE + // node.next == null -> NPE + // node == node.next + // node.next.value == node.value + 1 + mockStrategy = MockStrategyApi.OTHER_CLASSES, + coverage = atLeast(13) + ) + } + + @Test + fun testClinitMockExample() { + check( + CommonMocksExample::clinitMockExample, + eq(1), + { r -> r == -420 }, + mockStrategy = MockStrategyApi.OTHER_CLASSES, + coverage = atLeast(70), + ) + } + + @Test + fun testMocksForNullOfDifferentTypes() { + check( + CommonMocksExample::mocksForNullOfDifferentTypes, + eq(1), + mockStrategy = MockStrategyApi.OTHER_PACKAGES, + coverage = atLeast(75) + ) + } +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/FieldMockTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/FieldMockTest.kt similarity index 96% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mock/FieldMockTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/FieldMockTest.kt index 3e48b8c7ba..ff94de21b2 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/FieldMockTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/FieldMockTest.kt @@ -1,18 +1,21 @@ package org.utbot.examples.mock -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.between -import org.utbot.examples.eq import org.utbot.examples.mock.provider.Provider import org.utbot.examples.mock.service.impl.ExampleClass import org.utbot.examples.mock.service.impl.ServiceWithField -import org.utbot.examples.mocksMethod -import org.utbot.examples.value import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_PACKAGES import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between +import org.utbot.testing.mocksMethod +import org.utbot.testing.value -internal class FieldMockTest : AbstractTestCaseGeneratorTest(testClass = ServiceWithField::class) { +internal class FieldMockTest : UtValueTestCaseChecker( + testClass = ServiceWithField::class, +) { @Test fun testMockForField_callMultipleMethods() { checkMocksAndInstrumentation( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/InnerMockWithFieldChecker.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/InnerMockWithFieldChecker.kt new file mode 100644 index 0000000000..30a746e0c1 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/InnerMockWithFieldChecker.kt @@ -0,0 +1,52 @@ +package org.utbot.examples.mock + +import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_PACKAGES +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.isMockModel +import org.utbot.framework.plugin.api.isNotNull +import org.utbot.framework.plugin.api.isNull +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtModelTestCaseChecker +import org.utbot.testing.getOrThrow +import org.utbot.testing.primitiveValue + +internal class InnerMockWithFieldChecker : UtModelTestCaseChecker(testClass = InnerMockWithFieldExample::class) { + @Test + fun testCheckAndUpdate() { + checkStatic( + InnerMockWithFieldExample::checkAndUpdate, + eq(4), + { example, r -> example.isNull() && r.isException() }, + { example, r -> example.isNotNull() && example.stamp.isNull() && r.isException() }, + { example, r -> + val result = r.getOrThrow() + val isMockModels = example.stamp.isMockModel() && result.isMockModel() + val stampConstraint = example.stamp.initial > example.stamp.version + val postcondition = result.initial == example.stamp.initial && result.version == result.initial + + isMockModels && stampConstraint && postcondition + }, + { example, r -> + val result = r.getOrThrow() + val stamp = example.stamp + + val isMockModels = stamp.isMockModel() && result.isMockModel() + val stampConstraint = stamp.initial <= stamp.version + val postcondition = result.initial == stamp.initial && result.version == stamp.version + 1 + + isMockModels && stampConstraint && postcondition + }, + mockStrategy = OTHER_PACKAGES + ) + } + + private val UtModel.stamp: UtModel + get() = findField("stamp") + + private val UtModel.initial: Int + get() = findField("initial").primitiveValue() + + private val UtModel.version: Int + get() = findField("version").primitiveValue() +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockFinalClassTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockFinalClassTest.kt new file mode 100644 index 0000000000..8f0f2ad1b3 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockFinalClassTest.kt @@ -0,0 +1,33 @@ +package org.utbot.examples.mock + +import org.utbot.examples.mock.others.FinalClass +import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_CLASSES +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.testcheckers.ge +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.singleMock +import org.utbot.testing.value + +internal class MockFinalClassTest : UtValueTestCaseChecker( + testClass = MockFinalClassExample::class, +) { + @Test + fun testFinalClass() { + checkMocks( + MockFinalClassExample::useFinalClass, + ge(2), + { mocks, r -> + val intProvider = mocks.singleMock("intProvider", FinalClass::provideInt) + intProvider.value(0) == 1 && r == 1 + }, + { mocks, r -> + val intProvider = mocks.singleMock("intProvider", FinalClass::provideInt) + intProvider.value(0) != 1 && r == 2 + }, + coverage = DoNotCalculate, + mockStrategy = OTHER_CLASSES + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockRandomTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockRandomTest.kt similarity index 90% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockRandomTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockRandomTest.kt index eb94eb94b4..5d7bba68ff 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockRandomTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockRandomTest.kt @@ -1,19 +1,24 @@ package org.utbot.examples.mock -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.isParameter -import org.utbot.examples.mockValues -import org.utbot.examples.mocksMethod -import org.utbot.examples.singleMock -import org.utbot.examples.value import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation import java.util.Random import org.junit.jupiter.api.Test - -internal class MockRandomTest : AbstractTestCaseGeneratorTest(testClass = MockRandomExamples::class) { +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isParameter +import org.utbot.testing.mockValues +import org.utbot.testing.mocksMethod +import org.utbot.testing.singleMock +import org.utbot.testing.value + +// TODO Kotlin mocks generics https://github.com/UnitTestBot/UTBotJava/issues/88 +internal class MockRandomTest : UtValueTestCaseChecker( + testClass = MockRandomExamples::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { @Test fun testRandomAsParameter() { val method: Random.() -> Int = Random::nextInt diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockReturnObjectExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockReturnObjectExampleTest.kt similarity index 85% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockReturnObjectExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockReturnObjectExampleTest.kt index b3c787e5da..51cd5f279b 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockReturnObjectExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockReturnObjectExampleTest.kt @@ -1,19 +1,21 @@ package org.utbot.examples.mock -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq +import org.junit.jupiter.api.Disabled import org.utbot.examples.mock.others.Generator import org.utbot.examples.mock.others.Locator -import org.utbot.examples.mockValue -import org.utbot.examples.singleMock -import org.utbot.examples.singleMockOrNull -import org.utbot.examples.value import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_PACKAGES import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.mockValue +import org.utbot.testing.singleMock +import org.utbot.testing.singleMockOrNull +import org.utbot.testing.value -internal class MockReturnObjectExampleTest : AbstractTestCaseGeneratorTest(testClass = MockReturnObjectExample::class) { +internal class MockReturnObjectExampleTest : UtValueTestCaseChecker(testClass = MockReturnObjectExample::class) { @Test + @Disabled("Java 11 transition") fun testMockReturnObject() { checkMocks( MockReturnObjectExample::calculate, diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockStaticFieldExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockStaticFieldExampleTest.kt new file mode 100644 index 0000000000..a08efc9175 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockStaticFieldExampleTest.kt @@ -0,0 +1,119 @@ +package org.utbot.examples.mock + +import org.utbot.examples.mock.others.Generator +import org.utbot.framework.plugin.api.FieldMockTarget +import org.utbot.framework.plugin.api.MockInfo +import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_PACKAGES +import kotlin.reflect.KClass +import org.junit.jupiter.api.Test +import org.utbot.examples.mock.others.ClassWithStaticField +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.singleMock +import org.utbot.testing.singleMockOrNull +import org.utbot.testing.value + +internal class MockStaticFieldExampleTest : UtValueTestCaseChecker( + testClass = MockStaticFieldExample::class, + testCodeGeneration = true, + // disabled due to https://github.com/UnitTestBot/UTBotJava/issues/88 + configurations = ignoreKotlinCompilationConfigurations, +) { + + @Test + fun testMockStaticField() { + withoutConcrete { // TODO JIRA:1420 + checkMocks( + MockStaticFieldExample::calculate, + eq(4), // 2 NPE + // NPE, privateGenerator is null + { _, mocks, r -> + val privateGenerator = mocks.singleMockOrNull("privateGenerator", Generator::generateInt) + privateGenerator == null && r == null + }, + // NPE, publicGenerator is null + { _, mocks, r -> + val publicGenerator = mocks.singleMockOrNull("publicGenerator", Generator::generateInt) + publicGenerator == null && r == null + }, + { threshold, mocks, r -> + val mock1 = mocks.singleMock("privateGenerator", Generator::generateInt) + val mock2 = mocks.singleMock("publicGenerator", Generator::generateInt) + + val (index1, index2) = if (mock1.values.size > 1) 0 to 1 else 0 to 0 + + val value1 = mock1.value(index1) + val value2 = mock2.value(index2) + + val firstMockConstraint = mock1.mocksStaticField(MockStaticFieldExample::class) + val secondMockConstraint = mock2.mocksStaticField(MockStaticFieldExample::class) + val resultConstraint = threshold < value1 + value2 && r == threshold + + firstMockConstraint && secondMockConstraint && resultConstraint + }, + { threshold, mocks, r -> + val mock1 = mocks.singleMock("privateGenerator", Generator::generateInt) + val mock2 = mocks.singleMock("publicGenerator", Generator::generateInt) + + val (index1, index2) = if (mock1.values.size > 1) 0 to 1 else 0 to 0 + + val value1 = mock1.value(index1) + val value2 = mock2.value(index2) + + val firstMockConstraint = mock1.mocksStaticField(MockStaticFieldExample::class) + val secondMockConstraint = mock2.mocksStaticField(MockStaticFieldExample::class) + val resultConstraint = threshold >= value1 + value2 && r == value1 + value2 + 1 + + firstMockConstraint && secondMockConstraint && resultConstraint + }, + coverage = DoNotCalculate, + mockStrategy = OTHER_PACKAGES + ) + } + } + + @Test + fun testCheckMocksInLeftAndRightAssignPartFinalField() { + checkMocks( + MockStaticFieldExample::checkMocksInLeftAndRightAssignPartFinalField, + eq(1), + { mocks, _ -> + val mock = mocks.singleMock("staticFinalField", ClassWithStaticField::foo) + + mock.mocksStaticField(MockStaticFieldExample::class) + }, + coverage = DoNotCalculate, + mockStrategy = OTHER_PACKAGES + ) + } + + @Test + fun testCheckMocksInLeftAndRightAssignPart() { + checkMocksAndInstrumentation( + MockStaticFieldExample::checkMocksInLeftAndRightAssignPart, + eq(2), + { _, statics, _ -> + val instrumentation = statics.single() as UtNewInstanceInstrumentation + val model = instrumentation.instances.last() as UtCompositeModel + + model.fields.isEmpty() // NPE + }, + { mocks, _, _ -> + val mock = mocks.singleMock("staticField", ClassWithStaticField::foo) + + mock.mocksStaticField(MockStaticFieldExample::class) + }, + coverage = DoNotCalculate, + mockStrategy = OTHER_PACKAGES + ) + } + + private fun MockInfo.mocksStaticField(kClass: KClass<*>) = when (val mock = mock) { + is FieldMockTarget -> mock.ownerClassName == kClass.qualifiedName && mock.owner == null + else -> false + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockStaticMethodExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockStaticMethodExampleTest.kt new file mode 100644 index 0000000000..1515664910 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockStaticMethodExampleTest.kt @@ -0,0 +1,55 @@ +package org.utbot.examples.mock + +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.util.singleModel +import org.utbot.framework.util.singleStaticMethod +import org.utbot.framework.util.singleValue +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.util.id +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +// TODO Kotlin mocks generics https://github.com/UnitTestBot/UTBotJava/issues/88 +internal class MockStaticMethodExampleTest : UtValueTestCaseChecker( + testClass = MockStaticMethodExample::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testUseStaticMethod() { + checkMocksAndInstrumentation( + MockStaticMethodExample::useStaticMethod, + eq(2), + { _, instrumentation, r -> + val mockValue = instrumentation + .singleStaticMethod("nextRandomInt") + .singleModel() + .singleValue() as Int + + mockValue > 50 && r == 100 + }, + { _, instrumentation, r -> + val mockValue = instrumentation + .singleStaticMethod("nextRandomInt") + .singleModel() + .singleValue() as Int + + mockValue <= 50 && r == 0 + }, + coverage = DoNotCalculate, + mockStrategy = MockStrategyApi.OTHER_PACKAGES + ) + } + + @Test + fun testMockStaticMethodFromAlwaysMockClass() { + checkMocksAndInstrumentation( + MockStaticMethodExample::mockStaticMethodFromAlwaysMockClass, + eq(1), + coverage = DoNotCalculate, + additionalMockAlwaysClasses = setOf(System::class.id) + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockWithFieldChecker.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockWithFieldChecker.kt new file mode 100644 index 0000000000..397981d54e --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockWithFieldChecker.kt @@ -0,0 +1,47 @@ +package org.utbot.examples.mock + +import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_PACKAGES +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.isMockModel +import org.utbot.framework.plugin.api.isNull +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtModelTestCaseChecker +import org.utbot.testing.getOrThrow +import org.utbot.testing.primitiveValue + +internal class MockWithFieldChecker : UtModelTestCaseChecker(testClass = MockWithFieldExample::class) { + @Test + fun testCheckAndUpdate() { + check( + MockWithFieldExample::checkAndUpdate, + eq(3), + { stamp, r -> stamp.isNull() && r.isException() }, + { stamp, r -> + val result = r.getOrThrow() + + val mockModels = stamp.isMockModel() && result.isMockModel() + val stampValues = stamp.initial > stamp.version + val resultConstraint = result.initial == stamp.initial && result.version == result.initial + + mockModels && stampValues && resultConstraint + }, + { stamp, r -> + val result = r.getOrThrow() + + val mockModels = stamp.isMockModel() && result.isMockModel() + val stampValues = stamp.initial <= stamp.version + val resultConstraint = result.initial == stamp.initial && result.version == stamp.version + 1 + + mockModels && stampValues && resultConstraint + }, + mockStrategy = OTHER_PACKAGES + ) + } + + private val UtModel.initial: Int + get() = findField("initial").primitiveValue() + + private val UtModel.version: Int + get() = findField("version").primitiveValue() +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockWithSideEffectExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockWithSideEffectExampleTest.kt similarity index 85% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockWithSideEffectExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockWithSideEffectExampleTest.kt index a8aeb24a9c..bb31390546 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockWithSideEffectExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/MockWithSideEffectExampleTest.kt @@ -1,14 +1,13 @@ package org.utbot.examples.mock -import org.junit.Ignore -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.isException import org.utbot.framework.plugin.api.MockStrategyApi import org.junit.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException -internal class MockWithSideEffectExampleTest : AbstractTestCaseGeneratorTest(testClass = MockWithSideEffectExample::class) { +internal class MockWithSideEffectExampleTest : UtValueTestCaseChecker(testClass = MockWithSideEffectExample::class) { @Test fun testSideEffect() { checkWithException( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/StaticFieldMockTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/StaticFieldMockTest.kt similarity index 97% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mock/StaticFieldMockTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/StaticFieldMockTest.kt index 5bae958d0a..e963a1e6b6 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/StaticFieldMockTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/StaticFieldMockTest.kt @@ -1,17 +1,16 @@ package org.utbot.examples.mock -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq import org.utbot.examples.mock.provider.Provider import org.utbot.examples.mock.service.impl.ExampleClass import org.utbot.examples.mock.service.impl.ServiceWithStaticField -import org.utbot.examples.mocksMethod -import org.utbot.examples.value import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_PACKAGES import org.junit.jupiter.api.Test - -internal class StaticFieldMockTest : AbstractTestCaseGeneratorTest(testClass = ServiceWithStaticField::class) { +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.mocksMethod +import org.utbot.testing.value +internal class StaticFieldMockTest : UtValueTestCaseChecker(testClass = ServiceWithStaticField::class) { @Test fun testMockForStaticField_callMultipleMethods() { diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/UseNetworkTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/UseNetworkTest.kt similarity index 88% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mock/UseNetworkTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/UseNetworkTest.kt index 85e4de9806..772990f206 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/UseNetworkTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/UseNetworkTest.kt @@ -1,14 +1,14 @@ package org.utbot.examples.mock -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.isException import org.utbot.framework.plugin.api.MockStrategyApi import org.utbot.framework.plugin.api.UtConcreteValue import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException -internal class UseNetworkTest : AbstractTestCaseGeneratorTest(testClass = UseNetwork::class) { +internal class UseNetworkTest : UtValueTestCaseChecker(testClass = UseNetwork::class) { @Test fun testReadBytes() { val method = UseNetwork::readBytes diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/aliasing/AliasingInParamsExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/aliasing/AliasingInParamsExampleTest.kt similarity index 75% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mock/aliasing/AliasingInParamsExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/aliasing/AliasingInParamsExampleTest.kt index c6a214d8b6..6efcdf3089 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/aliasing/AliasingInParamsExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/aliasing/AliasingInParamsExampleTest.kt @@ -1,12 +1,12 @@ package org.utbot.examples.mock.aliasing -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq import org.utbot.framework.plugin.api.MockStrategyApi import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker -internal class AliasingInParamsExampleTest : AbstractTestCaseGeneratorTest(testClass = AliasingInParamsExample::class) { +internal class AliasingInParamsExampleTest : UtValueTestCaseChecker(testClass = AliasingInParamsExample::class) { @Test fun testExamplePackageBased() { check( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/fields/ClassUsingClassWithRandomFieldTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/fields/ClassUsingClassWithRandomFieldTest.kt new file mode 100644 index 0000000000..bca47545b0 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/fields/ClassUsingClassWithRandomFieldTest.kt @@ -0,0 +1,37 @@ +package org.utbot.examples.mock.fields + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +class ClassUsingClassWithRandomFieldTest : UtValueTestCaseChecker( + testClass = ClassUsingClassWithRandomField::class, + testCodeGeneration = true, + // because of mocks + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testUseClassWithRandomField() { + checkMocksAndInstrumentation( + ClassUsingClassWithRandomField::useClassWithRandomField, + eq(1), + { mocks, instrumentation, r -> + val noMocks = mocks.isEmpty() + + val constructorMock = instrumentation.single() as UtNewInstanceInstrumentation + val classIdEquality = constructorMock.classId == java.util.Random::class.id + val callSiteIdEquality = constructorMock.callSites.single() == ClassWithRandomField::class.id + val instance = constructorMock.instances.single() as UtCompositeModel + val methodMock = instance.mocks.entries.single() + val methodNameEquality = methodMock.key.name == "nextInt" + val mockValueResult = r == (methodMock.value.single() as UtPrimitiveModel).value as Int + + noMocks && classIdEquality && callSiteIdEquality && instance.isMock && methodNameEquality && mockValueResult + } + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/model/FieldMockChecker.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/model/FieldMockChecker.kt new file mode 100644 index 0000000000..07fb1ae337 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/model/FieldMockChecker.kt @@ -0,0 +1,38 @@ +package org.utbot.examples.mock.model + +import org.utbot.examples.mock.provider.impl.ProviderImpl +import org.utbot.examples.mock.service.impl.ServiceWithField +import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_PACKAGES +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.isNotNull +import org.utbot.framework.plugin.api.isNull +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtModelTestCaseChecker +import org.utbot.testing.primitiveValue + +internal class FieldMockChecker : UtModelTestCaseChecker(testClass = ServiceWithField::class) { + @Test + fun testMockForField_IntPrimitive() { + checkStatic( + ServiceWithField::staticCalculateBasedOnInteger, + eq(4), + { service, r -> service.isNull() && r.isException() }, + { service, r -> service.provider.isNull() && r.isException() }, + { service, r -> + service.provider.isNotNull() && + service.provider.mocksMethod(ProviderImpl::provideInteger)!!.single() + .primitiveValue() > 5 && r.primitiveValue() == 1 + }, + { service, r -> + service.provider.isNotNull() && + service.provider.mocksMethod(ProviderImpl::provideInteger)!!.single() + .primitiveValue() <= 5 && r.primitiveValue() == 0 + }, + mockStrategy = OTHER_PACKAGES + ) + } + + private val UtModel.provider: UtModel + get() = this.findField("provider") +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/model/UseNetworkModelBasedTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/model/UseNetworkModelBasedTest.kt similarity index 82% rename from utbot-framework/src/test/kotlin/org/utbot/examples/mock/model/UseNetworkModelBasedTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/model/UseNetworkModelBasedTest.kt index f1fe9ff5e3..b405533f6c 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/model/UseNetworkModelBasedTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/mock/model/UseNetworkModelBasedTest.kt @@ -1,14 +1,14 @@ package org.utbot.examples.mock.model -import org.utbot.examples.AbstractModelBasedTest -import org.utbot.examples.eq import org.utbot.examples.mock.UseNetwork import org.utbot.framework.plugin.api.MockStrategyApi import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtVoidModel import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtModelTestCaseChecker -internal class UseNetworkModelBasedTest : AbstractModelBasedTest(testClass = UseNetwork::class) { +internal class UseNetworkModelBasedTest : UtModelTestCaseChecker(testClass = UseNetwork::class) { @Test fun testMockVoidMethod() { check( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/models/CompositeModelMinimizationChecker.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/models/CompositeModelMinimizationChecker.kt new file mode 100644 index 0000000000..b8b7d11c2d --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/models/CompositeModelMinimizationChecker.kt @@ -0,0 +1,75 @@ +package org.utbot.examples.models + +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.junit.Test +import org.utbot.framework.plugin.api.UtCustomModel +import org.utbot.testcheckers.eq +import org.utbot.testing.UtModelTestCaseChecker + +internal class CompositeModelMinimizationChecker : UtModelTestCaseChecker( + testClass = CompositeModelMinimizationExample::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + private fun UtModel.getFieldsOrNull(): Map? = when(this) { + is UtAssembleModel -> origin?.fields + is UtCompositeModel -> fields + is UtCustomModel -> origin?.fields + else -> null + } + + private fun UtModel.hasInitializedFields(): Boolean = getFieldsOrNull()?.isNotEmpty() == true + private fun UtModel.isNotInitialized(): Boolean = getFieldsOrNull()?.isEmpty() == true + + @Test + fun singleNotNullArgumentInitializationRequiredTest() { + check( + CompositeModelMinimizationExample::singleNotNullArgumentInitializationRequired, + eq(2), + { o, _ -> o.hasInitializedFields() } + ) + } + + @Test + fun sameArgumentsInitializationRequiredTest() { + check( + CompositeModelMinimizationExample::sameArgumentsInitializationRequired, + eq(3), + { a, b, _ -> + a as UtReferenceModel + b as UtReferenceModel + a.id == b.id && a.hasInitializedFields() && b.hasInitializedFields() + } + ) + } + + @Test + fun distinctNotNullArgumentsSecondInitializationNotExpected() { + check( + CompositeModelMinimizationExample::distinctNotNullArgumentsSecondInitializationNotExpected, + eq(2), + { a, b, _ -> + a as UtReferenceModel + b as UtReferenceModel + a.hasInitializedFields() && b.isNotInitialized() + } + ) + } + + @Test + fun distinctNotNullArgumentsInitializationRequired() { + check( + CompositeModelMinimizationExample::distinctNotNullArgumentsInitializationRequired, + eq(2), + { a, b, _ -> + a as UtReferenceModel + b as UtReferenceModel + a.hasInitializedFields() && b.hasInitializedFields() + } + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/models/ModelsIdEqualityChecker.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/models/ModelsIdEqualityChecker.kt new file mode 100644 index 0000000000..f528a9a6e7 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/models/ModelsIdEqualityChecker.kt @@ -0,0 +1,140 @@ +package org.utbot.examples.models + +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtReferenceModel +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtModelTestCaseChecker + +// TODO failed Kotlin compilation SAT-1332 +internal class ModelsIdEqualityChecker : UtModelTestCaseChecker( + testClass = ModelsIdEqualityExample::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testObjectItself() { + check( + ModelsIdEqualityExample::objectItself, + eq(1), + { o, r -> (o as UtReferenceModel).id == ((r as UtExecutionSuccess).model as UtReferenceModel).id } + ) + } + + @Test + fun testRefField() { + check( + ModelsIdEqualityExample::refField, + eq(1), + { o, r -> + val resultId = ((r as UtExecutionSuccess).model as UtReferenceModel).id + val fieldId = (o as UtAssembleModel).findFieldId() + fieldId == resultId + } + ) + } + + @Test + fun testArrayField() { + check( + ModelsIdEqualityExample::arrayField, + eq(1), + { o, r -> + val resultId = ((r as UtExecutionSuccess).model as UtReferenceModel).id + val fieldId = (o as UtAssembleModel).findFieldId() + fieldId == resultId + } + ) + } + + @Test + fun testArrayItself() { + check( + ModelsIdEqualityExample::arrayItself, + eq(1), + { o, r -> (o as? UtReferenceModel)?.id == ((r as UtExecutionSuccess).model as? UtReferenceModel)?.id } + ) + } + + @Test + fun testSubArray() { + check( + ModelsIdEqualityExample::subArray, + eq(1), + { array, r -> + val resultId = ((r as UtExecutionSuccess).model as UtReferenceModel).id + val arrayId = (array as UtArrayModel).findElementId(0) + resultId == arrayId + } + ) + } + + @Test + fun testSubRefArray() { + check( + ModelsIdEqualityExample::subRefArray, + eq(1), + { array, r -> + val resultId = ((r as UtExecutionSuccess).model as UtReferenceModel).id + val arrayId = (array as UtArrayModel).findElementId(0) + resultId == arrayId + } + ) + } + + @Test + fun testWrapperExample() { + check( + ModelsIdEqualityExample::wrapperExample, + eq(1), + { o, r -> (o as? UtReferenceModel)?.id == ((r as UtExecutionSuccess).model as? UtReferenceModel)?.id } + ) + } + + @Test + fun testObjectFromArray() { + check( + ModelsIdEqualityExample::objectFromArray, + eq(1), + { array, r -> + val resultId = ((r as UtExecutionSuccess).model as UtReferenceModel).id + val objectId = (array as UtArrayModel).findElementId(0) + resultId == objectId + } + ) + } + + @Test + fun testObjectAndStatic() { + checkStaticsAfter( + ModelsIdEqualityExample::staticSetter, + eq(1), + { obj, statics, r -> + val resultId = ((r as UtExecutionSuccess).model as UtReferenceModel).id + val objectId = (obj as UtReferenceModel).id + val staticId = (statics.values.single() as UtReferenceModel).id + resultId == objectId && resultId == staticId + } + ) + + } + + private fun UtReferenceModel.findFieldId(): Int? { + this as UtAssembleModel + val fieldModel = this.modificationsChain + .filterIsInstance() + .single() + .fieldModel + return (fieldModel as UtReferenceModel).id + } + + private fun UtArrayModel.findElementId(index: Int) = + if (index in stores.keys) { + (stores[index] as UtReferenceModel).id + } else { + (constModel as UtReferenceModel).id + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/natives/NativeExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/natives/NativeExamplesTest.kt new file mode 100644 index 0000000000..1873e21210 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/natives/NativeExamplesTest.kt @@ -0,0 +1,37 @@ +package org.utbot.examples.natives + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testcheckers.withSolverTimeoutInMillis +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +// TODO Kotlin mocks generics https://github.com/UnitTestBot/UTBotJava/issues/88 +internal class NativeExamplesTest : UtValueTestCaseChecker( + testClass = NativeExamples::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + + @Test + fun testFindAndPrintSum() { + // TODO related to the https://github.com/UnitTestBot/UTBotJava/issues/131 + withSolverTimeoutInMillis(5000) { + check( + NativeExamples::findAndPrintSum, + ge(1), + coverage = DoNotCalculate, + ) + } + } + + @Test + fun testFindSumWithMathRandom() { + check( + NativeExamples::findSumWithMathRandom, + eq(1), + coverage = DoNotCalculate, + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/AbstractAnonymousClassTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/AbstractAnonymousClassTest.kt new file mode 100644 index 0000000000..c0f7304310 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/AbstractAnonymousClassTest.kt @@ -0,0 +1,27 @@ +package org.utbot.examples.objects + +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +class AbstractAnonymousClassTest : UtValueTestCaseChecker(testClass = AbstractAnonymousClass::class) { + @Test + fun testNonOverriddenMethod() { + check( + AbstractAnonymousClass::methodWithoutOverrides, + eq(1) + ) + } + + @Test + fun testOverriddenMethod() { + // check we have error during execution + assertThrows { + check( + AbstractAnonymousClass::methodWithOverride, + eq(0) + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/AnonymousClassesExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/AnonymousClassesExampleTest.kt new file mode 100644 index 0000000000..c3e7ed1ecf --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/AnonymousClassesExampleTest.kt @@ -0,0 +1,56 @@ +package org.utbot.examples.objects + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.testcheckers.eq +import org.utbot.testing.Full +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException + +class AnonymousClassesExampleTest : UtValueTestCaseChecker( + testClass = AnonymousClassesExample::class, +) { + @Test + fun testAnonymousClassAsParam() { + checkWithException( + AnonymousClassesExample::anonymousClassAsParam, + eq(3), + { abstractAnonymousClass, r -> abstractAnonymousClass == null && r.isException() }, + { abstractAnonymousClass, r -> abstractAnonymousClass != null && r.getOrNull() == 0 }, + { abstractAnonymousClass, r -> abstractAnonymousClass != null && abstractAnonymousClass::class.java.isAnonymousClass && r.getOrNull() == 42 }, + coverage = Full + ) + } + + @Test + fun testNonFinalAnonymousStatic() { + checkStaticsAndException( + AnonymousClassesExample::nonFinalAnonymousStatic, + eq(3), + { statics, r -> statics.values.single().value == null && r.isException() }, + { _, r -> r.getOrNull() == 0 }, + { _, r -> r.getOrNull() == 42 }, + coverage = Full + ) + } + + @Test + fun testAnonymousClassAsStatic() { + check( + AnonymousClassesExample::anonymousClassAsStatic, + eq(1), + { r -> r == 42 }, + coverage = Full + ) + } + + @Test + fun testAnonymousClassAsResult() { + check( + AnonymousClassesExample::anonymousClassAsResult, + eq(1), + { abstractAnonymousClass -> abstractAnonymousClass != null && abstractAnonymousClass::class.java.isAnonymousClass }, + coverage = Full + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ClassForTestClinitSectionsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ClassForTestClinitSectionsTest.kt new file mode 100644 index 0000000000..2121db3aef --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ClassForTestClinitSectionsTest.kt @@ -0,0 +1,68 @@ +package org.utbot.examples.objects + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withProcessingAllClinitSectionsConcretely +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testcheckers.withProcessingClinitSections +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast + +internal class ClassForTestClinitSectionsTest : UtValueTestCaseChecker(testClass = ClassForTestClinitSections::class) { + @Test + fun testClinitWithoutClinitAnalysis() { + withoutConcrete { + withProcessingClinitSections(value = false) { + check( + ClassForTestClinitSections::resultDependingOnStaticSection, + eq(2), + { r -> r == -1 }, + { r -> r == 1 } + ) + } + } + } + + @Test + fun testClinitWithClinitAnalysis() { + withoutConcrete { + check( + ClassForTestClinitSections::resultDependingOnStaticSection, + eq(2), + { r -> r == -1 }, + { r -> r == 1 } + ) + } + } + + @Test + fun testProcessConcretelyWithoutClinitAnalysis() { + withoutConcrete { + withProcessingClinitSections(value = false) { + withProcessingAllClinitSectionsConcretely(value = true) { + check( + ClassForTestClinitSections::resultDependingOnStaticSection, + eq(2), + { r -> r == -1 }, + { r -> r == 1 } + ) + } + } + } + } + + @Test + fun testProcessClinitConcretely() { + withoutConcrete { + withProcessingAllClinitSectionsConcretely(value = true) { + check( + ClassForTestClinitSections::resultDependingOnStaticSection, + eq(1), + { r -> r == -1 }, + coverage = atLeast(71) + ) + } + } + } +} + diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ClassRefTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ClassRefTest.kt similarity index 84% rename from utbot-framework/src/test/kotlin/org/utbot/examples/objects/ClassRefTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ClassRefTest.kt index 56988441cd..955d05e607 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ClassRefTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ClassRefTest.kt @@ -2,25 +2,20 @@ package org.utbot.examples.objects -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.atLeast -import org.utbot.examples.eq -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import java.lang.Boolean import kotlin.Array import kotlin.Suppress import kotlin.arrayOf import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast -internal class ClassRefTest : AbstractTestCaseGeneratorTest( +internal class ClassRefTest : UtValueTestCaseChecker( testClass = ClassRef::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - // TODO: SAT-1457 Restore Kotlin codegen for a group of tests with type casts - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + // TODO: SAT-1457 Restore Kotlin codegen for a group of tests with type casts + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testTakeBooleanClassRef() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ClassWithClassRefTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ClassWithClassRefTest.kt new file mode 100644 index 0000000000..fbc060324a --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ClassWithClassRefTest.kt @@ -0,0 +1,36 @@ +package org.utbot.examples.objects + +import org.utbot.framework.plugin.api.CodegenLanguage +import org.junit.jupiter.api.Test +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.* + +// TODO Kotlin compilation SAT-1332 +// Code generation executions fail due we cannot analyze strings properly for now +internal class ClassWithClassRefTest : UtValueTestCaseChecker( + testClass = ClassWithClassRef::class, + testCodeGeneration = true, + // TODO JIRA:1479 + configurations = listOf( + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.DO_NOT_PARAMETRIZE, Compilation), + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.PARAMETRIZE, Compilation), + Configuration(CodegenLanguage.KOTLIN, ParametrizedTestSource.DO_NOT_PARAMETRIZE, CodeGeneration), + ) +) { + @Test + // TODO test does not work properly JIRA:1479 + // TODO we don't fail now, but we do not generate correct value as well + fun testClassRefGetName() { + withoutConcrete { // TODO: concrete execution returns "java.lang.Object" + checkWithThisAndException( + ClassWithClassRef::classRefName, + eq(2), + { instance, r -> instance.someListClass == null && r.isException() }, + { instance, r -> instance.someListClass != null && r.getOrNull() == "" }, + coverage = DoNotCalculate // TODO: Method coverage with `this` parameter isn't supported + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/HiddenFieldAccessModifiersTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/HiddenFieldAccessModifiersTest.kt new file mode 100644 index 0000000000..c9ef29e55e --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/HiddenFieldAccessModifiersTest.kt @@ -0,0 +1,20 @@ +package org.utbot.examples.objects + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +internal class HiddenFieldAccessModifiersTest : UtValueTestCaseChecker(testClass = HiddenFieldAccessModifiersExample::class) { + @Test + fun testCheckSuperFieldEqualsOne() { + withEnabledTestingCodeGeneration(testCodeGeneration = true) { + check( + HiddenFieldAccessModifiersExample::checkSuperFieldEqualsOne, + eq(3), + { o, _ -> o == null }, + { _, r -> r == true }, + { _, r -> r == false }, + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/HiddenFieldExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/HiddenFieldExampleTest.kt new file mode 100644 index 0000000000..5aa703df56 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/HiddenFieldExampleTest.kt @@ -0,0 +1,36 @@ +package org.utbot.examples.objects + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +internal class HiddenFieldExampleTest : UtValueTestCaseChecker(testClass = HiddenFieldExample::class) { + @Test + fun testCheckHiddenField() { + check( + HiddenFieldExample::checkHiddenField, + eq(4), + { o, _ -> o == null }, + { o, r -> o != null && o.a != 1 && r == 2 }, + { o, r -> o != null && o.a == 1 && o.b != 2 && r == 2 }, + { o, r -> o != null && o.a == 1 && o.b == 2 && r == 1 }, + coverage = DoNotCalculate + ) + } + + @Test + fun testCheckSuccField() { + withEnabledTestingCodeGeneration(testCodeGeneration = true) { + check( + HiddenFieldExample::checkSuccField, + eq(5), + { o, _ -> o == null }, + { o, r -> o.a == 1 && r == 1 }, + { o, r -> o.a != 1 && o.b == 2.0 && r == 2 }, + { o, r -> o.a != 1 && o.b != 2.0 && (o as HiddenFieldSuperClass).b == 3 && r == 3 }, + { o, r -> o.a != 1 && o.b != 2.0 && (o as HiddenFieldSuperClass).b != 3 && r == 4 }, + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/LocalClassExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/LocalClassExampleTest.kt new file mode 100644 index 0000000000..ed5f188458 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/LocalClassExampleTest.kt @@ -0,0 +1,16 @@ +package org.utbot.examples.objects + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +class LocalClassExampleTest : UtValueTestCaseChecker(testClass = LocalClassExample::class) { + @Test + fun testLocalClassFieldExample() { + check( + LocalClassExample::localClassFieldExample, + eq(1), + { y, r -> r == y + 42 } + ) + } +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ModelMinimizationExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ModelMinimizationExamplesTest.kt similarity index 94% rename from utbot-framework/src/test/kotlin/org/utbot/examples/objects/ModelMinimizationExamplesTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ModelMinimizationExamplesTest.kt index db84fb2da0..658f834229 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ModelMinimizationExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ModelMinimizationExamplesTest.kt @@ -1,12 +1,11 @@ package org.utbot.examples.objects -import org.junit.Ignore -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq import org.junit.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker -internal class ModelMinimizationExamplesTest : AbstractTestCaseGeneratorTest(testClass = ModelMinimizationExamples::class) { +internal class ModelMinimizationExamplesTest : UtValueTestCaseChecker(testClass = ModelMinimizationExamples::class) { @Test fun singleValueComparisonTest() { check( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithFinalStaticTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithFinalStaticTest.kt new file mode 100644 index 0000000000..388968bdf2 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithFinalStaticTest.kt @@ -0,0 +1,25 @@ +package org.utbot.examples.objects + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.singleValue + +class ObjectWithFinalStaticTest : UtValueTestCaseChecker( + testClass = ObjectWithFinalStatic::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testParameterEqualsFinalStatic() { + checkStatics( + ObjectWithFinalStatic::parameterEqualsFinalStatic, + eq(2), + { key, _, statics, result -> key != statics.singleValue() as Int && result == -420 }, + // matcher checks equality by value, but branch is executed if objects are equal by reference + { key, i, statics, result -> key == statics.singleValue() && i == result }, + coverage = DoNotCalculate + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithPrimitivesClassTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithPrimitivesClassTest.kt similarity index 80% rename from utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithPrimitivesClassTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithPrimitivesClassTest.kt index 4ddb71f2ef..6cc5aa08e3 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithPrimitivesClassTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithPrimitivesClassTest.kt @@ -1,13 +1,13 @@ package org.utbot.examples.objects -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq import kotlin.reflect.KFunction0 import kotlin.reflect.KFunction3 import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker -internal class ObjectWithPrimitivesClassTest : AbstractTestCaseGeneratorTest(testClass = ObjectWithPrimitivesClass::class) { +internal class ObjectWithPrimitivesClassTest : UtValueTestCaseChecker(testClass = ObjectWithPrimitivesClass::class) { @Test fun testDefaultConstructor() { val method: KFunction0 = ::ObjectWithPrimitivesClass diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithPrimitivesExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithPrimitivesExampleTest.kt similarity index 96% rename from utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithPrimitivesExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithPrimitivesExampleTest.kt index 93e083be24..c712d08715 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithPrimitivesExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithPrimitivesExampleTest.kt @@ -1,15 +1,15 @@ package org.utbot.examples.objects -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.atLeast -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException -internal class ObjectWithPrimitivesExampleTest : AbstractTestCaseGeneratorTest(testClass = ObjectWithPrimitivesExample::class) { +internal class ObjectWithPrimitivesExampleTest : UtValueTestCaseChecker(testClass = ObjectWithPrimitivesExample::class) { @Test fun testMax() { checkWithException( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithRefFieldsExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithRefFieldsExampleTest.kt similarity index 94% rename from utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithRefFieldsExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithRefFieldsExampleTest.kt index a3f6202095..af99f58ec1 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithRefFieldsExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithRefFieldsExampleTest.kt @@ -1,14 +1,14 @@ package org.utbot.examples.objects -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.atLeast -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException -internal class ObjectWithRefFieldsExampleTest : AbstractTestCaseGeneratorTest(testClass = ObjectWithRefFieldExample::class) { +internal class ObjectWithRefFieldsExampleTest : UtValueTestCaseChecker(testClass = ObjectWithRefFieldExample::class) { @Test fun testDefaultValue() { check( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithStaticFieldsExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithStaticFieldsExampleTest.kt similarity index 94% rename from utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithStaticFieldsExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithStaticFieldsExampleTest.kt index cd4e2fba50..dcd1e9955a 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithStaticFieldsExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithStaticFieldsExampleTest.kt @@ -1,14 +1,14 @@ package org.utbot.examples.objects -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.findByName -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.singleValue import org.junit.jupiter.api.Test - -internal class ObjectWithStaticFieldsExampleTest : AbstractTestCaseGeneratorTest(testClass = ObjectWithStaticFieldsExample::class) { +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.findByName +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.singleValue + +internal class ObjectWithStaticFieldsExampleTest : UtValueTestCaseChecker(testClass = ObjectWithStaticFieldsExample::class) { @Test fun testReadFromStaticArray() { checkStatics( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithThrowableConstructorTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithThrowableConstructorTest.kt new file mode 100644 index 0000000000..297bf4c932 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/ObjectWithThrowableConstructorTest.kt @@ -0,0 +1,22 @@ +package org.utbot.examples.objects + +import kotlin.reflect.KFunction2 +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +internal class ObjectWithThrowableConstructorTest : UtValueTestCaseChecker(testClass = ObjectWithThrowableConstructor::class) { + @Test + @Disabled("SAT-1500 Support verification of UtAssembleModel for possible exceptions") + fun testThrowableConstructor() { + val method: KFunction2 = ::ObjectWithThrowableConstructor + checkStaticMethod( + method, + eq(2), + // TODO: SAT-933 Add support for constructor testing + coverage = DoNotCalculate + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/PrivateFieldsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/PrivateFieldsTest.kt new file mode 100644 index 0000000000..a32d8036cb --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/PrivateFieldsTest.kt @@ -0,0 +1,18 @@ +package org.utbot.examples.objects + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException +internal class PrivateFieldsTest : UtValueTestCaseChecker(testClass = PrivateFields::class) { + @Test + fun testAccessWithGetter() { + checkWithException( + PrivateFields::accessWithGetter, + eq(3), + { x, r -> x == null && r.isException() }, + { x, r -> x.a == 1 && r.getOrNull() == true }, + { x, r -> x.a != 1 && r.getOrNull() == false }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/RecursiveTypeTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/RecursiveTypeTest.kt similarity index 82% rename from utbot-framework/src/test/kotlin/org/utbot/examples/objects/RecursiveTypeTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/RecursiveTypeTest.kt index b821cf8de6..e1dc21ad88 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/RecursiveTypeTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/RecursiveTypeTest.kt @@ -1,11 +1,11 @@ package org.utbot.examples.objects -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker -internal class RecursiveTypeTest : AbstractTestCaseGeneratorTest(testClass = RecursiveType::class) { +internal class RecursiveTypeTest : UtValueTestCaseChecker(testClass = RecursiveType::class) { @Test fun testNextValue() { check( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/SimpleClassExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/SimpleClassExampleTest.kt new file mode 100644 index 0000000000..218721e731 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/SimpleClassExampleTest.kt @@ -0,0 +1,71 @@ +package org.utbot.examples.objects + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between +import org.utbot.testing.isException + +internal class SimpleClassExampleTest : UtValueTestCaseChecker(testClass = SimpleClassExample::class) { + @Test + fun simpleConditionTest() { + check( + SimpleClassExample::simpleCondition, + eq(4), + { c, _ -> c == null }, // NPE + { c, r -> c.a >= 5 && r == 3 }, + { c, r -> c.a < 5 && c.b <= 10 && r == 3 }, + { c, r -> c.a < 5 && c.b > 10 && r == 0 }, + coverage = DoNotCalculate // otherwise we overwrite original values + ) + } + + /** + * Additional bytecode instructions between IFs, because of random, makes different order of executing the branches, + * that affects their number. Changing random seed in PathSelector can explore 6th branch + * + * @see multipleFieldAccessesTest + */ + @Test + fun singleFieldAccessTest() { + check( + SimpleClassExample::singleFieldAccess, + between(5..6), // could be 6 + { c, _ -> c == null }, // NPE + { c, r -> c.a == 3 && c.b != 5 && r == 2 }, + { c, r -> c.a == 3 && c.b == 5 && r == 1 }, + { c, r -> c.a == 2 && c.b != 3 && r == 2 }, + { c, r -> c.a == 2 && c.b == 3 && r == 0 } + ) + } + + /** + * Additional bytecode instructions between IFs, because of random, makes different order of executing the branches, + * that affects their number + */ + @Test + fun multipleFieldAccessesTest() { + check( + SimpleClassExample::multipleFieldAccesses, + eq(6), + { c, _ -> c == null }, // NPE + { c, r -> c.a != 2 && c.a != 3 && r == 2 }, // this one appears + { c, r -> c.a == 3 && c.b != 5 && r == 2 }, + { c, r -> c.a == 3 && c.b == 5 && r == 1 }, + { c, r -> c.a == 2 && c.b != 3 && r == 2 }, + { c, r -> c.a == 2 && c.b == 3 && r == 0 } + ) + } + + @Test + fun immutableFieldAccessTest() { + checkWithException( + SimpleClassExample::immutableFieldAccess, + eq(3), + { c, r -> c == null && r.isException() }, + { c, r -> c.b == 10 && r.getOrNull() == 0 }, + { c, r -> c.b != 10 && r.getOrNull() == 1 } + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/SimpleClassMultiInstanceExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/SimpleClassMultiInstanceExampleTest.kt new file mode 100644 index 0000000000..7ba04d569a --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/objects/SimpleClassMultiInstanceExampleTest.kt @@ -0,0 +1,21 @@ +package org.utbot.examples.objects + +import org.junit.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +internal class SimpleClassMultiInstanceExampleTest : UtValueTestCaseChecker(testClass = + SimpleClassMultiInstanceExample::class) { + @Test + fun singleObjectChangeTest() { + check( + SimpleClassMultiInstanceExample::singleObjectChange, + eq(3), + { first, _, _ -> first == null }, // NPE + { first, _, r -> first.a < 5 && r == 3 }, + { first, _, r -> first.a >= 5 && r == first.b }, + coverage = DoNotCalculate + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/primitives/ByteExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/ByteExamplesTest.kt similarity index 82% rename from utbot-framework/src/test/kotlin/org/utbot/examples/primitives/ByteExamplesTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/ByteExamplesTest.kt index 968cf9d80c..4b709e11f5 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/primitives/ByteExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/ByteExamplesTest.kt @@ -1,10 +1,10 @@ package org.utbot.examples.primitives -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker -internal class ByteExamplesTest : AbstractTestCaseGeneratorTest(testClass = ByteExamples::class) { +internal class ByteExamplesTest : UtValueTestCaseChecker(testClass = ByteExamples::class) { @Test fun testNegByte() { check( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/primitives/CharExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/CharExamplesTest.kt similarity index 87% rename from utbot-framework/src/test/kotlin/org/utbot/examples/primitives/CharExamplesTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/CharExamplesTest.kt index b08a0d755a..c93b615eb9 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/primitives/CharExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/CharExamplesTest.kt @@ -1,11 +1,11 @@ package org.utbot.examples.primitives -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.utbot.examples.isException import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException -internal class CharExamplesTest : AbstractTestCaseGeneratorTest(testClass = CharExamples::class) { +internal class CharExamplesTest : UtValueTestCaseChecker(testClass = CharExamples::class) { @Test fun testCharDiv() { checkWithException( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/primitives/DoubleExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/DoubleExamplesTest.kt similarity index 96% rename from utbot-framework/src/test/kotlin/org/utbot/examples/primitives/DoubleExamplesTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/DoubleExamplesTest.kt index 7897743404..157d5be275 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/primitives/DoubleExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/DoubleExamplesTest.kt @@ -1,11 +1,11 @@ package org.utbot.examples.primitives -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker @Suppress("SimplifyNegatedBinaryExpression") -internal class DoubleExamplesTest : AbstractTestCaseGeneratorTest(testClass = DoubleExamples::class) { +internal class DoubleExamplesTest : UtValueTestCaseChecker(testClass = DoubleExamples::class) { @Test fun testCompareSum() { check( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/FloatExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/FloatExamplesTest.kt new file mode 100644 index 0000000000..87baa7a2a3 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/FloatExamplesTest.kt @@ -0,0 +1,18 @@ +package org.utbot.examples.primitives + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +internal class FloatExamplesTest : UtValueTestCaseChecker(testClass = FloatExamples::class) { + @Test + fun testFloatInfinity() { + check( + FloatExamples::floatInfinity, + eq(3), + { f, r -> f == Float.POSITIVE_INFINITY && r == 1 }, + { f, r -> f == Float.NEGATIVE_INFINITY && r == 2 }, + { f, r -> !f.isInfinite() && r == 3 }, + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/primitives/IntExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/IntExamplesTest.kt similarity index 94% rename from utbot-framework/src/test/kotlin/org/utbot/examples/primitives/IntExamplesTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/IntExamplesTest.kt index fea160c24f..baa6ded345 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/primitives/IntExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/primitives/IntExamplesTest.kt @@ -1,12 +1,12 @@ package org.utbot.examples.primitives -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker @Suppress("ConvertTwoComparisonsToRangeCheck") -internal class IntExamplesTest : AbstractTestCaseGeneratorTest(testClass = IntExamples::class) { +internal class IntExamplesTest : UtValueTestCaseChecker(testClass = IntExamples::class) { @Test @Disabled("SAT-1009 [JAVA] Engine can't analyze isInteger") fun testIsInteger() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/recursion/RecursionTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/recursion/RecursionTest.kt new file mode 100644 index 0000000000..127e3cd1b4 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/recursion/RecursionTest.kt @@ -0,0 +1,108 @@ +package org.utbot.examples.recursion + +import kotlin.math.pow +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast +import org.utbot.testing.between +import org.utbot.testing.isException + +// TODO Kotlin mocks generics https://github.com/UnitTestBot/UTBotJava/issues/88 +internal class RecursionTest : UtValueTestCaseChecker( + testClass = Recursion::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testFactorial() { + checkWithException( + Recursion::factorial, + eq(3), + { x, r -> x < 0 && r.isException() }, + { x, r -> x == 0 && r.getOrNull() == 1 }, + { x, r -> x > 0 && r.getOrNull() == (1..x).reduce { a, b -> a * b } } + ) + } + + @Test + fun testFib() { + checkWithException( + Recursion::fib, + eq(4), + { x, r -> x < 0 && r.isException() }, + { x, r -> x == 0 && r.getOrNull() == 0 }, + { x, r -> x == 1 && r.getOrNull() == 1 }, + { x, r -> x > 1 && r.getOrNull() == Recursion().fib(x) } + ) + } + + @Test + @Disabled("Freezes the execution when snd != 0 JIRA:1293") + fun testSum() { + check( + Recursion::sum, + eq(2), + { x, y, r -> y == 0 && r == x }, + { x, y, r -> y != 0 && r == x + y } + ) + } + + @Test + fun testPow() { + checkWithException( + Recursion::pow, + eq(4), + { _, y, r -> y < 0 && r.isException() }, + { _, y, r -> y == 0 && r.getOrNull() == 1 }, + { x, y, r -> y % 2 == 1 && r.getOrNull() == x.toDouble().pow(y.toDouble()).toInt() }, + { x, y, r -> y % 2 != 1 && r.getOrNull() == x.toDouble().pow(y.toDouble()).toInt() } + ) + } + + @Test + fun infiniteRecursionTest() { + checkWithException( + Recursion::infiniteRecursion, + eq(2), + { x, r -> x > 10000 && r.isException() }, + { x, r -> x <= 10000 && r.isException() }, + coverage = atLeast(50) + ) + } + + @Test + fun vertexSumTest() { + check( + Recursion::vertexSum, + between(2..3), + { x, _ -> x <= 10 }, + { x, _ -> x > 10 } + ) + } + + @Test + fun recursionWithExceptionTest() { + checkWithException( + Recursion::recursionWithException, + ge(3), + { x, r -> x < 42 && r.isException() }, + { x, r -> x == 42 && r.isException() }, + { x, r -> x > 42 && r.isException() }, + coverage = atLeast(50) + ) + } + + @Test + fun recursionLoopTest() { + check( + Recursion::firstMethod, + eq(2), + { x, _ -> x < 4 }, + { x, _ -> x >= 4 }, + coverage = atLeast(50) + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/reflection/NewInstanceExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/reflection/NewInstanceExampleTest.kt new file mode 100644 index 0000000000..813360bf27 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/reflection/NewInstanceExampleTest.kt @@ -0,0 +1,16 @@ +package org.utbot.examples.reflection + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +class NewInstanceExampleTest : UtValueTestCaseChecker(NewInstanceExample::class) { + @Test + fun testNewInstanceExample() { + check( + NewInstanceExample::createWithReflectionExample, + eq(1), + { r -> r == 0 } + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/statics/substitution/StaticsSubstitutionTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/statics/substitution/StaticsSubstitutionTest.kt new file mode 100644 index 0000000000..ff5c014a68 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/statics/substitution/StaticsSubstitutionTest.kt @@ -0,0 +1,32 @@ +package org.utbot.examples.statics.substitution + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutSubstituteStaticsWithSymbolicVariable +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +class StaticsSubstitutionTest : UtValueTestCaseChecker(testClass = StaticSubstitutionExamples::class) { + + @Test + fun lessThanZeroWithSubstitution() { + check( + StaticSubstitutionExamples::lessThanZero, + eq(2), + { r -> r != 0 }, + { r -> r == 0 }, + ) + } + + @Test + fun lessThanZeroWithoutSubstitution() { + withoutSubstituteStaticsWithSymbolicVariable { + checkWithoutStaticsSubstitution( + StaticSubstitutionExamples::lessThanZero, + eq(1), + { r -> r != 0 }, + coverage = DoNotCalculate, + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/stdlib/DateExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stdlib/DateExampleTest.kt new file mode 100644 index 0000000000..af887d6c4c --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stdlib/DateExampleTest.kt @@ -0,0 +1,67 @@ +package org.utbot.examples.stdlib + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withUsingReflectionForMaximizingCoverage +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException +import java.util.Date + +@Disabled("Java 11 transition -- these tests seems to take too much time and memory") +class DateExampleTest : UtValueTestCaseChecker(testClass = DateExample::class) { + @Suppress("SpellCheckingInspection") + @Tag("slow") + @Test + fun testGetTimeWithNpeChecksForNonPublicFields() { + withUsingReflectionForMaximizingCoverage(maximizeCoverage = true) { + checkWithException( + DateExample::getTime, + eq(5), + *commonMatchers, + { date: Date?, r: Result -> + val cdate = date!!.getDeclaredFieldValue("cdate") + val calendarDate = cdate!!.getDeclaredFieldValue("date") + + calendarDate == null && r.isException() + }, + { date: Date?, r: Result -> + val cdate = date!!.getDeclaredFieldValue("cdate") + val calendarDate = cdate!!.getDeclaredFieldValue("date") + + val gcal = date.getDeclaredFieldValue("gcal") + + val normalized = calendarDate!!.getDeclaredFieldValue("normalized") as Boolean + val gregorianYear = calendarDate.getDeclaredFieldValue("gregorianYear") as Int + + gcal == null && !normalized && gregorianYear >= 1582 && r.isException() + } + ) + } + } + + @Test + fun testGetTimeWithoutReflection() { + withUsingReflectionForMaximizingCoverage(maximizeCoverage = false) { + checkWithException( + DateExample::getTime, + eq(3), + *commonMatchers + ) + } + } + + private val commonMatchers = arrayOf( + { date: Date?, r: Result -> date == null && r.isException() }, + { date: Date?, r: Result -> date != null && date.time == 100L && r.getOrThrow() }, + { date: Date?, r: Result -> date != null && date.time != 100L && !r.getOrThrow() } + ) + + private fun Any.getDeclaredFieldValue(fieldName: String): Any? { + val declaredField = javaClass.getDeclaredField(fieldName) + declaredField.isAccessible = true + + return declaredField.get(this) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/stdlib/JavaIOFileInputStreamCheckTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stdlib/JavaIOFileInputStreamCheckTest.kt new file mode 100644 index 0000000000..db4082881e --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stdlib/JavaIOFileInputStreamCheckTest.kt @@ -0,0 +1,39 @@ +package org.utbot.examples.stdlib + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +internal class JavaIOFileInputStreamCheckTest : UtValueTestCaseChecker( + testClass = JavaIOFileInputStreamCheck::class, + testCodeGeneration = true, + // because of mocks + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testRead() { + checkMocksAndInstrumentation( + JavaIOFileInputStreamCheck::read, + eq(1), + { _, _, instrumentation, r -> + val constructorMock = instrumentation.single() as UtNewInstanceInstrumentation + + val classIdEquality = constructorMock.classId == java.io.FileInputStream::class.id + val callSiteIdEquality = constructorMock.callSites.single() == JavaIOFileInputStreamCheck::class.id + val instance = constructorMock.instances.single() as UtCompositeModel + val methodMock = instance.mocks.entries.single() + val methodNameEquality = methodMock.key.name == "read" + val mockValueResult = r == (methodMock.value.single() as UtPrimitiveModel).value as Int + + classIdEquality && callSiteIdEquality && instance.isMock && methodNameEquality && mockValueResult + }, + additionalMockAlwaysClasses = setOf(java.io.FileInputStream::class.id), + coverage = DoNotCalculate // there is a problem with coverage calculation of mocked values + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/stdlib/StaticsPathDiversionTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stdlib/StaticsPathDiversionTest.kt new file mode 100644 index 0000000000..5d46100f88 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stdlib/StaticsPathDiversionTest.kt @@ -0,0 +1,36 @@ +package org.utbot.examples.stdlib + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.util.id +import org.utbot.testcheckers.ge +import org.utbot.testing.FullWithAssumptions +import org.utbot.testing.UtValueTestCaseChecker +import java.io.File + +internal class StaticsPathDiversionTest : UtValueTestCaseChecker( + testClass = StaticsPathDiversion::class, +) { + @Test + @Disabled("See https://github.com/UnitTestBot/UTBotJava/issues/716") + fun testJavaIOFile() { + // TODO Here we have a path diversion example - the static field `java.io.File#separator` is considered as not meaningful, + // so it is not passed to the concrete execution because of absence in the `stateBefore` models. + // So, the symbolic engine has 2 results - true and false, as expected, but the concrete executor may produce 1 or 2, + // depending on the model for the argument of the MUT produced by the solver. + // Such diversion was predicted to some extent - see `org.utbot.common.WorkaroundReason.IGNORE_STATICS_FROM_TRUSTED_LIBRARIES` + // and the corresponding issue https://github.com/UnitTestBot/UTBotJava/issues/716 + check( + StaticsPathDiversion::separatorEquality, + ge(2), // We cannot guarantee the exact number of branches without minimization + + // In the matchers below we check that the symbolic does not change the static field `File.separator` - we should + // change the parameter, not the static field + { s, separator -> separator == File.separator && s == separator }, + { s, separator -> separator == File.separator && s != separator }, + additionalMockAlwaysClasses = setOf(java.io.File::class.id), // From the use-case + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt similarity index 77% rename from utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt index 5ab5a133b9..c0f4abe0ef 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/BaseStreamExampleTest.kt @@ -3,52 +3,34 @@ package org.utbot.examples.stream import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.Full -import org.utbot.examples.FullWithAssumptions -import org.utbot.examples.StaticsType -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.examples.withoutConcrete -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.AtLeast +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.Full +import org.utbot.testing.FullWithAssumptions +import org.utbot.testing.StaticsType +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException import java.util.Optional import java.util.stream.Stream -import kotlin.streams.toList +import org.utbot.testing.asList // TODO 1 instruction is always uncovered https://github.com/UnitTestBot/UTBotJava/issues/193 // TODO failed Kotlin compilation (generics) JIRA:1332 -class BaseStreamExampleTest : AbstractTestCaseGeneratorTest( +class BaseStreamExampleTest : UtValueTestCaseChecker( testClass = BaseStreamExample::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { - @Test - fun testReturningStreamExample() { - withoutConcrete { - check( - BaseStreamExample::returningStreamExample, - eq(2), - // NOTE: the order of the matchers is important because Stream could be used only once - { c, r -> c.isNotEmpty() && c == r!!.toList() }, - { c, r -> c.isEmpty() && c == r!!.toList() }, - coverage = FullWithAssumptions(assumeCallsNumber = 1) - ) - } - } - @Test fun testReturningStreamAsParameterExample() { withoutConcrete { check( BaseStreamExample::returningStreamAsParameterExample, eq(1), - { s, r -> s != null && s.toList() == r!!.toList() }, + { s, r -> s != null && s.asList() == r!!.asList() }, coverage = FullWithAssumptions(assumeCallsNumber = 1) ) } @@ -69,10 +51,46 @@ class BaseStreamExampleTest : AbstractTestCaseGeneratorTest( fun testMapExample() { checkWithException( BaseStreamExample::mapExample, - eq(2), + ignoreExecutionsNumber, { c, r -> null in c && r.isException() }, { c, r -> r.getOrThrow().contentEquals(c.map { it * 2 }.toTypedArray()) }, - coverage = DoNotCalculate + coverage = AtLeast(90) + ) + } + + @Test + @Tag("slow") + fun testMapToIntExample() { + checkWithException( + BaseStreamExample::mapToIntExample, + ignoreExecutionsNumber, + { c, r -> null in c && r.isException() }, + { c, r -> r.getOrThrow().contentEquals(c.map { it.toInt() }.toIntArray()) }, + coverage = AtLeast(90) + ) + } + + @Test + @Tag("slow") + fun testMapToLongExample() { + checkWithException( + BaseStreamExample::mapToLongExample, + ignoreExecutionsNumber, + { c, r -> null in c && r.isException() }, + { c, r -> r.getOrThrow().contentEquals(c.map { it.toLong() }.toLongArray()) }, + coverage = AtLeast(90) + ) + } + + @Test + @Tag("slow") + fun testMapToDoubleExample() { + checkWithException( + BaseStreamExample::mapToDoubleExample, + ignoreExecutionsNumber, + { c, r -> null in c && r.isException() }, + { c, r -> r.getOrThrow().contentEquals(c.map { it.toDouble() }.toDoubleArray()) }, + coverage = AtLeast(90) ) } @@ -87,6 +105,38 @@ class BaseStreamExampleTest : AbstractTestCaseGeneratorTest( } @Test + @Tag("slow") + fun testFlatMapToIntExample() { + check( + BaseStreamExample::flatMapToIntExample, + ignoreExecutionsNumber, + { c, r -> r.contentEquals(c.flatMap { listOf(it?.toInt() ?: 0, it?.toInt() ?: 0) }.toIntArray()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testFlatMapToLongExample() { + check( + BaseStreamExample::flatMapToLongExample, + ignoreExecutionsNumber, + { c, r -> r.contentEquals(c.flatMap { listOf(it?.toLong() ?: 0L, it?.toLong() ?: 0L) }.toLongArray()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testFlatMapToDoubleExample() { + check( + BaseStreamExample::flatMapToDoubleExample, + ignoreExecutionsNumber, + { c, r -> r.contentEquals(c.flatMap { listOf(it?.toDouble() ?: 0.0, it?.toDouble() ?: 0.0) }.toDoubleArray()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + @Tag("slow") fun testDistinctExample() { check( BaseStreamExample::distinctExample, @@ -145,9 +195,9 @@ class BaseStreamExampleTest : AbstractTestCaseGeneratorTest( fun testForEachExample() { checkThisAndStaticsAfter( BaseStreamExample::forEachExample, - eq(2), + ignoreExecutionsNumber, *streamConsumerStaticsMatchers, - coverage = DoNotCalculate + coverage = AtLeast(92) ) } @@ -155,7 +205,7 @@ class BaseStreamExampleTest : AbstractTestCaseGeneratorTest( fun testToArrayExample() { check( BaseStreamExample::toArrayExample, - ignoreExecutionsNumber, + eq(2), { c, r -> c.toTypedArray().contentEquals(r) }, coverage = FullWithAssumptions(assumeCallsNumber = 1) ) @@ -310,10 +360,11 @@ class BaseStreamExampleTest : AbstractTestCaseGeneratorTest( fun testIteratorExample() { checkWithException( BaseStreamExample::iteratorSumExample, - eq(2), + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == 0 }, { c, r -> null in c && r.isException() }, - { c, r -> null !in c && r.getOrThrow() == c.sum() }, - coverage = DoNotCalculate + { c, r -> c.isNotEmpty() && null !in c && r.getOrThrow() == c.sum() }, + coverage = AtLeast(75) ) } @@ -342,14 +393,13 @@ class BaseStreamExampleTest : AbstractTestCaseGeneratorTest( } @Test - @Disabled("TODO unsat type constraints https://github.com/UnitTestBot/UTBotJava/issues/253") fun testCustomCollectionStreamExample() { check( BaseStreamExample::customCollectionStreamExample, ignoreExecutionsNumber, { c, r -> c.isEmpty() && r == 0L }, { c, r -> c.isNotEmpty() && c.size.toLong() == r }, - coverage = DoNotCalculate + coverage = DoNotCalculate // TODO failed coverage calculation ) } @@ -393,15 +443,15 @@ class BaseStreamExampleTest : AbstractTestCaseGeneratorTest( coverage = Full ) } +} - private val streamConsumerStaticsMatchers = arrayOf( - { _: BaseStreamExample, c: List, _: StaticsType, _: Int? -> null in c }, - { _: BaseStreamExample, c: List, statics: StaticsType, r: Int? -> - val x = statics.values.single().value as Int +internal val streamConsumerStaticsMatchers = arrayOf( + { _: Any, c: List, _: StaticsType, _: Int? -> null in c }, + { _: Any, c: List, statics: StaticsType, r: Int? -> + val x = statics.values.single().value as Int - r!! + c.sumOf { it ?: 0 } == x - } - ) -} + r!! + c.sumOf { it ?: 0 } == x + } +) -private fun > Sequence.isSorted(): Boolean = zipWithNext { a, b -> a <= b }.all { it } +internal fun > Sequence.isSorted(): Boolean = zipWithNext { a, b -> a <= b }.all { it } diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/DoubleStreamExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/DoubleStreamExampleTest.kt new file mode 100644 index 0000000000..ebfa983bae --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/DoubleStreamExampleTest.kt @@ -0,0 +1,534 @@ +package org.utbot.examples.stream + +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withPathSelectorStepsLimit +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.AtLeast +import org.utbot.testing.Full +import org.utbot.testing.FullWithAssumptions +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException +import java.util.OptionalDouble +import java.util.stream.DoubleStream +import kotlin.streams.toList + +// TODO failed Kotlin compilation (generics) JIRA:1332 +@Tag("slow") // we do not really need to always use this test in CI because it is almost the same as BaseStreamExampleTest +class DoubleStreamExampleTest : UtValueTestCaseChecker( + testClass = DoubleStreamExample::class, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testReturningStreamAsParameterExample() { + withoutConcrete { + check( + DoubleStreamExample::returningStreamAsParameterExample, + eq(1), + { s, r -> s != null && s.toList() == r!!.toList() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testUseParameterStream() { + check( + DoubleStreamExample::useParameterStream, + eq(2), + { s, r -> s.toArray().isEmpty() && r == 0 }, + { s, r -> s.toArray().let { + it.isNotEmpty() && r == it.size } + }, + coverage = AtLeast(94) + ) + } + + @Test + fun testFilterExample() { + check( + DoubleStreamExample::filterExample, + ignoreExecutionsNumber, + { c, r -> null !in c && r == false }, + { c, r -> null in c && r == true }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapExample() { + check( + DoubleStreamExample::mapExample, + ignoreExecutionsNumber, + { c, r -> null in c && r.contentEquals(c.doubles { it?.toDouble()?.times(2) ?: 0.0 }) }, + { c: List, r -> null !in c && r.contentEquals(c.doubles { it?.toDouble()?.times(2) ?: 0.0 }) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapToObjExample() { + check( + DoubleStreamExample::mapToObjExample, + ignoreExecutionsNumber, + { c, r -> + val intArrays = c.doubles().map { it.let { i -> doubleArrayOf(i, i) } }.toTypedArray() + + null in c && intArrays.zip(r as Array) + .all { it.first.contentEquals(it.second as DoubleArray?) } + }, + { c: List, r -> + val intArrays = c.doubles().map { it.let { i -> doubleArrayOf(i, i) } }.toTypedArray() + + null !in c && intArrays.zip(r as Array) + .all { it.first.contentEquals(it.second as DoubleArray?) } + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapToIntExample() { + check( + DoubleStreamExample::mapToIntExample, + ignoreExecutionsNumber, + { c, r -> + val ints = c.doubles().map { it.toInt() }.toIntArray() + + null in c && ints.contentEquals(r) + }, + { c: List, r -> + val ints = c.doubles().map { it.toInt() }.toIntArray() + + null !in c && ints.contentEquals(r) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapToLongExample() { + check( + DoubleStreamExample::mapToLongExample, + ignoreExecutionsNumber, + { c, r -> + val longs = c.doubles().map { it.toLong() }.toLongArray() + + null in c && longs.contentEquals(r) + }, + { c: List, r -> + val longs = c.doubles().map { it.toLong() }.toLongArray() + + null !in c && longs.contentEquals(r) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testFlatMapExample() { + check( + DoubleStreamExample::flatMapExample, + ignoreExecutionsNumber, + { c, r -> + val intLists = c.mapNotNull { + it.toDouble().let { i -> listOf(i, i) } + } + + r!!.contentEquals(intLists.flatten().toDoubleArray()) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testDistinctExample() { + check( + DoubleStreamExample::distinctExample, + ignoreExecutionsNumber, + { c, r -> + val doubles = c.doubles() + + doubles.contentEquals(doubles.distinct().toDoubleArray()) && r == false + }, + { c, r -> + val doubles = c.doubles() + + !doubles.contentEquals(doubles.distinct().toDoubleArray()) && r == true + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + @Tag("slow") + // TODO slow sorting https://github.com/UnitTestBot/UTBotJava/issues/188 + fun testSortedExample() { + check( + DoubleStreamExample::sortedExample, + ignoreExecutionsNumber, + { c, r -> c.last() < c.first() && r!!.asSequence().isSorted() }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + + @Test + fun testPeekExample() { + checkThisAndStaticsAfter( + DoubleStreamExample::peekExample, + ignoreExecutionsNumber, + *streamConsumerStaticsMatchers, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testLimitExample() { + check( + DoubleStreamExample::limitExample, + ignoreExecutionsNumber, + { c, r -> c.size <= 2 && c.doubles().contentEquals(r) }, + { c, r -> c.size > 2 && c.take(2).doubles().contentEquals(r) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testSkipExample() { + check( + DoubleStreamExample::skipExample, + ignoreExecutionsNumber, + { c, r -> c.size > 2 && c.drop(2).doubles().contentEquals(r) }, + { c, r -> c.size <= 2 && r!!.isEmpty() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testForEachExample() { + checkThisAndStaticsAfter( + DoubleStreamExample::forEachExample, + ignoreExecutionsNumber, + *streamConsumerStaticsMatchers, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testToArrayExample() { + check( + DoubleStreamExample::toArrayExample, + ignoreExecutionsNumber, + { c, r -> c.doubles().contentEquals(r) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testReduceExample() { + check( + DoubleStreamExample::reduceExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 42.0 }, + { c: List, r -> c.isNotEmpty() && r == c.filterNotNull().sum() + 42.0 }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testOptionalReduceExample() { + checkWithException( + DoubleStreamExample::optionalReduceExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == OptionalDouble.empty() }, + { c: List, r -> + c.isNotEmpty() && r.getOrThrow() == OptionalDouble.of( + c.filterNotNull().sum().toDouble() + ) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testSumExample() { + check( + DoubleStreamExample::sumExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 0.0 }, + { c, r -> c.isNotEmpty() && c.filterNotNull().sum().toDouble() == r }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMinExample() { + checkWithException( + DoubleStreamExample::minExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == OptionalDouble.empty() }, + { c, r -> + c.isNotEmpty() && r.getOrThrow() == OptionalDouble.of(c.mapNotNull { it.toDouble() }.minOrNull()!!) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMaxExample() { + checkWithException( + DoubleStreamExample::maxExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == OptionalDouble.empty() }, + { c, r -> + c.isNotEmpty() && r.getOrThrow() == OptionalDouble.of(c.mapNotNull { it.toDouble() }.maxOrNull()!!) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testCountExample() { + check( + DoubleStreamExample::countExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 0L }, + { c, r -> c.isNotEmpty() && c.size.toLong() == r }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testAverageExample() { + check( + DoubleStreamExample::averageExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == OptionalDouble.empty() }, + { c, r -> c.isNotEmpty() && c.mapNotNull { it.toDouble() }.average() == r!!.asDouble }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testSummaryStatisticsExample() { + withoutConcrete { + check( + DoubleStreamExample::summaryStatisticsExample, + ignoreExecutionsNumber, + { c, r -> + val sum = r!!.sum + val count = r.count + val min = r.min + val max = r.max + + val allStatisticsAreCorrect = sum == 0.0 && + count == 0L && + min == Double.POSITIVE_INFINITY && + max == Double.NEGATIVE_INFINITY + + c.isEmpty() && allStatisticsAreCorrect + }, + { c, r -> + val sum = r!!.sum + val count = r.count + val min = r.min + val max = r.max + + val doubles = c.doubles() + + val allStatisticsAreCorrect = sum == doubles.sum() && + count == doubles.size.toLong() && + min == doubles.minOrNull() && + max == doubles.maxOrNull() + + c.isNotEmpty() && allStatisticsAreCorrect + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testAnyMatchExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(2000) { + check( + DoubleStreamExample::anyMatchExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == false }, + { c, r -> c.isNotEmpty() && c.doubles().all { it == 0.0 } && r == false }, + { c, r -> + val doubles = c.doubles() + + c.isNotEmpty() && doubles.first() != 0.0 && doubles.last() == 0.0 && r == true + }, + { c, r -> + val doubles = c.doubles() + + c.isNotEmpty() && doubles.first() == 0.0 && doubles.last() != 0.0 && r == true + }, + { c, r -> + val doubles = c.doubles() + + c.isNotEmpty() && doubles.none { it == 0.0 } && r == true + }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + } + + @Test + fun testAllMatchExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(2000) { + check( + DoubleStreamExample::allMatchExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == true }, + { c, r -> c.isNotEmpty() && c.doubles().all { it == 0.0 } && r == false }, + { c, r -> + val doubles = c.doubles() + + c.isNotEmpty() && doubles.first() != 0.0 && doubles.last() == 0.0 && r == false + }, + { c, r -> + val doubles = c.doubles() + + c.isNotEmpty() && doubles.first() == 0.0 && doubles.last() != 0.0 && r == false + }, + { c, r -> + val doubles = c.doubles() + + c.isNotEmpty() && doubles.none { it == 0.0 } && r == true + }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + } + + @Test + fun testNoneMatchExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(2000) { + check( + DoubleStreamExample::noneMatchExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == true }, + { c, r -> c.isNotEmpty() && c.doubles().all { it == 0.0 } && r == true }, + { c, r -> + val doubles = c.doubles() + + c.isNotEmpty() && doubles.first() != 0.0 && doubles.last() == 0.0 && r == false + }, + { c, r -> + val doubles = c.doubles() + + c.isNotEmpty() && doubles.first() == 0.0 && doubles.last() != 0.0 && r == false + }, + { c, r -> + val doubles = c.doubles() + + c.isNotEmpty() && doubles.none { it == 0.0 } && r == false + }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + } + + @Test + fun testFindFirstExample() { + check( + DoubleStreamExample::findFirstExample, + eq(3), + { c, r -> c.isEmpty() && r == OptionalDouble.empty() }, + { c, r -> c.isNotEmpty() && r == OptionalDouble.of(c.doubles().first()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testBoxedExample() { + check( + DoubleStreamExample::boxedExample, + ignoreExecutionsNumber, + { c, r -> c.doubles().toList() == r!!.toList() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testIteratorExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(1000) { + check( + DoubleStreamExample::iteratorSumExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 0.0 }, + { c: List, r -> c.isNotEmpty() && c.doubles().sum() == r }, + coverage = AtLeast(76) + ) + } + } + + @Test + fun testStreamOfExample() { + withoutConcrete { + check( + DoubleStreamExample::streamOfExample, + ignoreExecutionsNumber, + // NOTE: the order of the matchers is important because Stream could be used only once + { c, r -> c.isNotEmpty() && c.contentEquals(r!!.toArray()) }, + { c, r -> c.isEmpty() && DoubleStream.empty().toArray().contentEquals(r!!.toArray()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testClosedStreamExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(500) { + checkWithException( + DoubleStreamExample::closedStreamExample, + ignoreExecutionsNumber, + { _, r -> r.isException() }, + coverage = AtLeast(88) + ) + } + } + + @Test + fun testGenerateExample() { + check( + DoubleStreamExample::generateExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(DoubleArray(10) { 42.0 }) }, + coverage = Full + ) + } + + @Test + fun testIterateExample() { + check( + DoubleStreamExample::iterateExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(DoubleArray(10) { i -> 42.0 + i }) }, + coverage = Full + ) + } + + @Test + fun testConcatExample() { + check( + DoubleStreamExample::concatExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(DoubleArray(10) { 42.0 } + DoubleArray(10) { i -> 42.0 + i }) }, + coverage = Full + ) + } +} + +private fun List.doubles(mapping: (Short?) -> Double = { it?.toDouble() ?: 0.0 }): DoubleArray = + map { mapping(it) }.toDoubleArray() diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/IntStreamExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/IntStreamExampleTest.kt new file mode 100644 index 0000000000..4aad1a7855 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/IntStreamExampleTest.kt @@ -0,0 +1,566 @@ +package org.utbot.examples.stream + +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withPathSelectorStepsLimit +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.AtLeast +import org.utbot.testing.Full +import org.utbot.testing.FullWithAssumptions +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException +import java.util.OptionalDouble +import java.util.OptionalInt +import java.util.stream.IntStream +import kotlin.streams.toList + +// TODO failed Kotlin compilation (generics) JIRA:1332 +@Tag("slow") // we do not really need to always use this test in CI because it is almost the same as BaseStreamExampleTest +class IntStreamExampleTest : UtValueTestCaseChecker( + testClass = IntStreamExample::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testReturningStreamAsParameterExample() { + withoutConcrete { + check( + IntStreamExample::returningStreamAsParameterExample, + eq(1), + { s, r -> s != null && s.toList() == r!!.toList() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testUseParameterStream() { + check( + IntStreamExample::useParameterStream, + eq(2), + { s, r -> s.toArray().isEmpty() && r == 0 }, + { s, r -> s.toArray().let { + it.isNotEmpty() && r == it.size } + }, + coverage = AtLeast(94) + ) + } + + @Test + fun testFilterExample() { + check( + IntStreamExample::filterExample, + ignoreExecutionsNumber, + { c, r -> null !in c && r == false }, + { c, r -> null in c && r == true }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapExample() { + check( + IntStreamExample::mapExample, + ignoreExecutionsNumber, + { c, r -> null in c && r.contentEquals(c.ints { it?.toInt()?.times(2) ?: 0 }) }, + { c: List, r -> null !in c && r.contentEquals(c.ints { it?.toInt()?.times(2) ?: 0 }) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapToObjExample() { + check( + IntStreamExample::mapToObjExample, + ignoreExecutionsNumber, + { c, r -> + val intArrays = c.ints().map { it.let { i -> intArrayOf(i, i) } }.toTypedArray() + + null in c && intArrays.zip(r as Array).all { it.first.contentEquals(it.second as IntArray?) } + }, + { c: List, r -> + val intArrays = c.ints().map { it.let { i -> intArrayOf(i, i) } }.toTypedArray() + + null !in c && intArrays.zip(r as Array).all { it.first.contentEquals(it.second as IntArray?) } + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapToLongExample() { + check( + IntStreamExample::mapToLongExample, + ignoreExecutionsNumber, + { c, r -> + val longs = c.ints().map { it.toLong() * 2 }.toLongArray() + + null in c && longs.contentEquals(r) + }, + { c: List, r -> + val longs = c.ints().map { it.toLong() * 2 }.toLongArray() + + null !in c && longs.contentEquals(r) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapToDoubleExample() { + check( + IntStreamExample::mapToDoubleExample, + ignoreExecutionsNumber, + { c, r -> + val doubles = c.ints().map { it.toDouble() / 2 }.toDoubleArray() + + null in c && doubles.contentEquals(r) + }, + { c: List, r -> + val doubles = c.filterNotNull().map { it.toDouble() / 2 }.toDoubleArray() + + null !in c && doubles.contentEquals(r) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testFlatMapExample() { + check( + IntStreamExample::flatMapExample, + ignoreExecutionsNumber, + { c, r -> + val intLists = c.mapNotNull { + it.toInt().let { i -> listOf(i, i) } + } + + r!!.contentEquals(intLists.flatten().toIntArray()) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testDistinctExample() { + check( + IntStreamExample::distinctExample, + ignoreExecutionsNumber, + { c, r -> + val ints = c.ints() + + ints.contentEquals(ints.distinct().toIntArray()) && r == false + }, + { c, r -> + val ints = c.ints() + + !ints.contentEquals(ints.distinct().toIntArray()) && r == true + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + @Tag("slow") + // TODO slow sorting https://github.com/UnitTestBot/UTBotJava/issues/188 + fun testSortedExample() { + check( + IntStreamExample::sortedExample, + ignoreExecutionsNumber, + { c, r -> c.last() < c.first() && r!!.asSequence().isSorted() }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + + @Test + fun testPeekExample() { + checkThisAndStaticsAfter( + IntStreamExample::peekExample, + ignoreExecutionsNumber, + *streamConsumerStaticsMatchers, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testLimitExample() { + check( + IntStreamExample::limitExample, + ignoreExecutionsNumber, + { c, r -> c.size <= 2 && c.ints().contentEquals(r) }, + { c, r -> c.size > 2 && c.take(2).ints().contentEquals(r) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testSkipExample() { + check( + IntStreamExample::skipExample, + ignoreExecutionsNumber, + { c, r -> c.size > 2 && c.drop(2).ints().contentEquals(r) }, + { c, r -> c.size <= 2 && r!!.isEmpty() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testForEachExample() { + checkThisAndStaticsAfter( + IntStreamExample::forEachExample, + ignoreExecutionsNumber, + *streamConsumerStaticsMatchers, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testToArrayExample() { + check( + IntStreamExample::toArrayExample, + ignoreExecutionsNumber, + { c, r -> c.ints().contentEquals(r) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testReduceExample() { + check( + IntStreamExample::reduceExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 42 }, + { c: List, r -> c.isNotEmpty() && r == c.filterNotNull().sum() + 42 }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testOptionalReduceExample() { + checkWithException( + IntStreamExample::optionalReduceExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == OptionalInt.empty() }, + { c: List, r -> c.isNotEmpty() && r.getOrThrow() == OptionalInt.of(c.filterNotNull().sum()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testSumExample() { + check( + IntStreamExample::sumExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 0 }, + { c, r -> c.isNotEmpty() && c.filterNotNull().sum() == r }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMinExample() { + checkWithException( + IntStreamExample::minExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == OptionalInt.empty() }, + { c, r -> c.isNotEmpty() && r.getOrThrow() == OptionalInt.of(c.mapNotNull { it.toInt() }.minOrNull()!!) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMaxExample() { + checkWithException( + IntStreamExample::maxExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == OptionalInt.empty() }, + { c, r -> c.isNotEmpty() && r.getOrThrow() == OptionalInt.of(c.mapNotNull { it.toInt() }.maxOrNull()!!) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testCountExample() { + check( + IntStreamExample::countExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 0L }, + { c, r -> c.isNotEmpty() && c.size.toLong() == r }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testAverageExample() { + check( + IntStreamExample::averageExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == OptionalDouble.empty() }, + { c, r -> c.isNotEmpty() && c.mapNotNull { it.toInt() }.average() == r!!.asDouble }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testSummaryStatisticsExample() { + withoutConcrete { + check( + IntStreamExample::summaryStatisticsExample, + ignoreExecutionsNumber, + { c, r -> + val sum = r!!.sum + val count = r.count + val min = r.min + val max = r.max + + val allStatisticsAreCorrect = sum == 0L && + count == 0L && + min == Int.MAX_VALUE && + max == Int.MIN_VALUE + + c.isEmpty() && allStatisticsAreCorrect + }, + { c, r -> + val sum = r!!.sum + val count = r.count + val min = r.min + val max = r.max + + val ints = c.ints() + + val allStatisticsAreCorrect = sum == ints.sum().toLong() && + count == ints.size.toLong() && + min == ints.minOrNull() && + max == ints.maxOrNull() + + c.isNotEmpty() && allStatisticsAreCorrect + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testAnyMatchExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(2000) { + check( + IntStreamExample::anyMatchExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == false }, + { c, r -> c.isNotEmpty() && c.ints().all { it == 0 } && r == false }, + { c, r -> + val ints = c.ints() + + c.isNotEmpty() && ints.first() != 0 && ints.last() == 0 && r == true + }, + { c, r -> + val ints = c.ints() + + c.isNotEmpty() && ints.first() == 0 && ints.last() != 0 && r == true + }, + { c, r -> + val ints = c.ints() + + c.isNotEmpty() && ints.none { it == 0 } && r == true + }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + } + + @Test + fun testAllMatchExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(2000) { + check( + IntStreamExample::allMatchExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == true }, + { c, r -> c.isNotEmpty() && c.ints().all { it == 0 } && r == false }, + { c, r -> + val ints = c.ints() + + c.isNotEmpty() && ints.first() != 0 && ints.last() == 0 && r == false + }, + { c, r -> + val ints = c.ints() + + c.isNotEmpty() && ints.first() == 0 && ints.last() != 0 && r == false + }, + { c, r -> + val ints = c.ints() + + c.isNotEmpty() && ints.none { it == 0 } && r == true + }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + } + + @Test + fun testNoneMatchExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(2000) { + check( + IntStreamExample::noneMatchExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == true }, + { c, r -> c.isNotEmpty() && c.ints().all { it == 0 } && r == true }, + { c, r -> + val ints = c.ints() + + c.isNotEmpty() && ints.first() != 0 && ints.last() == 0 && r == false + }, + { c, r -> + val ints = c.ints() + + c.isNotEmpty() && ints.first() == 0 && ints.last() != 0 && r == false + }, + { c, r -> + val ints = c.ints() + + c.isNotEmpty() && ints.none { it == 0 } && r == false + }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + } + + @Test + fun testFindFirstExample() { + check( + IntStreamExample::findFirstExample, + eq(3), + { c, r -> c.isEmpty() && r == OptionalInt.empty() }, + { c, r -> c.isNotEmpty() && r == OptionalInt.of(c.ints().first()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testAsLongStreamExample() { + check( + IntStreamExample::asLongStreamExample, + ignoreExecutionsNumber, + { c, r -> c.ints().map { it.toLong() }.toList() == r!!.toList() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testAsDoubleStreamExample() { + check( + IntStreamExample::asDoubleStreamExample, + ignoreExecutionsNumber, + { c, r -> c.ints().map { it.toDouble() }.toList() == r!!.toList() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testBoxedExample() { + check( + IntStreamExample::boxedExample, + ignoreExecutionsNumber, + { c, r -> c.ints().toList() == r!!.toList() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testIteratorExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(1000) { + check( + IntStreamExample::iteratorSumExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 0 }, + { c: List, r -> c.isNotEmpty() && c.ints().sum() == r }, + coverage = AtLeast(76) + ) + } + } + + @Test + fun testStreamOfExample() { + withoutConcrete { + check( + IntStreamExample::streamOfExample, + ignoreExecutionsNumber, + // NOTE: the order of the matchers is important because Stream could be used only once + { c, r -> c.isNotEmpty() && c.contentEquals(r!!.toArray()) }, + { c, r -> c.isEmpty() && IntStream.empty().toArray().contentEquals(r!!.toArray()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testClosedStreamExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(500) { + checkWithException( + IntStreamExample::closedStreamExample, + ignoreExecutionsNumber, + { _, r -> r.isException() }, + coverage = AtLeast(88) + ) + } + } + + @Test + fun testGenerateExample() { + check( + IntStreamExample::generateExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(IntArray(10) { 42 }) }, + coverage = Full + ) + } + + @Test + fun testIterateExample() { + check( + IntStreamExample::iterateExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(IntArray(10) { i -> 42 + i }) }, + coverage = Full + ) + } + + @Test + fun testConcatExample() { + check( + IntStreamExample::concatExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(IntArray(10) { 42 } + IntArray(10) { i -> 42 + i }) }, + coverage = Full + ) + } + + @Test + fun testRangeExample() { + check( + IntStreamExample::rangeExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(IntArray(10) { it }) }, + coverage = Full + ) + } + + @Test + fun testRangeClosedExample() { + check( + IntStreamExample::rangeClosedExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(IntArray(11) { it }) }, + coverage = Full + ) + } +} + +private fun List.ints(mapping: (Short?) -> Int = { it?.toInt() ?: 0 }): IntArray = + map { mapping(it) }.toIntArray() diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/LongStreamExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/LongStreamExampleTest.kt new file mode 100644 index 0000000000..bf9731a27b --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/LongStreamExampleTest.kt @@ -0,0 +1,560 @@ +package org.utbot.examples.stream + +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withPathSelectorStepsLimit +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.AtLeast +import org.utbot.testing.Full +import org.utbot.testing.FullWithAssumptions +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException +import java.util.OptionalDouble +import java.util.OptionalLong +import java.util.stream.LongStream +import kotlin.streams.toList + +// TODO failed Kotlin compilation (generics) JIRA:1332 +@Tag("slow") // we do not really need to always use this test in CI because it is almost the same as BaseStreamExampleTest +class LongStreamExampleTest : UtValueTestCaseChecker( + testClass = LongStreamExample::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testReturningStreamAsParameterExample() { + withoutConcrete { + check( + LongStreamExample::returningStreamAsParameterExample, + eq(1), + { s, r -> s != null && s.toList() == r!!.toList() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testUseParameterStream() { + check( + LongStreamExample::useParameterStream, + eq(2), + { s, r -> s.toArray().isEmpty() && r == 0 }, + { s, r -> s.toArray().let { + it.isNotEmpty() && r == it.size } + }, + coverage = AtLeast(94) + ) + } + + @Test + fun testFilterExample() { + check( + LongStreamExample::filterExample, + ignoreExecutionsNumber, + { c, r -> null !in c && r == false }, + { c, r -> null in c && r == true }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapExample() { + check( + LongStreamExample::mapExample, + ignoreExecutionsNumber, + { c, r -> null in c && r.contentEquals(c.longs { it?.toLong()?.times(2) ?: 0L }) }, + { c: List, r -> null !in c && r.contentEquals(c.longs { it?.toLong()?.times(2) ?: 0L }) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapToObjExample() { + check( + LongStreamExample::mapToObjExample, + ignoreExecutionsNumber, + { c, r -> + val intArrays = c.longs().map { it.let { i -> longArrayOf(i, i) } }.toTypedArray() + + null in c && intArrays.zip(r as Array).all { it.first.contentEquals(it.second as LongArray?) } + }, + { c: List, r -> + val intArrays = c.longs().map { it.let { i -> longArrayOf(i, i) } }.toTypedArray() + + null !in c && intArrays.zip(r as Array).all { it.first.contentEquals(it.second as LongArray?) } + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapToIntExample() { + check( + LongStreamExample::mapToIntExample, + ignoreExecutionsNumber, + { c, r -> + val ints = c.longs().map { it.toInt() }.toIntArray() + + null in c && ints.contentEquals(r) + }, + { c: List, r -> + val ints = c.longs().map { it.toInt() }.toIntArray() + + null !in c && ints.contentEquals(r) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMapToDoubleExample() { + check( + LongStreamExample::mapToDoubleExample, + ignoreExecutionsNumber, + { c, r -> + val doubles = c.longs().map { it.toDouble() / 2 }.toDoubleArray() + + null in c && doubles.contentEquals(r) + }, + { c: List, r -> + val doubles = c.filterNotNull().map { it.toDouble() / 2 }.toDoubleArray() + + null !in c && doubles.contentEquals(r) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testFlatMapExample() { + check( + LongStreamExample::flatMapExample, + ignoreExecutionsNumber, + { c, r -> + val intLists = c.map { + (it?.toLong() ?: 0L).let { i -> listOf(i, i) } + } + + r!!.contentEquals(intLists.flatten().toLongArray()) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testDistinctExample() { + check( + LongStreamExample::distinctExample, + ignoreExecutionsNumber, + { c, r -> + val longs = c.longs() + + longs.contentEquals(longs.distinct().toLongArray()) && r == false + }, + { c, r -> + val longs = c.longs() + + !longs.contentEquals(longs.distinct().toLongArray()) && r == true + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + @Tag("slow") + // TODO slow sorting https://github.com/UnitTestBot/UTBotJava/issues/188 + fun testSortedExample() { + check( + LongStreamExample::sortedExample, + ignoreExecutionsNumber, + { c, r -> c.last() < c.first() && r!!.asSequence().isSorted() }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + + @Test + fun testPeekExample() { + checkThisAndStaticsAfter( + LongStreamExample::peekExample, + ignoreExecutionsNumber, + *streamConsumerStaticsMatchers, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testLimitExample() { + check( + LongStreamExample::limitExample, + ignoreExecutionsNumber, + { c, r -> c.size <= 2 && c.longs().contentEquals(r) }, + { c, r -> c.size > 2 && c.take(2).longs().contentEquals(r) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testSkipExample() { + check( + LongStreamExample::skipExample, + ignoreExecutionsNumber, + { c, r -> c.size > 2 && c.drop(2).longs().contentEquals(r) }, + { c, r -> c.size <= 2 && r!!.isEmpty() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testForEachExample() { + checkThisAndStaticsAfter( + LongStreamExample::forEachExample, + ignoreExecutionsNumber, + *streamConsumerStaticsMatchers, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testToArrayExample() { + check( + LongStreamExample::toArrayExample, + ignoreExecutionsNumber, + { c, r -> c.longs().contentEquals(r) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testReduceExample() { + check( + LongStreamExample::reduceExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 42L }, + { c: List, r -> c.isNotEmpty() && r == c.filterNotNull().sum() + 42L }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testOptionalReduceExample() { + checkWithException( + LongStreamExample::optionalReduceExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == OptionalLong.empty() }, + { c: List, r -> + c.isNotEmpty() && r.getOrThrow() == OptionalLong.of( + c.filterNotNull().sum().toLong() + ) + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testSumExample() { + check( + LongStreamExample::sumExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 0L }, + { c, r -> c.isNotEmpty() && c.filterNotNull().sum().toLong() == r }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMinExample() { + checkWithException( + LongStreamExample::minExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == OptionalLong.empty() }, + { c, r -> c.isNotEmpty() && r.getOrThrow() == OptionalLong.of(c.mapNotNull { it.toLong() }.minOrNull()!!) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testMaxExample() { + checkWithException( + LongStreamExample::maxExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r.getOrThrow() == OptionalLong.empty() }, + { c, r -> c.isNotEmpty() && r.getOrThrow() == OptionalLong.of(c.mapNotNull { it.toLong() }.maxOrNull()!!) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testCountExample() { + check( + LongStreamExample::countExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 0L }, + { c, r -> c.isNotEmpty() && c.size.toLong() == r }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testAverageExample() { + check( + LongStreamExample::averageExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == OptionalDouble.empty() }, + { c, r -> c.isNotEmpty() && c.mapNotNull { it.toLong() }.average() == r!!.asDouble }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testSummaryStatisticsExample() { + withoutConcrete { + check( + LongStreamExample::summaryStatisticsExample, + ignoreExecutionsNumber, + { c, r -> + val sum = r!!.sum + val count = r.count + val min = r.min + val max = r.max + + val allStatisticsAreCorrect = sum == 0L && + count == 0L && + min == Long.MAX_VALUE && + max == Long.MIN_VALUE + + c.isEmpty() && allStatisticsAreCorrect + }, + { c, r -> + val sum = r!!.sum + val count = r.count + val min = r.min + val max = r.max + + val longs = c.longs() + + val allStatisticsAreCorrect = sum == longs.sum() && + count == longs.size.toLong() && + min == longs.minOrNull() && + max == longs.maxOrNull() + + c.isNotEmpty() && allStatisticsAreCorrect + }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testAnyMatchExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(2000) { + check( + LongStreamExample::anyMatchExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == false }, + { c, r -> c.isNotEmpty() && c.longs().all { it == 0L } && r == false }, + { c, r -> + val longs = c.longs() + + c.isNotEmpty() && longs.first() != 0L && longs.last() == 0L && r == true + }, + { c, r -> + val longs = c.longs() + + c.isNotEmpty() && longs.first() == 0L && longs.last() != 0L && r == true + }, + { c, r -> + val longs = c.longs() + + c.isNotEmpty() && longs.none { it == 0L } && r == true + }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + } + + @Test + fun testAllMatchExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(2000) { + check( + LongStreamExample::allMatchExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == true }, + { c, r -> c.isNotEmpty() && c.longs().all { it == 0L } && r == false }, + { c, r -> + val longs = c.longs() + + c.isNotEmpty() && longs.first() != 0L && longs.last() == 0L && r == false + }, + { c, r -> + val longs = c.longs() + + c.isNotEmpty() && longs.first() == 0L && longs.last() != 0L && r == false + }, + { c, r -> + val longs = c.longs() + + c.isNotEmpty() && longs.none { it == 0L } && r == true + }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + } + + @Test + fun testNoneMatchExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(2000) { + check( + LongStreamExample::noneMatchExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == true }, + { c, r -> c.isNotEmpty() && c.longs().all { it == 0L } && r == true }, + { c, r -> + val longs = c.longs() + + c.isNotEmpty() && longs.first() != 0L && longs.last() == 0L && r == false + }, + { c, r -> + val longs = c.longs() + + c.isNotEmpty() && longs.first() == 0L && longs.last() != 0L && r == false + }, + { c, r -> + val longs = c.longs() + + c.isNotEmpty() && longs.none { it == 0L } && r == false + }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + } + + @Test + fun testFindFirstExample() { + check( + LongStreamExample::findFirstExample, + eq(3), + { c, r -> c.isEmpty() && r == OptionalLong.empty() }, + { c, r -> c.isNotEmpty() && r == OptionalLong.of(c.longs().first()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testAsDoubleStreamExample() { + check( + LongStreamExample::asDoubleStreamExample, + ignoreExecutionsNumber, + { c, r -> c.longs().map { it.toDouble() }.toList() == r!!.toList() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testBoxedExample() { + check( + LongStreamExample::boxedExample, + ignoreExecutionsNumber, + { c, r -> c.longs().toList() == r!!.toList() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testIteratorExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(1000) { + check( + LongStreamExample::iteratorSumExample, + ignoreExecutionsNumber, + { c, r -> c.isEmpty() && r == 0L }, + { c: List, r -> c.isNotEmpty() && c.longs().sum() == r }, + coverage = AtLeast(76) + ) + } + } + + @Test + fun testStreamOfExample() { + withoutConcrete { + check( + LongStreamExample::streamOfExample, + ignoreExecutionsNumber, + // NOTE: the order of the matchers is important because Stream could be used only once + { c, r -> c.isNotEmpty() && c.contentEquals(r!!.toArray()) }, + { c, r -> c.isEmpty() && LongStream.empty().toArray().contentEquals(r!!.toArray()) }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + } + + @Test + fun testClosedStreamExample() { + // TODO exceeds even default step limit 3500 => too slow + withPathSelectorStepsLimit(500) { + checkWithException( + LongStreamExample::closedStreamExample, + ignoreExecutionsNumber, + { _, r -> r.isException() }, + coverage = AtLeast(88) + ) + } + } + + @Test + fun testGenerateExample() { + check( + LongStreamExample::generateExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(LongArray(10) { 42L }) }, + coverage = Full + ) + } + + @Test + fun testIterateExample() { + check( + LongStreamExample::iterateExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(LongArray(10) { i -> 42L + i }) }, + coverage = Full + ) + } + + @Test + fun testConcatExample() { + check( + LongStreamExample::concatExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(LongArray(10) { 42L } + LongArray(10) { i -> 42L + i }) }, + coverage = Full + ) + } + + @Test + fun testRangeExample() { + check( + LongStreamExample::rangeExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(LongArray(10) { it.toLong() }) }, + coverage = Full + ) + } + + @Test + fun testRangeClosedExample() { + check( + LongStreamExample::rangeClosedExample, + ignoreExecutionsNumber, + { r -> r!!.contentEquals(LongArray(11) { it.toLong() }) }, + coverage = Full + ) + } +} + +private fun List.longs(mapping: (Short?) -> Long = { it?.toLong() ?: 0L }): LongArray = + map { mapping(it) }.toLongArray() diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/StreamsAsMethodResultExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/StreamsAsMethodResultExampleTest.kt new file mode 100644 index 0000000000..a35347eea0 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/stream/StreamsAsMethodResultExampleTest.kt @@ -0,0 +1,65 @@ +package org.utbot.examples.stream + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.visible.UtStreamConsumingException +import org.utbot.testcheckers.eq +import org.utbot.testing.FullWithAssumptions +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException +import kotlin.streams.toList +import org.utbot.testing.asList + +// TODO 1 instruction is always uncovered https://github.com/UnitTestBot/UTBotJava/issues/193 +// TODO failed Kotlin compilation (generics) JIRA:1332 +class StreamsAsMethodResultExampleTest : UtValueTestCaseChecker( + testClass = StreamsAsMethodResultExample::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testReturningStreamExample() { + check( + StreamsAsMethodResultExample::returningStreamExample, + eq(2), + { c, r -> c.isEmpty() && c == r!!.asList() }, + { c, r -> c.isNotEmpty() && c == r!!.asList() }, + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + + @Test + fun testReturningIntStreamExample() { + checkWithException( + StreamsAsMethodResultExample::returningIntStreamExample, + eq(3), + { c, r -> c.isEmpty() && c == r.getOrThrow().toList() }, + { c, r -> c.isNotEmpty() && c.none { it == null } && c.toIntArray().contentEquals(r.getOrThrow().toArray()) }, + { c, r -> c.isNotEmpty() && c.any { it == null } && r.isException() }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + + @Test + fun testReturningLongStreamExample() { + checkWithException( + StreamsAsMethodResultExample::returningLongStreamExample, + eq(3), + { c, r -> c.isEmpty() && c == r.getOrThrow().toList() }, + { c, r -> c.isNotEmpty() && c.none { it == null } && c.map { it.toLong() }.toLongArray().contentEquals(r.getOrThrow().toArray()) }, + { c, r -> c.isNotEmpty() && c.any { it == null } && r.isException() }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + + @Test + fun testReturningDoubleStreamExample() { + checkWithException( + StreamsAsMethodResultExample::returningDoubleStreamExample, + eq(3), + { c, r -> c.isEmpty() && c == r.getOrThrow().toList() }, + { c, r -> c.isNotEmpty() && c.none { it == null } && c.map { it.toDouble() }.toDoubleArray().contentEquals(r.getOrThrow().toArray()) }, + { c, r -> c.isNotEmpty() && c.any { it == null } && r.isException() }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/strings/GenericExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/strings/GenericExamplesTest.kt new file mode 100644 index 0000000000..8b7077705e --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/strings/GenericExamplesTest.kt @@ -0,0 +1,33 @@ +package org.utbot.examples.strings + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException + +@Disabled("TODO: Fails and takes too long") +internal class GenericExamplesTest : UtValueTestCaseChecker( + testClass = GenericExamples::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testContainsOkWithIntegerType() { + checkWithException( + GenericExamples::containsOk, + eq(2), + { obj, result -> obj == null && result.isException() }, + { obj, result -> obj != null && result.isSuccess && result.getOrNull() == false } + ) + } + + @Test + fun testContainsOkExampleTest() { + check( + GenericExamples::containsOkExample, + eq(1), + { result -> result == true } + ) + } +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/strings/StringExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/strings/StringExamplesTest.kt similarity index 75% rename from utbot-framework/src/test/kotlin/org/utbot/examples/strings/StringExamplesTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/strings/StringExamplesTest.kt index 196b63e19f..6dbf32c930 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/strings/StringExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/strings/StringExamplesTest.kt @@ -1,41 +1,53 @@ package org.utbot.examples.strings -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.atLeast -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.examples.keyMatch -import org.utbot.examples.withPushingStateFromPathSelectorForConcrete -import org.utbot.examples.withoutMinimization -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test -import org.utbot.examples.withSolverTimeoutInMillis - -internal class StringExamplesTest : AbstractTestCaseGeneratorTest( +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.ge +import org.utbot.testcheckers.withPushingStateFromPathSelectorForConcrete +import org.utbot.testcheckers.withSolverTimeoutInMillis +import org.utbot.testcheckers.withoutMinimization +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.FullWithAssumptions +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast +import org.utbot.testing.between +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException +import java.util.Locale + +internal class StringExamplesTest : UtValueTestCaseChecker( testClass = StringExamples::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun testByteToString() { - // TODO related to the https://github.com/UnitTestBot/UTBotJava/issues/131 - withSolverTimeoutInMillis(5000) { - check( - StringExamples::byteToString, - eq(2), - { a, b, r -> a > b && r == a.toString() }, - { a, b, r -> a <= b && r == b.toString() }, - ) - } + check( + StringExamples::byteToString, + eq(2), + { a, b, r -> a > b && r == a.toString() }, + { a, b, r -> a <= b && r == b.toString() }, + ) + } + + @Test + fun testByteToStringWithConstants() { + val values: Array = arrayOf( + Byte.MIN_VALUE, + (Byte.MIN_VALUE + 100).toByte(), + 0.toByte(), + (Byte.MAX_VALUE - 100).toByte(), + Byte.MAX_VALUE + ) + + val expected = values.map { it.toString() } + + check( + StringExamples::byteToStringWithConstants, + eq(1), + { r -> r != null && r.indices.all { r[it] == expected[it] } } + ) } @Test @@ -52,45 +64,91 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( @Test fun testShortToString() { - // TODO related to the https://github.com/UnitTestBot/UTBotJava/issues/131 - withSolverTimeoutInMillis(5000) { - check( - StringExamples::shortToString, - eq(2), - { a, b, r -> a > b && r == a.toString() }, - { a, b, r -> a <= b && r == b.toString() }, - ) - } + check( + StringExamples::shortToString, + ignoreExecutionsNumber, + { a, b, r -> a > b && r == a.toString() }, + { a, b, r -> a <= b && r == b.toString() }, + ) } + @Test + fun testShortToStringWithConstants() { + val values: Array = arrayOf( + Short.MIN_VALUE, + (Short.MIN_VALUE + 100).toShort(), + 0.toShort(), + (Short.MAX_VALUE - 100).toShort(), + Short.MAX_VALUE + ) + + val expected = values.map { it.toString() } + + check( + StringExamples::shortToStringWithConstants, + eq(1), + { r -> r != null && r.indices.all { r[it] == expected[it] } } + ) + } @Test fun testIntToString() { - // TODO related to the https://github.com/UnitTestBot/UTBotJava/issues/131 - withSolverTimeoutInMillis(5000) { - check( - StringExamples::intToString, - ignoreExecutionsNumber, - { a, b, r -> a > b && r == a.toString() }, - { a, b, r -> a <= b && r == b.toString() }, - ) - } + check( + StringExamples::intToString, + ignoreExecutionsNumber, + { a, b, r -> a > b && r == a.toString() }, + { a, b, r -> a <= b && r == b.toString() }, + ) } + @Test + fun testIntToStringWithConstants() { + val values: Array = arrayOf( + Integer.MIN_VALUE, + Integer.MIN_VALUE + 100, + 0, + Integer.MAX_VALUE - 100, + Integer.MAX_VALUE + ) + + val expected = values.map { it.toString() } + + check( + StringExamples::intToStringWithConstants, + eq(1), + { r -> r != null && r.indices.all { r[it] == expected[it] } } + ) + } @Test fun testLongToString() { - // TODO related to the https://github.com/UnitTestBot/UTBotJava/issues/131 - withSolverTimeoutInMillis(5000) { - check( - StringExamples::longToString, - ignoreExecutionsNumber, - { a, b, r -> a > b && r == a.toString() }, - { a, b, r -> a <= b && r == b.toString() }, - ) - } + check( + StringExamples::longToString, + ignoreExecutionsNumber, + { a, b, r -> a > b && r == a.toString() }, + { a, b, r -> a <= b && r == b.toString() }, + ) } + @Test + fun testLongToStringWithConstants() { + val values: Array = arrayOf( + Long.MIN_VALUE, + Long.MIN_VALUE + 100L, + 0L, + Long.MAX_VALUE - 100L, + Long.MAX_VALUE + ) + + val expected = values.map { it.toString() } + + check( + StringExamples::longToStringWithConstants, + eq(1), + { r -> r != null && r.indices.all { r[it] == expected[it] } } + ) + } + @Test fun testStartsWithLiteral() { check( @@ -99,7 +157,7 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( { v, _ -> v == null }, { v, r -> v != null && v.startsWith("1234567890") && r!!.startsWith("12a4567890") }, { v, r -> v != null && v[0] == 'x' && r!![0] == 'x' }, - { v, r -> v != null && v.toLowerCase() == r } + { v, r -> v != null && v.lowercase(Locale.getDefault()) == r } ) } @@ -183,7 +241,7 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( check( StringExamples::concat, between(1..2), - { fst, snd, r -> fst == null || snd == null && r == fst + snd }, + { fst, snd, r -> (fst == null || snd == null) && r == fst + snd }, { fst, snd, r -> r == fst + snd }, ) } @@ -237,6 +295,15 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( ) } + @Test + fun testStringBuilderAsParameterExample() { + check( + StringExamples::stringBuilderAsParameterExample, + eq(1), + coverage = FullWithAssumptions(assumeCallsNumber = 1) + ) + } + @Test fun testNullableStringBuffer() { checkWithException( @@ -250,14 +317,24 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( } @Test + fun testIsStringBuilderEmpty() { + check( + StringExamples::isStringBuilderEmpty, + eq(2), + { stringBuilder, result -> result == stringBuilder.isEmpty() } + ) + } + + @Test + @Disabled("Flaky on GitHub: https://github.com/UnitTestBot/UTBotJava/issues/1004") fun testIsValidUuid() { val pattern = Regex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}") check( StringExamples::isValidUuid, ignoreExecutionsNumber, - { uuid, r -> uuid == null || uuid.length == 0 && r == false }, - { uuid, r -> uuid.length > 0 && uuid.isBlank() && r == false }, - { uuid, r -> uuid.length > 0 && uuid.isNotBlank() && r == false }, + { uuid, r -> uuid == null || uuid.isEmpty() && r == false }, + { uuid, r -> uuid.isNotEmpty() && uuid.isBlank() && r == false }, + { uuid, r -> uuid.isNotEmpty() && uuid.isNotBlank() && r == false }, { uuid, r -> uuid.length > 1 && uuid.isNotBlank() && !uuid.matches(pattern) && r == false }, { uuid, r -> uuid.length > 1 && uuid.isNotBlank() && uuid.matches(pattern) && r == true }, ) @@ -275,20 +352,29 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( ) } + @Test + fun testSplitExample() { + check( + StringExamples::splitExample, + ignoreExecutionsNumber, + { s, r -> s.all { it.isWhitespace() } && r == 0 }, + { s, r -> s.none { it.isWhitespace() } && r == 1 }, + { s, r -> s[0].isWhitespace() && s.any { !it.isWhitespace() } && r == 2 }, + { s, r -> !s[0].isWhitespace() && s[2].isWhitespace() && r == 1 }, + { s, r -> !s[0].isWhitespace() && s[1].isWhitespace() && !s[2].isWhitespace() && r == 2 }, + coverage = FullWithAssumptions(assumeCallsNumber = 2) + ) + } + @Test fun testIsBlank() { check( StringExamples::isBlank, ge(4), { cs, r -> cs == null && r == true }, - { cs, r -> cs.length == 0 && r == true }, - { cs, r -> cs.length > 0 && cs.isBlank() && r == true }, - { cs, r -> cs.length > 0 && cs.isNotBlank() && r == false }, - summaryNameChecks = listOf( - keyMatch("testIsBlank_StrLenEqualsZero"), - keyMatch("testIsBlank_NotCharacterIsWhitespace"), - keyMatch("testIsBlank_CharacterIsWhitespace") - ) + { cs, r -> cs.isEmpty() && r == true }, + { cs, r -> cs.isNotEmpty() && cs.isBlank() && r == true }, + { cs, r -> cs.isNotEmpty() && cs.isNotBlank() && r == false } ) } @@ -310,7 +396,7 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( { _, i, r -> i <= 0 && r.isException() }, { cs, i, r -> i > 0 && cs == null && !r.getOrThrow() }, { cs, i, r -> i > 0 && cs != null && cs.length > i && r.getOrThrow() }, - coverage = DoNotCalculate // TODO: Coverage calculation fails in the child process with Illegal Argument Exception + coverage = DoNotCalculate // TODO: Coverage calculation fails in the instrumented process with Illegal Argument Exception ) } @@ -330,7 +416,7 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( fun testSubstring() { checkWithException( StringExamples::substring, - between(5..7), + between(5..8), { s, _, r -> s == null && r.isException() }, { s, i, r -> s != null && i < 0 || i > s.length && r.isException() }, { s, i, r -> s != null && i in 0..s.length && r.getOrThrow() == s.substring(i) && s.substring(i) != "password" }, @@ -469,9 +555,9 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( check( StringExamples::endsWith, between(5..6), - { s, suffix, r -> suffix == null }, - { s, suffix, r -> suffix != null && suffix.length < 2 }, - { s, suffix, r -> suffix != null && suffix.length >= 2 && s == null }, + { _, suffix, _ -> suffix == null }, + { _, suffix, _ -> suffix != null && suffix.length < 2 }, + { s, suffix, _ -> suffix != null && suffix.length >= 2 && s == null }, { s, suffix, r -> suffix != null && suffix.length >= 2 && s != null && s.endsWith(suffix) && r == true }, { s, suffix, r -> suffix != null && suffix.length >= 2 && s != null && !s.endsWith(suffix) && r == false } ) @@ -537,10 +623,10 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( checkWithException( StringExamples::compareCodePoints, between(8..10), - { s, t, i, r -> s == null && r.isException() }, - { s, t, i, r -> s != null && i < 0 || i >= s.length && r.isException() }, - { s, t, i, r -> s != null && t == null && r.isException() }, - { s, t, i, r -> t != null && i < 0 || i >= t.length && r.isException() }, + { s, _, _, r -> s == null && r.isException() }, + { s, _, i, r -> s != null && i < 0 || i >= s.length && r.isException() }, + { s, t, _, r -> s != null && t == null && r.isException() }, + { _, t, i, r -> t != null && i < 0 || i >= t.length && r.isException() }, { s, t, i, r -> s != null && t != null && s.codePointAt(i) < t.codePointAt(i) && i == 0 && r.getOrThrow() == 0 }, { s, t, i, r -> s != null && t != null && s.codePointAt(i) < t.codePointAt(i) && i != 0 && r.getOrThrow() == 1 }, { s, t, i, r -> s != null && t != null && s.codePointAt(i) >= t.codePointAt(i) && i == 0 && r.getOrThrow() == 2 }, @@ -553,7 +639,7 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( check( StringExamples::toCharArray, eq(2), - { s, r -> s == null }, + { s, _ -> s == null }, { s, r -> s.toCharArray().contentEquals(r) } ) } @@ -583,13 +669,14 @@ internal class StringExamplesTest : AbstractTestCaseGeneratorTest( withPushingStateFromPathSelectorForConcrete { check( StringExamples::equalsIgnoreCase, - eq(2), + ignoreExecutionsNumber, { s, r -> "SUCCESS".equals(s, ignoreCase = true) && r == "success" }, { s, r -> !"SUCCESS".equals(s, ignoreCase = true) && r == "failure" }, ) } } + // TODO: This test fails without concrete execution as it uses a symbolic variable @Test fun testListToString() { check( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/strings11/StringConcatTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/strings11/StringConcatTest.kt new file mode 100644 index 0000000000..d412589611 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/strings11/StringConcatTest.kt @@ -0,0 +1,115 @@ +package org.utbot.examples.strings11 + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException + +class StringConcatTest : UtValueTestCaseChecker( + testClass = StringConcat::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testConcatArguments() { + withoutConcrete { + check( + StringConcat::concatArguments, + eq(1), + { a, b, c, r -> "$a$b$c" == r } + ) + } + } + + @Test + fun testConcatWithConstants() { + withoutConcrete { + check( + StringConcat::concatWithConstants, + eq(4), + { a, r -> a == "head" && r == 1 }, + { a, r -> a == "body" && r == 2 }, + { a, r -> a == null && r == 3 }, + { a, r -> a != "head" && a != "body" && a != null && r == 4 }, + ) + } + } + + @Disabled("Flickers too much with JVM 17") + @Test + fun testConcatWithPrimitives() { + withoutConcrete { + check( + StringConcat::concatWithPrimitives, + eq(1), + { a, r -> "$a#4253.0" == r} + ) + } + } + + @Test + fun testExceptionInToString() { + withoutConcrete { + checkWithException( + StringConcat::exceptionInToString, + ignoreExecutionsNumber, + { t, r -> t.x == 42 && r.isException() }, + { t, r -> t.x != 42 && r.getOrThrow() == "Test: x = ${t.x}!" }, + coverage = DoNotCalculate + ) + } + } + + @Test + fun testConcatWithField() { + withoutConcrete { + checkWithThis( + StringConcat::concatWithField, + eq(1), + { o, a, r -> "$a${o.str}#" == r } + ) + } + } + + @Test + fun testConcatWithPrimitiveWrappers() { + withoutConcrete { + check( + StringConcat::concatWithPrimitiveWrappers, + ignoreExecutionsNumber, + { b, c, r -> b.toString().endsWith("4") && c == '2' && r == 1 }, + { _, c, r -> !c.toString().endsWith("42") && r == 2 }, + coverage = DoNotCalculate + ) + } + } + + @Test + fun testSameConcat() { + withoutConcrete { + check( + StringConcat::sameConcat, + ignoreExecutionsNumber, + { a, b, r -> a == b && r == 0 }, + { a, b, r -> a != b && r == 1 }, + coverage = DoNotCalculate + ) + } + } + + @Test + fun testConcatStrangeSymbols() { + withoutConcrete { + check( + StringConcat::concatStrangeSymbols, + eq(1), + { r -> r == "\u0000#\u0001!\u0002@\u0012\t" } + ) + } + } + +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/structures/HeapTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/structures/HeapTest.kt similarity index 78% rename from utbot-framework/src/test/kotlin/org/utbot/examples/structures/HeapTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/structures/HeapTest.kt index ff9cea7309..dd405f15b0 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/structures/HeapTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/structures/HeapTest.kt @@ -1,10 +1,10 @@ package org.utbot.examples.structures -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.ignoreExecutionsNumber import org.junit.jupiter.api.Test +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber -internal class HeapTest : AbstractTestCaseGeneratorTest(testClass = Heap::class) { +internal class HeapTest : UtValueTestCaseChecker(testClass = Heap::class) { @Test fun testIsHeap() { val method = Heap::isHeap diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/structures/MinStackExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/structures/MinStackExampleTest.kt similarity index 93% rename from utbot-framework/src/test/kotlin/org/utbot/examples/structures/MinStackExampleTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/structures/MinStackExampleTest.kt index ea97e066cb..f391339671 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/structures/MinStackExampleTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/structures/MinStackExampleTest.kt @@ -1,13 +1,13 @@ package org.utbot.examples.structures -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.between -import org.utbot.examples.eq import kotlin.math.min import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.between -internal class MinStackExampleTest : AbstractTestCaseGeneratorTest(testClass = MinStackExample::class) { +internal class MinStackExampleTest : UtValueTestCaseChecker(testClass = MinStackExample::class) { @Test fun testCreate() { check( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/structures/StandardStructuresTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/structures/StandardStructuresTest.kt new file mode 100644 index 0000000000..34092a108d --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/structures/StandardStructuresTest.kt @@ -0,0 +1,58 @@ +package org.utbot.examples.structures + +import java.util.LinkedList +import java.util.TreeMap +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +internal class StandardStructuresTest : UtValueTestCaseChecker( + testClass = StandardStructures::class, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + @Disabled("TODO down cast for object wrapper JIRA:1480") + fun testGetList() { + check( + StandardStructures::getList, + eq(4), + { l, r -> l is ArrayList && r is ArrayList }, + { l, r -> l is LinkedList && r is LinkedList }, + { l, r -> l == null && r == null }, + { l, r -> + l !is ArrayList && l !is LinkedList && l != null && r !is ArrayList && r !is LinkedList && r != null + }, + coverage = DoNotCalculate + ) + } + + @Test + @Disabled("TODO down cast for object wrapper JIRA:1480") + fun testGetMap() { + check( + StandardStructures::getMap, + eq(3), + { m, r -> m is TreeMap && r is TreeMap }, + { m, r -> m == null && r == null }, + { m, r -> m !is TreeMap && m != null && r !is TreeMap && r != null }, + coverage = DoNotCalculate + ) + } + + @Test + fun testGetDeque() { + check( + StandardStructures::getDeque, + eq(4), + { d, r -> d is java.util.ArrayDeque && r is java.util.ArrayDeque }, + { d, r -> d is LinkedList && r is LinkedList }, + { d, r -> d == null && r == null }, + { d, r -> + d !is java.util.ArrayDeque<*> && d !is LinkedList && d != null && r !is java.util.ArrayDeque<*> && r !is LinkedList && r != null + }, + coverage = DoNotCalculate + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintBranchingTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintBranchingTest.kt new file mode 100644 index 0000000000..c884ed8517 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintBranchingTest.kt @@ -0,0 +1,31 @@ +package org.utbot.examples.taint + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.taint.TaintConfigurationProviderResources +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseCheckerForTaint +import org.utbot.testing.isException + +internal class TaintBranchingTest : UtValueTestCaseCheckerForTaint( + testClass = TaintBranching::class, + taintConfigurationProvider = TaintConfigurationProviderResources("taint/TaintBranchingConfig.yaml") +) { + @Test + fun testTaintBad() { + checkWithException( + TaintBranching::bad, + eq(3), // success (x2) & taint error + { cond, r -> cond == r.isException() }, + ) + } + + @Test + fun testTaintGood() { + checkWithException( + TaintBranching::good, + eq(2), // success in both cases + { _, r -> r.isSuccess }, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintCleanerConditionsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintCleanerConditionsTest.kt new file mode 100644 index 0000000000..e33f1bd692 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintCleanerConditionsTest.kt @@ -0,0 +1,70 @@ +package org.utbot.examples.taint + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.taint.TaintConfigurationProviderResources +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseCheckerForTaint +import org.utbot.testing.isException + +internal class TaintCleanerConditionsTest : UtValueTestCaseCheckerForTaint( + testClass = TaintCleanerConditions::class, + taintConfigurationProvider = TaintConfigurationProviderResources("taint/TaintCleanerConditionsConfig.yaml") +) { + @Test + fun testTaintBadArg() { + checkWithException( + TaintCleanerConditions::badArg, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBadReturn() { + checkWithException( + TaintCleanerConditions::badReturn, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBadThis() { + checkWithException( + TaintCleanerConditions::badThis, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodArg() { + checkWithException( + TaintCleanerConditions::goodArg, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodReturn() { + checkWithException( + TaintCleanerConditions::goodReturn, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodThis() { + checkWithException( + TaintCleanerConditions::goodThis, + eq(1), // only success + { r -> r.isSuccess }, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintCleanerSimpleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintCleanerSimpleTest.kt new file mode 100644 index 0000000000..3c214cb00a --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintCleanerSimpleTest.kt @@ -0,0 +1,32 @@ +package org.utbot.examples.taint + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.taint.TaintConfigurationProviderResources +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseCheckerForTaint +import org.utbot.testing.isException + +internal class TaintCleanerSimpleTest : UtValueTestCaseCheckerForTaint( + testClass = TaintCleanerSimple::class, + taintConfigurationProvider = TaintConfigurationProviderResources("taint/TaintCleanerSimpleConfig.yaml") +) { + @Test + fun testTaintBad() { + checkWithException( + TaintCleanerSimple::bad, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGood() { + checkWithException( + TaintCleanerSimple::good, + eq(1), // only success + { r -> r.isSuccess }, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintLongPathTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintLongPathTest.kt new file mode 100644 index 0000000000..e4aeeed6da --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintLongPathTest.kt @@ -0,0 +1,32 @@ +package org.utbot.examples.taint + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.taint.TaintConfigurationProviderResources +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseCheckerForTaint +import org.utbot.testing.isException + +internal class TaintLongPathTest : UtValueTestCaseCheckerForTaint( + testClass = TaintLongPath::class, + taintConfigurationProvider = TaintConfigurationProviderResources("taint/TaintLongPathConfig.yaml") +) { + @Test + fun testTaintBad() { + checkWithException( + TaintLongPath::bad, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGood() { + checkWithException( + TaintLongPath::good, + eq(1), // only success + { r -> r.isSuccess }, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintOtherClassTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintOtherClassTest.kt new file mode 100644 index 0000000000..d915e3e1f9 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintOtherClassTest.kt @@ -0,0 +1,32 @@ +package org.utbot.examples.taint + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.taint.TaintConfigurationProviderResources +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseCheckerForTaint +import org.utbot.testing.isException + +internal class TaintOtherClassTest : UtValueTestCaseCheckerForTaint( + testClass = TaintOtherClass::class, + taintConfigurationProvider = TaintConfigurationProviderResources("taint/TaintOtherClassConfig.yaml") +) { + @Test + fun testTaintBad() { + checkWithException( + TaintOtherClass::bad, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGood() { + checkWithException( + TaintOtherClass::good, + eq(1), // only success + { r -> r.isSuccess }, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintPassConditionsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintPassConditionsTest.kt new file mode 100644 index 0000000000..f46e65f7ed --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintPassConditionsTest.kt @@ -0,0 +1,70 @@ +package org.utbot.examples.taint + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.taint.TaintConfigurationProviderResources +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseCheckerForTaint +import org.utbot.testing.isException + +internal class TaintPassConditionsTest : UtValueTestCaseCheckerForTaint( + testClass = TaintPassConditions::class, + taintConfigurationProvider = TaintConfigurationProviderResources("taint/TaintPassConditionsConfig.yaml") +) { + @Test + fun testTaintBadArg() { + checkWithException( + TaintPassConditions::badArg, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBadReturn() { + checkWithException( + TaintPassConditions::badReturn, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBadThis() { + checkWithException( + TaintPassConditions::badThis, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodArg() { + checkWithException( + TaintPassConditions::goodArg, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodReturn() { + checkWithException( + TaintPassConditions::goodReturn, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodThis() { + checkWithException( + TaintPassConditions::goodThis, + eq(1), // only success + { r -> r.isSuccess }, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintPassSimpleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintPassSimpleTest.kt new file mode 100644 index 0000000000..3a40ea053c --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintPassSimpleTest.kt @@ -0,0 +1,42 @@ +package org.utbot.examples.taint + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.taint.TaintConfigurationProviderResources +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseCheckerForTaint +import org.utbot.testing.isException + +internal class TaintPassSimpleTest : UtValueTestCaseCheckerForTaint( + testClass = TaintPassSimple::class, + taintConfigurationProvider = TaintConfigurationProviderResources("taint/TaintPassSimpleConfig.yaml") +) { + @Test + fun testTaintBad() { + checkWithException( + TaintPassSimple::bad, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBadDoublePass() { + checkWithException( + TaintPassSimple::badDoublePass, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGood() { + checkWithException( + TaintPassSimple::good, + eq(1), // only success + { r -> r.isSuccess }, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSeveralMarksTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSeveralMarksTest.kt new file mode 100644 index 0000000000..0df3a948d6 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSeveralMarksTest.kt @@ -0,0 +1,173 @@ +package org.utbot.examples.taint + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.taint.TaintConfigurationProviderResources +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutThrowTaintErrorForEachMarkSeparately +import org.utbot.testing.UtValueTestCaseCheckerForTaint +import org.utbot.testing.isException + +internal class TaintSeveralMarksTest : UtValueTestCaseCheckerForTaint( + testClass = TaintSeveralMarks::class, + taintConfigurationProvider = TaintConfigurationProviderResources("taint/TaintSeveralMarksConfig.yaml") +) { + @Test + fun testTaintBad1() { + checkWithException( + TaintSeveralMarks::bad1, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBad2() { + checkWithException( + TaintSeveralMarks::bad2, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBad13() { + checkWithException( + TaintSeveralMarks::bad13, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBad13NotSeparately() { + withoutThrowTaintErrorForEachMarkSeparately { + checkWithException( + TaintSeveralMarks::bad13, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + } + + @Test + fun testTaintBad123() { + checkWithException( + TaintSeveralMarks::bad123, + eq(3), // success & taint error (x2, for each mark separately) + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBad123NotSeparately() { + withoutThrowTaintErrorForEachMarkSeparately { + checkWithException( + TaintSeveralMarks::bad123, + eq(2), // success & taint error (one for two marks) + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + } + + @Test + fun testTaintBadSourceAll() { + checkWithException( + TaintSeveralMarks::badSourceAll, + eq(4), // success & taint error (x3) + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBadSinkAll() { + checkWithException( + TaintSeveralMarks::badSinkAll, + eq(3), // success & taint error (x2) + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBadWrongCleaner() { + checkWithException( + TaintSeveralMarks::badWrongCleaner, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGood1() { + checkWithException( + TaintSeveralMarks::good1, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGood2() { + checkWithException( + TaintSeveralMarks::good2, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGood13() { + checkWithException( + TaintSeveralMarks::good13, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGood13NotSeparately() { + withoutThrowTaintErrorForEachMarkSeparately { + checkWithException( + TaintSeveralMarks::good13, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + } + + @Test + fun testTaintGoodWrongSource() { + checkWithException( + TaintSeveralMarks::goodWrongSource, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodWrongSink() { + checkWithException( + TaintSeveralMarks::goodWrongSink, + eq(1), // only success + { r -> r.isSuccess } + ) + } + + @Test + fun testTaintGoodWrongPass() { + checkWithException( + TaintSeveralMarks::goodWrongPass, + eq(1), // only success + { r -> r.isSuccess }, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSignatureTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSignatureTest.kt new file mode 100644 index 0000000000..57a2632749 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSignatureTest.kt @@ -0,0 +1,59 @@ +package org.utbot.examples.taint + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.taint.TaintConfigurationProviderResources +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseCheckerForTaint +import org.utbot.testing.isException + +internal class TaintSignatureTest : UtValueTestCaseCheckerForTaint( + testClass = TaintSignature::class, + taintConfigurationProvider = TaintConfigurationProviderResources("taint/TaintSignatureConfig.yaml") +) { + @Test + fun testTaintBad() { + checkWithException( + TaintSignature::badFakeCleaner, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodCleaner() { + checkWithException( + TaintSignature::goodCleaner, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodFakeSources() { + checkWithException( + TaintSignature::goodFakeSources, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodFakePass() { + checkWithException( + TaintSignature::goodFakePass, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodFakeSink() { + checkWithException( + TaintSignature::goodFakeSink, + eq(1), // only success + { r -> r.isSuccess }, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSimpleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSimpleTest.kt new file mode 100644 index 0000000000..a7ab2b5558 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSimpleTest.kt @@ -0,0 +1,32 @@ +package org.utbot.examples.taint + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.taint.TaintConfigurationProviderResources +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseCheckerForTaint +import org.utbot.testing.isException + +internal class TaintSimpleTest : UtValueTestCaseCheckerForTaint( + testClass = TaintSimple::class, + taintConfigurationProvider = TaintConfigurationProviderResources("taint/TaintSimpleConfig.yaml") +) { + @Test + fun testTaintBad() { + checkWithException( + TaintSimple::bad, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGood() { + checkWithException( + TaintSimple::good, + eq(1), // only success + { r -> r.isSuccess }, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSinkConditionsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSinkConditionsTest.kt new file mode 100644 index 0000000000..a78c40a359 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSinkConditionsTest.kt @@ -0,0 +1,51 @@ +package org.utbot.examples.taint + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.taint.TaintConfigurationProviderResources +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseCheckerForTaint +import org.utbot.testing.isException + +internal class TaintSinkConditionsTest : UtValueTestCaseCheckerForTaint( + testClass = TaintSinkConditions::class, + taintConfigurationProvider = TaintConfigurationProviderResources("taint/TaintSinkConditionsConfig.yaml") +) { + @Test + fun testTaintBadArg() { + checkWithException( + TaintSinkConditions::badArg, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBadThis() { + checkWithException( + TaintSinkConditions::badThis, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodArg() { + checkWithException( + TaintSinkConditions::goodArg, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodThis() { + checkWithException( + TaintSinkConditions::goodThis, + eq(1), // only success + { r -> r.isSuccess }, + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSourceConditionsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSourceConditionsTest.kt new file mode 100644 index 0000000000..198d0fe8b1 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/taint/TaintSourceConditionsTest.kt @@ -0,0 +1,70 @@ +package org.utbot.examples.taint + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.taint.TaintConfigurationProviderResources +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseCheckerForTaint +import org.utbot.testing.isException + +internal class TaintSourceConditionsTest : UtValueTestCaseCheckerForTaint( + testClass = TaintSourceConditions::class, + taintConfigurationProvider = TaintConfigurationProviderResources("taint/TaintSourceConditionsConfig.yaml") +) { + @Test + fun testTaintBadArg() { + checkWithException( + TaintSourceConditions::badArg, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBadReturn() { + checkWithException( + TaintSourceConditions::badReturn, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintBadThis() { + checkWithException( + TaintSourceConditions::badThis, + eq(2), // success & taint error + { r -> r.isException() }, + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodArg() { + checkWithException( + TaintSourceConditions::goodArg, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodReturn() { + checkWithException( + TaintSourceConditions::goodReturn, + eq(1), // only success + { r -> r.isSuccess }, + ) + } + + @Test + fun testTaintGoodThis() { + checkWithException( + TaintSourceConditions::goodThis, + eq(1), // only success + { r -> r.isSuccess }, + ) + } +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/thirdparty/numbers/ArithmeticUtilsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/thirdparty/numbers/ArithmeticUtilsTest.kt similarity index 86% rename from utbot-framework/src/test/kotlin/org/utbot/examples/thirdparty/numbers/ArithmeticUtilsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/thirdparty/numbers/ArithmeticUtilsTest.kt index a1aaf0af8d..fc7658c50d 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/thirdparty/numbers/ArithmeticUtilsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/thirdparty/numbers/ArithmeticUtilsTest.kt @@ -1,12 +1,12 @@ package org.utbot.examples.thirdparty.numbers -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker // example from Apache common-numbers -internal class ArithmeticUtilsTest : AbstractTestCaseGeneratorTest(testClass = ArithmeticUtils::class) { +internal class ArithmeticUtilsTest : UtValueTestCaseChecker(testClass = ArithmeticUtils::class) { @Test @Tag("slow") fun testPow() { diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/CountDownLatchExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/CountDownLatchExamplesTest.kt new file mode 100644 index 0000000000..125251f73f --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/CountDownLatchExamplesTest.kt @@ -0,0 +1,23 @@ +package org.utbot.examples.threads + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.AtLeast +import org.utbot.testing.UtValueTestCaseChecker + +class CountDownLatchExamplesTest : UtValueTestCaseChecker(testClass = CountDownLatchExamples::class) { + @Test + fun testGetAndDown() { + check( + CountDownLatchExamples::getAndDown, + eq(2), + { countDownLatch, l -> countDownLatch.count == 0L && l == 0L }, + { countDownLatch, l -> + val firstCount = countDownLatch.count + + firstCount != 0L && l == firstCount - 1 + }, + coverage = AtLeast(83) + ) + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ExecutorServiceExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ExecutorServiceExamplesTest.kt new file mode 100644 index 0000000000..dcd8af4731 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ExecutorServiceExamplesTest.kt @@ -0,0 +1,40 @@ +package org.utbot.examples.threads + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.AtLeast +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException + +// IMPORTANT: most of the these tests test only the symbolic engine +// and should not be used for testing conrete or code generation since they are possibly flaky in the concrete execution +// (see https://github.com/UnitTestBot/UTBotJava/issues/1610) +class ExecutorServiceExamplesTest : UtValueTestCaseChecker(testClass = ExecutorServiceExamples::class) { + @Test + fun testExceptionInExecute() { + withoutConcrete { + withEnabledTestingCodeGeneration(false) { + checkWithException( + ExecutorServiceExamples::throwingInExecute, + ignoreExecutionsNumber, + { r -> r.isException() } + ) + } + } + } + + @Test + fun testChangingCollectionInExecute() { + withoutConcrete { + withEnabledTestingCodeGeneration(false) { + check( + ExecutorServiceExamples::changingCollectionInExecute, + ignoreExecutionsNumber, + { r -> r == 42 }, + coverage = AtLeast(78) + ) + } + } + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt new file mode 100644 index 0000000000..c6c600b46f --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/FutureExamplesTest.kt @@ -0,0 +1,60 @@ +package org.utbot.examples.threads + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.AtLeast +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.isException +import java.util.concurrent.ExecutionException + +// IMPORTANT: most of the these tests test only the symbolic engine +// and should not be used for testing conrete or code generation since they are possibly flaky in the concrete execution +// (see https://github.com/UnitTestBot/UTBotJava/issues/1610) +class FutureExamplesTest : UtValueTestCaseChecker(testClass = FutureExamples::class) { + @Test + fun testThrowingRunnable() { + withoutConcrete { + checkWithException( + FutureExamples::throwingRunnableExample, + eq(1), + { r -> r.isException() }, + coverage = AtLeast(71) + ) + } + } + + @Test + fun testResultFromGet() { + check( + FutureExamples::resultFromGet, + eq(1), + { r -> r == 42 }, + ) + } + + @Test + fun testChangingCollectionInFuture() { + withEnabledTestingCodeGeneration(false) { + check( + FutureExamples::changingCollectionInFuture, + eq(1), + { r -> r == 42 }, + ) + } + } + + @Test + fun testChangingCollectionInFutureWithoutGet() { + withoutConcrete { + withEnabledTestingCodeGeneration(false) { + check( + FutureExamples::changingCollectionInFutureWithoutGet, + eq(1), + { r -> r == 42 }, + coverage = AtLeast(78) + ) + } + } + } +} diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadExamplesTest.kt new file mode 100644 index 0000000000..3432eb8779 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/threads/ThreadExamplesTest.kt @@ -0,0 +1,54 @@ +package org.utbot.examples.threads + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.withoutConcrete +import org.utbot.testing.AtLeast +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException + +// IMPORTANT: most of the these tests test only the symbolic engine +// and should not be used for testing conrete or code generation since they are possibly flaky in the concrete execution +// (see https://github.com/UnitTestBot/UTBotJava/issues/1610) +class ThreadExamplesTest : UtValueTestCaseChecker(testClass = ThreadExamples::class) { + @Test + fun testExceptionInStart() { + withoutConcrete { + withEnabledTestingCodeGeneration(false) { + checkWithException( + ThreadExamples::explicitExceptionInStart, + ignoreExecutionsNumber, + { r -> r.isException() } + ) + } + } + } + + @Test + fun testChangingCollectionInThread() { + withoutConcrete { + withEnabledTestingCodeGeneration(false) { + check( + ThreadExamples::changingCollectionInThread, + ignoreExecutionsNumber, + { r -> r == 42 }, + coverage = AtLeast(81) + ) + } + } + } + + @Test + fun testChangingCollectionInThreadWithoutStart() { + withoutConcrete { + withEnabledTestingCodeGeneration(false) { + checkWithException( + ThreadExamples::changingCollectionInThreadWithoutStart, + ignoreExecutionsNumber, + { r -> r.isException() }, + coverage = AtLeast(81) + ) + } + } + } +} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/types/CastExamplesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/CastExamplesTest.kt similarity index 93% rename from utbot-framework/src/test/kotlin/org/utbot/examples/types/CastExamplesTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/types/CastExamplesTest.kt index bd43ff4fa5..1cc939a373 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/types/CastExamplesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/CastExamplesTest.kt @@ -1,11 +1,11 @@ package org.utbot.examples.types -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker @Suppress("SimplifyNegatedBinaryExpression") -internal class CastExamplesTest : AbstractTestCaseGeneratorTest(testClass = CastExamples::class) { +internal class CastExamplesTest : UtValueTestCaseChecker(testClass = CastExamples::class) { @Test fun testLongToByte() { check( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/GenericsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/GenericsTest.kt new file mode 100644 index 0000000000..d77d4db529 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/GenericsTest.kt @@ -0,0 +1,74 @@ +package org.utbot.examples.types + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.ignoreExecutionsNumber + +internal class GenericsTest : UtValueTestCaseChecker( + testClass = GenericsTest::class, + testCodeGeneration = false // TODO empty files are generated https://github.com/UnitTestBot/UTBotJava/issues/1616 +) { + @Test + fun mapAsParameterTest() { + check( + Generics<*>::mapAsParameter, + eq(2), + { map, _ -> map == null }, + { map, r -> map != null && r == "value" }, + ) + } + + @Test + @Disabled("https://github.com/UnitTestBot/UTBotJava/issues/1620 wrong equals") + fun genericAsFieldTest() { + check( + Generics<*>::genericAsField, + ignoreExecutionsNumber, + { obj, r -> obj?.field == null && r == false }, + // we can cover this line with any of these two conditions + { obj, r -> (obj.field != null && obj.field != "abc" && r == false) || (obj.field == "abc" && r == true) }, + ) + } + + @Test + fun mapAsStaticFieldTest() { + check( + Generics<*>::mapAsStaticField, + ignoreExecutionsNumber, + { r -> r == "value" }, + ) + } + + @Test + fun mapAsNonStaticFieldTest() { + check( + Generics<*>::mapAsNonStaticField, + ignoreExecutionsNumber, + { map, _ -> map == null }, + { map, r -> map != null && r == "value" }, + ) + } + + @Test + fun methodWithRawTypeTest() { + check( + Generics<*>::methodWithRawType, + eq(2), + { map, _ -> map == null }, + { map, r -> map != null && r == "value" }, + ) + } + + @Test + fun testMethodWithArrayTypeBoundary() { + checkWithException( + Generics<*>::methodWithArrayTypeBoundary, + eq(1), + { r -> r.exceptionOrNull() is java.lang.NullPointerException }, + coverage = DoNotCalculate + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/PathDependentGenericsExampleTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/PathDependentGenericsExampleTest.kt new file mode 100644 index 0000000000..14ff4ed25f --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/PathDependentGenericsExampleTest.kt @@ -0,0 +1,33 @@ +package org.utbot.examples.types + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast + +internal class PathDependentGenericsExampleTest : UtValueTestCaseChecker( + testClass = PathDependentGenericsExample::class, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun testPathDependentGenerics() { + check( + PathDependentGenericsExample::pathDependentGenerics, + eq(3), + { elem, r -> elem is ClassWithOneGeneric<*> && r == 1 }, + { elem, r -> elem is ClassWithTwoGenerics<*, *> && r == 2 }, + { elem, r -> elem !is ClassWithOneGeneric<*> && elem !is ClassWithTwoGenerics<*, *> && r == 3 }, + ) + } + + @Test + fun testFunctionWithSeveralTypeConstraintsForTheSameObject() { + check( + PathDependentGenericsExample::functionWithSeveralTypeConstraintsForTheSameObject, + eq(2), + { e, r -> e !is List<*> && r == 3 }, + { e, r -> e is List<*> && r == 1 }, + coverage = atLeast(89) + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/types/TypeBordersTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/TypeBordersTest.kt similarity index 90% rename from utbot-framework/src/test/kotlin/org/utbot/examples/types/TypeBordersTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/types/TypeBordersTest.kt index d5c8a149cf..a2251d00c4 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/types/TypeBordersTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/TypeBordersTest.kt @@ -1,11 +1,11 @@ package org.utbot.examples.types -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.atLeast -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.atLeast -internal class TypeBordersTest : AbstractTestCaseGeneratorTest(testClass = TypeBorders::class) { +internal class TypeBordersTest : UtValueTestCaseChecker(testClass = TypeBorders::class) { @Test fun testByteBorder() { check( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/types/TypeMatchesTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/TypeMatchesTest.kt similarity index 89% rename from utbot-framework/src/test/kotlin/org/utbot/examples/types/TypeMatchesTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/types/TypeMatchesTest.kt index 7d3293e8d5..4a3f8d3c76 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/types/TypeMatchesTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/types/TypeMatchesTest.kt @@ -1,11 +1,11 @@ package org.utbot.examples.types -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker @Suppress("SimplifyNegatedBinaryExpression") -internal class TypeMatchesTest : AbstractTestCaseGeneratorTest(testClass = TypeMatches::class) { +internal class TypeMatchesTest : UtValueTestCaseChecker(testClass = TypeMatches::class) { @Test fun testCompareDoubleByte() { check( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/unsafe/UnsafeOperationsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/unsafe/UnsafeOperationsTest.kt new file mode 100644 index 0000000000..03d29c8eab --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/unsafe/UnsafeOperationsTest.kt @@ -0,0 +1,39 @@ +package org.utbot.examples.unsafe + +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutSandbox +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +internal class UnsafeOperationsTest : UtValueTestCaseChecker(testClass = UnsafeOperations::class) { + @Test + fun checkGetAddressSizeOrZero() { + withoutSandbox { + check( + UnsafeOperations::getAddressSizeOrZero, + eq(1), + { r -> r!! > 0 }, + // Coverage matcher fails (branches: 0/0, instructions: 15/21, lines: 0/0) + // TODO: check coverage calculation: https://github.com/UnitTestBot/UTBotJava/issues/807 + coverage = DoNotCalculate + ) + } + } + + @Test + fun checkGetAddressSizeOrZeroWithMocks() { + withoutSandbox { + check( + UnsafeOperations::getAddressSizeOrZero, + eq(1), + { r -> r!! > 0 }, + // Coverage matcher fails (branches: 0/0, instructions: 15/21, lines: 0/0) + // TODO: check coverage calculation: https://github.com/UnitTestBot/UTBotJava/issues/807 + coverage = DoNotCalculate, + mockStrategy = MockStrategyApi.OTHER_CLASSES + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/unsafe/UnsafeWithFieldTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/unsafe/UnsafeWithFieldTest.kt new file mode 100644 index 0000000000..4b486a3d23 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/unsafe/UnsafeWithFieldTest.kt @@ -0,0 +1,18 @@ +package org.utbot.examples.unsafe + +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.UtValueTestCaseChecker + +internal class UnsafeWithFieldTest: UtValueTestCaseChecker(UnsafeWithField::class) { + + @Test + fun checkSetField() { + check( + UnsafeWithField::setField, + eq(1) + // TODO JIRA:1579 + // for now we might have any value as a result, so there is no need in the matcher + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/BooleanWrapperTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/BooleanWrapperTest.kt similarity index 78% rename from utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/BooleanWrapperTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/BooleanWrapperTest.kt index a9605860e3..6f97af67d6 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/BooleanWrapperTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/BooleanWrapperTest.kt @@ -1,12 +1,11 @@ package org.utbot.examples.wrappers -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker -internal class BooleanWrapperTest : AbstractTestCaseGeneratorTest(testClass = BooleanWrapper::class) { +internal class BooleanWrapperTest : UtValueTestCaseChecker(testClass = BooleanWrapper::class) { @Test fun primitiveToWrapperTest() { check( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/ByteWrapperTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/ByteWrapperTest.kt similarity index 78% rename from utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/ByteWrapperTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/ByteWrapperTest.kt index 1b36245469..489e04fcf7 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/ByteWrapperTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/ByteWrapperTest.kt @@ -1,12 +1,11 @@ package org.utbot.examples.wrappers -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker -internal class ByteWrapperTest : AbstractTestCaseGeneratorTest(testClass = ByteWrapper::class) { +internal class ByteWrapperTest : UtValueTestCaseChecker(testClass = ByteWrapper::class) { @Test fun primitiveToWrapperTest() { check( diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/CharacterWrapperTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/CharacterWrapperTest.kt new file mode 100644 index 0000000000..0995b70559 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/CharacterWrapperTest.kt @@ -0,0 +1,48 @@ +package org.utbot.examples.wrappers + +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker + +// TODO failed Kotlin compilation +internal class CharacterWrapperTest : UtValueTestCaseChecker( + testClass = CharacterWrapper::class, + testCodeGeneration = true, + configurations = ignoreKotlinCompilationConfigurations, +) { + @Test + fun primitiveToWrapperTest() { + check( + CharacterWrapper::primitiveToWrapper, + eq(2), + { x, r -> x.toInt() >= 100 && r!!.toInt() >= 100 }, + { x, r -> x.toInt() < 100 && r!!.toInt() == x.toInt() + 100 }, + ) + } + + @Test + fun wrapperToPrimitiveTest() { + check( + CharacterWrapper::wrapperToPrimitive, + eq(3), + { x, _ -> x == null }, + { x, r -> x.toInt() >= 100 && r!!.toInt() >= 100 }, + { x, r -> x.toInt() < 100 && r!!.toInt() == x.toInt() + 100 }, + ) + } + + @Disabled("Caching char values between -128 and 127 isn't supported JIRA:1481") + @Test + fun equalityTest() { + check( + CharacterWrapper::equality, + eq(3), + { a, b, result -> a == b && a.toInt() <= 127 && result == 1 }, + { a, b, result -> a == b && a.toInt() > 127 && result == 2 }, + { a, b, result -> a != b && result == 4 }, + coverage = DoNotCalculate + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/DoubleWrapperTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/DoubleWrapperTest.kt similarity index 77% rename from utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/DoubleWrapperTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/DoubleWrapperTest.kt index c42fe7bc9e..d355ca86ac 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/DoubleWrapperTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/DoubleWrapperTest.kt @@ -1,12 +1,12 @@ package org.utbot.examples.wrappers -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker @Suppress("SimplifyNegatedBinaryExpression") -internal class DoubleWrapperTest : AbstractTestCaseGeneratorTest(testClass = DoubleWrapper::class) { +internal class DoubleWrapperTest : UtValueTestCaseChecker(testClass = DoubleWrapper::class) { @Test fun primitiveToWrapperTest() { check( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/FloatWrapperTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/FloatWrapperTest.kt similarity index 76% rename from utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/FloatWrapperTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/FloatWrapperTest.kt index ba73d3ad04..fcb3eeae52 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/FloatWrapperTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/FloatWrapperTest.kt @@ -1,12 +1,12 @@ package org.utbot.examples.wrappers -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker @Suppress("SimplifyNegatedBinaryExpression") -internal class FloatWrapperTest : AbstractTestCaseGeneratorTest(testClass = FloatWrapper::class) { +internal class FloatWrapperTest : UtValueTestCaseChecker(testClass = FloatWrapper::class) { @Test fun primitiveToWrapperTest() { check( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/IntegerWrapperTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/IntegerWrapperTest.kt similarity index 89% rename from utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/IntegerWrapperTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/IntegerWrapperTest.kt index c7d684a078..f9e360c352 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/IntegerWrapperTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/IntegerWrapperTest.kt @@ -1,12 +1,12 @@ package org.utbot.examples.wrappers -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker -internal class IntegerWrapperTest : AbstractTestCaseGeneratorTest(testClass = IntegerWrapper::class) { +internal class IntegerWrapperTest : UtValueTestCaseChecker(testClass = IntegerWrapper::class) { @Test fun primitiveToWrapperTest() { check( diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/LongWrapperTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/LongWrapperTest.kt similarity index 77% rename from utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/LongWrapperTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/LongWrapperTest.kt index 269dabf5f7..4f418a35ac 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/LongWrapperTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/LongWrapperTest.kt @@ -1,21 +1,16 @@ package org.utbot.examples.wrappers -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.withoutMinimization -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testcheckers.withoutMinimization +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker -internal class LongWrapperTest : AbstractTestCaseGeneratorTest( +internal class LongWrapperTest : UtValueTestCaseChecker( testClass = LongWrapper::class, testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) + configurations = ignoreKotlinCompilationConfigurations, ) { @Test fun primitiveToWrapperTest() { diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/ShortWrapperTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/ShortWrapperTest.kt similarity index 83% rename from utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/ShortWrapperTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/ShortWrapperTest.kt index 3631719ccc..ec4e0498e2 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/ShortWrapperTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/examples/wrappers/ShortWrapperTest.kt @@ -1,12 +1,12 @@ package org.utbot.examples.wrappers -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.testcheckers.eq +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.UtValueTestCaseChecker -internal class ShortWrapperTest : AbstractTestCaseGeneratorTest(testClass = ShortWrapper::class) { +internal class ShortWrapperTest : UtValueTestCaseChecker(testClass = ShortWrapper::class) { @Test fun primitiveToWrapperTest() { check( diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/JUnitSetup.kt b/utbot-framework-test/src/test/kotlin/org/utbot/framework/JUnitSetup.kt similarity index 100% rename from utbot-framework/src/test/kotlin/org/utbot/framework/JUnitSetup.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/framework/JUnitSetup.kt diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt b/utbot-framework-test/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt similarity index 93% rename from utbot-framework/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt index ffa6b63c69..c2bee2ab8d 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/framework/assemble/AssembleModelGeneratorTests.kt @@ -1,56 +1,70 @@ package org.utbot.framework.assemble -import org.utbot.examples.assemble.arrays.ArrayOfComplexArrays -import org.utbot.examples.assemble.arrays.ArrayOfPrimitiveArrays -import org.utbot.examples.assemble.arrays.AssignedArray -import org.utbot.examples.assemble.arrays.ComplexArray -import org.utbot.examples.assemble.arrays.PrimitiveArray -import org.utbot.examples.assemble.constructors.ComplexConstructor -import org.utbot.examples.assemble.constructors.ComplexConstructorWithSetter -import org.utbot.examples.assemble.constructors.ConstructorModifyingStatic -import org.utbot.examples.assemble.constructors.InheritComplexConstructor -import org.utbot.examples.assemble.constructors.InheritPrimitiveConstructor -import org.utbot.examples.assemble.constructors.PrimitiveConstructor -import org.utbot.examples.assemble.constructors.PrimitiveConstructorWithDefaultField -import org.utbot.examples.assemble.constructors.PseudoComplexConstructor -import org.utbot.examples.assemble.defaults.DefaultField -import org.utbot.examples.assemble.defaults.DefaultFieldModifiedInConstructor -import org.utbot.examples.assemble.defaults.DefaultFieldWithDirectAccessor -import org.utbot.examples.assemble.constructors.PrivateConstructor -import org.utbot.examples.assemble.defaults.DefaultFieldWithSetter -import org.utbot.examples.assemble.defaults.DefaultPackagePrivateField -import org.utbot.examples.assemble.statics.StaticField -import org.utbot.framework.SootUtils +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.examples.assemble.AssembleTestUtils +import org.utbot.examples.assemble.ComplexField +import org.utbot.examples.assemble.DirectAccess +import org.utbot.examples.assemble.DirectAccessAndSetter +import org.utbot.examples.assemble.DirectAccessFinal +import org.utbot.examples.assemble.InheritedField +import org.utbot.examples.assemble.ListItem +import org.utbot.examples.assemble.NoModifier +import org.utbot.examples.assemble.PackagePrivateFields +import org.utbot.examples.assemble.PrimitiveFields +import org.utbot.examples.assemble.ArrayOfComplexArrays +import org.utbot.examples.assemble.ArrayOfPrimitiveArrays +import org.utbot.examples.assemble.AssignedArray +import org.utbot.examples.assemble.ComplexArray +import org.utbot.examples.assemble.another.MethodUnderTest +import org.utbot.examples.assemble.PrimitiveArray +import org.utbot.examples.assemble.ComplexConstructor +import org.utbot.examples.assemble.ComplexConstructorWithSetter +import org.utbot.examples.assemble.ConstructorModifyingStatic +import org.utbot.examples.assemble.InheritComplexConstructor +import org.utbot.examples.assemble.InheritPrimitiveConstructor +import org.utbot.examples.assemble.PrimitiveConstructor +import org.utbot.examples.assemble.PrimitiveConstructorWithDefaultField +import org.utbot.examples.assemble.PrivateConstructor +import org.utbot.examples.assemble.PseudoComplexConstructor +import org.utbot.examples.assemble.DefaultField +import org.utbot.examples.assemble.DefaultFieldModifiedInConstructor +import org.utbot.examples.assemble.DefaultFieldWithDirectAccessor +import org.utbot.examples.assemble.DefaultFieldWithSetter +import org.utbot.examples.assemble.DefaultPackagePrivateField +import org.utbot.examples.assemble.StaticField import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtCompositeModel -import org.utbot.framework.plugin.api.UtMethod import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.util.UtContext import org.utbot.framework.plugin.api.util.UtContext.Companion.setUtContext +import org.utbot.framework.plugin.api.util.arrayTypeOf +import org.utbot.framework.plugin.api.util.executableId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intArrayClassId import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.services.JdkInfoDefaultProvider +import org.utbot.framework.util.SootUtils import org.utbot.framework.util.instanceCounter -import org.utbot.framework.util.modelIdCounter +import java.util.concurrent.atomic.AtomicInteger import kotlin.reflect.full.functions -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.utbot.examples.assemble.* /** * Test classes must be located in the same folder as [AssembleTestUtils] class. */ class AssembleModelGeneratorTests { + val modelIdCounter = AtomicInteger(0) private lateinit var context: AutoCloseable private lateinit var statementsChain: MutableList @@ -60,7 +74,7 @@ class AssembleModelGeneratorTests { instanceCounter.set(0) modelIdCounter.set(0) statementsChain = mutableListOf() - SootUtils.runSoot(AssembleTestUtils::class) + SootUtils.runSoot(AssembleTestUtils::class.java, forceReload = false, jdkInfo = JdkInfoDefaultProvider().info) context = setUtContext(UtContext(AssembleTestUtils::class.java.classLoader)) } @@ -136,10 +150,9 @@ class AssembleModelGeneratorTests { fields(testClassId, "a" to 5, "b" to 3) ) - val methodFromAnotherPackage = - org.utbot.examples.assemble.arrays.MethodUnderTest::class.functions.first() + val methodFromAnotherPackage = MethodUnderTest::class.functions.first() - createModelAndAssert(compositeModel, null, UtMethod.from(methodFromAnotherPackage)) + createModelAndAssert(compositeModel, null, methodFromAnotherPackage.executableId) } @Test @@ -170,7 +183,7 @@ class AssembleModelGeneratorTests { val firstExpectedRepresentation = printExpectedModel(testClassId.simpleName, v1, statementsChain.toList()) statementsChain.clear() - statementsChain.add("val $v2 = ${innerClassId.canonicalName}()") + statementsChain.add("${innerClassId.canonicalName}()") statementsChain.add("$v2." + addExpectedSetter("a", 2)) statementsChain.add("$v2." + ("b" `=` 4)) val secondExpectedRepresentation = printExpectedModel(innerClassId.simpleName, v2, statementsChain.toList()) @@ -224,7 +237,7 @@ class AssembleModelGeneratorTests { val firstExpectedRepresentation = printExpectedModel(listClassId.simpleName, v1, statementsChain.toList()) statementsChain.clear() - statementsChain.add("val $v2 = ${listClassId.canonicalName}()") + statementsChain.add("${listClassId.canonicalName}()") statementsChain.add("$v2." + addExpectedSetter("value", 2)) val secondExpectedRepresentation = printExpectedModel(listClassId.simpleName, v2, statementsChain.toList()) @@ -254,13 +267,13 @@ class AssembleModelGeneratorTests { val firstExpectedRepresentation = printExpectedModel(listClassId.simpleName, v1, statementsChain.toList()) statementsChain.clear() - statementsChain.add("val $v2 = ${listClassId.canonicalName}()") + statementsChain.add("${listClassId.canonicalName}()") statementsChain.add("$v2." + addExpectedSetter("value", 2)) statementsChain.add("$v2." + addExpectedSetter("next", v3)) val secondExpectedRepresentation = printExpectedModel(listClassId.simpleName, v2, statementsChain.toList()) statementsChain.clear() - statementsChain.add("val $v3 = ${listClassId.canonicalName}()") + statementsChain.add("${listClassId.canonicalName}()") statementsChain.add("$v3." + addExpectedSetter("value", 3)) statementsChain.add("$v3." + addExpectedSetter("next", v1)) val thirdExpectedRepresentation = printExpectedModel(listClassId.simpleName, v3, statementsChain.toList()) @@ -399,7 +412,7 @@ class AssembleModelGeneratorTests { val baseClassId = PrimitiveFields::class.id val thisFields = fields(inheritedFieldClassId, "i" to 5, "d" to 3.0) - val baseFields = fields(baseClassId, "a" to 2, "b" to 4) + val baseFields = fields(baseClassId, "b" to 4) val compositeModel = UtCompositeModel( modelIdCounter.incrementAndGet(), @@ -411,7 +424,6 @@ class AssembleModelGeneratorTests { val v1 = statementsChain.addExpectedVariableDecl() statementsChain.add("$v1." + ("i" `=` 5)) statementsChain.add("$v1." + ("d" `=` 3.0)) - statementsChain.add("$v1." + addExpectedSetter("a", 2)) statementsChain.add("$v1." + ("b" `=` 4)) val expectedRepresentation = printExpectedModel(inheritedFieldClassId.simpleName, v1, statementsChain) @@ -975,7 +987,7 @@ class AssembleModelGeneratorTests { val firstExpectedRepresentation = printExpectedModel(listClassId.simpleName, v1, statementsChain.toList()) statementsChain.clear() - statementsChain.add("val $v2 = ${listClassId.canonicalName}()") + statementsChain.add("${listClassId.canonicalName}()") statementsChain.add("$v2." + addExpectedSetter("value", 2)) val secondExpectedRepresentation = printExpectedModel(listClassId.simpleName, v2, statementsChain.toList()) @@ -1003,7 +1015,7 @@ class AssembleModelGeneratorTests { val firstExpectedRepresentation = printExpectedModel(listClassId.simpleName, v1, statementsChain.toList()) statementsChain.clear() - statementsChain.add("val $v2 = ${listClassId.canonicalName}()") + statementsChain.add("${listClassId.canonicalName}()") statementsChain.add("$v2." + addExpectedSetter("value", 2)) statementsChain.add("$v2." + addExpectedSetter("next", v1)) val secondExpectedRepresentation = printExpectedModel(listClassId.simpleName, v2, statementsChain) @@ -1105,7 +1117,7 @@ class AssembleModelGeneratorTests { testClassId, "array" to UtArrayModel( modelIdCounter.incrementAndGet(), - ClassId("[L${innerClassId.canonicalName}", innerClassId), + arrayTypeOf(innerClassId), length = 3, UtNullModel(innerClassId), mutableMapOf( @@ -1126,7 +1138,7 @@ class AssembleModelGeneratorTests { statementsChain.add( "$v1." + ("array" `=` "[" + "null, " + - "UtAssembleModel(${innerClassId.simpleName} $v2) val $v2 = ${innerClassId.canonicalName}() $v2.setA(5), " + + "UtAssembleModel(${innerClassId.simpleName} $v2) ${innerClassId.canonicalName}() $v2.setA(5), " + "null" + "]") ) @@ -1175,11 +1187,11 @@ class AssembleModelGeneratorTests { val testClassId = ArrayOfComplexArrays::class.id val innerClassId = PrimitiveFields::class.id - val innerArrayClassId = ClassId("[L${innerClassId.canonicalName}", innerClassId) + val innerArrayClassId = arrayTypeOf(innerClassId) val arrayOfArraysModel = UtArrayModel( modelIdCounter.incrementAndGet(), - ClassId("[Lorg.utbot.examples.assemble.ComplexArray", testClassId), + arrayTypeOf(testClassId), length = 2, UtNullModel(innerArrayClassId), mutableMapOf( @@ -1226,8 +1238,8 @@ class AssembleModelGeneratorTests { val v3 = createExpectedVariableName() statementsChain.add( "$v1." + ("array" `=` "[" + - "[UtAssembleModel(${innerClassId.simpleName} $v2) val $v2 = ${innerClassId.canonicalName}() $v2.setA(5), ${null}], " + - "[UtAssembleModel(${innerClassId.simpleName} $v3) val $v3 = ${innerClassId.canonicalName}() $v3.b = 4, ${null}]" + + "[UtAssembleModel(${innerClassId.simpleName} $v2) ${innerClassId.canonicalName}() $v2.setA(5), ${null}], " + + "[UtAssembleModel(${innerClassId.simpleName} $v3) ${innerClassId.canonicalName}() $v3.b = 4, ${null}]" + "]") ) @@ -1414,7 +1426,7 @@ class AssembleModelGeneratorTests { private fun createModelAndAssert( model: UtModel, expectedModelRepresentation: String?, - methodUnderTest: UtMethod<*> = UtMethod.from(AssembleTestUtils::class.functions.first()), + methodUnderTest: ExecutableId = AssembleTestUtils::class.id.allMethods.first(), ) = createModelsAndAssert(listOf(model), listOf(expectedModelRepresentation), methodUnderTest) /** @@ -1434,9 +1446,9 @@ class AssembleModelGeneratorTests { private fun createModelsAndAssert( models: List, expectedModelRepresentations: List, - assembleTestUtils: UtMethod<*> = UtMethod.from(AssembleTestUtils::class.functions.first()), + assembleTestDummyMethod: ExecutableId = AssembleTestUtils::class.id.allMethods.first(), ) { - val modelsMap = AssembleModelGenerator(assembleTestUtils).createAssembleModels(models) + val modelsMap = AssembleModelGenerator(assembleTestDummyMethod.classId.packageName).createAssembleModels(models) //we sort values to fix order of models somehow (IdentityHashMap does not guarantee the order) val assembleModels = modelsMap.values .filterIsInstance() @@ -1466,7 +1478,7 @@ class AssembleModelGeneratorTests { val varName = createExpectedVariableName() val paramString = if (params.any()) params.joinToString(", ") else "" - this.add("val $varName = $fqn($paramString)") + this.add("$fqn($paramString)") return varName } diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/minimization/MinimizationGreedyEssentialTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/framework/minimization/MinimizationGreedyEssentialTest.kt similarity index 85% rename from utbot-framework/src/test/kotlin/org/utbot/framework/minimization/MinimizationGreedyEssentialTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/framework/minimization/MinimizationGreedyEssentialTest.kt index 993f23059a..2a96a677de 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/minimization/MinimizationGreedyEssentialTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/framework/minimization/MinimizationGreedyEssentialTest.kt @@ -68,4 +68,18 @@ class MinimizationGreedyEssentialTest { val minimizedExecutions = GreedyEssential.minimize(executions) assertEquals((1..size).toList(), minimizedExecutions.sorted()) } + + @Test + fun testExecutionPriority() { + val executions = mapOf( + 0 to listOf(0, 1, 2, 3, 4), + 1 to listOf(0, 1, 2, 3, 4) + ) + val executionToPriority = mapOf( + 0 to 1, + 1 to 0 + ) + val minimizedExecutions = GreedyEssential.minimize(executions, executionToPriority) + assertEquals(listOf(1), minimizedExecutions) + } } \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/modificators/UtBotFieldModificatorsTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/framework/modificators/UtBotFieldModificatorsTest.kt similarity index 92% rename from utbot-framework/src/test/kotlin/org/utbot/framework/modificators/UtBotFieldModificatorsTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/framework/modificators/UtBotFieldModificatorsTest.kt index 88455929a9..b552dc7176 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/modificators/UtBotFieldModificatorsTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/framework/modificators/UtBotFieldModificatorsTest.kt @@ -1,6 +1,5 @@ package org.utbot.framework.modificators -import org.utbot.common.packageName import org.utbot.examples.modificators.ConstructorsAndSetters import org.utbot.examples.modificators.DefaultField import org.utbot.examples.modificators.InvokeInAssignment @@ -13,11 +12,10 @@ import org.utbot.examples.modificators.StronglyConnectedComponents import org.utbot.examples.modificators.coupling.ClassA import org.utbot.examples.modificators.coupling.ClassB import org.utbot.examples.modificators.hierarchy.InheritedModifications -import org.utbot.framework.SootUtils -import org.utbot.framework.modifications.AnalysisMode -import org.utbot.framework.modifications.AnalysisMode.AllModificators -import org.utbot.framework.modifications.AnalysisMode.SettersAndDirectAccessors -import org.utbot.framework.modifications.UtBotFieldsModificatorsSearcher +import org.utbot.modifications.AnalysisMode +import org.utbot.modifications.AnalysisMode.AllModificators +import org.utbot.modifications.AnalysisMode.SettersAndDirectAccessors +import org.utbot.modifications.UtBotFieldsModificatorsSearcher import org.utbot.framework.plugin.api.util.UtContext import org.utbot.framework.plugin.api.util.id import kotlin.reflect.KClass @@ -26,6 +24,9 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.services.JdkInfoDefaultProvider +import org.utbot.framework.util.SootUtils +import org.utbot.modifications.FieldInvolvementMode internal class UtBotFieldModificatorsTest { private lateinit var fieldsModificatorsSearcher: UtBotFieldsModificatorsSearcher @@ -170,8 +171,14 @@ internal class UtBotFieldModificatorsTest { } private fun initAnalysis() { - SootUtils.runSoot(PrimitiveModifications::class) - fieldsModificatorsSearcher = UtBotFieldsModificatorsSearcher() + SootUtils.runSoot( + PrimitiveModifications::class.java, + forceReload = false, + jdkInfo = JdkInfoDefaultProvider().info + ) + fieldsModificatorsSearcher = UtBotFieldsModificatorsSearcher( + fieldInvolvementMode = FieldInvolvementMode.WriteOnly + ) } private fun runUpdate(classes: Set>) { @@ -188,7 +195,7 @@ internal class UtBotFieldModificatorsTest { //We use sorting here to make comparing with sorted in advance expected collections easier private fun runFieldModificatorsSearch(analysisMode: AnalysisMode) = - fieldsModificatorsSearcher.findModificators(analysisMode, PrimitiveModifications::class.java.packageName) + fieldsModificatorsSearcher.getFieldToModificators(analysisMode) .map { (key, value) -> val modificatorNames = value.filterNot { it.name.startsWith("direct_set_") }.map { it.name } key.name to modificatorNames.toSortedSet() diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/framework/plugin/api/MockStrategyApiTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/framework/plugin/api/MockStrategyApiTest.kt new file mode 100644 index 0000000000..21e3730342 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/framework/plugin/api/MockStrategyApiTest.kt @@ -0,0 +1,58 @@ +package org.utbot.framework.plugin.api + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Test +import org.utbot.engine.MockStrategy +import org.utbot.framework.util.toModel + +internal class MockStrategyApiTest { + + @Test + fun testApiToModel() { + assertEquals( + MockStrategyApi.values().size, MockStrategy.values().size, + "The number of strategies in the contract and engine model should match" + ) + + assertEquals(3, MockStrategyApi.values().size, "Three options only (so far)") + assertEquals(MockStrategy.NO_MOCKS, MockStrategyApi.NO_MOCKS.toModel()) + assertEquals(MockStrategy.OTHER_PACKAGES, MockStrategyApi.OTHER_PACKAGES.toModel()) + assertEquals(MockStrategy.OTHER_CLASSES, MockStrategyApi.OTHER_CLASSES.toModel()) + } + + @Test + fun ensureDefaultStrategyIsOtherPackages() { + assertEquals( + MockStrategyApi.OTHER_PACKAGES, + MockStrategyApi.defaultItem, + "Expecting that ${MockStrategyApi.OTHER_PACKAGES} is the default policy for Mocks " + + "but ${MockStrategyApi.defaultItem} found" + ) + } + + @Test + fun ensureDefaultStrategyIsOtherClassesInSpringApplication() { + assertEquals( + MockStrategyApi.OTHER_CLASSES, + MockStrategyApi.springDefaultItem, + "Expecting that ${MockStrategyApi.OTHER_CLASSES} is the default policy for Mocks in Spring application" + + "but ${MockStrategyApi.springDefaultItem} found" + ) + } + + @Test + fun testLabelToEnum() { + assertEquals( + MockStrategyApi.values().size, + MockStrategyApi.labels().toSet().size, + "Expecting all labels are unique" + ) + + assertFalse( + MockStrategyApi.labels().any { it.isBlank() }, + "Expecting all labels are not empty" + ) + } + +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/framework/z3/extension/Z3ExtensionForTests.kt b/utbot-framework-test/src/test/kotlin/org/utbot/framework/z3/extension/Z3ExtensionForTests.kt new file mode 100644 index 0000000000..a0e9efcb9f --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/framework/z3/extension/Z3ExtensionForTests.kt @@ -0,0 +1,64 @@ +@file:Suppress("PackageDirectoryMismatch", "unused", "NonAsciiCharacters") + +package com.microsoft.z3 + +import kotlin.reflect.KProperty + + +operator fun ArithExpr<*>.plus(other: ArithExpr<*>): ArithExpr<*> = context.mkAdd(this, other) +operator fun ArithExpr<*>.plus(other: Int): ArithExpr<*> = this + context.mkInt(other) + +operator fun ArithExpr<*>.minus(other: ArithExpr<*>): ArithExpr<*> = context.mkSub(this, other) +operator fun ArithExpr<*>.minus(other: Int): ArithExpr<*> = this - context.mkInt(other) + +operator fun ArithExpr<*>.times(other: ArithExpr<*>): ArithExpr<*> = context.mkMul(this, other) +operator fun ArithExpr<*>.times(other: Int): ArithExpr<*> = this * context.mkInt(other) + +infix fun Expr<*>.`=`(other: Int): BoolExpr = this eq context.mkInt(other) +infix fun Expr<*>.`=`(other: Expr<*>): BoolExpr = this eq other +infix fun Expr<*>.eq(other: Expr<*>): BoolExpr = context.mkEq(this, other) +infix fun Expr<*>.`!=`(other: Expr<*>): BoolExpr = context.mkNot(this `=` other) +operator fun BoolExpr.not(): BoolExpr = context.mkNot(this) + +infix fun BoolExpr.`⇒`(other: BoolExpr): BoolExpr = this implies other +infix fun BoolExpr.`⇒`(other: Boolean): BoolExpr = this implies other +infix fun BoolExpr.implies(other: Boolean): BoolExpr = this implies context.mkBool(other) +infix fun BoolExpr.implies(other: BoolExpr): BoolExpr = context.mkImplies(this, other) +infix fun BoolExpr.and(other: BoolExpr): BoolExpr = context.mkAnd(this, other) +infix fun BoolExpr.or(other: BoolExpr): BoolExpr = context.mkOr(this, other) + +infix fun ArithExpr<*>.gt(other: ArithExpr<*>): BoolExpr = context.mkGt(this, other) +infix fun ArithExpr<*>.gt(other: Int): BoolExpr = context.mkGt(this, context.mkInt(other)) + +infix fun ArithExpr<*>.`=`(other: Int): BoolExpr = context.mkEq(this, context.mkInt(other)) + +operator fun ArrayExpr<*, *>.get(index: IntExpr): Expr<*> = context.mkSelect(this, index.cast()) +operator fun ArrayExpr<*, *>.get(index: Int): Expr<*> = this[context.mkInt(index)] +fun ArrayExpr<*, *>.set(index: IntExpr, value: Expr<*>): ArrayExpr<*, *> = context.mkStore(this, index.cast(), value.cast()) +fun ArrayExpr<*, *>.set(index: Int, value: Expr<*>): ArrayExpr<*, *> = set(context.mkInt(index), value) + +operator fun SeqExpr<*>.plus(other: SeqExpr<*>): SeqExpr<*> = context.mkConcat(this, other.cast()) +operator fun SeqExpr<*>.plus(other: String): SeqExpr<*> = context.mkConcat(context.mkString(other)) + +@Suppress("UNCHECKED_CAST") +fun Expr.cast(): Expr = this as Expr + + +infix fun SeqExpr<*>.`=`(other: String): BoolExpr = context.mkEq(this, context.mkString(other)) + +class Const(private val ctx: Context, val produce: (Context, name: String) -> T) { + operator fun getValue(thisRef: Nothing?, property: KProperty<*>): T = produce(ctx, property.name) +} + +fun Context.declareInt() = Const(this) { ctx, name -> ctx.mkIntConst(name) } +fun Context.declareBool() = Const(this) { ctx, name -> ctx.mkBoolConst(name) } +fun Context.declareReal() = Const(this) { ctx, name -> ctx.mkRealConst(name) } +fun Context.declareString() = Const(this) { ctx, name -> ctx.mkConst(name, stringSort) as SeqExpr<*> } +fun Context.declareList(sort: ListSort<*>) = Const(this) { ctx, name -> ctx.mkConst(name, sort) } +fun Context.declareIntArray() = Const(this) { ctx, name -> ctx.mkArrayConst(name, intSort, intSort) } + + +operator fun FuncDecl<*>.invoke(vararg expr: Expr<*>): Expr<*> = context.mkApp(this, *expr) + +fun Model.eval(expr: Expr<*>): Expr<*> = this.eval(expr, true) +fun Model.eval(vararg exprs: Expr<*>): List> = exprs.map { this.eval(it, true) } \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/z3/extension/Z3ExtensionTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/framework/z3/extension/Z3ExtensionTest.kt similarity index 98% rename from utbot-framework/src/test/kotlin/org/utbot/framework/z3/extension/Z3ExtensionTest.kt rename to utbot-framework-test/src/test/kotlin/org/utbot/framework/z3/extension/Z3ExtensionTest.kt index c31298bac8..896d1e3a98 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/z3/extension/Z3ExtensionTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/framework/z3/extension/Z3ExtensionTest.kt @@ -99,8 +99,8 @@ class Z3ExtensionTest : Z3Initializer() { val (aVal, bVal, cVal) = solver.model .eval(a, b, c) - .filterIsInstance() - .map(SeqExpr::getString) + .filterIsInstance>() + .map(SeqExpr<*>::getString) .also { list -> assertEquals(3, list.size) } assertEquals("abcd", "$aVal$bVal") diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/sarif/SarifReportTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/sarif/SarifReportTest.kt new file mode 100644 index 0000000000..cfc4fdf4f6 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/sarif/SarifReportTest.kt @@ -0,0 +1,440 @@ +package org.utbot.sarif + +import org.junit.Test +import org.mockito.Mockito +import org.utbot.framework.plugin.api.* + +class SarifReportTest { + + @Test + fun testNonEmptyReport() { + val actualReport = SarifReport( + testSets = listOf(), + generatedTestsCode = "", + sourceFindingEmpty + ).createReport().toJson() + + assert(actualReport.isNotEmpty()) + } + + @Test + fun testNoUncheckedExceptions() { + val sarif = SarifReport( + testSets = listOf(testSet), + generatedTestsCode = "", + sourceFindingEmpty + ).createReport() + + assert(sarif.runs.first().results.isEmpty()) + } + + @Test + fun testDetectAllUncheckedExceptions() { + mockUtMethodNames() + + val mockUtExecutionNPE = Mockito.mock(UtExecution::class.java, Mockito.RETURNS_DEEP_STUBS) + Mockito.`when`(mockUtExecutionNPE.result).thenReturn( + UtImplicitlyThrownException(NullPointerException(), false), + ) + Mockito.`when`(mockUtExecutionNPE.stateBefore.parameters).thenReturn(listOf()) + + val mockUtExecutionAIOBE = Mockito.mock(UtExecution::class.java, Mockito.RETURNS_DEEP_STUBS) + Mockito.`when`(mockUtExecutionAIOBE.result).thenReturn( + UtImplicitlyThrownException(ArrayIndexOutOfBoundsException(), false), + ) + Mockito.`when`(mockUtExecutionAIOBE.stateBefore.parameters).thenReturn(listOf()) + + defaultMockCoverage(mockUtExecutionNPE) + defaultMockCoverage(mockUtExecutionAIOBE) + + val testSets = listOf( + UtMethodTestSet(mockExecutableId, listOf(mockUtExecutionNPE)), + UtMethodTestSet(mockExecutableId, listOf(mockUtExecutionAIOBE)) + ) + + val report = SarifReport( + testSets = testSets, + generatedTestsCode = "", + sourceFindingEmpty + ).createReport() + + assert(report.runs.first().results[0].message.text.contains("NullPointerException")) + assert(report.runs.first().results[1].message.text.contains("ArrayIndexOutOfBoundsException")) + } + + @Test + fun testCorrectResultLocations() { + mockUtMethodNames() + + Mockito.`when`(mockUtExecution.result).thenReturn( + UtImplicitlyThrownException(NullPointerException(), false) + ) + mockCoverage(mockUtExecution, 1337, "Main") + Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn(listOf()) + Mockito.`when`(mockUtExecution.testMethodName).thenReturn("testMain_ThrowArithmeticException") + + val report = sarifReportMain.createReport() + + val result = report.runs.first().results.first() + val location = result.locations.filterIsInstance().first().physicalLocation + val relatedLocation = result.relatedLocations.first().physicalLocation + + assert(location.artifactLocation.uri.contains("Main.java")) + assert(location.region.startLine == 1337) + assert(relatedLocation.artifactLocation.uri.contains("MainTest.java")) + assert(relatedLocation.region.startLine == 1) + assert(relatedLocation.region.startColumn == 1) + } + + @Test + fun testCorrectMethodParameters() { + mockUtMethodNames() + + Mockito.`when`(mockUtExecution.result).thenReturn( + UtImplicitlyThrownException(NullPointerException(), false) + ) + Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn( + listOf( + UtPrimitiveModel(227), + UtPrimitiveModel(3.14), + UtPrimitiveModel(false) + ) + ) + + defaultMockCoverage(mockUtExecution) + + val report = sarifReportMain.createReport() + + val result = report.runs.first().results.first() + assert(result.message.text.contains("227")) + assert(result.message.text.contains("3.14")) + assert(result.message.text.contains("false")) + } + + @Test + fun testCorrectCodeFlows() { + mockUtMethodNames() + + val uncheckedException = Mockito.mock(NullPointerException::class.java) + val stackTraceElement = StackTraceElement("Main", "main", "Main.java", 17) + Mockito.`when`(uncheckedException.stackTrace).thenReturn( + Array(2) { stackTraceElement } + ) + + Mockito.`when`(mockUtExecution.result).thenReturn( + UtImplicitlyThrownException(uncheckedException, false) + ) + Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn(listOf()) + + defaultMockCoverage(mockUtExecution) + + val report = sarifReportMain.createReport() + + val result = report.runs.first().results.first().codeFlows.first().threadFlows.first().locations.map { + it.location.physicalLocation + } + for (index in 0..1) { + assert(result[index].artifactLocation.uri.contains("Main.java")) + assert(result[index].region.startLine == 17) + } + } + + @Test + fun testProcessSandboxFailure() { + mockUtMethodNames() + + val uncheckedException = Mockito.mock(java.security.NoSuchAlgorithmException::class.java) + Mockito.`when`(uncheckedException.stackTrace).thenReturn(arrayOf()) + Mockito.`when`(mockUtExecution.result).thenReturn(UtSandboxFailure(uncheckedException)) + + defaultMockCoverage(mockUtExecution) + + val report = sarifReportMain.createReport() + val result = report.runs.first().results.first() + assert(result.message.text.contains("NoSuchAlgorithmException")) + } + + @Test + fun testProcessTimeoutException() { + mockUtMethodNames() + + val timeoutMessage = "Timeout 1000 elapsed" + val timeoutException = TimeoutException(timeoutMessage) + Mockito.`when`(mockUtExecution.result).thenReturn(UtTimeoutException(timeoutException)) + + defaultMockCoverage(mockUtExecution) + + val report = sarifReportMain.createReport() + val result = report.runs.first().results.first() + assert(result.message.text.contains(timeoutMessage)) + } + + @Test + fun testConvertCoverageToStackTrace() { + mockUtMethodNames() + + val timeoutException = TimeoutException("Timeout 1000 elapsed") + Mockito.`when`(mockUtExecution.result).thenReturn(UtTimeoutException(timeoutException)) + + val classMainPath = "com/abc/Main" + val classUtilPath = "com/cba/Util" + Mockito.`when`(mockUtExecution.symbolicSteps).thenReturn(listOf()) + Mockito.`when`(mockUtExecution.coverage?.coveredInstructions).thenReturn(listOf( + Instruction(classMainPath, "main(l)l", 3, 1), + Instruction(classMainPath, "main(l)l", 4, 2), + Instruction(classMainPath, "main(l)l", 4, 3), // last for main + Instruction(classUtilPath, "util(ll)l", 6, 4), + Instruction(classUtilPath, "util(ll)l", 7, 5), // last for util + )) + + val report = sarifReportMain.createReport() + val result = report.runs.first().results.first() + val stackTrace = result.codeFlows.first().threadFlows.first().locations + + assert(stackTrace[0].location.physicalLocation.artifactLocation.uri == "$classMainPath.java") + assert(stackTrace[0].location.physicalLocation.region.startLine == 4) + + assert(stackTrace[1].location.physicalLocation.artifactLocation.uri == "$classUtilPath.java") + assert(stackTrace[1].location.physicalLocation.region.startLine == 7) + } + + @Test + fun testCodeFlowsStartsWithMethodCall() { + mockUtMethodNames() + + val uncheckedException = Mockito.mock(NullPointerException::class.java) + val stackTraceElement = StackTraceElement("Main", "main", "Main.java", 3) + Mockito.`when`(uncheckedException.stackTrace).thenReturn(arrayOf(stackTraceElement)) + + Mockito.`when`(mockUtExecution.result).thenReturn( + UtImplicitlyThrownException(uncheckedException, false) + ) + Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn(listOf()) + Mockito.`when`(mockUtExecution.testMethodName).thenReturn("testMain_ThrowArithmeticException") + + defaultMockCoverage(mockUtExecution) + + val report = sarifReportMain.createReport() + + val codeFlowPhysicalLocations = report.runs[0].results[0].codeFlows[0].threadFlows[0].locations.map { + it.location.physicalLocation + } + assert(codeFlowPhysicalLocations[0].artifactLocation.uri.contains("MainTest.java")) + assert(codeFlowPhysicalLocations[0].region.startLine == 5) + assert(codeFlowPhysicalLocations[0].region.startColumn == 7) + } + + @Test + fun testCodeFlowsStartsWithPrivateMethodCall() { + mockUtMethodNames() + + val uncheckedException = Mockito.mock(NullPointerException::class.java) + val stackTraceElement = StackTraceElement("Main", "main", "Main.java", 3) + Mockito.`when`(uncheckedException.stackTrace).thenReturn(arrayOf(stackTraceElement)) + + Mockito.`when`(mockUtExecution.result).thenReturn( + UtImplicitlyThrownException(uncheckedException, false) + ) + Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn(listOf()) + Mockito.`when`(mockUtExecution.testMethodName).thenReturn("testMain_ThrowArithmeticException") + + defaultMockCoverage(mockUtExecution) + + val report = sarifReportPrivateMain.createReport() + + val codeFlowPhysicalLocations = report.runs[0].results[0].codeFlows[0].threadFlows[0].locations.map { + it.location.physicalLocation + } + assert(codeFlowPhysicalLocations[0].artifactLocation.uri.contains("MainTest.java")) + assert(codeFlowPhysicalLocations[0].region.startLine == 6) + assert(codeFlowPhysicalLocations[0].region.startColumn == 5) + } + + @Test + fun testMinimizationRemovesDuplicates() { + mockUtMethodNames() + + val mockUtExecution = Mockito.mock(UtExecution::class.java, Mockito.RETURNS_DEEP_STUBS) + Mockito.`when`(mockUtExecution.result).thenReturn(UtImplicitlyThrownException(NullPointerException(), false)) + + defaultMockCoverage(mockUtExecution) + + val testSets = listOf( + UtMethodTestSet(mockExecutableId, listOf(mockUtExecution)), + UtMethodTestSet(mockExecutableId, listOf(mockUtExecution)) // duplicate + ) + + val report = SarifReport( + testSets = testSets, + generatedTestsCode = "", + sourceFindingMain + ).createReport() + + assert(report.runs.first().results.size == 1) // no duplicates + } + + @Test + fun testMinimizationDoesNotRemoveResultsWithDifferentRuleId() { + mockUtMethodNames() + + val mockUtExecution1 = Mockito.mock(UtExecution::class.java, Mockito.RETURNS_DEEP_STUBS) + val mockUtExecution2 = Mockito.mock(UtExecution::class.java, Mockito.RETURNS_DEEP_STUBS) + + // different ruleId's + Mockito.`when`(mockUtExecution1.result).thenReturn(UtImplicitlyThrownException(NullPointerException(), false)) + Mockito.`when`(mockUtExecution2.result).thenReturn(UtImplicitlyThrownException(ArithmeticException(), false)) + + defaultMockCoverage(mockUtExecution1) + defaultMockCoverage(mockUtExecution2) + + val testSets = listOf( + UtMethodTestSet(mockExecutableId, listOf(mockUtExecution1)), + UtMethodTestSet(mockExecutableId, listOf(mockUtExecution2)) // not a duplicate + ) + + val report = SarifReport( + testSets = testSets, + generatedTestsCode = "", + sourceFindingMain + ).createReport() + + assert(report.runs.first().results.size == 2) // no results have been removed + } + + @Test + fun testMinimizationDoesNotRemoveResultsWithDifferentLocations() { + mockUtMethodNames() + + val mockUtExecution1 = Mockito.mock(UtSymbolicExecution::class.java, Mockito.RETURNS_DEEP_STUBS) + val mockUtExecution2 = Mockito.mock(UtSymbolicExecution::class.java, Mockito.RETURNS_DEEP_STUBS) + + // the same ruleId's + Mockito.`when`(mockUtExecution1.result).thenReturn(UtImplicitlyThrownException(NullPointerException(), false)) + Mockito.`when`(mockUtExecution2.result).thenReturn(UtImplicitlyThrownException(NullPointerException(), false)) + + // different locations + mockCoverage(mockUtExecution1, 11, "Main") + mockCoverage(mockUtExecution2, 22, "Main") + + val testSets = listOf( + UtMethodTestSet(mockExecutableId, listOf(mockUtExecution1)), + UtMethodTestSet(mockExecutableId, listOf(mockUtExecution2)) // not a duplicate + ) + + val report = SarifReport( + testSets = testSets, + generatedTestsCode = "", + sourceFindingMain + ).createReport() + + assert(report.runs.first().results.size == 2) // no results have been removed + } + + @Test + fun testMinimizationChoosesShortestCodeFlow() { + mockUtMethodNames() + + val mockNPE1 = Mockito.mock(NullPointerException::class.java) + val mockNPE2 = Mockito.mock(NullPointerException::class.java) + + val mockUtExecution1 = Mockito.mock(UtExecution::class.java, Mockito.RETURNS_DEEP_STUBS) + val mockUtExecution2 = Mockito.mock(UtExecution::class.java, Mockito.RETURNS_DEEP_STUBS) + + // the same ruleId's + Mockito.`when`(mockUtExecution1.result).thenReturn(UtImplicitlyThrownException(mockNPE1, false)) + Mockito.`when`(mockUtExecution2.result).thenReturn(UtImplicitlyThrownException(mockNPE2, false)) + + // but different stack traces + val stackTraceElement1 = StackTraceElement("Main", "main", "Main.java", 3) + val stackTraceElement2 = StackTraceElement("Main", "main", "Main.java", 7) + Mockito.`when`(mockNPE1.stackTrace).thenReturn(arrayOf(stackTraceElement1)) + Mockito.`when`(mockNPE2.stackTrace).thenReturn(arrayOf(stackTraceElement1, stackTraceElement2)) + + defaultMockCoverage(mockUtExecution1) + defaultMockCoverage(mockUtExecution2) + + val testSets = listOf( + UtMethodTestSet(mockExecutableId, listOf(mockUtExecution1)), + UtMethodTestSet(mockExecutableId, listOf(mockUtExecution2)) // duplicate with a longer stack trace + ) + + val report = SarifReport( + testSets = testSets, + generatedTestsCode = "", + sourceFindingMain + ).createReport() + + assert(report.runs.first().results.size == 1) // no duplicates + assert(report.runs.first().results.first().totalCodeFlowLocations() == 1) // with a shorter stack trace + } + + // internal + + private val mockExecutableId = Mockito.mock(ExecutableId::class.java, Mockito.RETURNS_DEEP_STUBS) + + private val mockUtExecution = Mockito.mock(UtSymbolicExecution::class.java, Mockito.RETURNS_DEEP_STUBS) + + private val testSet = UtMethodTestSet(mockExecutableId, listOf(mockUtExecution)) + + private fun mockUtMethodNames() { + Mockito.`when`(mockExecutableId.name).thenReturn("main") + Mockito.`when`(mockExecutableId.classId.name).thenReturn("Main") + } + + private fun mockCoverage(mockExecution: UtExecution, lineNumber: Int, className: String) { + Mockito.`when`(mockExecution.coverage?.coveredInstructions?.lastOrNull()?.lineNumber).thenReturn(1) + Mockito.`when`(mockExecution.coverage?.coveredInstructions?.lastOrNull()?.internalName).thenReturn("Main") + Mockito.`when`(mockExecution.coverage?.coveredInstructions?.lastOrNull()?.className).thenReturn("Main") + (mockExecution as? UtSymbolicExecution)?.let { mockSymbolicSteps(it, lineNumber, className) } + } + + private fun mockSymbolicSteps(mockExecution: UtSymbolicExecution, lineNumber: Int, className: String) { + Mockito.`when`(mockExecution.symbolicSteps.lastOrNull()?.lineNumber).thenReturn(lineNumber) + Mockito.`when`(mockExecution.symbolicSteps.lastOrNull()?.method?.declaringClass?.name).thenReturn(className) + } + + private fun defaultMockCoverage(mockExecution: UtExecution) { + mockCoverage(mockExecution, 1, "Main") + } + + // constants + + private val sourceFindingEmpty = SourceFindingStrategyDefault( + sourceClassFqn = "", + sourceFilePath = "", + testsFilePath = "", + projectRootPath = "" + ) + + private val sourceFindingMain = SourceFindingStrategyDefault( + sourceClassFqn = "Main", + sourceFilePath = "src/Main.java", + testsFilePath = "test/MainTest.java", + projectRootPath = "." + ) + + private val generatedTestsCodeMain = """ + public void testMain_ThrowArithmeticException() { + /* This test fails because method [Main.main] produces [java.lang.ArithmeticException: / by zero] + Main.main(Main.java:15) */ + Main main = new Main(); + main.main(0); // shift for `startColumn` == 7 + } + """.trimIndent() + + private val generatedTestsCodePrivateMain = """ + public void testMain_ThrowArithmeticException() { + /* This test fails because method [Main.main] produces [java.lang.ArithmeticException: / by zero] + Main.main(Main.java:15) */ + Main main = new Main(); + // ... + mainMethod.invoke(main, mainMethodArguments); + } + """.trimIndent() + + private val sarifReportMain = + SarifReport(listOf(testSet), generatedTestsCodeMain, sourceFindingMain) + + private val sarifReportPrivateMain = + SarifReport(listOf(testSet), generatedTestsCodePrivateMain, sourceFindingMain) +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/Constants.kt b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/Constants.kt new file mode 100644 index 0000000000..0dfc8745d6 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/Constants.kt @@ -0,0 +1,25 @@ +package org.utbot.taint.parser.yaml + +internal const val k_sources = Constants.KEY_SOURCES +internal const val k_passes = Constants.KEY_PASSES +internal const val k_cleaners = Constants.KEY_CLEANERS +internal const val k_sinks = Constants.KEY_SINKS + +internal const val k_addTo = Constants.KEY_ADD_TO +internal const val k_getFrom = Constants.KEY_GET_FROM +internal const val k_removeFrom = Constants.KEY_REMOVE_FROM +internal const val k_check = Constants.KEY_CHECK +internal const val k_marks = Constants.KEY_MARKS + +internal const val k_signature = Constants.KEY_SIGNATURE +internal const val k_conditions = Constants.KEY_CONDITIONS + +internal const val k_not = Constants.KEY_CONDITION_NOT + +internal const val k_this = Constants.KEY_THIS +internal const val k_return = Constants.KEY_RETURN +internal const val k_arg = Constants.KEY_ARG + +internal const val k_lt = Constants.ARGUMENT_TYPE_PREFIX +internal const val k_gt = Constants.ARGUMENT_TYPE_SUFFIX +internal const val k__ = Constants.ARGUMENT_TYPE_UNDERSCORE diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/MethodArgumentParserTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/MethodArgumentParserTest.kt new file mode 100644 index 0000000000..290cb8d483 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/MethodArgumentParserTest.kt @@ -0,0 +1,156 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.Yaml +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class MethodArgumentParserTest { + + @Nested + @DisplayName("isArgumentType") + inner class IsArgumentTypeTest { + @Test + fun `should return true on underscore`() { + val yamlScalar = Yaml.default.parseToYamlNode(k__) + assertTrue(MethodArgumentParser.isArgumentType(yamlScalar)) + } + + @Test + fun `should return true on type fqn in brackets`() { + val yamlScalar = Yaml.default.parseToYamlNode("${k_lt}java.lang.String$k_gt") + assertTrue(MethodArgumentParser.isArgumentType(yamlScalar)) + } + + @Test + fun `should return false on values`() { + val yamlScalar = Yaml.default.parseToYamlNode("some-value") + assertFalse(MethodArgumentParser.isArgumentType(yamlScalar)) + } + + @Test + fun `should return false on another yaml type`() { + val yamlList = Yaml.default.parseToYamlNode("[ ${k_lt}int$k_gt ]") + assertFalse(MethodArgumentParser.isArgumentType(yamlList)) + } + } + + @Nested + @DisplayName("isArgumentValue") + inner class IsArgumentValueTest { + @Test + fun `should return true on scalar`() { + val yamlScalar = Yaml.default.parseToYamlNode("0") + assertTrue(MethodArgumentParser.isArgumentValue(yamlScalar)) + } + + @Test + fun `should return true on type fqn in brackets`() { + val yamlNull = Yaml.default.parseToYamlNode("null") + assertTrue(MethodArgumentParser.isArgumentValue(yamlNull)) + } + + @Test + fun `should return false on type fqn in brackets`() { + val yamlScalar = Yaml.default.parseToYamlNode("${k_lt}float$k_gt") + assertFalse(MethodArgumentParser.isArgumentValue(yamlScalar)) + } + + @Test + fun `should return false on another yaml type`() { + val yamlList = Yaml.default.parseToYamlNode("[ test ]") + assertFalse(MethodArgumentParser.isArgumentValue(yamlList)) + } + } + + @Nested + @DisplayName("parseArgumentType") + inner class ParseArgumentTypeTest { + @Test + fun `should parse underscore as ArgumentTypeAny`() { + val yamlScalar = Yaml.default.parseToYamlNode(k__) + val expectedArgumentType = YamlArgumentTypeAny + + val actualArgumentType = MethodArgumentParser.parseArgumentType(yamlScalar) + assertEquals(expectedArgumentType, actualArgumentType) + } + + @Test + fun `should parse type fqn in brackets`() { + val yamlScalar = Yaml.default.parseToYamlNode("${k_lt}double$k_gt") + val expectedArgumentType = YamlArgumentTypeString("double") + + val actualArgumentType = MethodArgumentParser.parseArgumentType(yamlScalar) + assertEquals(expectedArgumentType, actualArgumentType) + } + + @Test + fun `should fail on another yaml type`() { + val yamlMap = Yaml.default.parseToYamlNode("type: ${k_lt}double$k_gt") + + assertThrows { + MethodArgumentParser.parseArgumentType(yamlMap) + } + } + } + + @Nested + @DisplayName("parseArgumentValue") + inner class ParseArgumentValueTest { + @Test + fun `should parse yaml null as ArgumentValueNull`() { + val yamlNull = Yaml.default.parseToYamlNode("null") + val expectedArgumentValue = YamlArgumentValueNull + + val actualArgumentValue = MethodArgumentParser.parseArgumentValue(yamlNull) + assertEquals(expectedArgumentValue, actualArgumentValue) + } + + @Test + fun `should parse boolean yaml scalar`() { + val yamlScalar = Yaml.default.parseToYamlNode("false") + val expectedArgumentValue = YamlArgumentValueBoolean(false) + + val actualArgumentValue = MethodArgumentParser.parseArgumentValue(yamlScalar) + assertEquals(expectedArgumentValue, actualArgumentValue) + } + + @Test + fun `should parse long yaml scalar`() { + val yamlScalar = Yaml.default.parseToYamlNode("17") + val expectedArgumentValue = YamlArgumentValueLong(17L) + + val actualArgumentValue = MethodArgumentParser.parseArgumentValue(yamlScalar) + assertEquals(expectedArgumentValue, actualArgumentValue) + } + + @Test + fun `should parse double yaml scalar`() { + val yamlScalar = Yaml.default.parseToYamlNode("1.2") + val expectedArgumentValue = YamlArgumentValueDouble(1.2) + + val actualArgumentValue = MethodArgumentParser.parseArgumentValue(yamlScalar) + assertEquals(expectedArgumentValue, actualArgumentValue) + } + + @Test + fun `should parse string yaml scalar`() { + val yamlScalar = Yaml.default.parseToYamlNode("some-string") + val expectedArgumentValue = YamlArgumentValueString("some-string") + + val actualArgumentValue = MethodArgumentParser.parseArgumentValue(yamlScalar) + assertEquals(expectedArgumentValue, actualArgumentValue) + } + + @Test + fun `should fail on another yaml type`() { + val yamlList = Yaml.default.parseToYamlNode("[ some-string ]") + + assertThrows { + MethodArgumentParser.parseArgumentValue(yamlList) + } + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintConditionParserTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintConditionParserTest.kt new file mode 100644 index 0000000000..5c9968e565 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintConditionParserTest.kt @@ -0,0 +1,188 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class TaintConditionParserTest { + + @Nested + @DisplayName("parseCondition") + inner class ParseConditionTest { + @Test + fun `should parse yaml null as ValueCondition`() { + val yamlNull = Yaml.default.parseToYamlNode("null") + val expectedCondition = YamlTaintConditionEqualValue(YamlArgumentValueNull) + + val actualCondition = TaintConditionParser.parseCondition(yamlNull) + assertEquals(expectedCondition, actualCondition) + } + + @Test + fun `should parse yaml scalar with argument value as ValueCondition`() { + val yamlScalar = Yaml.default.parseToYamlNode("some-string") + val expectedCondition = YamlTaintConditionEqualValue(YamlArgumentValueString("some-string")) + + val actualCondition = TaintConditionParser.parseCondition(yamlScalar) + assertEquals(expectedCondition, actualCondition) + } + + @Test + fun `should parse yaml scalar with argument type as TypeCondition`() { + val yamlScalar = Yaml.default.parseToYamlNode("${k_lt}java.lang.Integer$k_gt") + val expectedCondition = YamlTaintConditionIsType(YamlArgumentTypeString("java.lang.Integer")) + + val actualCondition = TaintConditionParser.parseCondition(yamlScalar) + assertEquals(expectedCondition, actualCondition) + } + + @Test + fun `should parse yaml list as OrCondition`() { + val yamlList = Yaml.default.parseToYamlNode("[ 1, true, ${k_lt}java.lang.Integer$k_gt ]") + val expectedCondition = YamlTaintConditionOr( + listOf( + YamlTaintConditionEqualValue(YamlArgumentValueLong(1L)), + YamlTaintConditionEqualValue(YamlArgumentValueBoolean(true)), + YamlTaintConditionIsType(YamlArgumentTypeString("java.lang.Integer")), + ) + ) + + val actualCondition = TaintConditionParser.parseCondition(yamlList) + assertEquals(expectedCondition, actualCondition) + } + + @Test + fun `should parse yaml map with a key 'not' as NotCondition`() { + val yamlMap = Yaml.default.parseToYamlNode("$k_not: ${k_lt}int$k_gt") + val expectedCondition = YamlTaintConditionNot(YamlTaintConditionIsType(YamlArgumentTypeString("int"))) + + val actualCondition = TaintConditionParser.parseCondition(yamlMap) + assertEquals(expectedCondition, actualCondition) + } + + @Test + fun `should fail on yaml map without a key 'not'`() { + val yamlMap = Yaml.default.parseToYamlNode("net: ${k_lt}int$k_gt") + + assertThrows { + TaintConditionParser.parseCondition(yamlMap) + } + } + + @Test + fun `should fail on yaml map with unknown keys`() { + val yamlMap = Yaml.default.parseToYamlNode("{ $k_not: ${k_lt}int$k_gt, unknown-key: 0 }") + + assertThrows { + TaintConditionParser.parseCondition(yamlMap) + } + } + + @Test + fun `should parse complicated yaml node`() { + val yamlMap = Yaml.default.parseToYamlNode("$k_not: [ { $k_not: 0 }, ${k_lt}int$k_gt, { $k_not: null } ]") + val expectedCondition = YamlTaintConditionNot( + YamlTaintConditionOr( + listOf( + YamlTaintConditionNot(YamlTaintConditionEqualValue(YamlArgumentValueLong(0L))), + YamlTaintConditionIsType(YamlArgumentTypeString("int")), + YamlTaintConditionNot(YamlTaintConditionEqualValue(YamlArgumentValueNull)) + ) + ) + ) + + val actualCondition = TaintConditionParser.parseCondition(yamlMap) + assertEquals(expectedCondition, actualCondition) + } + + @Test + fun `should fail on another yaml type`() { + val yamlTaggedNode = YamlTaggedNode("some-tag", YamlNull(YamlPath.root)) + + assertThrows { + TaintConditionParser.parseCondition(yamlTaggedNode) + } + } + } + + @Nested + @DisplayName("parseConditions") + inner class ParseConditionsTest { + @Test + fun `should parse correct yaml map as Conditions`() { + val yamlMap = + Yaml.default.parseToYamlNode("{ $k_this: \"\", ${k_arg}2: { $k_not: ${k_lt}int$k_gt }, $k_return: [ 0, 1 ] }") + val expectedConditions = YamlTaintConditionsMap( + mapOf( + YamlTaintEntityThis to YamlTaintConditionEqualValue(YamlArgumentValueString("")), + YamlTaintEntityArgument(2u) to YamlTaintConditionNot( + YamlTaintConditionIsType( + YamlArgumentTypeString( + "int" + ) + ) + ), + YamlTaintEntityReturn to YamlTaintConditionOr( + listOf( + YamlTaintConditionEqualValue(YamlArgumentValueLong(0L)), YamlTaintConditionEqualValue( + YamlArgumentValueLong(1L) + ) + ) + ) + ) + ) + + val actualConditions = TaintConditionParser.parseConditions(yamlMap) + assertEquals(expectedConditions, actualConditions) + } + + @Test + fun `should parse empty yaml map as NoConditions`() { + val yamlMap = Yaml.default.parseToYamlNode("{}") + val expectedConditions = YamlNoTaintConditions + + val actualConditions = TaintConditionParser.parseConditions(yamlMap) + assertEquals(expectedConditions, actualConditions) + } + + @Test + fun `should fail on another yaml type`() { + val yamlList = Yaml.default.parseToYamlNode("[]") + + assertThrows { + TaintConditionParser.parseConditions(yamlList) + } + } + } + + @Nested + @DisplayName("parseConditionsKey") + inner class ParseConditionsKeyTest { + @Test + fun `should parse yaml map with a key 'conditions'`() { + val yamlMap = Yaml.default.parseToYamlNode("$k_conditions: { $k_return: null }").yamlMap + val expectedConditions = YamlTaintConditionsMap( + mapOf( + YamlTaintEntityReturn to YamlTaintConditionEqualValue( + YamlArgumentValueNull + ) + ) + ) + + val actualConditions = TaintConditionParser.parseConditionsKey(yamlMap) + assertEquals(expectedConditions, actualConditions) + } + + @Test + fun `should parse yaml map without a key 'conditions' as NoConditions`() { + val yamlMap = Yaml.default.parseToYamlNode("$k_marks: []").yamlMap + val expectedConditions = YamlNoTaintConditions + + val actualConditions = TaintConditionParser.parseConditionsKey(yamlMap) + assertEquals(expectedConditions, actualConditions) + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintConfigurationParserTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintConfigurationParserTest.kt new file mode 100644 index 0000000000..3a57811a41 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintConfigurationParserTest.kt @@ -0,0 +1,54 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.Yaml +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class TaintConfigurationParserTest { + + @Nested + @DisplayName("parseConfiguration") + inner class ParseConfigurationTest { + @Test + fun `should parse yaml map as Configuration`() { + val yamlMap = + Yaml.default.parseToYamlNode("{ $k_sources: [], $k_passes: [], $k_cleaners: [], $k_sinks: [] }") + val expectedConfiguration = YamlTaintConfiguration(listOf(), listOf(), listOf(), listOf()) + + val actualConfiguration = TaintConfigurationParser.parseConfiguration(yamlMap) + assertEquals(expectedConfiguration, actualConfiguration) + } + + @Test + fun `should not fail if yaml map does not contain some keys`() { + val yamlMap = Yaml.default.parseToYamlNode("{ $k_sources: [], $k_sinks: [] }") + val expectedConfiguration = YamlTaintConfiguration(listOf(), listOf(), listOf(), listOf()) + + val actualConfiguration = TaintConfigurationParser.parseConfiguration(yamlMap) + assertEquals(expectedConfiguration, actualConfiguration) + } + + @Test + fun `should fail on other yaml types`() { + val yamlList = Yaml.default.parseToYamlNode("[]") + + assertThrows { + TaintConfigurationParser.parseConfiguration(yamlList) + } + } + + @Test + fun `should fail on yaml map with unknown keys`() { + val yamlMap = Yaml.default.parseToYamlNode( + "{ $k_sources: [], $k_passes: [], $k_cleaners: [], $k_sinks: [], unknown-key: [] }" + ) + + assertThrows { + TaintConfigurationParser.parseConfiguration(yamlMap) + } + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintEntityParserTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintEntityParserTest.kt new file mode 100644 index 0000000000..ab73546696 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintEntityParserTest.kt @@ -0,0 +1,104 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.Yaml +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class TaintEntityParserTest { + + @Nested + @DisplayName("taintEntityByName") + inner class TaintEntityByNameTest { + @Test + fun `should return ThisObject on 'this'`() { + val actualEntity = TaintEntityParser.taintEntityByName(k_this) + val expectedEntity = YamlTaintEntityThis + assertEquals(expectedEntity, actualEntity) + } + + @Test + fun `should return ReturnValue on 'return'`() { + val actualEntity = TaintEntityParser.taintEntityByName(k_return) + val expectedEntity = YamlTaintEntityReturn + assertEquals(expectedEntity, actualEntity) + } + + @Test + fun `should return MethodArgument(1) on 'arg1'`() { + val actualEntity = TaintEntityParser.taintEntityByName("${k_arg}1") + val expectedEntity = YamlTaintEntityArgument(1u) + assertEquals(expectedEntity, actualEntity) + } + + @Test + fun `should return MethodArgument(227) on 'arg227'`() { + val actualEntity = TaintEntityParser.taintEntityByName("${k_arg}227") + val expectedEntity = YamlTaintEntityArgument(227u) + assertEquals(expectedEntity, actualEntity) + } + + @Test + fun `should fail on zero index 'arg0'`() { + assertThrows { + TaintEntityParser.taintEntityByName("${k_arg}0") + } + } + + @Test + fun `should fail on another entity name`() { + assertThrows { + TaintEntityParser.taintEntityByName("argument1") + } + } + } + + @Nested + @DisplayName("parseTaintEntities") + inner class ParseTaintEntitiesTest { + @Test + fun `should parse yaml scalar`() { + val yamlScalar = Yaml.default.parseToYamlNode(k_this) + val expectedEntities = YamlTaintEntitiesSet(setOf(YamlTaintEntityThis)) + + val actualEntities = TaintEntityParser.parseTaintEntities(yamlScalar) + assertEquals(expectedEntities, actualEntities) + } + + @Test + fun `should parse yaml list`() { + val yamlList = Yaml.default.parseToYamlNode("[ $k_this, ${k_arg}1, ${k_arg}5, $k_return ]") + val expectedEntities = YamlTaintEntitiesSet( + setOf( + YamlTaintEntityThis, + YamlTaintEntityArgument(1u), + YamlTaintEntityArgument(5u), + YamlTaintEntityReturn + ) + ) + + val actualEntities = TaintEntityParser.parseTaintEntities(yamlList) + assertEquals(expectedEntities, actualEntities) + } + + @Test + fun `should fail on empty yaml list`() { + val yamlListEmpty = Yaml.default.parseToYamlNode("[]") + + assertThrows { + TaintEntityParser.parseTaintEntities(yamlListEmpty) + } + } + + @Test + fun `should fail on another yaml type`() { + val yamlMap = Yaml.default.parseToYamlNode("$k_addTo: $k_return") + + assertThrows { + TaintEntityParser.parseTaintEntities(yamlMap) + } + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintMarkParserTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintMarkParserTest.kt new file mode 100644 index 0000000000..3be0b44982 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintMarkParserTest.kt @@ -0,0 +1,58 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.Yaml +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class TaintMarkParserTest { + + @Nested + @DisplayName("parseTaintMarks") + inner class ParseTaintMarksTest { + @Test + fun `should parse yaml scalar`() { + val yamlScalar = Yaml.default.parseToYamlNode("sensitive-data") + val expectedMarks = YamlTaintMarksSet(setOf(YamlTaintMark("sensitive-data"))) + + val actualMarks = TaintMarkParser.parseTaintMarks(yamlScalar) + assertEquals(expectedMarks, actualMarks) + } + + @Test + fun `should parse yaml list`() { + val yamlList = Yaml.default.parseToYamlNode("[ xss, sensitive-data, sql-injection ]") + val expectedMarks = + YamlTaintMarksSet( + setOf( + YamlTaintMark("xss"), + YamlTaintMark("sensitive-data"), + YamlTaintMark("sql-injection") + ) + ) + + val actualMarks = TaintMarkParser.parseTaintMarks(yamlList) + assertEquals(expectedMarks, actualMarks) + } + + @Test + fun `should parse empty yaml list`() { + val yamlListEmpty = Yaml.default.parseToYamlNode("[]") + val expectedMarks = YamlTaintMarksAll + + val actualMarks = TaintMarkParser.parseTaintMarks(yamlListEmpty) + assertEquals(expectedMarks, actualMarks) + } + + @Test + fun `should fail on another yaml type`() { + val yamlMap = Yaml.default.parseToYamlNode("$k_marks: [ xss ]") + + assertThrows { + TaintMarkParser.parseTaintMarks(yamlMap) + } + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintRuleParserTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintRuleParserTest.kt new file mode 100644 index 0000000000..e669c27dbd --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintRuleParserTest.kt @@ -0,0 +1,287 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.Yaml +import org.junit.jupiter.api.* +import org.junit.jupiter.api.Assertions.* + +class TaintRuleParserTest { + + @Nested + @DisplayName("isRule") + inner class IsRuleTest { + + private val isRuleData = listOf( + SourceData to TaintRuleParser::isSourceRule, + PassData to TaintRuleParser::isPassRule, + CleanerData to TaintRuleParser::isCleanerRule, + SinkData to TaintRuleParser::isSinkRule + ) + + @TestFactory + fun `should return true on a rule`() = isRuleData.map { (ruleData, isRule) -> + DynamicTest.dynamicTest(isRule.name) { + val yamlMap = Yaml.default.parseToYamlNode(ruleData.yamlInput) + assertTrue(isRule(yamlMap)) + } + } + + @TestFactory + fun `should return true on a rule with optional keys`() = isRuleData.map { (ruleData, isRule) -> + DynamicTest.dynamicTest(isRule.name) { + val yamlMap = Yaml.default.parseToYamlNode(ruleData.yamlInputAdvanced) + assertTrue(isRule(yamlMap)) + } + } + + @TestFactory + fun `should return false on an invalid rule`() = isRuleData.map { (ruleData, isRule) -> + DynamicTest.dynamicTest(isRule.name) { + val yamlMap = Yaml.default.parseToYamlNode(ruleData.yamlInputInvalid) + assertFalse(isRule(yamlMap)) + } + } + + @TestFactory + fun `should return false on rules node`() = isRuleData.map { (ruleData, isRule) -> + DynamicTest.dynamicTest(isRule.name) { + val rulesData = RulesData(ruleData) + val yamlMap = Yaml.default.parseToYamlNode(rulesData.yamlInput) + assertFalse(isRule(yamlMap)) + } + } + } + + @Nested + @DisplayName("parseRule") + inner class ParseRuleTest { + + private val parseRuleData = listOf( + SourceData to TaintRuleParser::parseSourceRule, + PassData to TaintRuleParser::parsePassRule, + CleanerData to TaintRuleParser::parseCleanerRule, + SinkData to TaintRuleParser::parseSinkRule + ) + + @TestFactory + fun `should parse yaml map that satisfies isRule`() = parseRuleData.map { (ruleData, parseRule) -> + DynamicTest.dynamicTest(parseRule.name) { + val yamlMap = Yaml.default.parseToYamlNode(ruleData.yamlInput) + val expectedRule = ruleData.yamlInputParsed(defaultMethodFqn) + + val actualRule = parseRule(yamlMap, defaultMethodNameParts) + assertEquals(expectedRule, actualRule) + } + } + + @TestFactory + fun `should parse yaml map with optional keys that satisfies isRule`() = + parseRuleData.map { (ruleData, parseRule) -> + DynamicTest.dynamicTest(parseRule.name) { + val yamlMap = Yaml.default.parseToYamlNode(ruleData.yamlInputAdvanced) + val expectedRule = ruleData.yamlInputAdvancedParsed(defaultMethodFqn) + + val actualRule = parseRule(yamlMap, defaultMethodNameParts) + assertEquals(expectedRule, actualRule) + } + } + + @TestFactory + fun `should fail on yaml map with unknown keys`() = parseRuleData.map { (ruleData, parseRule) -> + DynamicTest.dynamicTest(parseRule.name) { + val yamlMap = Yaml.default.parseToYamlNode(ruleData.yamlInputUnknownKey) + + assertThrows { + parseRule(yamlMap, defaultMethodNameParts) + } + } + } + } + + @Nested + @DisplayName("parseSources") + inner class ParseSourcesTest { + + private val parseRulesData = listOf( + SourcesData to TaintRuleParser::parseSources, + PassesData to TaintRuleParser::parsePasses, + CleanersData to TaintRuleParser::parseCleaners, + SinksData to TaintRuleParser::parseSinks + ) + + @TestFactory + fun `should parse yaml list with rules`() = parseRulesData.map { (rulesData, parseRules) -> + DynamicTest.dynamicTest(parseRules.name) { + val yamlMap = Yaml.default.parseToYamlNode(rulesData.yamlInput) + val expectedRules = rulesData.yamlInputParsed + + val actualRules = parseRules(yamlMap) + assertEquals(expectedRules, actualRules) + } + } + + @TestFactory + fun `should parse yaml list with rules specified using nested names`() = + parseRulesData.map { (rulesData, parseRules) -> + DynamicTest.dynamicTest(parseRules.name) { + val yamlMap = Yaml.default.parseToYamlNode(rulesData.yamlInputNestedNames) + val expectedRules = rulesData.yamlInputNestedNamesParsed + + val actualRules = parseRules(yamlMap) + assertEquals(expectedRules, actualRules) + } + } + + @TestFactory + fun `should fail on invalid yaml structure`() = parseRulesData.map { (rulesData, parseRules) -> + DynamicTest.dynamicTest(parseRules.name) { + val yamlMap = Yaml.default.parseToYamlNode(rulesData.yamlInputInvalid) + + assertThrows { + parseRules(yamlMap) + } + } + } + } + + // test data + + private val defaultMethodNameParts = listOf("org.example.Server.start") + private val defaultMethodFqn = YamlMethodFqn(listOf("org", "example"), "Server", "start") + + // one rule + + interface RuleData { + val yamlInput: String + val yamlInputAdvanced: String + val yamlInputInvalid: String + val yamlInputUnknownKey: String + + fun yamlInputParsed(methodFqn: YamlMethodFqn): Rule + fun yamlInputAdvancedParsed(methodFqn: YamlMethodFqn): Rule + } + + private object SourceData : RuleData { + override val yamlInput = "{ $k_addTo: $k_return, $k_marks: [] }" + override val yamlInputAdvanced = "{ $k_signature: [], $k_conditions: {}, $k_addTo: $k_return, $k_marks: [] }" + override val yamlInputInvalid = "{ invalid-key: $k_return, $k_marks: [] }" + override val yamlInputUnknownKey = "{ $k_addTo: $k_return, $k_marks: [], unknown-key: [] }" + + override fun yamlInputParsed(methodFqn: YamlMethodFqn) = + YamlTaintSource(methodFqn, YamlTaintEntitiesSet(setOf(YamlTaintEntityReturn)), YamlTaintMarksAll) + + override fun yamlInputAdvancedParsed(methodFqn: YamlMethodFqn) = + YamlTaintSource( + methodFqn, + YamlTaintEntitiesSet(setOf(YamlTaintEntityReturn)), + YamlTaintMarksAll, + YamlTaintSignatureList(listOf()), + YamlNoTaintConditions + ) + } + + private object PassData : RuleData { + override val yamlInput = "{ $k_getFrom: $k_this, $k_addTo: $k_return, $k_marks: [] }" + override val yamlInputAdvanced = + "{ $k_signature: [], $k_conditions: {}, $k_getFrom: $k_this, $k_addTo: $k_return, $k_marks: [] }" + override val yamlInputInvalid = "{ $k_getFrom: $k_this, $k_addTo: $k_return, invalid-key: [] }" + override val yamlInputUnknownKey = "{ $k_getFrom: $k_this, $k_addTo: $k_return, $k_marks: [], unknown-key: [] }" + + override fun yamlInputParsed(methodFqn: YamlMethodFqn) = + YamlTaintPass( + methodFqn, YamlTaintEntitiesSet(setOf(YamlTaintEntityThis)), YamlTaintEntitiesSet( + setOf( + YamlTaintEntityReturn + ) + ), YamlTaintMarksAll + ) + + override fun yamlInputAdvancedParsed(methodFqn: YamlMethodFqn) = + YamlTaintPass( + methodFqn, YamlTaintEntitiesSet(setOf(YamlTaintEntityThis)), YamlTaintEntitiesSet( + setOf( + YamlTaintEntityReturn + ) + ), YamlTaintMarksAll, YamlTaintSignatureList(listOf()), YamlNoTaintConditions + ) + } + + private object CleanerData : RuleData { + override val yamlInput = "{ $k_removeFrom: $k_this, $k_marks: [] }" + override val yamlInputAdvanced = "{ $k_signature: [], $k_conditions: {}, $k_removeFrom: $k_this, $k_marks: [] }" + override val yamlInputInvalid = "{ $k_removeFrom: $k_this, invalid-key: [] }" + override val yamlInputUnknownKey = "{ $k_removeFrom: $k_this, $k_marks: [], unknown-key: [] }" + + override fun yamlInputParsed(methodFqn: YamlMethodFqn) = + YamlTaintCleaner(methodFqn, YamlTaintEntitiesSet(setOf(YamlTaintEntityThis)), YamlTaintMarksAll) + + override fun yamlInputAdvancedParsed(methodFqn: YamlMethodFqn) = + YamlTaintCleaner( + methodFqn, + YamlTaintEntitiesSet(setOf(YamlTaintEntityThis)), + YamlTaintMarksAll, + YamlTaintSignatureList(listOf()), + YamlNoTaintConditions + ) + } + + private object SinkData : RuleData { + override val yamlInput = "{ $k_check: $k_this, $k_marks: [] }" + override val yamlInputAdvanced = "{ $k_signature: [], $k_conditions: {}, $k_check: $k_this, $k_marks: [] }" + override val yamlInputInvalid = "{ $k_check: $k_this, invalid-key: [] }" + override val yamlInputUnknownKey = "{ $k_check: $k_this, $k_marks: [], unknown-key: [] }" + + override fun yamlInputParsed(methodFqn: YamlMethodFqn) = + YamlTaintSink(methodFqn, YamlTaintEntitiesSet(setOf(YamlTaintEntityThis)), YamlTaintMarksAll) + + override fun yamlInputAdvancedParsed(methodFqn: YamlMethodFqn) = + YamlTaintSink( + methodFqn, + YamlTaintEntitiesSet(setOf(YamlTaintEntityThis)), + YamlTaintMarksAll, + YamlTaintSignatureList(listOf()), + YamlNoTaintConditions + ) + } + + // combined rules + + private object SourcesData : RulesData(SourceData) + private object PassesData : RulesData(PassData) + private object CleanersData : RulesData(CleanerData) + private object SinksData : RulesData(SinkData) + + private open class RulesData(ruleData: RuleData) { + val yamlInput = """ + - a.b.c.m: ${ruleData.yamlInput} + - d.e.f.m: ${ruleData.yamlInputAdvanced} + """.trimIndent() + + val yamlInputNestedNames = """ + - a: + - b.c: + - m: ${ruleData.yamlInput} + - m: ${ruleData.yamlInputAdvanced} + - d: + - e.m1: ${ruleData.yamlInput} + - e.m2: ${ruleData.yamlInputAdvanced} + """.trimIndent() + + val yamlInputInvalid = """ + - a: + - b.c: + m: ${ruleData.yamlInput} + """.trimIndent() + + val yamlInputParsed = listOf( + ruleData.yamlInputParsed(YamlMethodFqn(listOf("a", "b"), "c", "m")), + ruleData.yamlInputAdvancedParsed(YamlMethodFqn(listOf("d", "e"), "f", "m")) + ) + + val yamlInputNestedNamesParsed = listOf( + ruleData.yamlInputParsed(YamlMethodFqn(listOf("a", "b"), "c", "m")), + ruleData.yamlInputAdvancedParsed(YamlMethodFqn(listOf("a", "b"), "c", "m")), + ruleData.yamlInputParsed(YamlMethodFqn(listOf("a", "d"), "e", "m1")), + ruleData.yamlInputAdvancedParsed(YamlMethodFqn(listOf("a", "d"), "e", "m2")) + ) + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintSignatureParserTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintSignatureParserTest.kt new file mode 100644 index 0000000000..a1b0513ea2 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintSignatureParserTest.kt @@ -0,0 +1,81 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.Yaml +import com.charleskorn.kaml.yamlMap +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Nested +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class TaintSignatureParserTest { + + @Nested + @DisplayName("parseSignatureKey") + inner class ParseSignatureKeyTest { + @Test + fun `should parse yaml list of the argument types`() { + val yamlList = Yaml.default.parseToYamlNode("[ ${k_lt}int$k_gt, $k__, ${k_lt}java.lang.String$k_gt ]") + val expectedSignature = YamlTaintSignatureList( + listOf( + YamlArgumentTypeString("int"), + YamlArgumentTypeAny, + YamlArgumentTypeString("java.lang.String") + ) + ) + + val actualSignature = TaintSignatureParser.parseSignature(yamlList) + assertEquals(expectedSignature, actualSignature) + } + + @Test + fun `should parse empty yaml list`() { + val yamlList = Yaml.default.parseToYamlNode("[]") + val expectedSignature = YamlTaintSignatureList(listOf()) + + val actualSignature = TaintSignatureParser.parseSignature(yamlList) + assertEquals(expectedSignature, actualSignature) + } + + @Test + fun `should fail on incorrect signature`() { + val yamlList = Yaml.default.parseToYamlNode("[ 0, $k__, 2 ]") + + assertThrows { + TaintSignatureParser.parseSignature(yamlList) + } + } + + @Test + fun `should fail on another yaml type`() { + val yamlMap = Yaml.default.parseToYamlNode("$k_signature: []") + + assertThrows { + TaintSignatureParser.parseSignature(yamlMap) + } + } + } + + @Nested + @DisplayName("parseSignature") + inner class ParseSignatureTest { + @Test + fun `should parse yaml map with a key 'signature'`() { + val yamlList = Yaml.default.parseToYamlNode("$k_signature: [ $k__, $k__, ${k_lt}int$k_gt ]").yamlMap + val expectedSignature = + YamlTaintSignatureList(listOf(YamlArgumentTypeAny, YamlArgumentTypeAny, YamlArgumentTypeString("int"))) + + val actualSignature = TaintSignatureParser.parseSignatureKey(yamlList) + assertEquals(expectedSignature, actualSignature) + } + + @Test + fun `should parse yaml map without a key 'signature' as AnySignature`() { + val yamlMap = Yaml.default.parseToYamlNode("$k_marks: []").yamlMap + val expectedSignature = YamlTaintSignatureAny + + val actualSignature = TaintSignatureParser.parseSignatureKey(yamlMap) + assertEquals(expectedSignature, actualSignature) + } + } +} \ No newline at end of file diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintYamlParserTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintYamlParserTest.kt new file mode 100644 index 0000000000..b49d3c8b51 --- /dev/null +++ b/utbot-framework-test/src/test/kotlin/org/utbot/taint/parser/yaml/TaintYamlParserTest.kt @@ -0,0 +1,148 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.YamlException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +class TaintYamlParserTest { + + @Test + fun `parse should parse correct yaml`() { + val actualConfiguration = TaintYamlParser.parse(yamlInput) + assertEquals(expectedConfiguration, actualConfiguration) + } + + @Test + fun `parse should throw exception on malformed yaml`() { + val malformedYamlInput = yamlInput.replace("{", "") + assertThrows { + TaintYamlParser.parse(malformedYamlInput) + } + } + + @Test + fun `parse should throw exception on incorrect yaml`() { + val incorrectYamlInput = yamlInput.replace(k_not, "net") + assertThrows { + TaintYamlParser.parse(incorrectYamlInput) + } + } + + // test data + + private val yamlInput = """ + $k_sources: + - java.lang.System.getenv: + $k_signature: [ ${k_lt}java.lang.String$k_gt ] + $k_addTo: $k_return + $k_marks: environment + + $k_passes: + - java.lang.String: + - concat: + $k_conditions: + $k_this: { $k_not: "" } + $k_getFrom: $k_this + $k_addTo: $k_return + $k_marks: sensitive-data + - concat: + $k_conditions: + ${k_arg}1: { $k_not: "" } + $k_getFrom: ${k_arg}1 + $k_addTo: $k_return + $k_marks: sensitive-data + + $k_cleaners: + - java.lang.String.isEmpty: + $k_conditions: + $k_return: true + $k_removeFrom: $k_this + $k_marks: [ sql-injection, xss ] + + $k_sinks: + - org.example.util.unsafe: + $k_signature: [ $k__, ${k_lt}java.lang.Integer$k_gt ] + $k_conditions: + ${k_arg}2: 0 + $k_check: ${k_arg}2 + $k_marks: environment + """.trimIndent() + + private val expectedConfiguration = YamlTaintConfiguration( + sources = listOf( + YamlTaintSource( + methodFqn = YamlMethodFqn(listOf("java", "lang"), "System", "getenv"), + addTo = YamlTaintEntitiesSet(setOf(YamlTaintEntityReturn)), + marks = YamlTaintMarksSet(setOf(YamlTaintMark("environment"))), + signature = YamlTaintSignatureList(listOf(YamlArgumentTypeString("java.lang.String"))), + conditions = YamlNoTaintConditions + ) + ), + passes = listOf( + YamlTaintPass( + methodFqn = YamlMethodFqn(listOf("java", "lang"), "String", "concat"), + getFrom = YamlTaintEntitiesSet(setOf(YamlTaintEntityThis)), + addTo = YamlTaintEntitiesSet(setOf(YamlTaintEntityReturn)), + marks = YamlTaintMarksSet(setOf(YamlTaintMark("sensitive-data"))), + signature = YamlTaintSignatureAny, + conditions = YamlTaintConditionsMap( + mapOf( + YamlTaintEntityThis to YamlTaintConditionNot( + YamlTaintConditionEqualValue(YamlArgumentValueString("")) + ) + ) + ) + ), + YamlTaintPass( + methodFqn = YamlMethodFqn(listOf("java", "lang"), "String", "concat"), + getFrom = YamlTaintEntitiesSet(setOf(YamlTaintEntityArgument(1u))), + addTo = YamlTaintEntitiesSet(setOf(YamlTaintEntityReturn)), + marks = YamlTaintMarksSet(setOf(YamlTaintMark("sensitive-data"))), + signature = YamlTaintSignatureAny, + conditions = YamlTaintConditionsMap( + mapOf( + YamlTaintEntityArgument(1u) to YamlTaintConditionNot( + YamlTaintConditionEqualValue(YamlArgumentValueString("")) + ) + ) + ) + ) + ), + cleaners = listOf( + YamlTaintCleaner( + methodFqn = YamlMethodFqn(listOf("java", "lang"), "String", "isEmpty"), + removeFrom = YamlTaintEntitiesSet(setOf(YamlTaintEntityThis)), + marks = YamlTaintMarksSet(setOf(YamlTaintMark("sql-injection"), YamlTaintMark("xss"))), + signature = YamlTaintSignatureAny, + conditions = YamlTaintConditionsMap( + mapOf( + YamlTaintEntityReturn to YamlTaintConditionEqualValue( + YamlArgumentValueBoolean(true) + ) + ) + ) + ) + ), + sinks = listOf( + YamlTaintSink( + methodFqn = YamlMethodFqn(listOf("org", "example"), "util", "unsafe"), + check = YamlTaintEntitiesSet(setOf(YamlTaintEntityArgument(2u))), + marks = YamlTaintMarksSet(setOf(YamlTaintMark("environment"))), + signature = YamlTaintSignatureList( + argumentTypes = listOf( + YamlArgumentTypeAny, + YamlArgumentTypeString("java.lang.Integer") + ) + ), + conditions = YamlTaintConditionsMap( + mapOf( + YamlTaintEntityArgument(2u) to YamlTaintConditionEqualValue( + YamlArgumentValueLong(0L) + ) + ) + ) + ) + ) + ) +} \ No newline at end of file diff --git a/utbot-framework/src/test/resources/junit-platform.properties b/utbot-framework-test/src/test/resources/junit-platform.properties similarity index 100% rename from utbot-framework/src/test/resources/junit-platform.properties rename to utbot-framework-test/src/test/resources/junit-platform.properties diff --git a/utbot-framework-test/src/test/resources/log4j2.xml b/utbot-framework-test/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..ac3d2f2abf --- /dev/null +++ b/utbot-framework-test/src/test/resources/log4j2.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utbot-framework/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/utbot-framework-test/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker similarity index 100% rename from utbot-framework/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker rename to utbot-framework-test/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/utbot-framework/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/utbot-framework-test/src/test/resources/services/org.junit.jupiter.api.extension.Extension similarity index 100% rename from utbot-framework/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension rename to utbot-framework-test/src/test/resources/services/org.junit.jupiter.api.extension.Extension diff --git a/utbot-framework/build.gradle b/utbot-framework/build.gradle index 98a734f37b..c471f88cef 100644 --- a/utbot-framework/build.gradle +++ b/utbot-framework/build.gradle @@ -1,69 +1,56 @@ -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" - -repositories { - flatDir { - dirs 'dist' - } +plugins { + id 'org.jetbrains.kotlin.plugin.serialization' version '1.7.20' } configurations { - z3native + fetchInstrumentationJar } dependencies { - api project(':utbot-core') + api project(':utbot-java-fuzzing') api project(':utbot-instrumentation') api project(':utbot-summary') - implementation 'junit:junit:4.13.1' api project(':utbot-framework-api') + api project(':utbot-rd') + api project(':utbot-modificators-analyzer') - implementation "com.github.UnitTestBot:soot:${soot_commit_hash}" + implementation group: 'com.jetbrains.rd', name: 'rd-framework', version: rdVersion + implementation group: 'com.jetbrains.rd', name: 'rd-core', version: rdVersion - implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: jackson_version - implementation group: 'org.sosy-lab', name: 'javasmt-solver-z3', version: javasmt_solver_z3_version - implementation group: 'com.github.curious-odd-man', name: 'rgxgen', version: rgxgen_version - implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: log4j2_version - implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version - implementation group: 'org.jacoco', name: 'org.jacoco.report', version: jacoco_version - implementation group: 'org.apache.commons', name: 'commons-text', version: apache_commons_text_version + implementation("org.unittestbot.soot:soot-utbot-fork:${sootVersion}") { + exclude group:'com.google.guava', module:'guava' + } + implementation group: 'com.google.guava', name: 'guava', version: guavaVersion + implementation group: 'com.esotericsoftware.kryo', name: 'kryo5', version: kryoVersion + // this is necessary for serialization of some collections + implementation group: 'de.javakaffee', name: 'kryo-serializers', version: kryoSerializersVersion + + implementation group: 'com.charleskorn.kaml', name: 'kaml', version: kamlVersion + implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: jacksonVersion + implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-serialization-cbor', version: kotlinxSerializationVersion + implementation group: 'com.github.curious-odd-man', name: 'rgxgen', version: rgxgenVersion + implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: log4j2Version + implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlinLoggingVersion + implementation group: 'org.jacoco', name: 'org.jacoco.report', version: jacocoVersion + implementation group: 'org.apache.commons', name: 'commons-text', version: apacheCommonsTextVersion // we need this for construction mocks from composite models - implementation group: 'org.mockito', name: 'mockito-core', version: '4.2.0' - api project(':utbot-api') - api project(':utbot-fuzzers') - - testImplementation project(':utbot-summary') - testImplementation project(':utbot-sample') - testImplementation project(':utbot-analytics') + implementation group: 'org.mockito', name: 'mockito-core', version: mockitoVersion - // used for testing code generation - testImplementation group: 'commons-io', name: 'commons-io', version: commons_io_version - testImplementation group: 'junit', name: 'junit', version: junit4_version - testImplementation group: 'org.junit.platform', name: 'junit-platform-console-standalone', version: junit4_platform_version - testImplementation group: 'org.antlr', name: 'antlr4', version: antlr_version - testImplementation group: 'org.mockito', name: 'mockito-core', version: mockito_version - testImplementation group: 'org.testng', name: 'testng', version: testng_version - testImplementation group: 'org.mockito', name: 'mockito-inline', version: mockito_inline_version - testImplementation group: 'com.google.guava', name: 'guava', version: guava_version + // To use JUnit4, comment out JUnit5 and uncomment JUnit4 dependencies here. Please also check "test" section + //implementation group: 'junit', name: 'junit', version: '4.13.1' + implementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.8.1' + implementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.1' - testCompile group: 'org.mockito', name: 'mockito-inline', version: mockito_inline_version - testCompile group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j2_version + // TODO sbft-usvm-merge: UtBot engine expects `com.github.UnitTestBot.ksmt` here + implementation group: 'io.ksmt', name: 'ksmt-core', version: ksmtVersion + implementation group: 'io.ksmt', name: 'ksmt-z3', version: ksmtVersion - z3native group: 'com.microsoft.z3', name: 'z3-native-win64', version: z3_version, ext: 'zip' - z3native group: 'com.microsoft.z3', name: 'z3-native-linux64', version: z3_version, ext: 'zip' - z3native group: 'com.microsoft.z3', name: 'z3-native-osx', version: z3_version, ext: 'zip' + fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration: 'instrumentationArchive') } processResources { - configurations.z3native.resolvedConfiguration.resolvedArtifacts.each { artifact -> - from(zipTree(artifact.getFile())) { - into "lib/x64" - } - } -} - -test { - if (System.getProperty('DEBUG', 'false') == 'true') { - jvmArgs '-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=9009' + from(configurations.fetchInstrumentationJar) { + into "lib" } } diff --git a/utbot-framework/dist/z3-native-linux64-4.8.9.1.zip b/utbot-framework/dist/z3-native-linux64-4.8.9.1.zip deleted file mode 100644 index a5a3ac9e4a..0000000000 Binary files a/utbot-framework/dist/z3-native-linux64-4.8.9.1.zip and /dev/null differ diff --git a/utbot-framework/dist/z3-native-osx-4.8.9.1.zip b/utbot-framework/dist/z3-native-osx-4.8.9.1.zip deleted file mode 100644 index 9b42561fc9..0000000000 Binary files a/utbot-framework/dist/z3-native-osx-4.8.9.1.zip and /dev/null differ diff --git a/utbot-framework/dist/z3-native-win64-4.8.9.1.zip b/utbot-framework/dist/z3-native-win64-4.8.9.1.zip deleted file mode 100644 index 2024be077e..0000000000 Binary files a/utbot-framework/dist/z3-native-win64-4.8.9.1.zip and /dev/null differ diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/AccessController.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/AccessController.java new file mode 100644 index 0000000000..f2cb72f38f --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/AccessController.java @@ -0,0 +1,103 @@ +package org.utbot.engine.overrides; + +import org.utbot.api.annotation.UtClassMock; + +import java.security.AccessControlContext; +import java.security.Permission; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; + +@UtClassMock(target = java.security.AccessController.class, internalUsage = true) +public class AccessController { + public static T doPrivileged(PrivilegedAction action) { + return action.run(); + } + + public static T doPrivilegedWithCombiner(PrivilegedAction action) { + return action.run(); + } + + public static T doPrivileged(PrivilegedAction action, + AccessControlContext ignoredContext) { + return action.run(); + } + + public static T doPrivileged(PrivilegedAction action, + AccessControlContext ignoredContext, Permission... perms) { + if (perms == null) { + throw new NullPointerException(); + } + + for (Permission permission : perms) { + if (permission == null) { + throw new NullPointerException(); + } + } + + return action.run(); + } + + public static T doPrivilegedWithCombiner(PrivilegedAction action, + AccessControlContext context, Permission... perms) { + return doPrivileged(action, context, perms); + } + + public static T doPrivileged(PrivilegedExceptionAction action) throws PrivilegedActionException { + try { + return action.run(); + } catch (RuntimeException e) { + // If the action's run method throws an unchecked exception, it will propagate through this method. + throw e; + } catch (Exception e) { + // Exception is wrapped with the PrivilegedActionException ONLY if this exception is CHECKED + throw new PrivilegedActionException(e); + } + } + + public static T doPrivilegedWithCombiner(PrivilegedExceptionAction action) throws PrivilegedActionException { + return doPrivileged(action); + } + + public static T doPrivileged( + PrivilegedExceptionAction action, + AccessControlContext ignoredContext + ) throws PrivilegedActionException { + return doPrivileged(action); + } + + public static T doPrivileged( + PrivilegedExceptionAction action, + AccessControlContext ignoredContext, + Permission... perms + ) throws PrivilegedActionException { + if (perms == null) { + throw new NullPointerException(); + } + + for (Permission permission : perms) { + if (permission == null) { + throw new NullPointerException(); + } + } + + return doPrivileged(action); + } + + public static T doPrivilegedWithCombiner( + PrivilegedExceptionAction action, + AccessControlContext context, + Permission... perms + ) throws PrivilegedActionException { + return doPrivileged(action, context, perms); + } + + public static void checkPermission(Permission perm) { + if (perm == null) { + throw new NullPointerException(); + } + + // We cannot check real permission do determines whether we should throw an AccessControlException, + // so do nothing more. + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/AutoCloseable.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/AutoCloseable.java new file mode 100644 index 0000000000..6ba7913699 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/AutoCloseable.java @@ -0,0 +1,10 @@ +package org.utbot.engine.overrides; + +import org.utbot.api.annotation.UtClassMock; + +@UtClassMock(target = java.lang.AutoCloseable.class, internalUsage = true) +public interface AutoCloseable { + default void close() throws Exception { + // Do nothing + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/Boolean.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/Boolean.java index 103917f23a..c8c8992642 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/Boolean.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/Boolean.java @@ -4,7 +4,7 @@ @UtClassMock(target = java.lang.Boolean.class, internalUsage = true) public class Boolean { - @SuppressWarnings({"UnnecessaryBoxing", "BooleanConstructorCall", "unused"}) + @SuppressWarnings({"UnnecessaryBoxing", "unused", "deprecation"}) public static java.lang.Boolean valueOf(boolean x) { return new java.lang.Boolean(x); } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/Byte.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/Byte.java index 2b34640b25..717e2c4f2b 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/Byte.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/Byte.java @@ -1,10 +1,10 @@ package org.utbot.engine.overrides; import org.utbot.api.annotation.UtClassMock; -import org.utbot.engine.overrides.strings.UtNativeString; -import org.utbot.engine.overrides.strings.UtString; import org.utbot.engine.overrides.strings.UtStringBuilder; +import java.util.Arrays; + import static org.utbot.api.mock.UtMock.assume; import static org.utbot.engine.overrides.UtLogicMock.ite; import static org.utbot.api.mock.UtMock.assumeOrExecuteConcretely; @@ -13,7 +13,7 @@ @UtClassMock(target = java.lang.Byte.class, internalUsage = true) public class Byte { - @SuppressWarnings({"UnnecessaryBoxing", "unused"}) + @SuppressWarnings({"UnnecessaryBoxing", "unused", "deprecation"}) public static java.lang.Byte valueOf(byte x) { return new java.lang.Byte(x); } @@ -48,17 +48,39 @@ public static byte parseByte(String s, int radix) { @SuppressWarnings("ConstantConditions") public static String toString(byte b) { - // condition = b < 0 - boolean condition = less(b, (byte) 0); + if (b == -128) { + return "-128"; + } + + if (b == 0) { + return "0"; + } + // assumes are placed here to limit search space of solver // and reduce time of solving queries with bv2int expressions - assume(b < 128); - assume(b >= -128); - // prefix = condition ? "-" : "" - String prefix = ite(condition, "-", ""); - UtStringBuilder sb = new UtStringBuilder(prefix); - // value = condition ? -i : i - int value = ite(condition, (byte) -b, b); - return sb.append(new UtString(new UtNativeString(value)).toStringImpl()).toString(); + assume(b <= 127); + assume(b > -128); + assume(b != 0); + + // isNegative = b < 0 + boolean isNegative = less(b, (byte) 0); + String prefix = ite(isNegative, "-", ""); + + int value = ite(isNegative, (byte) -b, b); + char[] reversed = new char[3]; + int offset = 0; + while (value > 0) { + reversed[offset] = (char) ('0' + (value % 10)); + value /= 10; + offset++; + } + + char[] buffer = new char[offset]; + int counter = 0; + while (offset > 0) { + offset--; + buffer[counter++] = reversed[offset]; + } + return prefix + new String(buffer); } } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/Character.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/Character.java index df627c67e0..41266c2242 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/Character.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/Character.java @@ -4,7 +4,7 @@ @UtClassMock(target = java.lang.Character.class, internalUsage = true) public class Character { - @SuppressWarnings({"UnnecessaryBoxing", "unused"}) + @SuppressWarnings({"UnnecessaryBoxing", "unused", "deprecation"}) public static java.lang.Character valueOf(char x) { return new java.lang.Character(x); } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/Class.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/Class.java index 5f472feb60..53c3b0a797 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/Class.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/Class.java @@ -7,4 +7,14 @@ public class Class { public static boolean desiredAssertionStatus() { return true; } + + private void checkMemberAccess(SecurityManager sm, int which, + java.lang.Class caller, boolean checkProxyInterfaces) { + // Do nothing to allow everything + } + + private void checkPackageAccess(SecurityManager sm, final ClassLoader ccl, + boolean checkProxyInterfaces) { + // Do nothing to allow everything + } } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/Integer.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/Integer.java index 1142f01606..34d936364c 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/Integer.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/Integer.java @@ -1,10 +1,9 @@ package org.utbot.engine.overrides; import org.utbot.api.annotation.UtClassMock; -import org.utbot.engine.overrides.strings.UtNativeString; -import org.utbot.engine.overrides.strings.UtString; import org.utbot.engine.overrides.strings.UtStringBuilder; +import static org.utbot.api.mock.UtMock.assume; import static org.utbot.api.mock.UtMock.assumeOrExecuteConcretely; import static org.utbot.engine.overrides.UtLogicMock.ite; import static org.utbot.engine.overrides.UtLogicMock.less; @@ -12,7 +11,7 @@ @UtClassMock(target = java.lang.Integer.class, internalUsage = true) public class Integer { - @SuppressWarnings({"UnnecessaryBoxing", "unused"}) + @SuppressWarnings({"UnnecessaryBoxing", "unused", "deprecation"}) public static java.lang.Integer valueOf(int x) { return new java.lang.Integer(x); } @@ -71,21 +70,35 @@ public static int parseUnsignedInt(String s, int radix) { } } + @SuppressWarnings("ConstantConditions") public static String toString(int i) { - if (i == 0x80000000) { // java.lang.MIN_VALUE + if (i == 0x80000000) { // java.lang.Integer.MIN_VALUE return "-2147483648"; } - // assumes are placed here to limit search space of solver - // and reduce time of solving queries with bv2int expressions - assumeOrExecuteConcretely(i <= 0x8000); - assumeOrExecuteConcretely(i >= -0x8000); - // condition = i < 0 - boolean condition = less(i, 0); - // prefix = condition ? "-" : "" - String prefix = ite(condition, "-", ""); - UtStringBuilder sb = new UtStringBuilder(prefix); - // value = condition ? -i : i - int value = ite(condition, -i, i); - return sb.append(new UtString(new UtNativeString(value)).toStringImpl()).toString(); + + if (i == 0) { + return "0"; + } + + // isNegative = i < 0 + boolean isNegative = less(i, 0); + String prefix = ite(isNegative, "-", ""); + + int value = ite(isNegative, -i, i); + char[] reversed = new char[10]; + int offset = 0; + while (value > 0) { + reversed[offset] = (char) ('0' + (value % 10)); + value /= 10; + offset++; + } + + char[] buffer = new char[offset]; + int counter = 0; + while (offset > 0) { + offset--; + buffer[counter++] = reversed[offset]; + } + return prefix + new String(buffer); } } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/Long.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/Long.java index 4ee5283716..2128115279 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/Long.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/Long.java @@ -1,10 +1,9 @@ package org.utbot.engine.overrides; import org.utbot.api.annotation.UtClassMock; -import org.utbot.engine.overrides.strings.UtNativeString; -import org.utbot.engine.overrides.strings.UtString; import org.utbot.engine.overrides.strings.UtStringBuilder; +import static org.utbot.api.mock.UtMock.assume; import static org.utbot.api.mock.UtMock.assumeOrExecuteConcretely; import static org.utbot.engine.overrides.UtLogicMock.ite; import static org.utbot.engine.overrides.UtLogicMock.less; @@ -12,7 +11,7 @@ @UtClassMock(target = java.lang.Long.class, internalUsage = true) public class Long { - @SuppressWarnings({"UnnecessaryBoxing", "unused"}) + @SuppressWarnings({"UnnecessaryBoxing", "unused", "deprecation"}) public static java.lang.Long valueOf(long x) { return new java.lang.Long(x); } @@ -71,21 +70,35 @@ public static long parseUnsignedLong(String s, int radix) { } } + @SuppressWarnings("ConstantConditions") public static String toString(long l) { if (l == 0x8000000000000000L) { // java.lang.Long.MIN_VALUE return "-9223372036854775808"; } - // assumes are placed here to limit search space of solver - // and reduce time of solving queries with bv2int expressions - assumeOrExecuteConcretely(l <= 10000); - assumeOrExecuteConcretely(l >= -10000); - // condition = l < 0 - boolean condition = less(l, 0); - // prefix = condition ? "-" : "" - String prefix = ite(condition, "-", ""); - UtStringBuilder sb = new UtStringBuilder(prefix); - // value = condition ? -l : l - long value = ite(condition, -l, l); - return sb.append(new UtString(new UtNativeString(value)).toStringImpl()).toString(); + + if (l == 0) { + return "0"; + } + + // isNegative = i < 0 + boolean isNegative = less(l, 0); + String prefix = ite(isNegative, "-", ""); + + long value = ite(isNegative, -l, l); + char[] reversed = new char[19]; + int offset = 0; + while (value > 0) { + reversed[offset] = (char) ('0' + (value % 10)); + value /= 10; + offset++; + } + + char[] buffer = new char[offset]; + int counter = 0; + while (offset > 0) { + offset--; + buffer[counter++] = reversed[offset]; + } + return prefix + new String(buffer); } } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/Short.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/Short.java index d8b47fbaf3..d64533bb78 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/Short.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/Short.java @@ -1,10 +1,9 @@ package org.utbot.engine.overrides; import org.utbot.api.annotation.UtClassMock; -import org.utbot.engine.overrides.strings.UtNativeString; -import org.utbot.engine.overrides.strings.UtString; import org.utbot.engine.overrides.strings.UtStringBuilder; +import static org.utbot.api.mock.UtMock.assume; import static org.utbot.api.mock.UtMock.assumeOrExecuteConcretely; import static org.utbot.engine.overrides.UtLogicMock.ite; import static org.utbot.engine.overrides.UtLogicMock.less; @@ -12,7 +11,7 @@ @UtClassMock(target = java.lang.Short.class, internalUsage = true) public class Short { - @SuppressWarnings({"UnnecessaryBoxing", "unused"}) + @SuppressWarnings({"UnnecessaryBoxing", "unused", "deprecation"}) public static java.lang.Short valueOf(short x) { return new java.lang.Short(x); } @@ -45,18 +44,41 @@ public static java.lang.Short parseShort(String s, int radix) { } } + @SuppressWarnings("ConstantConditions") public static String toString(short s) { - // condition = s < 0 - boolean condition = less(s, (short) 0); + if (s == -32768) { + return "-32768"; + } + + if (s == 0) { + return "0"; + } + // assumes are placed here to limit search space of solver // and reduce time of solving queries with bv2int expressions - assumeOrExecuteConcretely(s <= 10000); - assumeOrExecuteConcretely(s >= -10000); - // prefix = condition ? "-" : "" - String prefix = ite(condition, "-", ""); - UtStringBuilder sb = new UtStringBuilder(prefix); - // value = condition ? -i : i - int value = ite(condition, (short)-s, s); - return sb.append(new UtString(new UtNativeString(value)).toStringImpl()).toString(); + assume(s <= 32767); + assume(s > -32768); + assume(s != 0); + + // isNegative = s < 0 + boolean isNegative = less(s, (short) 0); + String prefix = ite(isNegative, "-", ""); + + int value = ite(isNegative, (short) -s, s); + char[] reversed = new char[5]; + int offset = 0; + while (value > 0) { + reversed[offset] = (char) ('0' + (value % 10)); + value /= 10; + offset++; + } + + char[] buffer = new char[offset]; + int counter = 0; + while (offset > 0) { + offset--; + buffer[counter++] = reversed[offset]; + } + return prefix + new String(buffer); } } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/System.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/System.java index fb37ceed9e..f34ddd851b 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/System.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/System.java @@ -210,7 +210,7 @@ public static void arraycopy(Object src, int srcPos, Object dest, int destPos, i } UtArrayMock.arraycopy(srcArray, srcPos, destArray, destPos, length); - } else { + } else if (src instanceof Object[]) { if (!(dest instanceof Object[])) { throw new ArrayStoreException(); } @@ -223,6 +223,9 @@ public static void arraycopy(Object src, int srcPos, Object dest, int destPos, i } UtArrayMock.arraycopy(srcArray, srcPos, destArray, destPos, length); + } else { + // From docs: if the src argument refers to an object that is not an array, an ArrayStoreException will be thrown + throw new ArrayStoreException(); } } } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/Throwable.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/Throwable.java new file mode 100644 index 0000000000..9c51eade7f --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/Throwable.java @@ -0,0 +1,31 @@ +package org.utbot.engine.overrides; + +import org.utbot.api.annotation.UtClassMock; +import org.utbot.api.mock.UtMock; + +@UtClassMock(target = java.lang.Throwable.class, internalUsage = true) +public class Throwable { + public void printStackTrace() { + // Do nothing + } + + public synchronized java.lang.Throwable fillInStackTrace() { + return UtMock.makeSymbolic(); + } + + public StackTraceElement[] getStackTrace() { + return UtMock.makeSymbolic(); + } + + public void setStackTrace(StackTraceElement[] stackTrace) { + // Do nothing + } + + public final synchronized void addSuppressed(java.lang.Throwable exception) { + // Do nothing + } + + public final synchronized java.lang.Throwable[] getSuppressed() { + return UtMock.makeSymbolic(); + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java index 4fa2d34b2c..eb88456a6e 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtArrayMock.java @@ -5,7 +5,7 @@ /** * Auxiliary class with static methods without implementation. - * These static methods are just markers for UtBotSymbolicEngine, + * These static methods are just markers for {@link org.utbot.engine.Traverser}., * to do some corresponding behavior, that can't be represent * with java instructions. *

@@ -15,7 +15,7 @@ @SuppressWarnings("unused") public class UtArrayMock { /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of {@link java.util.Arrays#copyOf(Object[], int)} * if length is less or equals to src.length, otherwise first * src.length elements are equal to src, but the rest are undefined. @@ -45,7 +45,7 @@ public static char[] copyOf(char[] src, int length) { } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -67,7 +67,7 @@ public static void arraycopy(Object[] src, int srcPos, Object[] dst, int destPos } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -83,7 +83,7 @@ public static void arraycopy(boolean[] src, int srcPos, boolean[] dst, int destP } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -99,7 +99,7 @@ public static void arraycopy(byte[] src, int srcPos, byte[] dst, int destPos, in } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -115,7 +115,7 @@ public static void arraycopy(char[] src, int srcPos, char[] dst, int destPos, in } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -131,7 +131,7 @@ public static void arraycopy(short[] src, int srcPos, short[] dst, int destPos, } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -147,7 +147,7 @@ public static void arraycopy(int[] src, int srcPos, int[] dst, int destPos, int } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -163,7 +163,7 @@ public static void arraycopy(long[] src, int srcPos, long[] dst, int destPos, in } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. @@ -179,7 +179,7 @@ public static void arraycopy(float[] src, int srcPos, float[] dst, int destPos, } /** - * Traversing this instruction by UtBotSymbolicEngine should + * Traversing this instruction by {@link org.utbot.engine.Traverser} should * behave similar to call of * {@link java.lang.System#arraycopy(Object, int, Object, int, int)} * if all the arguments are valid. diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtLogicMock.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtLogicMock.java index 550f09803c..79bccfe301 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtLogicMock.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtLogicMock.java @@ -2,7 +2,7 @@ /** * Auxiliary class with static methods without implementation. - * These static methods are just markers for UtBotSymbolicEngine, + * These static methods are just markers for {@link org.utbot.engine.Traverser}, * to do some corresponding behavior, that can be represented with smt expressions. *

* UtLogicMock is used to store bool smt bool expressions in diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtOverrideMock.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtOverrideMock.java index 4a0e1b863f..2a016f12b7 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/UtOverrideMock.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/UtOverrideMock.java @@ -2,13 +2,13 @@ /** * Auxiliary class with static methods without implementation. - * These static methods are just markers for UtBotSymbolicEngine, + * These static methods are just markers for {@link org.utbot.engine.Traverser}, * to do some corresponding behavior, that can't be represent * with java instructions. * * Set of methods in UtOverrideMock is used in code of classes, * that override implementation of some standard class by new implementation, - * that is more simple for UtBotSymbolicEngine to traverse. + * that is more simple for {@link org.utbot.engine.Traverser} to traverse. */ @SuppressWarnings("unused") public class UtOverrideMock { @@ -25,7 +25,7 @@ public static boolean alreadyVisited(Object o) { } /** - * If UtBotSymbolicEngine meets invoke of this method in code, + * If {@link org.utbot.engine.Traverser} meets invoke of this method in code, * then it marks the address of object o in memory as visited * and creates new MemoryUpdate with parameter isVisited, equal to o.addr * @param o parameter, that need to be marked as visited. @@ -34,7 +34,7 @@ public static void visit(Object o) { } /** - * If UtBotSymbolicEngine meets invoke of this method in code, + * If {@link org.utbot.engine.Traverser} meets invoke of this method in code, * then it marks the method, where met instruction is placed, * and all the methods that will be traversed in nested invokes * as methods that couldn't throw exceptions. @@ -44,7 +44,7 @@ public static void doesntThrow() { } /** - * If UtBotSymbolicEngine meets invoke of this method in code, + * If {@link org.utbot.engine.Traverser} meets invoke of this method in code, * then it assumes that the specified object is parameter, * and need to be marked as parameter. * As address space of parameters in engine is non-positive, while @@ -63,7 +63,7 @@ public static void parameter(Object[] objects) { } /** - * If UtBotSymbolicEngine meets invoke of this method in code, + * If {@link org.utbot.engine.Traverser} meets invoke of this method in code, * then it starts concrete execution from this point. */ public static void executeConcretely() { diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/Collection.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/Collection.java index 8f7edebe37..d2f22ba15c 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/Collection.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/Collection.java @@ -1,6 +1,7 @@ package org.utbot.engine.overrides.collections; import org.utbot.api.annotation.UtClassMock; +import org.utbot.api.mock.UtMock; import org.utbot.engine.overrides.stream.UtStream; import java.util.stream.Stream; @@ -9,19 +10,17 @@ public interface Collection extends java.util.Collection { @SuppressWarnings("unchecked") @Override - default Stream parallelStream() { + default Stream stream() { Object[] data = toArray(); + UtMock.disableClassCastExceptionCheck(data); + int size = data.length; return new UtStream<>((E[]) data, size); } - @SuppressWarnings("unchecked") @Override - default Stream stream() { - Object[] data = toArray(); - int size = data.length; - - return new UtStream<>((E[]) data, size); + default Stream parallelStream() { + return stream(); } } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/RangeModifiableUnlimitedArray.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/RangeModifiableUnlimitedArray.java index ac8a2bfac0..54ee0e4cd2 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/RangeModifiableUnlimitedArray.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/RangeModifiableUnlimitedArray.java @@ -1,5 +1,7 @@ package org.utbot.engine.overrides.collections; +import org.utbot.api.mock.UtMock; + /** * Interface shows API for UtExpressions of infinite modifiable array. *

@@ -115,6 +117,14 @@ public Object[] toArray(int offset, int length) { return null; } + @SuppressWarnings("unchecked") + public T[] toCastedArray(int offset, int length) { + final Object[] toArray = toArray(offset, length); + UtMock.disableClassCastExceptionCheck(toArray); + + return (T[]) toArray; + } + /** * set specified value to the element with specified index in array. *

@@ -141,4 +151,17 @@ public void set(int index, E value) { public E get(int i) { return null; } + + /** + * Returns the element of this array on specified index without check for ClassCastException. + * + * @param i - index in list with element, that needs to be returned + */ + @SuppressWarnings({"unchecked", "CastCanBeRemovedNarrowingVariableType"}) + public E getWithoutClassCastExceptionCheck(int i) { + final Object object = get(i); + UtMock.disableClassCastExceptionCheck(object); + + return (E) object; + } } \ No newline at end of file diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java index 5db94b3fb7..818526ab4d 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtArrayList.java @@ -28,7 +28,7 @@ /** - * Class represents hybrid implementation (java + engine instructions) of List interface for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of List interface for {@link org.utbot.engine.Traverser}. *

* Implementation is based on org.utbot.engine.overrides.collections.RangeModifiableArray. * Should behave similar to {@link java.util.ArrayList}. @@ -322,7 +322,7 @@ public int lastIndexOf(Object o) { @Override public Iterator iterator() { preconditionCheck(); - return new UtArrayListIterator(0); + return new UtArrayListSimpleIterator(0); } @NotNull @@ -372,26 +372,19 @@ public void replaceAll(UnaryOperator operator) { } } - @SuppressWarnings("unchecked") @Override public Stream stream() { preconditionCheck(); int size = elementData.end; - Object[] data = elementData.toArray(0, size); + E[] data = elementData.toCastedArray(0, size); - return new UtStream<>((E[]) data, size); + return new UtStream<>(data, size); } - @SuppressWarnings("unchecked") @Override public Stream parallelStream() { - preconditionCheck(); - - int size = elementData.end; - Object[] data = elementData.toArray(0, size); - - return new UtStream<>((E[]) data, size); + return stream(); } /** @@ -412,6 +405,43 @@ public List subList(int fromIndex, int toIndex) { return this.toList().subList(fromIndex, toIndex); } + public class UtArrayListSimpleIterator implements Iterator { + int index; + int prevIndex = -1; + + UtArrayListSimpleIterator(int index) { + rangeCheckForAdd(index); + this.index = index; + } + + @Override + public boolean hasNext() { + preconditionCheck(); + return index != elementData.end; + } + + @Override + public E next() { + preconditionCheck(); + if (index == elementData.end) { + throw new NoSuchElementException(); + } + prevIndex = index; + return elementData.get(index++); + } + + @Override + public void remove() { + preconditionCheck(); + if (prevIndex == -1) { + throw new IllegalStateException(); + } + elementData.end--; + elementData.remove(prevIndex); + prevIndex = -1; + } + } + public class UtArrayListIterator implements ListIterator { int index; int prevIndex = -1; diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtGenericStorage.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtGenericStorage.java index 007a69e64a..0ae53a2c53 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtGenericStorage.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtGenericStorage.java @@ -10,4 +10,10 @@ public interface UtGenericStorage { @SuppressWarnings("unused") default void setEqualGenericType(RangeModifiableUnlimitedArray elements) {} + /** + * Auxiliary method that tells engine to add constraint, that binds type parameter of this storage + * to the type of the specified object value. + */ + @SuppressWarnings("unused") + default void setGenericTypeToTypeOfValue(RangeModifiableUnlimitedArray array, E value) {} } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java index 57aba7f5c4..97efe821d9 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashMap.java @@ -23,7 +23,7 @@ /** - * Class represents hybrid implementation (java + engine instructions) of Map interface for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of Map interface for {@link org.utbot.engine.Traverser}. *

* Implementation is based on using org.utbot.engine.overrides.collections.RangeModifiableArray as keySet * and org.utbot.engine.overrides.collections.UtArray as associative array from keys to values. @@ -102,6 +102,12 @@ void preconditionCheck() { parameter(keys); parameter(keys.storage); + // Following three instructions are required to avoid possible aliasing + // between nested arrays + assume(keys.storage != values.storage); + assume(keys.storage != values.touched); + assume(values.storage != values.touched); + assume(values.size == keys.end); assume(values.touched.length == keys.end); doesntThrow(); @@ -205,11 +211,18 @@ public V put(K key, V value) { if (index == -1) { oldValue = null; keys.set(keys.end++, key); + values.store(key, value); } else { - // newKey equals to oldKey so we can use it instead - oldValue = values.select(key); + K oldKey = keys.get(index); + oldValue = values.select(oldKey); + values.store(oldKey, value); } - values.store(key, value); + + // Avoid optimization here. Despite the fact that old key is equal + // to a new one in case of `index != -1`, it is important to have + // this connection between `index`, `key`, `oldKey` and `value`. + // So, do not rewrite it with `values.store(key, value)` extracted from the if instruction + return oldValue; } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java index 69dd700e22..7f0675d28f 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtHashSet.java @@ -20,7 +20,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of Set interface for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of Set interface for {@link org.utbot.engine.Traverser}. *

* Implementation is based on RangedModifiableArray, and all operations are linear. * Should behave similar to @@ -266,26 +266,19 @@ public Iterator iterator() { return new UtHashSetIterator(); } - @SuppressWarnings("unchecked") @Override public Stream stream() { preconditionCheck(); int size = elementData.end; - Object[] data = elementData.toArray(0, size); + E[] data = elementData.toCastedArray(0, size); - return new UtStream<>((E[]) data, size); + return new UtStream<>(data, size); } - @SuppressWarnings("unchecked") @Override public Stream parallelStream() { - preconditionCheck(); - - int size = elementData.end; - Object[] data = elementData.toArray(0, size); - - return new UtStream<>((E[]) data, size); + return stream(); } public class UtHashSetIterator implements Iterator { diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedList.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedList.java index 55a577cb52..986b043262 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedList.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedList.java @@ -72,7 +72,7 @@ public UtLinkedList(Collection c) { *

  • elementData is marked as parameter
  • *
  • elementData.storage and it's elements are marked as parameters
  • */ - private void preconditionCheck() { + protected void preconditionCheck() { if (alreadyVisited(this)) { return; } @@ -88,13 +88,13 @@ private void preconditionCheck() { visit(this); } - private void rangeCheck(int index) { + protected void rangeCheck(int index) { if (index < 0 || index >= elementData.end - elementData.begin) { throw new IndexOutOfBoundsException(); } } - private void rangeCheckForAdd(int index) { + protected void rangeCheckForAdd(int index) { if (index < 0 || index > elementData.end - elementData.begin) { throw new IndexOutOfBoundsException(); } @@ -435,6 +435,8 @@ public List subList(int fromIndex, int toIndex) { @Override public Iterator iterator() { preconditionCheck(); + + // Some implementations of `iterator` return an instance of ListIterator return new UtLinkedListIterator(elementData.begin); } @@ -449,37 +451,30 @@ public ListIterator listIterator() { @Override public Iterator descendingIterator() { preconditionCheck(); - return new ReverseIteratorWrapper(elementData.end); + return new UtReverseIterator(elementData.end); } - @SuppressWarnings("unchecked") @Override public Stream stream() { preconditionCheck(); int size = elementData.end; - Object[] data = elementData.toArray(0, size); + E[] data = elementData.toCastedArray(0, size); - return new UtStream<>((E[]) data, size); + return new UtStream<>(data, size); } - @SuppressWarnings("unchecked") @Override public Stream parallelStream() { - preconditionCheck(); - - int size = elementData.end; - Object[] data = elementData.toArray(0, size); - - return new UtStream<>((E[]) data, size); + return stream(); } - public class ReverseIteratorWrapper implements ListIterator { + public class UtReverseIterator implements ListIterator { int index; int prevIndex = -1; - ReverseIteratorWrapper(int index) { + UtReverseIterator(int index) { this.index = index; } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedListWithNullableCheck.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedListWithNullableCheck.java new file mode 100644 index 0000000000..4def825276 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtLinkedListWithNullableCheck.java @@ -0,0 +1,122 @@ +package org.utbot.engine.overrides.collections; + +import java.util.Collection; + +/** + * This list forbids inserting null elements to support some implementations of {@link java.util.Deque} like + * {@link java.util.ArrayDeque}. + * + * TODO: Support super calls in inherited wrappers + * + * @see UtLinkedList + * @param + */ +public class UtLinkedListWithNullableCheck extends UtLinkedList { + @SuppressWarnings("unused") + UtLinkedListWithNullableCheck(RangeModifiableUnlimitedArray elementData, int fromIndex, int toIndex) { + super(elementData, fromIndex, toIndex); + for (int i = elementData.begin; i < elementData.end; i++) { + if (elementData.get(i) == null) { + throw new NullPointerException(); + } + } + } + + @SuppressWarnings("unused") + public UtLinkedListWithNullableCheck() { + super(); + } + + @SuppressWarnings("unused") + public UtLinkedListWithNullableCheck(Collection c) { + super(c); + } + + @Override + public E set(int index, E element) { + if (element == null) { + throw new NullPointerException(); + } + preconditionCheck(); + rangeCheck(index); + E oldElement = elementData.get(index + elementData.begin); + elementData.set(index + elementData.begin, element); + return oldElement; + } + + @Override + public void addFirst(E e) { + if (e == null) { + throw new NullPointerException(); + } + preconditionCheck(); + elementData.set(--elementData.begin, e); + } + + @Override + public void addLast(E e) { + if (e == null) { + throw new NullPointerException(); + } + preconditionCheck(); + elementData.set(elementData.end++, e); + } + + @Override + public boolean offerFirst(E e) { + if (e == null) { + throw new NullPointerException(); + } + preconditionCheck(); + elementData.set(--elementData.begin, e); + return true; + } + + @Override + public boolean offerLast(E e) { + if (e == null) { + throw new NullPointerException(); + } + preconditionCheck(); + elementData.set(elementData.end++, e); + return true; + } + + @Override + public void add(int index, E element) { + if (element == null) { + throw new NullPointerException(); + } + preconditionCheck(); + rangeCheckForAdd(index); + elementData.end++; + elementData.insert(index + elementData.begin, element); + } + + @Override + public boolean addAll(Collection c) { + for (Object elem : c.toArray()) { + if (elem == null) { + throw new NullPointerException(); + } + } + preconditionCheck(); + elementData.setRange(elementData.end, c.toArray(), 0, c.size()); + elementData.end += c.size(); + return true; + } + + @Override + public boolean addAll(int index, Collection c) { + for (Object elem : c.toArray()) { + if (elem == null) { + throw new NullPointerException(); + } + } + preconditionCheck(); + rangeCheckForAdd(index); + elementData.insertRange(index + elementData.begin, c.toArray(), 0, c.size()); + elementData.end += c.size(); + return true; + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptional.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptional.java index 3fbe639a74..7fca5dbf49 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptional.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptional.java @@ -13,7 +13,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of Optional for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of Optional for {@link org.utbot.engine.Traverser}. *

    * Should behave the same as {@link java.util.Optional}. * @see org.utbot.engine.OptionalWrapper @@ -28,7 +28,8 @@ public UtOptional() { } public UtOptional(T value) { - Objects.requireNonNull(value); + // In different Java versions Optional.EMPTY is created differently - using the empty constructor in Java 8 and 11, + // and with this constructor in Java 17. It means we cannot use `requireNonNull` here because it fails for Java 17. visit(this); this.value = value; } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalDouble.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalDouble.java index 7dba6bdd45..99b2d4d6c5 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalDouble.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalDouble.java @@ -11,7 +11,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of OptionalDouble for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of OptionalDouble for {@link org.utbot.engine.Traverser}. *

    * Should behave the same as {@link java.util.OptionalDouble}. * @see org.utbot.engine.OptionalWrapper diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalInt.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalInt.java index bf6c5757e7..d88c65c0ad 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalInt.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalInt.java @@ -11,7 +11,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of OptionalInt for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of OptionalInt for {@link org.utbot.engine.Traverser}. *

    * Should behave the same as {@link java.util.OptionalInt}. * @see org.utbot.engine.OptionalWrapper diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalLong.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalLong.java index 534eae3eba..34d3282907 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalLong.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/collections/UtOptionalLong.java @@ -10,7 +10,7 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; /** - * Class represents hybrid implementation (java + engine instructions) of Optional for UtBotSymbolicEngine. + * Class represents hybrid implementation (java + engine instructions) of Optional for {@link org.utbot.engine.Traverser}. *

    * Should behave the same as {@link java.util.Optional}. * @see org.utbot.engine.OptionalWrapper diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/security/UtSecurityManager.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/security/UtSecurityManager.java new file mode 100644 index 0000000000..c703789a7c --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/security/UtSecurityManager.java @@ -0,0 +1,22 @@ +package org.utbot.engine.overrides.security; + +import org.utbot.api.mock.UtMock; + +import java.security.Permission; + +/** + * Overridden implementation for [java.lang.SecurityManager] class + */ +public class UtSecurityManager { + public void checkPermission(Permission perm) { + // Do nothing to allow everything + } + + public void checkPackageAccess(String pkg) { + // Do nothing to allow everything + } + + public ThreadGroup getThreadGroup() { + return new ThreadGroup(UtMock.makeSymbolic()); + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Arrays.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Arrays.java index 9534452ff9..90566e068c 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Arrays.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Arrays.java @@ -4,6 +4,9 @@ import org.utbot.engine.overrides.collections.UtArrayList; import java.util.List; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; import java.util.stream.Stream; @UtClassMock(target = java.util.Arrays.class, internalUsage = true) @@ -18,12 +21,58 @@ public static Stream stream(T[] array, int startInclusive, int endExclusi return new UtStream<>(array, startInclusive, endExclusive); } + // from docs - array is assumed to be unmodified during use + public static IntStream stream(int[] array, int startInclusive, int endExclusive) { + int size = array.length; + + if (startInclusive < 0 || endExclusive < startInclusive || endExclusive > size) { + throw new ArrayIndexOutOfBoundsException(); + } + + Integer[] data = new Integer[size]; + for (int i = 0; i < size; i++) { + data[i] = array[i]; + } + + return new UtIntStream(data, startInclusive, endExclusive); + } + + // from docs - array is assumed to be unmodified during use + public static LongStream stream(long[] array, int startInclusive, int endExclusive) { + int size = array.length; + + if (startInclusive < 0 || endExclusive < startInclusive || endExclusive > size) { + throw new ArrayIndexOutOfBoundsException(); + } + + Long[] data = new Long[size]; + for (int i = 0; i < size; i++) { + data[i] = array[i]; + } + + return new UtLongStream(data, startInclusive, endExclusive); + } + + // from docs - array is assumed to be unmodified during use + public static DoubleStream stream(double[] array, int startInclusive, int endExclusive) { + int size = array.length; + + if (startInclusive < 0 || endExclusive < startInclusive || endExclusive > size) { + throw new ArrayIndexOutOfBoundsException(); + } + + Double[] data = new Double[size]; + for (int i = 0; i < size; i++) { + data[i] = array[i]; + } + + return new UtDoubleStream(data, startInclusive, endExclusive); + } + @SuppressWarnings({"unused", "varargs"}) @SafeVarargs public static List asList(T... a) { // TODO immutable collection https://github.com/UnitTestBot/UTBotJava/issues/398 return new UtArrayList<>(a); } - - // TODO primitive arrays https://github.com/UnitTestBot/UTBotJava/issues/146 } diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/DoubleStream.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/DoubleStream.java new file mode 100644 index 0000000000..8a1bd9a7c4 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/DoubleStream.java @@ -0,0 +1,53 @@ +package org.utbot.engine.overrides.stream; + +import org.utbot.api.annotation.UtClassMock; + +import java.util.function.DoubleSupplier; +import java.util.function.DoubleUnaryOperator; +import java.util.stream.BaseStream; + +import static org.utbot.engine.overrides.UtOverrideMock.executeConcretely; + +@UtClassMock(target = java.util.stream.DoubleStream.class, internalUsage = true) +public interface DoubleStream extends BaseStream { + static java.util.stream.DoubleStream empty() { + return new UtDoubleStream(); + } + + static java.util.stream.DoubleStream of(double t) { + Double[] data = new Double[]{t}; + + return new UtDoubleStream(data, 1); + } + + static java.util.stream.DoubleStream of(double... values) { + int size = values.length; + Double[] data = new Double[size]; + for (int i = 0; i < size; i++) { + data[i] = values[i]; + } + + return new UtDoubleStream(data, size); + } + + @SuppressWarnings("unused") + static java.util.stream.DoubleStream generate(DoubleSupplier s) { + // as "generate" method produces an infinite stream, we cannot analyze it symbolically + executeConcretely(); + return null; + } + + @SuppressWarnings("unused") + static java.util.stream.DoubleStream iterate(final double seed, final DoubleUnaryOperator f) { + // as "iterate" method produces an infinite stream, we cannot analyze it symbolically + executeConcretely(); + return null; + } + + @SuppressWarnings("unused") + static java.util.stream.DoubleStream concat(java.util.stream.DoubleStream a, java.util.stream.DoubleStream b) { + // as provided streams might be infinite, we cannot analyze this method symbolically + executeConcretely(); + return null; + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/IntStream.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/IntStream.java new file mode 100644 index 0000000000..be079e334b --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/IntStream.java @@ -0,0 +1,82 @@ +package org.utbot.engine.overrides.stream; + +import org.utbot.api.annotation.UtClassMock; + +import java.util.function.IntSupplier; +import java.util.function.IntUnaryOperator; +import java.util.stream.BaseStream; + +import static org.utbot.engine.overrides.UtOverrideMock.executeConcretely; +@UtClassMock(target = java.util.stream.IntStream.class, internalUsage = true) +public interface IntStream extends BaseStream { + static java.util.stream.IntStream empty() { + return new UtIntStream(); + } + + static java.util.stream.IntStream of(int t) { + Integer[] data = new Integer[]{t}; + + return new UtIntStream(data, 1); + } + + static java.util.stream.IntStream of(int... values) { + int size = values.length; + Integer[] data = new Integer[size]; + for (int i = 0; i < size; i++) { + data[i] = values[i]; + } + + return new UtIntStream(data, size); + } + + @SuppressWarnings("unused") + static java.util.stream.IntStream generate(IntSupplier s) { + // as "generate" method produces an infinite stream, we cannot analyze it symbolically + executeConcretely(); + return null; + } + + static java.util.stream.IntStream range(int startInclusive, int endExclusive) { + if (startInclusive >= endExclusive) { + return new UtIntStream(); + } + + int size = endExclusive - startInclusive; + Integer[] data = new Integer[size]; + for (int i = startInclusive; i < endExclusive; i++) { + data[i - startInclusive] = i; + } + + return new UtIntStream(data, size); + } + + @SuppressWarnings("unused") + static java.util.stream.IntStream rangeClosed(int startInclusive, int endInclusive) { + if (startInclusive > endInclusive) { + return new UtIntStream(); + } + + // Do not use `range` above to prevent overflow + int size = endInclusive - startInclusive + 1; + Integer[] data = new Integer[size]; + for (int i = startInclusive; i <= endInclusive; i++) { + data[i - startInclusive] = i; + } + + return new UtIntStream(data, size); + } + + @SuppressWarnings("unused") + static java.util.stream.IntStream iterate(final int seed, final IntUnaryOperator f) { + // as "iterate" method produces an infinite stream, we cannot analyze it symbolically + executeConcretely(); + return null; + } + + @SuppressWarnings("unused") + static java.util.stream.IntStream concat(java.util.stream.IntStream a, java.util.stream.IntStream b) { + // as provided streams might be infinite, we cannot analyze this method symbolically + executeConcretely(); + return null; + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/LongStream.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/LongStream.java new file mode 100644 index 0000000000..814b5f3178 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/LongStream.java @@ -0,0 +1,100 @@ +package org.utbot.engine.overrides.stream; + +import org.utbot.api.annotation.UtClassMock; +import org.utbot.api.mock.UtMock; + +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; +import java.util.stream.BaseStream; + +import static org.utbot.engine.overrides.UtOverrideMock.executeConcretely; + +@UtClassMock(target = java.util.stream.LongStream.class, internalUsage = true) +public interface LongStream extends BaseStream { + static java.util.stream.LongStream empty() { + return new UtLongStream(); + } + + static java.util.stream.LongStream of(long t) { + Long[] data = new Long[]{t}; + + return new UtLongStream(data, 1); + } + + static java.util.stream.LongStream of(long... values) { + int size = values.length; + Long[] data = new Long[size]; + for (int i = 0; i < size; i++) { + data[i] = values[i]; + } + + return new UtLongStream(data, size); + } + + @SuppressWarnings("unused") + static java.util.stream.LongStream generate(LongSupplier s) { + // as "generate" method produces an infinite stream, we cannot analyze it symbolically + executeConcretely(); + return null; + } + + static java.util.stream.LongStream range(long startInclusive, long endExclusive) { + if (startInclusive >= endExclusive) { + return new UtLongStream(); + } + + int start = (int) startInclusive; + int end = (int) endExclusive; + + // check that borders fit in int range + UtMock.assumeOrExecuteConcretely(start == startInclusive); + UtMock.assumeOrExecuteConcretely(end == endExclusive); + + int size = end - start; + + Long[] data = new Long[size]; + for (int i = start; i < end; i++) { + data[i - start] = (long) i; + } + + return new UtLongStream(data, size); + } + + @SuppressWarnings("unused") + static java.util.stream.LongStream rangeClosed(long startInclusive, long endInclusive) { + if (startInclusive > endInclusive) { + return new UtLongStream(); + } + + // Do not use `range` above to prevent overflow + int start = (int) startInclusive; + int end = (int) endInclusive; + + // check that borders fit in int range + UtMock.assumeOrExecuteConcretely(start == startInclusive); + UtMock.assumeOrExecuteConcretely(end == endInclusive); + + int size = end - start + 1; + + Long[] data = new Long[size]; + for (int i = start; i <= end; i++) { + data[i - start] = (long) i; + } + + return new UtLongStream(data, size); + } + + @SuppressWarnings("unused") + static java.util.stream.LongStream iterate(final long seed, final LongUnaryOperator f) { + // as "iterate" method produces an infinite stream, we cannot analyze it symbolically + executeConcretely(); + return null; + } + + @SuppressWarnings("unused") + static java.util.stream.LongStream concat(java.util.stream.LongStream a, java.util.stream.LongStream b) { + // as provided streams might be infinite, we cannot analyze this method symbolically + executeConcretely(); + return null; + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Stream.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Stream.java index cf9b533a4d..15b8022681 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Stream.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/Stream.java @@ -13,10 +13,10 @@ public interface Stream extends BaseStream> { @SuppressWarnings("unchecked") static java.util.stream.Stream of(E element) { - Object[] data = new Object[1]; + E[] data = (E[]) new Object[1]; data[0] = element; - return new UtStream<>((E[]) data, 1); + return new UtStream<>(data, 1); } @SuppressWarnings("unchecked") diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtDoubleStream.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtDoubleStream.java new file mode 100644 index 0000000000..82c081011b --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtDoubleStream.java @@ -0,0 +1,722 @@ +package org.utbot.engine.overrides.stream; + +import org.jetbrains.annotations.NotNull; +import org.utbot.engine.ResolverKt; +import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray; +import org.utbot.engine.overrides.collections.UtGenericStorage; +import org.utbot.framework.plugin.api.visible.UtStreamConsumingException; + +import java.util.DoubleSummaryStatistics; +import java.util.NoSuchElementException; +import java.util.OptionalDouble; +import java.util.PrimitiveIterator; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.DoubleBinaryOperator; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleFunction; +import java.util.function.DoublePredicate; +import java.util.function.DoubleToIntFunction; +import java.util.function.DoubleToLongFunction; +import java.util.function.DoubleUnaryOperator; +import java.util.function.ObjDoubleConsumer; +import java.util.function.Supplier; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import static org.utbot.api.mock.UtMock.assume; +import static org.utbot.api.mock.UtMock.assumeOrExecuteConcretely; +import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; +import static org.utbot.engine.overrides.UtOverrideMock.executeConcretely; +import static org.utbot.engine.overrides.UtOverrideMock.parameter; +import static org.utbot.engine.overrides.UtOverrideMock.visit; + +public class UtDoubleStream implements DoubleStream, UtGenericStorage { + private final RangeModifiableUnlimitedArray elementData; + + private final RangeModifiableUnlimitedArray closeHandlers = new RangeModifiableUnlimitedArray<>(); + + private boolean isParallel = false; + + /** + * {@code false} by default, assigned to {@code true} after performing any operation on this stream. Any operation, + * performed on a closed UtStream, throws the {@link IllegalStateException}. + */ + private boolean isClosed = false; + + public UtDoubleStream() { + visit(this); + elementData = new RangeModifiableUnlimitedArray<>(); + } + + public UtDoubleStream(Double[] data, int length) { + this(data, 0, length); + } + + public UtDoubleStream(Double[] data, int startInclusive, int endExclusive) { + visit(this); + + int size = endExclusive - startInclusive; + + elementData = new RangeModifiableUnlimitedArray<>(); + elementData.setRange(0, data, startInclusive, size); + elementData.end = endExclusive; + } + + /** + * Precondition check is called only once by object, + * if it was passed as parameter to method under test. + *

    + * Preconditions that are must be satisfied: + *

  • elementData.size in 0..MAX_STREAM_SIZE.
  • + *
  • elementData is marked as parameter
  • + *
  • elementData.storage and it's elements are marked as parameters
  • + */ + @SuppressWarnings("DuplicatedCode") + void preconditionCheck() { + if (alreadyVisited(this)) { + return; + } + setGenericTypeToTypeOfValue(elementData, 0.0); + + assume(elementData != null); + assume(elementData.storage != null); + + parameter(elementData); + parameter(elementData.storage); + + assume(elementData.begin == 0); + + assume(elementData.end >= 0); + // we can create a stream for an array using Stream.of + assumeOrExecuteConcretely(elementData.end <= ResolverKt.MAX_STREAM_SIZE); + + // As real primitive streams contain primitives, we cannot accept nulls. + for (int i = 0; i < elementData.end; i++) { + assume(elementData.get(i) != null); + } + + // Do not assume that firstly used stream may be already closed to prevent garbage branches + isClosed = false; + + visit(this); + } + + private void preconditionCheckWithClosingStream() { + preconditionCheck(); + + if (isClosed) { + throw new IllegalStateException(); + } + + // Even if exception occurs in the body of a stream operation, this stream could not be used later. + isClosed = true; + } + + public DoubleStream filter(DoublePredicate predicate) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Double[] filtered = new Double[size]; + int j = 0; + for (int i = 0; i < size; i++) { + double element = elementData.get(i); + + try { + if (predicate.test(element)) { + filtered[j++] = element; + } + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtDoubleStream(filtered, j); + } + + @Override + public DoubleStream map(DoubleUnaryOperator mapper) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Double[] mapped = new Double[size]; + for (int i = 0; i < size; i++) { + try { + mapped[i] = mapper.applyAsDouble(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtDoubleStream(mapped, size); + } + + @SuppressWarnings("unchecked") + @Override + public Stream mapToObj(DoubleFunction mapper) { + // Here we assume that this mapping does not produce infinite streams + // - otherwise it should always be executed concretely. + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Object[] mapped = new Object[size]; + for (int i = 0; i < size; i++) { + try { + mapped[i] = mapper.apply(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtStream<>((U[]) mapped, size); + } + + @Override + public IntStream mapToInt(DoubleToIntFunction mapper) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Integer[] mapped = new Integer[size]; + for (int i = 0; i < size; i++) { + try { + mapped[i] = mapper.applyAsInt(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtIntStream(mapped, size); + } + + @Override + public LongStream mapToLong(DoubleToLongFunction mapper) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Long[] mapped = new Long[size]; + for (int i = 0; i < size; i++) { + try { + mapped[i] = mapper.applyAsLong(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtLongStream(mapped, size); + } + + @Override + public DoubleStream flatMap(DoubleFunction mapper) { + preconditionCheckWithClosingStream(); + // as mapper can produce infinite streams, we cannot process it symbolically + executeConcretely(); + return null; + } + + @Override + public DoubleStream distinct() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Double[] distinctElements = new Double[size]; + int distinctSize = 0; + for (int i = 0; i < size; i++) { + double element = elementData.get(i); + boolean isDuplicate = false; + + for (int j = 0; j < distinctSize; j++) { + double alreadyProcessedElement = distinctElements[j]; + if (element == alreadyProcessedElement) { + isDuplicate = true; + break; + } + } + + if (!isDuplicate) { + distinctElements[distinctSize++] = element; + } + } + + return new UtDoubleStream(distinctElements, distinctSize); + } + + // TODO choose the best sorting https://github.com/UnitTestBot/UTBotJava/issues/188 + @Override + public DoubleStream sorted() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + + if (size == 0) { + return new UtDoubleStream(); + } + + Double[] sortedElements = new Double[size]; + for (int i = 0; i < size; i++) { + sortedElements[i] = elementData.get(i); + } + + // bubble sort + for (int i = 0; i < size - 1; i++) { + for (int j = 0; j < size - i - 1; j++) { + if (sortedElements[j] > sortedElements[j + 1]) { + Double tmp = sortedElements[j]; + sortedElements[j] = sortedElements[j + 1]; + sortedElements[j + 1] = tmp; + } + } + } + + return new UtDoubleStream(sortedElements, size); + } + + @Override + public DoubleStream peek(DoubleConsumer action) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + try { + action.accept(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + // returned stream should be opened, so we "reopen" this stream to return it + isClosed = false; + + return this; + } + + @Override + public DoubleStream limit(long maxSize) { + preconditionCheckWithClosingStream(); + + if (maxSize < 0) { + throw new IllegalArgumentException(); + } + + if (maxSize == 0) { + return new UtDoubleStream(); + } + + assumeOrExecuteConcretely(maxSize <= Integer.MAX_VALUE); + + int newSize = (int) maxSize; + int curSize = elementData.end; + + if (newSize > curSize) { + newSize = curSize; + } + + Double[] elements = elementData.toCastedArray(0, newSize); + + return new UtDoubleStream(elements, newSize); + } + + @Override + public DoubleStream skip(long n) { + preconditionCheckWithClosingStream(); + + if (n < 0) { + throw new IllegalArgumentException(); + } + + int curSize = elementData.end; + if (n >= curSize) { + return new UtDoubleStream(); + } + + // n is 1...(Integer.MAX_VALUE - 1) here + int newSize = (int) (curSize - n); + + Double[] elements = elementData.toCastedArray((int) n, newSize); + + return new UtDoubleStream(elements, newSize); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Override + public void forEach(DoubleConsumer action) { + try { + peek(action); + } catch (UtStreamConsumingException e) { + // Since this is a terminal operation, we should throw an original exception + } + } + + @Override + public void forEachOrdered(DoubleConsumer action) { + forEach(action); + } + + @Override + public double[] toArray() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + double[] result = new double[size]; + for (int i = 0; i < size; i++) { + result[i] = elementData.get(i); + } + + return result; + } + + @Override + public double reduce(double identity, DoubleBinaryOperator op) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + double result = identity; + for (int i = 0; i < size; i++) { + result = op.applyAsDouble(result, elementData.get(i)); + } + + return result; + } + + @Override + public OptionalDouble reduce(DoubleBinaryOperator op) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return OptionalDouble.empty(); + } + + double result = elementData.get(0); + for (int i = 1; i < size; i++) { + double element = elementData.get(i); + result = op.applyAsDouble(result, element); + } + + return OptionalDouble.of(result); + } + + @Override + public R collect(Supplier supplier, ObjDoubleConsumer accumulator, BiConsumer combiner) { + preconditionCheckWithClosingStream(); + + // since this implementation is always sequential, we do not need to use the combiner + int size = elementData.end; + R result = supplier.get(); + for (int i = 0; i < size; i++) { + accumulator.accept(result, elementData.get(i)); + } + + return result; + } + + @Override + public double sum() { + preconditionCheckWithClosingStream(); + + final int size = elementData.end; + + if (size == 0) { + return 0; + } + + double sum = 0; + boolean anyNaN = false; + boolean anyPositiveInfinity = false; + boolean anyNegativeInfinity = false; + + for (int i = 0; i < size; i++) { + double element = elementData.get(i); + sum += element; + + anyNaN |= Double.isNaN(element); + anyPositiveInfinity |= element == Double.POSITIVE_INFINITY; + anyNegativeInfinity |= element == Double.NEGATIVE_INFINITY; + } + + if (anyNaN) { + return Double.NaN; + } + + if (anyPositiveInfinity && anyNegativeInfinity) { + return Double.NaN; + } + + if (anyPositiveInfinity && sum == Double.NEGATIVE_INFINITY) { + return Double.NaN; + } + + if (anyNegativeInfinity && sum == Double.POSITIVE_INFINITY) { + return Double.NaN; + } + + return sum; + } + + @Override + public OptionalDouble min() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return OptionalDouble.empty(); + } + + double min = elementData.get(0); + for (int i = 1; i < size; i++) { + final double element = elementData.get(i); + min = Math.min(element, min); + } + + return OptionalDouble.of(min); + } + + @Override + public OptionalDouble max() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return OptionalDouble.empty(); + } + + double max = elementData.get(0); + for (int i = 1; i < size; i++) { + final double element = elementData.get(i); + max = Math.max(element, max); + } + + return OptionalDouble.of(max); + } + + @Override + public long count() { + preconditionCheckWithClosingStream(); + + return elementData.end; + } + + @Override + public OptionalDouble average() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return OptionalDouble.empty(); + } + + // "reopen" this stream to use sum and count + isClosed = false; + final double sum = sum(); + isClosed = false; + final long count = count(); + + double average = sum / count; + + return OptionalDouble.of(average); + } + + @Override + public DoubleSummaryStatistics summaryStatistics() { + preconditionCheckWithClosingStream(); + + DoubleSummaryStatistics statistics = new DoubleSummaryStatistics(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + double element = elementData.get(i); + statistics.accept(element); + } + + return statistics; + } + + @Override + public boolean anyMatch(DoublePredicate predicate) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + if (predicate.test(elementData.get(i))) { + return true; + } + } + + return false; + } + + @Override + public boolean allMatch(DoublePredicate predicate) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + if (!predicate.test(elementData.get(i))) { + return false; + } + } + + return true; + } + + @Override + public boolean noneMatch(DoublePredicate predicate) { + return !anyMatch(predicate); + } + + @Override + public OptionalDouble findFirst() { + preconditionCheckWithClosingStream(); + + if (elementData.end == 0) { + return OptionalDouble.empty(); + } + + double first = elementData.get(0); + + return OptionalDouble.of(first); + } + + @Override + public OptionalDouble findAny() { + preconditionCheckWithClosingStream(); + + // since this implementation is always sequential, we can just return the first element + return findFirst(); + } + + @Override + public Stream boxed() { + preconditionCheckWithClosingStream(); + + final int size = elementData.end; + if (size == 0) { + return new UtStream<>(); + } + + Double[] elements = new Double[size]; + for (int i = 0; i < size; i++) { + elements[i] = elementData.get(i); + } + + return new UtStream<>(elements, size); + } + + @Override + public DoubleStream sequential() { + // this method does not "close" this stream + preconditionCheck(); + + isParallel = false; + + return this; + } + + @Override + public DoubleStream parallel() { + // this method does not "close" this stream + preconditionCheck(); + + isParallel = true; + + return this; + } + + @Override + public PrimitiveIterator.OfDouble iterator() { + preconditionCheckWithClosingStream(); + + return new UtDoubleStreamIterator(0); + } + + @SuppressWarnings("ConstantConditions") + @Override + public Spliterator.OfDouble spliterator() { + preconditionCheckWithClosingStream(); + + // each implementation is extremely difficult and almost impossible to analyze + executeConcretely(); + return null; + } + + @Override + public boolean isParallel() { + // this method does not "close" this stream + preconditionCheck(); + + return isParallel; + } + + @NotNull + @Override + public DoubleStream unordered() { + // this method does not "close" this stream + preconditionCheck(); + + return this; + } + + @NotNull + @Override + public DoubleStream onClose(Runnable closeHandler) { + // this method does not "close" this stream + preconditionCheck(); + + // adds closeHandler to existing + closeHandlers.set(closeHandlers.end++, closeHandler); + + return this; + } + + @Override + public void close() { + // Stream can be closed via this method many times + preconditionCheck(); + + // TODO resources closing https://github.com/UnitTestBot/UTBotJava/issues/189 + + // NOTE: this implementation does not care about suppressing and throwing exceptions produced by handlers + for (int i = 0; i < closeHandlers.end; i++) { + closeHandlers.get(i).run(); + } + + // clear handlers (we do not need to manually clear all elements) + closeHandlers.end = 0; + } + + public class UtDoubleStreamIterator implements PrimitiveIterator.OfDouble { + int index; + + UtDoubleStreamIterator(int index) { + if (index < 0 || index > elementData.end) { + throw new IndexOutOfBoundsException(); + } + + this.index = index; + } + + @Override + public boolean hasNext() { + preconditionCheck(); + + return index != elementData.end; + } + + @Override + public double nextDouble() { + return next(); + } + + @Override + public Double next() { + preconditionCheck(); + + if (index == elementData.end) { + throw new NoSuchElementException(); + } + + return elementData.get(index++); + } + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtIntStream.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtIntStream.java new file mode 100644 index 0000000000..0d1d530bbc --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtIntStream.java @@ -0,0 +1,754 @@ +package org.utbot.engine.overrides.stream; + +import org.jetbrains.annotations.NotNull; +import org.utbot.engine.ResolverKt; +import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray; +import org.utbot.engine.overrides.collections.UtGenericStorage; +import org.utbot.framework.plugin.api.visible.UtStreamConsumingException; + +import java.util.IntSummaryStatistics; +import java.util.NoSuchElementException; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.PrimitiveIterator; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.IntBinaryOperator; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; +import java.util.function.IntPredicate; +import java.util.function.IntToDoubleFunction; +import java.util.function.IntToLongFunction; +import java.util.function.IntUnaryOperator; +import java.util.function.ObjIntConsumer; +import java.util.function.Supplier; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import static org.utbot.api.mock.UtMock.assume; +import static org.utbot.api.mock.UtMock.assumeOrExecuteConcretely; +import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; +import static org.utbot.engine.overrides.UtOverrideMock.executeConcretely; +import static org.utbot.engine.overrides.UtOverrideMock.parameter; +import static org.utbot.engine.overrides.UtOverrideMock.visit; + +public class UtIntStream implements IntStream, UtGenericStorage { + private final RangeModifiableUnlimitedArray elementData; + + private final RangeModifiableUnlimitedArray closeHandlers = new RangeModifiableUnlimitedArray<>(); + + private boolean isParallel = false; + + /** + * {@code false} by default, assigned to {@code true} after performing any operation on this stream. Any operation, + * performed on a closed UtStream, throws the {@link IllegalStateException}. + */ + private boolean isClosed = false; + + public UtIntStream() { + visit(this); + elementData = new RangeModifiableUnlimitedArray<>(); + } + + public UtIntStream(Integer[] data, int length) { + this(data, 0, length); + } + + public UtIntStream(Integer[] data, int startInclusive, int endExclusive) { + visit(this); + + int size = endExclusive - startInclusive; + + elementData = new RangeModifiableUnlimitedArray<>(); + elementData.setRange(0, data, startInclusive, size); + elementData.end = endExclusive; + } + + /** + * Precondition check is called only once by object, + * if it was passed as parameter to method under test. + *

    + * Preconditions that are must be satisfied: + *

  • elementData.size in 0..HARD_MAX_ARRAY_SIZE.
  • + *
  • elementData is marked as parameter
  • + *
  • elementData.storage and it's elements are marked as parameters
  • + */ + @SuppressWarnings("DuplicatedCode") + void preconditionCheck() { + if (alreadyVisited(this)) { + return; + } + setGenericTypeToTypeOfValue(elementData, 0); + + assume(elementData != null); + assume(elementData.storage != null); + + parameter(elementData); + parameter(elementData.storage); + + assume(elementData.begin == 0); + + assume(elementData.end >= 0); + // we can create a stream for an array using Stream.of + assumeOrExecuteConcretely(elementData.end <= ResolverKt.MAX_STREAM_SIZE); + + // As real primitive streams contain primitives, we cannot accept nulls. + for (int i = 0; i < elementData.end; i++) { + assume(elementData.get(i) != null); + } + + // Do not assume that firstly used stream may be already closed to prevent garbage branches + isClosed = false; + + visit(this); + } + + private void preconditionCheckWithClosingStream() { + preconditionCheck(); + + if (isClosed) { + throw new IllegalStateException(); + } + + // Even if exception occurs in the body of a stream operation, this stream could not be used later. + isClosed = true; + } + + public IntStream filter(IntPredicate predicate) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Integer[] filtered = new Integer[size]; + int j = 0; + for (int i = 0; i < size; i++) { + int element = elementData.get(i); + + try { + if (predicate.test(element)) { + filtered[j++] = element; + } + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtIntStream(filtered, j); + } + + @Override + public IntStream map(IntUnaryOperator mapper) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Integer[] mapped = new Integer[size]; + for (int i = 0; i < size; i++) { + try { + mapped[i] = mapper.applyAsInt(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtIntStream(mapped, size); + } + + @SuppressWarnings("unchecked") + @Override + public Stream mapToObj(IntFunction mapper) { + // Here we assume that this mapping does not produce infinite streams + // - otherwise it should always be executed concretely. + preconditionCheckWithClosingStream(); + + int size = elementData.end; + U[] mapped = (U[]) new Object[size]; + for (int i = 0; i < size; i++) { + try { + mapped[i] = mapper.apply(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtStream<>(mapped, size); + } + + @Override + public LongStream mapToLong(IntToLongFunction mapper) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Long[] mapped = new Long[size]; + for (int i = 0; i < size; i++) { + try { + mapped[i] = mapper.applyAsLong(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtLongStream(mapped, size); + } + + @Override + public DoubleStream mapToDouble(IntToDoubleFunction mapper) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Double[] mapped = new Double[size]; + for (int i = 0; i < size; i++) { + try { + mapped[i] = mapper.applyAsDouble(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtDoubleStream(mapped, size); + } + + @Override + public IntStream flatMap(IntFunction mapper) { + preconditionCheckWithClosingStream(); + // as mapper can produce infinite streams, we cannot process it symbolically + executeConcretely(); + return null; + } + + @Override + public IntStream distinct() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Integer[] distinctElements = new Integer[size]; + int distinctSize = 0; + for (int i = 0; i < size; i++) { + int element = elementData.get(i); + boolean isDuplicate = false; + + for (int j = 0; j < distinctSize; j++) { + int alreadyProcessedElement = distinctElements[j]; + if (element == alreadyProcessedElement) { + isDuplicate = true; + break; + } + } + + if (!isDuplicate) { + distinctElements[distinctSize++] = element; + } + } + + return new UtIntStream(distinctElements, distinctSize); + } + + // TODO choose the best sorting https://github.com/UnitTestBot/UTBotJava/issues/188 + @Override + public IntStream sorted() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + + if (size == 0) { + return new UtIntStream(); + } + + Integer[] sortedElements = new Integer[size]; + for (int i = 0; i < size; i++) { + sortedElements[i] = elementData.get(i); + } + + // bubble sort + for (int i = 0; i < size - 1; i++) { + for (int j = 0; j < size - i - 1; j++) { + if (sortedElements[j] > sortedElements[j + 1]) { + Integer tmp = sortedElements[j]; + sortedElements[j] = sortedElements[j + 1]; + sortedElements[j + 1] = tmp; + } + } + } + + return new UtIntStream(sortedElements, size); + } + + @Override + public IntStream peek(IntConsumer action) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + try { + action.accept(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + // returned stream should be opened, so we "reopen" this stream to return it + isClosed = false; + + return this; + } + + @Override + public IntStream limit(long maxSize) { + preconditionCheckWithClosingStream(); + + if (maxSize < 0) { + throw new IllegalArgumentException(); + } + + if (maxSize == 0) { + return new UtIntStream(); + } + + assumeOrExecuteConcretely(maxSize <= Integer.MAX_VALUE); + + int newSize = (int) maxSize; + int curSize = elementData.end; + + if (newSize > curSize) { + newSize = curSize; + } + + Integer[] newData = elementData.toCastedArray(0, newSize); + + return new UtIntStream(newData, newSize); + } + + @Override + public IntStream skip(long n) { + preconditionCheckWithClosingStream(); + + if (n < 0) { + throw new IllegalArgumentException(); + } + + int curSize = elementData.end; + if (n >= curSize) { + return new UtIntStream(); + } + + // n is 1...(Integer.MAX_VALUE - 1) here + int newSize = (int) (curSize - n); + + Integer[] newData = elementData.toCastedArray((int) n, newSize); + + return new UtIntStream(newData, newSize); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Override + public void forEach(IntConsumer action) { + try { + peek(action); + } catch (UtStreamConsumingException e) { + // Since this is a terminal operation, we should throw an original exception + } + } + + @Override + public void forEachOrdered(IntConsumer action) { + forEach(action); + } + + @Override + public int[] toArray() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + int[] result = new int[size]; + + for (int i = 0; i < size; i++) { + result[i] = elementData.get(i); + } + + return result; + } + + @Override + public int reduce(int identity, IntBinaryOperator op) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + int result = identity; + for (int i = 0; i < size; i++) { + result = op.applyAsInt(result, elementData.get(i)); + } + + return result; + } + + @Override + public OptionalInt reduce(IntBinaryOperator op) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return OptionalInt.empty(); + } + + int result = elementData.get(0); + for (int i = 1; i < size; i++) { + int element = elementData.get(i); + result = op.applyAsInt(result, element); + } + + return OptionalInt.of(result); + } + + @Override + public R collect(Supplier supplier, ObjIntConsumer accumulator, BiConsumer combiner) { + preconditionCheckWithClosingStream(); + + // since this implementation is always sequential, we do not need to use the combiner + int size = elementData.end; + R result = supplier.get(); + for (int i = 0; i < size; i++) { + accumulator.accept(result, elementData.get(i)); + } + + return result; + } + + @Override + public int sum() { + preconditionCheckWithClosingStream(); + + final int size = elementData.end; + + if (size == 0) { + return 0; + } + + int sum = 0; + + for (int i = 0; i < size; i++) { + int element = elementData.get(i); + sum += element; + } + + return sum; + } + + @SuppressWarnings("ManualMinMaxCalculation") + @Override + public OptionalInt min() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return OptionalInt.empty(); + } + + int min = elementData.get(0); + for (int i = 1; i < size; i++) { + final int element = elementData.get(i); + min = (element < min) ? element : min; + } + + return OptionalInt.of(min); + } + + @SuppressWarnings("ManualMinMaxCalculation") + @Override + public OptionalInt max() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return OptionalInt.empty(); + } + + int max = elementData.get(0); + for (int i = 1; i < size; i++) { + final int element = elementData.get(i); + max = (element > max) ? element : max; + } + + return OptionalInt.of(max); + } + + @Override + public long count() { + preconditionCheckWithClosingStream(); + + return elementData.end; + } + + @Override + public OptionalDouble average() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return OptionalDouble.empty(); + } + + // "reopen" this stream to use sum and count + isClosed = false; + final double sum = sum(); + isClosed = false; + final long count = count(); + + double average = sum / count; + + return OptionalDouble.of(average); + } + + @Override + public IntSummaryStatistics summaryStatistics() { + preconditionCheckWithClosingStream(); + + IntSummaryStatistics statistics = new IntSummaryStatistics(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + int element = elementData.get(i); + statistics.accept(element); + } + + return statistics; + } + + @Override + public boolean anyMatch(IntPredicate predicate) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + if (predicate.test(elementData.get(i))) { + return true; + } + } + + return false; + } + + @Override + public boolean allMatch(IntPredicate predicate) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + if (!predicate.test(elementData.get(i))) { + return false; + } + } + + return true; + } + + @Override + public boolean noneMatch(IntPredicate predicate) { + return !anyMatch(predicate); + } + + @Override + public OptionalInt findFirst() { + preconditionCheckWithClosingStream(); + + if (elementData.end == 0) { + return OptionalInt.empty(); + } + + int first = elementData.get(0); + + return OptionalInt.of(first); + } + + @Override + public OptionalInt findAny() { + preconditionCheckWithClosingStream(); + + // since this implementation is always sequential, we can just return the first element + return findFirst(); + } + + @Override + public LongStream asLongStream() { + preconditionCheckWithClosingStream(); + + final int size = elementData.end; + + if (size == 0) { + return new UtLongStream(); + } + + // "open" stream to use toArray method + final int[] elements = copyData(); + + Long[] longs = new Long[size]; + + for (int i = 0; i < size; i++) { + longs[i] = (long) elements[i]; + } + + return new UtLongStream(longs, size); + } + + @Override + public DoubleStream asDoubleStream() { + preconditionCheckWithClosingStream(); + + final int size = elementData.end; + + if (size == 0) { + return new UtDoubleStream(); + } + + final int[] elements = copyData(); + + Double[] doubles = new Double[size]; + + for (int i = 0; i < size; i++) { + doubles[i] = (double) elements[i]; + } + + return new UtDoubleStream(doubles, size); + } + + @Override + public Stream boxed() { + preconditionCheckWithClosingStream(); + + final int size = elementData.end; + if (size == 0) { + return new UtStream<>(); + } + + Integer[] elements = new Integer[size]; + for (int i = 0; i < size; i++) { + elements[i] = elementData.get(i); + } + + return new UtStream<>(elements, size); + } + + @Override + public IntStream sequential() { + // this method does not "close" this stream + preconditionCheck(); + + isParallel = false; + + return this; + } + + @Override + public IntStream parallel() { + // this method does not "close" this stream + preconditionCheck(); + + isParallel = true; + + return this; + } + + @Override + public PrimitiveIterator.OfInt iterator() { + preconditionCheckWithClosingStream(); + + return new UtIntStreamIterator(0); + } + + @SuppressWarnings("ConstantConditions") + @Override + public Spliterator.OfInt spliterator() { + preconditionCheckWithClosingStream(); + + // each implementation is extremely difficult and almost impossible to analyze + executeConcretely(); + return null; + } + + @Override + public boolean isParallel() { + // this method does not "close" this stream + preconditionCheck(); + + return isParallel; + } + + @NotNull + @Override + public IntStream unordered() { + // this method does not "close" this stream + preconditionCheck(); + + return this; + } + + @NotNull + @Override + public IntStream onClose(Runnable closeHandler) { + // this method does not "close" this stream + preconditionCheck(); + + // adds closeHandler to existing + closeHandlers.set(closeHandlers.end++, closeHandler); + + return this; + } + + @Override + public void close() { + // Stream can be closed via this method many times + preconditionCheck(); + + // TODO resources closing https://github.com/UnitTestBot/UTBotJava/issues/189 + + // NOTE: this implementation does not care about suppressing and throwing exceptions produced by handlers + for (int i = 0; i < closeHandlers.end; i++) { + closeHandlers.get(i).run(); + } + + // clear handlers + closeHandlers.end = 0; + } + + // Copies data to int array. Might be used on already "closed" stream. Marks this stream as closed. + private int[] copyData() { + // "open" stream to use toArray method + isClosed = false; + + return toArray(); + } + + public class UtIntStreamIterator implements PrimitiveIterator.OfInt { + int index; + + UtIntStreamIterator(int index) { + if (index < 0 || index > elementData.end) { + throw new IndexOutOfBoundsException(); + } + + this.index = index; + } + + @Override + public boolean hasNext() { + preconditionCheck(); + + return index != elementData.end; + } + + @Override + public int nextInt() { + return next(); + } + + @Override + public Integer next() { + preconditionCheck(); + + if (index == elementData.end) { + throw new NoSuchElementException(); + } + + return elementData.get(index++); + } + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtLongStream.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtLongStream.java new file mode 100644 index 0000000000..74c4465a64 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtLongStream.java @@ -0,0 +1,730 @@ +package org.utbot.engine.overrides.stream; + +import org.jetbrains.annotations.NotNull; +import org.utbot.engine.ResolverKt; +import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray; +import org.utbot.engine.overrides.collections.UtGenericStorage; +import org.utbot.framework.plugin.api.visible.UtStreamConsumingException; + +import java.util.LongSummaryStatistics; +import java.util.NoSuchElementException; +import java.util.OptionalDouble; +import java.util.OptionalLong; +import java.util.PrimitiveIterator; +import java.util.Spliterator; +import java.util.function.BiConsumer; +import java.util.function.LongBinaryOperator; +import java.util.function.LongConsumer; +import java.util.function.LongFunction; +import java.util.function.LongPredicate; +import java.util.function.LongToDoubleFunction; +import java.util.function.LongToIntFunction; +import java.util.function.LongUnaryOperator; +import java.util.function.ObjLongConsumer; +import java.util.function.Supplier; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import static org.utbot.api.mock.UtMock.assume; +import static org.utbot.api.mock.UtMock.assumeOrExecuteConcretely; +import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; +import static org.utbot.engine.overrides.UtOverrideMock.executeConcretely; +import static org.utbot.engine.overrides.UtOverrideMock.parameter; +import static org.utbot.engine.overrides.UtOverrideMock.visit; + +public class UtLongStream implements LongStream, UtGenericStorage { + private final RangeModifiableUnlimitedArray elementData; + + private final RangeModifiableUnlimitedArray closeHandlers = new RangeModifiableUnlimitedArray<>(); + + private boolean isParallel = false; + + /** + * {@code false} by default, assigned to {@code true} after performing any operation on this stream. Any operation, + * performed on a closed UtStream, throws the {@link IllegalStateException}. + */ + private boolean isClosed = false; + + public UtLongStream() { + visit(this); + elementData = new RangeModifiableUnlimitedArray<>(); + } + + public UtLongStream(Long[] data, int length) { + this(data, 0, length); + } + + public UtLongStream(Long[] data, int startInclusive, int endExclusive) { + visit(this); + + int size = endExclusive - startInclusive; + + elementData = new RangeModifiableUnlimitedArray<>(); + elementData.setRange(0, data, startInclusive, size); + elementData.end = endExclusive; + } + + /** + * Precondition check is called only once by object, + * if it was passed as parameter to method under test. + *

    + * Preconditions that are must be satisfied: + *

  • elementData.size in 0..HARD_MAX_ARRAY_SIZE.
  • + *
  • elementData is marked as parameter
  • + *
  • elementData.storage and it's elements are marked as parameters
  • + */ + @SuppressWarnings("DuplicatedCode") + void preconditionCheck() { + if (alreadyVisited(this)) { + return; + } + setGenericTypeToTypeOfValue(elementData, 0L); + + assume(elementData != null); + assume(elementData.storage != null); + + parameter(elementData); + parameter(elementData.storage); + + assume(elementData.begin == 0); + + assume(elementData.end >= 0); + // we can create a stream for an array using Stream.of + assumeOrExecuteConcretely(elementData.end <= ResolverKt.MAX_STREAM_SIZE); + + // As real primitive streams contain primitives, we cannot accept nulls. + for (int i = 0; i < elementData.end; i++) { + assume(elementData.get(i) != null); + } + + // Do not assume that firstly used stream may be already closed to prevent garbage branches + isClosed = false; + + visit(this); + } + + private void preconditionCheckWithClosingStream() { + preconditionCheck(); + + if (isClosed) { + throw new IllegalStateException(); + } + + // Even if exception occurs in the body of a stream operation, this stream could not be used later. + isClosed = true; + } + + public LongStream filter(LongPredicate predicate) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Long[] filtered = new Long[size]; + int j = 0; + for (int i = 0; i < size; i++) { + long element = elementData.get(i); + + try { + if (predicate.test(element)) { + filtered[j++] = element; + } + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtLongStream(filtered, j); + } + + @Override + public LongStream map(LongUnaryOperator mapper) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Long[] mapped = new Long[size]; + for (int i = 0; i < size; i++) { + try { + mapped[i] = mapper.applyAsLong(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtLongStream(mapped, size); + } + + @SuppressWarnings("unchecked") + @Override + public Stream mapToObj(LongFunction mapper) { + // Here we assume that this mapping does not produce infinite streams + // - otherwise it should always be executed concretely. + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Object[] mapped = new Object[size]; + for (int i = 0; i < size; i++) { + try { + mapped[i] = mapper.apply(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtStream<>((U[]) mapped, size); + } + + @Override + public IntStream mapToInt(LongToIntFunction mapper) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Integer[] mapped = new Integer[size]; + for (int i = 0; i < size; i++) { + try { + mapped[i] = mapper.applyAsInt(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtIntStream(mapped, size); + } + + @Override + public DoubleStream mapToDouble(LongToDoubleFunction mapper) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Double[] mapped = new Double[size]; + for (int i = 0; i < size; i++) { + try { + mapped[i] = mapper.applyAsDouble(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtDoubleStream(mapped, size); + } + + @Override + public LongStream flatMap(LongFunction mapper) { + preconditionCheckWithClosingStream(); + // as mapper can produce infinite streams, we cannot process it symbolically + executeConcretely(); + return null; + } + + @Override + public LongStream distinct() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + Long[] distinctElements = new Long[size]; + int distinctSize = 0; + for (int i = 0; i < size; i++) { + long element = elementData.get(i); + boolean isDuplicate = false; + + for (int j = 0; j < distinctSize; j++) { + long alreadyProcessedElement = distinctElements[j]; + if (element == alreadyProcessedElement) { + isDuplicate = true; + break; + } + } + + if (!isDuplicate) { + distinctElements[distinctSize++] = element; + } + } + + return new UtLongStream(distinctElements, distinctSize); + } + + // TODO choose the best sorting https://github.com/UnitTestBot/UTBotJava/issues/188 + @Override + public LongStream sorted() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + + if (size == 0) { + return new UtLongStream(); + } + + Long[] sortedElements = new Long[size]; + for (int i = 0; i < size; i++) { + sortedElements[i] = elementData.get(i); + } + + // bubble sort + for (int i = 0; i < size - 1; i++) { + for (int j = 0; j < size - i - 1; j++) { + if (sortedElements[j] > sortedElements[j + 1]) { + Long tmp = sortedElements[j]; + sortedElements[j] = sortedElements[j + 1]; + sortedElements[j + 1] = tmp; + } + } + } + + return new UtLongStream(sortedElements, size); + } + + @Override + public LongStream peek(LongConsumer action) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + try { + action.accept(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + // returned stream should be opened, so we "reopen" this stream to return it + isClosed = false; + + return this; + } + + @Override + public LongStream limit(long maxSize) { + preconditionCheckWithClosingStream(); + + if (maxSize < 0) { + throw new IllegalArgumentException(); + } + + if (maxSize == 0) { + return new UtLongStream(); + } + + assumeOrExecuteConcretely(maxSize <= Integer.MAX_VALUE); + + int newSize = (int) maxSize; + int curSize = elementData.end; + + if (newSize > curSize) { + newSize = curSize; + } + + Long[] elements = elementData.toCastedArray(0, newSize); + + return new UtLongStream(elements, newSize); + } + + @Override + public LongStream skip(long n) { + preconditionCheckWithClosingStream(); + + if (n < 0) { + throw new IllegalArgumentException(); + } + + int curSize = elementData.end; + if (n >= curSize) { + return new UtLongStream(); + } + + // n is 1...(Integer.MAX_VALUE - 1) here + int newSize = (int) (curSize - n); + + Long[] elements = elementData.toCastedArray((int) n, newSize); + + return new UtLongStream(elements, newSize); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Override + public void forEach(LongConsumer action) { + try { + peek(action); + } catch (UtStreamConsumingException e) { + // Since this is a terminal operation, we should throw an original exception + } + } + + @Override + public void forEachOrdered(LongConsumer action) { + forEach(action); + } + + @Override + public long[] toArray() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + long[] result = new long[size]; + for (int i = 0; i < size; i++) { + result[i] = elementData.get(i); + } + + return result; + } + + @Override + public long reduce(long identity, LongBinaryOperator op) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + long result = identity; + for (int i = 0; i < size; i++) { + result = op.applyAsLong(result, elementData.get(i)); + } + + return result; + } + + @Override + public OptionalLong reduce(LongBinaryOperator op) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return OptionalLong.empty(); + } + + long result = elementData.get(0); + for (int i = 1; i < size; i++) { + long element = elementData.get(i); + result = op.applyAsLong(result, element); + } + + return OptionalLong.of(result); + } + + @Override + public R collect(Supplier supplier, ObjLongConsumer accumulator, BiConsumer combiner) { + preconditionCheckWithClosingStream(); + + // since this implementation is always sequential, we do not need to use the combiner + int size = elementData.end; + R result = supplier.get(); + for (int i = 0; i < size; i++) { + accumulator.accept(result, elementData.get(i)); + } + + return result; + } + + @Override + public long sum() { + preconditionCheckWithClosingStream(); + + final int size = elementData.end; + + if (size == 0) { + return 0; + } + + long sum = 0; + + for (int i = 0; i < size; i++) { + long element = elementData.get(i); + sum += element; + } + + return sum; + } + + @SuppressWarnings("ManualMinMaxCalculation") + @Override + public OptionalLong min() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return OptionalLong.empty(); + } + + long min = elementData.get(0); + for (int i = 1; i < size; i++) { + final long element = elementData.get(i); + min = (element < min) ? element : min; + } + + return OptionalLong.of(min); + } + + @SuppressWarnings("ManualMinMaxCalculation") + @Override + public OptionalLong max() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return OptionalLong.empty(); + } + + long max = elementData.get(0); + for (int i = 1; i < size; i++) { + final long element = elementData.get(i); + max = (element > max) ? element : max; + } + + return OptionalLong.of(max); + } + + @Override + public long count() { + preconditionCheckWithClosingStream(); + + return elementData.end; + } + + @Override + public OptionalDouble average() { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + if (size == 0) { + return OptionalDouble.empty(); + } + + // "reopen" this stream to use sum and count + isClosed = false; + final double sum = sum(); + isClosed = false; + final long count = count(); + + double average = sum / count; + + return OptionalDouble.of(average); + } + + @Override + public LongSummaryStatistics summaryStatistics() { + preconditionCheckWithClosingStream(); + + LongSummaryStatistics statistics = new LongSummaryStatistics(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + long element = elementData.get(i); + statistics.accept(element); + } + + return statistics; + } + + @Override + public boolean anyMatch(LongPredicate predicate) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + if (predicate.test(elementData.get(i))) { + return true; + } + } + + return false; + } + + @Override + public boolean allMatch(LongPredicate predicate) { + preconditionCheckWithClosingStream(); + + int size = elementData.end; + for (int i = 0; i < size; i++) { + if (!predicate.test(elementData.get(i))) { + return false; + } + } + + return true; + } + + @Override + public boolean noneMatch(LongPredicate predicate) { + return !anyMatch(predicate); + } + + @Override + public OptionalLong findFirst() { + preconditionCheckWithClosingStream(); + + if (elementData.end == 0) { + return OptionalLong.empty(); + } + + long first = elementData.get(0); + + return OptionalLong.of(first); + } + + @Override + public OptionalLong findAny() { + preconditionCheckWithClosingStream(); + + // since this implementation is always sequential, we can just return the first element + return findFirst(); + } + + @Override + public DoubleStream asDoubleStream() { + preconditionCheckWithClosingStream(); + + final int size = elementData.end; + + if (size == 0) { + return new UtDoubleStream(); + } + + final long[] elements = copyData(); + Double[] doubles = new Double[size]; + + for (int i = 0; i < size; i++) { + doubles[i] = (double) elements[i]; + } + + return new UtDoubleStream(doubles, size); + } + + @Override + public Stream boxed() { + preconditionCheckWithClosingStream(); + + final int size = elementData.end; + if (size == 0) { + return new UtStream<>(); + } + + Long[] elements = new Long[size]; + for (int i = 0; i < size; i++) { + elements[i] = elementData.get(i); + } + + return new UtStream<>(elements, size); + } + + @Override + public LongStream sequential() { + // this method does not "close" this stream + preconditionCheck(); + + isParallel = false; + + return this; + } + + @Override + public LongStream parallel() { + // this method does not "close" this stream + preconditionCheck(); + + isParallel = true; + + return this; + } + + @Override + public PrimitiveIterator.OfLong iterator() { + preconditionCheckWithClosingStream(); + + return new UtLongStreamIterator(0); + } + + @SuppressWarnings("ConstantConditions") + @Override + public Spliterator.OfLong spliterator() { + preconditionCheckWithClosingStream(); + + // each implementation is extremely difficult and almost impossible to analyze + executeConcretely(); + return null; + } + + @Override + public boolean isParallel() { + // this method does not "close" this stream + preconditionCheck(); + + return isParallel; + } + + @NotNull + @Override + public LongStream unordered() { + // this method does not "close" this stream + preconditionCheck(); + + return this; + } + + @NotNull + @Override + public LongStream onClose(Runnable closeHandler) { + // this method does not "close" this stream + preconditionCheck(); + + // adds closeHandler to existing + closeHandlers.set(closeHandlers.end++, closeHandler); + + return this; + } + + @Override + public void close() { + // Stream can be closed via this method many times + preconditionCheck(); + + // TODO resources closing https://github.com/UnitTestBot/UTBotJava/issues/189 + + // NOTE: this implementation does not care about suppressing and throwing exceptions produced by handlers + for (int i = 0; i < closeHandlers.end; i++) { + closeHandlers.get(i).run(); + } + + // clear handlers + closeHandlers.end = 0; + } + + // Copies data to long array. Might be used on already "closed" stream. Marks this stream as closed. + private long[] copyData() { + // "open" stream to use toArray method + isClosed = false; + + return toArray(); + } + + public class UtLongStreamIterator implements PrimitiveIterator.OfLong { + int index; + + UtLongStreamIterator(int index) { + if (index < 0 || index > elementData.end) { + throw new IndexOutOfBoundsException(); + } + + this.index = index; + } + + @Override + public boolean hasNext() { + preconditionCheck(); + + return index != elementData.end; + } + + @Override + public long nextLong() { + return next(); + } + + @Override + public Long next() { + preconditionCheck(); + + if (index == elementData.end) { + throw new NoSuchElementException(); + } + + return elementData.get(index++); + } + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java index 790a7ef16e..37413f0725 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/stream/UtStream.java @@ -1,8 +1,11 @@ package org.utbot.engine.overrides.stream; +import org.jetbrains.annotations.NotNull; +import org.utbot.engine.ResolverKt; import org.utbot.engine.overrides.UtArrayMock; import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray; import org.utbot.engine.overrides.collections.UtGenericStorage; +import org.utbot.framework.plugin.api.visible.UtStreamConsumingException; import java.util.Comparator; import java.util.Iterator; @@ -26,11 +29,9 @@ import java.util.stream.IntStream; import java.util.stream.LongStream; import java.util.stream.Stream; -import org.jetbrains.annotations.NotNull; import static org.utbot.api.mock.UtMock.assume; import static org.utbot.api.mock.UtMock.assumeOrExecuteConcretely; -import static org.utbot.engine.ResolverKt.HARD_MAX_ARRAY_SIZE; import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; import static org.utbot.engine.overrides.UtOverrideMock.executeConcretely; import static org.utbot.engine.overrides.UtOverrideMock.parameter; @@ -55,10 +56,7 @@ public UtStream() { } public UtStream(E[] data, int length) { - visit(this); - elementData = new RangeModifiableUnlimitedArray<>(); - elementData.setRange(0, data, 0, length); - elementData.end = length; + this(data, 0, length); } public UtStream(E[] data, int startInclusive, int endExclusive) { @@ -97,7 +95,7 @@ void preconditionCheck() { assume(elementData.end >= 0); // we can create a stream for an array using Stream.of - assume(elementData.end <= HARD_MAX_ARRAY_SIZE); + assume(elementData.end <= ResolverKt.MAX_STREAM_SIZE); visit(this); } @@ -119,16 +117,21 @@ public Stream filter(Predicate predicate) { preconditionCheckWithClosingStream(); int size = elementData.end; - Object[] filtered = new Object[size]; + E[] filtered = (E[]) new Object[size]; int j = 0; for (int i = 0; i < size; i++) { E element = elementData.get(i); - if (predicate.test(element)) { - filtered[j++] = element; + + try { + if (predicate.test(element)) { + filtered[j++] = element; + } + } catch (Exception e) { + throw new UtStreamConsumingException(e); } } - return new UtStream<>((E[]) filtered, j); + return new UtStream<>(filtered, j); } @SuppressWarnings("unchecked") @@ -139,7 +142,11 @@ public Stream map(Function mapper) { int size = elementData.end; Object[] mapped = new Object[size]; for (int i = 0; i < size; i++) { - mapped[i] = mapper.apply(elementData.get(i)); + try { + mapped[i] = mapper.apply(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } } return new UtStream<>((R[]) mapped, size); @@ -148,25 +155,52 @@ public Stream map(Function mapper) { @Override public IntStream mapToInt(ToIntFunction mapper) { preconditionCheckWithClosingStream(); - // TODO https://github.com/UnitTestBot/UTBotJava/issues/146 - executeConcretely(); - return null; + + int size = elementData.end; + Integer[] data = new Integer[size]; + for (int i = 0; i < size; i++) { + try { + data[i] = mapper.applyAsInt(elementData.getWithoutClassCastExceptionCheck(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtIntStream(data, size); } @Override public LongStream mapToLong(ToLongFunction mapper) { preconditionCheckWithClosingStream(); - // TODO https://github.com/UnitTestBot/UTBotJava/issues/146 - executeConcretely(); - return null; + + int size = elementData.end; + Long[] data = new Long[size]; + for (int i = 0; i < size; i++) { + try { + data[i] = mapper.applyAsLong(elementData.getWithoutClassCastExceptionCheck(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtLongStream(data, size); } @Override public DoubleStream mapToDouble(ToDoubleFunction mapper) { preconditionCheckWithClosingStream(); - // TODO https://github.com/UnitTestBot/UTBotJava/issues/146 - executeConcretely(); - return null; + + int size = elementData.end; + Double[] data = new Double[size]; + for (int i = 0; i < size; i++) { + try { + data[i] = mapper.applyAsDouble(elementData.getWithoutClassCastExceptionCheck(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } + } + + return new UtDoubleStream(data, size); } @Override @@ -180,7 +214,7 @@ public Stream flatMap(Function> @Override public IntStream flatMapToInt(Function mapper) { preconditionCheckWithClosingStream(); - // TODO https://github.com/UnitTestBot/UTBotJava/issues/146 + // as mapper can produce infinite streams, we cannot process it symbolically executeConcretely(); return null; } @@ -188,7 +222,7 @@ public IntStream flatMapToInt(Function mapper) { @Override public LongStream flatMapToLong(Function mapper) { preconditionCheckWithClosingStream(); - // TODO https://github.com/UnitTestBot/UTBotJava/issues/146 + // as mapper can produce infinite streams, we cannot process it symbolically executeConcretely(); return null; } @@ -196,7 +230,7 @@ public LongStream flatMapToLong(Function mapper @Override public DoubleStream flatMapToDouble(Function mapper) { preconditionCheckWithClosingStream(); - // TODO https://github.com/UnitTestBot/UTBotJava/issues/146 + // as mapper can produce infinite streams, we cannot process it symbolically executeConcretely(); return null; } @@ -207,7 +241,7 @@ public Stream distinct() { preconditionCheckWithClosingStream(); int size = elementData.end; - Object[] distinctElements = new Object[size]; + E[] distinctElements = (E[]) new Object[size]; int distinctSize = 0; for (int i = 0; i < size; i++) { E element = elementData.get(i); @@ -236,7 +270,7 @@ public Stream distinct() { } } - return new UtStream<>((E[]) distinctElements, distinctSize); + return new UtStream<>(distinctElements, distinctSize); } // TODO choose the best sorting https://github.com/UnitTestBot/UTBotJava/issues/188 @@ -251,20 +285,23 @@ public Stream sorted() { return new UtStream<>(); } - Object[] sortedElements = UtArrayMock.copyOf(elementData.toArray(0, size), size); + E[] sortedElements = (E[]) new Object[size]; + for (int i = 0; i < size; i++) { + sortedElements[i] = elementData.get(i); + } // bubble sort for (int i = 0; i < size - 1; i++) { for (int j = 0; j < size - i - 1; j++) { - if (((Comparable) sortedElements[j]).compareTo((E) sortedElements[j + 1]) > 0) { - Object tmp = sortedElements[j]; + if (((Comparable) sortedElements[j]).compareTo(sortedElements[j + 1]) > 0) { + E tmp = sortedElements[j]; sortedElements[j] = sortedElements[j + 1]; sortedElements[j + 1] = tmp; } } } - return new UtStream<>((E[]) sortedElements, size); + return new UtStream<>(sortedElements, size); } // TODO choose the best sorting https://github.com/UnitTestBot/UTBotJava/issues/188 @@ -279,20 +316,23 @@ public Stream sorted(Comparator comparator) { return new UtStream<>(); } - Object[] sortedElements = UtArrayMock.copyOf(elementData.toArray(0, size), size); + E[] sortedElements = (E[]) new Object[size]; + for (int i = 0; i < size; i++) { + sortedElements[i] = elementData.get(i); + } // bubble sort for (int i = 0; i < size - 1; i++) { for (int j = 0; j < size - i - 1; j++) { - if (comparator.compare((E) sortedElements[j], (E) sortedElements[j + 1]) > 0) { - Object tmp = sortedElements[j]; + if (comparator.compare(sortedElements[j], sortedElements[j + 1]) > 0) { + E tmp = sortedElements[j]; sortedElements[j] = sortedElements[j + 1]; sortedElements[j + 1] = tmp; } } } - return new UtStream<>((E[]) sortedElements, size); + return new UtStream<>(sortedElements, size); } @Override @@ -301,7 +341,11 @@ public Stream peek(Consumer action) { int size = elementData.end; for (int i = 0; i < size; i++) { - action.accept(elementData.get(i)); + try { + action.accept(elementData.get(i)); + } catch (Exception e) { + throw new UtStreamConsumingException(e); + } } // returned stream should be opened, so we "reopen" this stream to return it @@ -319,20 +363,25 @@ public Stream limit(long maxSize) { throw new IllegalArgumentException(); } + if (maxSize == 0) { + return new UtStream<>(); + } + assumeOrExecuteConcretely(maxSize <= Integer.MAX_VALUE); int newSize = (int) maxSize; int curSize = elementData.end; - if (newSize == curSize) { - return this; - } - if (newSize > curSize) { newSize = curSize; } - return new UtStream<>((E[]) elementData.toArray(0, newSize), newSize); + E[] elements = (E[]) new Object[newSize]; + for (int i = 0; i < newSize; i++) { + elements[i] = elementData.get(i); + } + + return new UtStream<>(elements, newSize); } @SuppressWarnings("unchecked") @@ -344,10 +393,6 @@ public Stream skip(long n) { throw new IllegalArgumentException(); } - if (n == 0) { - return this; - } - int curSize = elementData.end; if (n > curSize) { return new UtStream<>(); @@ -356,17 +401,30 @@ public Stream skip(long n) { // n is 1...Integer.MAX_VALUE here int newSize = (int) (curSize - n); - return new UtStream<>((E[]) elementData.toArray((int) n, newSize), newSize); + if (newSize == 0) { + return new UtStream<>(); + } + + E[] elements = (E[]) new Object[newSize]; + for (int i = (int) n; i < newSize; i++) { + elements[i] = elementData.get(i); + } + + return new UtStream<>(elements, newSize); } @Override public void forEach(Consumer action) { - peek(action); + try { + peek(action); + } catch (UtStreamConsumingException e) { + // Since this is a terminal operation, we should throw an original exception + } } @Override public void forEachOrdered(Consumer action) { - peek(action); + forEach(action); } @NotNull @@ -655,6 +713,9 @@ public void close() { for (int i = 0; i < closeHandlers.end; i++) { closeHandlers.get(i).run(); } + + // clear handlers + closeHandlers.end = 0; } public class UtStreamIterator implements Iterator { diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtNativeString.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtNativeString.java deleted file mode 100644 index 5d75c6dd05..0000000000 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtNativeString.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.utbot.engine.overrides.strings; - -/** - * An auxiliary class without implementation, - * that specifies interface of interaction with - * smt string objects. - * @see org.utbot.engine.UtNativeStringWrapper - */ -@SuppressWarnings("unused") -public class UtNativeString { - /** - * Constructor, that creates a new symbolic string. - * Length and content can be arbitrary and is set - * via path constraints. - */ - public UtNativeString() {} - - /** - * Constructor, that creates a new string - * that is equal smt expression: int2string(i)> - * - * if i is greater or equal to 0, than - * constructed string will be equal to - * i.toString()>, otherwise, it is undefined - * @param i number, that needs to be converted to string - */ - public UtNativeString(int i) { } - - /** - * Constructor, that creates a new string - * that is equal smt expression: int2string(l) - *

    - * if i is greater or equal to 0, than - * constructed string will be equal to - * l.toString(), otherwise, it is undefined - * @param l number, that needs to be converted to string - */ - public UtNativeString(long l) {} - - /** - * Returned variable's expression is equal to - * mkInt2BV(str.length(this), Long.SIZE_BITS) - * @return the length of this string - */ - public int length() { - return 0; - } - - /** - * If string represent a decimal positive number, - * then returned value is equal to Integer.valueOf(this)> - * Otherwise, the result is equal to -1 - *

    - * Returned variable's expression is equal to - * mkInt2BV(string2int(this), Long.SIZE_BITS) - */ - public int toInteger() { return 0; } - - /** - * If string represent a decimal positive number, - * then returned value is equal to Long.valueOf(this)> - * Otherwise, the result is equal to -1L - *

    - * Returned variable's expression is equal to - * mkInt2BV(string2int(this), Long.SIZE_BITS)> - */ - public long toLong() { return 0; } - - /** - * If i in valid index range of string, then - * a returned value's expression is equal to - * mkSeqNth(this, i).cast(char) - * @param i the index of char value to be returned - * @return the specified char value - */ - public char charAt(int i) { - return '\0'; - } - - /** - * Returns a char array with the same content as this string, - * shifted by offset indexes to the left. - *

    - * The returned value's UtExpression is equal to - * UtStringToArray(this, offset). - * @param offset - the number of indexes to be shifted to the left - * @return array of the string chars with shifted indexes by specified offset. - * @see org.utbot.engine.pc.UtArrayToString - */ - public char[] toCharArray(int offset) { return null; } - - /** - * If i in valid index range of string, then - * a returned value's expression is equal to - * mkSeqNth(this, i).cast(int) - * @param i the index of codePoint value to be returned - * @return the specified codePoint value - */ - public int codePointAt(int i) { - return 0; - } -} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtString.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtString.java index 9db996fb52..2d95283ada 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtString.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtString.java @@ -13,31 +13,13 @@ import static org.utbot.engine.overrides.UtOverrideMock.visit; import static java.lang.Math.min; -@SuppressWarnings({"ConstantConditions", "unused"}) +@SuppressWarnings("unused") public class UtString implements java.io.Serializable, Comparable, CharSequence { char[] value; int length; private static final long serialVersionUID = -6849794470754667710L; - public UtString(UtNativeString str) { - visit(this); - length = str.length(); - value = str.toCharArray(0); - } - - public static UtNativeString toUtNativeString(String s, int offset) { - char[] value = s.toCharArray(); - int length = value.length; - UtNativeString nativeString = new UtNativeString(); - assume(nativeString.length() == length - offset); - assume(nativeString.length() <= 2); - for (int i = offset; i < length; i++) { - assume(nativeString.charAt(i - offset) == value[i]); - } - return nativeString; - } - public UtString() { visit(this); value = new char[0]; @@ -774,7 +756,7 @@ public String concat(String str) { } char[] newValue = new char[length + str.length()]; UtArrayMock.arraycopy(value, 0, newValue, 0, length); - UtArrayMock.arraycopy(otherVal, 0, newValue, length + 1, str.length()); + UtArrayMock.arraycopy(otherVal, 0, newValue, length, str.length()); return new String(newValue); } @@ -866,47 +848,22 @@ public String replace(CharSequence target, CharSequence replacement) { return toString().replace(target, replacement); } + private String[] splitWithLimitImpl(String regex, int limit) { + return toString().split(regex, limit); + } + public String[] split(String regex, int limit) { - preconditionCheck(); - if (regex == null) { - throw new NullPointerException(); - } - if (limit < 0) { - throw new IllegalArgumentException(); - } - if (regex.length() == 0) { - int size = limit == 0 ? length + 1 : min(limit, length + 1); - String[] strings = new String[size]; - strings[size] = substring(size - 1); - // TODO remove assume - assume(size < 10); - for (int i = 0; i < size - 1; i++) { - strings[i] = Character.toString(value[i]); - } - return strings; - } - assume(regex.length() < 10); + return splitWithLimitImpl(regex, limit); + } + + private String[] splitImpl(String regex) { executeConcretely(); - return toStringImpl().split(regex, limit); + return toString().split(regex); } public String[] split(String regex) { preconditionCheck(); - if (regex == null) { - throw new NullPointerException(); - } - if (regex.length() == 0) { - String[] strings = new String[length + 1]; - strings[length] = ""; - // TODO remove assume - assume(length <= 25); - for (int i = 0; i < length; i++) { - strings[i] = Character.toString(value[i]); - } - return strings; - } - executeConcretely(); - return toStringImpl().split(regex); + return splitImpl(regex); } public String toLowerCase(Locale locale) { diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtStringBuffer.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtStringBuffer.java index 7931fc166c..d778a49ce3 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtStringBuffer.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtStringBuffer.java @@ -450,7 +450,7 @@ public StringBuffer insert(int dstOffset, CharSequence s, int start, int end) { } public StringBuffer insert(int offset, boolean b) { - return insert(offset, String.valueOf(b)); + return insert(offset, Boolean.toString(b)); } public StringBuffer insert(int offset, char c) { @@ -463,19 +463,19 @@ public StringBuffer insert(int offset, char c) { } public StringBuffer insert(int offset, int i) { - return insert(offset, String.valueOf(i)); + return insert(offset, Integer.toString(i)); } public StringBuffer insert(int offset, long l) { - return insert(offset, String.valueOf(l)); + return insert(offset, Long.toString(l)); } public StringBuffer insert(int offset, float f) { - return insert(offset, String.valueOf(f)); + return insert(offset, Float.toString(f)); } public StringBuffer insert(int offset, double d) { - return insert(offset, String.valueOf(d)); + return insert(offset, Double.toString(d)); } public int indexOf(String str) { diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtStringBuilder.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtStringBuilder.java index 4e06aaf24d..b627fa2aa1 100644 --- a/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtStringBuilder.java +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/strings/UtStringBuilder.java @@ -451,7 +451,7 @@ public StringBuilder insert(int dstOffset, CharSequence s, int start, int end) { } public StringBuilder insert(int offset, boolean b) { - return insert(offset, String.valueOf(b)); + return insert(offset, Boolean.toString(b)); } public StringBuilder insert(int offset, char c) { @@ -464,19 +464,19 @@ public StringBuilder insert(int offset, char c) { } public StringBuilder insert(int offset, int i) { - return insert(offset, String.valueOf(i)); + return insert(offset, Integer.toString(i)); } public StringBuilder insert(int offset, long l) { - return insert(offset, String.valueOf(l)); + return insert(offset, Long.toString(l)); } public StringBuilder insert(int offset, float f) { - return insert(offset, String.valueOf(f)); + return insert(offset, Float.toString(f)); } public StringBuilder insert(int offset, double d) { - return insert(offset, String.valueOf(d)); + return insert(offset, Double.toString(d)); } public int indexOf(String str) { diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java new file mode 100644 index 0000000000..f1ea088412 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/CompletableFuture.java @@ -0,0 +1,30 @@ +package org.utbot.engine.overrides.threads; + +import org.utbot.api.annotation.UtClassMock; + +import java.util.concurrent.Executor; +import java.util.function.Supplier; + +@UtClassMock(target = java.util.concurrent.CompletableFuture.class, internalUsage = true) +public class CompletableFuture { + public static java.util.concurrent.CompletableFuture runAsync(Runnable runnable) { + java.util.concurrent.CompletableFuture future = new java.util.concurrent.CompletableFuture<>(); + + return future.thenRun(runnable); + } + + @SuppressWarnings("unused") + public static java.util.concurrent.CompletableFuture runAsync(Runnable runnable, Executor ignoredExecutor) { + return runAsync(runnable); + } + + public static java.util.concurrent.CompletableFuture supplyAsync(Supplier supplier) { + try { + final U value = supplier.get(); + + return new UtCompletableFuture<>(value).toCompletableFuture(); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/Executors.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/Executors.java new file mode 100644 index 0000000000..f04c0661e5 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/Executors.java @@ -0,0 +1,120 @@ +package org.utbot.engine.overrides.threads; + +import org.utbot.api.annotation.UtClassMock; +import org.utbot.api.mock.UtMock; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; + +@UtClassMock(target = java.util.concurrent.Executors.class, internalUsage = true) +public class Executors { + public static ExecutorService newFixedThreadPool(int nThreads) { + if (nThreads <= 0) { + throw new IllegalArgumentException(); + } + + return new UtExecutorService(); + } + + public static ExecutorService newWorkStealingPool() { + return new UtExecutorService(); + } + + public static ExecutorService newWorkStealingPool(int parallelism) { + if (parallelism <= 0) { + throw new IllegalArgumentException(); + } + + return new UtExecutorService(); + } + + public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { + if (threadFactory == null) { + throw new NullPointerException(); + } + + return newFixedThreadPool(nThreads); + } + + public static ExecutorService newSingleThreadExecutor() { + return new UtExecutorService(); + } + + public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { + if (threadFactory == null) { + throw new NullPointerException(); + } + + return new UtExecutorService(); + } + + public static ExecutorService newCachedThreadPool() { + return new UtExecutorService(); + } + + public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { + if (threadFactory == null) { + throw new NullPointerException(); + } + + return new UtExecutorService(); + } + + public static ScheduledExecutorService newSingleThreadScheduledExecutor() { + return new UtExecutorService(); + } + + public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { + if (threadFactory == null) { + throw new NullPointerException(); + } + + return new UtExecutorService(); + } + + public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { + if (corePoolSize < 0) { + throw new IllegalArgumentException(); + } + + return new UtExecutorService(); + } + + public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) { + if (corePoolSize < 0) { + throw new IllegalArgumentException(); + } + + if (threadFactory == null) { + throw new NullPointerException(); + } + + return new UtExecutorService(); + } + + public static ExecutorService unconfigurableExecutorService(ExecutorService executor) { + if (executor == null) { + throw new NullPointerException(); + } + + return new UtExecutorService(); + } + + public static ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor) { + if (executor == null) { + throw new NullPointerException(); + } + + return new UtExecutorService(); + } + + public static ThreadFactory defaultThreadFactory() { + // TODO make a wrapper? + return UtMock.makeSymbolic(); + } + + public static ThreadFactory privilegedThreadFactory() { + return defaultThreadFactory(); + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/ThreadFactory.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/ThreadFactory.java new file mode 100644 index 0000000000..f143c7be49 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/ThreadFactory.java @@ -0,0 +1,12 @@ +package org.utbot.engine.overrides.threads; + +import org.jetbrains.annotations.NotNull; +import org.utbot.api.annotation.UtClassMock; + +@UtClassMock(target = ThreadFactory.class, internalUsage = true) +public class ThreadFactory implements java.util.concurrent.ThreadFactory { + @Override + public Thread newThread(@NotNull Runnable r) { + return new Thread(r); + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java new file mode 100644 index 0000000000..c87f14e356 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCompletableFuture.java @@ -0,0 +1,453 @@ +package org.utbot.engine.overrides.threads; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +public class UtCompletableFuture implements ScheduledFuture, CompletionStage { + T result; + + Throwable exception; + + public UtCompletableFuture(T result) { + this.result = result; + } + + public UtCompletableFuture() {} + + public UtCompletableFuture(Throwable exception) { + this.exception = exception; + } + + public UtCompletableFuture(UtCompletableFuture future) { + result = future.result; + exception = future.exception; + } + + public void eqGenericType(T ignoredValue) { + // Will be processed symbolically + } + + public void preconditionCheck() { + eqGenericType(result); + } + + @Override + public CompletableFuture thenApply(Function fn) { + preconditionCheck(); + + final U nextResult; + try { + nextResult = fn.apply(result); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture<>(nextResult).toCompletableFuture(); + } + + @Override + public CompletableFuture thenApplyAsync(Function fn) { + return thenApply(fn); + } + + @Override + public CompletableFuture thenApplyAsync(Function fn, Executor executor) { + return thenApply(fn); + } + + @Override + public CompletableFuture thenAccept(Consumer action) { + preconditionCheck(); + + try { + action.accept(result); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture().toCompletableFuture(); + } + + @Override + public CompletableFuture thenAcceptAsync(Consumer action) { + return thenAccept(action); + } + + @Override + public CompletableFuture thenAcceptAsync(Consumer action, Executor executor) { + return thenAccept(action); + } + + @Override + public CompletableFuture thenRun(Runnable action) { + preconditionCheck(); + + try { + action.run(); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture().toCompletableFuture(); + } + + @Override + public CompletableFuture thenRunAsync(Runnable action) { + return thenRun(action); + } + + @Override + public CompletableFuture thenRunAsync(Runnable action, Executor executor) { + return thenRun(action); + } + + @Override + public CompletableFuture thenCombine(CompletionStage other, BiFunction fn) { + preconditionCheck(); + + final CompletableFuture completableFuture = other.toCompletableFuture(); + if (fn == null || completableFuture == null) { + throw new NullPointerException(); + } + + U otherResult; + try { + otherResult = completableFuture.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + + final V nextResult; + try { + nextResult = fn.apply(result, otherResult); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture<>(nextResult).toCompletableFuture(); + } + + @Override + public CompletableFuture thenCombineAsync(CompletionStage other, BiFunction fn) { + return thenCombine(other, fn); + } + + @Override + public CompletableFuture thenCombineAsync(CompletionStage other, BiFunction fn, Executor executor) { + return thenCombine(other, fn); + } + + @Override + public CompletableFuture thenAcceptBoth(CompletionStage other, BiConsumer action) { + preconditionCheck(); + + final CompletableFuture completableFuture = other.toCompletableFuture(); + if (action == null || completableFuture == null) { + throw new NullPointerException(); + } + + U otherResult; + try { + otherResult = completableFuture.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + + try { + action.accept(result, otherResult); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture().toCompletableFuture(); + } + + @Override + public CompletableFuture thenAcceptBothAsync(CompletionStage other, BiConsumer action) { + return thenAcceptBoth(other, action); + } + + @Override + public CompletableFuture thenAcceptBothAsync(CompletionStage other, BiConsumer action, Executor executor) { + return thenAcceptBoth(other, action); + } + + @Override + public CompletableFuture runAfterBoth(CompletionStage other, Runnable action) { + preconditionCheck(); + + final CompletableFuture completableFuture = other.toCompletableFuture(); + if (action == null || completableFuture == null) { + throw new NullPointerException(); + } + + try { + action.run(); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture().toCompletableFuture(); + } + + @Override + public CompletableFuture runAfterBothAsync(CompletionStage other, Runnable action) { + return runAfterBoth(other, action); + } + + @Override + public CompletableFuture runAfterBothAsync(CompletionStage other, Runnable action, Executor executor) { + return runAfterBoth(other, action); + } + + @Override + public CompletableFuture applyToEither(CompletionStage other, Function fn) { + preconditionCheck(); + + final CompletableFuture completableFuture = other.toCompletableFuture(); + if (fn == null || completableFuture == null) { + throw new NullPointerException(); + } + + final T eitherResult; + try { + eitherResult = (result != null) ? result : completableFuture.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + + final U newResult; + try { + newResult = fn.apply(eitherResult); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture<>(newResult).toCompletableFuture(); + } + + @Override + public CompletableFuture applyToEitherAsync(CompletionStage other, Function fn) { + return applyToEither(other, fn); + } + + @Override + public CompletableFuture applyToEitherAsync(CompletionStage other, Function fn, Executor executor) { + return applyToEither(other, fn); + } + + @Override + public CompletableFuture acceptEither(CompletionStage other, Consumer action) { + preconditionCheck(); + + final CompletableFuture completableFuture = other.toCompletableFuture(); + if (action == null || completableFuture == null) { + throw new NullPointerException(); + } + + final T eitherResult; + try { + eitherResult = (result != null) ? result : completableFuture.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + + try { + action.accept(eitherResult); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture().toCompletableFuture(); + } + + @Override + public CompletableFuture acceptEitherAsync(CompletionStage other, Consumer action) { + return acceptEither(other, action); + } + + @Override + public CompletableFuture acceptEitherAsync(CompletionStage other, Consumer action, Executor executor) { + return acceptEither(other, action); + } + + @Override + public CompletableFuture runAfterEither(CompletionStage other, Runnable action) { + preconditionCheck(); + + try { + action.run(); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture().toCompletableFuture(); + } + + @Override + public CompletableFuture runAfterEitherAsync(CompletionStage other, Runnable action) { + return runAfterEither(other, action); + } + + @Override + public CompletableFuture runAfterEitherAsync(CompletionStage other, Runnable action, Executor executor) { + return runAfterEither(other, action); + } + + @Override + public CompletableFuture thenCompose(Function> fn) { + preconditionCheck(); + + return fn.apply(result).toCompletableFuture(); + } + + @Override + public CompletableFuture thenComposeAsync(Function> fn) { + return thenCompose(fn); + } + + @Override + public CompletableFuture thenComposeAsync(Function> fn, Executor executor) { + return thenCompose(fn); + } + + @Override + public CompletableFuture handle(BiFunction fn) { + preconditionCheck(); + + U newResult; + try { + newResult = fn.apply(result, exception); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return new UtCompletableFuture<>(newResult).toCompletableFuture(); + } + + @Override + public CompletableFuture handleAsync(BiFunction fn) { + return handle(fn); + } + + @Override + public CompletableFuture handleAsync(BiFunction fn, Executor executor) { + return handle(fn); + } + + @Override + public CompletableFuture whenComplete(BiConsumer action) { + preconditionCheck(); + + final UtCompletableFuture next = new UtCompletableFuture<>(this); + try { + action.accept(next.result, next.exception); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + + return next.toCompletableFuture(); + } + + @Override + public CompletableFuture whenCompleteAsync(BiConsumer action) { + return whenComplete(action); + } + + @Override + public CompletableFuture whenCompleteAsync(BiConsumer action, Executor executor) { + return whenComplete(action); + } + + @Override + public CompletableFuture exceptionally(Function fn) { + preconditionCheck(); + + if (fn == null) { + throw new NullPointerException(); + } + + if (exception != null) { + try { + final T exceptionalResult = fn.apply(exception); + return new UtCompletableFuture<>(exceptionalResult).toCompletableFuture(); + } catch (Throwable e) { + return new UtCompletableFuture(e).toCompletableFuture(); + } + } + + return new UtCompletableFuture<>(result).toCompletableFuture(); + } + + @Override + public CompletableFuture toCompletableFuture() { + // Will be processed symbolically + return null; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + preconditionCheck(); + // Tasks could not be canceled since they are supposed to be executed immediately + return false; + } + + @Override + public boolean isCancelled() { + preconditionCheck(); + // Tasks could not be canceled since they are supposed to be executed immediately + return false; + } + + @Override + public boolean isDone() { + preconditionCheck(); + + return true; + } + + @Override + public T get() throws InterruptedException, ExecutionException { + preconditionCheck(); + + if (exception != null) { + throw new ExecutionException(exception); + } + + return result; + } + + @Override + public T get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return get(); + } + + @Override + public long getDelay(@NotNull TimeUnit unit) { + return 0; + } + + @Override + public int compareTo(@NotNull Delayed o) { + if (o == this) { // compare zero if same object{ + return 0; + } + + long diff = getDelay(NANOSECONDS) - o.getDelay(NANOSECONDS); + return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCountDownLatch.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCountDownLatch.java new file mode 100644 index 0000000000..f46bea70f0 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtCountDownLatch.java @@ -0,0 +1,63 @@ +package org.utbot.engine.overrides.threads; + +import java.util.concurrent.TimeUnit; + +import static org.utbot.api.mock.UtMock.assume; +import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; +import static org.utbot.engine.overrides.UtOverrideMock.visit; + +public class UtCountDownLatch { + private int count; + + public UtCountDownLatch(int count) { + if (count < 0) { + throw new IllegalArgumentException(); + } + + visit(this); + this.count = count; + } + + void preconditionCheck() { + if (alreadyVisited(this)) { + return; + } + + assume(count >= 0); + + visit(this); + } + + public void await() { + preconditionCheck(); + // Do nothing + } + + public boolean await(long ignoredTimeout, TimeUnit ignoredUnit) { + preconditionCheck(); + + return count == 0; + } + + public void countDown() { + preconditionCheck(); + + if (count != 0) { + count--; + } + } + + public long getCount() { + preconditionCheck(); + + return count; + } + + @Override + public String toString() { + preconditionCheck(); + // Actually, the real string representation also contains some meta-information about this class, + // but it looks redundant for this wrapper + return String.valueOf(count); + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtExecutorService.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtExecutorService.java new file mode 100644 index 0000000000..b5246654b9 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtExecutorService.java @@ -0,0 +1,160 @@ +package org.utbot.engine.overrides.threads; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class UtExecutorService implements ScheduledExecutorService { + private boolean isShutdown; + private boolean isTerminated; + + @Override + public void shutdown() { + isShutdown = true; + isTerminated = true; + } + + @NotNull + @Override + public List shutdownNow() { + shutdown(); + // Since all tasks are processed immediately, there are no waiting tasks + return new ArrayList<>(); + } + + @Override + public boolean isShutdown() { + return isShutdown; + } + + @Override + public boolean isTerminated() { + return isTerminated; + } + + @Override + public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) { + // No need to wait tasks + return true; + } + + @NotNull + @Override + public Future submit(@NotNull Callable task) { + try { + T result = task.call(); + return new UtCompletableFuture<>(result); + } catch (Exception e) { + return new UtCompletableFuture<>(e); + } + } + + @NotNull + @Override + public Future submit(@NotNull Runnable task, T result) { + try { + task.run(); + return new UtCompletableFuture<>(result); + } catch (Exception e) { + return new UtCompletableFuture<>(e); + } + } + + @NotNull + @Override + public Future submit(@NotNull Runnable task) { + return submit(task, null); + } + + @NotNull + @Override + public List> invokeAll(@NotNull Collection> tasks) { + List> results = new ArrayList<>(); + for (Callable task : tasks) { + results.add(submit(task)); + } + + return results; + } + + @NotNull + @Override + public List> invokeAll(@NotNull Collection> tasks, long timeout, @NotNull TimeUnit unit) { + return invokeAll(tasks); + } + + @NotNull + @Override + public T invokeAny(@NotNull Collection> tasks) throws ExecutionException { + for (Callable task : tasks) { + try { + return task.call(); + } catch (Exception e) { + // Do nothing + } + } + + // ExecutionException no-parameters constructor is protected + throw new ExecutionException(new RuntimeException()); + } + + @Override + public T invokeAny(@NotNull Collection> tasks, long timeout, @NotNull TimeUnit unit) throws ExecutionException { + return invokeAny(tasks); + } + + @Override + public void execute(@NotNull Runnable command) { + command.run(); + } + + @NotNull + @Override + public ScheduledFuture schedule(@NotNull Runnable command, long delay, @NotNull TimeUnit unit) { + try { + command.run(); + return new UtCompletableFuture<>(); + } catch (Exception e) { + return new UtCompletableFuture<>(e); + } + } + + @NotNull + @Override + public ScheduledFuture schedule(@NotNull Callable callable, long delay, @NotNull TimeUnit unit) { + try { + V result = callable.call(); + return new UtCompletableFuture<>(result); + } catch (Exception e) { + return new UtCompletableFuture<>(e); + } + } + + @NotNull + @Override + public ScheduledFuture scheduleAtFixedRate(@NotNull Runnable command, long initialDelay, long period, @NotNull TimeUnit unit) { + if (period <= 0) { + throw new IllegalArgumentException(); + } + + return schedule(command, initialDelay, unit); + } + + @NotNull + @Override + public ScheduledFuture scheduleWithFixedDelay(@NotNull Runnable command, long initialDelay, long delay, @NotNull TimeUnit unit) { + if (delay <= 0) { + throw new IllegalArgumentException(); + } + + return schedule(command, initialDelay, unit); + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java new file mode 100644 index 0000000000..8a8b3560c6 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThread.java @@ -0,0 +1,434 @@ +package org.utbot.engine.overrides.threads; + +import org.utbot.api.mock.UtMock; + +import java.security.AccessControlContext; +import java.util.Map; + +import static org.utbot.api.mock.UtMock.assume; +import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; +import static org.utbot.engine.overrides.UtOverrideMock.visit; + +@SuppressWarnings("unused") +public class UtThread { + private String name; + private int priority; + + private boolean daemon; + + private final Runnable target; + + private final ThreadGroup group; + + /* The context ClassLoader for this thread */ + private ClassLoader contextClassLoader; + + /* For autonumbering anonymous threads. */ + private static int threadInitNumber = 0; + + private static int nextThreadNum() { + return threadInitNumber++; + } + + /* + * Thread ID + */ + private final long tid; + + /* For generating thread ID */ + private static long threadSeqNumber; + + private static long nextThreadID() { + return ++threadSeqNumber; + } + + private boolean isInterrupted; + + // This field is required by ThreadLocal class. The real type is a package-private type ThreadLocal.ThreadLocalMap + Object threadLocals; + + // This field is required by InheritableThreadLocal class. The real type is a package-private type ThreadLocal.ThreadLocalMap + Object inheritableThreadLocals; + + /** + * The minimum priority that a thread can have. + */ + public static final int MIN_PRIORITY = 1; + + /** + * The maximum priority that a thread can have. + */ + public static final int MAX_PRIORITY = 10; + + // null unless explicitly set + private volatile Thread.UncaughtExceptionHandler uncaughtExceptionHandler; + + // null unless explicitly set + private static volatile Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler; + + public static UtThread currentThread() { + return UtMock.makeSymbolic(); + } + + public static void yield() { + // Do nothing + } + + @SuppressWarnings("RedundantThrows") + public static void sleep(long ignoredMillis) throws InterruptedException { + // Do nothing + } + + @SuppressWarnings("RedundantThrows") + public static void sleep(long millis, int nanos) throws InterruptedException { + if (millis < 0) { + throw new IllegalArgumentException(); + } + + if (nanos < 0 || nanos > 999999) { + throw new IllegalArgumentException(); + } + } + + public UtThread() { + this(null, null, UtMock.makeSymbolic(), 0); + } + + public UtThread(Runnable target) { + this(null, target, UtMock.makeSymbolic(), 0); + } + + public UtThread(ThreadGroup group, Runnable target) { + this(group, target, UtMock.makeSymbolic(), 0); + } + + public UtThread(String name) { + this(null, null, name, 0); + } + + public UtThread(ThreadGroup group, String name) { + this(group, null, name, 0); + } + + public UtThread(Runnable target, String name) { + this(null, target, name, 0); + } + + public UtThread(ThreadGroup group, Runnable target, String name) { + this(group, target, name, 0); + } + + public UtThread(ThreadGroup group, Runnable target, String name, + long stackSize) { + this(group, target, name, stackSize, null, true); + } + + public UtThread(ThreadGroup group, Runnable target, String name, + long stackSize, boolean inheritUtThreadLocals) { + this(group, target, name, stackSize, null, inheritUtThreadLocals); + } + + private UtThread(ThreadGroup g, Runnable target, String name, + long stackSize, AccessControlContext ignoredAcc, + boolean ignoredInheritUtThreadLocals) { + visit(this); + + if (name == null) { + throw new NullPointerException(); + } + + this.name = name; + + if (g == null) { + g = UtMock.makeSymbolic(); + } + + this.group = g; + this.daemon = UtMock.makeSymbolic(); + + final Integer priority = UtMock.makeSymbolic(); + assume(priority >= MIN_PRIORITY); + assume(priority <= MIN_PRIORITY); + this.priority = priority; + setPriority(this.priority); + + this.contextClassLoader = UtMock.makeSymbolic(); + this.target = target; + + this.tid = nextThreadID(); + + // We need to make it possible to cast these fields to the ThreadLocal.ThreadLocalMap type + UtMock.disableClassCastExceptionCheck(threadLocals); + UtMock.disableClassCastExceptionCheck(inheritableThreadLocals); + } + + public void preconditionCheck() { + if (alreadyVisited(this)) { + return; + } + + assume(name != null); + + assume(priority >= MIN_PRIORITY); + assume(priority <= MIN_PRIORITY); + + assume(group != null); + assume(contextClassLoader != null); + + assume(threadInitNumber >= 0); + assume(tid >= 0); + assume(threadSeqNumber >= 0); + + visit(this); + } + + public void start() { + run(); + } + + public void run() { + preconditionCheck(); + + if (target != null) { + target.run(); + } + } + + public final void stop() { + preconditionCheck(); + // DO nothing + } + + public void interrupt() { + preconditionCheck(); + // Set interrupted status + isInterrupted = true; + } + + public static boolean interrupted() { + return UtMock.makeSymbolic(); + } + + public boolean isInterrupted() { + preconditionCheck(); + + return isInterrupted; + } + + private boolean isInterrupted(boolean clearInterrupted) { + preconditionCheck(); + + boolean result = isInterrupted; + + if (clearInterrupted) { + isInterrupted = false; + } + + return result; + } + + public final boolean isAlive() { + preconditionCheck(); + + return UtMock.makeSymbolic(); + } + + public final void suspend() { + preconditionCheck(); + // Do nothing + } + + public final void resume() { + preconditionCheck(); + // Do nothing + } + + public final void setPriority(int newPriority) { + preconditionCheck(); + + if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { + throw new IllegalArgumentException(); + } + + if (group != null) { + if (newPriority > group.getMaxPriority()) { + newPriority = group.getMaxPriority(); + } + + priority = newPriority; + } + } + + public final int getPriority() { + preconditionCheck(); + + return priority; + } + + public final void setName(String name) { + preconditionCheck(); + + if (name == null) { + throw new NullPointerException(); + } + + this.name = name; + } + + public final String getName() { + preconditionCheck(); + + return name; + } + + public final ThreadGroup getThreadGroup() { + preconditionCheck(); + + return group; + } + + public static int activeCount() { + final Integer result = UtMock.makeSymbolic(); + assume(result >= 0); + + return result; + } + + public static int enumerate(UtThread[] tarray) { + Integer length = UtMock.makeSymbolic(); + assume(length >= 0); + assume(length <= tarray.length); + + for (int i = 0; i < length; i++) { + tarray[i] = UtMock.makeSymbolic(); + } + + return length; + } + + public int countStackFrames() { + preconditionCheck(); + + return UtMock.makeSymbolic(); + } + + public final void join(long millis) throws InterruptedException { + preconditionCheck(); + + if (millis < 0) { + throw new IllegalArgumentException(); + } + + // Do nothing + } + + public final void join(long millis, int nanos) throws InterruptedException { + preconditionCheck(); + + if (millis < 0) { + throw new IllegalArgumentException(); + } + + if (nanos < 0 || nanos > 999999) { + throw new IllegalArgumentException(); + } + + // Do nothing + } + + public final void join() throws InterruptedException { + preconditionCheck(); + + join(0); + } + + public static void dumpStack() { + // Do nothing + } + + public final void setDaemon(boolean on) { + preconditionCheck(); + + daemon = on; + } + + public final boolean isDaemon() { + preconditionCheck(); + + return daemon; + } + + public String toString() { + preconditionCheck(); + + if (group != null) { + return "Thread[" + getName() + "," + getPriority() + "," + + group.getName() + "]"; + } else { + return "Thread[" + getName() + "," + getPriority() + "," + + "" + "]"; + } + } + + public ClassLoader getContextClassLoader() { + preconditionCheck(); + + return contextClassLoader; + } + + public void setContextClassLoader(ClassLoader cl) { + preconditionCheck(); + + contextClassLoader = cl; + } + + public static boolean holdsLock(Object obj) { + if (obj == null) { + throw new NullPointerException(); + } + + return UtMock.makeSymbolic(); + } + + public StackTraceElement[] getStackTrace() { + preconditionCheck(); + + return UtMock.makeSymbolic(); + } + + public static Map getAllStackTraces() { + return UtMock.makeSymbolic(); + } + + public long getId() { + preconditionCheck(); + + return tid; + } + + public Thread.State getState() { + preconditionCheck(); + + return UtMock.makeSymbolic(); + } + + public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) { + defaultUncaughtExceptionHandler = eh; + } + + public static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() { + return defaultUncaughtExceptionHandler; + } + + public Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() { + preconditionCheck(); + + return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; + } + + public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) { + preconditionCheck(); + + uncaughtExceptionHandler = eh; + } +} diff --git a/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java new file mode 100644 index 0000000000..5450ed7706 --- /dev/null +++ b/utbot-framework/src/main/java/org/utbot/engine/overrides/threads/UtThreadGroup.java @@ -0,0 +1,327 @@ +package org.utbot.engine.overrides.threads; + +import org.utbot.api.mock.UtMock; + +import java.util.Arrays; + +import static org.utbot.api.mock.UtMock.assume; +import static org.utbot.engine.overrides.UtOverrideMock.alreadyVisited; +import static org.utbot.engine.overrides.UtOverrideMock.visit; + +@SuppressWarnings("unused") +public class UtThreadGroup implements Thread.UncaughtExceptionHandler { + private final ThreadGroup parent; + String name; + int maxPriority; + boolean destroyed; + boolean daemon; + + int nUnstartedThreads = 0; + int nthreads; + Thread[] threads; + + int ngroups; + ThreadGroup[] groups; + + public UtThreadGroup(String name) { + this(UtThread.currentThread().getThreadGroup(), name); + } + + public UtThreadGroup(ThreadGroup parent, String name) { + visit(this); + + this.name = name; + + final Integer maxPriority = UtMock.makeSymbolic(); + assume(maxPriority >= UtThread.MIN_PRIORITY); + assume(maxPriority <= UtThread.MAX_PRIORITY); + this.maxPriority = maxPriority; + + this.daemon = UtMock.makeSymbolic(); + this.parent = parent; + } + + public void preconditionCheck() { + if (alreadyVisited(this)) { + return; + } + + assume(parent != null); + assume(name != null); + + assume(maxPriority >= UtThread.MIN_PRIORITY); + assume(maxPriority <= UtThread.MAX_PRIORITY); + + assume(nUnstartedThreads >= 0); + assume(ngroups >= 0); + + visit(this); + } + + public final String getName() { + preconditionCheck(); + + return name; + } + + public final ThreadGroup getParent() { + preconditionCheck(); + + return parent; + } + + public final int getMaxPriority() { + preconditionCheck(); + + return maxPriority; + } + + public final boolean isDaemon() { + preconditionCheck(); + + return daemon; + } + + public boolean isDestroyed() { + preconditionCheck(); + + return destroyed; + } + + public final void setDaemon(boolean daemon) { + preconditionCheck(); + + this.daemon = daemon; + } + + public final void setMaxPriority(int pri) { + preconditionCheck(); + + if (pri < UtThread.MIN_PRIORITY || pri > UtThread.MAX_PRIORITY) { + return; + } + + for (int i = 0; i < ngroups; i++) { + groups[i].setMaxPriority(pri); + } + } + + public final boolean parentOf(ThreadGroup g) { + preconditionCheck(); + + return UtMock.makeSymbolic(); + } + + public final void checkAccess() { + preconditionCheck(); + // Do nothing + } + + public int activeCount() { + preconditionCheck(); + + if (destroyed) { + return 0; + } + + final Integer result = UtMock.makeSymbolic(); + assume(result >= 0); + return result; + } + + public int enumerate(Thread[] list) { + return enumerate(list, 0, true); + } + + public int enumerate(Thread[] list, boolean recurse) { + return enumerate(list, 0, recurse); + } + + @SuppressWarnings({"SameParameterValue", "ParameterCanBeLocal"}) + private int enumerate(Thread[] list, int ignoredN, boolean ignoredRecurse) { + preconditionCheck(); + + if (destroyed) { + return 0; + } + + list = UtMock.makeSymbolic(); + + final Integer result = UtMock.makeSymbolic(); + assume(result <= list.length); + assume(result >= 0); + + return result; + } + + public int activeGroupCount() { + preconditionCheck(); + + if (destroyed) { + return 0; + } + + final Integer result = UtMock.makeSymbolic(); + assume(result >= 0); + return result; + } + + public int enumerate(ThreadGroup[] list) { + return enumerate(list, 0, true); + } + + public int enumerate(ThreadGroup[] list, boolean recurse) { + return enumerate(list, 0, recurse); + } + + @SuppressWarnings({"SameParameterValue", "ParameterCanBeLocal"}) + private int enumerate(ThreadGroup[] list, int ignoredN, boolean ignoredRecurse) { + preconditionCheck(); + + if (destroyed) { + return 0; + } + + list = UtMock.makeSymbolic(); + + final Integer result = UtMock.makeSymbolic(); + assume(result <= list.length); + assume(result >= 0); + + return result; + } + + public final void stop() { + preconditionCheck(); + // Do nothing + } + + public final void interrupt() { + preconditionCheck(); + + for (int i = 0; i < nthreads; i++) { + threads[i].interrupt(); + } + + for (int i = 0; i < ngroups; i++) { + groups[i].interrupt(); + } + } + + public final void suspend() { + preconditionCheck(); + // Do nothing + } + + public final void resume() { + preconditionCheck(); + // Do nothing + } + + public final void destroy() { + preconditionCheck(); + + if (destroyed || nthreads > 0) { + throw new IllegalThreadStateException(); + } + + if (parent != null) { + destroyed = true; + ngroups = 0; + groups = null; + nthreads = 0; + threads = null; + } + for (int i = 0; i < ngroups; i += 1) { + groups[i].destroy(); + } + } + + private void add(ThreadGroup g) { + preconditionCheck(); + + if (destroyed) { + throw new IllegalThreadStateException(); + } + + if (groups == null) { + groups = new ThreadGroup[4]; + } else if (ngroups == groups.length) { + groups = Arrays.copyOf(groups, ngroups * 2); + } + groups[ngroups] = g; + + ngroups++; + } + + private void remove(ThreadGroup g) { + preconditionCheck(); + + if (destroyed) { + return; + } + + for (int i = 0; i < ngroups; i++) { + if (groups[i] == g) { + ngroups -= 1; + System.arraycopy(groups, i + 1, groups, i, ngroups - i); + groups[ngroups] = null; + break; + } + } + + if (daemon && nthreads == 0 && nUnstartedThreads == 0 && ngroups == 0) { + destroy(); + } + } + + void addUnstarted() { + preconditionCheck(); + + if (destroyed) { + throw new IllegalThreadStateException(); + } + + nUnstartedThreads++; + } + + void add(Thread t) { + preconditionCheck(); + + if (destroyed) { + throw new IllegalThreadStateException(); + } + + if (threads == null) { + threads = new Thread[4]; + } else if (nthreads == threads.length) { + threads = Arrays.copyOf(threads, nthreads * 2); + } + threads[nthreads] = t; + + nthreads++; + nUnstartedThreads--; + } + + public void list() { + preconditionCheck(); + // Do nothing + } + + public void uncaughtException(Thread t, Throwable e) { + preconditionCheck(); + // Do nothing + } + + public boolean allowThreadSuspension(boolean b) { + preconditionCheck(); + + return true; + } + + public String toString() { + preconditionCheck(); + + return getClass().getName() + "[name=" + getName() + ",maxpri=" + maxPriority + "]"; + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/analytics/AnalyticsConfigureUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/analytics/AnalyticsConfigureUtil.kt new file mode 100644 index 0000000000..82c74e887f --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/analytics/AnalyticsConfigureUtil.kt @@ -0,0 +1,53 @@ +package org.utbot.analytics + +import mu.KotlinLogging +import org.utbot.framework.PathSelectorType +import org.utbot.framework.UtSettings + +private val logger = KotlinLogging.logger {} + +object AnalyticsConfigureUtil { + /** + * Configures utbot-analytics models for the better path selection. + * + * NOTE: If analytics configuration for the NN Path Selector could not be loaded, + * it switches to the [PathSelectorType.INHERITORS_SELECTOR]. + */ + fun configureML() { + logger.info { "PathSelectorType: ${UtSettings.pathSelectorType}" } + + if (UtSettings.pathSelectorType == PathSelectorType.ML_SELECTOR) { + val analyticsConfigurationClassPath = UtSettings.analyticsConfigurationClassPath + tryToSetUpMLSelector(analyticsConfigurationClassPath) + } + + if (UtSettings.pathSelectorType == PathSelectorType.TORCH_SELECTOR) { + val analyticsConfigurationClassPath = UtSettings.analyticsTorchConfigurationClassPath + tryToSetUpMLSelector(analyticsConfigurationClassPath) + } + } + + private fun tryToSetUpMLSelector(analyticsConfigurationClassPath: String) { + try { + Class.forName(analyticsConfigurationClassPath) + Predictors.stateRewardPredictor = EngineAnalyticsContext.mlPredictorFactory() + + logger.info { "RewardModelPath: ${UtSettings.modelPath}" } + } catch (e: ClassNotFoundException) { + logger.error { + "Configuration of the predictors from the utbot-analytics module described in the class: " + + "$analyticsConfigurationClassPath is not found!" + } + + logger.info(e) { + "Error while initialization of ${UtSettings.pathSelectorType}. Changing pathSelectorType on INHERITORS_SELECTOR" + } + UtSettings.pathSelectorType = PathSelectorType.INHERITORS_SELECTOR + } + catch (e: Exception) { // engine not found, for example + logger.error { e.message } + UtSettings.pathSelectorType = PathSelectorType.INHERITORS_SELECTOR + } + } + +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/analytics/EngineAnalyticsContext.kt b/utbot-framework/src/main/kotlin/org/utbot/analytics/EngineAnalyticsContext.kt index b5624e8874..1cadac1b74 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/analytics/EngineAnalyticsContext.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/analytics/EngineAnalyticsContext.kt @@ -1,10 +1,10 @@ package org.utbot.analytics import org.utbot.engine.InterProceduralUnitGraph -import org.utbot.engine.selectors.NNRewardGuidedSelectorFactory -import org.utbot.engine.selectors.NNRewardGuidedSelectorWithRecalculationFactory -import org.utbot.engine.selectors.NNRewardGuidedSelectorWithoutRecalculationFactory -import org.utbot.framework.NNRewardGuidedSelectorType +import org.utbot.engine.selectors.MLSelectorFactory +import org.utbot.engine.selectors.MLSelectorWithRecalculationFactory +import org.utbot.engine.selectors.MLSelectorWithoutRecalculationFactory +import org.utbot.framework.MLSelectorRecalculationType import org.utbot.framework.UtSettings /** @@ -23,14 +23,14 @@ object EngineAnalyticsContext { } } - val nnRewardGuidedSelectorFactory: NNRewardGuidedSelectorFactory = when (UtSettings.nnRewardGuidedSelectorType) { - NNRewardGuidedSelectorType.WITHOUT_RECALCULATION -> NNRewardGuidedSelectorWithoutRecalculationFactory() - NNRewardGuidedSelectorType.WITH_RECALCULATION -> NNRewardGuidedSelectorWithRecalculationFactory() + val mlSelectorFactory: MLSelectorFactory = when (UtSettings.mlSelectorRecalculationType) { + MLSelectorRecalculationType.WITHOUT_RECALCULATION -> MLSelectorWithoutRecalculationFactory() + MLSelectorRecalculationType.WITH_RECALCULATION -> MLSelectorWithRecalculationFactory() } - var stateRewardPredictorFactory: StateRewardPredictorFactory = object : StateRewardPredictorFactory { - override fun invoke(): StateRewardPredictor { - error("NNStateRewardPredictor factory wasn't provided") + var mlPredictorFactory: MLPredictorFactory = object : MLPredictorFactory { + override fun invoke(): MLPredictor { + error("MLPredictor factory wasn't provided") } } } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/analytics/FeatureExtractor.kt b/utbot-framework/src/main/kotlin/org/utbot/analytics/FeatureExtractor.kt index c86cad1b54..e63ccdcc92 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/analytics/FeatureExtractor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/analytics/FeatureExtractor.kt @@ -1,6 +1,6 @@ package org.utbot.analytics -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState /** * Class that encapsulates work with FeatureExtractor during symbolic execution. diff --git a/utbot-framework/src/main/kotlin/org/utbot/analytics/MLPredictor.kt b/utbot-framework/src/main/kotlin/org/utbot/analytics/MLPredictor.kt new file mode 100644 index 0000000000..3eb90f2d8f --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/analytics/MLPredictor.kt @@ -0,0 +1,6 @@ +package org.utbot.analytics + +/** + * Interface, which should predict reward for state by features list. + */ +interface MLPredictor : UtBotAbstractPredictor, Double> \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/analytics/MLPredictorFactory.kt b/utbot-framework/src/main/kotlin/org/utbot/analytics/MLPredictorFactory.kt new file mode 100644 index 0000000000..b67518a1c5 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/analytics/MLPredictorFactory.kt @@ -0,0 +1,8 @@ +package org.utbot.analytics + +/** + * Encapsulates creation of [MLPredictor] + */ +interface MLPredictorFactory { + operator fun invoke(): MLPredictor +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/analytics/StateRewardPredictor.kt b/utbot-framework/src/main/kotlin/org/utbot/analytics/StateRewardPredictor.kt deleted file mode 100644 index bcb55a7eeb..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/analytics/StateRewardPredictor.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.utbot.analytics - -/** - * Interface, which should predict reward for state by features list. - */ -interface StateRewardPredictor : UtBotAbstractPredictor, Double> \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/analytics/StateRewardPredictorFactory.kt b/utbot-framework/src/main/kotlin/org/utbot/analytics/StateRewardPredictorFactory.kt deleted file mode 100644 index 60d1aa9b40..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/analytics/StateRewardPredictorFactory.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.utbot.analytics - -/** - * Encapsulates creation of [StateRewardPredictor] - */ -interface StateRewardPredictorFactory { - operator fun invoke(): StateRewardPredictor -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt index 5c53d0c751..d09277a977 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ArrayObjectWrappers.kt @@ -18,17 +18,23 @@ import org.utbot.engine.pc.mkInt import org.utbot.engine.pc.select import org.utbot.engine.pc.store import org.utbot.engine.symbolic.asHardConstraint +import org.utbot.engine.types.OBJECT_TYPE +import org.utbot.engine.types.TypeRegistry +import org.utbot.engine.types.TypeResolver import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.getIdOrThrow +import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId import soot.ArrayType +import soot.RefType import soot.Scene import soot.SootClass import soot.SootField @@ -37,176 +43,272 @@ import soot.SootMethod val rangeModifiableArrayId: ClassId = RangeModifiableUnlimitedArray::class.id class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { - override fun UtBotSymbolicEngine.invoke( + @Suppress("UNUSED_PARAMETER") + private fun initMethodWrapper( + traverser: Traverser, wrapper: ObjectValue, method: SootMethod, parameters: List - ): List { - return when (method.name) { - "" -> { - val arrayAddr = findNewAddr() - - listOf( - MethodResult( - SymbolicSuccess(voidValue), - memoryUpdates = arrayUpdateWithValue( - arrayAddr, - OBJECT_TYPE.arrayType, - mkArrayWithConst(UtArraySort(UtIntSort, UtAddrSort), mkInt(0)) - ) - + objectUpdate(wrapper, storageField, arrayAddr) - + objectUpdate(wrapper, beginField, mkInt(0)) - + objectUpdate(wrapper, endField, mkInt(0)) + ): List = + with(traverser) { + val arrayAddr = findNewAddr() + + listOf( + MethodResult( + SymbolicSuccess(voidValue), + memoryUpdates = arrayUpdateWithValue( + arrayAddr, + OBJECT_TYPE.arrayType, + mkArrayWithConst(UtArraySort(UtIntSort, UtAddrSort), mkInt(0)) ) + + objectUpdate(wrapper, storageField, arrayAddr) + + objectUpdate(wrapper, beginField, mkInt(0)) + + objectUpdate(wrapper, endField, mkInt(0)) ) - } - "insert" -> { - val value = UtArrayInsert( - getStorageArrayExpression(wrapper), - parameters[0] as PrimitiveValue, - parameters[1].addr - ) + ) + } - listOf( - MethodResult( - SymbolicSuccess(voidValue), - memoryUpdates = arrayUpdateWithValue( - getStorageArrayField(wrapper.addr).addr, - OBJECT_TYPE.arrayType, - value - ) - ) - ) - } - "insertRange" -> { - val value = UtArrayInsertRange( - getStorageArrayExpression(wrapper), - parameters[0] as PrimitiveValue, - selectArrayExpressionFromMemory(parameters[1] as ArrayValue), - parameters[2] as PrimitiveValue, - parameters[3] as PrimitiveValue - ) - listOf( - MethodResult( - SymbolicSuccess(voidValue), - memoryUpdates = arrayUpdateWithValue( - getStorageArrayField(wrapper.addr).addr, - OBJECT_TYPE.arrayType, - value - ), - ) - ) - } - "remove" -> { - val value = UtArrayRemove( - getStorageArrayExpression(wrapper), - parameters[0] as PrimitiveValue - ) - listOf( - MethodResult( - SymbolicSuccess(voidValue), - memoryUpdates = arrayUpdateWithValue( - getStorageArrayField(wrapper.addr).addr, - OBJECT_TYPE.arrayType, - value - ), + @Suppress("UNUSED_PARAMETER") + private fun insertMethodWrapper( + traverser: Traverser, + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List = + with(traverser) { + val value = UtArrayInsert( + getStorageArrayExpression(wrapper), + parameters[0] as PrimitiveValue, + parameters[1].addr + ) + + listOf( + MethodResult( + SymbolicSuccess(voidValue), + memoryUpdates = arrayUpdateWithValue( + getStorageArrayField(wrapper.addr).addr, + OBJECT_TYPE.arrayType, + value ) ) - } - "removeRange" -> { - val value = UtArrayRemoveRange( - getStorageArrayExpression(wrapper), - parameters[0] as PrimitiveValue, - parameters[1] as PrimitiveValue - ) - listOf( - MethodResult( - SymbolicSuccess(voidValue), - memoryUpdates = arrayUpdateWithValue( - getStorageArrayField(wrapper.addr).addr, - OBJECT_TYPE.arrayType, - value - ), - ) + ) + } + + @Suppress("UNUSED_PARAMETER") + private fun insertRangeMethodWrapper( + traverser: Traverser, + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List = + with(traverser) { + val value = UtArrayInsertRange( + getStorageArrayExpression(wrapper), + parameters[0] as PrimitiveValue, + selectArrayExpressionFromMemory(parameters[1] as ArrayValue), + parameters[2] as PrimitiveValue, + parameters[3] as PrimitiveValue + ) + listOf( + MethodResult( + SymbolicSuccess(voidValue), + memoryUpdates = arrayUpdateWithValue( + getStorageArrayField(wrapper.addr).addr, + OBJECT_TYPE.arrayType, + value + ), ) - } - "set" -> { - val value = - getStorageArrayExpression(wrapper).store((parameters[0] as PrimitiveValue).expr, parameters[1].addr) - listOf( - MethodResult( - SymbolicSuccess(voidValue), - memoryUpdates = arrayUpdateWithValue( - getStorageArrayField(wrapper.addr).addr, - OBJECT_TYPE.arrayType, - value - ), - ) + ) + } + + @Suppress("UNUSED_PARAMETER") + private fun removeMethodWrapper( + traverser: Traverser, + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List = + with(traverser) { + val value = UtArrayRemove( + getStorageArrayExpression(wrapper), + parameters[0] as PrimitiveValue + ) + listOf( + MethodResult( + SymbolicSuccess(voidValue), + memoryUpdates = arrayUpdateWithValue( + getStorageArrayField(wrapper.addr).addr, + OBJECT_TYPE.arrayType, + value + ), ) - } - "get" -> { - val value = getStorageArrayExpression(wrapper).select((parameters[0] as PrimitiveValue).expr) - val addr = UtAddrExpression(value) - val resultObject = createObject(addr, OBJECT_TYPE, useConcreteType = false) - - listOf( - MethodResult( - SymbolicSuccess(resultObject), - typeRegistry.typeConstraintToGenericTypeParameter(addr, wrapper.addr, i = TYPE_PARAMETER_INDEX) - .asHardConstraint() - ) + ) + } + + @Suppress("UNUSED_PARAMETER") + private fun removeRangeMethodWrapper( + traverser: Traverser, + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List = + with(traverser) { + val value = UtArrayRemoveRange( + getStorageArrayExpression(wrapper), + parameters[0] as PrimitiveValue, + parameters[1] as PrimitiveValue + ) + listOf( + MethodResult( + SymbolicSuccess(voidValue), + memoryUpdates = arrayUpdateWithValue( + getStorageArrayField(wrapper.addr).addr, + OBJECT_TYPE.arrayType, + value + ), ) - } - "toArray" -> { - val arrayAddr = findNewAddr() - val offset = parameters[0] as PrimitiveValue - val length = parameters[1] as PrimitiveValue - - val value = UtArrayShiftIndexes(getStorageArrayExpression(wrapper), offset) - - val typeStorage = typeResolver.constructTypeStorage(OBJECT_TYPE.arrayType, useConcreteType = false) - val array = ArrayValue(typeStorage, arrayAddr) - - val hardConstraints = setOf( - Eq(memory.findArrayLength(arrayAddr), length), - typeRegistry.typeConstraint(arrayAddr, array.typeStorage).all(), - ).asHardConstraint() - - listOf( - MethodResult( - SymbolicSuccess(array), - hardConstraints = hardConstraints, - memoryUpdates = arrayUpdateWithValue(arrayAddr, OBJECT_TYPE.arrayType, value) - ) + ) + } + + @Suppress("UNUSED_PARAMETER") + private fun setMethodWrapper( + traverser: Traverser, + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List = + with(traverser) { + val value = + getStorageArrayExpression(wrapper).store((parameters[0] as PrimitiveValue).expr, parameters[1].addr) + listOf( + MethodResult( + SymbolicSuccess(voidValue), + memoryUpdates = arrayUpdateWithValue( + getStorageArrayField(wrapper.addr).addr, + OBJECT_TYPE.arrayType, + value + ), ) + ) + } + + @Suppress("UNUSED_PARAMETER") + private fun getMethodWrapper( + traverser: Traverser, + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List = + with(traverser) { + val value = getStorageArrayExpression(wrapper).select((parameters[0] as PrimitiveValue).expr) + val addr = UtAddrExpression(value) + + // Try to retrieve manually set type if present + val valueType = extractSingleTypeParameterForRangeModifiableArray(wrapper.addr, memory) + ?.leastCommonType + ?: OBJECT_TYPE + + val resultObject = if (valueType is RefType) { + val mockInfoGenerator = UtMockInfoGenerator { mockAddr -> + UtObjectMockInfo(valueType.id, mockAddr) + } + createObject(addr, valueType, useConcreteType = false, mockInfoGenerator) + } else { + require(valueType is ArrayType) { + "Unexpected Primitive Type $valueType in generic parameter for RangeModifiableUnlimitedArray $wrapper" + } + + createArray(addr, valueType, useConcreteType = false) } - "setRange" -> { - val value = UtArraySetRange( - getStorageArrayExpression(wrapper), - parameters[0] as PrimitiveValue, - selectArrayExpressionFromMemory(parameters[1] as ArrayValue), - parameters[2] as PrimitiveValue, - parameters[3] as PrimitiveValue + + val typeIndex = wrapper.asWrapperOrNull?.getOperationTypeIndex + ?: error("Wrapper was expected, got $wrapper") + val typeConstraint = typeRegistry.typeConstraintToGenericTypeParameter( + addr, + wrapper.addr, + i = typeIndex + ).asHardConstraint() + + val methodResult = MethodResult(SymbolicSuccess(resultObject), typeConstraint) + + listOf(methodResult) + } + + @Suppress("UNUSED_PARAMETER") + private fun toArrayMethodWrapper( + traverser: Traverser, + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List = + with(traverser) { + val arrayAddr = findNewAddr() + val offset = parameters[0] as PrimitiveValue + val length = parameters[1] as PrimitiveValue + + val value = UtArrayShiftIndexes(getStorageArrayExpression(wrapper), offset) + + val typeStorage = typeResolver.constructTypeStorage(OBJECT_TYPE.arrayType, useConcreteType = false) + val array = ArrayValue(typeStorage, arrayAddr) + + val hardConstraints = setOf( + Eq(memory.findArrayLength(arrayAddr), length), + typeRegistry.typeConstraint(arrayAddr, array.typeStorage).all(), + ).asHardConstraint() + + listOf( + MethodResult( + SymbolicSuccess(array), + hardConstraints = hardConstraints, + memoryUpdates = arrayUpdateWithValue(arrayAddr, OBJECT_TYPE.arrayType, value) ) - listOf( - MethodResult( - SymbolicSuccess(voidValue), - memoryUpdates = arrayUpdateWithValue( - getStorageArrayField(wrapper.addr).addr, - OBJECT_TYPE.arrayType, - value - ), - ) + ) + } + + @Suppress("UNUSED_PARAMETER") + private fun setRangeMethodWrapper( + traverser: Traverser, + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List = + with(traverser) { + val value = UtArraySetRange( + getStorageArrayExpression(wrapper), + parameters[0] as PrimitiveValue, + selectArrayExpressionFromMemory(parameters[1] as ArrayValue), + parameters[2] as PrimitiveValue, + parameters[3] as PrimitiveValue + ) + listOf( + MethodResult( + SymbolicSuccess(voidValue), + memoryUpdates = arrayUpdateWithValue( + getStorageArrayField(wrapper.addr).addr, + OBJECT_TYPE.arrayType, + value + ), ) - } - else -> error("unknown method ${method.name} for ${javaClass.simpleName} class") + ) } - } - private fun UtBotSymbolicEngine.getStorageArrayField(addr: UtAddrExpression) = + override val wrappedMethods: Map = + mapOf( + "" to ::initMethodWrapper, + "insert" to ::insertMethodWrapper, + "insertRange" to ::insertRangeMethodWrapper, + "remove" to ::removeMethodWrapper, + "removeRange" to ::removeRangeMethodWrapper, + "set" to ::setMethodWrapper, + "get" to ::getMethodWrapper, + "toArray" to ::toArrayMethodWrapper, + "setRange" to ::setRangeMethodWrapper, + ) + + private fun Traverser.getStorageArrayField(addr: UtAddrExpression) = getArrayField(addr, rangeModifiableArrayClass, storageField) - private fun UtBotSymbolicEngine.getStorageArrayExpression( + private fun Traverser.getStorageArrayExpression( wrapper: ObjectValue ): UtExpression = selectArrayExpressionFromMemory(getStorageArrayField(wrapper.addr)) @@ -245,8 +347,8 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { resolver.addConstructedModel(concreteAddr, resultModel) // try to retrieve type storage for the single type parameter - val typeStorage = - resolver.typeRegistry.getTypeStoragesForObjectTypeParameters(wrapper.addr)?.singleOrNull() ?: TypeRegistry.objectTypeStorage + val typeStorage = extractSingleTypeParameterForRangeModifiableArray(wrapper.addr, resolver.memory) + ?: TypeRegistry.objectTypeStorage (0 until sizeValue).associateWithTo(resultModel.stores) { i -> val addr = UtAddrExpression(arrayExpression.select(mkInt(i + firstValue))) @@ -263,6 +365,15 @@ class RangeModifiableUnlimitedArrayWrapper : WrapperInterface { return resultModel } + private fun extractSingleTypeParameterForRangeModifiableArray( + addr: UtAddrExpression, + memory: Memory + ): TypeStorage? = TypeResolver.extractTypeStorageForObjectWithSingleTypeParameter( + addr, + objectClassName = "Range modifiable array", + memory + ) + companion object { internal val rangeModifiableArrayClass: SootClass get() = Scene.v().getSootClass(rangeModifiableArrayId.name) @@ -284,73 +395,105 @@ class AssociativeArrayWrapper : WrapperInterface { private val touchedField = associativeArrayClass.getField("java.lang.Object[] touched") private val storageField = associativeArrayClass.getField("java.lang.Object[] storage") - - override fun UtBotSymbolicEngine.invoke( + @Suppress("UNUSED_PARAMETER") + private fun initMethodWrapper( + traverser: Traverser, wrapper: ObjectValue, method: SootMethod, parameters: List - ): List { - return when (method.name) { - "" -> { - val storageArrayAddr = findNewAddr() - val touchedArrayAddr = findNewAddr() - - listOf( - MethodResult( - SymbolicSuccess(voidValue), - memoryUpdates = arrayUpdateWithValue( - storageArrayAddr, - OBJECT_TYPE.arrayType, - mkArrayWithConst(UtArraySort(UtAddrSort, UtAddrSort), mkInt(0)) - ) + arrayUpdateWithValue( - touchedArrayAddr, - OBJECT_TYPE.arrayType, - mkArrayWithConst(UtArraySort(UtIntSort, UtAddrSort), mkInt(0)) - ) - + objectUpdate(wrapper, storageField, storageArrayAddr) - + objectUpdate(wrapper, touchedField, touchedArrayAddr) - + objectUpdate(wrapper, sizeField, mkInt(0)) + ): List = + with(traverser) { + val storageArrayAddr = findNewAddr() + val touchedArrayAddr = findNewAddr() + + return listOf( + MethodResult( + SymbolicSuccess(voidValue), + memoryUpdates = arrayUpdateWithValue( + storageArrayAddr, + OBJECT_TYPE.arrayType, + mkArrayWithConst(UtArraySort(UtAddrSort, UtAddrSort), mkInt(0)) + ) + arrayUpdateWithValue( + touchedArrayAddr, + OBJECT_TYPE.arrayType, + mkArrayWithConst(UtArraySort(UtIntSort, UtAddrSort), mkInt(0)) ) + + objectUpdate(wrapper, storageField, storageArrayAddr) + + objectUpdate(wrapper, touchedField, touchedArrayAddr) + + objectUpdate(wrapper, sizeField, mkInt(0)) ) - } - "select" -> { - val value = getStorageArrayExpression(wrapper).select(parameters[0].addr) - val addr = UtAddrExpression(value) - val resultObject = createObject(addr, OBJECT_TYPE, useConcreteType = false) - - listOf( - MethodResult( - SymbolicSuccess(resultObject), - typeRegistry.typeConstraintToGenericTypeParameter( - addr, - wrapper.addr, - TYPE_PARAMETER_INDEX - ).asHardConstraint() - ) - ) - } - "store" -> { - val storageValue = getStorageArrayExpression(wrapper).store(parameters[0].addr, parameters[1].addr) - val sizeValue = getIntFieldValue(wrapper, sizeField) - val touchedValue = getTouchedArrayExpression(wrapper).store(sizeValue, parameters[0].addr) - listOf( - MethodResult( - SymbolicSuccess(voidValue), - memoryUpdates = arrayUpdateWithValue( - getStorageArrayField(wrapper.addr).addr, - OBJECT_TYPE.arrayType, - storageValue - ) + arrayUpdateWithValue( - getTouchedArrayField(wrapper.addr).addr, - OBJECT_TYPE.arrayType, - touchedValue, - ) + objectUpdate(wrapper, sizeField, Add(sizeValue.toIntValue(), 1.toPrimitiveValue())) - ) - ) - } - else -> error("unknown method ${method.name} for AssociativeArray class") + ) } - } + + @Suppress("UNUSED_PARAMETER") + private fun selectMethodWrapper( + traverser: Traverser, + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List = + with(traverser) { + val value = getStorageArrayExpression(wrapper).select(parameters[0].addr) + val addr = UtAddrExpression(value) + // TODO it is probably a bug, what mock generator should we provide here? + // Seems like we don't know anything about its type here + val resultObject = createObject(addr, OBJECT_TYPE, useConcreteType = false, mockInfoGenerator = null) + + val typeIndex = wrapper.asWrapperOrNull?.selectOperationTypeIndex + ?: error("Wrapper was expected, got $wrapper") + val hardConstraints = typeRegistry.typeConstraintToGenericTypeParameter( + addr, + wrapper.addr, + typeIndex + ).asHardConstraint() + + val methodResult = MethodResult(SymbolicSuccess(resultObject), hardConstraints) + + listOf(methodResult) + } + + @Suppress("UNUSED_PARAMETER") + private fun storeMethodWrapper( + traverser: Traverser, + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List = + with(traverser) { + val storageValue = getStorageArrayExpression(wrapper).store(parameters[0].addr, parameters[1].addr) + val sizeValue = getIntFieldValue(wrapper, sizeField) + + // it is the reason why it's important to use an `oldKey` in `UtHashMap.put` method. + // We navigate in the associative array using only this old address, not a new one. + val touchedValue = getTouchedArrayExpression(wrapper).store(sizeValue, parameters[0].addr) + val storageArrayAddr = getStorageArrayField(wrapper.addr).addr + val touchedArrayFieldAddr = getTouchedArrayField(wrapper.addr).addr + + val storageArrayUpdate = arrayUpdateWithValue( + storageArrayAddr, + OBJECT_TYPE.arrayType, + storageValue + ) + + val touchedArrayUpdate = arrayUpdateWithValue( + touchedArrayFieldAddr, + OBJECT_TYPE.arrayType, + touchedValue, + ) + + val sizeUpdate = objectUpdate(wrapper, sizeField, Add(sizeValue.toIntValue(), 1.toPrimitiveValue())) + + val memoryUpdates = storageArrayUpdate + touchedArrayUpdate + sizeUpdate + val methodResult = MethodResult(SymbolicSuccess(voidValue), memoryUpdates = memoryUpdates) + + listOf(methodResult) + } + + override val wrappedMethods: Map = mapOf( + "" to ::initMethodWrapper, + "select" to ::selectMethodWrapper, + "store" to ::storeMethodWrapper, + ) override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel { // get arrayExpression from arrayChunk with object type by arrayAddr @@ -369,13 +512,13 @@ class AssociativeArrayWrapper : WrapperInterface { // construct model values of an array val touchedValues = UtArrayModel( resolver.holder.concreteAddr(UtAddrExpression(touchedArrayAddr)), - objectClassId, + objectArrayClassId, sizeValue, UtNullModel(objectClassId), stores = (0 until sizeValue).associateWithTo(mutableMapOf()) { i -> resolver.resolveModel( ObjectValue( - TypeStorage(OBJECT_TYPE), + TypeStorage.constructTypeStorageWithSingleType(OBJECT_TYPE), UtAddrExpression(touchedArrayExpression.select(mkInt(i))) ) ) @@ -393,40 +536,42 @@ class AssociativeArrayWrapper : WrapperInterface { val storageValues = UtArrayModel( resolver.holder.concreteAddr(UtAddrExpression(storageArrayAddr)), - objectClassId, + objectArrayClassId, sizeValue, UtNullModel(objectClassId), stores = (0 until sizeValue).associateTo(mutableMapOf()) { i -> val model = touchedValues.stores[i] - val addr = if (model is UtNullModel) 0 else (model as UtReferenceModel).id!! + val addr = model.getIdOrThrow() addr to resolver.resolveModel( ObjectValue( - TypeStorage(OBJECT_TYPE), + TypeStorage.constructTypeStorageWithSingleType(OBJECT_TYPE), UtAddrExpression(storageArrayExpression.select(mkInt(addr))) ) ) }) - val model = UtCompositeModel(resolver.holder.concreteAddr(wrapper.addr), associativeArrayId, false) + val model = UtCompositeModel(resolver.holder.concreteAddr(wrapper.addr), associativeArrayId, isMock = false) + model.fields[sizeField.fieldId] = UtPrimitiveModel(sizeValue) model.fields[touchedField.fieldId] = touchedValues model.fields[storageField.fieldId] = storageValues + return model } - private fun UtBotSymbolicEngine.getStorageArrayField(addr: UtAddrExpression) = + private fun Traverser.getStorageArrayField(addr: UtAddrExpression) = getArrayField(addr, associativeArrayClass, storageField) - private fun UtBotSymbolicEngine.getTouchedArrayField(addr: UtAddrExpression) = + private fun Traverser.getTouchedArrayField(addr: UtAddrExpression) = getArrayField(addr, associativeArrayClass, touchedField) - private fun UtBotSymbolicEngine.getTouchedArrayExpression(wrapper: ObjectValue): UtExpression = + private fun Traverser.getTouchedArrayExpression(wrapper: ObjectValue): UtExpression = selectArrayExpressionFromMemory(getTouchedArrayField(wrapper.addr)) - private fun UtBotSymbolicEngine.getStorageArrayExpression( + private fun Traverser.getStorageArrayExpression( wrapper: ObjectValue ): UtExpression = selectArrayExpressionFromMemory(getStorageArrayField(wrapper.addr)) -} -// Arrays and lists have the only type parameter with index zero -private const val TYPE_PARAMETER_INDEX = 0 + override val selectOperationTypeIndex: Int + get() = 1 +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionIteratorWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionIteratorWrappers.kt new file mode 100644 index 0000000000..9eae85413f --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionIteratorWrappers.kt @@ -0,0 +1,109 @@ +package org.utbot.engine + +import org.utbot.engine.overrides.collections.UtArrayList.UtArrayListIterator +import org.utbot.engine.overrides.collections.UtArrayList.UtArrayListSimpleIterator +import org.utbot.engine.overrides.collections.UtHashSet.UtHashSetIterator +import org.utbot.engine.overrides.collections.UtLinkedList.UtReverseIterator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.util.fieldId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.methodId +import soot.SootClass +import soot.SootMethod +import kotlin.reflect.KClass +import kotlin.reflect.jvm.jvmName + +/** + * Abstract wrapper for iterator of [java.util.Collection]. + */ +abstract class CollectionIteratorWrapper(overriddenClass: KClass<*>) : BaseOverriddenWrapper(overriddenClass.jvmName) { + protected abstract val modelName: String + protected abstract val javaCollectionClassId: ClassId + protected abstract val iteratorMethodId: MethodId + protected abstract val iteratorClassId: ClassId + + override fun Traverser.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = null + + override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = resolver.run { + val addr = holder.concreteAddr(wrapper.addr) + val fieldModels = collectFieldModels(wrapper.addr, overriddenClass.type) + + val containerFieldId = overriddenClass.enclosingClassField + val containerFieldModel = fieldModels[containerFieldId] as UtReferenceModel + + val instantiationCall = UtExecutableCallModel( + instance = containerFieldModel, + executable = iteratorMethodId, + params = emptyList() + ) + + UtAssembleModel(addr, iteratorClassId, modelName, instantiationCall) + } +} + +class IteratorOfListWrapper : CollectionIteratorWrapper(UtArrayListSimpleIterator::class) { + override val modelName: String = "iteratorOfList" + override val javaCollectionClassId: ClassId = java.util.List::class.id + override val iteratorClassId: ClassId = java.util.Iterator::class.id + override val iteratorMethodId: MethodId = methodId( + classId = javaCollectionClassId, + name = "iterator", + returnType = iteratorClassId, + arguments = emptyArray() + ) +} + +class ListIteratorOfListWrapper : CollectionIteratorWrapper(UtArrayListIterator::class) { + override val modelName: String = "listIteratorOfList" + override val javaCollectionClassId: ClassId = java.util.List::class.id + override val iteratorClassId: ClassId = java.util.ListIterator::class.id + override val iteratorMethodId: MethodId = methodId( + classId = javaCollectionClassId, + name = "listIterator", + returnType = iteratorClassId, + arguments = emptyArray() + ) +} + +class IteratorOfSetWrapper : CollectionIteratorWrapper(UtHashSetIterator::class) { + override val modelName: String = "iteratorOfSet" + override val javaCollectionClassId: ClassId = java.util.Set::class.id + override val iteratorClassId: ClassId = java.util.Iterator::class.id + override val iteratorMethodId: MethodId = methodId( + classId = javaCollectionClassId, + name = "iterator", + returnType = iteratorClassId, + arguments = emptyArray() + ) +} + +class ReverseIteratorWrapper :CollectionIteratorWrapper(UtReverseIterator::class) { + override val modelName: String = "reverseIterator" + override val javaCollectionClassId: ClassId = java.util.Deque::class.id + override val iteratorClassId: ClassId = java.util.Iterator::class.id + override val iteratorMethodId: MethodId = methodId( + classId = javaCollectionClassId, + name = "descendingIterator", + returnType = iteratorClassId, + arguments = emptyArray() + ) +} + +internal val SootClass.enclosingClassField: FieldId + get() { + require(isInnerClass) { + "Cannot get field for enclosing class of non-inner class $this" + } + + return getFieldByName("this$0").fieldId + } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt index 55a65470cf..914a2edc21 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/CollectionWrappers.kt @@ -8,11 +8,13 @@ import org.utbot.engine.overrides.collections.UtGenericStorage import org.utbot.engine.overrides.collections.UtHashMap import org.utbot.engine.overrides.collections.UtHashSet import org.utbot.engine.overrides.collections.UtLinkedList +import org.utbot.engine.overrides.collections.UtLinkedListWithNullableCheck import org.utbot.engine.pc.UtAddrExpression import org.utbot.engine.pc.UtExpression -import org.utbot.engine.pc.UtFalse import org.utbot.engine.pc.select +import org.utbot.engine.symbolic.SymbolicStateUpdate import org.utbot.engine.symbolic.asHardConstraint +import org.utbot.engine.types.TypeResolver import org.utbot.engine.z3.intValue import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.FieldId @@ -23,13 +25,13 @@ import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.framework.plugin.api.UtReferenceModel -import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.classId -import org.utbot.framework.plugin.api.graph +import org.utbot.framework.plugin.api.getIdOrThrow +import org.utbot.framework.util.graph import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.constructorId +import org.utbot.framework.plugin.api.util.fieldId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.methodId import org.utbot.framework.plugin.api.util.objectClassId @@ -50,7 +52,7 @@ abstract class BaseOverriddenWrapper(protected val overriddenClassName: String) * * @see invoke */ - protected abstract fun UtBotSymbolicEngine.overrideInvoke( + protected abstract fun Traverser.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -65,11 +67,11 @@ abstract class BaseOverriddenWrapper(protected val overriddenClassName: String) * * Multiple GraphResults are returned because, we shouldn't substitute invocation of specified * that was called inside substituted method of object with the same address as specified [wrapper]. - * (For example UtArrayList. invokes AbstractList. that also leads to UtBotSymbolicEngine.invoke, + * (For example UtArrayList. invokes AbstractList. that also leads to [Traverser.invoke], * and shouldn't be substituted with UtArrayList. again). Only one GraphResult is valid, that is * guaranteed by contradictory to each other sets of constraints, added to them. */ - override fun UtBotSymbolicEngine.invoke( + override fun Traverser.invoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -102,6 +104,11 @@ abstract class BaseOverriddenWrapper(protected val overriddenClassName: String) listOf(graphResult) } } + + override fun isWrappedMethod(method: SootMethod): Boolean = true + + override val wrappedMethods: Map = + emptyMap() } /** @@ -119,22 +126,15 @@ abstract class BaseContainerWrapper(containerClassName: String) : BaseOverridden val classId = chooseClassIdWithConstructor(wrapper.type.sootClass.id) - val instantiationChain = mutableListOf() - val modificationsChain = mutableListOf() - - UtAssembleModel(addr, classId, modelName, instantiationChain, modificationsChain) - .apply { - instantiationChain += UtExecutableCallModel( - instance = null, - executable = constructorId(classId), - params = emptyList(), - returnValue = this - ) + val instantiationCall = UtExecutableCallModel( + instance = null, + executable = constructorId(classId), + params = emptyList() + ) - modificationsChain += parameterModels.map { - UtExecutableCallModel(this, modificationMethodId, it) - } - } + UtAssembleModel(addr, classId, modelName, instantiationCall) { + parameterModels.map { UtExecutableCallModel(this, modificationMethodId, it) } + } } /** @@ -162,20 +162,40 @@ abstract class BaseContainerWrapper(containerClassName: String) : BaseOverridden } abstract class BaseGenericStorageBasedContainerWrapper(containerClassName: String) : BaseContainerWrapper(containerClassName) { - override fun UtBotSymbolicEngine.overrideInvoke( + override fun Traverser.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List ): List? = when (method.signature) { UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> { - val equalGenericTypeConstraint = typeRegistry - .eqGenericSingleTypeParameterConstraint(parameters[0].addr, wrapper.addr) - .asHardConstraint() + val (equalGenericTypeConstraint, memoryUpdate) = TypeResolver + .eqGenericSingleTypeParameterConstraint( + parameters[0].addr, + wrapper.addr, + memory.getAllGenericTypeInfo() + ) + + val methodResult = MethodResult( + SymbolicSuccess(voidValue), + equalGenericTypeConstraint.asHardConstraint(), + memoryUpdates = memoryUpdate + ) + + listOf(methodResult) + } + UT_GENERIC_STORAGE_SET_GENERIC_TYPE_TO_TYPE_OF_VALUE_SIGNATURE -> { + val valueTypeStorage = parameters[1].typeStorage + + val memoryUpdate = TypeResolver.createGenericTypeInfoUpdate( + parameters[0].addr, + listOf(valueTypeStorage), + memory.getAllGenericTypeInfo() + ) val methodResult = MethodResult( SymbolicSuccess(voidValue), - equalGenericTypeConstraint + SymbolicStateUpdate(memoryUpdates = memoryUpdate) ) listOf(methodResult) @@ -196,24 +216,29 @@ abstract class BaseGenericStorageBasedContainerWrapper(containerClassName: Strin */ enum class UtListClass { UT_ARRAY_LIST, - UT_LINKED_LIST; + UT_LINKED_LIST, + UT_LINKED_LIST_WITH_NULLABLE_CHECK; val className: String get() = when (this) { UT_ARRAY_LIST -> UtArrayList::class.java.canonicalName UT_LINKED_LIST -> UtLinkedList::class.java.canonicalName + UT_LINKED_LIST_WITH_NULLABLE_CHECK -> UtLinkedListWithNullableCheck::class.java.canonicalName } } /** - * BaseCollectionWrapper that uses implementation of [UtArrayList] or [UtLinkedList] + * BaseCollectionWrapper that uses implementation of [UtArrayList], [UtLinkedList], or [UtLinkedListWithNullableCheck] * depending on specified [utListClass] parameter. * + * This class is also used for wrapping [java.util.Queue], because [UtLinkedList] and [UtLinkedListWithNullableCheck] + * both implement [java.util.Queue]. + * * At resolving stage ListWrapper is resolved to [UtAssembleModel]. - * This model is instantiated by [java.util.ArrayList] constructor if utListClass is [UtListClass.UT_ARRAY_LIST] - * or by [java.util.LinkedList] constructor, if utListClass is [UtListClass.UT_LINKED_LIST] + * This model is instantiated by constructor which is taken from the type from the passed [ReferenceValue] in [value] + * function. * - * Modification chain consists of consequent [java.util.List.add] methods + * Modification chain consists of consequent [java.util.Collection.add] methods * that are arranged to iterating order of list. */ class ListWrapper(private val utListClass: UtListClass) : BaseGenericStorageBasedContainerWrapper(utListClass.className) { @@ -228,7 +253,7 @@ class ListWrapper(private val utListClass: UtListClass) : BaseGenericStorageBase } override val modificationMethodId: MethodId = methodId( - classId = java.util.List::class.id, + classId = java.util.Collection::class.id, name = "add", returnType = booleanClassId, arguments = arrayOf(objectClassId), @@ -283,29 +308,45 @@ class SetWrapper : BaseGenericStorageBasedContainerWrapper(UtHashSet::class.qual * entries, then real behavior of generated test can differ from expected and undefined. */ class MapWrapper : BaseContainerWrapper(UtHashMap::class.qualifiedName!!) { - override fun UtBotSymbolicEngine.overrideInvoke( + override fun Traverser.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List ): List? = when (method.signature) { - UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> listOf( - MethodResult( - SymbolicSuccess(voidValue), - typeRegistry.eqGenericSingleTypeParameterConstraint(parameters[0].addr, wrapper.addr) - .asHardConstraint() + UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> { + val (eqGenericSingleTypeParameterConstraint, memoryUpdate) = + TypeResolver.eqGenericSingleTypeParameterConstraint( + parameters[0].addr, + wrapper.addr, + memory.getAllGenericTypeInfo() + ) + + listOf( + MethodResult( + SymbolicSuccess(voidValue), + eqGenericSingleTypeParameterConstraint.asHardConstraint(), + memoryUpdates = memoryUpdate + ) ) - ) - UT_GENERIC_ASSOCIATIVE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> listOf( - MethodResult( - SymbolicSuccess(voidValue), - typeRegistry.eqGenericTypeParametersConstraint( + } + UT_GENERIC_ASSOCIATIVE_SET_EQUAL_GENERIC_TYPE_SIGNATURE -> { + val (eqGenericTypeParametersConstraint, memoryUpdate) = + TypeResolver.eqGenericTypeParametersConstraint( parameters[0].addr, wrapper.addr, - parameterSize = 2 - ).asHardConstraint() + parameterSize = 2, + memory.getAllGenericTypeInfo() + ) + + listOf( + MethodResult( + SymbolicSuccess(voidValue), + eqGenericTypeParametersConstraint.asHardConstraint(), + memoryUpdates = memoryUpdate + ) ) - ) + } else -> null } @@ -374,7 +415,7 @@ private fun constructKeysAndValues(keysModel: UtModel, valuesModel: UtModel, siz keysModel is UtArrayModel && valuesModel is UtArrayModel -> { List(size) { keysModel.stores[it].let { model -> - val addr = if (model is UtNullModel) 0 else (model as UtReferenceModel).id + val addr = model.getIdOrThrow() // as we do not support generics for now, valuesModel.classId.elementClassId is unknown, // but it can be known with generics support val defaultValue = UtNullModel(valuesModel.classId.elementClassId ?: objectClassId) @@ -394,6 +435,9 @@ private val UT_GENERIC_STORAGE_CLASS internal val UT_GENERIC_STORAGE_SET_EQUAL_GENERIC_TYPE_SIGNATURE = UT_GENERIC_STORAGE_CLASS.getMethodByName(UtGenericStorage<*>::setEqualGenericType.name).signature +internal val UT_GENERIC_STORAGE_SET_GENERIC_TYPE_TO_TYPE_OF_VALUE_SIGNATURE = + UT_GENERIC_STORAGE_CLASS.getMethodByName(UtGenericStorage<*>::setGenericTypeToTypeOfValue.name).signature + private val UT_GENERIC_ASSOCIATIVE_CLASS get() = Scene.v().getSootClass(UtGenericAssociative::class.java.canonicalName) @@ -404,6 +448,8 @@ val ARRAY_LIST_TYPE: RefType get() = Scene.v().getSootClass(java.util.ArrayList::class.java.canonicalName).type val LINKED_LIST_TYPE: RefType get() = Scene.v().getSootClass(java.util.LinkedList::class.java.canonicalName).type +val ARRAY_DEQUE_TYPE: RefType + get() = Scene.v().getSootClass(java.util.ArrayDeque::class.java.canonicalName).type val LINKED_HASH_SET_TYPE: RefType get() = Scene.v().getSootClass(java.util.LinkedHashSet::class.java.canonicalName).type @@ -415,17 +461,31 @@ val LINKED_HASH_MAP_TYPE: RefType val HASH_MAP_TYPE: RefType get() = Scene.v().getSootClass(java.util.HashMap::class.java.canonicalName).type +val ITERATOR_TYPE: RefType + get() = Scene.v().getSootClass(java.util.Iterator::class.java.canonicalName).type +val LIST_ITERATOR_TYPE: RefType + get() = Scene.v().getSootClass(java.util.ListIterator::class.java.canonicalName).type + val STREAM_TYPE: RefType get() = Scene.v().getSootClass(java.util.stream.Stream::class.java.canonicalName).type -internal fun UtBotSymbolicEngine.getArrayField( +val INT_STREAM_TYPE: RefType + get() = Scene.v().getSootClass(java.util.stream.IntStream::class.java.canonicalName).type + +val LONG_STREAM_TYPE: RefType + get() = Scene.v().getSootClass(java.util.stream.LongStream::class.java.canonicalName).type + +val DOUBLE_STREAM_TYPE: RefType + get() = Scene.v().getSootClass(java.util.stream.DoubleStream::class.java.canonicalName).type + +internal fun Traverser.getArrayField( addr: UtAddrExpression, wrapperClass: SootClass, field: SootField ): ArrayValue = createFieldOrMock(wrapperClass.type, addr, field, mockInfoGenerator = null) as ArrayValue -internal fun UtBotSymbolicEngine.getIntFieldValue(wrapper: ObjectValue, field: SootField): UtExpression { +internal fun Traverser.getIntFieldValue(wrapper: ObjectValue, field: SootField): UtExpression { val chunkId = hierarchy.chunkIdForField(field.declaringClass.type, field) val descriptor = MemoryChunkDescriptor(chunkId, field.declaringClass.type, IntType.v()) val array = memory.findArray(descriptor, MemoryState.CURRENT) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ConstructedSootMethods.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ConstructedSootMethods.kt index 1bc3f9aa5e..67a5e89e45 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ConstructedSootMethods.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ConstructedSootMethods.kt @@ -1,13 +1,18 @@ package org.utbot.engine +import org.utbot.engine.types.OBJECT_TYPE +import org.utbot.engine.types.STRING_TYPE import soot.ArrayType import soot.IntType import soot.PrimType import soot.RefType +import soot.Scene +import soot.SootClass import soot.SootMethod import soot.Type import soot.Unit import soot.VoidType +import soot.jimple.StringConstant import soot.jimple.internal.JArrayRef import soot.jimple.internal.JAssignStmt import soot.jimple.internal.JNewMultiArrayExpr @@ -169,4 +174,117 @@ fun unfoldMultiArrayExpr(assignStmt: JAssignStmt): ExceptionalUnitGraph { createSootMethod(methodName, jimpleLocalsForSizes.map { it.type }, multiArray.type, sootClass, graphBody) return ExceptionalUnitGraph(graphBody) +} + + +fun makeSootConcat(declaringClass: SootClass, recipe: String, paramTypes: List, constants: List): SootMethod { + val paramsHashcode = paramTypes.hashCode() + val constantsHashcode = constants.hashCode() + val name = "utbot\$concatenateStringWithRecipe<$recipe>AndParams<$paramsHashcode>AndConstants<$constantsHashcode>" + + + // check if it is already defined + val cachedMethod = declaringClass.getMethodByNameUnsafe(name) + if (cachedMethod != null) { + return cachedMethod + } + + var objectCounter = 0 + + val units = mutableListOf() + val locals = mutableSetOf() + + + // initialize parameter locals + val parameterLocals = paramTypes.map { + JimpleLocal("r${objectCounter++}", it) + } + locals += parameterLocals + + + val parameterRefs = paramTypes.mapIndexed { idx, type -> parameterRef(type, idx) } + val identityStmts = parameterLocals.zip(parameterRefs) { local, ref -> identityStmt(local, ref) } + units += identityStmts + + + // initialize string builder + val sbSootClass = Scene.v().getSootClass("java.lang.StringBuilder") + + + val sb = JimpleLocal("\$r${objectCounter++}", sbSootClass.type) + locals += sb + + val newSbExpr = newNewExpr(sbSootClass.type) + units += assignStmt(sb, newSbExpr) + val initSootMethod = sbSootClass.getMethod("", emptyList()) + val initSbExpr = initSootMethod.toSpecialInvokeExpr(sb) + units += initSbExpr.toInvokeStmt() + + + var paramPointer = 0 // pointer to dynamic parameter + var constantPointer = 0 // pointer to constant list + var delayedString = "" // string which is build of sequent values from [constants] + + val appendStringSootMethod = sbSootClass.getMethod("append", listOf(STRING_TYPE), sbSootClass.type) + + // helper function for appending constants to delayedString + fun appendDelayedStringIfNotEmpty() { + if (delayedString.isEmpty()) { + return + } + + val invokeStringBuilderAppendStmt = appendStringSootMethod.toVirtualInvokeExpr(sb, StringConstant.v(delayedString)) + units += invokeStringBuilderAppendStmt.toInvokeStmt() + + delayedString = "" + } + + // recipe parsing and building the result string + for (c in recipe) { + when (c) { + '\u0001' -> { + appendDelayedStringIfNotEmpty() + + val local = parameterLocals[paramPointer++] + val type = local.type + + val appendSootMethod = sbSootClass.getMethodUnsafe("append", listOf(type), sbSootClass.type) + ?: sbSootClass.getMethod("append", listOf(OBJECT_TYPE), sbSootClass.type) + + val invokeStringBuilderAppendStmt = appendSootMethod.toVirtualInvokeExpr(sb, local) + units += invokeStringBuilderAppendStmt.toInvokeStmt() + } + '\u0002' -> { + appendDelayedStringIfNotEmpty() + + val const = constants[constantPointer++] + + val stringBuilderAppendExpr = appendStringSootMethod.toVirtualInvokeExpr(sb, StringConstant.v(const)) + units += stringBuilderAppendExpr.toInvokeStmt() + } + else -> { + delayedString += c + } + } + } + appendDelayedStringIfNotEmpty() + + // receiving result + val toStringSootMethod = sbSootClass.getMethodByName("toString") + + val result = JimpleLocal("\$r${objectCounter++}", STRING_TYPE) + locals += result + + val stringBuilderToStringExpr = toStringSootMethod.toVirtualInvokeExpr(sb) + val assignStmt = assignStmt(result, stringBuilderToStringExpr) + units += assignStmt + + val returnStmt = returnStatement(result) + units += returnStmt + + + val body = units.toGraphBody() + body.locals.addAll(locals) + + return createSootMethod(name, paramTypes, STRING_TYPE, declaringClass, body) } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/DataClasses.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/DataClasses.kt index 860d1c7d71..46e7a49792 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/DataClasses.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/DataClasses.kt @@ -1,19 +1,22 @@ package org.utbot.engine -import org.utbot.engine.TypeRegistry.Companion.objectTypeStorage +import org.utbot.api.mock.UtMock +import org.utbot.engine.overrides.UtOverrideMock +import org.utbot.engine.types.TypeRegistry.Companion.objectTypeStorage import org.utbot.engine.pc.UtAddrExpression import org.utbot.engine.pc.UtBoolExpression +import org.utbot.engine.pc.UtFalse import org.utbot.engine.pc.UtInstanceOfExpression import org.utbot.engine.pc.UtIsExpression import org.utbot.engine.pc.UtTrue import org.utbot.engine.pc.mkAnd import org.utbot.engine.pc.mkOr -import org.utbot.engine.symbolic.Assumption -import org.utbot.engine.symbolic.HardConstraint -import org.utbot.engine.symbolic.SoftConstraint -import org.utbot.engine.symbolic.SymbolicStateUpdate +import org.utbot.engine.state.ExecutionState +import org.utbot.engine.symbolic.* +import org.utbot.engine.types.TypeResolver import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.UtInstrumentation +import soot.RefType import java.util.Objects import soot.Scene import soot.SootMethod @@ -60,11 +63,11 @@ fun explicitThrown(exception: Throwable, addr: UtAddrExpression, inNestedMethod: /** * Implicitly thrown exception. There are no difference if it happens in nested call or not. */ -fun implicitThrown(exception: Throwable, addr: UtAddrExpression, inNestedMethod: Boolean) = - SymbolicFailure(symbolic(exception, addr), exception, explicit = false, inNestedMethod = inNestedMethod) +fun implicitThrown(throwable: Throwable, addr: UtAddrExpression, inNestedMethod: Boolean) = + SymbolicFailure(symbolic(throwable, addr), throwable, explicit = false, inNestedMethod = inNestedMethod) -private fun symbolic(exception: Throwable, addr: UtAddrExpression) = - objectValue(Scene.v().getRefType(exception.javaClass.canonicalName), addr, ThrowableWrapper(exception)) +private fun symbolic(throwable: Throwable, addr: UtAddrExpression) = + objectValue(Scene.v().getRefType(throwable.javaClass.canonicalName), addr, ThrowableWrapper(throwable)) private fun extractConcrete(symbolic: SymbolicValue) = (symbolic.asWrapperOrNull as? ThrowableWrapper)?.throwable @@ -83,22 +86,25 @@ inline fun SymbolicFailure.fold( data class Parameter(private val localVariable: LocalVariable, private val type: Type, val value: SymbolicValue) /** - * Keeps most common type and possible types, to resolve types in uncertain situations, like virtual invokes. + * Contains type information about some object: set of its [possibleConcreteTypes] and their [leastCommonType]. + * Note that in some situations [leastCommonType] will not present in [possibleConcreteTypes] set, and + * it should not be used to encode this type information into a solver. * * Note: [leastCommonType] might be an interface or abstract type in opposite to the [possibleConcreteTypes] * that **usually** contains only concrete types (so-called appropriate). The only way to create [TypeStorage] with - * inappropriate possibleType is to create it using constructor with the only type. + * inappropriate possibleType is to create it using static methods [constructTypeStorageUnsafe] and + * [constructTypeStorageWithSingleType]. + * + * The right way to create an instance of TypeStorage is to use methods provided by [TypeResolver], e.g., + * [TypeResolver.constructTypeStorage]. * * @see isAppropriate + * @see TypeResolver.constructTypeStorage */ -data class TypeStorage(val leastCommonType: Type, val possibleConcreteTypes: Set) { +class TypeStorage private constructor(val leastCommonType: Type, val possibleConcreteTypes: Set) { private val hashCode = Objects.hash(leastCommonType, possibleConcreteTypes) - /** - * Construct a type storage with some type. In this case [possibleConcreteTypes] might contains - * abstract class or interface. Usually it means such typeStorage represents wrapper object type. - */ - constructor(concreteType: Type) : this(concreteType, setOf(concreteType)) + private constructor(concreteType: Type) : this(concreteType, setOf(concreteType)) fun isObjectTypeStorage(): Boolean = possibleConcreteTypes.size == objectTypeStorage.possibleConcreteTypes.size @@ -121,6 +127,32 @@ data class TypeStorage(val leastCommonType: Type, val possibleConcreteTypes: Set } else { "(leastCommonType=$leastCommonType, ${possibleConcreteTypes.size} possibleTypes=${possibleConcreteTypes.take(10)})" } + + operator fun component1(): Type = leastCommonType + operator fun component2(): Set = possibleConcreteTypes + + companion object { + /** + * Constructs a type storage with particular leastCommonType and set of possibleConcreteTypes. + * This method doesn't give any guarantee on correctness of the constructed type storage and + * should not be used directly except the situations where you want to create abnormal type storage, + * for example, a one that contains interfaces in [possibleConcreteTypes]. + * + * In regular cases you should use [TypeResolver.constructTypeStorage] method instead. + */ + fun constructTypeStorageUnsafe( + leastCommonType: Type, + possibleConcreteTypes: Set + ): TypeStorage = TypeStorage(leastCommonType, possibleConcreteTypes) + + /** + * Constructs a type storage with some type. In this case [possibleConcreteTypes] might contain + * an abstract class or an interface. Usually it means such typeStorage represents wrapper object type. + */ + fun constructTypeStorageWithSingleType( + leastCommonType: Type + ): TypeStorage = TypeStorage(leastCommonType) + } } sealed class InvokeResult @@ -129,21 +161,19 @@ data class MethodResult( val symbolicResult: SymbolicResult, val symbolicStateUpdate: SymbolicStateUpdate = SymbolicStateUpdate() ) : InvokeResult() { - val memoryUpdates by symbolicStateUpdate::memoryUpdates - constructor( symbolicResult: SymbolicResult, - hardConstraints: HardConstraint = HardConstraint(), - softConstraints: SoftConstraint = SoftConstraint(), - assumption: Assumption = Assumption(), + hardConstraints: HardConstraint = emptyHardConstraint(), + softConstraints: SoftConstraint = emptySoftConstraint(), + assumption: Assumption = emptyAssumption(), memoryUpdates: MemoryUpdate = MemoryUpdate() ) : this(symbolicResult, SymbolicStateUpdate(hardConstraints, softConstraints, assumption, memoryUpdates)) constructor( value: SymbolicValue, - hardConstraints: HardConstraint = HardConstraint(), - softConstraints: SoftConstraint = SoftConstraint(), - assumption: Assumption = Assumption(), + hardConstraints: HardConstraint = emptyHardConstraint(), + softConstraints: SoftConstraint = emptySoftConstraint(), + assumption: Assumption = emptyAssumption(), memoryUpdates: MemoryUpdate = MemoryUpdate(), ) : this(SymbolicSuccess(value), hardConstraints, softConstraints, assumption, memoryUpdates) } @@ -184,7 +214,7 @@ data class OverrideResult( * there is only one usage of it: to support instanceof for arrays we have to update them in the memory. * * @see UtInstanceOfExpression - * @see UtBotSymbolicEngine.resolveIfCondition + * @see Traverser.resolveIfCondition */ data class ResolvedCondition( val condition: UtBoolExpression, @@ -281,7 +311,16 @@ data class TypeConstraint( /** * Returns a conjunction of the [isConstraint] and [correctnessConstraint]. Suitable for an object creation. */ - fun all(): UtBoolExpression = mkAnd(isOrNullConstraint(), correctnessConstraint) + fun all(): UtBoolExpression { + // There is no need in constraint for UtMock and UtOverrideMock instances + val sootClass = (isConstraint.type as? RefType)?.sootClass + + if (sootClass == utMockClass || sootClass == utOverrideMockClass) { + return UtTrue + } + + return mkAnd(isOrNullConstraint(), correctnessConstraint) + } /** * Returns a condition that either the object is an instance of the types in [isConstraint], or it is null. @@ -293,7 +332,13 @@ data class TypeConstraint( * For example, it is suitable for instanceof checks or negation of equality with some types. * NOTE: for Object we always return UtTrue. */ - fun isConstraint(): UtBoolExpression = if (isConstraint.typeStorage.isObjectTypeStorage()) UtTrue else isConstraint + fun isConstraint(): UtBoolExpression { + if (isConstraint.typeStorage.possibleConcreteTypes.isEmpty()) { + return UtFalse + } + + return if (isConstraint.typeStorage.isObjectTypeStorage()) UtTrue else isConstraint + } override fun hashCode(): Int = this.hashcode @@ -318,3 +363,6 @@ data class TypeConstraint( * should be initialized. We don't need to initialize fields that are not accessed in the method being tested. */ data class InstanceFieldReadOperation(val addr: UtAddrExpression, val fieldId: FieldId) + +private val utMockClassName: String = UtMock::class.java.name +private val utOverrideMockClassName: String = UtOverrideMock::class.java.name \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/DynamicInvokeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/DynamicInvokeResolver.kt new file mode 100644 index 0000000000..4c07e8d485 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/DynamicInvokeResolver.kt @@ -0,0 +1,114 @@ +package org.utbot.engine + +import soot.SootClass +import soot.Type +import soot.jimple.StringConstant +import soot.jimple.internal.JDynamicInvokeExpr + + +/** + * Map of supported boostrap function names to their resolvers + */ +private val defaultProcessors = mapOf( + // referencing by a name instead of a signature, because we believe, that all bootstrap methods have unique names + StringMakeConcatResolver.MAKE_CONCAT_METHOD_NAME to StringMakeConcatResolver, + StringMakeConcatResolver.MAKE_CONCAT_WITH_CONSTANTS_NAME to StringMakeConcatResolver +) + +interface DynamicInvokeResolver { + /** + * @return a successfully resolved [Invocation] or `null`, if it can't be resolved with this [DynamicInvokeResolver] + */ + context(Traverser) + fun TraversalContext.resolveDynamicInvoke(invokeExpr: JDynamicInvokeExpr): Invocation? +} + +/** + * Composes several [DynamicInvokeResolver]s into a single [DynamicInvokeResolver] based on bootstrap method names. + */ +class DelegatingDynamicInvokeResolver( + private val bootstrapMethodToProcessor: Map = defaultProcessors +) : DynamicInvokeResolver { + context(Traverser) + override fun TraversalContext.resolveDynamicInvoke(invokeExpr: JDynamicInvokeExpr): Invocation? { + val processor = bootstrapMethodToProcessor[invokeExpr.bootstrapMethod.name] ?: return null + return with(processor) { resolveDynamicInvoke(invokeExpr) } + } + +} + +/** + * Implements the logic of [java.lang.invoke.StringConcatFactory]. + * + * This is useful when analyzing only Java 9+ bytecode. + */ +object StringMakeConcatResolver : DynamicInvokeResolver { + const val STRING_CONCAT_LIBRARY_NAME = "java.lang.invoke.StringConcatFactory" + const val MAKE_CONCAT_METHOD_NAME = "makeConcat" + const val MAKE_CONCAT_WITH_CONSTANTS_NAME = "makeConcatWithConstants" + + /** + * Implements the logic of [java.lang.invoke.StringConcatFactory.makeConcat] and + * [java.lang.invoke.StringConcatFactory.makeConcatWithConstants] in a symbolic way. + * + * - Generates [soot.SootMethod] performing string concatenation if it has not generated yet + * - Links it + * + * Check out [java.lang.invoke.StringConcatFactory] documentation for more details. + * + * @return [Invocation] with a single generated [soot.SootMethod], which represents a concatenating function. + */ + context(Traverser) + override fun TraversalContext.resolveDynamicInvoke(invokeExpr: JDynamicInvokeExpr): Invocation { + val bootstrapMethod = invokeExpr.bootstrapMethod + require(bootstrapMethod.declaringClass.name == STRING_CONCAT_LIBRARY_NAME) + + val bootstrapArguments = invokeExpr.bootstrapArgs.map { arg -> + requireNotNull((arg as? StringConstant)?.value) { "StringConstant expected, but got ${arg::class.java}"} + } + + val (recipe, constants) = when (bootstrapMethod.name) { + MAKE_CONCAT_METHOD_NAME -> { + "\u0001".repeat(invokeExpr.args.size) to emptyList() + } + MAKE_CONCAT_WITH_CONSTANTS_NAME -> { + val recipe = requireNotNull(bootstrapArguments.firstOrNull()) { "At least one bootstrap argument expected" } + recipe to bootstrapArguments.drop(1) + } + else -> error("Unknown bootstrap method for string concatenation!") + } + + val declaringClass = environment.method.declaringClass + val dynamicParameterTypes = invokeExpr.methodRef.parameterTypes + + val parameters = resolveParameters(invokeExpr.args, dynamicParameterTypes) + return makeInvocation(declaringClass, recipe, dynamicParameterTypes, constants, parameters) + } + + private fun makeInvocation( + declaringClass: SootClass, + recipe: String, + dynamicParameterTypes: MutableList, + constants: List, + parameters: List + ): Invocation { + val sootMethod = makeSootConcat( + declaringClass, + recipe, + dynamicParameterTypes, + constants + ) + + val invocationTarget = InvocationTarget( + instance = null, + sootMethod + ) + + return Invocation( + instance = null, + sootMethod, + parameters, + invocationTarget + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt deleted file mode 100644 index 9c831771c0..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionState.kt +++ /dev/null @@ -1,389 +0,0 @@ -package org.utbot.engine - -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.PersistentMap -import kotlinx.collections.immutable.PersistentSet -import kotlinx.collections.immutable.persistentHashMapOf -import kotlinx.collections.immutable.persistentHashSetOf -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.plus -import org.utbot.common.md5 -import org.utbot.engine.pc.UtSolver -import org.utbot.engine.pc.UtSolverStatusUNDEFINED -import org.utbot.engine.symbolic.SymbolicState -import org.utbot.engine.symbolic.SymbolicStateUpdate -import org.utbot.framework.UtSettings -import org.utbot.framework.plugin.api.Step -import soot.SootMethod -import soot.jimple.Stmt -import java.util.Objects - -const val RETURN_DECISION_NUM = -1 -const val CALL_DECISION_NUM = -2 - -data class Edge(val src: Stmt, val dst: Stmt, val decisionNum: Int) - -/** - * The stack element of the [ExecutionState]. - * Contains properties, that are suitable for specified method in call stack. - * - * @param doesntThrow if true, then engine should drop states with throwing exceptions. - * @param localVariableMemory the local memory associated with the current stack element. - */ -data class ExecutionStackElement( - val caller: Stmt?, - val localVariableMemory: LocalVariableMemory = LocalVariableMemory(), - val parameters: MutableList = mutableListOf(), - val inputArguments: ArrayDeque = ArrayDeque(), - val doesntThrow: Boolean = false, - val method: SootMethod, -) { - fun update(memoryUpdate: LocalMemoryUpdate, doesntThrow: Boolean = this.doesntThrow) = - this.copy(localVariableMemory = localVariableMemory.update(memoryUpdate), doesntThrow = doesntThrow) -} - -/** - * Class that store all information about execution state that needed only for analytics module - */ -data class StateAnalyticsProperties( - /** - * Number of forks already performed along state's path, where fork is statement with multiple successors - */ - val depth: Int = 0, - var visitedAfterLastFork: Int = 0, - var visitedBeforeLastFork: Int = 0, - var stmtsSinceLastCovered: Int = 0, - val parent: ExecutionState? = null, -) { - var executingTime: Long = 0 - var reward: Double? = null - val features: MutableList = mutableListOf() - - /** - * Flag that indicates whether this state is fork or not. Fork here means that we have more than one successor - */ - private var isFork: Boolean = false - - fun definitelyFork() { - isFork = true - } - - private var isVisitedNew: Boolean = false - - fun updateIsVisitedNew() { - isVisitedNew = true - stmtsSinceLastCovered = 0 - visitedAfterLastFork++ - } - - private val successorDepth: Int get() = depth + if (isFork) 1 else 0 - - private val successorVisitedAfterLastFork: Int get() = if (!isFork) visitedAfterLastFork else 0 - private val successorVisitedBeforeLastFork: Int get() = visitedBeforeLastFork + if (isFork) visitedAfterLastFork else 0 - private val successorStmtSinceLastCovered: Int get() = 1 + stmtsSinceLastCovered - - fun successorProperties(parent: ExecutionState) = StateAnalyticsProperties( - successorDepth, - successorVisitedAfterLastFork, - successorVisitedBeforeLastFork, - successorStmtSinceLastCovered, - if (UtSettings.enableFeatureProcess) parent else null - ) -} - -/** - * [visitedStatementsHashesToCountInPath] is a map representing how many times each instruction from the [path] - * has occurred. It is required to calculate priority of the branches and decrease the priority for branches leading - * inside a cycle. To improve performance it is a persistent map using state's hashcode to imitate an identity hashmap. - * - * @param symbolicState the current symbolic state. - */ -data class ExecutionState( - val stmt: Stmt, - val symbolicState: SymbolicState, - val executionStack: PersistentList, - val path: PersistentList = persistentListOf(), - val visitedStatementsHashesToCountInPath: PersistentMap = persistentHashMapOf(), - val decisionPath: PersistentList = persistentListOf(0), - val edges: PersistentSet = persistentHashSetOf(), - val stmts: PersistentMap = persistentHashMapOf(), - val pathLength: Int = 0, - val lastEdge: Edge? = null, - val lastMethod: SootMethod? = null, - val methodResult: MethodResult? = null, - val exception: SymbolicFailure? = null, - private var stateAnalyticsProperties: StateAnalyticsProperties = StateAnalyticsProperties() -) : AutoCloseable { - val solver: UtSolver by symbolicState::solver - - val memory: Memory by symbolicState::memory - - private var outgoingEdges = 0 - - fun isInNestedMethod() = executionStack.size > 1 - - val localVariableMemory - get() = executionStack.last().localVariableMemory - - val inputArguments - get() = executionStack.last().inputArguments - - val parameters - get() = executionStack.last().parameters - - /** - * Retrieves MUT parameters. - */ - val methodUnderTestParameters - get() = executionStack.firstOrNull()?.parameters?.map { it.value } - ?: error("Cannot retrieve MUT parameters from empty execution stack") - - val isThrowException: Boolean - get() = (lastEdge?.decisionNum ?: 0) < CALL_DECISION_NUM - - fun createExceptionState( - exception: SymbolicFailure, - update: SymbolicStateUpdate - ): ExecutionState { - val last = executionStack.last() - // go to negative indexing below CALL_DECISION_NUM for exceptions - val edge = Edge(stmt, stmt, CALL_DECISION_NUM - (++outgoingEdges)) - return ExecutionState( - stmt = stmt, - symbolicState = symbolicState + update, - executionStack = executionStack.set(executionStack.lastIndex, last.update(update.localMemoryUpdates)), - path = path, - visitedStatementsHashesToCountInPath = visitedStatementsHashesToCountInPath, - decisionPath = decisionPath + edge.decisionNum, - edges = edges + edge, - stmts = stmts, - pathLength = pathLength + 1, - lastEdge = edge, - lastMethod = executionStack.last().method, - exception = exception, - stateAnalyticsProperties = stateAnalyticsProperties.successorProperties(this) - ) - } - - fun pop(methodResult: MethodResult): ExecutionState { - val caller = executionStack.last().caller!! - val edge = Edge(stmt, caller, RETURN_DECISION_NUM) - - val stmtHashcode = stmt.hashCode() - val stmtCountInPath = (visitedStatementsHashesToCountInPath[stmtHashcode] ?: 0) + 1 - - return ExecutionState( - stmt = caller, - symbolicState = symbolicState, - executionStack = executionStack.removeAt(executionStack.lastIndex), - path = path + stmt, - visitedStatementsHashesToCountInPath = visitedStatementsHashesToCountInPath.put( - stmtHashcode, - stmtCountInPath - ), - decisionPath = decisionPath + edge.decisionNum, - edges = edges + edge, - stmts = stmts.putIfAbsent(stmt, pathLength), - pathLength = pathLength + 1, - lastEdge = edge, - lastMethod = executionStack.last().method, - methodResult, - stateAnalyticsProperties = stateAnalyticsProperties.successorProperties(this) - ) - } - - fun push( - stmt: Stmt, - inputArguments: ArrayDeque, - update: SymbolicStateUpdate, - method: SootMethod - ): ExecutionState { - val edge = Edge(this.stmt, stmt, CALL_DECISION_NUM) - val stackElement = ExecutionStackElement( - this.stmt, - localVariableMemory = localVariableMemory.memoryForNestedMethod().update(update.localMemoryUpdates), - inputArguments = inputArguments, - method = method, - doesntThrow = executionStack.last().doesntThrow - ) - outgoingEdges++ - - val stmtHashCode = this.stmt.hashCode() - val stmtCountInPath = (visitedStatementsHashesToCountInPath[stmtHashCode] ?: 0) + 1 - - return ExecutionState( - stmt = stmt, - symbolicState = symbolicState.stateForNestedMethod() + update, - executionStack = executionStack + stackElement, - path = path + this.stmt, - visitedStatementsHashesToCountInPath = visitedStatementsHashesToCountInPath.put( - stmtHashCode, - stmtCountInPath - ), - decisionPath = decisionPath + edge.decisionNum, - edges = edges + edge, - stmts = stmts.putIfAbsent(this.stmt, pathLength), - pathLength = pathLength + 1, - lastEdge = edge, - lastMethod = stackElement.method, - stateAnalyticsProperties = stateAnalyticsProperties.successorProperties(this) - ) - } - - fun updateMemory( - stateUpdate: SymbolicStateUpdate - ): ExecutionState { - val last = executionStack.last() - val stackElement = last.update(stateUpdate.localMemoryUpdates) - return copy( - symbolicState = symbolicState + stateUpdate, - executionStack = executionStack.set(executionStack.lastIndex, stackElement) - ) - } - - fun update( - edge: Edge, - symbolicStateUpdate: SymbolicStateUpdate, - doesntThrow: Boolean, - ): ExecutionState { - val last = executionStack.last() - val stackElement = last.update( - symbolicStateUpdate.localMemoryUpdates, - last.doesntThrow || doesntThrow - ) - outgoingEdges++ - - val stmtHashCode = stmt.hashCode() - val stmtCountInPath = (visitedStatementsHashesToCountInPath[stmtHashCode] ?: 0) + 1 - - return ExecutionState( - stmt = edge.dst, - symbolicState = symbolicState + symbolicStateUpdate, - executionStack = executionStack.set(executionStack.lastIndex, stackElement), - path = path + stmt, - visitedStatementsHashesToCountInPath = visitedStatementsHashesToCountInPath.put( - stmtHashCode, - stmtCountInPath - ), - decisionPath = decisionPath + edge.decisionNum, - edges = edges + edge, - stmts = stmts.putIfAbsent(stmt, pathLength), - pathLength = pathLength + 1, - lastEdge = edge, - lastMethod = stackElement.method, - stateAnalyticsProperties = stateAnalyticsProperties.successorProperties(this) - ) - } - - /** - * Tell to solver that states with status [UtSolverStatusUNDEFINED] can be created from current state. - * - * Note: Solver optimize cloning respect this flag. - */ - fun expectUndefined() { - solver.expectUndefined = true - } - - override fun close() { - solver.close() - } - - - /** - * Collects full statement path from method entry point to current statement, including current statement. - * - * Each step contains statement, call depth for nested calls (returns belong to called method) and decision. - * Decision for current statement is zero. - * - * Note: calculates depth wrongly for thrown exception, check SAT-811, SAT-812 - * TODO: fix SAT-811, SAT-812 - */ - fun fullPath(): List { - var depth = 0 - val path = path.zip( - decisionPath.subList(1, decisionPath.size) - ).map { (stmt, decision) -> - val stepDepth = when (decision) { - CALL_DECISION_NUM -> depth++ - RETURN_DECISION_NUM -> depth-- - else -> depth - } - Step(stmt, stepDepth, decision) - } - return path + Step(stmt, depth, 0) - } - - /** - * Prettifies full statement path for logging. - * - * Note: marks return statements with *depth-1* to pair with call statement. - */ - fun prettifiedPathLog(): String { - val path = fullPath() - val prettifiedPath = prettifiedPath(path) - return " MD5(path)=${md5(prettifiedPath)}\n$prettifiedPath" - } - - private fun md5(prettifiedPath: String) = prettifiedPath.md5() - - fun md5() = prettifiedPath(fullPath()).md5() - - private fun prettifiedPath(path: List) = - path.joinToString(separator = "\n") { (stmt, depth, decision) -> - val prefix = when (decision) { - CALL_DECISION_NUM -> "call[${depth}] - " + "".padEnd(2 * depth, ' ') - RETURN_DECISION_NUM -> " ret[${depth - 1}] - " + "".padEnd(2 * depth, ' ') - else -> " " + "".padEnd(2 * depth, ' ') - } - "$prefix$stmt" - } - - fun definitelyFork() { - stateAnalyticsProperties.definitelyFork() - } - - fun updateIsVisitedNew() { - stateAnalyticsProperties.updateIsVisitedNew() - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as ExecutionState - - if (stmt != other.stmt) return false - if (symbolicState != other.symbolicState) return false - if (executionStack != other.executionStack) return false - if (path != other.path) return false - if (visitedStatementsHashesToCountInPath != other.visitedStatementsHashesToCountInPath) return false - if (decisionPath != other.decisionPath) return false - if (edges != other.edges) return false - if (stmts != other.stmts) return false - if (pathLength != other.pathLength) return false - if (lastEdge != other.lastEdge) return false - if (lastMethod != other.lastMethod) return false - if (methodResult != other.methodResult) return false - if (exception != other.exception) return false - - return true - } - - private val hashCode by lazy { - Objects.hash( - stmt, symbolicState, executionStack, path, visitedStatementsHashesToCountInPath, decisionPath, - edges, stmts, pathLength, lastEdge, lastMethod, methodResult, exception - ) - } - - override fun hashCode(): Int = hashCode - - var reward by stateAnalyticsProperties::reward - val features by stateAnalyticsProperties::features - var executingTime by stateAnalyticsProperties::executingTime - val depth by stateAnalyticsProperties::depth - var visitedBeforeLastFork by stateAnalyticsProperties::visitedBeforeLastFork - var visitedAfterLastFork by stateAnalyticsProperties::visitedAfterLastFork - var stmtsSinceLastCovered by stateAnalyticsProperties::stmtsSinceLastCovered - val parent by stateAnalyticsProperties::parent -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionStateListener.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionStateListener.kt new file mode 100644 index 0000000000..9b92984a1b --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ExecutionStateListener.kt @@ -0,0 +1,10 @@ +package org.utbot.engine + +import org.utbot.engine.state.ExecutionState + +/** + * [UtBotSymbolicEngine] will fire an event every time it traverses new [ExecutionState]. + */ +fun interface ExecutionStateListener { + fun visit(graph: InterProceduralUnitGraph, state: ExecutionState) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt index d94d51b1b6..6c54d304d9 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Extensions.kt @@ -14,10 +14,10 @@ import org.utbot.engine.pc.UtFp32Sort import org.utbot.engine.pc.UtFp64Sort import org.utbot.engine.pc.UtIntSort import org.utbot.engine.pc.UtLongSort -import org.utbot.engine.pc.UtSeqSort import org.utbot.engine.pc.UtShortSort import org.utbot.engine.pc.UtSolverStatusKind import org.utbot.engine.pc.UtSolverStatusSAT +import org.utbot.engine.pc.UtSolverStatusUNDEFINED import org.utbot.engine.pc.UtSort import org.utbot.engine.pc.mkArrayWithConst import org.utbot.engine.pc.mkBool @@ -28,24 +28,12 @@ import org.utbot.engine.pc.mkFloat import org.utbot.engine.pc.mkInt import org.utbot.engine.pc.mkLong import org.utbot.engine.pc.mkShort -import org.utbot.engine.pc.mkString import org.utbot.engine.pc.toSort +import org.utbot.engine.state.ExecutionState import org.utbot.framework.UtSettings.checkNpeInNestedMethods import org.utbot.framework.UtSettings.checkNpeInNestedNotPrivateMethods import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.UtMethod import org.utbot.framework.plugin.api.id -import java.lang.reflect.Constructor -import java.lang.reflect.Method -import java.lang.reflect.Modifier -import java.util.concurrent.ConcurrentHashMap -import kotlin.reflect.KFunction -import kotlin.reflect.KProperty -import kotlin.reflect.jvm.javaConstructor -import kotlin.reflect.jvm.javaMethod -import kotlinx.collections.immutable.PersistentMap -import kotlinx.collections.immutable.persistentHashMapOf -import org.utbot.engine.pc.UtSolverStatusUNDEFINED import soot.ArrayType import soot.PrimType import soot.RefLikeType @@ -57,11 +45,6 @@ import soot.SootField import soot.SootMethod import soot.Type import soot.Value -import soot.jimple.Expr -import soot.jimple.InvokeExpr -import soot.jimple.JimpleBody -import soot.jimple.StaticFieldRef -import soot.jimple.Stmt import soot.jimple.internal.JDynamicInvokeExpr import soot.jimple.internal.JIdentityStmt import soot.jimple.internal.JInterfaceInvokeExpr @@ -71,7 +54,17 @@ import soot.jimple.internal.JStaticInvokeExpr import soot.jimple.internal.JVirtualInvokeExpr import soot.jimple.internal.JimpleLocal import soot.tagkit.ArtificialEntityTag -import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl +import java.lang.reflect.ParameterizedType +import java.util.ArrayDeque +import java.util.Deque +import java.util.LinkedList +import java.util.Queue +import java.util.concurrent.ConcurrentHashMap +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentHashMapOf +import org.utbot.engine.types.OBJECT_TYPE +import org.utbot.framework.plugin.api.util.enumConstants +import soot.jimple.* val JIdentityStmt.lines: String get() = tags.joinToString { "$it" } @@ -88,6 +81,15 @@ fun Expr.isInvokeExpr() = this is JDynamicInvokeExpr || this is JVirtualInvokeExpr || this is JSpecialInvokeExpr +fun InvokeExpr.baseOrNull(): Value? = + when (this) { + is StaticInvokeExpr -> null + is SpecialInvokeExpr -> base + is VirtualInvokeExpr -> base + is InterfaceInvokeExpr -> base + else -> null + } + val SootMethod.pureJavaSignature get() = bytecodeSignature.substringAfter(' ').dropLast(1) @@ -181,10 +183,10 @@ val SootClass.isAppropriate get() = !isInappropriate /** - * Returns true if the class is abstract, interface, local or if the class has UtClassMock annotation, false otherwise. + * Returns true if the class is abstract, interface, or if the class has UtClassMock annotation, false otherwise. */ val SootClass.isInappropriate - get() = isAbstract || isInterface || isLocal || findMockAnnotationOrNull != null + get() = isAbstract || isInterface || findMockAnnotationOrNull != null private val isLocalRegex = ".*\\$\\d+[\\p{L}\\p{M}0-9][\\p{L}\\p{M}0-9]*".toRegex() @@ -196,12 +198,17 @@ private val isAnonymousRegex = ".*\\$\\d+$".toRegex() val SootClass.isAnonymous get() = name matches isAnonymousRegex +private val isLambdaRegex = ".*(\\$)lambda_.*".toRegex() + +val SootClass.isLambda: Boolean + get() = this.isArtificialEntity && this.name matches isLambdaRegex + val Type.numDimensions get() = if (this is ArrayType) numDimensions else 0 /** * Invocation. Can generate multiple targets. * - * @see UtBotSymbolicEngine.virtualAndInterfaceInvoke + * @see Traverser.virtualAndInterfaceInvoke */ data class Invocation( val instance: ReferenceValue?, @@ -220,7 +227,7 @@ data class Invocation( /** * Invocation target. Contains constraints to be satisfied for this instance class (related to virtual invoke). * - * @see UtBotSymbolicEngine.virtualAndInterfaceInvoke + * @see Traverser.virtualAndInterfaceInvoke */ data class InvocationTarget( val instance: ReferenceValue?, @@ -243,18 +250,20 @@ data class MethodInvocationTarget( ) /** - * Used in the [UtBotSymbolicEngine.findLibraryTargets] to substitute common types + * Used in the [Traverser.findLibraryTargets] to substitute common types * like [Iterable] with the types that have corresponding wrappers. * - * @see UtBotSymbolicEngine.findLibraryTargets - * @see UtBotSymbolicEngine.findInvocationTargets + * @see Traverser.findLibraryTargets + * @see Traverser.findInvocationTargets */ val libraryTargets: Map> = mapOf( Iterable::class.java.name to listOf(ArrayList::class.java.name, HashSet::class.java.name), Collection::class.java.name to listOf(ArrayList::class.java.name, HashSet::class.java.name), List::class.java.name to listOf(ArrayList::class.java.name), Set::class.java.name to listOf(HashSet::class.java.name), - Map::class.java.name to listOf(HashMap::class.java.name) + Map::class.java.name to listOf(HashMap::class.java.name), + Queue::class.java.name to listOf(LinkedList::class.java.name, ArrayDeque::class.java.name), + Deque::class.java.name to listOf(LinkedList::class.java.name, ArrayDeque::class.java.name) ) fun Collection<*>.prettify() = joinToString("\n", "\n", "\n") @@ -263,33 +272,6 @@ fun Map<*, *>.prettify() = entries.joinToString("\n", "\n", "\n") { (key, value) "$key -> $value" } -val Constructor<*>.isPublic: Boolean - get() = (this.modifiers and Modifier.PUBLIC) != 0 - -val Constructor<*>.isPrivate: Boolean - get() = (this.modifiers and Modifier.PRIVATE) != 0 - -val UtMethod.isStatic: Boolean - get() = when { - // TODO: here we consider constructor to be non-static for code generation to generate its arguments correctly - // TODO: may need to rewrite it in a more reasonable way - isConstructor -> false - isMethod -> { - val modifiers = javaMethod?.modifiers ?: error("$this was expected to be a method") - Modifier.isStatic(modifiers) - } - else -> error("$this is neither a constructor, nor a method") - } - -val UtMethod<*>.callerName: String - get() { - // TODO: add code generation error processing - require(!isStatic) { "Creating caller name for static method" } - require(!isConstructor) { "Creating caller name for a constructor" } - val typeName = clazz.simpleName?.decapitalize() ?: error("Can not find name for $clazz") - return "${typeName}Obj" - } - /** * Extracts fqn for the class by its [signature]. * @@ -302,49 +284,14 @@ fun classBytecodeSignatureToClassNameOrNull(signature: String?) = ?.replace("$", ".") ?.let { it.substring(1, it.lastIndex) } -val UtMethod.javaConstructor: Constructor<*>? - get() = (callable as? KFunction<*>)?.javaConstructor - -val UtMethod.javaMethod: Method? - get() = (callable as? KFunction<*>)?.javaMethod ?: (callable as? KProperty<*>)?.getter?.javaMethod - -val UtMethod.isConstructor: Boolean - get() = javaConstructor != null - -val UtMethod.isMethod: Boolean - get() = javaMethod != null - -val UtMethod.signature: String - get() { - val methodName = this.callable.name - val javaMethod = this.javaMethod ?: this.javaConstructor - if (javaMethod != null) { - val parameters = javaMethod.parameters.joinToString(separator = ", ") { "${it.type}" } - return "${methodName}($parameters)" - } - return "${methodName}()" - } - -val UtMethod.displayName: String - get() { - val methodName = this.callable.name - val javaMethod = this.javaMethod ?: this.javaConstructor - if (javaMethod != null) { - val parameters = javaMethod.parameters.joinToString(separator = ", ") { "${it.type.canonicalName}" } - return "${methodName}($parameters)" - } - return "${methodName}()" - } - - val JimpleLocal.variable: LocalVariable get() = LocalVariable(this.name) val Type.defaultSymValue: UtExpression get() = toSort().defaultValue -val SootField.fieldId: FieldId - get() = FieldId(declaringClass.id, name) +val SootField.isEnumConstant: Boolean + get() = name in declaringClass.id.enumConstants.orEmpty().map { enum -> enum.name } val UtSort.defaultValue: UtExpression get() = when (this) { @@ -356,8 +303,6 @@ val UtSort.defaultValue: UtExpression UtFp32Sort -> mkFloat(0f) UtFp64Sort -> mkDouble(0.0) UtBoolSort -> mkBool(false) - // empty string because we want to have a default value of the same sort as the items stored in the strings array - UtSeqSort -> mkString("") is UtArraySort -> if (itemSort is UtArraySort) nullObjectAddr else mkArrayWithConst(this, itemSort.defaultValue) else -> nullObjectAddr } @@ -403,7 +348,7 @@ val Type.baseType: Type get() = if (this is ArrayType) this.baseType else this val java.lang.reflect.Type.rawType: java.lang.reflect.Type - get() = if (this is ParameterizedTypeImpl) rawType else this + get() = if (this is ParameterizedType) rawType else this /** * Returns true if the addr belongs to “this” value, false otherwise. @@ -477,6 +422,9 @@ val SootMethod.isUtMockAssume val SootMethod.isUtMockAssumeOrExecuteConcretely get() = signature == assumeOrExecuteConcretelyMethod.signature +val SootMethod.isUtMockForbidClassCastException + get() = signature == disableClassCastExceptionCheckMethod.signature + /** * Returns true is the [SootMethod] is defined in a class from * [UTBOT_OVERRIDE_PACKAGE_NAME] package and its name is `preconditionCheck`. diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt index 113485b18a..bc70375df3 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Hierarchy.kt @@ -1,5 +1,7 @@ package org.utbot.engine +import org.utbot.engine.types.OBJECT_TYPE +import org.utbot.engine.types.TypeRegistry import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.id import soot.RefType @@ -28,10 +30,24 @@ class Hierarchy(private val typeRegistry: TypeRegistry) { type as? RefType ?: error("$type is not a refType") val realType = typeRegistry.findRealType(type) as RefType + val realFieldDeclaringType = typeRegistry.findRealType(field.declaringClass.type) as RefType - val ancestorType = ancestors(realType.sootClass.id).lastOrNull { it.declaresField(field.subSignature) }?.type - ?: error("No such field ${field.subSignature} found in ${realType.sootClass.name}") - return ChunkId("$ancestorType", field.name) + // java.lang.Thread class has package-private fields, that can be used outside the class. + // Since wrapper UtThread does not inherit java.lang.Thread, we cannot use this inheritance condition only. + // The possible presence of hidden field is not important here - we just need + // to know whether we have at least one such field. + + // NOTE: we cannot use `getFieldByNameUnsafe` here because of possible hidden fields presence, and we also + // cannot use `ClassId::hasField` here because it use classloader to check it but we cannot load our wrappers. + // Also, we cannot `getFieldUnsafe` by signature of field, because, for example, `threadLocals` field in `UtThread` + // is declared with `Object` type since its real type is package-private. + val realTypeHasFieldByName = realType.sootClass.fields.any { it.name == field.name } + val realTypeIsInheritor = realFieldDeclaringType.sootClass in ancestors(realType.sootClass.id) + + if (!realTypeIsInheritor && !realTypeHasFieldByName) { + error("No such field ${field.subSignature} found in ${realType.sootClass.name}") + } + return ChunkId("$realFieldDeclaringType", field.name) } /** @@ -51,14 +67,20 @@ class Hierarchy(private val typeRegistry: TypeRegistry) { private fun findAncestors(id: ClassId) = with(Scene.v().getSootClass(id.name)) { - if (this.isInterface) { - Scene.v().activeHierarchy.getSuperinterfacesOfIncluding(this) + val superClasses = mutableListOf() + val superInterfaces = mutableListOf() + + if (isInterface) { + superClasses += OBJECT_TYPE.sootClass + superInterfaces += Scene.v().activeHierarchy.getSuperinterfacesOfIncluding(this) } else { - Scene.v().activeHierarchy.getSuperclassesOfIncluding(this) + - this.interfaces.flatMap { - Scene.v().activeHierarchy.getSuperinterfacesOfIncluding(it) - } + superClasses += Scene.v().activeHierarchy.getSuperclassesOfIncluding(this) + superInterfaces += superClasses + .flatMap { it.interfaces } + .flatMap { Scene.v().activeHierarchy.getSuperinterfacesOfIncluding(it) } } + + superClasses + superInterfaces } private fun findInheritors(id: ClassId) = diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt index ff533b1085..c732463d37 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/InterProceduralUnitGraph.kt @@ -1,6 +1,9 @@ package org.utbot.engine import org.utbot.engine.selectors.strategies.TraverseGraphStatistics +import org.utbot.engine.state.CALL_DECISION_NUM +import org.utbot.engine.state.Edge +import org.utbot.engine.state.ExecutionState import soot.SootClass import soot.SootMethod import soot.jimple.Stmt diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/JimpleCreation.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/JimpleCreation.kt index 4c7887eab9..791d78f55d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/JimpleCreation.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/JimpleCreation.kt @@ -1,5 +1,8 @@ package org.utbot.engine +import soot.Local +import soot.Modifier +import soot.RefType import soot.SootClass import soot.SootMethod import soot.Type @@ -17,13 +20,20 @@ import soot.jimple.InvokeStmt import soot.jimple.Jimple import soot.jimple.JimpleBody import soot.jimple.NewArrayExpr +import soot.jimple.NewExpr import soot.jimple.ParameterRef import soot.jimple.ReturnStmt import soot.jimple.ReturnVoidStmt +import soot.jimple.SpecialInvokeExpr import soot.jimple.StaticInvokeExpr +import soot.jimple.VirtualInvokeExpr fun SootMethod.toStaticInvokeExpr(): StaticInvokeExpr = Jimple.v().newStaticInvokeExpr(this.makeRef()) +fun SootMethod.toVirtualInvokeExpr(local: Local, vararg values: Value): VirtualInvokeExpr = Jimple.v().newVirtualInvokeExpr(local, this.makeRef(), *values) + +fun SootMethod.toSpecialInvokeExpr(local: Local, vararg values: Value): SpecialInvokeExpr = Jimple.v().newSpecialInvokeExpr(local, this.makeRef(), *values) + fun InvokeExpr.toInvokeStmt(): InvokeStmt = Jimple.v().newInvokeStmt(this) fun returnVoidStatement(): ReturnVoidStmt = Jimple.v().newReturnVoidStmt() @@ -34,6 +44,8 @@ fun parameterRef(type: Type, number: Int): ParameterRef = Jimple.v().newParamete fun identityStmt(local: Value, identityRef: Value): IdentityStmt = Jimple.v().newIdentityStmt(local, identityRef) +fun newNewExpr(type: RefType): NewExpr = Jimple.v().newNewExpr(type) + fun newArrayExpr(type: Type, size: Value): NewArrayExpr = Jimple.v().newNewArrayExpr(type, size) fun assignStmt(variable: Value, rValue: Value): AssignStmt = Jimple.v().newAssignStmt(variable, rValue) @@ -60,11 +72,10 @@ fun createSootMethod( argsTypes: List, returnType: Type, declaringClass: SootClass, - graphBody: JimpleBody -) = SootMethod(name, argsTypes, returnType) + graphBody: JimpleBody, + isStatic: Boolean = true +) = SootMethod(name, argsTypes, returnType, if (isStatic) Modifier.STATIC else 0) .also { - it.declaringClass = declaringClass declaringClass.addMethod(it) - graphBody.method = it it.activeBody = graphBody } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt index cf77433848..8aca0de6cf 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Memory.kt @@ -1,50 +1,33 @@ package org.utbot.engine -import com.google.common.collect.HashBiMap -import org.utbot.common.WorkaroundReason.MAKE_SYMBOLIC -import org.utbot.common.workaround import org.utbot.engine.MemoryState.CURRENT import org.utbot.engine.MemoryState.INITIAL import org.utbot.engine.MemoryState.STATIC_INITIAL -import org.utbot.engine.overrides.strings.UtNativeString -import org.utbot.engine.pc.RewritingVisitor import org.utbot.engine.pc.UtAddrExpression import org.utbot.engine.pc.UtAddrSort import org.utbot.engine.pc.UtArrayExpressionBase import org.utbot.engine.pc.UtArraySelectExpression import org.utbot.engine.pc.UtArraySort -import org.utbot.engine.pc.UtBoolExpression import org.utbot.engine.pc.UtBoolSort import org.utbot.engine.pc.UtConstArrayExpression -import org.utbot.engine.pc.UtEqGenericTypeParametersExpression import org.utbot.engine.pc.UtExpression import org.utbot.engine.pc.UtFalse -import org.utbot.engine.pc.UtGenericExpression import org.utbot.engine.pc.UtInt32Sort import org.utbot.engine.pc.UtIntSort -import org.utbot.engine.pc.UtIsExpression -import org.utbot.engine.pc.UtIsGenericTypeExpression +import org.utbot.engine.pc.UtLongSort import org.utbot.engine.pc.UtMkArrayExpression import org.utbot.engine.pc.UtMkTermArrayExpression import org.utbot.engine.pc.UtSeqSort import org.utbot.engine.pc.UtSort import org.utbot.engine.pc.UtTrue -import org.utbot.engine.pc.mkAnd import org.utbot.engine.pc.mkArrayConst -import org.utbot.engine.pc.mkArrayWithConst -import org.utbot.engine.pc.mkEq import org.utbot.engine.pc.mkInt -import org.utbot.engine.pc.mkNot -import org.utbot.engine.pc.mkOr +import org.utbot.engine.pc.mkLong import org.utbot.engine.pc.select import org.utbot.engine.pc.store import org.utbot.engine.pc.toSort -import org.utbot.engine.symbolic.asHardConstraint import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.FieldId -import java.math.BigInteger -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.atomic.AtomicLong import kotlinx.collections.immutable.ImmutableCollection import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.PersistentMap @@ -56,47 +39,21 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentMap +import org.utbot.engine.types.STRING_TYPE +import org.utbot.engine.types.SeqType +import org.utbot.engine.types.TypeResolver +import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.fieldId +import org.utbot.framework.plugin.api.util.isEnum import soot.ArrayType -import soot.BooleanType -import soot.ByteType import soot.CharType -import soot.DoubleType -import soot.FloatType import soot.IntType -import soot.LongType import soot.RefLikeType -import soot.RefType import soot.Scene -import soot.ShortType -import soot.SootClass import soot.SootField -import soot.SootMethod import soot.Type -import soot.tagkit.AnnotationClassElem -/** - * Represents a memory associated with a certain method call. For now consists only of local variables mapping. - * TODO: think on other fields later - * - * @param [locals] represents a mapping from [LocalVariable]s of a specific method call to [SymbolicValue]s. - */ -data class LocalVariableMemory( - private val locals: PersistentMap = persistentHashMapOf() -) { - fun memoryForNestedMethod(): LocalVariableMemory = this.copy(locals = persistentHashMapOf()) - - fun update(update: LocalMemoryUpdate): LocalVariableMemory = this.copy(locals = locals.update(update.locals)) - - /** - * Returns local variable value. - */ - fun local(variable: LocalVariable): SymbolicValue? = locals[variable] - - val localValues: Set - get() = locals.values.toSet() -} - /** * Local memory implementation based on arrays. * @@ -113,6 +70,11 @@ data class LocalVariableMemory( * * Note: [staticInitial] contains mapping from [FieldId] to the memory state at the moment of the field initialization. * + * [fieldValues] stores symbolic values for specified fields of the concrete object instances. + * We need to associate field of a concrete instance with the created symbolic value to not lose an information about its type. + * For example, if field's declared type is Runnable but at the current state it is a specific lambda, + * we have to save this lambda as a type of this field to be able to retrieve it in the future. + * * @see memoryForNestedMethod * @see FieldStates */ @@ -123,10 +85,12 @@ data class Memory( // TODO: split purely symbolic memory and information about s private val concrete: PersistentMap = persistentHashMapOf(), private val mockInfos: PersistentList = persistentListOf(), private val staticInstanceStorage: PersistentMap = persistentHashMapOf(), - private val initializedStaticFields: PersistentMap = persistentHashMapOf(), + private val initializedStaticFields: PersistentSet = persistentHashSetOf(), private val staticFieldsStates: PersistentMap = persistentHashMapOf(), private val meaningfulStaticFields: PersistentSet = persistentHashSetOf(), + private val fieldValues: PersistentMap> = persistentHashMapOf(), private val addrToArrayType: PersistentMap = persistentHashMapOf(), + private val genericTypeStorageByAddr: PersistentMap> = persistentHashMapOf(), private val addrToMockInfo: PersistentMap = persistentHashMapOf(), private val updates: MemoryUpdate = MemoryUpdate(), // TODO: refactor this later. Now we use it only for statics substitution private val visitedValues: UtArrayExpressionBase = UtConstArrayExpression( @@ -147,7 +111,14 @@ data class Memory( // TODO: split purely symbolic memory and information about s private val speculativelyNotNullAddresses: UtArrayExpressionBase = UtConstArrayExpression( UtFalse, UtArraySort(UtAddrSort, UtBoolSort) - ) + ), + // Const array here is because the initial values of all taint bit-vectors are 0. + // If you want to mark some symbolic variable, you should do it manually. + private var taintArray: UtArrayExpressionBase = UtConstArrayExpression( + mkLong(value = 0L), + UtArraySort(indexSort = UtAddrSort, itemSort = UtLongSort) + ), + private val symbolicEnumValues: PersistentList = persistentListOf() ) { val chunkIds: Set get() = initial.keys @@ -158,6 +129,25 @@ data class Memory( // TODO: split purely symbolic memory and information about s fun staticFields(): Map = staticFieldsStates.filterKeys { it in meaningfulStaticFields } + /** + * Retrieves parameter type storages of an object with the given [addr] if present, or null otherwise. + */ + fun getTypeStoragesForObjectTypeParameters( + addr: UtAddrExpression + ): List? = genericTypeStorageByAddr[addr] + + /** + * Returns all collected information about addresses and corresponding generic types. + */ + fun getAllGenericTypeInfo(): Map> = genericTypeStorageByAddr + + /** + * Returns a symbolic value, associated with the specified [field] of the object with the specified [instanceAddr], + * if present, and null otherwise. + */ + fun fieldValue(field: SootField, instanceAddr: UtAddrExpression): SymbolicValue? = + fieldValues[field]?.get(instanceAddr) + /** * Construct the mapping from addresses to sets of fields whose values are read during the code execution * and therefore should be initialized in a constructed model. @@ -182,6 +172,8 @@ data class Memory( // TODO: split purely symbolic memory and information about s */ fun isSpeculativelyNotNull(addr: UtAddrExpression): UtArraySelectExpression = speculativelyNotNullAddresses.select(addr) + fun taintVector(addr: UtAddrExpression): UtArraySelectExpression = taintArray.select(addr) + /** * @return ImmutableCollection of the initial values for all the arrays we touched during the execution */ @@ -236,17 +228,25 @@ data class Memory( // TODO: split purely symbolic memory and information about s .mapValues { (_, values) -> values.first().value to values.last().value } val previousMemoryStates = staticFieldsStates.toMutableMap() + val previousFieldValues = fieldValues.toMutableMap() - // sometimes we want to change initial memory states of fields of a certain class, so we erase all the - // information about previous states and update it with the current state. For now, this processing only takes - // place after receiving MethodResult from [STATIC_INITIALIZER] method call at the end of - // [UtBotSymbolicEngine.processStaticInitializer]. The value of `update.classIdToClearStatics` is equal to the - // class for which the static initialization has performed. - // TODO: JIRA:1610 -- refactor working with statics later + /** + * sometimes we want to change initial memory states of fields of a certain class, so we erase all the + * information about previous states and update it with the current state. For now, this processing only takes + * place after receiving MethodResult from [STATIC_INITIALIZER] method call at the end of + * [Traverser.processStaticInitializer]. The value of `update.classIdToClearStatics` is equal to the + * class for which the static initialization has performed. + * TODO: JIRA:1610 -- refactor working with statics later + */ update.classIdToClearStatics?.let { classId -> Scene.v().getSootClass(classId.name).fields.forEach { sootField -> previousMemoryStates.remove(sootField.fieldId) + + if (sootField.isStatic) { + // Only statics should be cleared here + previousFieldValues.remove(sootField) + } } } @@ -279,6 +279,36 @@ data class Memory( // TODO: split purely symbolic memory and information about s acc.store(addr, UtTrue) } + // TODO: Fold inside a fold is not the best choice, it might cause + // significant growth of size of the taint bit-vec values + val updTaintArray = update.taintArrayUpdate + .groupBy { (addr, _) -> + addr + }.map { (addr, listUpdates) -> + addr to listUpdates.fold(mkLong(0).toLongValue()) { acc, (_, taintVector) -> + Or(acc, taintVector.toLongValue()).toLongValue() + } + }.fold(taintArray) { acc, (addr, taintVector) -> + acc.store(addr, taintVector.expr) + } + + // We have a list with updates for generic type info, and we want to apply + // them in such way that only updates with more precise type information + // should be applied. + val currentGenericsMap = update + .genericTypeStorageByAddr + // go over type generic updates and apply them to already existed info + .fold(genericTypeStorageByAddr.toMutableMap()) { acc, value -> + // If we have more type information, a new type storage will be returned. + // Otherwise, we will have the same info taken from the memory. + val (addr, typeStorages) = + TypeResolver.createGenericTypeInfoUpdate(value.first, value.second, acc) + .genericTypeStorageByAddr + .single() + acc[addr] = typeStorages + acc + } + return this.copy( initial = updInitial, current = updCurrent, @@ -286,16 +316,20 @@ data class Memory( // TODO: split purely symbolic memory and information about s concrete = concrete.putAll(update.concrete), mockInfos = mockInfos.mergeWithUpdate(update.mockInfos), staticInstanceStorage = staticInstanceStorage.putAll(update.staticInstanceStorage), - initializedStaticFields = initializedStaticFields.putAll(update.initializedStaticFields), + initializedStaticFields = initializedStaticFields.addAll(update.initializedStaticFields), staticFieldsStates = previousMemoryStates.toPersistentMap().putAll(updatedStaticFields), meaningfulStaticFields = meaningfulStaticFields.addAll(update.meaningfulStaticFields), + fieldValues = previousFieldValues.toPersistentMap().putAll(update.fieldValues), addrToArrayType = addrToArrayType.putAll(update.addrToArrayType), + genericTypeStorageByAddr = currentGenericsMap.toPersistentMap(), addrToMockInfo = addrToMockInfo.putAll(update.addrToMockInfo), updates = updates + update, visitedValues = updVisitedValues, touchedAddresses = updTouchedAddresses, instanceFieldReadOperations = instanceFieldReadOperations.addAll(update.instanceFieldReads), - speculativelyNotNullAddresses = updSpeculativelyNotNullAddresses + speculativelyNotNullAddresses = updSpeculativelyNotNullAddresses, + taintArray = updTaintArray, + symbolicEnumValues = symbolicEnumValues.addAll(update.symbolicEnumValues) ) } @@ -305,14 +339,14 @@ data class Memory( // TODO: split purely symbolic memory and information about s */ fun memoryForNestedMethod(): Memory = this.copy(updates = MemoryUpdate()) - /** * Returns copy of queued [updates] which consists only of updates of static fields. * This is necessary for substituting unbounded symbolic variables into the static fields. */ fun queuedStaticMemoryUpdates(): MemoryUpdate = MemoryUpdate( staticInstanceStorage = updates.staticInstanceStorage, - staticFieldsUpdates = updates.staticFieldsUpdates + staticFieldsUpdates = updates.staticFieldsUpdates, + fieldValues = updates.fieldValues.filter { it.key.isStatic }.toPersistentMap() ) /** @@ -348,591 +382,9 @@ data class Memory( // TODO: split purely symbolic memory and information about s fun findStaticInstanceOrNull(id: ClassId): ObjectValue? = staticInstanceStorage[id] fun findTypeForArrayOrNull(addr: UtAddrExpression): ArrayType? = addrToArrayType[addr] -} - -/** - * Types/classes registry. - * - * Registers and keeps two mappings: - * - Type <-> unique type id (int) - * - Object address -> to type id - */ -class TypeRegistry { - init { - // initializes type storage for OBJECT_TYPE from current scene - objectTypeStorage = TypeStorage(OBJECT_TYPE, Scene.v().classes.mapTo(mutableSetOf()) { it.type }) - } - - private val typeIdBiMap = HashBiMap.create() - - // A cache for strings representing bit-vectors for some collection of types. - private val typesToBitVecString = mutableMapOf, String>() - private val typeToRating = mutableMapOf() - private val typeToInheritorsTypes = mutableMapOf>() - private val typeToAncestorsTypes = mutableMapOf>() - - /** - * Contains types storages for type parameters of object by its address. - */ - private val genericTypeStorageByAddr = mutableMapOf>() - - // A BiMap containing bijection from every type to an address of the object - // presenting its classRef and vise versa - private val classRefBiMap = HashBiMap.create() - - /** - * Contains mapping from a class to the class containing substitutions for its methods. - */ - private val targetToSubstitution: Map by lazy { - val classesWithTargets = Scene.v().classes.mapNotNull { clazz -> - val annotation = clazz.findMockAnnotationOrNull - ?.elems - ?.singleOrNull { it.name == "target" } as? AnnotationClassElem - - val classNameFromSignature = classBytecodeSignatureToClassNameOrNull(annotation?.desc) - - if (classNameFromSignature == null) { - null - } else { - val target = Scene.v().getSootClass(classNameFromSignature) - target to clazz - } - } - classesWithTargets.toMap() - } - - /** - * Contains mapping from a class with substitutions of the methods of the target class to the target class itself. - */ - private val substitutionToTarget: Map by lazy { - targetToSubstitution.entries.associate { (k, v) -> v to k } - } - - private val typeToFields = mutableMapOf>() - - /** - * An array containing information about whether the object with particular addr could throw a [ClassCastException]. - * - * Note: all objects can throw it by default. - * @see disableCastClassExceptionCheck - */ - private var isClassCastExceptionAllowed: UtArrayExpressionBase = - mkArrayWithConst(UtArraySort(UtAddrSort, UtBoolSort), UtTrue) - - - /** - * Contains information about types for ReferenceValues. - * An element on some position k contains information about type for an object with address == k - * Each element in addrToTypeId is in range [1..numberOfTypes] - */ - private val addrToTypeId: UtArrayExpressionBase by lazy { - mkArrayConst( - "addrToTypeId", - UtAddrSort, - UtInt32Sort - ) - } - - private val genericAddrToTypeArrays = mutableMapOf() - - private fun genericAddrToType(i: Int) = genericAddrToTypeArrays.getOrPut(i) { - mkArrayConst( - "genericAddrToTypeId_$i", - UtAddrSort, - UtInt32Sort - ) - } - - /** - * Contains information about number of dimensions for ReferenceValues. - */ - private val addrToNumDimensions: UtArrayExpressionBase by lazy { - mkArrayConst( - "addrToNumDimensions", - UtAddrSort, - UtInt32Sort - ) - } - - private val genericAddrToNumDimensionsArrays = mutableMapOf() - - private fun genericAddrToNumDimensions(i: Int) = genericAddrToNumDimensionsArrays.getOrPut(i) { - mkArrayConst( - "genericAddrToNumDimensions_$i", - UtAddrSort, - UtInt32Sort - ) - } - - /** - * Contains information about whether the object with some addr is a mock or not. - */ - private val isMockArray: UtArrayExpressionBase by lazy { - mkArrayConst( - "isMock", - UtAddrSort, - UtBoolSort - ) - } - - /** - * Takes information about whether the object with [addr] is mock or not. - * - * @see isMockArray - */ - fun isMock(addr: UtAddrExpression) = isMockArray.select(addr) - - /** - * Makes the numbers of dimensions for every object in the program equal to zero by default - */ - fun softZeroNumDimensions() = UtMkTermArrayExpression(addrToNumDimensions) - - /** - * addrToTypeId is created as const array of the emptyType. If such type occurs anywhere in the program, it means - * we haven't touched the element that this type belongs to - * @see emptyTypeId - */ - fun softEmptyTypes() = UtMkTermArrayExpression(addrToTypeId, mkInt(emptyTypeId)) - - /** - * Calculates type 'rating' for a particular type. Used for ordering ObjectValue's possible types. - * The type with a higher rating is more likely than the one with a lower rating. - */ - fun findRating(type: RefType) = typeToRating.getOrPut(type) { - var finalCost = 0 - - val sootClass = type.sootClass - - // TODO: let's have "preferred types" - if (sootClass.name == "java.util.ArrayList") finalCost += 4096 - if (sootClass.name == "java.util.LinkedList") finalCost += 2048 - if (sootClass.name == "java.util.HashMap") finalCost += 4096 - if (sootClass.name == "java.util.TreeMap") finalCost += 2048 - if (sootClass.name == "java.util.HashSet") finalCost += 2048 - if (sootClass.name == "java.lang.Integer") finalCost += 8192 - if (sootClass.name == "java.lang.Character") finalCost += 8192 - if (sootClass.name == "java.lang.Double") finalCost += 8192 - if (sootClass.name == "java.lang.Long") finalCost += 8192 - - if (sootClass.packageName.startsWith("java.lang")) finalCost += 1024 - - if (sootClass.packageName.startsWith("java.util")) finalCost += 512 - - if (sootClass.packageName.startsWith("java")) finalCost += 128 - - if (sootClass.isPublic) finalCost += 16 - - if (sootClass.isPrivate) finalCost += -16 - - if ("blocking" in sootClass.name.toLowerCase()) finalCost -= 32 - - if (sootClass.type.isJavaLangObject()) finalCost += -32 - - if (sootClass.isAnonymous) finalCost -= 128 - - if (sootClass.name.contains("$")) finalCost += -4096 - - if (sootClass.type.sootClass.isInappropriate) finalCost += -8192 - - finalCost - } - - private val objectCounter = AtomicInteger(objectCounterInitialValue) - fun findNewAddr(insideStaticInitializer: Boolean): UtAddrExpression { - val newAddr = objectCounter.getAndIncrement() - // return negative address for objects created inside static initializer - // to make their address space be intersected with address space of - // parameters of method under symbolic execution - // @see ObjectWithFinalStaticTest::testParameterEqualsFinalStatic - val signedAddr = if (insideStaticInitializer) -newAddr else newAddr - return UtAddrExpression(signedAddr) - } - - private val classRefCounter = AtomicInteger(classRefAddrsInitialValue) - private fun nextClassRefAddr() = UtAddrExpression(classRefCounter.getAndIncrement()) - - private val symbolicReturnNameCounter = AtomicLong(symbolicReturnNameCounterInitialValue) - fun findNewSymbolicReturnValueName() = - workaround(MAKE_SYMBOLIC) { "symbolicReturnValue\$${symbolicReturnNameCounter.incrementAndGet()}" } - - private val typeCounter = AtomicInteger(typeCounterInitialValue) - private fun nextTypeId() = typeCounter.getAndIncrement() - - /** - * Returns unique typeId for the given type - */ - fun findTypeId(type: Type): Int = typeIdBiMap.getOrPut(type) { nextTypeId() } - - /** - * Returns type for the given typeId - * - * @return If there is such typeId in the program, returns the corresponding type, otherwise returns null - */ - fun typeByIdOrNull(typeId: Int): Type? = typeIdBiMap.getByValue(typeId) - - /** - * Returns symbolic representation for a typeId corresponding to the given address - */ - fun symTypeId(addr: UtAddrExpression) = addrToTypeId.select(addr) - - /** - * Returns a symbolic representation for an [i]th type parameter - * corresponding to the given address - */ - fun genericTypeId(addr: UtAddrExpression, i: Int) = genericAddrToType(i).select(addr) - - /** - * Returns symbolic representation for a number of dimensions corresponding to the given address - */ - fun symNumDimensions(addr: UtAddrExpression) = addrToNumDimensions.select(addr) - - fun genericNumDimensions(addr: UtAddrExpression, i: Int) = genericAddrToNumDimensions(i).select(addr) - - /** - * Returns a constraint stating that number of dimensions for the given address is zero - */ - fun zeroDimensionConstraint(addr: UtAddrExpression) = mkEq(symNumDimensions(addr), mkInt(objectNumDimensions)) - - /** - * Constructs a binary bit-vector by the given types with length 'numberOfTypes'. Each position - * corresponding to one of the typeId. - * - * @param types the collection of possible type - * @return decimal string representing the binary bit-vector - */ - fun constructBitVecString(types: Collection) = typesToBitVecString.getOrPut(types) { - val initialValue = BigInteger(ByteArray(numberOfTypes) { 0 }) - - return types.fold(initialValue) { acc, type -> - val typeId = if (type is ArrayType) findTypeId(type.baseType) else findTypeId(type) - acc.setBit(typeId) - }.toString() - } - - /** - * Creates class reference, i.e. Class<Integer> - * - * Note: Uses type id as an address to have the one and the same class reference for all objects of one class - */ - fun createClassRef(baseType: Type, numDimensions: Int = 0): MethodResult { - val addr = classRefBiMap.getOrPut(baseType) { nextClassRefAddr() } - - val objectValue = ObjectValue(TypeStorage(CLASS_REF_TYPE), addr) - - val typeConstraint = typeConstraint(addr, TypeStorage(CLASS_REF_TYPE)).all() - - val typeId = mkInt(findTypeId(baseType)) - val symNumDimensions = mkInt(numDimensions) - - val stores = persistentListOf( - simplifiedNamedStore(CLASS_REF_TYPE_DESCRIPTOR, addr, typeId), - simplifiedNamedStore(CLASS_REF_NUM_DIMENSIONS_DESCRIPTOR, addr, symNumDimensions) - ) - - val touchedDescriptors = persistentSetOf(CLASS_REF_TYPE_DESCRIPTOR, CLASS_REF_NUM_DIMENSIONS_DESCRIPTOR) - - val memoryUpdate = MemoryUpdate(stores = stores, touchedChunkDescriptors = touchedDescriptors) - - return MethodResult(objectValue, typeConstraint.asHardConstraint(), memoryUpdates = memoryUpdate) - } - - /** - * Returns a list of inheritors for the given [type], including itself. - */ - fun findInheritorsIncludingTypes(type: RefType, defaultValue: () -> Set) = - typeToInheritorsTypes.getOrPut(type, defaultValue) - - /** - * Returns a list of ancestors for the given [type], including itself. - */ - fun findAncestorsIncludingTypes(type: RefType, defaultValue: () -> Set) = - typeToAncestorsTypes.getOrPut(type, defaultValue) - - fun findFields(type: RefType, defaultValue: () -> List) = - typeToFields.getOrPut(type, defaultValue) - - /** - * Returns a [TypeConstraint] instance for the given [addr] and [typeStorage]. - */ - fun typeConstraint(addr: UtAddrExpression, typeStorage: TypeStorage): TypeConstraint = - TypeConstraint( - constructIsExpression(addr, typeStorage), - mkEq(addr, nullObjectAddr), - constructCorrectnessConstraint(addr, typeStorage) - ) - - private fun constructIsExpression(addr: UtAddrExpression, typeStorage: TypeStorage): UtIsExpression = - UtIsExpression(addr, typeStorage, numberOfTypes) - - /** - * Returns a conjunction of the constraints responsible for the type construction: - * * typeId must be in range [[emptyTypeId]..[numberOfTypes]]; - * * numDimensions must be in range [0..[MAX_NUM_DIMENSIONS]]; - * * if the baseType for [TypeStorage.leastCommonType] is a [java.lang.Object], - * should be added constraints for primitive arrays to prevent - * impossible resolved types: Object[] must be at least primType[][]. - */ - private fun constructCorrectnessConstraint(addr: UtAddrExpression, typeStorage: TypeStorage): UtBoolExpression { - val symType = symTypeId(addr) - val symNumDimensions = symNumDimensions(addr) - val type = typeStorage.leastCommonType - - val constraints = mutableListOf() - - // add constraints for typeId, it must be in 0..numberOfTypes - constraints += Ge(symType.toIntValue(), emptyTypeId.toPrimitiveValue()) - constraints += Le(symType.toIntValue(), numberOfTypes.toPrimitiveValue()) - - // add constraints for number of dimensions, it must be in 0..MAX_NUM_DIMENSIONS - constraints += Ge(symNumDimensions.toIntValue(), 0.toPrimitiveValue()) - constraints += Le(symNumDimensions.toIntValue(), MAX_NUM_DIMENSIONS.toPrimitiveValue()) - - // add constraints for object and arrays of primitives - if (type.baseType.isJavaLangObject()) { - primTypes.forEach { - val typesAreEqual = mkEq(symType, mkInt(findTypeId(it))) - val numDimensions = Gt(symNumDimensions.toIntValue(), type.numDimensions.toPrimitiveValue()) - constraints += mkOr(mkNot(typesAreEqual), numDimensions) - } - } - - // there are no arrays of anonymous classes - typeStorage.possibleConcreteTypes - .mapNotNull { (it.baseType as? RefType) } - .filter { it.sootClass.isAnonymous } - .forEach { - val typesAreEqual = mkEq(symType, mkInt(findTypeId(it))) - val numDimensions = mkEq(symNumDimensions.toIntValue(), mkInt(objectNumDimensions).toIntValue()) - constraints += mkOr(mkNot(typesAreEqual), numDimensions) - } - - return mkAnd(constraints) - } - - /** - * returns constraint representing, that object with address [addr] is parametrized by [types] type parameters. - * @see UtGenericExpression - */ - fun genericTypeParameterConstraint(addr: UtAddrExpression, types: List) = - UtGenericExpression(addr, types, numberOfTypes) - - /** - * returns constraint representing that type parameters of an object with address [firstAddr] are equal to - * type parameters of an object with address [secondAddr], corresponding to [indexInjection] - * @see UtEqGenericTypeParametersExpression - */ - @Suppress("unused") - fun eqGenericTypeParametersConstraint( - firstAddr: UtAddrExpression, - secondAddr: UtAddrExpression, - vararg indexInjection: Pair - ): UtEqGenericTypeParametersExpression { - setParameterTypeStoragesEquality(firstAddr, secondAddr, indexInjection) - - return UtEqGenericTypeParametersExpression(firstAddr, secondAddr, mapOf(*indexInjection)) - } - - /** - * returns constraint representing that type parameters of an object with address [firstAddr] are equal to - * the corresponding type parameters of an object with address [secondAddr] - * @see UtEqGenericTypeParametersExpression - */ - fun eqGenericTypeParametersConstraint( - firstAddr: UtAddrExpression, - secondAddr: UtAddrExpression, - parameterSize: Int - ) : UtEqGenericTypeParametersExpression { - val injections = Array(parameterSize) { it to it } - - return eqGenericTypeParametersConstraint(firstAddr, secondAddr, *injections) - } - - /** - * returns constraint representing that the first type parameter of an object with address [firstAddr] are equal to - * the first type parameter of an object with address [secondAddr] - * @see UtEqGenericTypeParametersExpression - */ - fun eqGenericSingleTypeParameterConstraint(firstAddr: UtAddrExpression, secondAddr: UtAddrExpression) = - eqGenericTypeParametersConstraint(firstAddr, secondAddr, 0 to 0) - - /** - * Associates provided [typeStorages] with an object with the provided [addr]. - */ - fun saveObjectParameterTypeStorages(addr: UtAddrExpression, typeStorages: List) { - genericTypeStorageByAddr += addr to typeStorages - } - /** - * Retrieves parameter type storages of an object with the given [addr] if present, or null otherwise. - */ - fun getTypeStoragesForObjectTypeParameters(addr: UtAddrExpression): List? = genericTypeStorageByAddr[addr] - - /** - * Set types storages for [firstAddr]'s type parameters equal to type storages for [secondAddr]'s type parameters - * according to provided types injection represented by [indexInjection]. - */ - private fun setParameterTypeStoragesEquality( - firstAddr: UtAddrExpression, - secondAddr: UtAddrExpression, - indexInjection: Array> - ) { - val existingGenericTypes = genericTypeStorageByAddr[secondAddr] ?: return - - val currentGenericTypes = mutableMapOf() - - indexInjection.forEach { (from, to) -> - require(from >= 0 && from < existingGenericTypes.size) { - "Type injection is out of bounds: should be in [0; ${existingGenericTypes.size}) but is $from" - } - - currentGenericTypes[to] = existingGenericTypes[from] - } - - genericTypeStorageByAddr[firstAddr] = currentGenericTypes - .entries - .sortedBy { it.key } - .mapTo(mutableListOf()) { it.value } - } - - /** - * Returns constraint representing that an object with address [addr] has the same type as the type parameter - * with index [i] of an object with address [baseAddr]. - * - * For a SomeCollection the type parameters are [A, B], where A and B are type variables - * with indices zero and one respectively. To connect some element of the collection with its generic type - * add to the constraints `typeConstraintToGenericTypeParameter(elementAddr, collectionAddr, typeParamIndex)`. - * - * @see UtIsGenericTypeExpression - */ - fun typeConstraintToGenericTypeParameter( - addr: UtAddrExpression, - baseAddr: UtAddrExpression, - i: Int - ): UtIsGenericTypeExpression = UtIsGenericTypeExpression(addr, baseAddr, i) - - /** - * Looks for a substitution for the given [method]. - * - * @param method a method to be substituted. - * @return substituted method if the given [method] has substitution, null otherwise. - * - * Note: all the methods in the class with substitutions will be returned instead of methods of the target class - * with the same name and parameters' types without any additional annotations. The only exception is `` - * method, substitutions will be returned only for constructors marked by [org.utbot.api.annotation.UtConstructorMock] - * annotation. - */ - fun findSubstitutionOrNull(method: SootMethod): SootMethod? { - val declaringClass = method.declaringClass - val classWithSubstitutions = targetToSubstitution[declaringClass] - - val substitutedMethod = classWithSubstitutions - ?.methods - ?.singleOrNull { it.name == method.name && it.parameterTypes == method.parameterTypes } - // Note: subSignature is not used in order to support `this` as method's return value. - // Otherwise we'd have to check for wrong `this` type in the subSignature - - if (method.isConstructor) { - // if the constructor doesn't have the mock annotation do not substitute it - substitutedMethod?.findMockAnnotationOrNull ?: return null - } - return substitutedMethod - } - - /** - * Returns a class containing substitutions for the methods belong to the target class, null if there is not such class. - */ - @Suppress("unused") - fun findSubstitutionByTargetOrNull(targetClass: SootClass): SootClass? = targetToSubstitution[targetClass] - - /** - * Returns a target class by given class with methods substitutions. - */ - @Suppress("MemberVisibilityCanBePrivate") - fun findTargetBySubstitutionOrNull(classWithSubstitutions: SootClass): SootClass? = - substitutionToTarget[classWithSubstitutions] - - /** - * Looks for 'real' type. - * - * For example, we have two classes: A and B, B contains substitutions for A's methods. - * `findRealType(a.type)` will return `a.type`, but `findRealType(b.type)` will return `a.type` as well. - * - * Returns: - * * [type] if it is not a RefType; - * * [type] if it is a RefType and it doesn't have a target class to substitute; - * * otherwise a type of the target class, which methods should be substituted. - */ - fun findRealType(type: Type): Type = - if (type !is RefType) type else findTargetBySubstitutionOrNull(type.sootClass)?.type ?: type - - /** - * Returns a select expression containing information about whether [ClassCastException] is allowed or not - * for an object with the given [addr]. - * - * True means that [ClassCastException] might be thrown, false will restrict it. - */ - fun isClassCastExceptionAllowed(addr: UtAddrExpression) = isClassCastExceptionAllowed.select(addr) - - /** - * Modify [isClassCastExceptionAllowed] to make impossible for a [ClassCastException] to be thrown for an object - * with the given [addr]. - */ - fun disableCastClassExceptionCheck(addr: UtAddrExpression) { - isClassCastExceptionAllowed = isClassCastExceptionAllowed.store(addr, UtFalse) - } - - /** - * Returns chunkId for the given [arrayType]. - * - * Examples: - * * Object[] -> RefValues_Arrays - * * int[] -> intArrays - * * int[][] -> MultiArrays - */ - fun arrayChunkId(arrayType: ArrayType) = when (arrayType.numDimensions) { - 1 -> if (arrayType.baseType is RefType) { - ChunkId("RefValues", "Arrays") - } else { - ChunkId("${findRealType(arrayType.baseType)}", "Arrays") - } - else -> ChunkId("Multi", "Arrays") - } - - companion object { - // we use different shifts to distinguish easily types from objects in z3 listings - const val objectCounterInitialValue = 0x00000001 // 0x00000000 is reserved for NULL - - // we want to reserve addresses for every ClassRef in the program starting from this one - // Note: the number had been chosen randomly and can be changes without any consequences - const val classRefAddrsInitialValue = -16777216 // -(2 ^ 24) - - // since we use typeId as addr for ConstRef, we can not use 0x00000000 because of NULL value - const val typeCounterInitialValue = 0x00000001 - const val symbolicReturnNameCounterInitialValue = 0x80000000 - const val objectNumDimensions = 0 - const val emptyTypeId = 0 - private const val primitivesNumber = 8 - - internal val primTypes - get() = listOf( - ByteType.v(), - ShortType.v(), - IntType.v(), - LongType.v(), - FloatType.v(), - DoubleType.v(), - BooleanType.v(), - CharType.v() - ) - - val numberOfTypes get() = Scene.v().classes.size + primitivesNumber + typeCounterInitialValue - - /** - * Stores [TypeStorage] for [OBJECT_TYPE]. As it should be changed when Soot scene changes, - * it is loaded each time when [TypeRegistry] is created in init section. - */ - lateinit var objectTypeStorage: TypeStorage - } + fun getSymbolicEnumValues(classId: ClassId): List = + extractSymbolicEnumValues(symbolicEnumValues, classId) } private fun initialArray(descriptor: MemoryChunkDescriptor) = @@ -967,16 +419,20 @@ data class MemoryUpdate( val concrete: PersistentMap = persistentHashMapOf(), val mockInfos: PersistentList = persistentListOf(), val staticInstanceStorage: PersistentMap = persistentHashMapOf(), - val initializedStaticFields: PersistentMap = persistentHashMapOf(), + val initializedStaticFields: PersistentSet = persistentHashSetOf(), val staticFieldsUpdates: PersistentList = persistentListOf(), val meaningfulStaticFields: PersistentSet = persistentHashSetOf(), + val fieldValues: PersistentMap> = persistentHashMapOf(), val addrToArrayType: PersistentMap = persistentHashMapOf(), + val genericTypeStorageByAddr: PersistentList>> = persistentListOf(), val addrToMockInfo: PersistentMap = persistentHashMapOf(), val visitedValues: PersistentList = persistentListOf(), val touchedAddresses: PersistentList = persistentListOf(), val classIdToClearStatics: ClassId? = null, val instanceFieldReads: PersistentSet = persistentHashSetOf(), - val speculativelyNotNullAddresses: PersistentList = persistentListOf() + val speculativelyNotNullAddresses: PersistentList = persistentListOf(), + val taintArrayUpdate: PersistentList> = persistentListOf(), + val symbolicEnumValues: PersistentList = persistentListOf() ) { operator fun plus(other: MemoryUpdate) = this.copy( @@ -985,17 +441,24 @@ data class MemoryUpdate( concrete = concrete.putAll(other.concrete), mockInfos = mockInfos.mergeWithUpdate(other.mockInfos), staticInstanceStorage = staticInstanceStorage.putAll(other.staticInstanceStorage), - initializedStaticFields = initializedStaticFields.putAll(other.initializedStaticFields), + initializedStaticFields = initializedStaticFields.addAll(other.initializedStaticFields), staticFieldsUpdates = staticFieldsUpdates.addAll(other.staticFieldsUpdates), meaningfulStaticFields = meaningfulStaticFields.addAll(other.meaningfulStaticFields), + fieldValues = fieldValues.putAll(other.fieldValues), addrToArrayType = addrToArrayType.putAll(other.addrToArrayType), + genericTypeStorageByAddr = genericTypeStorageByAddr.addAll(other.genericTypeStorageByAddr), addrToMockInfo = addrToMockInfo.putAll(other.addrToMockInfo), visitedValues = visitedValues.addAll(other.visitedValues), touchedAddresses = touchedAddresses.addAll(other.touchedAddresses), classIdToClearStatics = other.classIdToClearStatics, instanceFieldReads = instanceFieldReads.addAll(other.instanceFieldReads), speculativelyNotNullAddresses = speculativelyNotNullAddresses.addAll(other.speculativelyNotNullAddresses), + taintArrayUpdate = taintArrayUpdate.addAll(other.taintArrayUpdate), + symbolicEnumValues = symbolicEnumValues.addAll(other.symbolicEnumValues), ) + + fun getSymbolicEnumValues(classId: ClassId): List = + extractSymbolicEnumValues(symbolicEnumValues, classId) } // array - Java Array @@ -1039,20 +502,20 @@ data class UtNamedStore( ) /** - * Create [UtNamedStore] with simplified [index] and [value] expressions. + * Create [UtNamedStore] with unsimplified [index] and [value] expressions. * - * @see RewritingVisitor + * @note simplifications occur explicitly in [Traverser] */ -fun simplifiedNamedStore( +fun namedStore( chunkDescriptor: MemoryChunkDescriptor, index: UtExpression, value: UtExpression -) = RewritingVisitor().let { visitor -> UtNamedStore(chunkDescriptor, index.accept(visitor), value.accept(visitor)) } +) = UtNamedStore(chunkDescriptor, index, value) /** * Updates persistent map where value = null in update means deletion of original key-value */ -private fun PersistentMap.update(update: Map): PersistentMap { +fun PersistentMap.update(update: Map): PersistentMap { if (update.isEmpty()) return this val deletions = mutableListOf() val updates = mutableMapOf() @@ -1076,8 +539,6 @@ fun localMemoryUpdate(vararg updates: Pair) = private val STRING_INTERNAL = ChunkId(java.lang.String::class.qualifiedName!!, "internal") -private val NATIVE_STRING_VALUE = ChunkId(UtNativeString::class.qualifiedName!!, "value") - internal val STRING_LENGTH get() = utStringClass.getField("length", IntType.v()) internal val STRING_VALUE @@ -1090,15 +551,6 @@ internal val STRING_INTERNAL_DESCRIPTOR: MemoryChunkDescriptor get() = MemoryChunkDescriptor(STRING_INTERNAL, STRING_TYPE, SeqType) -internal val NATIVE_STRING_VALUE_DESCRIPTOR: MemoryChunkDescriptor - get() = MemoryChunkDescriptor(NATIVE_STRING_VALUE, utNativeStringClass.type, SeqType) - -/** - * Returns internal string representation by String object address, addr -> String - */ -fun Memory.nativeStringValue(addr: UtAddrExpression) = - PrimitiveValue(SeqType, findArray(NATIVE_STRING_VALUE_DESCRIPTOR).select(addr)).expr - private const val STRING_INTERN_MAP_LABEL = "java.lang.String_intern_map" /** @@ -1147,3 +599,21 @@ private operator fun MockInfoEnriched.plus(update: MockInfoEnriched?): MockInfoE private fun MutableMap>.mergeValues(other: Map>): Map> = apply { other.forEach { (key, values) -> merge(key, values) { v1, v2 -> v1 + v2 } } } + +private fun extractSymbolicEnumValues( + symbolicEnumValuesSource: PersistentList, + classId: ClassId +): List = symbolicEnumValuesSource.filter { + val symbolicValueClassId = it.type.id + + // If symbolicValueClassId is not an enum in accordance with java.lang.Class.isEnum + // function, we have to take results for its superclass (a direct inheritor of java.lang.Enum). + // Otherwise, we should get results by its own classId. + val enumClass = if (symbolicValueClassId.isEnum) { + symbolicValueClassId + } else { + it.type.sootClass.superClassOrNull()?.id + } + + enumClass == classId +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/MockStrategy.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/MockStrategy.kt index f4948b0587..0b6694415c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/MockStrategy.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/MockStrategy.kt @@ -25,7 +25,7 @@ enum class MockStrategy { OTHER_CLASSES { override fun eligibleToMock(classToMock: ClassId, classUnderTest: ClassId): Boolean = - classToMock != classUnderTest && !isSystemPackage(classToMock.packageName) + classToMock != classUnderTest && !isSystemPackage(classToMock.packageName) }; /** @@ -45,8 +45,10 @@ private val systemPackages = setOf( "sun.reflect", // we cannot mock Reflection since mockers are using it during the execution "java.awt", "sun.misc", + "jdk.internal", "kotlin.jvm.internal", - "kotlin.internal" + "kotlin.internal", + "javax" ) private fun isSystemPackage(packageName: String): Boolean = systemPackages.any { packageName.startsWith(it) } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt index 913990c39e..9faf511ab6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Mocks.kt @@ -1,7 +1,6 @@ package org.utbot.engine import org.utbot.api.mock.UtMock -import org.utbot.common.packageName import org.utbot.engine.overrides.UtArrayMock import org.utbot.engine.overrides.UtLogicMock import org.utbot.engine.overrides.UtOverrideMock @@ -12,13 +11,16 @@ import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.isRefType import org.utbot.framework.util.executableId import java.util.concurrent.atomic.AtomicInteger import kotlin.reflect.KFunction2 import kotlin.reflect.KFunction5 import kotlinx.collections.immutable.persistentListOf +import org.utbot.common.nameOfPackage +import org.utbot.engine.types.OBJECT_TYPE import org.utbot.engine.util.mockListeners.MockListenerController +import org.utbot.framework.context.MockerContext +import org.utbot.framework.plugin.api.util.isInaccessibleViaReflection import soot.BooleanType import soot.RefType import soot.Scene @@ -46,7 +48,7 @@ class UtMockInfoGenerator(private val generator: (UtAddrExpression) -> UtMockInf * Contains mock class id and mock address to work with object cache. * * Note: addr for static method mocks contains addr of the "host" object - * received by [UtBotSymbolicEngine.locateStaticObject]. + * received by [Traverser.locateStaticObject]. * * @property classId classId of the object this mock represents. * @property addr address of the mock object. @@ -90,9 +92,9 @@ data class UtObjectMockInfo( /** * Mock for the "host" object for static methods and fields with [classId] declaringClass. - * [addr] is a value received by [UtBotSymbolicEngine.locateStaticObject]. + * [addr] is a value received by [Traverser.locateStaticObject]. * - * @see UtBotSymbolicEngine.locateStaticObject + * @see Traverser.locateStaticObject */ data class UtStaticObjectMockInfo( override val classId: ClassId, @@ -115,18 +117,48 @@ data class UtNewInstanceMockInfo( * Represents mocks for static methods. * Contains the methodId. * - * Used only in [UtBotSymbolicEngine.mockStaticMethod] method to pass information into [Mocker] about the method. + * Used only in [Traverser.mockStaticMethod] method to pass information into [Mocker] about the method. * All the executables will be stored in executables of the object with [UtStaticObjectMockInfo] and the same addr. * * Note: we use non null addr here because of [createMockObject] method. We have to know address of the object * that we want to make. Although static method doesn't have "caller", we still can use address of the object - * received by [UtBotSymbolicEngine.locateStaticObject]. + * received by [Traverser.locateStaticObject]. */ data class UtStaticMethodMockInfo( override val addr: UtAddrExpression, val methodId: MethodId ) : UtMockInfo(methodId.classId, addr) +/** + * A wrapper for [ObjectValue] to store additional info. + */ +sealed class MockedObjectInfo { + abstract val value: ObjectValue? +} + +object NoMock: MockedObjectInfo() { + override val value: ObjectValue? = null +} + +/** + * Represents a mock that occurs when mock strategy allows it + * or when an object type requires always requires mocking. + * + * See [Mocker.mockAlways] for more details. + */ +class ExpectedMock(objectValue: ObjectValue): MockedObjectInfo() { + override val value: ObjectValue = objectValue +} + +/** + * Represents a mock that occurs when it is not allowed. + * E.g. mock framework is not installed or + * mock strategy is [MockStrategy.NO_MOCKS] and class is not in [Mocker.mockAlways] set. + */ +class UnexpectedMock(objectValue: ObjectValue): MockedObjectInfo() { + override val value: ObjectValue = objectValue +} + /** * Service to mock things. Knows mock strategy, class under test and class hierarchy. */ @@ -136,21 +168,30 @@ class Mocker( private val hierarchy: Hierarchy, chosenClassesToMockAlways: Set, internal val mockListenerController: MockListenerController? = null, + private val mockerContext: MockerContext, ) { + private val mocksAreDesired: Boolean = strategy != MockStrategy.NO_MOCKS + /** - * Creates mocked instance of the [type] using mock info if it should be mocked by the mocker, - * otherwise returns null. + * Creates mocked instance (if it should be mocked by the mocker) of the [type] using [mockInfo] + * otherwise returns [NoMock]. * * @see shouldMock */ - fun mock(type: RefType, mockInfo: UtMockInfo): ObjectValue? = - if (shouldMock(type, mockInfo)) createMockObject(type, mockInfo) else null + fun mock(type: RefType, mockInfo: UtMockInfo): MockedObjectInfo { + val objectValue = if (shouldMock(type, mockInfo)) createMockObject(type, mockInfo) else null + return construct(objectValue, mockInfo) + } /** - * Creates mocked instance of the [type] using mock info. Unlike to [mock], it does not - * check anything and always returns the constructed mock. + * Unlike to [mock], unconditionally creates a mocked instance of the [type] using [mockInfo]. */ - fun forceMock(type: RefType, mockInfo: UtMockInfo): ObjectValue = createMockObject(type, mockInfo) + fun forceMock(type: RefType, mockInfo: UtMockInfo): MockedObjectInfo { + mockListenerController?.onShouldMock(strategy, mockInfo) + + val objectValue = createMockObject(type, mockInfo) + return construct(objectValue, mockInfo) + } /** * Checks if Engine should mock objects of particular type with current mock strategy and mock type. @@ -168,7 +209,37 @@ class Mocker( fun shouldMock( type: RefType, mockInfo: UtMockInfo, - ): Boolean = checkIfShouldMock(type, mockInfo).also { if (it) mockListenerController?.onShouldMock(strategy) } + ): Boolean = checkIfShouldMock(type, mockInfo).also { + //[utbotSuperClasses] are not involved in code generation, so + //we shouldn't listen events that such mocks happened + if (it && type.id !in utbotSuperClasses.map { it.id }) { + mockListenerController?.onShouldMock(strategy, mockInfo) + } + } + + /** + * Constructs [MockedObjectInfo]: enriches given [mockedValue] with an information if mocking is expected or not. + */ + private fun construct(mockedValue: ObjectValue?, mockInfo: UtMockInfo): MockedObjectInfo { + if (mockedValue == null) { + return NoMock + } + + val mockingIsPossible = when (mockInfo) { + is UtFieldMockInfo, + is UtObjectMockInfo -> mockerContext.mockFrameworkInstalled + is UtNewInstanceMockInfo, + is UtStaticMethodMockInfo, + is UtStaticObjectMockInfo -> mockerContext.staticsMockingIsConfigured + } + val mockingIsForcedAndPossible = mockAlways(mockedValue.type) && mockingIsPossible + + return if (mocksAreDesired || mockingIsForcedAndPossible) { + ExpectedMock(mockedValue) + } else { + UnexpectedMock(mockedValue) + } + } private fun checkIfShouldMock( type: RefType, @@ -176,14 +247,32 @@ class Mocker( ): Boolean { if (isUtMockAssume(mockInfo)) return false // never mock UtMock.assume invocation if (isUtMockAssumeOrExecuteConcretely(mockInfo)) return false // never mock UtMock.assumeOrExecuteConcretely invocation + if (isUtMockDisableClassCastExceptionCheck(mockInfo)) return false // never mock UtMock.disableClassCastExceptionCheck invocation if (isOverriddenClass(type)) return false // never mock overriden classes + if (type.isInaccessibleViaReflection) return false // never mock classes that we can't process with reflection if (isMakeSymbolic(mockInfo)) return true // support for makeSymbolic - if (type.sootClass.isArtificialEntity) return false // never mock artificial types, i.e. Maps$lambda_computeValue_1__7 - if (!isEngineClass(type) && (type.sootClass.isInnerClass || type.sootClass.isLocal || type.sootClass.isAnonymous)) return false // there is no reason (and maybe no possibility) to mock such classes - if (!isEngineClass(type) && type.sootClass.isPrivate) return false // could not mock private classes (even if it is in mock always list) + + val sootClass = type.sootClass + + if (sootClass.isArtificialEntity) return false // never mock artificial types, i.e. Maps$lambda_computeValue_1__7 + + if (!isEngineClass(type)) { + // there is no reason (and maybe no possibility) to mock such classes + if (sootClass.isInnerClass || sootClass.isLocal || sootClass.isAnonymous) { + return false + } + + // could not mock private classes (even if it is in mock always list) + if (sootClass.isPrivate) { + return false + } + } + if (mockAlways(type)) return true // always mock randoms and loggers + if (mockInfo is UtFieldMockInfo) { - val declaringClass = mockInfo.fieldId.declaringClass + val fieldId = mockInfo.fieldId + val declaringClass = fieldId.declaringClass val sootDeclaringClass = Scene.v().getSootClass(declaringClass.name) if (sootDeclaringClass.isArtificialEntity || sootDeclaringClass.isOverridden) { @@ -193,29 +282,42 @@ class Mocker( return false } - return when { - declaringClass.packageName.startsWith("java.lang") -> false - !mockInfo.fieldId.type.isRefType -> false // mocks are allowed for ref fields only - else -> return strategy.eligibleToMock(mockInfo.fieldId.type, classUnderTest) // if we have a field with Integer type, we should not mock it + val sootField = sootDeclaringClass + .fields + .firstOrNull { it.name == fieldId.name && it.declaringClass.name == sootDeclaringClass.name } + ?: error("Unexpected $fieldId is provided into shouldMock function") + + val sootFieldType = sootField.type + + if (sootFieldType !is RefType) { + return false } + + return strategy.eligibleToMock(sootFieldType.id, classUnderTest) } + + // Note that eligibleToMock can use information retrieved from jClass + // Therefore, such classes should be already processed at this point return strategy.eligibleToMock(type.id, classUnderTest) // strategy to decide } /** * Checks whether [mockInfo] containing information about UtMock.makeSymbolic call or not. */ - private fun isMakeSymbolic(mockInfo: UtMockInfo) = + private fun isMakeSymbolic(mockInfo: UtMockInfo): Boolean = mockInfo is UtStaticMethodMockInfo && (mockInfo.methodId.signature == makeSymbolicBytecodeSignature || mockInfo.methodId.signature == nonNullableMakeSymbolicBytecodeSignature) - private fun isUtMockAssume(mockInfo: UtMockInfo) = + private fun isUtMockAssume(mockInfo: UtMockInfo): Boolean = mockInfo is UtStaticMethodMockInfo && mockInfo.methodId.signature == assumeBytecodeSignature - private fun isUtMockAssumeOrExecuteConcretely(mockInfo: UtMockInfo) = + private fun isUtMockAssumeOrExecuteConcretely(mockInfo: UtMockInfo): Boolean = mockInfo is UtStaticMethodMockInfo && mockInfo.methodId.signature == assumeOrExecuteConcretelyBytecodeSignature + private fun isUtMockDisableClassCastExceptionCheck(mockInfo: UtMockInfo): Boolean = + mockInfo is UtStaticMethodMockInfo && mockInfo.methodId.signature == disableClassCastExceptionCheckBytecodeSignature + private fun isEngineClass(type: RefType) = type.className in engineClasses private fun mockAlways(type: RefType) = type.className in classesToMockAlways @@ -264,8 +366,12 @@ class UtMockWrapper( val type: RefType, private val mockInfo: UtMockInfo ) : WrapperInterface { + override val wrappedMethods: Map = + emptyMap() - override fun UtBotSymbolicEngine.invoke( + override fun isWrappedMethod(method: SootMethod): Boolean = true + + override fun Traverser.invoke( wrapper: ObjectValue, method: SootMethod, parameters: List @@ -326,6 +432,9 @@ internal val assumeMethod: SootMethod internal val assumeOrExecuteConcretelyMethod: SootMethod get() = utMockClass.getMethod(ASSUME_OR_EXECUTE_CONCRETELY_NAME, listOf(BooleanType.v())) +internal val disableClassCastExceptionCheckMethod: SootMethod + get() = utMockClass.getMethod(DISABLE_CLASS_CAST_EXCEPTION_CHECK_NAME, listOf(OBJECT_TYPE)) + val makeSymbolicBytecodeSignature: String get() = makeSymbolicMethod.executableId.signature @@ -338,7 +447,10 @@ val assumeBytecodeSignature: String val assumeOrExecuteConcretelyBytecodeSignature: String get() = assumeOrExecuteConcretelyMethod.executableId.signature -internal val UTBOT_OVERRIDE_PACKAGE_NAME = UtOverrideMock::class.java.packageName +val disableClassCastExceptionCheckBytecodeSignature: String + get() = disableClassCastExceptionCheckMethod.executableId.signature + +internal val UTBOT_OVERRIDE_PACKAGE_NAME = UtOverrideMock::class.java.nameOfPackage private val arraycopyMethod : KFunction5, Int, Array, Int, Int, Unit> = UtArrayMock::arraycopy internal val utArrayMockArraycopyMethodName = arraycopyMethod.name @@ -357,3 +469,4 @@ internal val utLogicMockIteMethodName = UtLogicMock::ite.name private const val MAKE_SYMBOLIC_NAME = "makeSymbolic" private const val ASSUME_NAME = "assume" private const val ASSUME_OR_EXECUTE_CONCRETELY_NAME = "assumeOrExecuteConcretely" +private const val DISABLE_CLASS_CAST_EXCEPTION_CHECK_NAME = "disableClassCastExceptionCheck" diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectCounter.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectCounter.kt new file mode 100644 index 0000000000..53dd3d320a --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectCounter.kt @@ -0,0 +1,12 @@ +package org.utbot.engine + +import java.util.concurrent.atomic.AtomicInteger + +/** + * Counts new objects during execution. Used to give new addresses for objects in [Traverser] and [Resolver]. + */ +data class ObjectCounter(val initialValue: Int) { + private val internalCounter = AtomicInteger(initialValue) + + fun createNewAddr(): Int = internalCounter.getAndIncrement() +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt index 3a162ed092..2dd16eda3b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ObjectWrappers.kt @@ -4,26 +4,65 @@ import org.utbot.common.WorkaroundReason.MAKE_SYMBOLIC import org.utbot.common.workaround import org.utbot.engine.UtListClass.UT_ARRAY_LIST import org.utbot.engine.UtListClass.UT_LINKED_LIST +import org.utbot.engine.UtListClass.UT_LINKED_LIST_WITH_NULLABLE_CHECK import org.utbot.engine.UtOptionalClass.UT_OPTIONAL import org.utbot.engine.UtOptionalClass.UT_OPTIONAL_DOUBLE import org.utbot.engine.UtOptionalClass.UT_OPTIONAL_INT import org.utbot.engine.UtOptionalClass.UT_OPTIONAL_LONG +import org.utbot.engine.UtStreamClass.UT_DOUBLE_STREAM +import org.utbot.engine.UtStreamClass.UT_INT_STREAM +import org.utbot.engine.UtStreamClass.UT_LONG_STREAM import org.utbot.engine.UtStreamClass.UT_STREAM import org.utbot.engine.overrides.collections.AssociativeArray import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray +import org.utbot.engine.overrides.collections.UtArrayList +import org.utbot.engine.overrides.collections.UtArrayList.UtArrayListIterator +import org.utbot.engine.overrides.collections.UtArrayList.UtArrayListSimpleIterator import org.utbot.engine.overrides.collections.UtHashMap import org.utbot.engine.overrides.collections.UtHashSet -import org.utbot.engine.overrides.strings.UtNativeString +import org.utbot.engine.overrides.collections.UtHashSet.UtHashSetIterator +import org.utbot.engine.overrides.collections.UtLinkedList +import org.utbot.engine.overrides.collections.UtLinkedList.UtLinkedListIterator +import org.utbot.engine.overrides.collections.UtLinkedList.UtReverseIterator +import org.utbot.engine.overrides.collections.UtLinkedListWithNullableCheck +import org.utbot.engine.overrides.collections.UtOptional +import org.utbot.engine.overrides.collections.UtOptionalDouble +import org.utbot.engine.overrides.collections.UtOptionalInt +import org.utbot.engine.overrides.collections.UtOptionalLong +import org.utbot.engine.overrides.security.UtSecurityManager +import org.utbot.engine.overrides.stream.UtDoubleStream +import org.utbot.engine.overrides.stream.UtIntStream +import org.utbot.engine.overrides.stream.UtLongStream +import org.utbot.engine.overrides.stream.UtStream import org.utbot.engine.overrides.strings.UtString import org.utbot.engine.overrides.strings.UtStringBuffer import org.utbot.engine.overrides.strings.UtStringBuilder +import org.utbot.engine.overrides.threads.UtCompletableFuture +import org.utbot.engine.overrides.threads.UtCountDownLatch +import org.utbot.engine.overrides.threads.UtExecutorService +import org.utbot.engine.overrides.threads.UtThread +import org.utbot.engine.overrides.threads.UtThreadGroup import org.utbot.engine.pc.UtAddrExpression +import org.utbot.engine.types.COMPLETABLE_FUTURE_TYPE +import org.utbot.engine.types.COUNT_DOWN_LATCH_TYPE +import org.utbot.engine.types.EXECUTOR_SERVICE_TYPE +import org.utbot.engine.types.OPTIONAL_DOUBLE_TYPE +import org.utbot.engine.types.OPTIONAL_INT_TYPE +import org.utbot.engine.types.OPTIONAL_LONG_TYPE +import org.utbot.engine.types.OPTIONAL_TYPE +import org.utbot.engine.types.SECURITY_MANAGER_TYPE +import org.utbot.engine.types.STRING_BUFFER_TYPE +import org.utbot.engine.types.STRING_BUILDER_TYPE +import org.utbot.engine.types.STRING_TYPE +import org.utbot.engine.types.THREAD_GROUP_TYPE +import org.utbot.engine.types.THREAD_TYPE +import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.UtContext import org.utbot.framework.plugin.api.util.constructorId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.stringClassId @@ -32,12 +71,8 @@ import soot.RefType import soot.Scene import soot.SootClass import soot.SootMethod -import java.util.Optional -import java.util.OptionalDouble -import java.util.OptionalInt -import java.util.OptionalLong -import java.util.concurrent.CopyOnWriteArrayList import kotlin.reflect.KClass +import kotlin.reflect.jvm.jvmName typealias TypeToBeWrapped = RefType typealias WrapperType = RefType @@ -49,29 +84,39 @@ typealias WrapperType = RefType val classToWrapper: MutableMap = mutableMapOf().apply { putSootClass(java.lang.StringBuilder::class, utStringBuilderClass) - putSootClass(UtStringBuilder::class, utStringBuilderClass) putSootClass(java.lang.StringBuffer::class, utStringBufferClass) - putSootClass(UtStringBuffer::class, utStringBufferClass) - putSootClass(UtNativeString::class, utNativeStringClass) putSootClass(java.lang.CharSequence::class, utStringClass) putSootClass(java.lang.String::class, utStringClass) - putSootClass(UtString::class, utStringClass) - putSootClass(Optional::class, UT_OPTIONAL.className) - putSootClass(OptionalInt::class, UT_OPTIONAL_INT.className) - putSootClass(OptionalLong::class, UT_OPTIONAL_LONG.className) - putSootClass(OptionalDouble::class, UT_OPTIONAL_DOUBLE.className) + putSootClass(java.util.Optional::class, UT_OPTIONAL.className) + putSootClass(java.util.OptionalInt::class, UT_OPTIONAL_INT.className) + putSootClass(java.util.OptionalLong::class, UT_OPTIONAL_LONG.className) + putSootClass(java.util.OptionalDouble::class, UT_OPTIONAL_DOUBLE.className) - putSootClass(RangeModifiableUnlimitedArray::class, RangeModifiableUnlimitedArrayWrapper::class) - putSootClass(AssociativeArray::class, AssociativeArrayWrapper::class) + // threads + putSootClass(java.lang.Thread::class, utThreadClass) + putSootClass(java.lang.ThreadGroup::class, utThreadGroupClass) + + // executors, futures and latches + putSootClass(java.util.concurrent.ExecutorService::class, utExecutorServiceClass) + putSootClass(java.util.concurrent.ThreadPoolExecutor::class, utExecutorServiceClass) + putSootClass(java.util.concurrent.ForkJoinPool::class, utExecutorServiceClass) + putSootClass(java.util.concurrent.ScheduledThreadPoolExecutor::class, utExecutorServiceClass) + putSootClass(java.util.concurrent.CountDownLatch::class, utCountDownLatchClass) + putSootClass(java.util.concurrent.CompletableFuture::class, utCompletableFutureClass) + putSootClass(java.util.concurrent.CompletionStage::class, utCompletableFutureClass) putSootClass(java.util.List::class, UT_ARRAY_LIST.className) putSootClass(java.util.AbstractList::class, UT_ARRAY_LIST.className) putSootClass(java.util.ArrayList::class, UT_ARRAY_LIST.className) - putSootClass(CopyOnWriteArrayList::class, UT_ARRAY_LIST.className) + putSootClass(java.util.concurrent.CopyOnWriteArrayList::class, UT_ARRAY_LIST.className) putSootClass(java.util.LinkedList::class, UT_LINKED_LIST.className) putSootClass(java.util.AbstractSequentialList::class, UT_LINKED_LIST.className) - // TODO mistake JIRA:1495 - putSootClass(java.util.ArrayDeque::class, UT_LINKED_LIST.className) + + putSootClass(java.util.ArrayDeque::class, UT_LINKED_LIST_WITH_NULLABLE_CHECK.className) + putSootClass(java.util.concurrent.ConcurrentLinkedDeque::class, UT_LINKED_LIST_WITH_NULLABLE_CHECK.className) + putSootClass(java.util.concurrent.ConcurrentLinkedQueue::class, UT_LINKED_LIST_WITH_NULLABLE_CHECK.className) + putSootClass(java.util.concurrent.LinkedBlockingDeque::class, UT_LINKED_LIST_WITH_NULLABLE_CHECK.className) + putSootClass(java.util.concurrent.LinkedBlockingQueue::class, UT_LINKED_LIST_WITH_NULLABLE_CHECK.className) putSootClass(java.util.Set::class, UtHashSet::class) putSootClass(java.util.AbstractSet::class, UtHashSet::class) @@ -82,10 +127,33 @@ val classToWrapper: MutableMap = putSootClass(java.util.AbstractMap::class, UtHashMap::class) putSootClass(java.util.LinkedHashMap::class, UtHashMap::class) putSootClass(java.util.HashMap::class, UtHashMap::class) + putSootClass(java.util.concurrent.ConcurrentHashMap::class, UtHashMap::class) + + // Iterators + putSootClass(java.util.Iterator::class, UtArrayListSimpleIterator::class) + putSootClass(java.util.ListIterator::class, UtArrayListIterator::class) + putSootClass(UtLinkedListIterator::class, UtLinkedListIterator::class) + putSootClass(UtReverseIterator::class, UtReverseIterator::class) + putSootClass(UtHashSetIterator::class, UtHashSetIterator::class) putSootClass(java.util.stream.BaseStream::class, UT_STREAM.className) putSootClass(java.util.stream.Stream::class, UT_STREAM.className) - // TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146 + putSootClass(java.util.stream.IntStream::class, UT_INT_STREAM.className) + putSootClass(java.util.stream.LongStream::class, UT_LONG_STREAM.className) + putSootClass(java.util.stream.DoubleStream::class, UT_DOUBLE_STREAM.className) + + putSootClass(java.lang.SecurityManager::class, UtSecurityManager::class) + + putSootClass(RangeModifiableUnlimitedArray::class, RangeModifiableUnlimitedArrayWrapper::class) + putSootClass(AssociativeArray::class, AssociativeArray::class) + }.apply { + // Each wrapper has to wrap itself to make possible to create it but with the underlying type in UtMocks or in wrappers. + // We take this particular classloader because current classloader cannot load our classes + val applicationClassLoader = UtContext::class.java.classLoader + values.distinct().forEach { + val kClass = applicationClassLoader.loadClass(it.className).kotlin + putSootClass(kClass, it) + } } /** @@ -103,7 +171,7 @@ val wrapperToClass: Map> = private fun MutableMap.putSootClass( key: KClass<*>, value: KClass<*> -) = putSootClass(key, Scene.v().getSootClass(value.java.canonicalName).type) +) = putSootClass(key, Scene.v().getSootClass(value.jvmName).type) // It is important to use `jvmName` because `canonicalName` replaces `$` for nested classes to `.` private fun MutableMap.putSootClass( key: KClass<*>, @@ -118,80 +186,138 @@ private fun MutableMap.putSootClass( private fun MutableMap.putSootClass( key: KClass<*>, value: RefType -) = put(Scene.v().getSootClass(key.java.canonicalName).type, value) +) = put(Scene.v().getSootClass(key.jvmName).type, value) // It is important to use `jvmName` because `canonicalName` replaces `$` for nested classes to `.` -private val wrappers = mapOf( +private val wrappers: Map ObjectValue> = mutableMapOf( wrap(java.lang.StringBuilder::class) { type, addr -> objectValue(type, addr, UtStringBuilderWrapper()) }, - wrap(UtStringBuilder::class) { type, addr -> objectValue(type, addr, UtStringBuilderWrapper()) }, wrap(java.lang.StringBuffer::class) { type, addr -> objectValue(type, addr, UtStringBufferWrapper()) }, - wrap(UtStringBuffer::class) { type, addr -> objectValue(type, addr, UtStringBufferWrapper()) }, - wrap(UtNativeString::class) { type, addr -> objectValue(type, addr, UtNativeStringWrapper()) }, wrap(java.lang.CharSequence::class) { type, addr -> objectValue(type, addr, StringWrapper()) }, wrap(java.lang.String::class) { type, addr -> objectValue(type, addr, StringWrapper()) }, - wrap(UtString::class) { type, addr -> objectValue(type, addr, StringWrapper()) }, - wrap(Optional::class) { type, addr -> objectValue(type, addr, OptionalWrapper(UT_OPTIONAL)) }, - wrap(OptionalInt::class) { type, addr -> objectValue(type, addr, OptionalWrapper(UT_OPTIONAL_INT)) }, - wrap(OptionalLong::class) { type, addr -> objectValue(type, addr, OptionalWrapper(UT_OPTIONAL_LONG)) }, - wrap(OptionalDouble::class) { type, addr -> objectValue(type, addr, OptionalWrapper(UT_OPTIONAL_DOUBLE)) }, - - wrap(RangeModifiableUnlimitedArray::class) { type, addr -> - objectValue(type, addr, RangeModifiableUnlimitedArrayWrapper()) - }, - wrap(AssociativeArray::class) { type, addr -> - objectValue(type, addr, AssociativeArrayWrapper()) - }, + wrap(java.util.Optional::class) { type, addr -> objectValue(type, addr, OptionalWrapper(UT_OPTIONAL)) }, + wrap(java.util.OptionalInt::class) { type, addr -> objectValue(type, addr, OptionalWrapper(UT_OPTIONAL_INT)) }, + wrap(java.util.OptionalLong::class) { type, addr -> objectValue(type, addr, OptionalWrapper(UT_OPTIONAL_LONG)) }, + wrap(java.util.OptionalDouble::class) { type, addr -> objectValue(type, addr, OptionalWrapper(UT_OPTIONAL_DOUBLE)) }, + + // threads + wrap(java.lang.Thread::class) { type, addr -> objectValue(type, addr, ThreadWrapper()) }, + wrap(java.lang.ThreadGroup::class) { type, addr -> objectValue(type, addr, ThreadGroupWrapper()) }, + wrap(java.util.concurrent.ExecutorService::class) { type, addr -> objectValue(type, addr, ExecutorServiceWrapper()) }, + wrap(java.util.concurrent.ThreadPoolExecutor::class) { type, addr -> objectValue(type, addr, ExecutorServiceWrapper()) }, + wrap(java.util.concurrent.ForkJoinPool::class) { type, addr -> objectValue(type, addr, ExecutorServiceWrapper()) }, + wrap(java.util.concurrent.ScheduledThreadPoolExecutor::class) { type, addr -> objectValue(type, addr, ExecutorServiceWrapper()) }, + wrap(java.util.concurrent.CountDownLatch::class) { type, addr -> objectValue(type, addr, CountDownLatchWrapper()) }, + wrap(java.util.concurrent.CompletableFuture::class) { type, addr -> objectValue(type, addr, CompletableFutureWrapper()) }, + wrap(java.util.concurrent.CompletionStage::class) { type, addr -> objectValue(type, addr, CompletableFutureWrapper()) }, + // list wrappers - wrap(java.util.List::class) { _, addr -> - objectValue(ARRAY_LIST_TYPE, addr, ListWrapper(UT_ARRAY_LIST)) - }, - wrap(java.util.AbstractList::class) { _, addr -> - objectValue(ARRAY_LIST_TYPE, addr, ListWrapper(UT_ARRAY_LIST)) - }, - wrap(java.util.ArrayList::class) { _, addr -> - objectValue(ARRAY_LIST_TYPE, addr, ListWrapper(UT_ARRAY_LIST)) - }, - wrap(CopyOnWriteArrayList::class) { _, addr -> - objectValue(ARRAY_LIST_TYPE, addr, ListWrapper(UT_ARRAY_LIST)) - }, + wrap(java.util.List::class) { _, addr -> objectValue(ARRAY_LIST_TYPE, addr, ListWrapper(UT_ARRAY_LIST)) }, + wrap(java.util.AbstractList::class) { _, addr -> objectValue(ARRAY_LIST_TYPE, addr, ListWrapper(UT_ARRAY_LIST)) }, + wrap(java.util.ArrayList::class) { _, addr -> objectValue(ARRAY_LIST_TYPE, addr, ListWrapper(UT_ARRAY_LIST)) }, + wrap(java.util.concurrent.CopyOnWriteArrayList::class) { type, addr -> objectValue(type, addr, ListWrapper(UT_ARRAY_LIST)) }, - wrap(java.util.LinkedList::class) { _, addr -> - objectValue(LINKED_LIST_TYPE, addr, ListWrapper(UT_LINKED_LIST)) - }, - wrap(java.util.AbstractSequentialList::class) { _, addr -> - objectValue(LINKED_LIST_TYPE, addr, ListWrapper(UT_LINKED_LIST)) + wrap(java.util.LinkedList::class) { _, addr -> objectValue(LINKED_LIST_TYPE, addr, ListWrapper(UT_LINKED_LIST)) }, + wrap(java.util.AbstractSequentialList::class) { _, addr -> objectValue(LINKED_LIST_TYPE, addr, ListWrapper(UT_LINKED_LIST)) }, + + // queue, deque wrappers + wrap(java.util.ArrayDeque::class) { type, addr -> + objectValue(type, addr, ListWrapper(UT_LINKED_LIST_WITH_NULLABLE_CHECK)) }, - // TODO mistake JIRA:1495 - wrap(java.util.ArrayDeque::class) { _, addr -> - objectValue(LINKED_LIST_TYPE, addr, ListWrapper(UT_LINKED_LIST)) + wrap(java.util.concurrent.ConcurrentLinkedDeque::class) { type, addr -> + objectValue(type, addr, ListWrapper(UT_LINKED_LIST_WITH_NULLABLE_CHECK)) }, - // set wrappers - wrap(java.util.Set::class) { _, addr -> - objectValue(LINKED_HASH_SET_TYPE, addr, SetWrapper()) + wrap(java.util.concurrent.ConcurrentLinkedQueue::class) { type, addr -> + objectValue(type, addr, ListWrapper(UT_LINKED_LIST_WITH_NULLABLE_CHECK)) }, - wrap(java.util.AbstractSet::class) { _, addr -> - objectValue(LINKED_HASH_SET_TYPE, addr, SetWrapper()) + wrap(java.util.concurrent.LinkedBlockingDeque::class) { type, addr -> + objectValue(type, addr, ListWrapper(UT_LINKED_LIST_WITH_NULLABLE_CHECK)) }, - wrap(java.util.HashSet::class) { _, addr -> - objectValue(HASH_SET_TYPE, addr, SetWrapper()) - }, - wrap(java.util.LinkedHashSet::class) { _, addr -> - objectValue(LINKED_HASH_SET_TYPE, addr, SetWrapper()) + wrap(java.util.concurrent.LinkedBlockingQueue::class) { type, addr -> + objectValue(type, addr, ListWrapper(UT_LINKED_LIST_WITH_NULLABLE_CHECK)) }, + + // set wrappers + wrap(java.util.Set::class) { _, addr -> objectValue(LINKED_HASH_SET_TYPE, addr, SetWrapper()) }, + wrap(java.util.AbstractSet::class) { _, addr -> objectValue(LINKED_HASH_SET_TYPE, addr, SetWrapper()) }, + wrap(java.util.HashSet::class) { _, addr -> objectValue(HASH_SET_TYPE, addr, SetWrapper()) }, + wrap(java.util.LinkedHashSet::class) { _, addr -> objectValue(LINKED_HASH_SET_TYPE, addr, SetWrapper()) }, + // map wrappers wrap(java.util.Map::class) { _, addr -> objectValue(LINKED_HASH_MAP_TYPE, addr, MapWrapper()) }, wrap(java.util.AbstractMap::class) { _, addr -> objectValue(LINKED_HASH_MAP_TYPE, addr, MapWrapper()) }, wrap(java.util.LinkedHashMap::class) { _, addr -> objectValue(LINKED_HASH_MAP_TYPE, addr, MapWrapper()) }, wrap(java.util.HashMap::class) { _, addr -> objectValue(HASH_MAP_TYPE, addr, MapWrapper()) }, + wrap(java.util.concurrent.ConcurrentHashMap::class) { _, addr -> objectValue(HASH_MAP_TYPE, addr, MapWrapper()) }, + + // iterator wrappers + wrap(java.util.Iterator::class) { _, addr -> objectValue(ITERATOR_TYPE, addr, IteratorOfListWrapper()) }, + wrap(java.util.ListIterator::class) { _, addr -> objectValue(LIST_ITERATOR_TYPE, addr, ListIteratorOfListWrapper()) }, // stream wrappers wrap(java.util.stream.BaseStream::class) { _, addr -> objectValue(STREAM_TYPE, addr, CommonStreamWrapper()) }, wrap(java.util.stream.Stream::class) { _, addr -> objectValue(STREAM_TYPE, addr, CommonStreamWrapper()) }, - // TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146 -).also { + wrap(java.util.stream.IntStream::class) { _, addr -> objectValue(INT_STREAM_TYPE, addr, IntStreamWrapper()) }, + wrap(java.util.stream.LongStream::class) { _, addr -> objectValue(LONG_STREAM_TYPE, addr, LongStreamWrapper()) }, + wrap(java.util.stream.DoubleStream::class) { _, addr -> objectValue(DOUBLE_STREAM_TYPE, addr, DoubleStreamWrapper()) }, + + // Security-related wrappers + wrap(java.lang.SecurityManager::class) { type, addr -> objectValue(type, addr, SecurityManagerWrapper()) }, +).apply { + // Each wrapper has to wrap itself to make possible to create it but with the underlying type in UtMocks or in wrappers + arrayOf( + wrap(UtStringBuilder::class) { _, addr -> objectValue(STRING_BUILDER_TYPE, addr, UtStringBuilderWrapper()) }, + wrap(UtStringBuffer::class) { _, addr -> objectValue(STRING_BUFFER_TYPE, addr, UtStringBufferWrapper()) }, + wrap(UtString::class) { _, addr -> objectValue(STRING_TYPE, addr, StringWrapper()) }, + + wrap(UtOptional::class) { _, addr -> objectValue(OPTIONAL_TYPE, addr, OptionalWrapper(UT_OPTIONAL)) }, + wrap(UtOptionalInt::class) { _, addr -> objectValue(OPTIONAL_INT_TYPE, addr, OptionalWrapper(UT_OPTIONAL_INT)) }, + wrap(UtOptionalLong::class) { _, addr -> objectValue(OPTIONAL_LONG_TYPE, addr, OptionalWrapper(UT_OPTIONAL_LONG)) }, + wrap(UtOptionalDouble::class) { _, addr -> objectValue(OPTIONAL_DOUBLE_TYPE, addr, OptionalWrapper(UT_OPTIONAL_DOUBLE)) }, + + wrap(UtThread::class) { _, addr -> objectValue(THREAD_TYPE, addr, ThreadWrapper()) }, + wrap(UtThreadGroup::class) { _, addr -> objectValue(THREAD_GROUP_TYPE, addr, ThreadGroupWrapper()) }, + wrap(UtExecutorService::class) { _, addr -> objectValue(EXECUTOR_SERVICE_TYPE, addr, ExecutorServiceWrapper()) }, + wrap(UtCountDownLatch::class) { _, addr -> objectValue(COUNT_DOWN_LATCH_TYPE, addr, CountDownLatchWrapper()) }, + wrap(UtCompletableFuture::class) { _, addr -> objectValue(COMPLETABLE_FUTURE_TYPE, addr, CompletableFutureWrapper()) }, + + wrap(UtArrayList::class) { _, addr -> objectValue(ARRAY_LIST_TYPE, addr, ListWrapper(UT_ARRAY_LIST)) }, + wrap(UtLinkedList::class) { _, addr -> objectValue(LINKED_LIST_TYPE, addr, ListWrapper(UT_LINKED_LIST)) }, + wrap(UtLinkedListWithNullableCheck::class) { _, addr -> + objectValue(ARRAY_DEQUE_TYPE, addr, ListWrapper(UT_LINKED_LIST_WITH_NULLABLE_CHECK)) + }, + + wrap(UtHashSet::class) { _, addr -> objectValue(HASH_SET_TYPE, addr, SetWrapper()) }, + + wrap(UtHashMap::class) { _, addr -> objectValue(HASH_MAP_TYPE, addr, MapWrapper()) }, + + wrap(UtArrayListSimpleIterator::class) { _, addr -> objectValue(ITERATOR_TYPE, addr, IteratorOfListWrapper()) }, + wrap(UtArrayListIterator::class) { _, addr -> objectValue(LIST_ITERATOR_TYPE, addr, ListIteratorOfListWrapper()) }, + wrap(UtLinkedListIterator::class) { _, addr -> objectValue(LIST_ITERATOR_TYPE, addr, ListIteratorOfListWrapper()) }, // use ListIterator instead of simple Iterator because java.util.LinkedList may return ListIterator for `iterator` + wrap(UtReverseIterator::class) { _, addr -> objectValue(ITERATOR_TYPE, addr, ReverseIteratorWrapper()) }, + wrap(UtHashSetIterator::class) { _, addr -> objectValue(ITERATOR_TYPE, addr, IteratorOfSetWrapper()) }, + + wrap(UtStream::class) { _, addr -> objectValue(STREAM_TYPE, addr, CommonStreamWrapper()) }, + wrap(UtIntStream::class) { _, addr -> objectValue(INT_STREAM_TYPE, addr, IntStreamWrapper()) }, + wrap(UtLongStream::class) { _, addr -> objectValue(LONG_STREAM_TYPE, addr, LongStreamWrapper()) }, + wrap(UtDoubleStream::class) { _, addr -> objectValue(DOUBLE_STREAM_TYPE, addr, DoubleStreamWrapper()) }, + + wrap(UtSecurityManager::class) { _, addr -> objectValue(SECURITY_MANAGER_TYPE, addr, SecurityManagerWrapper()) }, + + wrap(RangeModifiableUnlimitedArray::class) { type, addr -> + objectValue(type, addr, RangeModifiableUnlimitedArrayWrapper()) + }, + wrap(AssociativeArray::class) { type, addr -> + objectValue(type, addr, AssociativeArrayWrapper()) + }, + ).let { putAll(it) } +}.also { // check every `wrapped` class has a corresponding value in [classToWrapper] - it.keys.all { key -> + val missedWrappers = it.keys.filterNot { key -> Scene.v().getSootClass(key.name).type in classToWrapper.keys } + + require(missedWrappers.isEmpty()) { + "Missed wrappers for classes [${missedWrappers.joinToString(", ")}]" + } } private fun wrap(kClass: KClass<*>, implementation: (RefType, UtAddrExpression) -> ObjectValue) = @@ -200,26 +326,67 @@ private fun wrap(kClass: KClass<*>, implementation: (RefType, UtAddrExpression) internal fun wrapper(type: RefType, addr: UtAddrExpression): ObjectValue? = wrappers[type.id]?.invoke(type, addr) +typealias MethodSymbolicImplementation = (Traverser, ObjectValue, SootMethod, List) -> List + interface WrapperInterface { + /** + * Checks whether a symbolic implementation exists for the [method]. + */ + fun isWrappedMethod(method: SootMethod): Boolean = method.name in wrappedMethods + + /** + * Mapping from a method signature to its symbolic implementation (if present). + */ + val wrappedMethods: Map + /** * Returns list of invocation results */ - operator fun UtBotSymbolicEngine.invoke( + operator fun Traverser.invoke( wrapper: ObjectValue, method: SootMethod, parameters: List - ): List + ): List { + val wrappedMethodResult = wrappedMethods[method.name] + ?: error("unknown wrapped method ${method.name} for ${this@WrapperInterface::class}") + + return wrappedMethodResult(this, wrapper, method, parameters) + } fun value(resolver: Resolver, wrapper: ObjectValue): UtModel + + /** + * It is an index for type parameter corresponding to the result + * value of `select` operation. For example, for arrays and lists it's zero, + * for associative array it's one. + */ + val selectOperationTypeIndex: Int + get() = 0 + + /** + * Similar to [selectOperationTypeIndex], it is responsible for type index + * of the returning value from `get` operation + */ + val getOperationTypeIndex: Int + get() = 0 } // TODO: perhaps we have to have wrapper around concrete value here data class ThrowableWrapper(val throwable: Throwable) : WrapperInterface { - override fun UtBotSymbolicEngine.invoke(wrapper: ObjectValue, method: SootMethod, parameters: List) = + override val wrappedMethods: Map = emptyMap() + + override fun isWrappedMethod(method: SootMethod): Boolean = true + + override fun Traverser.invoke(wrapper: ObjectValue, method: SootMethod, parameters: List) = workaround(MAKE_SYMBOLIC) { listOf( MethodResult( - createConst(method.returnType, typeRegistry.findNewSymbolicReturnValueName()) + createConst( + method.returnType, + typeRegistry.findNewSymbolicReturnValueName(), + // we don't want to mock anything returned from a throwable instance's methods + mockInfoGenerator = null + ) ) ) } @@ -230,18 +397,15 @@ data class ThrowableWrapper(val throwable: Throwable) : WrapperInterface { val addr = resolver.holder.concreteAddr(wrapper.addr) val modelName = nextModelName(throwable.javaClass.simpleName.decapitalize()) - val instantiationChain = mutableListOf() - return UtAssembleModel(addr, classId, modelName, instantiationChain) - .apply { - instantiationChain += when (val message = throwable.message) { - null -> UtExecutableCallModel(null, constructorId(classId), emptyList(), this) - else -> UtExecutableCallModel( - null, - constructorId(classId, stringClassId), - listOf(UtPrimitiveModel(message)), - this, - ) - } - } + val instantiationCall = when (val message = throwable.message) { + null -> UtExecutableCallModel(instance = null, constructorId(classId), emptyList()) + else -> UtExecutableCallModel( + instance = null, + constructorId(classId, stringClassId), + listOf(UtPrimitiveModel(message)) + ) + } + + return UtAssembleModel(addr, classId, modelName, instantiationCall) } } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt index 19348fc6f9..7b0dd6966f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/OptionalWrapper.kt @@ -16,7 +16,6 @@ import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.defaultValueModel @@ -58,14 +57,18 @@ class OptionalWrapper(private val utOptionalClass: UtOptionalClass) : BaseOverri private val AS_OPTIONAL_METHOD_SIGNATURE = overriddenClass.getMethodByName(UtOptional<*>::asOptional.name).signature - override fun UtBotSymbolicEngine.overrideInvoke( + override fun Traverser.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List ): List? { when (method.signature) { AS_OPTIONAL_METHOD_SIGNATURE -> { - return listOf(MethodResult(wrapper.copy(typeStorage = TypeStorage(method.returnType)))) + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(method.returnType) + val resultingWrapper = wrapper.copy(typeStorage = typeStorage) + val methodResult = MethodResult(resultingWrapper) + + return listOf(methodResult) } UT_OPTIONAL_EQ_GENERIC_TYPE_SIGNATURE -> { return listOf( @@ -90,15 +93,11 @@ class OptionalWrapper(private val utOptionalClass: UtOptionalClass) : BaseOverri val addr = holder.concreteAddr(wrapper.addr) val modelName = nextModelName(baseModelName) - val instantiationChain = mutableListOf() - val modificationsChain = mutableListOf() - return UtAssembleModel(addr, classId, modelName, instantiationChain, modificationsChain) - .apply { - instantiationChain += instantiationFactoryCallModel(classId, wrapper, this) - } + val instantiationCall = instantiationFactoryCallModel(classId, wrapper) + return UtAssembleModel(addr, classId, modelName, instantiationCall) } - private fun Resolver.instantiationFactoryCallModel(classId: ClassId, wrapper: ObjectValue, model: UtAssembleModel) : UtExecutableCallModel { + private fun Resolver.instantiationFactoryCallModel(classId: ClassId, wrapper: ObjectValue) : UtExecutableCallModel { val valueField = FieldId(overriddenClass.id, "value") val isPresentFieldId = FieldId(overriddenClass.id, "isPresent") val values = collectFieldModels(wrapper.addr, overriddenClass.type) @@ -110,21 +109,23 @@ class OptionalWrapper(private val utOptionalClass: UtOptionalClass) : BaseOverri } return if (!isPresent) { UtExecutableCallModel( - null, MethodId( + instance = null, + MethodId( classId, "empty", classId, emptyList() - ), emptyList(), model + ), emptyList() ) } else { UtExecutableCallModel( - null, MethodId( + instance = null, + MethodId( classId, "of", classId, listOf(utOptionalClass.elementClassId) - ), listOf(valueModel), model + ), listOf(valueModel) ) } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt index 4fca4b478d..809da5c78b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Resolver.kt @@ -7,7 +7,7 @@ import org.utbot.common.workaround import org.utbot.engine.MemoryState.CURRENT import org.utbot.engine.MemoryState.INITIAL import org.utbot.engine.MemoryState.STATIC_INITIAL -import org.utbot.engine.TypeRegistry.Companion.objectNumDimensions +import org.utbot.engine.types.TypeRegistry.Companion.objectNumDimensions import org.utbot.engine.pc.UtAddrExpression import org.utbot.engine.pc.UtArrayExpressionBase import org.utbot.engine.pc.UtArraySort @@ -25,8 +25,8 @@ import org.utbot.engine.pc.mkLong import org.utbot.engine.pc.mkShort import org.utbot.engine.pc.select import org.utbot.engine.pc.store -import org.utbot.engine.util.statics.concrete.isEnumValuesFieldName import org.utbot.engine.symbolic.asHardConstraint +import org.utbot.engine.util.statics.concrete.isEnumValuesFieldName import org.utbot.engine.z3.intValue import org.utbot.engine.z3.value import org.utbot.framework.assemble.AssembleModelGenerator @@ -34,6 +34,7 @@ import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.SYMBOLIC_NULL_ADDR import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtClassRefModel @@ -46,13 +47,15 @@ import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.framework.plugin.api.UtExplicitlyThrownException import org.utbot.framework.plugin.api.UtImplicitlyThrownException import org.utbot.framework.plugin.api.UtInstrumentation -import org.utbot.framework.plugin.api.UtMethod +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtOverflowFailure import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtSandboxFailure import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation +import org.utbot.framework.plugin.api.UtTaintAnalysisFailure import org.utbot.framework.plugin.api.UtVoidModel import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.id @@ -63,14 +66,6 @@ import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.primitiveByWrapper import org.utbot.framework.plugin.api.util.utContext import org.utbot.framework.util.nextModelName -import java.awt.color.ICC_ProfileRGB -import java.io.PrintStream -import java.security.AccessControlContext -import java.util.concurrent.atomic.AtomicInteger -import kotlin.math.max -import kotlin.math.min -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.persistentSetOf import soot.ArrayType import soot.BooleanType import soot.ByteType @@ -87,13 +82,41 @@ import soot.SootClass import soot.SootField import soot.Type import soot.VoidType -import sun.java2d.cmm.lcms.LcmsServiceProvider +import java.awt.color.ICC_ProfileRGB +import java.io.PrintStream +import java.security.AccessControlContext +import java.security.AccessControlException +import java.util.concurrent.atomic.AtomicInteger +import kotlin.math.max +import kotlin.math.min +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentSetOf +import org.utbot.framework.plugin.api.OverflowDetectionError +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.engine.types.CLASS_REF_CLASSNAME +import org.utbot.engine.types.CLASS_REF_CLASS_ID +import org.utbot.engine.types.CLASS_REF_NUM_DIMENSIONS_DESCRIPTOR +import org.utbot.engine.types.CLASS_REF_TYPE_DESCRIPTOR +import org.utbot.engine.types.ENUM_ORDINAL +import org.utbot.engine.types.OBJECT_TYPE +import org.utbot.engine.types.STRING_TYPE +import org.utbot.engine.types.TypeRegistry +import org.utbot.engine.types.TypeResolver +import org.utbot.framework.UtSettings +import org.utbot.framework.plugin.api.visible.UtStreamConsumingException +import org.utbot.framework.plugin.api.UtStreamConsumingFailure +import org.utbot.framework.plugin.api.util.constructor.ValueConstructor +import org.utbot.framework.plugin.api.util.fieldId +import org.utbot.framework.plugin.api.util.isStatic // hack +// IMPORTANT, if these values are used in code for analysis (e.g., in the wrappers), +// they must be a compilation time constant to avoid extra analysis. const val MAX_LIST_SIZE = 10 const val MAX_RESOLVE_LIST_SIZE = 256 const val MAX_STRING_SIZE = 40 -internal const val HARD_MAX_ARRAY_SIZE = 256 +internal const val MAX_STREAM_SIZE = 256 +internal val hardMaxArraySize = UtSettings.maxArraySize internal const val PREFERRED_ARRAY_SIZE = 2 internal const val MIN_PREFERRED_INTEGER = -256 @@ -112,12 +135,13 @@ typealias Address = Int */ class Resolver( val hierarchy: Hierarchy, - private val memory: Memory, + val memory: Memory, val typeRegistry: TypeRegistry, private val typeResolver: TypeResolver, val holder: UtSolverStatusSAT, - methodUnderTest: UtMethod<*>, - private val softMaxArraySize: Int + methodUnderTest: ExecutableId, + private val softMaxArraySize: Int, + private val objectCounter: ObjectCounter ) { private val classLoader: ClassLoader @@ -131,7 +155,7 @@ class Resolver( private val instrumentation = mutableListOf() private val requiredInstanceFields = mutableMapOf>() - private val assembleModelGenerator = AssembleModelGenerator(methodUnderTest) + private val assembleModelGenerator = AssembleModelGenerator(methodUnderTest.classId.packageName) /** * Contains FieldId of the static field which is construction at the moment and null of there is no such field. @@ -270,8 +294,11 @@ class Resolver( // Associates mock infos by concrete addresses // Sometimes we might have two mocks with the same concrete address, and here we group their mockInfos // Then we should merge executables for mocks with the same concrete addresses with respect to the calls order - val mocks = memory - .mocks() + val memoryMocks = memory.mocks() + // TODO add a comment about why is it impossible to have nulls here + .filter { holder.concreteAddr(it.mockInfo.addr) != SYMBOLIC_NULL_ADDR } + + val mocks = memoryMocks .groupBy { enriched -> holder.concreteAddr(enriched.mockInfo.addr) } .map { (address, mockInfos) -> address to mockInfos.mergeExecutables() } @@ -281,7 +308,7 @@ class Resolver( val staticMethodMocks = mutableMapOf>() // Enriches mock info with information from callsToMocks - memory.mocks().forEach { (mockInfo, executables) -> + memoryMocks.forEach { (mockInfo, executables) -> when (mockInfo) { // Collects static field mocks differently is UtFieldMockInfo -> if (mockInfo.ownerAddr == null) { @@ -297,7 +324,7 @@ class Resolver( } // Collects instrumentation - val newInstancesInstrumentation = memory.mocks() + val newInstancesInstrumentation = memoryMocks .map { it.mockInfo } .filterIsInstance() .groupBy { it.classId } @@ -325,7 +352,7 @@ class Resolver( val mockInfoEnriched = mockInfos.getValue(concreteAddr) val mockInfo = mockInfoEnriched.mockInfo - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(mockInfo.classId) } @@ -346,10 +373,9 @@ class Resolver( /** * Resolves current result (return value). */ - fun resolveResult(symResult: SymbolicResult?): UtExecutionResult = + fun resolveResult(symResult: SymbolicResult): UtExecutionResult = withMemoryState(CURRENT) { when (symResult) { - null -> UtExecutionSuccess(UtVoidModel) is SymbolicSuccess -> { collectMocksAndInstrumentation() val model = resolveModel(symResult.value) @@ -369,15 +395,20 @@ class Resolver( */ private fun SymbolicFailure.resolve(): UtExecutionFailure { val exception = concreteException() + + if (exception is UtStreamConsumingException) { + // This exception is artificial and is not really thrown + return UtStreamConsumingFailure(exception) + } + return if (explicit) { UtExplicitlyThrownException(exception, inNestedMethod) } else { - // TODO SAT-1561 - val isOverflow = exception is ArithmeticException && exception.message?.contains("overflow") == true - if (isOverflow) { - UtOverflowFailure(exception) - } else { - UtImplicitlyThrownException(exception, inNestedMethod) + when (exception) { + is OverflowDetectionError -> UtOverflowFailure(exception) + is AccessControlException -> UtSandboxFailure(exception) + is TaintAnalysisError -> UtTaintAnalysisFailure(exception) + else -> UtImplicitlyThrownException(exception, inNestedMethod) } } } @@ -427,7 +458,7 @@ class Resolver( // if the value is Object, we have to construct array or an object depending on the number of dimensions // it is possible if we had an object and we casted it into array val constructedType = holder.constructTypeOrNull(value.addr, value.type) ?: return UtNullModel(value.type.id) - val typeStorage = TypeStorage(constructedType) + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(constructedType) return if (constructedType is ArrayType) { constructArrayModel(ArrayValue(typeStorage, value.addr)) @@ -438,7 +469,7 @@ class Resolver( private fun resolveObject(objectValue: ObjectValue): UtModel { val concreteAddr = holder.concreteAddr(objectValue.addr) - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(objectValue.type.sootClass.id) } @@ -499,7 +530,7 @@ class Resolver( actualType: RefType, ): UtModel { val concreteAddr = holder.concreteAddr(addr) - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(defaultType.sootClass.id) } @@ -510,12 +541,54 @@ class Resolver( } val sootClass = actualType.sootClass - val clazz = classLoader.loadClass(sootClass.name) + if (sootClass.isLambda) { + return constructLambda(concreteAddr, sootClass).also { lambda -> + val collectedFieldModels = collectFieldModels(addr, actualType).toMutableMap() + + if (!lambda.lambdaMethodId.isStatic) { + val thisInstanceField = FieldId(declaringClass = sootClass.id, name = "cap0") + + if (thisInstanceField !in collectedFieldModels || collectedFieldModels[thisInstanceField] is UtNullModel) { + // Non-static lambda has to have `this` instance captured as `cap0` field that cannot be null, + // so if we do not have it as field or it is null (for example, an exception was thrown before initializing lambda), + // we need to construct `this` instance by ourselves. + // Since we do not know its fields, we create an empty object of the corresponding type that will be + // constructed in codegen using reflection. + val thisInstanceClassId = sootClass.name.substringBeforeLast("\$lambda").let { + Scene.v().getSootClass(it) + }.id + val thisInstanceModel = + UtCompositeModel(objectCounter.createNewAddr(), thisInstanceClassId, isMock = false) + + collectedFieldModels[thisInstanceField] = thisInstanceModel + } + } + + lambda.capturedValues += collectedFieldModels.values + } + } + + val clazz = classLoader.loadClass(sootClass.name) + // If we have an actual type as an Enum instance (a direct inheritor of java.lang.Enum), + // we should construct it using information about corresponding ordinal. + // The right enum constant will be constructed due to constraints added to the solver. if (clazz.isEnum) { return constructEnum(concreteAddr, actualType, clazz) } + // But, if we have the actualType that is not a direct inheritor of java.lang.Enum, + // we have to check its ancestor instead, because we might be in a situation when + // we worked with an enum constant as a parameter, and now we have correctly calculated actual type. + // Since actualType for enums represents its instances and enum constants are not actually + // enum instances (in accordance to java.lang.Class#isEnum), we have to check + // the defaultType below instead on the actual one whether is it an Enum or not. + val defaultClazz = classLoader.loadClass(defaultType.sootClass.name) + + if (defaultClazz.isEnum) { + return constructEnum(concreteAddr, actualType, defaultClazz) + } + // check if we have mock with this address // Unfortunately we cannot do it in constructModel only cause constructTypeOrNull returns null for mocks val mockInfo = mockInfos[concreteAddr] @@ -543,11 +616,8 @@ class Resolver( val baseModelName = primitiveClassId.name val constructorId = constructorId(classId, primitiveClassId) val valueModel = fields[FieldId(classId, "value")] ?: primitiveClassId.defaultValueModel() - val instantiationChain = mutableListOf() - UtAssembleModel(addr, classId, nextModelName(baseModelName), instantiationChain) - .apply { - instantiationChain += UtExecutableCallModel(null, constructorId, listOf(valueModel), this) - } + val instantiationCall = UtExecutableCallModel(instance = null, constructorId, listOf(valueModel)) + UtAssembleModel(addr, classId, nextModelName(baseModelName), instantiationCall) } } @@ -616,7 +686,7 @@ class Resolver( val modeledNumDimensions = holder.eval(numDimensionsArray.select(addrExpression)).intValue() val classRef = classRefByName(modeledType, modeledNumDimensions) - val model = UtClassRefModel(CLASS_REF_CLASS_ID, classRef) + val model = UtClassRefModel(addr, CLASS_REF_CLASS_ID, classRef.id) addConstructedModel(addr, model) return model @@ -631,6 +701,44 @@ class Resolver( return constructedType.classId.jClass } + private fun constructLambda(addr: Address, sootClass: SootClass): UtLambdaModel { + val samType = sootClass.interfaces.singleOrNull()?.id + ?: error("Lambda must implement single interface, but ${sootClass.interfaces.size} found for ${sootClass.name}") + + val declaringClass = classLoader.loadClass(sootClass.name.substringBefore("\$lambda")) + + // Java compiles lambdas into synthetic methods with specific names. + // However, Soot represents lambdas as classes. + // Names of these classes are the modified names of these synthetic methods. + // Specifically, Soot replaces some `$` signs by `_`, adds two underscores and some number + // to the end of the synthetic method name to form the name of a SootClass for lambda. + // For example, given a synthetic method `lambda$foo$1` (lambda declared in method `foo` of class `org.utbot.MyClass`), + // Soot will treat this lambda as a class named `org.utbot.MyClass$lambda_foo_1__5` (the last number is probably arbitrary, it's not important). + // Here we obtain the synthetic method name of lambda from the name of its SootClass. + val lambdaName = sootClass.name + .let { name -> + val start = name.indexOf("\$lambda") + 1 + val end = name.lastIndexOf("__") + name.substring(start, end) + } + .let { + val builder = StringBuilder(it) + builder[it.indexOfFirst { c -> c == '_' }] = '$' + builder[it.indexOfLast { c -> c == '_' }] = '$' + builder.toString() + } + + val lambdaModel = UtLambdaModel( + id = addr, + samType = samType, + declaringClass = declaringClass.id, + lambdaName = lambdaName + ) + addConstructedModel(addr, lambdaModel) + + return lambdaModel + } + private fun constructEnum(addr: Address, type: RefType, clazz: Class<*>): UtEnumConstantModel { val descriptor = MemoryChunkDescriptor(ENUM_ORDINAL, type, IntType.v()) val array = findArray(descriptor, state) @@ -641,7 +749,7 @@ class Resolver( clazz.enumConstants.indices.random() } val value = clazz.enumConstants[index] as Enum<*> - val model = UtEnumConstantModel(clazz.id, value) + val model = UtEnumConstantModel(addr, clazz.id, value) addConstructedModel(addr, model) return model @@ -675,15 +783,22 @@ class Resolver( * Constructs a type for the addr. * * There are three options here: + * * * it successfully constructs a type suitable with defaultType and returns it; + * * * it constructs a type that cannot be assigned in a variable with the [defaultType] and we `touched` - * the [addr] during the analysis. In such case the method returns [defaultType] as a result; + * the [addr] during the analysis. In such case we have only two options: either this object was aliased with + * an object of another type (by solver's decision) and we didn't touch it in fact, or it happened because + * of missed connection between an array type and types of its elements. For example, we might case + * array to a succ type after we `touched` it's element. In the first scenario null will be returns, in the + * second one -- a default type (that will be a subtype of actualType). + * * * it constructs a type that cannot be assigned in a variable with the [defaultType] and we did **not** `touched` * the [addr] during the analysis. It means we can create [UtNullModel] to represent such element. In such case * the method returns null as the result. * * @see Memory.touchedAddresses - * @see UtBotSymbolicEngine.touchAddress + * @see Traverser.touchAddress */ private fun UtSolverStatusSAT.constructTypeOrNull(addr: UtAddrExpression, defaultType: Type): Type? { return constructTypeOrNull(addr, defaultType, isTouched(addr)) @@ -772,11 +887,62 @@ class Resolver( // as const or store model. if (defaultBaseType is PrimType) return null + // In case when we have `actualType` equal to `byte` and defaultType is `java.lang.Object` + if (defaultType.isJavaLangObject() && actualType is PrimType) { + return defaultType + } + + // There is no way you have java.lang.Object as a defaultType here, since it'd mean that + // some actualType is not an inheritor of it + require(!defaultType.isJavaLangObject() || actualType is PrimType) { + "Object type $defaultType is unexpected in fallback to default type" + } + + // It might happen only if we have a wrong aliasing here: some object has not been touched + // during the execution, and solver returned for him an address of already existed object + // with another type. In such case `UtNullModel` should be constructed. + if (defaultBaseType.isJavaLangObject() && actualType.numDimensions < defaultType.numDimensions) { + return null + } + + val baseTypeIsAnonymous = (actualType.baseType as? RefType)?.sootClass?.isAnonymous == true + val actualTypeIsArrayOfAnonymous = actualType is ArrayType && baseTypeIsAnonymous + + // There must be no arrays of anonymous classes + if (defaultType.isJavaLangObject() && actualTypeIsArrayOfAnonymous) { + return defaultType + } + + // All cases with `java.lang.Object` as default base type should have been already processed + require(!defaultBaseType.isJavaLangObject()) { + "Unexpected `java.lang.Object` as a default base type" + } + val actualBaseType = actualType.baseType + // It is a tricky case: we might have a method with a parameter like `List`. + // Inside we will transform it into a wrapper with internal field `RangeModifiableArray` + // with Objects inside. Since we do not support generics for nested fields, it won't + // have type information and there will be no type constraints for its elements (because + // it contains java.lang.Objects inside). But, during the resolving, we will use type information + // from org.utbot.engine.types.TypeRegistry.getTypeStoragesForObjectTypeParameters. + // Therefore, we will encounter an object that will try to be resolved as an instance of `Integer`, + // but there will be no type constraints for it. Therefore, it might get `actualType` equal to some + // primitive type. In this case, we will return the default type (actually, it is not clear whether + // we should return null or defaultType, and maybe here some inconsistency exists). + if (actualType is PrimType && defaultType !is PrimType) { + return defaultType + } + require(actualBaseType is RefType) { "Expected RefType, but $actualBaseType found" } require(defaultBaseType is RefType) { "Expected RefType, but $defaultBaseType found" } + + // The same idea about fake aliasing. It might happen only if there have been an aliasing + // because of the solver's decision. In fact an object for which we construct type has not been + // touched during analysis. + if (actualType.numDimensions != defaultType.numDimensions) return null + val ancestors = typeResolver.findOrConstructAncestorsIncludingTypes(defaultBaseType) // This is intended to fix a specific problem. We have code: @@ -788,7 +954,27 @@ class Resolver( // when the array is ColoredPoint[], but the first element of it got type Point from the solver. // In such case here we'll have ColoredPoint as defaultType and Point as actualType. It is obvious from the example // that we can construct ColoredPoint instance instead of it with randomly filled colored-specific fields. - return defaultType.takeIf { actualBaseType in ancestors && actualType.numDimensions == defaultType.numDimensions } + // Note that it won't solve a problem when this `array[0]` has already been constructed somewhere above, + // since a model for it is already presented in cache and will be taken by addr from it. + // TODO corresponding issue https://github.com/UnitTestBot/UTBotJava/issues/1232 + if (actualBaseType in ancestors) return defaultType + + val inheritors = typeResolver.findOrConstructInheritorsIncludingTypes(defaultBaseType) + + // If we have an actual type that is not a subclass of defaultBaseType and isTouched is true, + // it means that we have encountered unexpected aliasing between an object for which we resolve its type + // and some object in the system. The reason is `isTouched` means that we processed this object + // during the analysis, therefore we create correct type constraints for it. Since we have + // inappropriate actualType here, these constraints were supposed to define type for another object, + // and, in fact, we didn't touch our object. + // For example, we have an array of two elements: Integer[] = new Integer[2], and this instance: ThisClass. + // During the execution we `touched` only the first element of the array, and we know its type. + // During resolving we found that second element of the array has set in true `isTouched` field, + // and its actualType is `ThisClass`. So, we have wrong aliasing here and can return `null` to construct + // UtNullModel instead. + if (actualBaseType !in inheritors) return null + + return null } /** @@ -796,7 +982,7 @@ class Resolver( */ private fun constructArrayModel(instance: ArrayValue): UtModel { val concreteAddr = holder.concreteAddr(instance.addr) - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(instance.type.id) } @@ -830,7 +1016,7 @@ class Resolver( concreteAddr: Address, details: ArrayExtractionDetails, ): UtModel { - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(actualType.id) } @@ -904,7 +1090,7 @@ class Resolver( elementType: ArrayType, details: ArrayExtractionDetails ): UtModel { - if (addr == NULL_ADDR) { + if (addr == SYMBOLIC_NULL_ADDR) { return UtNullModel(elementType.id) } @@ -928,7 +1114,7 @@ class Resolver( * Uses [constructTypeOrNull] to evaluate possible element type. */ private fun arrayOfObjectsElementModel(concreteAddr: Address, defaultType: RefType): UtModel { - if (concreteAddr == NULL_ADDR) { + if (concreteAddr == SYMBOLIC_NULL_ADDR) { return UtNullModel(defaultType.id) } @@ -941,7 +1127,9 @@ class Resolver( val constructedType = holder.constructTypeOrNull(addr, defaultType) ?: return UtNullModel(defaultType.id) if (defaultType.isJavaLangObject() && constructedType is ArrayType) { - return constructArrayModel(ArrayValue(TypeStorage(constructedType), addr)) + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(constructedType) + val arrayValue = ArrayValue(typeStorage, addr) + return constructArrayModel(arrayValue) } else { val concreteType = typeResolver.findAnyConcreteInheritorIncludingOrDefault( constructedType as RefType, @@ -977,8 +1165,7 @@ private data class ArrayExtractionDetails( val oneDimensionalArray: UtArrayExpressionBase ) -private const val NULL_ADDR = 0 -internal val nullObjectAddr = UtAddrExpression(mkInt(NULL_ADDR)) +internal val nullObjectAddr = UtAddrExpression(mkInt(SYMBOLIC_NULL_ADDR)) fun SymbolicValue.isNullObject() = @@ -1018,9 +1205,9 @@ val typesOfObjectsToRecreate = listOf( "java.lang.CharacterDataLatin1", "java.lang.CharacterData00", "[Ljava.lang.StackTraceElement", + "sun.java2d.cmm.lcms.LcmsServiceProvider", PrintStream::class.qualifiedName, AccessControlContext::class.qualifiedName, - LcmsServiceProvider::class.qualifiedName, ICC_ProfileRGB::class.qualifiedName, AtomicInteger::class.qualifiedName ) @@ -1032,13 +1219,14 @@ val typesOfObjectsToRecreate = listOf( * * we have to determine, which null values must be constructed; * * we must distinguish primitives and wrappers, but because of kotlin types we cannot do it without the [sootType]; */ -fun UtBotSymbolicEngine.toMethodResult(value: Any?, sootType: Type): MethodResult { +fun Traverser.toMethodResult(value: Any?, sootType: Type): MethodResult { if (sootType is PrimType) return MethodResult(value.primitiveToSymbolic()) return when (value) { null -> asMethodResult { if (sootType is RefType) { - createObject(nullObjectAddr, sootType, useConcreteType = true) + // We don't want to mock values we took from a concrete environment + createObject(nullObjectAddr, sootType, useConcreteType = true, mockInfoGenerator = null) } else { createArray(nullObjectAddr, sootType as ArrayType, useConcreteType = true) } @@ -1074,7 +1262,15 @@ fun UtBotSymbolicEngine.toMethodResult(value: Any?, sootType: Type): MethodResul val createdElement = if (elementType is RefType) { val className = value[it]!!.javaClass.id.name - createObject(addr, Scene.v().getRefType(className), useConcreteType = true) + // Try to take an instance of class we find in Runtime + // If it is impossible, take the default `elementType` without a concrete type instead. + val (type, useConcreteType) = + Scene.v().getRefTypeUnsafe(className) + ?.let { type -> type to true } + ?: (elementType to false) + + // We don't want to mock values we took from a concrete environment + createObject(addr, type, useConcreteType, mockInfoGenerator = null) } else { require(elementType is ArrayType) // We cannot use concrete types since we do not receive @@ -1088,31 +1284,40 @@ fun UtBotSymbolicEngine.toMethodResult(value: Any?, sootType: Type): MethodResul else -> { workaround(RUN_CONCRETE) { val className = value.javaClass.id.name - val superclassName = value.javaClass.superclass.name + val superClass = value.javaClass.superclass val refTypeName = when { // hardcoded string is used cause class is not public className in typesOfObjectsToRecreate -> className - superclassName == PrintStream::class.qualifiedName -> superclassName + // superClass is null for Object class + superClass != null && superClass.name == PrintStream::class.qualifiedName -> superClass.name // we want to generate an unbounded symbolic variable for every unknown class as well else -> workaround(MAKE_SYMBOLIC) { className } } return asMethodResult { val addr = UtAddrExpression(mkBVConst("staticVariable${value.hashCode()}", UtInt32Sort)) - createObject(addr, Scene.v().getRefType(refTypeName), useConcreteType = true) + // Try to take a type from an object from the Runtime. + // If it is impossible, create an instance of a default `sootType` without a concrete type. + val (type, useConcreteType) = Scene.v() + .getRefTypeUnsafe(refTypeName) + ?.let { type -> type to true } + ?: (sootType as RefType to false) + + // We don't want to mock values we took from a concrete environment + createObject(addr, type, useConcreteType, mockInfoGenerator = null) } } } } } -private fun UtBotSymbolicEngine.arrayToMethodResult( +private fun Traverser.arrayToMethodResult( size: Int, elementType: Type, takeElement: (Int) -> UtExpression ): MethodResult { - val updatedSize = min(size, HARD_MAX_ARRAY_SIZE) + val updatedSize = min(size, hardMaxArraySize) val newAddr = findNewAddr() val length = memory.findArrayLength(newAddr) @@ -1133,9 +1338,10 @@ private fun UtBotSymbolicEngine.arrayToMethodResult( } val memoryUpdate = MemoryUpdate( - stores = persistentListOf(simplifiedNamedStore(descriptor, newAddr, updatedArray)), - touchedChunkDescriptors = persistentSetOf(descriptor) + stores = persistentListOf(namedStore(descriptor, newAddr, updatedArray)), + touchedChunkDescriptors = persistentSetOf(descriptor), ) + return MethodResult( ArrayValue(typeStorage, newAddr), constraints.asHardConstraint(), @@ -1143,7 +1349,7 @@ private fun UtBotSymbolicEngine.arrayToMethodResult( ) } -fun UtBotSymbolicEngine.constructEnumStaticFieldResult( +fun Traverser.constructEnumStaticFieldResult( fieldName: String, fieldType: Type, declaringClass: SootClass, diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/SecurityManagerWrapper.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/SecurityManagerWrapper.kt new file mode 100644 index 0000000000..33f8511dfa --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/SecurityManagerWrapper.kt @@ -0,0 +1,44 @@ +package org.utbot.engine + +import org.utbot.engine.overrides.security.UtSecurityManager +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.classId +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.util.nextModelName +import soot.Scene +import soot.SootClass +import soot.SootMethod + +class SecurityManagerWrapper : BaseOverriddenWrapper(utSecurityManagerClass.name) { + private val baseModelName: String = "securityManager" + + override fun Traverser.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? { + // We currently do not overload any [SecurityManager] method symbolically + return null + } + + override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = resolver.run { + val classId = wrapper.type.classId + val addr = holder.concreteAddr(wrapper.addr) + val modelName = nextModelName(baseModelName) + + val instantiationCall = UtExecutableCallModel( + instance = null, + System::getSecurityManager.executableId, + emptyList() + ) + + return UtAssembleModel(addr, classId, modelName, instantiationCall) + } + + companion object { + val utSecurityManagerClass: SootClass + get() = Scene.v().getSootClass(UtSecurityManager::class.qualifiedName) + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt index e3cfe68a1b..0cef3d4473 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/StreamWrappers.kt @@ -1,5 +1,8 @@ package org.utbot.engine +import org.utbot.engine.overrides.stream.UtDoubleStream +import org.utbot.engine.overrides.stream.UtIntStream +import org.utbot.engine.overrides.stream.UtLongStream import org.utbot.engine.overrides.stream.UtStream import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.FieldId @@ -7,78 +10,86 @@ import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.classId -import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.plugin.api.util.doubleArrayClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.doubleStreamClassId +import org.utbot.framework.plugin.api.util.intArrayClassId +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.intStreamClassId +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.isPrimitiveWrapper +import org.utbot.framework.plugin.api.util.longArrayClassId +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.longStreamClassId import org.utbot.framework.plugin.api.util.methodId import org.utbot.framework.plugin.api.util.objectArrayClassId import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.streamClassId import org.utbot.framework.util.nextModelName -import soot.RefType -import soot.Scene /** - * Auxiliary enum class for specifying an implementation for [CommonStreamWrapper], that it will use. + * Auxiliary enum class for specifying an implementation for [StreamWrapper], that it will use. */ // TODO introduce a base interface for all such enums after https://github.com/UnitTestBot/UTBotJava/issues/146? enum class UtStreamClass { - UT_STREAM; - // TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146 -// UT_STREAM_INT, -// UT_STREAM_LONG, -// UT_STREAM_DOUBLE; + UT_STREAM, + UT_INT_STREAM, + UT_LONG_STREAM, + UT_DOUBLE_STREAM; val className: String get() = when (this) { UT_STREAM -> UtStream::class.java.canonicalName - // TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146 -// UT_STREAM_INT -> UtStreamInt::class.java.canonicalName -// UT_STREAM_LONG -> UtStreamLong::class.java.canonicalName -// UT_STREAM_DOUBLE -> UtStreamDouble::class.java.canonicalName + UT_INT_STREAM -> UtIntStream::class.java.canonicalName + UT_LONG_STREAM -> UtLongStream::class.java.canonicalName + UT_DOUBLE_STREAM -> UtDoubleStream::class.java.canonicalName } val elementClassId: ClassId get() = when (this) { UT_STREAM -> objectClassId - // TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146 -// UT_STREAM_INT -> intClassId -// UT_STREAM_LONG -> longClassId -// UT_STREAM_DOUBLE -> doubleClassId + UT_INT_STREAM -> intClassId + UT_LONG_STREAM -> longClassId + UT_DOUBLE_STREAM -> doubleClassId } val overriddenStreamClassId: ClassId get() = when (this) { - UT_STREAM -> java.util.stream.Stream::class.java.id - // TODO primitive streams https://github.com/UnitTestBot/UTBotJava/issues/146 + UT_STREAM -> streamClassId + UT_INT_STREAM -> intStreamClassId + UT_LONG_STREAM -> longStreamClassId + UT_DOUBLE_STREAM -> doubleStreamClassId } } abstract class StreamWrapper( - private val utStreamClass: UtStreamClass + utStreamClass: UtStreamClass, protected val elementsClassId: ClassId ) : BaseGenericStorageBasedContainerWrapper(utStreamClass.className) { + protected val streamClassId = utStreamClass.overriddenStreamClassId + override fun value(resolver: Resolver, wrapper: ObjectValue): UtAssembleModel = resolver.run { val addr = holder.concreteAddr(wrapper.addr) val modelName = nextModelName(baseModelName) - val parametersArrayModel = resolveElementsAsArrayModel(wrapper) - - val instantiationChain = mutableListOf() - val modificationsChain = emptyList() - - UtAssembleModel(addr, utStreamClass.overriddenStreamClassId, modelName, instantiationChain, modificationsChain) - .apply { - val (builder, params) = if (parametersArrayModel == null || parametersArrayModel.length == 0) { - streamEmptyMethodId to emptyList() - } else { - streamOfMethodId to listOf(parametersArrayModel) - } - - instantiationChain += UtExecutableCallModel( - instance = null, - executable = builder, - params = params, - returnValue = this - ) - } + val parametersArrayModel = resolveElementsAsArrayModel(wrapper)?.transformElementsModel() + + val (builder, params) = if (parametersArrayModel == null || parametersArrayModel.length == 0) { + streamEmptyMethodId to emptyList() + } else { + streamOfMethodId to listOf(parametersArrayModel) + } + + val instantiationCall = UtExecutableCallModel( + instance = null, + executable = builder, + params = params + ) + + UtAssembleModel(addr, streamClassId, modelName, instantiationCall) } override fun chooseClassIdWithConstructor(classId: ClassId): ClassId = error("No constructor for Stream") @@ -92,26 +103,84 @@ abstract class StreamWrapper( return collectFieldModels(wrapper.addr, overriddenClass.type)[elementDataFieldId] as? UtArrayModel } - private val streamOfMethodId: MethodId = methodId( - classId = utStreamClass.overriddenStreamClassId, - name = "of", - returnType = utStreamClass.overriddenStreamClassId, - arguments = arrayOf(objectArrayClassId) // vararg - ) + open fun UtArrayModel.transformElementsModel(): UtArrayModel = this + + private val streamOfMethodId: MethodId + get() = methodId( + classId = streamClassId, + name = "of", + returnType = streamClassId, + arguments = arrayOf(elementsClassId) // vararg + ) private val streamEmptyMethodId: MethodId = methodId( - classId = utStreamClass.overriddenStreamClassId, + classId = streamClassId, name = "empty", - returnType = utStreamClass.overriddenStreamClassId, + returnType = streamClassId, arguments = emptyArray() ) } -class CommonStreamWrapper : StreamWrapper(UtStreamClass.UT_STREAM) { +class CommonStreamWrapper : StreamWrapper(UtStreamClass.UT_STREAM, objectArrayClassId) { override val baseModelName: String = "stream" +} + +abstract class PrimitiveStreamWrapper( + utStreamClass: UtStreamClass, + elementsClassId: ClassId +) : StreamWrapper(utStreamClass, elementsClassId) { + init { + require(elementsClassId.isArray) { + "Elements $$elementsClassId of primitive Stream wrapper for $streamClassId are not arrays" + } + } + + /** + * Transforms a model for an array of wrappers (Integer, Long, etc) to an array of corresponding primitives. + */ + override fun UtArrayModel.transformElementsModel(): UtArrayModel { + val primitiveConstModel = if (constModel is UtNullModel) { + // UtNullModel is not allowed for primitive arrays + elementsClassId.elementClassId!!.defaultValueModel() + } else { + constModel.wrapperModelToPrimitiveModel() + } - companion object { - internal val utStreamType: RefType - get() = Scene.v().getSootClass(UtStream::class.java.canonicalName).type + return copy( + classId = elementsClassId, + constModel = primitiveConstModel, + stores = stores.mapValuesTo(mutableMapOf()) { it.value.wrapperModelToPrimitiveModel() } + ) } + + /** + * Transforms [this] to [UtPrimitiveModel] if it is an [UtAssembleModel] for the corresponding wrapper + * (primitive int and wrapper Integer, etc.), and throws an error otherwise. + */ + private fun UtModel.wrapperModelToPrimitiveModel(): UtModel { + require(this !is UtNullModel) { + "Unexpected null value in wrapper for primitive stream ${this@PrimitiveStreamWrapper.streamClassId}" + } + + require(classId.isPrimitiveWrapper && this is UtAssembleModel) { + "Unexpected not wrapper assemble model $this for value in wrapper " + + "for primitive stream ${this@PrimitiveStreamWrapper.streamClassId}" + } + + return (instantiationCall.params.firstOrNull() as? UtPrimitiveModel) + ?: error("No primitive value parameter for wrapper constructor $instantiationCall in model $this " + + "in wrapper for primitive stream ${this@PrimitiveStreamWrapper.streamClassId}") + } +} + +class IntStreamWrapper : PrimitiveStreamWrapper(UtStreamClass.UT_INT_STREAM, intArrayClassId) { + override val baseModelName: String = "intStream" +} + +class LongStreamWrapper : PrimitiveStreamWrapper(UtStreamClass.UT_LONG_STREAM, longArrayClassId) { + override val baseModelName: String = "longStream" +} + +class DoubleStreamWrapper : PrimitiveStreamWrapper(UtStreamClass.UT_DOUBLE_STREAM, doubleArrayClassId) { + override val baseModelName: String = "doubleStream" } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt index 72695c927f..49abe98a65 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Strings.kt @@ -1,41 +1,28 @@ package org.utbot.engine import com.github.curiousoddman.rgxgen.RgxGen -import org.utbot.common.unreachableBranch -import org.utbot.engine.overrides.strings.UtNativeString import org.utbot.engine.overrides.strings.UtString import org.utbot.engine.overrides.strings.UtStringBuffer import org.utbot.engine.overrides.strings.UtStringBuilder -import org.utbot.engine.pc.RewritingVisitor import org.utbot.engine.pc.UtAddrExpression import org.utbot.engine.pc.UtBoolExpression -import org.utbot.engine.pc.UtConvertToString import org.utbot.engine.pc.UtFalse -import org.utbot.engine.pc.UtIntSort -import org.utbot.engine.pc.UtLongSort -import org.utbot.engine.pc.UtStringCharAt -import org.utbot.engine.pc.UtStringLength -import org.utbot.engine.pc.UtStringToArray -import org.utbot.engine.pc.UtStringToInt import org.utbot.engine.pc.UtTrue -import org.utbot.engine.pc.cast import org.utbot.engine.pc.isConcrete import org.utbot.engine.pc.mkAnd import org.utbot.engine.pc.mkChar import org.utbot.engine.pc.mkEq import org.utbot.engine.pc.mkInt import org.utbot.engine.pc.mkNot -import org.utbot.engine.pc.mkString import org.utbot.engine.pc.select import org.utbot.engine.pc.toConcrete import org.utbot.engine.symbolic.asHardConstraint +import org.utbot.engine.types.STRING_TYPE import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.classId import org.utbot.framework.plugin.api.id import org.utbot.framework.plugin.api.util.charArrayClassId @@ -44,8 +31,6 @@ import org.utbot.framework.plugin.api.util.constructorId import org.utbot.framework.plugin.api.util.defaultValueModel import org.utbot.framework.util.nextModelName import kotlin.math.max -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.persistentSetOf import soot.CharType import soot.IntType import soot.Scene @@ -64,84 +49,28 @@ class StringWrapper : BaseOverriddenWrapper(utStringClass.name) { private val charAtMethodSignature = overriddenClass.getMethodByName(UtString::charAtImpl.name).subSignature - private fun UtBotSymbolicEngine.getValueArray(addr: UtAddrExpression) = + private fun Traverser.getValueArray(addr: UtAddrExpression) = getArrayField(addr, overriddenClass, STRING_VALUE) - override fun UtBotSymbolicEngine.overrideInvoke( + override fun Traverser.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List ): List? { return when (method.subSignature) { toStringMethodSignature -> { - listOf(MethodResult(wrapper.copy(typeStorage = TypeStorage(method.returnType)))) + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(method.returnType) + listOf(MethodResult(wrapper.copy(typeStorage = typeStorage))) } matchesMethodSignature -> { - val arg = parameters[0] as ObjectValue - val matchingLengthExpr = getIntFieldValue(arg, STRING_LENGTH).accept(RewritingVisitor()) - - if (!matchingLengthExpr.isConcrete) return null - - val matchingValueExpr = - selectArrayExpressionFromMemory(getValueArray(arg.addr)).accept(RewritingVisitor()) - val matchingLength = matchingLengthExpr.toConcrete() as Int - val matchingValue = CharArray(matchingLength) - - for (i in 0 until matchingLength) { - val charExpr = matchingValueExpr.select(mkInt(i)).accept(RewritingVisitor()) - - if (!charExpr.isConcrete) return null - - matchingValue[i] = (charExpr.toConcrete() as Number).toChar() - } - - val rgxGen = RgxGen(String(matchingValue)) - val matching = (rgxGen.generate()) - val notMatching = rgxGen.generateNotMatching() - - val thisLength = getIntFieldValue(wrapper, STRING_LENGTH) - val thisValue = selectArrayExpressionFromMemory(getValueArray(wrapper.addr)) - - val matchingConstraints = mutableSetOf() - matchingConstraints += mkEq(thisLength, mkInt(matching.length)) - for (i in matching.indices) { - matchingConstraints += mkEq(thisValue.select(mkInt(i)), mkChar(matching[i])) - } - - val notMatchingConstraints = mutableSetOf() - notMatchingConstraints += mkEq(thisLength, mkInt(notMatching.length)) - for (i in notMatching.indices) { - notMatchingConstraints += mkEq(thisValue.select(mkInt(i)), mkChar(notMatching[i])) - } - - return listOf( - MethodResult(UtTrue.toBoolValue(), matchingConstraints.asHardConstraint()), - MethodResult(UtFalse.toBoolValue(), notMatchingConstraints.asHardConstraint()) - ) + symbolicMatchesMethodImpl(wrapper, parameters) } charAtMethodSignature -> { - val index = parameters[0] as PrimitiveValue - val lengthExpr = getIntFieldValue(wrapper, STRING_LENGTH) - val inBoundsCondition = mkAnd(Le(0.toPrimitiveValue(), index), Lt(index, lengthExpr.toIntValue())) - val failMethodResult = - MethodResult( - explicitThrown( - StringIndexOutOfBoundsException(), - findNewAddr(), - isInNestedMethod() - ), - hardConstraints = mkNot(inBoundsCondition).asHardConstraint() - ) - - val valueExpr = selectArrayExpressionFromMemory(getValueArray(wrapper.addr)) - - val returnResult = MethodResult( - valueExpr.select(index.expr).toCharValue(), - hardConstraints = inBoundsCondition.asHardConstraint() - ) - return listOf(returnResult, failMethodResult) + symbolicCharAtMethodImpl(wrapper, parameters) + } + else -> { + null } - else -> return null } } @@ -179,126 +108,103 @@ class StringWrapper : BaseOverriddenWrapper(utStringClass.name) { val charValues = CharArray(length) { (values.stores[it] as UtPrimitiveModel).value as Char } val stringModel = UtPrimitiveModel(String(charValues)) - val instantiationChain = mutableListOf() - val modificationsChain = mutableListOf() - return UtAssembleModel(addr, classId, modelName, instantiationChain, modificationsChain) - .apply { - instantiationChain += UtExecutableCallModel( - instance = null, - constructorId(classId, STRING_TYPE.classId), - listOf(stringModel), - this - ) - } + val instantiationCall = UtExecutableCallModel( + instance = null, + constructorId(classId, STRING_TYPE.classId), + listOf(stringModel) + ) + return UtAssembleModel(addr, classId, modelName, instantiationCall) } -} - -internal val utNativeStringClass = Scene.v().getSootClass(UtNativeString::class.qualifiedName) - -private var stringNameIndex = 0 -private fun nextStringName() = "\$string${stringNameIndex++}" -class UtNativeStringWrapper : WrapperInterface { - private val valueDescriptor = NATIVE_STRING_VALUE_DESCRIPTOR - override fun UtBotSymbolicEngine.invoke( + private fun Traverser.symbolicMatchesMethodImpl( wrapper: ObjectValue, - method: SootMethod, parameters: List - ): List = - when (method.subSignature) { - "void ()" -> { - val newString = mkString(nextStringName()) - - val memoryUpdate = MemoryUpdate( - stores = persistentListOf(simplifiedNamedStore(valueDescriptor, wrapper.addr, newString)), - touchedChunkDescriptors = persistentSetOf(valueDescriptor) - ) - listOf( - MethodResult( - SymbolicSuccess(voidValue), - memoryUpdates = memoryUpdate - ) - ) - } - "void (int)" -> { - val newString = UtConvertToString((parameters[0] as PrimitiveValue).expr) - val memoryUpdate = MemoryUpdate( - stores = persistentListOf(simplifiedNamedStore(valueDescriptor, wrapper.addr, newString)), - touchedChunkDescriptors = persistentSetOf(valueDescriptor) - ) - listOf( - MethodResult( - SymbolicSuccess(voidValue), - memoryUpdates = memoryUpdate - ) - ) - } - "void (long)" -> { - val newString = UtConvertToString((parameters[0] as PrimitiveValue).expr) - val memoryUpdate = MemoryUpdate( - stores = persistentListOf(simplifiedNamedStore(valueDescriptor, wrapper.addr, newString)), - touchedChunkDescriptors = persistentSetOf(valueDescriptor) - ) - listOf( - MethodResult( - SymbolicSuccess(voidValue), - memoryUpdates = memoryUpdate - ) - ) - } - "int length()" -> { - val result = UtStringLength(memory.nativeStringValue(wrapper.addr)) - listOf(MethodResult(SymbolicSuccess(result.toByteValue().cast(IntType.v())))) - } - "char charAt(int)" -> { - val index = (parameters[0] as PrimitiveValue).expr - val result = UtStringCharAt(memory.nativeStringValue(wrapper.addr), index) - listOf(MethodResult(SymbolicSuccess(result.toCharValue()))) - } - "int codePointAt(int)" -> { - val index = (parameters[0] as PrimitiveValue).expr - val result = UtStringCharAt(memory.nativeStringValue(wrapper.addr), index) - listOf(MethodResult(SymbolicSuccess(result.toCharValue().cast(IntType.v())))) - } - "int toInteger()" -> { - val result = UtStringToInt(memory.nativeStringValue(wrapper.addr), UtIntSort) - listOf(MethodResult(SymbolicSuccess(result.toIntValue()))) - } - "long toLong()" -> { - val result = UtStringToInt(memory.nativeStringValue(wrapper.addr), UtLongSort) - listOf(MethodResult(SymbolicSuccess(result.toLongValue()))) - } - "char[] toCharArray(int)" -> { - val stringExpression = memory.nativeStringValue(wrapper.addr) - val result = UtStringToArray(stringExpression, parameters[0] as PrimitiveValue) - val length = UtStringLength(stringExpression) - val type = CharType.v() - val arrayType = type.arrayType - val arrayValue = createNewArray(length.toIntValue(), arrayType, type) - listOf( - MethodResult( - SymbolicSuccess(arrayValue), - memoryUpdates = arrayUpdateWithValue(arrayValue.addr, arrayType, result) - ) - ) - } - else -> unreachableBranch("Unknown signature at the NativeStringWrapper.invoke: ${method.signature}") + ): List? { + val arg = parameters[0] as ObjectValue + val matchingLengthExpr = getIntFieldValue(arg, STRING_LENGTH).accept(this.simplificator) + + if (!matchingLengthExpr.isConcrete) return null + + val matchingValueExpr = + selectArrayExpressionFromMemory(getValueArray(arg.addr)).accept(this.simplificator) + val matchingLength = matchingLengthExpr.toConcrete() as Int + val matchingValue = CharArray(matchingLength) + + for (i in 0 until matchingLength) { + val charExpr = matchingValueExpr.select(mkInt(i)).accept(this.simplificator) + + if (!charExpr.isConcrete) return null + + matchingValue[i] = (charExpr.toConcrete() as Number).toChar() + } + + val rgxGen = RgxGen(String(matchingValue)) + val matching = rgxGen.generate() + val notMatching = rgxGen.generateNotMatching() + + val thisLength = getIntFieldValue(wrapper, STRING_LENGTH) + val thisValue = selectArrayExpressionFromMemory(getValueArray(wrapper.addr)) + + val matchingConstraints = mutableSetOf() + matchingConstraints += mkEq(thisLength, mkInt(matching.length)) + for (i in matching.indices) { + matchingConstraints += mkEq(thisValue.select(mkInt(i)), mkChar(matching[i])) } - override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = UtNullModel(STRING_TYPE.classId) + val notMatchingConstraints = mutableSetOf() + notMatchingConstraints += mkEq(thisLength, mkInt(notMatching.length)) + for (i in notMatching.indices) { + notMatchingConstraints += mkEq(thisValue.select(mkInt(i)), mkChar(notMatching[i])) + } + + return listOf( + MethodResult(UtTrue.toBoolValue(), matchingConstraints.asHardConstraint()), + MethodResult(UtFalse.toBoolValue(), notMatchingConstraints.asHardConstraint()) + ) + } + + private fun Traverser.symbolicCharAtMethodImpl( + wrapper: ObjectValue, + parameters: List + ): List { + val index = parameters[0] as PrimitiveValue + val lengthExpr = getIntFieldValue(wrapper, STRING_LENGTH) + val inBoundsCondition = mkAnd(Le(0.toPrimitiveValue(), index), Lt(index, lengthExpr.toIntValue())) + val failMethodResult = + MethodResult( + explicitThrown( + StringIndexOutOfBoundsException(), + findNewAddr(), + environment.state.isInNestedMethod() + ), + hardConstraints = mkNot(inBoundsCondition).asHardConstraint() + ) + + val valueExpr = selectArrayExpressionFromMemory(getValueArray(wrapper.addr)) + + val returnResult = MethodResult( + valueExpr.select(index.expr).toCharValue(), + hardConstraints = inBoundsCondition.asHardConstraint() + ) + return listOf(returnResult, failMethodResult) + } } sealed class UtAbstractStringBuilderWrapper(className: String) : BaseOverriddenWrapper(className) { private val asStringBuilderMethodSignature = overriddenClass.getMethodByName("asStringBuilder").subSignature - override fun UtBotSymbolicEngine.overrideInvoke( + override fun Traverser.overrideInvoke( wrapper: ObjectValue, method: SootMethod, parameters: List ): List? { if (method.subSignature == asStringBuilderMethodSignature) { - return listOf(MethodResult(wrapper.copy(typeStorage = TypeStorage(method.returnType)))) + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(method.returnType) + val resultingWrapper = wrapper.copy(typeStorage = typeStorage) + val methodResult = MethodResult(resultingWrapper) + + return listOf(methodResult) } return null @@ -313,8 +219,8 @@ sealed class UtAbstractStringBuilderWrapper(className: String) : BaseOverriddenW val arrayValuesChunkId = typeRegistry.arrayChunkId(charArrayType) - val valuesFieldChunkId = hierarchy.chunkIdForField(utStringClass.type, overriddenClass.valueField) - val valuesArrayAddrDescriptor = MemoryChunkDescriptor(valuesFieldChunkId, wrapper.type, charType) + val valuesFieldChunkId = hierarchy.chunkIdForField(overriddenClass.type, overriddenClass.valueField) + val valuesArrayAddrDescriptor = MemoryChunkDescriptor(valuesFieldChunkId, wrapper.type, charArrayType) val valuesArrayAddr = findArray(valuesArrayAddrDescriptor, MemoryState.CURRENT).select(wrapper.addr) val valuesArrayDescriptor = MemoryChunkDescriptor(arrayValuesChunkId, charArrayType, charType) @@ -333,19 +239,13 @@ sealed class UtAbstractStringBuilderWrapper(className: String) : BaseOverriddenW val charValues = CharArray(length) { (values.stores[it] as UtPrimitiveModel).value as Char } val stringModel = UtPrimitiveModel(String(charValues)) - - val instantiationChain = mutableListOf() - val modificationsChain = mutableListOf() val constructorId = constructorId(wrapper.type.classId, STRING_TYPE.classId) - return UtAssembleModel(addr, wrapper.type.classId, modelName, instantiationChain, modificationsChain) - .apply { - instantiationChain += UtExecutableCallModel( - instance = null, - constructorId, - listOf(stringModel), - this - ) - } + val instantiationCall = UtExecutableCallModel( + instance = null, + constructorId, + listOf(stringModel) + ) + return UtAssembleModel(addr, wrapper.type.classId, modelName, instantiationCall) } private val SootClass.valueField: SootField diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/SymbolicValue.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/SymbolicValue.kt index a8713bae73..a7c8e960c0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/SymbolicValue.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/SymbolicValue.kt @@ -51,7 +51,7 @@ data class PrimitiveValue( val expr: UtExpression, override val concrete: Concrete? = null ) : SymbolicValue() { - constructor(type: Type, expr: UtExpression) : this(TypeStorage(type), expr) + constructor(type: Type, expr: UtExpression) : this(TypeStorage.constructTypeStorageWithSingleType(type), expr) override val type get() = typeStorage.leastCommonType @@ -88,7 +88,7 @@ sealed class ReferenceValue(open val addr: UtAddrExpression) : SymbolicValue() * otherwise it is possible for an object to have inappropriate or incorrect typeId and dimensionNum. * * @see TypeRegistry.typeConstraint - * @see UtBotSymbolicEngine.createObject + * @see Traverser.createObject */ data class ObjectValue( override val typeStorage: TypeStorage, @@ -127,7 +127,7 @@ data class ObjectValue( * otherwise it is possible for an object to have inappropriate or incorrect typeId and dimensionNum. * * @see TypeRegistry.typeConstraint - * @see UtBotSymbolicEngine.createObject + * @see Traverser.createObject */ data class ArrayValue( override val typeStorage: TypeStorage, @@ -166,6 +166,12 @@ val SymbolicValue.addr is PrimitiveValue -> error("PrimitiveValue $this doesn't have an address") } +val SymbolicValue.addrOrNull + get() = when (this) { + is ReferenceValue -> addr + is PrimitiveValue -> null + } + val SymbolicValue.isConcrete: Boolean get() = when (this) { is PrimitiveValue -> this.expr.isConcrete @@ -179,7 +185,7 @@ fun SymbolicValue.toConcrete(): Any = when (this) { // TODO: one more constructor? fun objectValue(type: RefType, addr: UtAddrExpression, implementation: WrapperInterface) = - ObjectValue(TypeStorage(type), addr, Concrete(implementation)) + ObjectValue(TypeStorage.constructTypeStorageWithSingleType(type), addr, Concrete(implementation)) val voidValue get() = PrimitiveValue(VoidType.v(), nullObjectAddr) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt new file mode 100644 index 0000000000..bb5bd68a23 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/ThreadWrappers.kt @@ -0,0 +1,236 @@ +package org.utbot.engine + +import org.utbot.engine.overrides.threads.UtCompletableFuture +import org.utbot.engine.overrides.threads.UtCountDownLatch +import org.utbot.engine.overrides.threads.UtExecutorService +import org.utbot.engine.overrides.threads.UtThread +import org.utbot.engine.overrides.threads.UtThreadGroup +import org.utbot.engine.symbolic.asHardConstraint +import org.utbot.engine.types.COMPLETABLE_FUTURE_TYPE +import org.utbot.engine.types.COUNT_DOWN_LATCH_TYPE +import org.utbot.engine.types.EXECUTORS_TYPE +import org.utbot.engine.types.OBJECT_TYPE +import org.utbot.engine.types.STRING_TYPE +import org.utbot.engine.types.THREAD_GROUP_TYPE +import org.utbot.engine.types.THREAD_TYPE +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.constructorId +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.plugin.api.util.fieldId +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.util.executableId +import org.utbot.framework.util.nextModelName +import soot.IntType +import soot.RefType +import soot.Scene +import soot.SootClass +import soot.SootMethod + +val utThreadClass: SootClass + get() = Scene.v().getSootClass(UtThread::class.qualifiedName) +val utThreadGroupClass: SootClass + get() = Scene.v().getSootClass(UtThreadGroup::class.qualifiedName) +val utCompletableFutureClass: SootClass + get() = Scene.v().getSootClass(UtCompletableFuture::class.qualifiedName) +val utExecutorServiceClass: SootClass + get() = Scene.v().getSootClass(UtExecutorService::class.qualifiedName) +val utCountDownLatchClass: SootClass + get() = Scene.v().getSootClass(UtCountDownLatch::class.qualifiedName) + +class ThreadWrapper : BaseOverriddenWrapper(utThreadClass.name) { + override fun Traverser.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = null + + override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = + resolver.run { + val classId = THREAD_TYPE.id + val addr = holder.concreteAddr(wrapper.addr) + val modelName = nextModelName("thread") + + val values = collectFieldModels(wrapper.addr, overriddenClass.type) + val targetModel = values[targetFieldId] as? UtLambdaModel + + val (constructor, params) = if (targetModel == null) { + constructorId(classId) to emptyList() + } else { + constructorId(classId, runnableType.id) to listOf(targetModel) + } + + val instantiationCall = UtExecutableCallModel( + instance = null, + constructor, + params + ) + + return UtAssembleModel(addr, classId, modelName, instantiationCall) + } + + companion object { + private val runnableType: RefType + get() = Scene.v().getSootClass(Runnable::class.qualifiedName).type!! + private val targetFieldId: FieldId + get() = utThreadClass.getField("target", runnableType).fieldId + } +} + +class ThreadGroupWrapper : BaseOverriddenWrapper(utThreadGroupClass.name) { + override fun Traverser.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = null + + override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = + resolver.run { + val classId = THREAD_GROUP_TYPE.id + val addr = holder.concreteAddr(wrapper.addr) + val modelName = nextModelName("threadGroup") + + val values = collectFieldModels(wrapper.addr, overriddenClass.type) + val nameModel = values[nameFieldId] ?: UtNullModel(stringClassId) + + val instantiationCall = UtExecutableCallModel( + instance = null, + constructorId(classId, stringClassId), + listOf(nameModel) + ) + + return UtAssembleModel(addr, classId, modelName, instantiationCall) + } + + companion object { + private val nameFieldId: FieldId + get() = utThreadGroupClass.getField("name", STRING_TYPE).fieldId + } +} + +private val TO_COMPLETABLE_FUTURE_SIGNATURE: String = + utCompletableFutureClass.getMethodByName(UtCompletableFuture<*>::toCompletableFuture.name).signature +private val UT_COMPLETABLE_FUTURE_EQ_GENERIC_TYPE_SIGNATURE: String = + utCompletableFutureClass.getMethodByName(UtCompletableFuture<*>::eqGenericType.name).signature + +class CompletableFutureWrapper : BaseOverriddenWrapper(utCompletableFutureClass.name) { + override fun Traverser.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = + when (method.signature) { + TO_COMPLETABLE_FUTURE_SIGNATURE -> { + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(method.returnType) + val resultingWrapper = wrapper.copy(typeStorage = typeStorage) + val methodResult = MethodResult(resultingWrapper) + + listOf(methodResult) + } + UT_COMPLETABLE_FUTURE_EQ_GENERIC_TYPE_SIGNATURE -> { + val firstParameter = parameters.single() + val genericTypeParameterTypeConstraint = typeRegistry.typeConstraintToGenericTypeParameter( + firstParameter.addr, + wrapper.addr, + i = 0 + ) + val methodResult = MethodResult( + firstParameter, + genericTypeParameterTypeConstraint.asHardConstraint() + ) + + listOf(methodResult) + } + else -> null + } + + override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = + resolver.run { + val classId = COMPLETABLE_FUTURE_TYPE.id + val addr = holder.concreteAddr(wrapper.addr) + val modelName = nextModelName("completableFuture") + + val values = collectFieldModels(wrapper.addr, overriddenClass.type) + val resultModel = values[resultFieldId] ?: UtNullModel(objectClassId) + + val instantiationCall = UtExecutableCallModel( + instance = null, + constructorId(classId, objectClassId), + listOf(resultModel) + ) + + return UtAssembleModel(addr, classId, modelName, instantiationCall) + } + + companion object { + private val resultFieldId: FieldId + get() = utCompletableFutureClass.getField("result", OBJECT_TYPE).fieldId + } +} + +class ExecutorServiceWrapper : BaseOverriddenWrapper(utExecutorServiceClass.name) { + override fun Traverser.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = null + + override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = + resolver.run { + val classId = EXECUTORS_TYPE.id + val addr = holder.concreteAddr(wrapper.addr) + val modelName = nextModelName("executorService") + + val instantiationCall = UtExecutableCallModel( + instance = null, + newSingleThreadExecutorMethod, + emptyList() + ) + + return UtAssembleModel(addr, classId, modelName, instantiationCall) + } + + companion object { + val newSingleThreadExecutorMethod: ExecutableId + get() = EXECUTORS_TYPE.sootClass.getMethod("newSingleThreadExecutor", emptyList()).executableId + } +} + +class CountDownLatchWrapper : BaseOverriddenWrapper(utCountDownLatchClass.name) { + override fun Traverser.overrideInvoke( + wrapper: ObjectValue, + method: SootMethod, + parameters: List + ): List? = null + + override fun value(resolver: Resolver, wrapper: ObjectValue): UtModel = + resolver.run { + val classId = COUNT_DOWN_LATCH_TYPE.id + val addr = holder.concreteAddr(wrapper.addr) + val modelName = nextModelName("countDownLatch") + + val values = collectFieldModels(wrapper.addr, overriddenClass.type) + val countModel = values[countFieldId] ?: intClassId.defaultValueModel() + + val instantiationCall = UtExecutableCallModel( + instance = null, + constructorId(classId, intClassId), + listOf(countModel) + ) + + return UtAssembleModel(addr, classId, modelName, instantiationCall) + } + + companion object { + private val countFieldId: FieldId + get() = utCountDownLatchClass.getField("count", IntType.v()).fieldId + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/TraversalContext.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/TraversalContext.kt new file mode 100644 index 0000000000..99fa3836e5 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/TraversalContext.kt @@ -0,0 +1,32 @@ +package org.utbot.engine + +import org.utbot.engine.state.ExecutionState + +/** + * Represents a mutable _Context_ during the [ExecutionState] traversing. This _Context_ consists of all mutable and + * immutable properties and fields which are created and updated during analysis of a **single** Jimple instruction. + * + * Traverser functions should be implemented as an extension functions with [TraversalContext] as a receiver. + * + * TODO: extend this class with other properties, such as [Environment], which is [Traverser] mutable property now. + */ +class TraversalContext { + // TODO: move properties from [UtBotSymbolicEngine] here + + + // TODO: Q: maybe it's better to pass stateConsumer as an argument to constructor? + private val states = mutableListOf() + + /** + * Offers new [ExecutionState] which can be obtained later with [nextStates]. + */ + fun offerState(state: ExecutionState) { + states.add(state) + } + + /** + * New states obtained from the traversal. + */ + val nextStates: Collection + get() = states +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt new file mode 100644 index 0000000000..33d9ca61ce --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -0,0 +1,4324 @@ +package org.utbot.engine + +import kotlinx.collections.immutable.persistentHashMapOf +import kotlinx.collections.immutable.persistentHashSetOf +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentSetOf +import kotlinx.collections.immutable.toPersistentList +import kotlinx.collections.immutable.toPersistentMap +import kotlinx.collections.immutable.toPersistentSet +import mu.KotlinLogging +import org.utbot.framework.plugin.api.ArtificialError +import org.utbot.framework.plugin.api.OverflowDetectionError +import org.utbot.framework.plugin.api.TaintAnalysisError +import org.utbot.common.WorkaroundReason.HACK +import org.utbot.framework.UtSettings.ignoreStaticsFromTrustedLibraries +import org.utbot.common.WorkaroundReason.IGNORE_STATICS_FROM_TRUSTED_LIBRARIES +import org.utbot.common.unreachableBranch +import org.utbot.common.withAccessibility +import org.utbot.common.workaround +import org.utbot.engine.overrides.UtArrayMock +import org.utbot.engine.overrides.UtLogicMock +import org.utbot.engine.overrides.UtOverrideMock +import org.utbot.engine.pc.NotBoolExpression +import org.utbot.engine.pc.Simplificator +import org.utbot.engine.pc.UtAddNoOverflowExpression +import org.utbot.engine.pc.UtAddrExpression +import org.utbot.engine.pc.UtAndBoolExpression +import org.utbot.engine.pc.UtArrayApplyForAll +import org.utbot.engine.pc.UtArrayExpressionBase +import org.utbot.engine.pc.UtArraySelectExpression +import org.utbot.engine.pc.UtArraySetRange +import org.utbot.engine.pc.UtArraySort +import org.utbot.engine.pc.UtBoolExpression +import org.utbot.engine.pc.UtBoolOpExpression +import org.utbot.engine.pc.UtBvConst +import org.utbot.engine.pc.UtBvLiteral +import org.utbot.engine.pc.UtByteSort +import org.utbot.engine.pc.UtCastExpression +import org.utbot.engine.pc.UtCharSort +import org.utbot.engine.pc.UtContextInitializer +import org.utbot.engine.pc.UtExpression +import org.utbot.engine.pc.UtFalse +import org.utbot.engine.pc.UtInstanceOfExpression +import org.utbot.engine.pc.UtIntSort +import org.utbot.engine.pc.UtIsExpression +import org.utbot.engine.pc.UtIteExpression +import org.utbot.engine.pc.UtLongSort +import org.utbot.engine.pc.UtMkTermArrayExpression +import org.utbot.engine.pc.UtNegExpression +import org.utbot.engine.pc.UtOrBoolExpression +import org.utbot.engine.pc.UtPrimitiveSort +import org.utbot.engine.pc.UtShortSort +import org.utbot.engine.pc.UtSolver +import org.utbot.engine.pc.UtSolverStatusSAT +import org.utbot.engine.pc.UtSolverStatusUNSAT +import org.utbot.engine.pc.UtSubNoOverflowExpression +import org.utbot.engine.pc.UtTrue +import org.utbot.engine.pc.addrEq +import org.utbot.engine.pc.align +import org.utbot.engine.pc.cast +import org.utbot.engine.pc.findTheMostNestedAddr +import org.utbot.engine.pc.isInteger +import org.utbot.engine.pc.mkAnd +import org.utbot.engine.pc.mkBVConst +import org.utbot.engine.pc.mkBoolConst +import org.utbot.engine.pc.mkChar +import org.utbot.engine.pc.mkEq +import org.utbot.engine.pc.mkFalse +import org.utbot.engine.pc.mkFpConst +import org.utbot.engine.pc.mkInt +import org.utbot.engine.pc.mkNot +import org.utbot.engine.pc.mkOr +import org.utbot.engine.pc.select +import org.utbot.engine.pc.store +import org.utbot.engine.state.Edge +import org.utbot.engine.state.ExecutionState +import org.utbot.engine.state.LocalVariableMemory +import org.utbot.engine.state.StateLabel +import org.utbot.engine.state.createExceptionState +import org.utbot.engine.state.pop +import org.utbot.engine.state.push +import org.utbot.engine.state.update +import org.utbot.engine.state.withLabel +import org.utbot.engine.symbolic.HardConstraint +import org.utbot.engine.symbolic.emptyAssumption +import org.utbot.engine.symbolic.emptyHardConstraint +import org.utbot.engine.symbolic.emptySoftConstraint +import org.utbot.engine.symbolic.SymbolicStateUpdate +import org.utbot.engine.symbolic.asHardConstraint +import org.utbot.engine.symbolic.asSoftConstraint +import org.utbot.engine.symbolic.asAssumption +import org.utbot.engine.symbolic.asUpdate +import org.utbot.engine.simplificators.MemoryUpdateSimplificator +import org.utbot.engine.simplificators.simplifySymbolicStateUpdate +import org.utbot.engine.simplificators.simplifySymbolicValue +import org.utbot.engine.types.* +import org.utbot.engine.types.ARRAYS_SOOT_CLASS +import org.utbot.engine.types.CLASS_REF_SOOT_CLASS +import org.utbot.engine.types.CLASS_REF_TYPE +import org.utbot.engine.types.ENUM_ORDINAL +import org.utbot.engine.types.EQUALS_SIGNATURE +import org.utbot.engine.types.HASHCODE_SIGNATURE +import org.utbot.engine.types.METHOD_FILTER_MAP_FIELD_SIGNATURE +import org.utbot.engine.types.NEW_INSTANCE_SIGNATURE +import org.utbot.engine.types.NUMBER_OF_PREFERRED_TYPES +import org.utbot.engine.types.OBJECT_TYPE +import org.utbot.engine.types.SECURITY_FIELD_SIGNATURE +import org.utbot.engine.util.statics.concrete.associateEnumSootFieldsWithConcreteValues +import org.utbot.engine.util.statics.concrete.isEnumAffectingExternalStatics +import org.utbot.engine.util.statics.concrete.isEnumValuesFieldName +import org.utbot.engine.util.statics.concrete.makeEnumNonStaticFieldsUpdates +import org.utbot.engine.util.statics.concrete.makeEnumStaticFieldsUpdates +import org.utbot.engine.util.statics.concrete.makeSymbolicValuesFromEnumConcreteValues +import org.utbot.framework.ExploreThrowableDepth +import org.utbot.framework.UtSettings +import org.utbot.framework.UtSettings.preferredCexOption +import org.utbot.framework.UtSettings.substituteStaticsWithSymbolicVariable +import org.utbot.framework.isFromTrustedLibrary +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.context.NonNullSpeculator +import org.utbot.framework.context.TypeReplacer +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.TypeReplacementMode.AnyImplementor +import org.utbot.framework.plugin.api.TypeReplacementMode.KnownImplementor +import org.utbot.framework.plugin.api.TypeReplacementMode.NoImplementors +import org.utbot.framework.plugin.api.classId +import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.isAbstractType +import org.utbot.framework.plugin.api.util.* +import org.utbot.framework.util.executableId +import org.utbot.framework.util.graph +import org.utbot.summary.ast.declaredClassName +import org.utbot.framework.util.sootMethodOrNull +import org.utbot.taint.TaintContext +import org.utbot.taint.model.* +import java.lang.reflect.ParameterizedType +import kotlin.collections.plus +import kotlin.collections.plusAssign +import kotlin.math.max +import kotlin.math.min +import soot.ArrayType +import soot.BooleanType +import soot.ByteType +import soot.CharType +import soot.DoubleType +import soot.FloatType +import soot.IntType +import soot.LongType +import soot.Modifier +import soot.PrimType +import soot.RefLikeType +import soot.RefType +import soot.Scene +import soot.ShortType +import soot.SootClass +import soot.SootField +import soot.SootMethod +import soot.SootMethodRef +import soot.Type +import soot.Value +import soot.VoidType +import soot.jimple.ArrayRef +import soot.jimple.BinopExpr +import soot.jimple.ClassConstant +import soot.jimple.Constant +import soot.jimple.DefinitionStmt +import soot.jimple.DoubleConstant +import soot.jimple.Expr +import soot.jimple.FieldRef +import soot.jimple.FloatConstant +import soot.jimple.IdentityRef +import soot.jimple.IntConstant +import soot.jimple.InvokeExpr +import soot.jimple.LongConstant +import soot.jimple.MonitorStmt +import soot.jimple.NeExpr +import soot.jimple.NullConstant +import soot.jimple.ParameterRef +import soot.jimple.ReturnStmt +import soot.jimple.StaticFieldRef +import soot.jimple.Stmt +import soot.jimple.StringConstant +import soot.jimple.SwitchStmt +import soot.jimple.ThisRef +import soot.jimple.internal.JAddExpr +import soot.jimple.internal.JArrayRef +import soot.jimple.internal.JAssignStmt +import soot.jimple.internal.JBreakpointStmt +import soot.jimple.internal.JCastExpr +import soot.jimple.internal.JCaughtExceptionRef +import soot.jimple.internal.JDivExpr +import soot.jimple.internal.JDynamicInvokeExpr +import soot.jimple.internal.JEqExpr +import soot.jimple.internal.JGeExpr +import soot.jimple.internal.JGotoStmt +import soot.jimple.internal.JGtExpr +import soot.jimple.internal.JIdentityStmt +import soot.jimple.internal.JIfStmt +import soot.jimple.internal.JInstanceFieldRef +import soot.jimple.internal.JInstanceOfExpr +import soot.jimple.internal.JInterfaceInvokeExpr +import soot.jimple.internal.JInvokeStmt +import soot.jimple.internal.JLeExpr +import soot.jimple.internal.JLengthExpr +import soot.jimple.internal.JLookupSwitchStmt +import soot.jimple.internal.JLtExpr +import soot.jimple.internal.JMulExpr +import soot.jimple.internal.JNeExpr +import soot.jimple.internal.JNegExpr +import soot.jimple.internal.JNewArrayExpr +import soot.jimple.internal.JNewExpr +import soot.jimple.internal.JNewMultiArrayExpr +import soot.jimple.internal.JNopStmt +import soot.jimple.internal.JRemExpr +import soot.jimple.internal.JRetStmt +import soot.jimple.internal.JReturnStmt +import soot.jimple.internal.JReturnVoidStmt +import soot.jimple.internal.JSpecialInvokeExpr +import soot.jimple.internal.JStaticInvokeExpr +import soot.jimple.internal.JSubExpr +import soot.jimple.internal.JTableSwitchStmt +import soot.jimple.internal.JThrowStmt +import soot.jimple.internal.JVirtualInvokeExpr +import soot.jimple.internal.JimpleLocal +import soot.toolkits.graph.ExceptionalUnitGraph +import java.lang.reflect.GenericArrayType +import java.lang.reflect.TypeVariable +import java.lang.reflect.WildcardType + +private val CAUGHT_EXCEPTION = LocalVariable("@caughtexception") +private val logger = KotlinLogging.logger {} + +class Traverser( + private val methodUnderTest: ExecutableId, + internal val typeRegistry: TypeRegistry, + internal val hierarchy: Hierarchy, + // TODO HACK violation of encapsulation + internal val typeResolver: TypeResolver, + private val globalGraph: InterProceduralUnitGraph, + private val mocker: Mocker, + private val typeReplacer: TypeReplacer, + private val nonNullSpeculator: NonNullSpeculator, + private val taintContext: TaintContext, +) : UtContextInitializer() { + + private val visitedStmts: MutableSet = mutableSetOf() + + private val classLoader: ClassLoader + get() = utContext.classLoader + + // TODO: move this and other mutable fields to [TraversalContext] + lateinit var environment: Environment + private val solver: UtSolver + get() = environment.state.solver + + // TODO HACK violation of encapsulation + val memory: Memory + get() = environment.state.memory + + private val localVariableMemory: LocalVariableMemory + get() = environment.state.localVariableMemory + + //HACK (long strings) + internal var softMaxArraySize = 40 + + /** + * Contains information about the generic types used in the parameters of the method under test. + * + * Mutable set here is required since this object might be passed into several methods + * and get several piece of information about their parameterized types + */ + private val instanceAddrToGenericType = mutableMapOf>() + + private val preferredCexInstanceCache = mutableMapOf>() + + private var queuedSymbolicStateUpdates = SymbolicStateUpdate() + + internal val objectCounter = ObjectCounter(TypeRegistry.objectCounterInitialValue) + + private fun findNewAddr(insideStaticInitializer: Boolean): UtAddrExpression { + val newAddr = objectCounter.createNewAddr() + // return negative address for objects created inside static initializer + // to make their address space be intersected with address space of + // parameters of method under symbolic execution + // @see ObjectWithFinalStaticTest::testParameterEqualsFinalStatic + val signedAddr = if (insideStaticInitializer) -newAddr else newAddr + return UtAddrExpression(signedAddr) + } + internal fun findNewAddr() = findNewAddr(environment.state.isInsideStaticInitializer).also { touchAddress(it) } + + private val dynamicInvokeResolver: DynamicInvokeResolver = DelegatingDynamicInvokeResolver() + + // Counter used for a creation symbolic results of "hashcode" and "equals" methods. + private var equalsCounter = 0 + private var hashcodeCounter = 0 + + // A counter for objects created as native method call result. + private var unboundedConstCounter = 0 + + fun traverse(state: ExecutionState): Collection { + val context = TraversalContext() + + val currentStmt = state.stmt + environment = Environment(globalGraph.method(state.stmt), state) + + + if (currentStmt !in visitedStmts) { + environment.state.updateIsVisitedNew() + visitedStmts += currentStmt + } + + environment.state.lastEdge?.let { + globalGraph.visitEdge(it) + } + + try { + val exception = environment.state.exception + if (exception != null) { + context.traverseException(currentStmt, exception) + } else { + context.traverseStmt(currentStmt) + } + } catch (ex: Throwable) { + environment.state.close() + + logger.error(ex) { "Test generation failed on stmt $currentStmt, symbolic stack trace:\n$symbolicStackTrace" } + // TODO: enrich with nice description for known issues + throw ex + } + queuedSymbolicStateUpdates = SymbolicStateUpdate() + return context.nextStates + } + + internal val simplificator = Simplificator() + private val memoryUpdateSimplificator = MemoryUpdateSimplificator(simplificator) + + private fun TraversalContext.traverseStmt(current: Stmt) { + if (doPreparatoryWorkIfRequired(current)) return + + when (current) { + is JAssignStmt -> traverseAssignStmt(current) + is JIdentityStmt -> traverseIdentityStmt(current) + is JIfStmt -> traverseIfStmt(current) + is JInvokeStmt -> traverseInvokeStmt(current) + is SwitchStmt -> traverseSwitchStmt(current) + is JReturnStmt -> processResult(symbolicSuccess(current)) + is JReturnVoidStmt -> processResult(SymbolicSuccess(voidValue)) + is JRetStmt -> error("This one should be already removed by Soot: $current") + is JThrowStmt -> traverseThrowStmt(current) + is JBreakpointStmt -> offerState(updateQueued(globalGraph.succ(current))) + is JGotoStmt -> offerState(updateQueued(globalGraph.succ(current))) + is JNopStmt -> offerState(updateQueued(globalGraph.succ(current))) + is MonitorStmt -> offerState(updateQueued(globalGraph.succ(current))) + is DefinitionStmt -> TODO("$current") + else -> error("Unsupported: ${current::class}") + } + } + + /** + * Handles preparatory work for static initializers, multi-dimensional arrays creation + * and `newInstance` reflection call post-processing. + * + * For instance, it could push handmade graph with preparation statements to the path selector. + * + * Returns: + * - True if work is required and the constructed graph was pushed. In this case current + * traverse stops and continues after the graph processing; + * - False if preparatory work is not required or it is already done. + * environment.state.methodResult can contain the work result. + */ + private fun TraversalContext.doPreparatoryWorkIfRequired(current: Stmt): Boolean { + if (current !is JAssignStmt) return false + + return when { + processStaticInitializerIfRequired(current) -> true + unfoldMultiArrayExprIfRequired(current) -> true + pushInitGraphAfterNewInstanceReflectionCall(current) -> true + else -> false + } + } + + /** + * Handles preparatory work for static initializers. To do it, this method checks if any parts of the given + * statement is StaticRefField and the class this field belongs to hasn't been initialized yet. + * If so, it pushes a graph of the corresponding `` to the path selector. + * + * Returns: + * - True if the work is required and the graph was pushed. In this case current + * traversal stops and continues after the graph processing; + * - False if preparatory work is not required or it is already done. In this case a result from the + * environment.state.methodResult already processed and applied. + * + * Note: similar but more granular approach used if Engine decides to process static field concretely. + */ + private fun TraversalContext.processStaticInitializerIfRequired(stmt: JAssignStmt): Boolean { + val right = stmt.rightOp + val left = stmt.leftOp + val method = environment.method + val declaringClass = method.declaringClass + val result = listOf(right, left) + .filterIsInstance() + .filterNot { insideStaticInitializer(it, method, declaringClass) } + .firstOrNull { processStaticInitializer(it, stmt) } + + return result != null + } + + /** + * Handles preparatory work for multi-dimensional arrays. Constructs unfolded representation for + * JNewMultiArrayExpr in the [unfoldMultiArrayExpr]. + * + * Returns: + * - True if right part of the JAssignStmt contains JNewMultiArrayExpr and there is no calculated result in the + * environment.state.methodResult. + * - False otherwise + */ + private fun TraversalContext.unfoldMultiArrayExprIfRequired(stmt: JAssignStmt): Boolean { + // We have already unfolded the statement and processed constructed graph, have the calculated result + if (environment.state.methodResult != null) return false + + val right = stmt.rightOp + if (right !is JNewMultiArrayExpr) return false + + val graph = unfoldMultiArrayExpr(stmt) + val resolvedSizes = right.sizes.map { (resolve(it, IntType.v()) as PrimitiveValue).align() } + + negativeArraySizeCheck(*resolvedSizes.toTypedArray()) + + pushToPathSelector(graph, caller = null, resolvedSizes) + return true + } + + /** + * If the previous stms was `newInstance` method invocation, + * pushes a graph of the default constructor of the constructed type, if present, + * and pushes a state with a [InstantiationException] otherwise. + */ + private fun TraversalContext.pushInitGraphAfterNewInstanceReflectionCall(stmt: JAssignStmt): Boolean { + // Check whether the previous stmt was a `newInstance` invocation + val lastStmt = environment.state.path.lastOrNull() as? JAssignStmt ?: return false + if (!lastStmt.containsInvokeExpr()) { + return false + } + + val lastMethodInvocation = lastStmt.invokeExpr.method + if (lastMethodInvocation.subSignature != NEW_INSTANCE_SIGNATURE) { + return false + } + + // Process the current stmt as cast expression + val right = stmt.rightOp as? JCastExpr ?: return false + val castType = right.castType as? RefType ?: return false + val castedJimpleVariable = right.op as? JimpleLocal ?: return false + + val castedLocalVariable = (localVariableMemory.local(castedJimpleVariable.variable) as? ReferenceValue) ?: return false + + val castSootClass = castType.sootClass + + // We need to consider a situation when this class does not have a default constructor + // Since it can be a cast of a class with constructor to the interface (or ot the ancestor without default constructor), + // we cannot always throw a `java.lang.InstantiationException`. + // So, instead we will just continue the analysis without analysis of . + val initMethod = castSootClass.getMethodUnsafe("void ()") ?: return false + + if (!initMethod.canRetrieveBody()) { + return false + } + + val initGraph = ExceptionalUnitGraph(initMethod.activeBody) + + pushToPathSelector( + initGraph, + castedLocalVariable, + callParameters = emptyList(), + ) + + return true + } + + /** + * Processes static initialization for class. + * + * If class is not initialized yet, creates graph for that and pushes to the path selector; + * otherwise class is initialized and environment.state.methodResult can contain initialization result. + * + * If contains, adds state with the last edge to the path selector; + * if doesn't contain, it's already processed few steps before, nothing to do. + * + * Returns true if processing takes place and Engine should end traversal of current statement. + */ + private fun TraversalContext.processStaticInitializer( + fieldRef: StaticFieldRef, + stmt: Stmt + ): Boolean { + // This order of processing options is important. + // First, we should process classes that + // cannot be analyzed without clinit sections, e.g., enums + if (shouldProcessStaticFieldConcretely(fieldRef)) { + return processStaticFieldConcretely(fieldRef, stmt) + } + + // Then we should check if we should analyze clinit sections at all + if (!UtSettings.enableClinitSectionsAnalysis) { + return false + } + + // Finally, we decide whether we should analyze clinit sections concretely or not + if (UtSettings.processAllClinitSectionsConcretely) { + return processStaticFieldConcretely(fieldRef, stmt) + } + + val field = fieldRef.field + val declaringClass = field.declaringClass + val declaringClassId = declaringClass.id + val methodResult = environment.state.methodResult + if (!memory.isInitialized(declaringClassId) && + !isStaticInstanceInMethodResult(declaringClassId, methodResult) + ) { + val initializer = declaringClass.staticInitializerOrNull() + return if (initializer == null) { + false + } else { + val graph = classInitGraph(initializer) + pushToPathSelector(graph, null, emptyList()) + true + } + } + + val result = methodResult ?: return false + + when (result.symbolicResult) { + // This branch could be useful if we have a static field, i.e. x = 5 / 0 + is SymbolicFailure -> traverseException(stmt, result.symbolicResult) + is SymbolicSuccess -> offerState( + updateQueued( + environment.state.lastEdge!!, + result.symbolicStateUpdate + ) + ) + } + return true + } + + /** + * Decides should we read this static field concretely or not. + */ + private fun shouldProcessStaticFieldConcretely(fieldRef: StaticFieldRef): Boolean { + workaround(HACK) { + val className = fieldRef.field.declaringClass.name + + // We should process clinit sections for classes from these packages. + // Note that this list is not exhaustive, so it may be supplemented in the future. + val packagesToProcessConcretely = javaPackagesToProcessConcretely + sunPackagesToProcessConcretely + + val declaringClass = fieldRef.field.declaringClass + + val isFromPackageToProcessConcretely = packagesToProcessConcretely.any { className.startsWith(it) } + // it is required to remove classes we override, since + // we could accidentally initialize their final fields + // with values that will later affect our overridden classes + && fieldRef.field.declaringClass.type !in classToWrapper.keys + // because of the same reason we should not use + // concrete information from clinit sections for enums + && !fieldRef.field.declaringClass.isEnum + //hardcoded string for class name is used cause class is not public + //this is a hack to avoid crashing on code with Math.random() + && !className.endsWith("RandomNumberGeneratorHolder") + + // we can process concretely only enums that does not affect the external system + val isEnumNotAffectingExternalStatics = declaringClass.let { + it.isEnum && !it.isEnumAffectingExternalStatics(typeResolver) + } + + return isEnumNotAffectingExternalStatics || isFromPackageToProcessConcretely + } + } + + private val javaPackagesToProcessConcretely = listOf( + "applet", "awt", "beans", "io", "lang", "math", "net", + "nio", "rmi", "security", "sql", "text", "time", "util" + ).map { "java.$it" } + + private val sunPackagesToProcessConcretely = listOf( + "applet", "audio", "awt", "corba", "font", "instrument", + "invoke", "io", "java2d", "launcher", "management", "misc", + "net", "nio", "print", "reflect", "rmi", "security", + "swing", "text", "tools.jar", "tracing", "util" + ).map { "sun.$it" } + + /** + * Checks if field was processed (read) already. + * Otherwise offers to path selector the same statement, but with memory and constraints updates for this field. + * + * Returns true if processing takes place and Engine should end traversal of current statement. + */ + private fun TraversalContext.processStaticFieldConcretely(fieldRef: StaticFieldRef, stmt: Stmt): Boolean { + val field = fieldRef.field + val fieldId = field.fieldId + if (memory.isInitialized(fieldId)) { + return false + } + + // Gets concrete value, converts to symbolic value + val declaringClass = field.declaringClass + + val updates = if (declaringClass.isEnum) { + makeConcreteUpdatesForEnumsWithStmt(fieldId, declaringClass, stmt) + } else { + makeConcreteUpdatesForNonEnumStaticField(field, fieldId, declaringClass, stmt) + } + + // a static initializer can be the first statement in method so there will be no last edge + // for example, as it is during Enum::values method analysis: + // public static ClassWithEnum$StatusEnum[] values() + // { + // ClassWithEnum$StatusEnum[] $r0, $r2; + // java.lang.Object $r1; + + // $r0 = ; + val edge = environment.state.lastEdge ?: globalGraph.succ(stmt) + + val newState = updateQueued(edge, updates) + offerState(newState) + + return true + } + + private fun makeConcreteUpdatesForEnum( + type: RefType, + fieldId: FieldId? = null + ): Pair { + val jClass = type.id.jClass + + // symbolic value for enum class itself, we don't want to have mocks for this value + val enumClassValue = findOrCreateStaticObject(type, mockInfoGenerator = null) + + // values for enum constants + val enumConstantConcreteValues = jClass.enumConstants.filterIsInstance>() + + val (enumConstantSymbolicValues, enumConstantSymbolicResultsByName) = + makeSymbolicValuesFromEnumConcreteValues(type, enumConstantConcreteValues) + + val enumFields = typeResolver.findFields(type) + + val sootFieldsWithRuntimeValues = + associateEnumSootFieldsWithConcreteValues(enumFields, enumConstantConcreteValues) + + val (staticFields, nonStaticFields) = sootFieldsWithRuntimeValues.partition { it.first.isStatic } + + val (staticFieldUpdates, curFieldSymbolicValueForLocalVariable) = makeEnumStaticFieldsUpdates( + staticFields, + type.sootClass, + enumConstantSymbolicResultsByName, + enumConstantSymbolicValues, + enumClassValue, + fieldId + ) + + val nonStaticFieldsUpdates = makeEnumNonStaticFieldsUpdates(enumConstantSymbolicValues, nonStaticFields) + + // we do not mark static fields for enum constants and $VALUES as meaningful + // because we should not set them in generated code + val meaningfulStaticFields = staticFields.filterNot { + val name = it.first.name + + name in enumConstantSymbolicResultsByName.keys || isEnumValuesFieldName(name) + } + + val initializedStaticFieldsMemoryUpdate = MemoryUpdate( + initializedStaticFields = staticFields.map { it.first.fieldId }.toPersistentSet(), + meaningfulStaticFields = meaningfulStaticFields.map { it.first.fieldId }.toPersistentSet(), + symbolicEnumValues = enumConstantSymbolicValues.toPersistentList() + ) + + return Pair( + staticFieldUpdates + nonStaticFieldsUpdates + initializedStaticFieldsMemoryUpdate, + curFieldSymbolicValueForLocalVariable + ) + } + + @Suppress("UnnecessaryVariable") + private fun makeConcreteUpdatesForEnumsWithStmt( + fieldId: FieldId, + declaringClass: SootClass, + stmt: Stmt + ): SymbolicStateUpdate { + val (enumUpdates, curFieldSymbolicValueForLocalVariable) = + makeConcreteUpdatesForEnum(declaringClass.type, fieldId) + val allUpdates = enumUpdates + createConcreteLocalValueUpdate(stmt, curFieldSymbolicValueForLocalVariable) + return allUpdates + } + + @Suppress("UnnecessaryVariable") + private fun makeConcreteUpdatesForNonEnumStaticField( + field: SootField, + fieldId: FieldId, + declaringClass: SootClass, + stmt: Stmt + ): SymbolicStateUpdate { + val concreteValue = extractConcreteValue(field) + val (symbolicResult, symbolicStateUpdate) = toMethodResult(concreteValue, field.type) + val symbolicValue = (symbolicResult as SymbolicSuccess).value + + // Collects memory updates + val initializedFieldUpdate = + MemoryUpdate(initializedStaticFields = persistentHashSetOf(fieldId)) + + val mockInfoGenerator = UtMockInfoGenerator { addr -> UtStaticObjectMockInfo(declaringClass.id, addr) } + + val objectUpdate = objectUpdate( + instance = findOrCreateStaticObject(declaringClass.type, mockInfoGenerator), + field = field, + value = valueToExpression(symbolicValue, field.type) + ) + val allUpdates = symbolicStateUpdate + + initializedFieldUpdate + + objectUpdate + + createConcreteLocalValueUpdate(stmt, symbolicValue) + + return allUpdates + } + + /** + * Creates a local update consisting [symbolicValue] for a local variable from [stmt] in case [stmt] is [JAssignStmt]. + */ + private fun createConcreteLocalValueUpdate( + stmt: Stmt, + symbolicValue: SymbolicValue?, + ): LocalMemoryUpdate { + // we need to make locals update if it is an assignment statement + // for enums we have only two types for assignment with enums — enum constant or $VALUES field + // for example, a jimple body for Enum::values method starts with the following lines: + // public static ClassWithEnum$StatusEnum[] values() + // { + // ClassWithEnum$StatusEnum[] $r0, $r2; + // java.lang.Object $r1; + // $r0 = ; + // $r1 = virtualinvoke $r0.(); + + // so, we have to make an update for the local $r0 + + return if (stmt is JAssignStmt && stmt.leftOp is JimpleLocal) { + val local = stmt.leftOp as JimpleLocal + + localMemoryUpdate(local.variable to symbolicValue) + } else { + LocalMemoryUpdate() + } + } + + // Some fields are inaccessible with reflection, so we have to instantiate it by ourselves. + // Otherwise, extract it from the class. + // TODO JIRA:1593 + private fun extractConcreteValue(field: SootField): Any? = + when (field.signature) { + SECURITY_FIELD_SIGNATURE -> SecurityManager() + // todo change to class loading + //FIELD_FILTER_MAP_FIELD_SIGNATURE -> mapOf(Reflection::class to arrayOf("fieldFilterMap", "methodFilterMap")) + METHOD_FILTER_MAP_FIELD_SIGNATURE -> emptyMap, Array>() + else -> { + val fieldId = field.fieldId + val jField = fieldId.jField + jField.let { + it.withAccessibility { + it.get(null) + } + } + } + } + + private fun isStaticInstanceInMethodResult(id: ClassId, methodResult: MethodResult?) = + methodResult != null && id in methodResult.symbolicStateUpdate.memoryUpdates.staticInstanceStorage + + private fun TraversalContext.skipVerticesForThrowableCreation(current: JAssignStmt) { + val rightType = current.rightOp.type as RefType + val exceptionType = Scene.v().getRefType(rightType.className) + val mockInfoGenerator = UtMockInfoGenerator { mockAddr -> + UtNewInstanceMockInfo(exceptionType.id, mockAddr, environment.method.declaringClass.id) + } + val createdException = createObject( + findNewAddr(), + exceptionType, + useConcreteType = true, + mockInfoGenerator = mockInfoGenerator + ) + val currentExceptionJimpleLocal = current.leftOp as JimpleLocal + + queuedSymbolicStateUpdates += localMemoryUpdate(currentExceptionJimpleLocal.variable to createdException) + + // mark the rest of the path leading to the '' statement as covered + do { + environment.state = updateQueued(globalGraph.succ(environment.state.stmt)) + globalGraph.visitEdge(environment.state.lastEdge!!) + globalGraph.visitNode(environment.state) + } while (!environment.state.stmt.isConstructorCall(currentExceptionJimpleLocal)) + + offerState(updateQueued(globalGraph.succ(environment.state.stmt))) + } + + private fun TraversalContext.traverseAssignStmt(current: JAssignStmt) { + val rightValue = current.rightOp + + if (UtSettings.exploreThrowableDepth == ExploreThrowableDepth.SKIP_ALL_STATEMENTS) { + val rightType = rightValue.type + if (rightValue is JNewExpr && rightType is RefType) { + val throwableInheritors = typeResolver.findOrConstructInheritorsIncludingTypes(THROWABLE_TYPE) + + // skip all the vertices in the CFG between `new` and `` statements + if (rightType in throwableInheritors) { + skipVerticesForThrowableCreation(current) + return + } + } + } + + val rightPartWrappedAsMethodResults = if (rightValue is InvokeExpr) { + invokeResult(rightValue) + } else { + val value = resolve(rightValue, current.leftOp.type) + listOf(MethodResult(value)) + } + + rightPartWrappedAsMethodResults.forEach { methodResult -> + val taintAnalysisUpdate = if (UtSettings.useTaintAnalysis && rightValue is InvokeExpr) { + processTaintAnalysis(rightValue, methodResult) + } else { + SymbolicStateUpdate() + } + + when (methodResult.symbolicResult) { + + is SymbolicFailure -> { //exception thrown + if (environment.state.executionStack.last().doesntThrow) return@forEach + + val nextState = createExceptionStateQueued( + methodResult.symbolicResult, + methodResult.symbolicStateUpdate + taintAnalysisUpdate + ) + globalGraph.registerImplicitEdge(nextState.lastEdge!!) + offerState(nextState) + } + + is SymbolicSuccess -> { + val update = traverseAssignLeftPart( + current.leftOp, + methodResult.symbolicResult.value + ) + offerState( + updateQueued( + globalGraph.succ(current), + update + methodResult.symbolicStateUpdate + taintAnalysisUpdate + ) + ) + } + } + } + } + + /** + * This hack solves the problem with static final fields, which are equal by reference with parameter. + * + * Let be the address of a parameter and correspondingly the address of final field be p0. + * The initial state of a chunk array for this static field is always (mkArray Class_field Int -> Int) + * And the current state of this chunk array is (store (mkArray Class_field Int -> Int) (p0: someValue)) + * At initial chunk array under address p0 can be placed any value, because during symbolic execution we + * always refer only to current state. + * + * At the resolving stage, to resolve model of parameter before invoke, we get it from initial chunk array + * by address p0, where can be placed any value. However, resolved model for parameter after execution will + * be correct, as current state has correct value in chunk array under p0 address. + */ + private fun addConstraintsForFinalAssign(left: SymbolicValue, value: SymbolicValue) { + if (left is PrimitiveValue) { + if (left.type is DoubleType) { + queuedSymbolicStateUpdates += mkOr( + Eq(left, value as PrimitiveValue), + Ne(left, left), + Ne(value, value) + ).asHardConstraint() + } else { + queuedSymbolicStateUpdates += Eq(left, value as PrimitiveValue).asHardConstraint() + } + } else if (left is ReferenceValue) { + queuedSymbolicStateUpdates += addrEq(left.addr, (value as ReferenceValue).addr).asHardConstraint() + } + } + + /** + * Check for [ArrayStoreException] when an array element is assigned + * + * @param arrayInstance Symbolic value corresponding to the array being updated + * @param value Symbolic value corresponding to the right side of the assignment + */ + private fun TraversalContext.arrayStoreExceptionCheck(arrayInstance: SymbolicValue, value: SymbolicValue) { + require(arrayInstance is ArrayValue) + val valueType = value.type + val valueBaseType = valueType.baseType + val arrayElementType = arrayInstance.type.elementType + + // We should check for [ArrayStoreException] only for reference types. + // * For arrays of primitive types, incorrect assignment is prevented as compile time. + // * When assigning primitive literals (e.g., `1`) to arrays of corresponding boxed classes (`Integer`), + // the conversion to the reference type is automatic. + // * [System.arraycopy] and similar functions that can throw [ArrayStoreException] accept [Object] arrays + // as arguments, so array elements are references. + + if (valueBaseType is RefType) { + val arrayElementTypeStorage = typeResolver.constructTypeStorage(arrayElementType, useConcreteType = false) + + // Generate ASE only if [value] is not a subtype of the type of array elements + val isExpression = typeRegistry.typeConstraint(value.addr, arrayElementTypeStorage).isConstraint() + val notIsExpression = mkNot(isExpression) + + // `null` is compatible with any reference type, so we should not throw ASE when `null` is assigned + val nullEqualityConstraint = addrEq(value.addr, nullObjectAddr) + val notNull = mkNot(nullEqualityConstraint) + + // Currently the negation of [UtIsExpression] seems to work incorrectly for [java.lang.Object]: + // https://github.com/UnitTestBot/UTBotJava/issues/1007 + + // It is related to [org.utbot.engine.pc.Z3TranslatorVisitor.filterInappropriateTypes] that removes + // internal engine classes for [java.lang.Object] type storage, and this logic is not fully + // consistent with the negation. + + // Here we have a specific test for [java.lang.Object] as the type of array elements: + // any reference type may be assigned to elements of an Object array, so we should not generate + // [ArrayStoreException] in these cases. + + // TODO: remove enclosing `if` when [UtIfExpression] negation is fixed + if (!arrayElementType.isJavaLangObject()) { + implicitlyThrowException(ArrayStoreException(), setOf(notIsExpression, notNull)) + } + + // If ASE is not thrown, we know that either the value is null, or it has a compatible type + queuedSymbolicStateUpdates += mkOr(isExpression, nullEqualityConstraint).asHardConstraint() + } + } + + /** + * Traverses left part of assignment i.e. where to store resolved value. + */ + private fun TraversalContext.traverseAssignLeftPart(left: Value, value: SymbolicValue): SymbolicStateUpdate = when (left) { + is ArrayRef -> { + val arrayInstance = resolve(left.base) as ArrayValue + val addr = arrayInstance.addr + nullPointerExceptionCheck(addr) + + val index = (resolve(left.index) as PrimitiveValue).align() + val length = memory.findArrayLength(addr) + indexOutOfBoundsChecks(index, length) + + queuedSymbolicStateUpdates += Le(length, softMaxArraySize).asHardConstraint() // TODO: fix big array length + + arrayStoreExceptionCheck(arrayInstance, value) + + // add constraint for possible array type + val valueType = value.type + val valueBaseType = valueType.baseType + if (valueBaseType is RefType) { + val valueTypeAncestors = typeResolver.findOrConstructAncestorsIncludingTypes(valueBaseType) + val valuePossibleBaseTypes = value.typeStorage.possibleConcreteTypes.map { it.baseType } + // Either one of the possible types or one of their ancestor (to add interfaces and abstract classes) + val arrayPossibleBaseTypes = valueTypeAncestors + valuePossibleBaseTypes + + val arrayPossibleTypes = arrayPossibleBaseTypes.map { + it.makeArrayType(arrayInstance.type.numDimensions) + } + val typeStorage = typeResolver.constructTypeStorage(OBJECT_TYPE, arrayPossibleTypes) + + queuedSymbolicStateUpdates += typeRegistry + .typeConstraint(arrayInstance.addr, typeStorage) + .isConstraint() + .asHardConstraint() + } + + val elementType = arrayInstance.type.elementType + val valueExpression = valueToExpression(value, elementType) + SymbolicStateUpdate(memoryUpdates = arrayUpdate(arrayInstance, index, valueExpression)) + } + is FieldRef -> { + val instanceForField = resolveInstanceForField(left) + + val objectUpdate = objectUpdate( + instance = instanceForField, + field = left.field, + value = valueToExpression(value, left.field.type) + ) + + // This hack solves the problem with static final fields, which are equal by reference with parameter + workaround(HACK) { + if (left.field.isFinal) { + addConstraintsForFinalAssign(resolve(left), value) + } + } + + if (left is StaticFieldRef) { + val fieldId = left.field.fieldId + val staticFieldMemoryUpdate = StaticFieldMemoryUpdateInfo(fieldId, value) + val touchedStaticFields = persistentListOf(staticFieldMemoryUpdate) + queuedSymbolicStateUpdates += MemoryUpdate(staticFieldsUpdates = touchedStaticFields) + if (!environment.method.isStaticInitializer && isStaticFieldMeaningful(left.field)) { + queuedSymbolicStateUpdates += MemoryUpdate(meaningfulStaticFields = persistentSetOf(fieldId)) + } + } + + // Associate created value with field of used instance. For more information check Memory#fieldValue docs. + val fieldValuesUpdate = fieldUpdate(left.field, instanceForField.addr, value) + + SymbolicStateUpdate(memoryUpdates = objectUpdate + fieldValuesUpdate) + } + is JimpleLocal -> SymbolicStateUpdate(localMemoryUpdates = localMemoryUpdate(left.variable to value)) + is InvokeExpr -> TODO("Not implemented: $left") + else -> error("${left::class} is not implemented") + } + + /** + * Resolves instance for field. For static field it's a special object represents static fields of particular class. + */ + private fun TraversalContext.resolveInstanceForField(fieldRef: FieldRef) = when (fieldRef) { + is JInstanceFieldRef -> { + // Runs resolve() to check possible NPE and create required arrays related to the field. + // Ignores the result of resolve(). + resolve(fieldRef) + val baseObject = resolve(fieldRef.base) as ObjectValue + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(fieldRef.field.declaringClass.type) + baseObject.copy(typeStorage = typeStorage) + } + is StaticFieldRef -> { + val declaringClassType = fieldRef.field.declaringClass.type + val generator = UtMockInfoGenerator { mockAddr -> + UtStaticObjectMockInfo(declaringClassType.id, mockAddr) + } + findOrCreateStaticObject(declaringClassType, generator) + } + else -> error("Unreachable branch") + } + + /** + * Converts value to expression with cast to target type for primitives. + */ + fun valueToExpression(value: SymbolicValue, type: Type): UtExpression = when (value) { + is ReferenceValue -> value.addr + // TODO: shall we add additional constraint that aligned expression still equals original? + // BitVector can lose valuable bites during extraction + is PrimitiveValue -> UtCastExpression(value, type) + } + + private fun TraversalContext.traverseIdentityStmt(current: JIdentityStmt) { + val localVariable = (current.leftOp as? JimpleLocal)?.variable ?: error("Unknown op: ${current.leftOp}") + when (val identityRef = current.rightOp as IdentityRef) { + is ParameterRef, is ThisRef -> { + // Nested method calls already have input arguments in state + val value = if (environment.state.inputArguments.isNotEmpty()) { + environment.state.inputArguments.removeFirst().let { + // implicit cast, if we pass to function with + // int parameter a value with e.g. byte type + if (it is PrimitiveValue && it.type != identityRef.type) { + it.cast(identityRef.type) + } else { + it + } + } + } else { + val suffix = if (identityRef is ParameterRef) "${identityRef.index}" else "_this" + val pName = "p$suffix" + val mockInfoGenerator = parameterMockInfoGenerator(identityRef) + + val isNonNullable = if (identityRef is ParameterRef) { + environment.method.paramHasNotNullAnnotation(identityRef.index) + } else { + true // "this" must be not null + } + + val createdValue = identityRef.createConst(pName, mockInfoGenerator) + + if (createdValue is ReferenceValue) { + // Update generic type info for method under test' parameters + val index = (identityRef as? ParameterRef)?.index?.plus(1) ?: 0 + updateGenericTypeInfoFromMethod(methodUnderTest, createdValue, index) + + if (isNonNullable) { + queuedSymbolicStateUpdates += mkNot( + addrEq( + createdValue.addr, + nullObjectAddr + ) + ).asHardConstraint() + } + } + if (preferredCexOption) { + applyPreferredConstraints(createdValue) + } + createdValue + } + + environment.state.parameters += Parameter(localVariable, identityRef.type, value) + + val nextState = updateQueued( + globalGraph.succ(current), + SymbolicStateUpdate(localMemoryUpdates = localMemoryUpdate(localVariable to value)) + ) + offerState(nextState) + } + is JCaughtExceptionRef -> { + val value = localVariableMemory.local(CAUGHT_EXCEPTION) + ?: error("Exception wasn't caught, stmt: $current, line: ${current.lines}") + val nextState = updateQueued( + globalGraph.succ(current), + SymbolicStateUpdate(localMemoryUpdates = localMemoryUpdate(localVariable to value, CAUGHT_EXCEPTION to null)) + ) + offerState(nextState) + } + else -> error("Unsupported $identityRef") + } + } + + /** + * Creates mock info for method under test' non-primitive parameter. + * + * Returns null if mock is not allowed - Engine traverses nested method call or parameter type is not RefType. + */ + private fun parameterMockInfoGenerator(parameterRef: IdentityRef): UtMockInfoGenerator? { + if (environment.state.isInNestedMethod()) return null + if (parameterRef !is ParameterRef) return null + val type = parameterRef.type + if (type !is RefType) return null + return UtMockInfoGenerator { mockAddr -> UtObjectMockInfo(type.id, mockAddr) } + } + + private fun updateGenericTypeInfoFromMethod(method: ExecutableId, value: ReferenceValue, parameterIndex: Int) { + val type = extractParameterizedType(method, parameterIndex) as? ParameterizedType ?: return + + updateGenericTypeInfo(type, value) + } + + /** + * Stores information about the generic types used in the parameters of the method under test. + */ + private fun updateGenericTypeInfo(type: ParameterizedType, value: ReferenceValue) { + val typeStorages = type.actualTypeArguments.map { actualTypeArgument -> + when (actualTypeArgument) { + is WildcardType -> { + val upperBounds = actualTypeArgument.upperBounds + val lowerBounds = actualTypeArgument.lowerBounds + val allTypes = upperBounds + lowerBounds + + if (allTypes.any { it is GenericArrayType }) { + val errorTypes = allTypes.filterIsInstance() + logger.warn { "we do not support GenericArrayTypeImpl yet, and $errorTypes found. SAT-1446" } + return + } + + val upperBoundsTypes = typeResolver.intersectInheritors(upperBounds) + val lowerBoundsTypes = typeResolver.intersectAncestors(lowerBounds) + + // For now, we take into account only one type bound. + // If we have the only upper bound, we should create a type storage + // with a corresponding type if it exists or with + // OBJECT_TYPE if there is no such type (e.g., E or T) + val leastCommonType = upperBounds + .singleOrNull() + ?.let { Scene.v().getRefTypeUnsafe(it.typeName) } + ?: OBJECT_TYPE + + typeResolver.constructTypeStorage(leastCommonType, upperBoundsTypes.intersect(lowerBoundsTypes)) + } + is TypeVariable<*> -> { // it is a type variable for the whole class, not the function type variable + val upperBounds = actualTypeArgument.bounds + + if (upperBounds.any { it is GenericArrayType }) { + val errorTypes = upperBounds.filterIsInstance() + logger.warn { "we do not support GenericArrayType yet, and $errorTypes found. SAT-1446" } + return + } + + val upperBoundsTypes = typeResolver.intersectInheritors(upperBounds) + + // For now, we take into account only one type bound. + // If we have the only upper bound, we should create a type storage + // with a corresponding type if it exists or with + // OBJECT_TYPE if there is no such type (e.g., E or T) + val leastCommonType = upperBounds + .singleOrNull() + ?.let { Scene.v().getRefTypeUnsafe(it.typeName) } + ?: OBJECT_TYPE + + typeResolver.constructTypeStorage(leastCommonType, upperBoundsTypes) + } + is GenericArrayType -> { + // TODO bug with T[][], because there is no such time T JIRA:1446 + typeResolver.constructTypeStorage(OBJECT_TYPE, useConcreteType = false) + } + is ParameterizedType, is Class<*> -> { + val sootType = Scene.v().getType(actualTypeArgument.rawType.typeName) + + typeResolver.constructTypeStorage(sootType, useConcreteType = false) + } + else -> error("Unsupported argument type ${actualTypeArgument::class}") + } + } + + queuedSymbolicStateUpdates += typeRegistry + .genericTypeParameterConstraint(value.addr, typeStorages) + .asHardConstraint() + + instanceAddrToGenericType.getOrPut(value.addr) { mutableSetOf() }.add(type) + + val memoryUpdate = TypeResolver.createGenericTypeInfoUpdate( + value.addr, + typeStorages, + memory.getAllGenericTypeInfo() + ) + + queuedSymbolicStateUpdates += memoryUpdate + } + + private fun extractParameterizedType( + method: ExecutableId, + index: Int + ): java.lang.reflect.Type? { + // If we don't have access to methodUnderTest's jClass, the engine should not fail + // We just won't update generic information for it + val callable = runCatching { method.executable }.getOrNull() ?: return null + + val type = if (index == 0) { + // TODO: for ThisRef both methods don't return parameterized type + if (method.isConstructor) { + callable.annotatedReturnType?.type + } else { + callable.declaringClass // same as it was, but it isn't parametrized type + ?: error("No instanceParameter for $callable found") + } + } else { + // Sometimes out of bound exception occurred here, e.g., com.alibaba.fescar.core.model.GlobalStatus. + workaround(HACK) { + val valueParameters = callable.genericParameterTypes + + if (index - 1 > valueParameters.lastIndex) return null + valueParameters[index - 1] + } + } + + return type + } + + private fun TraversalContext.traverseIfStmt(current: JIfStmt) { + // positiveCaseEdge could be null - see Conditions::emptyBranches + val (negativeCaseEdge, positiveCaseEdge) = globalGraph.succs(current).let { it[0] to it.getOrNull(1) } + val cond = current.condition + val resolvedCondition = resolveIfCondition(cond as BinopExpr) + val positiveCasePathConstraint = resolvedCondition.condition + val (positiveCaseSoftConstraint, negativeCaseSoftConstraint) = resolvedCondition.softConstraints + val negativeCasePathConstraint = mkNot(positiveCasePathConstraint) + + if (positiveCaseEdge != null) { + environment.state.definitelyFork() + } + + /* assumeOrExecuteConcrete in jimple looks like: + ``` z0 = a > 5 + if (z0 == 1) goto label1 + assumeOrExecuteConcretely(z0) + + label1: + assumeOrExecuteConcretely(z0) + ``` + + We have to detect such situations to avoid addition `a > 5` into hardConstraints, + because we want to add them into Assumptions. + + Note: we support only simple predicates right now (one logical operation), + otherwise they will be added as hard constraints, and we will not execute + the state concretely if there will be UNSAT because of assumptions. + */ + val isAssumeExpr = positiveCaseEdge?.let { isConditionForAssumeOrExecuteConcretely(it.dst) } ?: false + + // in case of assume we want to have the only branch where $z = 1 (it is a negative case) + if (!isAssumeExpr) { + positiveCaseEdge?.let { edge -> + environment.state.expectUndefined() + val positiveCaseState = updateQueued( + edge, + SymbolicStateUpdate( + hardConstraints = positiveCasePathConstraint.asHardConstraint(), + softConstraints = setOfNotNull(positiveCaseSoftConstraint).asSoftConstraint() + ) + resolvedCondition.symbolicStateUpdates.positiveCase + ) + offerState(positiveCaseState) + } + } + + // Depending on existance of assumeExpr we have to add corresponding hardConstraints and assumptions + val hardConstraints = if (!isAssumeExpr) negativeCasePathConstraint.asHardConstraint() else emptyHardConstraint() + val assumption = if (isAssumeExpr) negativeCasePathConstraint.asAssumption() else emptyAssumption() + + val negativeCaseState = updateQueued( + negativeCaseEdge, + SymbolicStateUpdate( + hardConstraints = hardConstraints, + softConstraints = setOfNotNull(negativeCaseSoftConstraint).asSoftConstraint(), + assumptions = assumption + ) + resolvedCondition.symbolicStateUpdates.negativeCase + ) + offerState(negativeCaseState) + } + + /** + * Returns true if the next stmt is an [assumeOrExecuteConcretelyMethod] invocation, false otherwise. + */ + private fun isConditionForAssumeOrExecuteConcretely(stmt: Stmt): Boolean { + val successor = globalGraph.succStmts(stmt).singleOrNull() as? JInvokeStmt ?: return false + val invokeExpression = successor.invokeExpr as? JStaticInvokeExpr ?: return false + return invokeExpression.method.isUtMockAssumeOrExecuteConcretely + } + + private fun TraversalContext.traverseInvokeStmt(current: JInvokeStmt) { + val results = invokeResult(current.invokeExpr) + + results.forEach { result -> + if (result.symbolicResult is SymbolicFailure && environment.state.executionStack.last().doesntThrow) { + return@forEach + } + + offerState( + when (result.symbolicResult) { + is SymbolicFailure -> createExceptionStateQueued( + result.symbolicResult, + result.symbolicStateUpdate + ) + is SymbolicSuccess -> updateQueued( + globalGraph.succ(current), + result.symbolicStateUpdate + ) + } + ) + } + } + + private fun TraversalContext.traverseSwitchStmt(current: SwitchStmt) { + val valueExpr = resolve(current.key) as PrimitiveValue + val successors = when (current) { + is JTableSwitchStmt -> { + val indexed = (current.lowIndex..current.highIndex).mapIndexed { i, index -> + Edge(current, current.getTarget(i) as Stmt, i) to Eq(valueExpr, index) + } + val targetExpr = mkOr( + Lt(valueExpr, current.lowIndex), + Gt(valueExpr, current.highIndex) + ) + indexed + (Edge(current, current.defaultTarget as Stmt, indexed.size) to targetExpr) + } + is JLookupSwitchStmt -> { + val lookups = current.lookupValues.mapIndexed { i, value -> + Edge(current, current.getTarget(i) as Stmt, i) to Eq(valueExpr, value.value) + } + val targetExpr = mkNot(mkOr(lookups.map { it.second })) + lookups + (Edge(current, current.defaultTarget as Stmt, lookups.size) to targetExpr) + } + else -> error("Unknown switch $current") + } + if (successors.size > 1) { + environment.state.expectUndefined() + environment.state.definitelyFork() + } + + successors.forEach { (target, expr) -> + offerState( + updateQueued( + target, + SymbolicStateUpdate(hardConstraints = expr.asHardConstraint()), + ) + ) + } + } + + private fun TraversalContext.traverseThrowStmt(current: JThrowStmt) { + val symException = explicitThrown(resolve(current.op), environment.state.isInNestedMethod()) + traverseException(current, symException) + } + + // TODO: HACK violation of encapsulation + fun createObject( + addr: UtAddrExpression, + type: RefType, + useConcreteType: Boolean, + mockInfoGenerator: UtMockInfoGenerator? + ): ObjectValue { + touchAddress(addr) + val nullEqualityConstraint = mkEq(addr, nullObjectAddr) + + // Some types (e.g., interfaces) need to be mocked or replaced with the concrete implementor. + // Typically, this implementor is selected by SMT solver later. + // However, if we have the restriction on implementor type (it may be obtained + // from Spring bean definitions, for example), we can just create a symbolic object + // with hard constraint on the mentioned type. + val replacedClassId = when (typeReplacer.typeReplacementMode) { + KnownImplementor -> typeReplacer.replaceTypeIfNeeded(type.id) + AnyImplementor, + NoImplementors -> null + } + + replacedClassId?.let { + val sootType = Scene.v().getRefType(it.canonicalName) + val typeStorage = typeResolver.constructTypeStorage(sootType, useConcreteType = false) + + val typeHardConstraint = typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() + queuedSymbolicStateUpdates += typeHardConstraint + + return ObjectValue(typeStorage, addr) + } + + if (mockInfoGenerator != null) { + val mockInfo = mockInfoGenerator.generate(addr) + + queuedSymbolicStateUpdates += MemoryUpdate(addrToMockInfo = persistentHashMapOf(addr to mockInfo)) + + val mockedObjectInfo = mocker.mock(type, mockInfo) + + if (mockedObjectInfo is UnexpectedMock) { + // if mock occurs, but it is unexpected due to some reasons + // (e.g. we do not have mock framework installed), + // we can only generate a test that uses null value for mocked object + queuedSymbolicStateUpdates += nullEqualityConstraint.asHardConstraint() + return mockedObjectInfo.value + } + + val mockedObject = mockedObjectInfo.value + if (mockedObject != null) { + queuedSymbolicStateUpdates += MemoryUpdate(mockInfos = persistentListOf(MockInfoEnriched(mockInfo))) + + // add typeConstraint for mocked object. It's a declared type of the object. + val typeConstraint = typeRegistry.typeConstraint(addr, mockedObject.typeStorage).all() + val isMockConstraint = typeRegistry.isMockConstraint(mockedObject.addr) + + queuedSymbolicStateUpdates += typeConstraint.asHardConstraint() + queuedSymbolicStateUpdates += isMockConstraint.asHardConstraint() + + return mockedObject + } + } + + // construct a type storage that might contain our own types, i.e., UtArrayList + val typeStoragePossiblyWithOverriddenTypes = typeResolver.constructTypeStorage(type, useConcreteType) + val leastCommonType = typeStoragePossiblyWithOverriddenTypes.leastCommonType as RefType + + // If the leastCommonType of the created typeStorage is one of our own classes, + // we must create a copy of the typeStorage with the real classes instead of wrappers. + // It is required because we do not want to have situations when some object might have + // only artificial classes as their possible, that would cause problems in the type constraints. + val typeStorage = if (leastCommonType in wrapperToClass.keys) { + val possibleConcreteTypes = wrapperToClass.getValue(leastCommonType) + + TypeStorage.constructTypeStorageUnsafe( + typeStoragePossiblyWithOverriddenTypes.leastCommonType, + possibleConcreteTypes + ) + } else { + if (addr.isThisAddr) { + val possibleTypes = typeStoragePossiblyWithOverriddenTypes.possibleConcreteTypes + val isTypeInappropriate = type.sootClass?.isInappropriate == true + + // If we're trying to construct this instance and it has an inappropriate type, + // we won't be able to instantiate its instance in resulting tests. + // Therefore, we have to test one of its inheritors that does not override + // the method under test + if (isTypeInappropriate) { + require(possibleTypes.isNotEmpty()) { + "We do not support testing for abstract classes (or interfaces) without any non-abstract " + + "inheritors (implementors). Probably, it'll be supported in the future." + } + + val possibleTypesWithNonOverriddenMethod = possibleTypes + .filterTo(mutableSetOf()) { + val methods = (it as RefType).sootClass.methods + methods.none { method -> + val methodUnderTest = environment.method + val parameterTypes = method.parameterTypes + + method.name == methodUnderTest.name && parameterTypes == methodUnderTest.parameterTypes + } + } + + require(possibleTypesWithNonOverriddenMethod.isNotEmpty()) { + "There is no instantiatable inheritor of the class under test that does not override " + + "a method given for testing" + } + + TypeStorage.constructTypeStorageUnsafe(type, possibleTypesWithNonOverriddenMethod) + } else { + // If we create a `this` instance and its type is instantiatable, + // we should construct a type storage with single type + TypeStorage.constructTypeStorageWithSingleType(type) + } + } else { + typeStoragePossiblyWithOverriddenTypes + } + } + + val typeHardConstraint = typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() + + wrapper(type, addr)?.let { + queuedSymbolicStateUpdates += typeHardConstraint + return it + } + + if (typeStorage.possibleConcreteTypes.isEmpty()) { + requireNotNull(mockInfoGenerator) { + "An object with $addr and $type doesn't have concrete possible types," + + "but there is no mock info generator provided to construct a mock value." + } + + return createMockedObject(addr, type, mockInfoGenerator, nullEqualityConstraint) + } + + val concreteImplementation: Concrete? = when (typeReplacer.typeReplacementMode) { + AnyImplementor -> findConcreteImplementation(addr, type, typeHardConstraint) + + // If our type is not abstract, both in `KnownImplementors` and `NoImplementors` mode, + // we should just still use concrete implementation that represents itself + // + // Otherwise: + // In case of `KnownImplementor` mode we should have already tried to replace type using `replaceTypeIfNeeded`. + // However, this replacement attempt might be unsuccessful even if some possible concrete types are present. + // For example, we may have two concrete implementors present in Spring bean definitions, so we do not know + // which one to use. In such case we try to mock this type, if it is possible, or prune branch as unsatisfiable. + // + // In case of `NoImplementors` mode we should try to mock this type or prune branch as unsatisfiable. + // Mocking can be impossible here as there are no guaranties that `mockInfoGenerator` is instantiated. + KnownImplementor, + NoImplementors -> { + if (!type.isAbstractType) { + findConcreteImplementation(addr, type, typeHardConstraint) + } else { + mockInfoGenerator?.let { + return createMockedObject(addr, type, it, nullEqualityConstraint) + } + + queuedSymbolicStateUpdates += mkFalse().asHardConstraint() + null + } + } + } + + return ObjectValue(typeStorage, addr, concreteImplementation) + } + + private fun findConcreteImplementation( + addr: UtAddrExpression, + type: RefType, + typeHardConstraint: HardConstraint, + ): Concrete? { + val isMockConstraint = mkEq(typeRegistry.isMock(addr), UtFalse) + + queuedSymbolicStateUpdates += typeHardConstraint + queuedSymbolicStateUpdates += isMockConstraint.asHardConstraint() + + // If we have this$0 with UtArrayList type, we have to create such instance. + // We should create an object with typeStorage of all possible real types and concrete implementation + // Otherwise we'd have either a wrong type in the resolver, or missing method like 'preconditionCheck'. + return wrapperToClass[type]?.first()?.let { wrapper(it, addr) }?.concrete + } + + private fun createMockedObject( + addr: UtAddrExpression, + type: RefType, + mockInfoGenerator: UtMockInfoGenerator, + nullEqualityConstraint: UtBoolExpression, + ): ObjectValue { + val mockInfo = mockInfoGenerator.generate(addr) + val mockedObjectInfo = mocker.forceMock(type, mockInfoGenerator.generate(addr)) + + val mockedObject: ObjectValue = when (mockedObjectInfo) { + is NoMock -> error("Value must be mocked after the force mock") + is ExpectedMock -> mockedObjectInfo.value + is UnexpectedMock -> { + // if mock occurs, but it is unexpected due to some reasons + // (e.g. we do not have mock framework installed), + // we can only generate a test that uses null value for mocked object + queuedSymbolicStateUpdates += nullEqualityConstraint.asHardConstraint() + + mockedObjectInfo.value + } + } + + if (mockedObjectInfo is UnexpectedMock) { + return mockedObject + } + + queuedSymbolicStateUpdates += MemoryUpdate(mockInfos = persistentListOf(MockInfoEnriched(mockInfo))) + + // add typeConstraint for mocked object. It's a declared type of the object. + val typeConstraint = typeRegistry.typeConstraint(addr, mockedObject.typeStorage).all() + val isMockConstraint = typeRegistry.isMockConstraint(mockedObject.addr) + + queuedSymbolicStateUpdates += typeConstraint.asHardConstraint() + queuedSymbolicStateUpdates += isMockConstraint.asHardConstraint() + + return mockedObject + } + + private fun TraversalContext.resolveConstant(constant: Constant): SymbolicValue = + when (constant) { + is IntConstant -> constant.value.toPrimitiveValue() + is LongConstant -> constant.value.toPrimitiveValue() + is FloatConstant -> constant.value.toPrimitiveValue() + is DoubleConstant -> constant.value.toPrimitiveValue() + is StringConstant -> { + val addr = findNewAddr() + val refType = constant.type as RefType + + // We disable creation of string literals to avoid unsats because of too long lines + if (UtSettings.ignoreStringLiterals && constant.value.length > MAX_STRING_SIZE) { + // instead of it we create an unbounded symbolic variable + workaround(HACK) { + offerState(environment.state.withLabel(StateLabel.CONCRETE)) + // We don't need to mock a string constant creation + createObject(addr, refType, useConcreteType = true, mockInfoGenerator = null) + } + } else { + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(refType) + val typeConstraint = typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() + + queuedSymbolicStateUpdates += typeConstraint + + objectValue(refType, addr, StringWrapper()).also { + initStringLiteral(it, constant.value) + } + } + } + is ClassConstant -> { + val sootType = constant.toSootType() + val result = if (sootType is RefLikeType) { + typeRegistry.createClassRef(sootType.baseType, sootType.numDimensions) + } else { + error("Can't get class constant for ${constant.value}") + } + queuedSymbolicStateUpdates += result.symbolicStateUpdate + (result.symbolicResult as SymbolicSuccess).value + } + else -> error("Unsupported type: $constant") + } + + private fun TraversalContext.resolve(expr: Expr, valueType: Type = expr.type): SymbolicValue = + when (expr) { + is BinopExpr -> { + val left = resolve(expr.op1) + val right = resolve(expr.op2) + when { + left is ReferenceValue && right is ReferenceValue -> { + when (expr) { + is JEqExpr -> addrEq(left.addr, right.addr).toBoolValue() + is JNeExpr -> mkNot(addrEq(left.addr, right.addr)).toBoolValue() + else -> TODO("Unknown op $expr for $left and $right") + } + } + left is PrimitiveValue && right is PrimitiveValue -> { + // division by zero special case + if ((expr is JDivExpr || expr is JRemExpr) && left.expr.isInteger() && right.expr.isInteger()) { + divisionByZeroCheck(right) + } + + if (UtSettings.treatOverflowAsError) { + // overflow detection + if (left.expr.isInteger() && right.expr.isInteger()) { + intOverflowCheck(expr, left, right) + } + } + + doOperation(expr, left, right).toPrimitiveValue(expr.type) + } + else -> TODO("Unknown op $expr for $left and $right") + } + } + is JNegExpr -> UtNegExpression(resolve(expr.op) as PrimitiveValue).toPrimitiveValue(expr.type) + is JNewExpr -> { + val addr = findNewAddr() + val generator = UtMockInfoGenerator { mockAddr -> + UtNewInstanceMockInfo( + expr.baseType.id, + mockAddr, + environment.method.declaringClass.id + ) + } + val objectValue = createObject(addr, expr.baseType, useConcreteType = true, generator) + addConstraintsForDefaultValues(objectValue) + objectValue + } + is JNewArrayExpr -> { + val size = (resolve(expr.size) as PrimitiveValue).align() + val type = expr.type as ArrayType + negativeArraySizeCheck(size) + createNewArray(size, type, type.elementType).also { + val defaultValue = type.defaultSymValue + queuedSymbolicStateUpdates += arrayUpdateWithValue(it.addr, type, defaultValue as UtArrayExpressionBase) + } + } + is JNewMultiArrayExpr -> { + val result = environment.state.methodResult + ?: error("There is no unfolded JNewMultiArrayExpr found in the methodResult") + queuedSymbolicStateUpdates += result.symbolicStateUpdate + (result.symbolicResult as SymbolicSuccess).value + } + is JLengthExpr -> { + val operand = expr.op as? JimpleLocal ?: error("Unknown op: ${expr.op}") + when (operand.type) { + is ArrayType -> { + val arrayInstance = localVariableMemory.local(operand.variable) as ArrayValue? + ?: error("${expr.op} not found in the locals") + nullPointerExceptionCheck(arrayInstance.addr) + memory.findArrayLength(arrayInstance.addr).also { length -> + queuedSymbolicStateUpdates += Ge(length, 0).asHardConstraint() + } + } + else -> error("Unknown op: ${expr.op}") + } + } + is JCastExpr -> when (val value = resolve(expr.op, valueType)) { + is PrimitiveValue -> value.cast(expr.type) + is ObjectValue -> { + castObject(value, expr.type, expr.op) + } + is ArrayValue -> castArray(value, expr.type) + } + is JInstanceOfExpr -> when (val value = resolve(expr.op, valueType)) { + is PrimitiveValue -> error("Unexpected instanceof on primitive $value") + is ObjectValue -> objectInstanceOf(value, expr.checkType, expr.op) + is ArrayValue -> arrayInstanceOf(value, expr.checkType) + } + else -> TODO("$expr") + } + + private fun initStringLiteral(stringWrapper: ObjectValue, value: String) { + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(utStringClass.type) + + queuedSymbolicStateUpdates += objectUpdate( + stringWrapper.copy(typeStorage = typeStorage), + STRING_LENGTH, + mkInt(value.length) + ) + queuedSymbolicStateUpdates += MemoryUpdate(visitedValues = persistentListOf(stringWrapper.addr)) + + val type = CharType.v() + val arrayType = type.arrayType + val arrayValue = createNewArray(value.length.toPrimitiveValue(), arrayType, type).also { + val defaultValue = arrayType.defaultSymValue + queuedSymbolicStateUpdates += arrayUpdateWithValue(it.addr, arrayType, defaultValue as UtArrayExpressionBase) + } + queuedSymbolicStateUpdates += objectUpdate( + stringWrapper.copy(typeStorage = typeStorage), + STRING_VALUE, + arrayValue.addr + ) + val newArray = value.indices.fold(selectArrayExpressionFromMemory(arrayValue)) { array, index -> + array.store(mkInt(index), mkChar(value[index])) + } + + queuedSymbolicStateUpdates += arrayUpdateWithValue(arrayValue.addr, CharType.v().arrayType, newArray) + environment.state = updateQueued(SymbolicStateUpdate()) + queuedSymbolicStateUpdates = queuedSymbolicStateUpdates.copy(memoryUpdates = MemoryUpdate()) + } + + /** + * Return a symbolic value of the ordinal corresponding to the enum value with the given address. + */ + private fun findEnumOrdinal(type: RefType, addr: UtAddrExpression): PrimitiveValue { + val array = memory.findArray(MemoryChunkDescriptor(ENUM_ORDINAL, type, IntType.v())) + return array.select(addr).toIntValue() + } + + /** + * Initialize enum class: create symbolic values for static enum values and generate constraints + * that restrict the new instance to match one of enum values. + */ + private fun initEnum(type: RefType, addr: UtAddrExpression, ordinal: PrimitiveValue) { + val classId = type.id + var predefinedEnumValues = memory.getSymbolicEnumValues(classId) + if (predefinedEnumValues.isEmpty()) { + val (enumValuesUpdate, _) = makeConcreteUpdatesForEnum(type) + queuedSymbolicStateUpdates += enumValuesUpdate + predefinedEnumValues = enumValuesUpdate.memoryUpdates.getSymbolicEnumValues(classId) + } + + val enumValueConstraints = mkOr( + listOf(addrEq(addr, nullObjectAddr)) + predefinedEnumValues.map { + mkAnd( + addrEq(addr, it.addr), + mkEq(ordinal, findEnumOrdinal(it.type, it.addr)) + ) + } + ) + + queuedSymbolicStateUpdates += enumValueConstraints.asHardConstraint() + } + + private fun arrayInstanceOf(value: ArrayValue, checkType: Type): PrimitiveValue { + val notNullConstraint = mkNot(addrEq(value.addr, nullObjectAddr)) + + if (checkType.isJavaLangObject()) { + return UtInstanceOfExpression(notNullConstraint.asHardConstraint().asUpdate()).toBoolValue() + } + + require(checkType is ArrayType) + + val checkBaseType = checkType.baseType + + // i.e., int[][] instanceof Object[] + if (checkBaseType.isJavaLangObject()) { + return UtInstanceOfExpression(notNullConstraint.asHardConstraint().asUpdate()).toBoolValue() + } + + // Object[] instanceof int[][] + if (value.type.baseType.isJavaLangObject() && checkBaseType is PrimType) { + val updatedTypeStorage = typeResolver.constructTypeStorage(checkType, useConcreteType = false) + val typeConstraint = typeRegistry.typeConstraint(value.addr, updatedTypeStorage).isConstraint() + + val constraint = mkAnd(notNullConstraint, typeConstraint) + val memoryUpdate = arrayTypeUpdate(value.addr, checkType) + val symbolicStateUpdate = SymbolicStateUpdate( + hardConstraints = constraint.asHardConstraint(), + memoryUpdates = memoryUpdate + ) + + return UtInstanceOfExpression(symbolicStateUpdate).toBoolValue() + } + + // We must create a new typeStorage containing ALL the inheritors for checkType, + // because later we will create a negation for the typeConstraint + val updatedTypeStorage = typeResolver.constructTypeStorage(checkType, useConcreteType = false) + + val typesIntersection = updatedTypeStorage.possibleConcreteTypes.intersect(value.possibleConcreteTypes) + if (typesIntersection.isEmpty()) return UtFalse.toBoolValue() + + val typeConstraint = typeRegistry.typeConstraint(value.addr, updatedTypeStorage).isConstraint() + val constraint = mkAnd(notNullConstraint, typeConstraint) + + val arrayType = updatedTypeStorage.leastCommonType as ArrayType + val memoryUpdate = arrayTypeUpdate(value.addr, arrayType) + val symbolicStateUpdate = SymbolicStateUpdate( + hardConstraints = constraint.asHardConstraint(), + memoryUpdates = memoryUpdate + ) + + return UtInstanceOfExpression(symbolicStateUpdate).toBoolValue() + } + + private fun objectInstanceOf(value: ObjectValue, checkType: Type, op: Value): PrimitiveValue { + val notNullConstraint = mkNot(addrEq(value.addr, nullObjectAddr)) + + // the only way to get false here is for the value to be null + if (checkType.isJavaLangObject()) { + return UtInstanceOfExpression(notNullConstraint.asHardConstraint().asUpdate()).toBoolValue() + } + + if (value.type.isJavaLangObject() && checkType is ArrayType) { + val castedArray = + createArray(value.addr, checkType, useConcreteType = false, addQueuedTypeConstraints = false) + val localVariable = (op as? JimpleLocal)?.variable ?: error("Unexpected op in the instanceof expr: $op") + + val typeMemoryUpdate = arrayTypeUpdate(value.addr, castedArray.type) + val localMemoryUpdate = localMemoryUpdate(localVariable to castedArray) + + val typeConstraint = typeRegistry.typeConstraint(value.addr, castedArray.typeStorage).isConstraint() + val constraint = mkAnd(notNullConstraint, typeConstraint) + val symbolicStateUpdate = SymbolicStateUpdate( + hardConstraints = constraint.asHardConstraint(), + memoryUpdates = typeMemoryUpdate, + localMemoryUpdates = localMemoryUpdate + ) + + return UtInstanceOfExpression(symbolicStateUpdate).toBoolValue() + } + + require(checkType is RefType) + + // We must create a new typeStorage containing ALL the inheritors for checkType, + // because later we will create a negation for the typeConstraint + val updatedTypeStorage = typeResolver.constructTypeStorage(checkType, useConcreteType = false) + + // drop this branch if we don't have an appropriate type in the possibleTypes + val typesIntersection = updatedTypeStorage.possibleConcreteTypes.intersect(value.possibleConcreteTypes) + if (typesIntersection.isEmpty()) return UtFalse.toBoolValue() + + val typeConstraint = typeRegistry.typeConstraint(value.addr, updatedTypeStorage).isConstraint() + val constraint = mkAnd(notNullConstraint, typeConstraint) + + return UtInstanceOfExpression(constraint.asHardConstraint().asUpdate()).toBoolValue() + } + + private fun addConstraintsForDefaultValues(objectValue: ObjectValue) { + val type = objectValue.type + for (field in typeResolver.findFields(type)) { + // final fields must be initialized inside the body of a constructor + if (field.isFinal) continue + val chunkId = hierarchy.chunkIdForField(type, field) + val memoryChunkDescriptor = MemoryChunkDescriptor(chunkId, type, field.type) + val array = memory.findArray(memoryChunkDescriptor) + val defaultValue = if (field.type is RefLikeType) nullObjectAddr else field.type.defaultSymValue + queuedSymbolicStateUpdates += mkEq(array.select(objectValue.addr), defaultValue).asHardConstraint() + } + } + + private fun TraversalContext.castObject(objectValue: ObjectValue, typeAfterCast: Type, op: Value): SymbolicValue { + classCastExceptionCheck(objectValue, typeAfterCast) + + val currentType = objectValue.type + val nullConstraint = addrEq(objectValue.addr, nullObjectAddr) + + // If we're trying to cast type A to the same type A + if (currentType == typeAfterCast) return objectValue + + // java.lang.Object -> array + if (currentType.isJavaLangObject() && typeAfterCast is ArrayType) { + val array = createArray(objectValue.addr, typeAfterCast, useConcreteType = false) + + val localVariable = (op as? JimpleLocal)?.variable ?: error("Unexpected op in the cast: $op") + +/* + val typeConstraint = typeRegistry.typeConstraint(array.addr, array.typeStorage).isOrNullConstraint() + + queuedSymbolicStateUpdates += typeConstraint.asHardConstraint() +*/ + + queuedSymbolicStateUpdates += localMemoryUpdate(localVariable to array) + queuedSymbolicStateUpdates += arrayTypeUpdate(array.addr, array.type) + + return array + } + + val ancestors = typeResolver.findOrConstructAncestorsIncludingTypes(currentType) + // if we're trying to cast type A to it's predecessor + if (typeAfterCast in ancestors) return objectValue + + require(typeAfterCast is RefType) + + val castedObject = typeResolver.downCast(objectValue, typeAfterCast) + + // The objectValue must be null to be casted to an impossible type + if (castedObject.possibleConcreteTypes.isEmpty()) { + queuedSymbolicStateUpdates += nullConstraint.asHardConstraint() + return objectValue.copy(addr = nullObjectAddr) + } + + val typeConstraint = typeRegistry.typeConstraint(castedObject.addr, castedObject.typeStorage).isOrNullConstraint() + + // When we do downCast, we should add possible equality to null + // to avoid situation like this: + // we have class A, class B extends A, class C extends A + // void foo(a A) { (B) a; (C) a; } -> a is null + queuedSymbolicStateUpdates += typeConstraint.asHardConstraint() + queuedSymbolicStateUpdates += typeRegistry.zeroDimensionConstraint(objectValue.addr).asHardConstraint() + + // If we are casting to an enum class, we should initialize enum values and add value equality constraints + if (typeAfterCast.sootClass?.isEnum == true) { + initEnum(typeAfterCast, castedObject.addr, findEnumOrdinal(typeAfterCast, castedObject.addr)) + } + + // TODO add memory constraints JIRA:1523 + return castedObject + } + + private fun TraversalContext.castArray(arrayValue: ArrayValue, typeAfterCast: Type): ArrayValue { + classCastExceptionCheck(arrayValue, typeAfterCast) + + if (typeAfterCast.isJavaLangObject()) return arrayValue + + require(typeAfterCast is ArrayType) + + // cast A[] to A[] + if (arrayValue.type == typeAfterCast) return arrayValue + + val baseTypeBeforeCast = arrayValue.type.baseType + val baseTypeAfterCast = typeAfterCast.baseType + + val nullConstraint = addrEq(arrayValue.addr, nullObjectAddr) + + // i.e. cast Object[] -> int[][] + if (baseTypeBeforeCast.isJavaLangObject() && baseTypeAfterCast is PrimType) { + val castedArray = createArray(arrayValue.addr, typeAfterCast) + + val memoryUpdate = arrayTypeUpdate(castedArray.addr, castedArray.type) + + queuedSymbolicStateUpdates += memoryUpdate + + return castedArray + } + + // int[][] -> Object[] + if (baseTypeBeforeCast is PrimType && baseTypeAfterCast.isJavaLangObject()) return arrayValue + + require(baseTypeBeforeCast is RefType) + require(baseTypeAfterCast is RefType) + + // Integer[] -> Number[] + val ancestors = typeResolver.findOrConstructAncestorsIncludingTypes(baseTypeBeforeCast) + if (baseTypeAfterCast in ancestors) return arrayValue + + val castedArray = typeResolver.downCast(arrayValue, typeAfterCast) + + // cast to an unreachable type + if (castedArray.possibleConcreteTypes.isEmpty()) { + queuedSymbolicStateUpdates += nullConstraint.asHardConstraint() + return arrayValue.copy(addr = nullObjectAddr) + } + + val typeConstraint = typeRegistry.typeConstraint(castedArray.addr, castedArray.typeStorage).isOrNullConstraint() + val memoryUpdate = arrayTypeUpdate(castedArray.addr, castedArray.type) + + queuedSymbolicStateUpdates += typeConstraint.asHardConstraint() + queuedSymbolicStateUpdates += memoryUpdate + + return castedArray + } + + /** + * @param size [SymbolicValue] representing size of an array. It's caller responsibility to handle negative + * size. + */ + internal fun createNewArray(size: PrimitiveValue, type: ArrayType, elementType: Type): ArrayValue { + val addr = findNewAddr() + val length = memory.findArrayLength(addr) + + queuedSymbolicStateUpdates += Eq(length, size).asHardConstraint() + queuedSymbolicStateUpdates += Ge(length, 0).asHardConstraint() + workaround(HACK) { + if (size.expr is UtBvLiteral) { + val sizeValue = size.expr.value.toInt() + softMaxArraySize = min(hardMaxArraySize, max(sizeValue, softMaxArraySize)) + + if (sizeValue > hardMaxArraySize) { + logger.warn( + "The engine encountered an array initialization with $sizeValue size." + + " It leads to elimination of paths containing current instruction." + ) + logger.warn("Current instruction: ${environment.state.stmt}") + logger.warn("Please, consider increasing `UtSettings.maxArraySize` value, " + + "currently it is ${UtSettings.maxArraySize}." + ) + } + } + } + queuedSymbolicStateUpdates += Le(length, softMaxArraySize).asHardConstraint() // TODO: fix big array length + + if (preferredCexOption) { + queuedSymbolicStateUpdates += Le(length, PREFERRED_ARRAY_SIZE).asSoftConstraint() + } + val chunkId = typeRegistry.arrayChunkId(type) + touchMemoryChunk(MemoryChunkDescriptor(chunkId, type, elementType)) + + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(type) + return ArrayValue(typeStorage, addr).also { + queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, it.typeStorage).all().asHardConstraint() + } + } + + // Type is needed for null values: we should know, which null do we require. + // If valueType is NullType, return typelessNullObject. It can happen in a situation, + // where we cannot find the type, for example in condition (null == null) + private fun TraversalContext.resolve( + value: Value, + valueType: Type = value.type + ): SymbolicValue = when (value) { + is JimpleLocal -> localVariableMemory.local(value.variable) ?: error("${value.name} not found in the locals") + is Constant -> if (value is NullConstant) typeResolver.nullObject(valueType) else resolveConstant(value) + is Expr -> resolve(value, valueType) + is JInstanceFieldRef -> { + val instance = (resolve(value.base) as ObjectValue) + recordInstanceFieldRead(instance.addr, value.field) + nullPointerExceptionCheck(instance.addr) + + val objectType = if (instance.concrete?.value is BaseOverriddenWrapper) { + instance.concrete.value.overriddenClass.type + } else { + value.field.declaringClass.type as RefType + } + val generator = (value.field.type as? RefType)?.let { refType -> + UtMockInfoGenerator { mockAddr -> + val fieldId = FieldId(objectType.id, value.field.name) + UtFieldMockInfo(refType.id, mockAddr, fieldId, instance.addr) + } + } + createFieldOrMock(objectType, instance.addr, value.field, generator).also { fieldValue -> + preferredCexInstanceCache[instance]?.let { usedCache -> + if (usedCache.add(value.field)) { + applyPreferredConstraints(fieldValue) + } + } + } + } + is JArrayRef -> { + val arrayInstance = resolve(value.base) as ArrayValue + nullPointerExceptionCheck(arrayInstance.addr) + + val index = (resolve(value.index) as PrimitiveValue).align() + val length = memory.findArrayLength(arrayInstance.addr) + indexOutOfBoundsChecks(index, length) + + val type = arrayInstance.type + val elementType = type.elementType + val chunkId = typeRegistry.arrayChunkId(type) + val descriptor = MemoryChunkDescriptor(chunkId, type, elementType).also { touchMemoryChunk(it) } + val array = memory.findArray(descriptor) + + when (elementType) { + is RefType -> { + val generator = UtMockInfoGenerator { mockAddr -> UtObjectMockInfo(elementType.id, mockAddr) } + + val objectValue = createObject( + UtAddrExpression(array.select(arrayInstance.addr, index.expr)), + elementType, + useConcreteType = false, + generator + ) + + if (objectValue.type.isJavaLangObject()) { + queuedSymbolicStateUpdates += typeRegistry.zeroDimensionConstraint(objectValue.addr).asSoftConstraint() + } + + objectValue + } + is ArrayType -> createArray( + UtAddrExpression(array.select(arrayInstance.addr, index.expr)), + elementType, + useConcreteType = false + ) + else -> PrimitiveValue(elementType, array.select(arrayInstance.addr, index.expr)) + } + } + is StaticFieldRef -> readStaticField(value) + else -> error("${value::class} is not supported") + }.also { + with(simplificator) { simplifySymbolicValue(it) } + } + + private fun TraversalContext.readStaticField(fieldRef: StaticFieldRef): SymbolicValue { + val field = fieldRef.field + val declaringClassType = field.declaringClass.type + val staticObject = resolveInstanceForField(fieldRef) + + val generator = (field.type as? RefType)?.let { refType -> + UtMockInfoGenerator { mockAddr -> + val fieldId = FieldId(declaringClassType.id, field.name) + UtFieldMockInfo(refType.id, mockAddr, fieldId, ownerAddr = null) + } + } + val createdField = createFieldOrMock(declaringClassType, staticObject.addr, field, generator).also { value -> + preferredCexInstanceCache.entries + .firstOrNull { declaringClassType == it.key.type }?.let { + if (it.value.add(field)) { + applyPreferredConstraints(value) + } + } + } + + val fieldId = field.fieldId + val staticFieldMemoryUpdate = StaticFieldMemoryUpdateInfo(fieldId, createdField) + val touchedStaticFields = persistentListOf(staticFieldMemoryUpdate) + queuedSymbolicStateUpdates += MemoryUpdate(staticFieldsUpdates = touchedStaticFields) + + if (!environment.method.isStaticInitializer && isStaticFieldMeaningful(field)) { + queuedSymbolicStateUpdates += MemoryUpdate(meaningfulStaticFields = persistentSetOf(fieldId)) + } + + return createdField + } + + /** + * For now the field is `meaningful` if it is safe to set, that is, it is not an internal system field nor a + * synthetic field or a wrapper's field. This filter is needed to prohibit changing internal fields, which can break up our own + * code and which are useless for the user. + * + * @return `true` if the field is meaningful, `false` otherwise. + */ + private fun isStaticFieldMeaningful(field: SootField) = + !Modifier.isSynthetic(field.modifiers) && + // we don't want to set fields that cannot be set via reflection anyway + !field.fieldId.isInaccessibleViaReflection && + // we should not set static fields from wrappers + !field.declaringClass.isOverridden && + // we should not manually set enum constants + !(field.declaringClass.isEnum && field.isEnumConstant) && + // we don't want to set fields from library classes + !workaround(IGNORE_STATICS_FROM_TRUSTED_LIBRARIES) { + ignoreStaticsFromTrustedLibraries && field.declaringClass.isFromTrustedLibrary() + } + + /** + * Locates object represents static fields of particular class. + * + * If object does not exist in the memory, returns null. + */ + fun locateStaticObject(classType: RefType): ObjectValue? = memory.findStaticInstanceOrNull(classType.id) + + /** + * Locates object represents static fields of particular class. + * + * If object is not exist in memory, creates a new one and put it into memory updates. + */ + private fun findOrCreateStaticObject( + classType: RefType, + mockInfoGenerator: UtMockInfoGenerator? + ): ObjectValue { + val fromMemory = locateStaticObject(classType) + + // true if the object exists in the memory and he already has concrete value or mockInfoGenerator is null + // It's important to avoid situations when we've already created object earlier without mock, and now + // we want to mock this object + if (fromMemory != null && (fromMemory.concrete != null || mockInfoGenerator == null)) { + return fromMemory + } + val addr = fromMemory?.addr ?: findNewAddr() + val created = createObject(addr, classType, useConcreteType = false, mockInfoGenerator) + queuedSymbolicStateUpdates += MemoryUpdate(staticInstanceStorage = persistentHashMapOf(classType.id to created)) + return created + } + + fun TraversalContext.resolveParameters(parameters: List, types: List) = + parameters.zip(types).map { (value, type) -> resolve(value, type) } + + private fun applyPreferredConstraints(value: SymbolicValue) { + when (value) { + is PrimitiveValue, is ArrayValue -> queuedSymbolicStateUpdates += preferredConstraints(value).asSoftConstraint() + is ObjectValue -> preferredCexInstanceCache.putIfAbsent(value, mutableSetOf()) + } + } + + private fun preferredConstraints(variable: SymbolicValue): List = + when (variable) { + is PrimitiveValue -> + when (variable.type) { + is ByteType, is ShortType, is IntType, is LongType -> { + listOf(Ge(variable, MIN_PREFERRED_INTEGER), Le(variable, MAX_PREFERRED_INTEGER)) + } + is CharType -> { + listOf(Ge(variable, MIN_PREFERRED_CHARACTER), Le(variable, MAX_PREFERRED_CHARACTER)) + } + else -> emptyList() + } + is ArrayValue -> { + val type = variable.type + val elementType = type.elementType + val constraints = mutableListOf() + val array = memory.findArray( + MemoryChunkDescriptor( + typeRegistry.arrayChunkId(variable.type), + variable.type, + elementType + ) + ) + constraints += Le(memory.findArrayLength(variable.addr), PREFERRED_ARRAY_SIZE) + for (i in 0 until softMaxArraySize) { + constraints += preferredConstraints( + array.select(variable.addr, mkInt(i)).toPrimitiveValue(elementType) + ) + } + constraints + } + is ObjectValue -> error("Unsupported type of $variable for preferredConstraints option") + } + + private fun createField( + objectType: RefType, + addr: UtAddrExpression, + fieldType: Type, + chunkId: ChunkId, + mockInfoGenerator: UtMockInfoGenerator? + ): SymbolicValue { + val descriptor = MemoryChunkDescriptor(chunkId, objectType, fieldType) + val array = memory.findArray(descriptor) + val value = array.select(addr) + touchMemoryChunk(descriptor) + return when (fieldType) { + is RefType -> createObject( + UtAddrExpression(value), + fieldType, + useConcreteType = false, + mockInfoGenerator + ) + is ArrayType -> createArray(UtAddrExpression(value), fieldType, useConcreteType = false) + else -> PrimitiveValue(fieldType, value) + } + } + + /** + * Creates field that can be mock. Mock strategy to decide. + */ + fun createFieldOrMock( + objectType: RefType, + addr: UtAddrExpression, + field: SootField, + mockInfoGenerator: UtMockInfoGenerator? + ): SymbolicValue { + memory.fieldValue(field, addr)?.let { + return it + } + + val chunkId = hierarchy.chunkIdForField(objectType, field) + val createdField = createField(objectType, addr, field.type, chunkId, mockInfoGenerator) + + if (field.type is RefLikeType) { + if (field.shouldBeNotNull()) { + queuedSymbolicStateUpdates += mkNot(mkEq(createdField.addr, nullObjectAddr)).asHardConstraint() + } + + // See docs/SpeculativeFieldNonNullability.md for details + checkAndMarkLibraryFieldSpeculativelyNotNull(field, createdField) + } + + updateGenericInfoForField(createdField, field) + + return createdField + } + + /** + * Updates generic info for provided [field] and [createdField] using + * type information. If [createdField] is not a reference value or + * if field's type is not a parameterized one, nothing will happen. + */ + private fun updateGenericInfoForField(createdField: SymbolicValue, field: SootField) { + runCatching { + if (createdField !is ReferenceValue) return + + // We must have `runCatching` here since might be a situation when we do not have + // such declaring class in a classpath, that might (but should not) lead to an exception + val classId = field.declaringClass.id + val requiredField = classId.findFieldByIdOrNull(field.fieldId) + val genericInfo = requiredField?.genericType as? ParameterizedType ?: return + + updateGenericTypeInfo(genericInfo, createdField) + } + } + + /** + * Marks the [createdField] as speculatively not null if the [field] is considering + * as not producing [NullPointerException]. + * + * See more detailed documentation in [ApplicationContext] mentioned methods. + */ + private fun checkAndMarkLibraryFieldSpeculativelyNotNull(field: SootField, createdField: SymbolicValue) { + if (nonNullSpeculator.speculativelyCannotProduceNullPointerException(field, methodUnderTest.classId)) + markAsSpeculativelyNotNull(createdField.addr) + } + + private fun createArray(pName: String, type: ArrayType): ArrayValue { + val addr = UtAddrExpression(mkBVConst(pName, UtIntSort)) + return createArray(addr, type, useConcreteType = false) + } + + /** + * Creates an array with given [addr] and [type]. + * + * [addQueuedTypeConstraints] is used to indicate whether we want to create array and work with its information + * by ourselves (i.e. in the instanceof) or to create an array and add type information + * into the [queuedSymbolicStateUpdates] right here. + */ + internal fun createArray( + addr: UtAddrExpression, type: ArrayType, + @Suppress("SameParameterValue") useConcreteType: Boolean = false, + addQueuedTypeConstraints: Boolean = true + ): ArrayValue { + touchAddress(addr) + + val length = memory.findArrayLength(addr) + + queuedSymbolicStateUpdates += Ge(length, 0).asHardConstraint() + queuedSymbolicStateUpdates += Le(length, softMaxArraySize).asHardConstraint() // TODO: fix big array length + + if (preferredCexOption) { + queuedSymbolicStateUpdates += Le(length, PREFERRED_ARRAY_SIZE).asSoftConstraint() + if (type.elementType is RefType) { + val descriptor = MemoryChunkDescriptor(typeRegistry.arrayChunkId(type), type, type.elementType) + val array = memory.findArray(descriptor) + queuedSymbolicStateUpdates += (0 until softMaxArraySize).flatMap { + val innerAddr = UtAddrExpression(array.select(addr, mkInt(it))) + mutableListOf().apply { + add(addrEq(innerAddr, nullObjectAddr)) + + // if we have an array of Object, assume that all of them have zero number of dimensions + if (type.elementType.isJavaLangObject()) { + add(typeRegistry.zeroDimensionConstraint(UtAddrExpression(innerAddr))) + } + } + }.asSoftConstraint() + } + + } + val typeStorage = typeResolver.constructTypeStorage(type, useConcreteType) + + if (addQueuedTypeConstraints) { + queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() + } + + touchMemoryChunk(MemoryChunkDescriptor(typeRegistry.arrayChunkId(type), type, type.elementType)) + return ArrayValue(typeStorage, addr) + } + + /** + * RefType and ArrayType consts have addresses less or equals to NULL_ADDR in order to separate objects + * created inside our program and given from outside. All given objects have negative addr or equal to NULL_ADDR. + * Since createConst called only for objects from outside at the beginning of the analysis, + * we can set Le(addr, NULL_ADDR) for all RefValue objects. + */ + private fun Value.createConst(pName: String, mockInfoGenerator: UtMockInfoGenerator?): SymbolicValue = + createConst(type, pName, mockInfoGenerator) + + fun createConst(type: Type, pName: String, mockInfoGenerator: UtMockInfoGenerator?): SymbolicValue = + when (type) { + is ByteType -> mkBVConst(pName, UtByteSort).toByteValue() + is ShortType -> mkBVConst(pName, UtShortSort).toShortValue() + is IntType -> mkBVConst(pName, UtIntSort).toIntValue() + is LongType -> mkBVConst(pName, UtLongSort).toLongValue() + is FloatType -> mkFpConst(pName, Float.SIZE_BITS).toFloatValue() + is DoubleType -> mkFpConst(pName, Double.SIZE_BITS).toDoubleValue() + is BooleanType -> mkBoolConst(pName).toBoolValue() + is CharType -> mkBVConst(pName, UtCharSort).toCharValue() + is ArrayType -> createArray(pName, type).also { + val addr = it.addr.toIntValue() + queuedSymbolicStateUpdates += Le(addr, nullObjectAddr.toIntValue()).asHardConstraint() + // if we don't 'touch' this array during the execution, it should be null + queuedSymbolicStateUpdates += addrEq(it.addr, nullObjectAddr).asSoftConstraint() + } + is RefType -> { + val addr = UtAddrExpression(mkBVConst(pName, UtIntSort)) + queuedSymbolicStateUpdates += Le(addr.toIntValue(), nullObjectAddr.toIntValue()).asHardConstraint() + // if we don't 'touch' this object during the execution, it should be null + queuedSymbolicStateUpdates += addrEq(addr, nullObjectAddr).asSoftConstraint() + + if (type.sootClass.isEnum) { + // We don't know which enum constant should we create, so we + // have to create an instance of unknown type to support virtual invokes. + createEnum(type, addr, useConcreteType = false) + } else { + createObject(addr, type, useConcreteType = false, mockInfoGenerator) + } + } + is VoidType -> voidValue + else -> error("Can't create const from ${type::class}") + } + + private fun createEnum(type: RefType, addr: UtAddrExpression, useConcreteType: Boolean): ObjectValue { + val typeStorage = typeResolver.constructTypeStorage(type, useConcreteType) + + queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() + + val ordinal = findEnumOrdinal(type, addr) + val enumSize = classLoader.loadClass(type.sootClass.name).enumConstants.size + + queuedSymbolicStateUpdates += mkOr(Ge(ordinal, 0), addrEq(addr, nullObjectAddr)).asHardConstraint() + queuedSymbolicStateUpdates += mkOr(Lt(ordinal, enumSize), addrEq(addr, nullObjectAddr)).asHardConstraint() + + initEnum(type, addr, ordinal) + touchAddress(addr) + + return ObjectValue(typeStorage, addr) + } + + @Suppress("SameParameterValue") + private fun arrayUpdate(array: ArrayValue, index: PrimitiveValue, value: UtExpression): MemoryUpdate { + val type = array.type + val chunkId = typeRegistry.arrayChunkId(type) + val descriptor = MemoryChunkDescriptor(chunkId, type, type.elementType) + + val updatedNestedArray = memory.findArray(descriptor) + .select(array.addr) + .store(index.expr, value) + + return MemoryUpdate(persistentListOf(namedStore(descriptor, array.addr, updatedNestedArray))) + } + + fun objectUpdate( + instance: ObjectValue, + field: SootField, + value: UtExpression + ): MemoryUpdate { + val chunkId = hierarchy.chunkIdForField(instance.type, field) + val descriptor = MemoryChunkDescriptor(chunkId, instance.type, field.type) + return MemoryUpdate(persistentListOf(namedStore(descriptor, instance.addr, value))) + } + + /** + * Creates a [MemoryUpdate] with [MemoryUpdate.fieldValues] containing [fieldValue] associated with the non-staitc [field] + * of the object instance with the specified [instanceAddr]. + */ + private fun fieldUpdate( + field: SootField, + instanceAddr: UtAddrExpression, + fieldValue: SymbolicValue + ): MemoryUpdate { + val fieldValuesUpdate = persistentHashMapOf(field to persistentHashMapOf(instanceAddr to fieldValue)) + + return MemoryUpdate(fieldValues = fieldValuesUpdate) + } + + fun arrayUpdateWithValue( + addr: UtAddrExpression, + type: ArrayType, + newValue: UtExpression + ): MemoryUpdate { + require(newValue.sort is UtArraySort) { "Expected UtArraySort, but ${newValue.sort} was found" } + + val chunkId = typeRegistry.arrayChunkId(type) + val descriptor = MemoryChunkDescriptor(chunkId, type, type.elementType) + + return MemoryUpdate(persistentListOf(namedStore(descriptor, addr, newValue))) + } + + fun selectArrayExpressionFromMemory( + array: ArrayValue + ): UtExpression { + val addr = array.addr + val arrayType = array.type + val chunkId = typeRegistry.arrayChunkId(arrayType) + val descriptor = MemoryChunkDescriptor(chunkId, arrayType, arrayType.elementType) + return memory.findArray(descriptor).select(addr) + } + + private fun touchMemoryChunk(chunkDescriptor: MemoryChunkDescriptor) { + queuedSymbolicStateUpdates += MemoryUpdate(touchedChunkDescriptors = persistentSetOf(chunkDescriptor)) + } + + private fun touchAddress(addr: UtAddrExpression) { + queuedSymbolicStateUpdates += MemoryUpdate(touchedAddresses = persistentListOf(addr)) + } + + private fun markAsSpeculativelyNotNull(addr: UtAddrExpression) { + queuedSymbolicStateUpdates += MemoryUpdate(speculativelyNotNullAddresses = persistentListOf(addr)) + } + + /** + * Add a memory update to reflect that a field was read. + * + * If the field belongs to a substitute object, record the read access for the real type instead. + */ + private fun recordInstanceFieldRead(addr: UtAddrExpression, field: SootField) { + val realType = typeRegistry.findRealType(field.declaringClass.type) + if (realType is RefType) { + val readOperation = InstanceFieldReadOperation(addr, FieldId(realType.id, field.name)) + queuedSymbolicStateUpdates += MemoryUpdate(instanceFieldReads = persistentSetOf(readOperation)) + } + } + + private fun TraversalContext.traverseException(current: Stmt, exception: SymbolicFailure) { + if (!traverseCatchBlock(current, exception, emptySet())) { + processResult(exception) + } + } + + /** + * Finds appropriate catch block and adds it as next state to path selector. + * + * Returns true if found, false otherwise. + */ + private fun TraversalContext.traverseCatchBlock( + current: Stmt, + exception: SymbolicFailure, + conditions: Set + ): Boolean { + if (exception.concrete is ArtificialError) { + return false + } + + val classId = exception.fold( + { it.javaClass.id }, + { (exception.symbolic as ObjectValue).type.id } + ) + val edge = findCatchBlock(current, classId) ?: return false + + offerState( + updateQueued( + edge, + SymbolicStateUpdate( + hardConstraints = conditions.asHardConstraint(), + localMemoryUpdates = localMemoryUpdate(CAUGHT_EXCEPTION to exception.symbolic) + ) + ) + ) + return true + } + + private fun findCatchBlock(current: Stmt, classId: ClassId): Edge? { + val stmtToEdge = globalGraph.exceptionalSuccs(current).associateBy { it.dst } + return globalGraph.traps.asSequence().mapNotNull { (stmt, exceptionClass) -> + stmtToEdge[stmt]?.let { it to exceptionClass } + }.firstOrNull { it.second in hierarchy.ancestors(classId) }?.first + } + + private fun TraversalContext.invokeResult(invokeExpr: InvokeExpr): List = + environment.state.methodResult?.let { methodResult -> + val taintUpdate = if (UtSettings.useTaintAnalysis) { + processTaintAnalysis(invokeExpr, methodResult) + } else { + SymbolicStateUpdate() + } + + listOf( + methodResult.copy(symbolicStateUpdate = methodResult.symbolicStateUpdate + taintUpdate) + ) + } ?: when (invokeExpr) { + is JStaticInvokeExpr -> staticInvoke(invokeExpr) + is JInterfaceInvokeExpr -> virtualAndInterfaceInvoke(invokeExpr.base, invokeExpr.methodRef, invokeExpr.args) + is JVirtualInvokeExpr -> virtualAndInterfaceInvoke(invokeExpr.base, invokeExpr.methodRef, invokeExpr.args) + is JSpecialInvokeExpr -> specialInvoke(invokeExpr) + is JDynamicInvokeExpr -> dynamicInvoke(invokeExpr) + else -> error("Unknown class ${invokeExpr::class}") + } + + /** + * Returns a [MethodResult] containing a mock for a static method call + * of the [method] if it should be mocked, null otherwise. + * + * @see Mocker.shouldMock + * @see UtStaticMethodMockInfo + */ + private fun mockStaticMethod(method: SootMethod, args: List): List? { + val methodId = method.executableId as MethodId + val declaringClassType = method.declaringClass.type + + val generator = UtMockInfoGenerator { addr -> UtStaticObjectMockInfo(declaringClassType.classId, addr) } + // It is important to save the previous state of the queuedMemoryUpdates, because `findOrCreateStaticObject` + // will change it. If we should not mock the object, we must `reset` memory updates to the previous state. + val prevMemoryUpdate = queuedSymbolicStateUpdates.memoryUpdates + val static = findOrCreateStaticObject(declaringClassType, generator) + + val mockInfo = UtStaticMethodMockInfo(static.addr, methodId) + + // We don't want to mock synthetic, private and protected methods + val isUnwantedToMockMethod = method.isSynthetic || method.isPrivate || method.isProtected + val shouldMock = mocker.shouldMock(declaringClassType, mockInfo) + val privateAndProtectedMethodsInArgs = parametersContainPrivateAndProtectedTypes(method) + + if (!shouldMock || method.isStaticInitializer) { + queuedSymbolicStateUpdates = queuedSymbolicStateUpdates.copy(memoryUpdates = prevMemoryUpdate) + return null + } + + // TODO temporary we return unbounded symbolic variable with a wrong name. + // TODO Probably it'll lead us to the path divergence + workaround(HACK) { + if (isUnwantedToMockMethod || privateAndProtectedMethodsInArgs) { + queuedSymbolicStateUpdates = queuedSymbolicStateUpdates.copy(memoryUpdates = prevMemoryUpdate) + return listOf(unboundedVariable(name = "staticMethod", method)) + } + } + + return static.asWrapperOrNull?.run { + invoke(static, method, args).map { it as MethodResult } + } + } + + private fun parametersContainPrivateAndProtectedTypes(method: SootMethod) = + method.parameterTypes.any { paramType -> + (paramType.baseType as? RefType)?.let { + it.sootClass.isPrivate || it.sootClass.isProtected + } == true + } + + /** + * Returns [MethodResult] with a mock for [org.utbot.api.mock.UtMock.makeSymbolic] call, + * if the [invokeExpr] contains it, null otherwise. + * + * @see mockStaticMethod + */ + private fun TraversalContext.mockMakeSymbolic(invokeExpr: JStaticInvokeExpr): List? { + val methodSignature = invokeExpr.method.signature + if (methodSignature != makeSymbolicMethod.signature && methodSignature != nonNullableMakeSymbolic.signature) return null + + val method = environment.method + val declaringClass = method.declaringClass + val isInternalMock = method.hasInternalMockAnnotation || declaringClass.allMethodsAreInternalMocks || declaringClass.isOverridden + val parameters = resolveParameters(invokeExpr.args, invokeExpr.method.parameterTypes) + val mockMethodResult = mockStaticMethod(invokeExpr.method, parameters)?.single() + ?: error("Unsuccessful mock attempt of the `makeSymbolic` method call: $invokeExpr") + val mockResult = mockMethodResult.symbolicResult as SymbolicSuccess + val mockValue = mockResult.value + + // the last parameter of the makeSymbolic is responsible for nullability + val isNullable = if (parameters.isEmpty()) UtFalse else UtCastExpression( + parameters.last() as PrimitiveValue, + BooleanType.v() + ) + + // isNullable || mockValue != null + val additionalConstraint = mkOr( + mkEq(isNullable, UtTrue), + mkNot(mkEq(mockValue.addr, nullObjectAddr)), + ) + + // since makeSymbolic returns Object and casts it during the next instruction, we should + // disable ClassCastException for it to avoid redundant ClassCastException + typeRegistry.disableCastClassExceptionCheck(mockValue.addr) + + return listOf( + MethodResult( + mockValue, + hardConstraints = additionalConstraint.asHardConstraint(), + memoryUpdates = if (isInternalMock) MemoryUpdate() else mockMethodResult.symbolicStateUpdate.memoryUpdates + ) + ) + } + + private fun TraversalContext.staticInvoke(invokeExpr: JStaticInvokeExpr): List { + val parameters = resolveParameters(invokeExpr.args, invokeExpr.method.parameterTypes) + + if (UtSettings.useTaintAnalysis) { + processTaintSink(SymbolicMethodData(invokeExpr.method.executableId, base = null, args = parameters, result = null)) + } + + val result = mockMakeSymbolic(invokeExpr) ?: mockStaticMethod(invokeExpr.method, parameters) + + if (result != null) return result + + val method = invokeExpr.retrieveMethod() + val invocation = Invocation(null, method, parameters, InvocationTarget(null, method)) + return commonInvokePart(invocation) + } + + /** + * Identifies different invocation targets by finding all overloads of invoked method. + * Each target defines/reduces object type to set of concrete (not abstract, not interface) + * classes with particular method implementation. + */ + private fun TraversalContext.virtualAndInterfaceInvoke( + base: Value, + methodRef: SootMethodRef, + parameters: List + ): List { + val instance = resolve(base) + if (instance !is ReferenceValue) error("We cannot run $methodRef on $instance") + + nullPointerExceptionCheck(instance.addr) + + if (instance.isNullObject()) return emptyList() // Nothing to call + + val method = methodRef.resolve() + val resolvedParameters = resolveParameters(parameters, method.parameterTypes) + + if (UtSettings.useTaintAnalysis) { + processTaintSink(SymbolicMethodData(method.executableId, instance, resolvedParameters, result = null)) + } + + val invocation = Invocation(instance, method, resolvedParameters) { + when (instance) { + is ObjectValue -> findInvocationTargets(instance, methodRef.subSignature.string) + is ArrayValue -> listOf(InvocationTarget(instance, method)) + } + } + return commonInvokePart(invocation) + } + + /** + * Returns invocation targets for particular method implementation. + * + * Note: for some well known classes returns hardcoded choices. + */ + private fun findInvocationTargets( + instance: ObjectValue, + methodSubSignature: String + ): List { + val visitor = solver.simplificator.axiomInstantiationVisitor + val simplifiedAddr = instance.addr.accept(visitor) + + // UtIsExpression for object with address the same as instance.addr + // If there are several such constraints, take the one with the least number of possible types + val instanceOfConstraint = solver.assertions + .filter { it is UtIsExpression && it.addr == simplifiedAddr } + .takeIf { it.isNotEmpty() } + ?.minBy { (it as UtIsExpression).typeStorage.possibleConcreteTypes.size } as? UtIsExpression + + // if we have UtIsExpression constraint for [instance], then find invocation targets + // for possibleTypes from this constraints, instead of the type maintained by solver. + + // While using simplifications with RewritingVisitor, assertions can maintain types + // for objects (especially objects with type equals to type parameter of generic) + // better than engine. + val types = instanceOfConstraint + ?.typeStorage + ?.possibleConcreteTypes + // we should take this constraint into consideration only if it has less + // possible types than our current object, otherwise, it doesn't add + // any helpful information + ?.takeIf { it.size < instance.possibleConcreteTypes.size } + ?: instance.possibleConcreteTypes + + val allPossibleConcreteTypes = typeResolver + .constructTypeStorage(instance.type, useConcreteType = false) + .possibleConcreteTypes + + val methodInvocationTargets = findLibraryTargets(instance.type, methodSubSignature)?.takeIf { + // we have no specified types, so we can take only library targets (if present) for optimization purposes + types.size == allPossibleConcreteTypes.size + } ?: findMethodInvocationTargets(types, methodSubSignature) + + return methodInvocationTargets + .map { (method, implementationClass, possibleTypes) -> + val typeStorage = typeResolver.constructTypeStorage(implementationClass, possibleTypes) + val mockInfo = memory.mockInfoByAddr(instance.addr) + val mockedObjectInfo = mockInfo?.let { + // TODO rewrite to fix JIRA:1611 + val type = Scene.v().getSootClass(mockInfo.classId.name).type + val ancestorTypes = typeResolver.findOrConstructAncestorsIncludingTypes(type) + val updatedMockInfo = if (implementationClass in ancestorTypes) { + it + } else { + it.copyWithClassId(classId = implementationClass.id) + } + + mocker.mock(implementationClass, updatedMockInfo) + } ?: NoMock + + when (mockedObjectInfo) { + is NoMock -> { + // Above we might get implementationClass that has to be substituted. + // For example, for a call "Collection.size()" such classes will be produced. + val wrapperOrInstance = wrapper(implementationClass, instance.addr) + ?: instance.copy(typeStorage = typeStorage) + + val typeConstraint = typeRegistry.typeConstraint(instance.addr, wrapperOrInstance.typeStorage) + val constraints = setOf(typeConstraint.isOrNullConstraint()) + + // TODO add memory updated for types JIRA:1523 + InvocationTarget(wrapperOrInstance, method, constraints) + } + is ExpectedMock -> { + val mockedObject = mockedObjectInfo.value + val typeConstraint = typeRegistry.typeConstraint(mockedObject.addr, mockedObject.typeStorage) + val constraints = setOf(typeConstraint.isOrNullConstraint()) + + // TODO add memory updated for types JIRA:1523 + // TODO isMock???? + InvocationTarget(mockedObject, method, constraints) + } + /* + Currently, it is unclear how this could happen. + Perhaps, the answer is somewhere in the following situation: + you have an interface with an abstract method `foo`, and it has an abstract inheritor with the implementation of the method, + but this inheritor doesn't have any concrete inheritors. It looks like in this case we would mock this instance + (because it doesn't have any possible concrete type), but it is impossible since either this class cannot present + in possible types of the object on which we call `foo` (since they contain only concrete types), + or this class would be already mocked (since it doesn't contain any concrete implementors). + */ + is UnexpectedMock -> unreachableBranch("If it ever happens, it should be investigated") + } + } + } + + private fun findLibraryTargets(type: RefType, methodSubSignature: String): List? { + val libraryTargets = libraryTargets[type.className] ?: return null + return libraryTargets.mapNotNull { className -> + val implementationClass = Scene.v().getSootClass(className) + val method = implementationClass.findMethodOrNull(methodSubSignature) + method?.let { + MethodInvocationTarget(method, implementationClass.type, listOf(implementationClass.type)) + } + } + } + + /** + * Returns sorted list of particular method implementations (invocation targets). + */ + private fun findMethodInvocationTargets( + concretePossibleTypes: Set, + methodSubSignature: String + ): List { + val implementationToClasses = concretePossibleTypes + .filterIsInstance() + .groupBy { it.sootClass.findMethodOrNull(methodSubSignature)?.declaringClass } + .filterValues { it.appropriateClasses().isNotEmpty() } + + val targets = mutableListOf() + for ((sootClass, types) in implementationToClasses) { + if (sootClass != null) { + targets += MethodInvocationTarget(sootClass.getMethod(methodSubSignature), sootClass.type, types) + } + } + + // do some hopeless sorting + return targets + .asSequence() + .sortedByDescending { typeRegistry.findRating(it.implementationClass) } + .take(10) + .sortedByDescending { it.possibleTypes.size } + .sortedBy { it.method.isNative } + .take(5) + .sortedByDescending { typeRegistry.findRating(it.implementationClass) } + .toList() + } + + private fun TraversalContext.specialInvoke(invokeExpr: JSpecialInvokeExpr): List { + if (UtSettings.exploreThrowableDepth == ExploreThrowableDepth.SKIP_INIT_STATEMENT) { + val throwableInheritors = typeResolver.findOrConstructInheritorsIncludingTypes(THROWABLE_TYPE) + if (invokeExpr.method.isConstructor && invokeExpr.method.declaringClass.type in throwableInheritors) { + // skip an exception's init + globalGraph.visitEdge(environment.state.lastEdge!!) + globalGraph.visitNode(environment.state) + offerState(updateQueued(globalGraph.succ(environment.state.stmt))) + return emptyList() + } + } + + val instance = resolve(invokeExpr.base) + if (instance !is ReferenceValue) error("We cannot run ${invokeExpr.methodRef} on $instance") + + nullPointerExceptionCheck(instance.addr) + + if (instance.isNullObject()) return emptyList() // Nothing to call + + val method = invokeExpr.retrieveMethod() + val parameters = resolveParameters(invokeExpr.args, method.parameterTypes) + + if (UtSettings.useTaintAnalysis) { + processTaintSink(SymbolicMethodData(method.executableId, instance, parameters, result = null)) + } + + val invocation = Invocation(instance, method, parameters, InvocationTarget(instance, method)) + + // Calls with super syntax are represented by invokeSpecial instruction, but we don't support them in wrappers + // TODO: https://github.com/UnitTestBot/UTBotJava/issues/819 -- Support super calls in inherited wrappers + return commonInvokePart(invocation) + } + + private fun TraversalContext.dynamicInvoke(invokeExpr: JDynamicInvokeExpr): List { + val invocation = with(dynamicInvokeResolver) { resolveDynamicInvoke(invokeExpr) } + + if (invocation == null) { + workaround(HACK) { + logger.warn { "Marking state as a concrete, because of an unknown dynamic invoke instruction: $invokeExpr" } + // The engine does not yet support JDynamicInvokeExpr, so switch to concrete execution if we encounter it + offerState(environment.state.withLabel(StateLabel.CONCRETE)) + queuedSymbolicStateUpdates += UtFalse.asHardConstraint() + return emptyList() + } + } + + return commonInvokePart(invocation) + } + + /** + * Runs common invocation part for object wrapper or object instance. + * + * Returns results of native calls cause other calls push changes directly to path selector. + */ + private fun TraversalContext.commonInvokePart(invocation: Invocation): List { + val method = invocation.method.executableId + + // This code is supposed to support generic information from signatures for nested methods. + // If we have some method 'foo` and a method `bar(List), and inside `foo` + // there is an invocation `bar(object)`, this object must have information about + // its `Integer` generic type. + invocation.parameters.forEachIndexed { index, param -> + if (param !is ReferenceValue) return@forEachIndexed + + updateGenericTypeInfoFromMethod(method, param, parameterIndex = index + 1) + } + + if (invocation.instance != null) { + updateGenericTypeInfoFromMethod(method, invocation.instance, parameterIndex = 0) + } + + /** + * First, check if there is override for the invocation itself, not for the targets. + * + * Note that calls with super syntax are represented by invokeSpecial instruction, but we don't support them in wrappers, + * so here we resolve [invocation] to the inherited method invocation if it's something like: + * + * ```java + * public class InheritedWrapper extends BaseWrapper { + * public void add(Object o) { + * // some stuff + * super.add(o); // this resolves to InheritedWrapper::add instead of BaseWrapper::add + * } + * } + * ``` + * + * TODO: https://github.com/UnitTestBot/UTBotJava/issues/819 -- Support super calls in inherited wrappers + */ + val artificialMethodOverride = overrideInvocation(invocation, target = null) + + // The problem here is that we might have an unsat current state. + // We get states with `SAT` last status only for traversing, + // but during the parameters resolving this status might be changed. + // It happens inside the `org.utbot.engine.Traverser.initStringLiteral` function. + // So, if it happens, we should just drop the state we processing now. + if (environment.state.solver.lastStatus is UtSolverStatusUNSAT) { + return emptyList() + } + + // If so, return the result of the override + if (artificialMethodOverride.success) { + if (artificialMethodOverride.results.size > 1) { + environment.state.definitelyFork() + } + + return mutableListOf().apply { + for (result in artificialMethodOverride.results) { + when (result) { + is MethodResult -> add(result) + is GraphResult -> pushToPathSelector( + result.graph, + invocation.instance, + invocation.parameters, + result.constraints, + isLibraryMethod = true + ) + } + } + } + } + + // If there is no such invocation, use the generator to produce invocation targets + val targets = invocation.generator.invoke() + + // Take all the targets and run them, at least one target must exist + require(targets.isNotEmpty()) { "No targets for $invocation" } + + // Note that sometimes invocation on the particular targets should be overridden as well. + // For example, Collection.size will produce two targets (ArrayList and HashSet) + // that will override the invocation. + val overrideResults = targets.map { it to overrideInvocation(invocation, it) } + + if (overrideResults.sumOf { (_, overriddenResult) -> overriddenResult.results.size } > 1) { + environment.state.definitelyFork() + } + + // Separate targets for which invocation should be overridden + // from the targets that should be processed regularly. + val (overridden, original) = overrideResults.partition { it.second.success } + + val overriddenResults = overridden + .flatMap { (target, overriddenResult) -> + mutableListOf().apply { + for (result in overriddenResult.results) { + when (result) { + is MethodResult -> add(result) + is GraphResult -> pushToPathSelector( + result.graph, + // take the instance from the target + target.instance, + invocation.parameters, + // It is important to add constraints for the target as well, because + // those constraints contain information about the type of the + // instance from the target + target.constraints + result.constraints, + // Since we override methods of the classes from the standard library + isLibraryMethod = true + ) + } + } + } + } + + // Add results for the targets that should be processed without override + val originResults = original.flatMap { (target: InvocationTarget, _) -> + invoke(target, invocation.parameters) + } + + // Return their concatenation + return overriddenResults + originResults + } + + private fun TraversalContext.invoke( + target: InvocationTarget, + parameters: List + ): List = with(target.method) { + val substitutedMethod = typeRegistry.findSubstitutionOrNull(this) + + if (isNative && substitutedMethod == null) return processNativeMethod(target) + + // If we face UtMock.assume call, we should continue only with the branch + // where the predicate from the parameters is equal true + when { + isUtMockAssume || isUtMockAssumeOrExecuteConcretely -> { + val param = UtCastExpression(parameters.single() as PrimitiveValue, BooleanType.v()) + + val assumptionStmt = mkEq(param, UtTrue) + val (hardConstraints, assumptions) = if (isUtMockAssume) { + // For UtMock.assume we must add the assumeStmt to the hard constraints + setOf(assumptionStmt) to emptySet() + } else { + // For assumeOrExecuteConcretely we must add the statement to the assumptions. + // It is required to have opportunity to remove it later in case of unsat state + // because of it and execute the state concretely. + emptySet() to setOf(assumptionStmt) + } + + val symbolicStateUpdate = SymbolicStateUpdate( + hardConstraints = hardConstraints.asHardConstraint(), + assumptions = assumptions.asAssumption() + ) + + val stateToContinue = updateQueued( + globalGraph.succ(environment.state.stmt), + symbolicStateUpdate + ) + offerState(stateToContinue) + + // we already pushed state with the fulfilled predicate, so we can just drop our branch here by + // adding UtFalse to the constraints. + queuedSymbolicStateUpdates += UtFalse.asHardConstraint() + emptyList() + } + declaringClass == utOverrideMockClass -> utOverrideMockInvoke(target, parameters) + declaringClass == utLogicMockClass -> utLogicMockInvoke(target, parameters) + declaringClass == utArrayMockClass -> utArrayMockInvoke(target, parameters) + isUtMockForbidClassCastException -> isUtMockDisableClassCastExceptionCheckInvoke(parameters) + else -> { + // Try to extract a body from substitution of our method or from the method itself. + // For the substitution, if it exists, we have a corresponding body and graph, + // but for the method itself its body might not be present in the memory. + // This may happen because of classloading issues (e.g. absence of required library JAR file) + val graph = (substitutedMethod ?: this).takeIf { it.canRetrieveBody() }?.jimpleBody()?.graph() + + if (graph != null) { + // If we have a graph to analyze, do it + pushToPathSelector(graph, target.instance, parameters, target.constraints, isLibraryMethod) + emptyList() + } else { + // Otherwise, depending on [treatAbsentMethodsAsUnboundedValue] either throw an exception + // or continue analysis with an unbounded variable as a result of the [this] method + if (UtSettings.treatAbsentMethodsAsUnboundedValue) { + listOf(unboundedVariable("methodWithoutBodyResult", method = this)) + } else { + error("Cannot retrieve body for a $declaredClassName.$name method") + } + } + } + } + } + + private fun isUtMockDisableClassCastExceptionCheckInvoke( + parameters: List + ): List { + val param = parameters.single() as ReferenceValue + val paramAddr = param.addr + typeRegistry.disableCastClassExceptionCheck(paramAddr) + + return listOf(MethodResult(voidValue)) + } + + private fun TraversalContext.utOverrideMockInvoke(target: InvocationTarget, parameters: List): List { + when (target.method.name) { + utOverrideMockAlreadyVisitedMethodName -> { + return listOf(MethodResult(memory.isVisited(parameters[0].addr).toBoolValue())) + } + utOverrideMockVisitMethodName -> { + return listOf( + MethodResult( + voidValue, + memoryUpdates = MemoryUpdate(visitedValues = persistentListOf(parameters[0].addr)) + ) + ) + } + utOverrideMockDoesntThrowMethodName -> { + val stateToContinue = updateQueued( + globalGraph.succ(environment.state.stmt), + doesntThrow = true + ) + offerState(stateToContinue) + queuedSymbolicStateUpdates += UtFalse.asHardConstraint() + return emptyList() + } + utOverrideMockParameterMethodName -> { + when (val param = parameters.single() as ReferenceValue) { + is ObjectValue -> { + val addr = param.addr.toIntValue() + val stateToContinue = updateQueued( + globalGraph.succ(environment.state.stmt), + SymbolicStateUpdate( + hardConstraints = Le(addr, nullObjectAddr.toIntValue()).asHardConstraint() + ) + ) + offerState(stateToContinue) + } + is ArrayValue -> { + val addr = param.addr + val descriptor = + MemoryChunkDescriptor( + typeRegistry.arrayChunkId(OBJECT_TYPE.arrayType), + OBJECT_TYPE.arrayType, + OBJECT_TYPE + ) + + val update = MemoryUpdate( + persistentListOf( + namedStore( + descriptor, + addr, + UtArrayApplyForAll(memory.findArray(descriptor).select(addr)) { array, i -> + Le(array.select(i.expr).toIntValue(), nullObjectAddr.toIntValue()) + } + ) + ) + ) + val stateToContinue = updateQueued( + edge = globalGraph.succ(environment.state.stmt), + SymbolicStateUpdate( + hardConstraints = Le(addr.toIntValue(), nullObjectAddr.toIntValue()).asHardConstraint(), + memoryUpdates = update + ) + ) + offerState(stateToContinue) + } + } + + + // we already pushed state with the fulfilled predicate, so we can just drop our branch here by + // adding UtFalse to the constraints. + queuedSymbolicStateUpdates += UtFalse.asHardConstraint() + return emptyList() + } + utOverrideMockExecuteConcretelyMethodName -> { + offerState(environment.state.withLabel(StateLabel.CONCRETE)) + queuedSymbolicStateUpdates += UtFalse.asHardConstraint() + return emptyList() + } + else -> unreachableBranch("unknown method ${target.method.signature} in ${UtOverrideMock::class.qualifiedName}") + } + } + + private fun utArrayMockInvoke(target: InvocationTarget, parameters: List): List { + when (target.method.name) { + utArrayMockArraycopyMethodName -> { + val src = parameters[0] as ArrayValue + val dst = parameters[2] as ArrayValue + val copyValue = UtArraySetRange( + selectArrayExpressionFromMemory(dst), + parameters[3] as PrimitiveValue, + selectArrayExpressionFromMemory(src), + parameters[1] as PrimitiveValue, + parameters[4] as PrimitiveValue + ) + return listOf( + MethodResult( + voidValue, + memoryUpdates = arrayUpdateWithValue(dst.addr, dst.type, copyValue) + ) + ) + } + utArrayMockCopyOfMethodName -> return listOf(createArrayCopyWithSpecifiedLength(parameters)) + else -> unreachableBranch("unknown method ${target.method.signature} for ${UtArrayMock::class.qualifiedName}") + } + } + + private fun createArrayCopyWithSpecifiedLength(parameters: List): MethodResult { + val src = parameters[0] as ArrayValue + val length = parameters[1] as PrimitiveValue + val arrayType = src.type + + // Even if the new length differs from the original one, it does not affect elements - we will retrieve + // correct elements in the new array anyway + val newArray = createNewArray(length, arrayType, arrayType.elementType) + + // Since z3 arrays are persistent, we can just copy the whole original array value instead of manual + // setting elements equality by indices + return MethodResult( + newArray, + memoryUpdates = arrayUpdateWithValue(newArray.addr, arrayType, selectArrayExpressionFromMemory(src)) + ) + } + + private fun utLogicMockInvoke(target: InvocationTarget, parameters: List): List { + when (target.method.name) { + utLogicMockLessMethodName -> { + val a = parameters[0] as PrimitiveValue + val b = parameters[1] as PrimitiveValue + return listOf(MethodResult(Lt(a, b).toBoolValue())) + } + utLogicMockIteMethodName -> { + var isPrimitive = false + val thenExpr = parameters[1].let { + if (it is PrimitiveValue) { + isPrimitive = true + it.expr + } else { + it.addr.internal + } + } + val elseExpr = parameters[2].let { + if (it is PrimitiveValue) { + isPrimitive = true + it.expr + } else { + it.addr.internal + } + } + val condition = (parameters[0] as PrimitiveValue).expr as UtBoolExpression + val iteExpr = UtIteExpression(condition, thenExpr, elseExpr) + val result = if (isPrimitive) { + PrimitiveValue(target.method.returnType, iteExpr) + } else { + ObjectValue( + typeResolver.constructTypeStorage(target.method.returnType, useConcreteType = false), + UtAddrExpression(iteExpr) + ) + } + return listOf(MethodResult(result)) + } + else -> unreachableBranch("unknown method ${target.method.signature} in ${UtLogicMock::class.qualifiedName}") + } + } + + /** + * Tries to override method. Override can be object wrapper or similar implementation. + * + * Proceeds overridden method as non-library. + */ + private fun TraversalContext.overrideInvocation(invocation: Invocation, target: InvocationTarget?): OverrideResult { + // If we try to override invocation itself, the target is null, and we have to process + // the instance from the invocation, otherwise take the one from the target + val instance = if (target == null) invocation.instance else target.instance + val subSignature = invocation.method.subSignature + + if (subSignature == "java.lang.Class getClass()") { + return when (instance) { + is ReferenceValue -> { + val type = instance.type + val createClassRef = if (type is RefLikeType) { + typeRegistry.createClassRef(type.baseType, type.numDimensions) + } else { + error("Can't get class name for $type") + } + OverrideResult(success = true, createClassRef) + } + null -> unreachableBranch("Static getClass call: $invocation") + } + } + + // Return an unbounded symbolic variable for any overloading of method `forName` of class `java.lang.Class` + // NOTE: we cannot match by a subsignature here since `forName` method has a few overloadings + if (instance == null && invocation.method.declaringClass == CLASS_REF_SOOT_CLASS && invocation.method.name == "forName") { + val forNameResult = unboundedVariable(name = "classForName", invocation.method) + + return OverrideResult(success = true, forNameResult) + } + + // Return an unbounded symbolic variable for the `newInstance` method invocation, + // and at the next traversing step push graph of the resulted type + if (instance?.type == CLASS_REF_TYPE && subSignature == NEW_INSTANCE_SIGNATURE) { + val getInstanceResult = unboundedVariable(name = "newInstance", invocation.method) + + return OverrideResult(success = true, getInstanceResult) + } + + val instanceAsWrapperOrNull = instance?.asWrapperOrNull + + if (instanceAsWrapperOrNull is UtMockWrapper && subSignature == HASHCODE_SIGNATURE) { + val result = MethodResult(mkBVConst("hashcode${hashcodeCounter++}", UtIntSort).toIntValue()) + return OverrideResult(success = true, result) + } + + if (instanceAsWrapperOrNull is UtMockWrapper && subSignature == EQUALS_SIGNATURE) { + val result = MethodResult(mkBoolConst("equals${equalsCounter++}").toBoolValue()) + return OverrideResult(success = true, result) + } + + // we cannot mock synthetic methods and methods that have private or protected arguments + val impossibleToMock = + invocation.method.isSynthetic || invocation.method.isProtected || parametersContainPrivateAndProtectedTypes( + invocation.method + ) + + if (instanceAsWrapperOrNull is UtMockWrapper && impossibleToMock) { + // TODO temporary we return unbounded symbolic variable with a wrong name. + // TODO Probably it'll lead us to the path divergence + workaround(HACK) { + val result = unboundedVariable("unbounded", invocation.method) + return OverrideResult(success = true, result) + } + } + + if (instance is ArrayValue && invocation.method.name == "clone") { + return OverrideResult(success = true, cloneArray(instance)) + } + + if (instance == null && invocation.method.declaringClass == ARRAYS_SOOT_CLASS && invocation.method.name == "copyOf") { + return OverrideResult(success = true, copyOf(invocation.parameters)) + } + + if (instance == null && invocation.method.declaringClass == ARRAYS_SOOT_CLASS && invocation.method.name == "copyOfRange") { + return OverrideResult(success = true, copyOfRange(invocation.parameters)) + } + + instanceAsWrapperOrNull?.run { + // For methods with concrete implementation (for example, RangeModifiableUnlimitedArray.toCastedArray) + // we should not return successful override result. + if (!isWrappedMethod(invocation.method)) { + return OverrideResult(success = false) + } + + val results = invoke(instance as ObjectValue, invocation.method, invocation.parameters) + if (results.isEmpty()) { + // Drop the branch and switch to concrete execution + offerState(environment.state.withLabel(StateLabel.CONCRETE)) + queuedSymbolicStateUpdates += UtFalse.asHardConstraint() + } + return OverrideResult(success = true, results) + } + + return OverrideResult(success = false) + } + + private fun cloneArray(array: ArrayValue): MethodResult { + val addr = findNewAddr() + + val type = array.type + val elementType = type.elementType + val chunkId = typeRegistry.arrayChunkId(type) + val descriptor = MemoryChunkDescriptor(chunkId, type, elementType) + val arrays = memory.findArray(descriptor) + + val arrayLength = memory.findArrayLength(array.addr) + val cloneLength = memory.findArrayLength(addr) + + val constraints = setOf( + mkEq(typeRegistry.symTypeId(array.addr), typeRegistry.symTypeId(addr)), + mkEq(typeRegistry.symNumDimensions(array.addr), typeRegistry.symNumDimensions(addr)), + mkEq(cloneLength, arrayLength) + ) + (0 until softMaxArraySize).map { + val index = mkInt(it) + mkEq( + arrays.select(array.addr, index).toPrimitiveValue(elementType), + arrays.select(addr, index).toPrimitiveValue(elementType) + ) + } + +// TODO: add preferred cex to: val softConstraints = preferredConstraints(clone) + + val memoryUpdate = MemoryUpdate(touchedChunkDescriptors = persistentSetOf(descriptor)) + + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(array.type) + val clone = ArrayValue(typeStorage, addr) + + return MethodResult(clone, constraints.asHardConstraint(), memoryUpdates = memoryUpdate) + } + + private fun TraversalContext.copyOf(parameters: List): MethodResult { + val src = parameters[0] as ArrayValue + nullPointerExceptionCheck(src.addr) + + val length = parameters[1] as PrimitiveValue + val isNegativeLength = Lt(length, 0) + implicitlyThrowException(NegativeArraySizeException("Length is less than zero"), setOf(isNegativeLength)) + queuedSymbolicStateUpdates += mkNot(isNegativeLength).asHardConstraint() + + return createArrayCopyWithSpecifiedLength(parameters) + } + + private fun TraversalContext.copyOfRange(parameters: List): MethodResult { + val original = parameters[0] as ArrayValue + nullPointerExceptionCheck(original.addr) + + val from = parameters[1] as PrimitiveValue + val to = parameters[2] as PrimitiveValue + + val originalLength = memory.findArrayLength(original.addr) + + val isNegativeFrom = Lt(from, 0) + implicitlyThrowException(ArrayIndexOutOfBoundsException("From is less than zero"), setOf(isNegativeFrom)) + queuedSymbolicStateUpdates += mkNot(isNegativeFrom).asHardConstraint() + + val isFromBiggerThanLength = Gt(from, originalLength) + implicitlyThrowException(ArrayIndexOutOfBoundsException("From is bigger than original length"), setOf(isFromBiggerThanLength)) + queuedSymbolicStateUpdates += mkNot(isFromBiggerThanLength).asHardConstraint() + + val isFromBiggerThanTo = Gt(from, to) + implicitlyThrowException(IllegalArgumentException("From is bigger than to"), setOf(isFromBiggerThanTo)) + queuedSymbolicStateUpdates += mkNot(isFromBiggerThanTo).asHardConstraint() + + val newLength = Sub(to, from) + val newLengthValue = newLength.toIntValue() + + val originalLengthDifference = Sub(originalLength, from) + val originalLengthDifferenceValue = originalLengthDifference.toIntValue() + + val resultedLength = + UtIteExpression(Lt(originalLengthDifferenceValue, newLengthValue), originalLengthDifference, newLength) + val resultedLengthValue = resultedLength.toIntValue() + + val arrayType = original.type + val newArray = createNewArray(newLengthValue, arrayType, arrayType.elementType) + val destPos = 0.toPrimitiveValue() + val copyValue = UtArraySetRange( + selectArrayExpressionFromMemory(newArray), + destPos, + selectArrayExpressionFromMemory(original), + from, + resultedLengthValue + ) + + return MethodResult( + newArray, + memoryUpdates = arrayUpdateWithValue(newArray.addr, newArray.type, copyValue) + ) + } + + // For now, we just create unbounded symbolic variable as a result. + private fun processNativeMethod(target: InvocationTarget): List = + listOf(unboundedVariable(name = "nativeConst", target.method)) + + private fun unboundedVariable(name: String, method: SootMethod): MethodResult { + val value = when (val returnType = method.returnType) { + is RefType -> createObject(findNewAddr(), returnType, useConcreteType = true, mockInfoGenerator = null) + is ArrayType -> createArray(findNewAddr(), returnType, useConcreteType = true) + else -> createConst(returnType, "$name${unboundedConstCounter++}", mockInfoGenerator = null) + } + + return MethodResult(value) + } + + fun SootClass.findMethodOrNull(subSignature: String): SootMethod? { + adjustLevel(SootClass.SIGNATURES) + + val classes = generateSequence(this) { it.superClassOrNull() } + val interfaces = generateSequence(this) { it.superClassOrNull() }.flatMap { sootClass -> + sootClass.interfaces.flatMap { hierarchy.ancestors(it.id) } + }.distinct() + return (classes + interfaces) + .filter { + it.adjustLevel(SootClass.SIGNATURES) + it.declaresMethod(subSignature) + } + .mapNotNull { it.getMethod(subSignature) } + .firstOrNull { it.canRetrieveBody() || it.isNative } + } + + private fun TraversalContext.pushToPathSelector( + graph: ExceptionalUnitGraph, + caller: ReferenceValue?, + callParameters: List, + constraints: Set = emptySet(), + isLibraryMethod: Boolean = false + ) { + globalGraph.join(environment.state.stmt, graph, !isLibraryMethod) + val parametersWithThis = listOfNotNull(caller) + callParameters + offerState( + pushQueued(graph, parametersWithThis, constraints.asHardConstraint()) + ) + } + + private fun TraversalContext.resolveIfCondition(cond: BinopExpr): ResolvedCondition { + // We add cond.op.type for null values only. If we have condition like "null == r1" + // we'll have ObjectInstance(r1::type) and ObjectInstance(r1::type) for now + // For non-null values type is ignored. + val lhs = resolve(cond.op1, cond.op2.type) + val rhs = resolve(cond.op2, cond.op1.type) + return when { + lhs.isNullObject() || rhs.isNullObject() -> { + val eq = addrEq(lhs.addr, rhs.addr) + if (cond is NeExpr) ResolvedCondition(mkNot(eq)) else ResolvedCondition(eq) + } + lhs is ReferenceValue && rhs is ReferenceValue -> { + ResolvedCondition(compareReferenceValues(lhs, rhs, cond is NeExpr)) + } + else -> { + val expr = resolve(cond).asPrimitiveValueOrError as UtBoolExpression + val memoryUpdates = collectSymbolicStateUpdates(expr) + ResolvedCondition( + expr, + constructSoftConstraintsForCondition(cond), + symbolicStateUpdates = memoryUpdates + ) + } + } + } + + /** + * Tries to collect all memory updates from nested [UtInstanceOfExpression]s in the [expr]. + * Resolves only basic cases: `not`, `and`, `z0 == 0`, `z0 == 1`, `z0 != 0`, `z0 != 1`. + * + * It's impossible now to make this function complete, because our [Memory] can't deal with some expressions + * (e.g. [UtOrBoolExpression] consisted of [UtInstanceOfExpression]s). + */ + private fun collectSymbolicStateUpdates(expr: UtBoolExpression): SymbolicStateUpdateForResolvedCondition { + return when (expr) { + is UtInstanceOfExpression -> { // for now only this type of expression produces deferred updates + val onlyMemoryUpdates = expr.symbolicStateUpdate.copy( + hardConstraints = emptyHardConstraint(), + softConstraints = emptySoftConstraint(), + assumptions = emptyAssumption() + ) + SymbolicStateUpdateForResolvedCondition(onlyMemoryUpdates) + } + is UtAndBoolExpression -> { + expr.exprs.fold(SymbolicStateUpdateForResolvedCondition()) { updates, nestedExpr -> + val nextPosUpdates = updates.positiveCase + collectSymbolicStateUpdates(nestedExpr).positiveCase + val nextNegUpdates = updates.negativeCase + collectSymbolicStateUpdates(nestedExpr).negativeCase + SymbolicStateUpdateForResolvedCondition(nextPosUpdates, nextNegUpdates) + } + } + // TODO: JIRA:1667 -- Engine can't apply memory updates for some expressions + is UtOrBoolExpression -> SymbolicStateUpdateForResolvedCondition() // Which clause should we apply? + is NotBoolExpression -> collectSymbolicStateUpdates(expr.expr).swap() + is UtBoolOpExpression -> { + // Java `instanceof` in `if` translates to UtBoolOpExpression. + // More precisely, something like this will be generated: + // ... + // z0: bool = obj instanceof A + // if z0 == 0 goto ... + // ... + // while traversing the condition, `BinopExpr` resolves to `UtBoolOpExpression` with the left part + // equals to `UtBoolExpession` (usually `UtInstanceOfExpression`), because it is stored in local `z0` + // and the right part equals to UtBvLiteral with the integer constant. + // + // If something more complex is written in the original `if`, these matches could not success. + // TODO: JIRA:1667 + val lhs = expr.left.expr as? UtBoolExpression + ?: return SymbolicStateUpdateForResolvedCondition() + val rhsAsIntValue = (expr.right.expr as? UtBvLiteral)?.value?.toInt() + ?: return SymbolicStateUpdateForResolvedCondition() + val updates = collectSymbolicStateUpdates(lhs) + + when (expr.operator) { + is Eq -> { + when (rhsAsIntValue) { + 1 -> updates // z0 == 1 + 0 -> updates.swap() // z0 == 0 + else -> SymbolicStateUpdateForResolvedCondition() + } + } + is Ne -> { + when (rhsAsIntValue) { + 1 -> updates.swap() // z0 != 1 + 0 -> updates // z0 != 0 + else -> SymbolicStateUpdateForResolvedCondition() + } + } + else -> SymbolicStateUpdateForResolvedCondition() + } + } + // TODO: JIRA:1667 -- Engine can't apply memory updates for some expressions + else -> SymbolicStateUpdateForResolvedCondition() + } + } + + private fun TraversalContext.constructSoftConstraintsForCondition(cond: BinopExpr): SoftConstraintsForResolvedCondition { + var positiveCaseConstraint: UtBoolExpression? = null + var negativeCaseConstraint: UtBoolExpression? = null + + val left = resolve(cond.op1, cond.op2.type) + val right = resolve(cond.op2, cond.op1.type) + + if (left !is PrimitiveValue || right !is PrimitiveValue) return SoftConstraintsForResolvedCondition() + + val one = 1.toPrimitiveValue() + + when (cond) { + is JLtExpr -> { + positiveCaseConstraint = mkEq(left, Sub(right, one).toIntValue()) + negativeCaseConstraint = mkEq(left, right) + } + is JLeExpr -> { + positiveCaseConstraint = mkEq(left, right) + negativeCaseConstraint = mkEq(Sub(left, one).toIntValue(), right) + } + is JGeExpr -> { + positiveCaseConstraint = mkEq(left, right) + negativeCaseConstraint = mkEq(left, Sub(right, one).toIntValue()) + } + is JGtExpr -> { + positiveCaseConstraint = mkEq(Sub(left, one).toIntValue(), right) + negativeCaseConstraint = mkEq(left, right) + + } + else -> Unit + } + + return SoftConstraintsForResolvedCondition(positiveCaseConstraint, negativeCaseConstraint) + } + + /** + * Compares two objects with types, lhs :: lhsType and rhs :: rhsType. + * + * Does it by checking types equality, then addresses equality. + * + * Notes: + * - Content (assertions on fields) comparison is not necessary cause solver finds on its own and provides + * different object addresses in such case + * - We do not compare null addresses here, it happens in resolveIfCondition + * + * @see Traverser.resolveIfCondition + */ + private fun compareReferenceValues( + lhs: ReferenceValue, + rhs: ReferenceValue, + negate: Boolean + ): UtBoolExpression { + val eq = addrEq(lhs.addr, rhs.addr) + return if (negate) mkNot(eq) else eq + } + + private fun TraversalContext.nullPointerExceptionCheck(addr: UtAddrExpression) { + val canBeNull = addrEq(addr, nullObjectAddr) + val canNotBeNull = mkNot(canBeNull) + val notMarked = mkEq(memory.isSpeculativelyNotNull(addr), mkFalse()) + val notMarkedAndNull = mkAnd(notMarked, canBeNull) + + if (environment.method.checkForNPE(environment.state.executionStack.size)) { + implicitlyThrowException(NullPointerException(), setOf(notMarkedAndNull)) + } + + queuedSymbolicStateUpdates += canNotBeNull.asHardConstraint() + } + + private fun TraversalContext.divisionByZeroCheck(denom: PrimitiveValue) { + val equalsToZero = Eq(denom, 0) + implicitlyThrowException(ArithmeticException("/ by zero"), setOf(equalsToZero)) + queuedSymbolicStateUpdates += mkNot(equalsToZero).asHardConstraint() + } + + // Use cast to Int and cmp with min/max for Byte and Short. + // Formulae for Int and Long does not work for lower integers because of sign_extend ops in SMT. + private fun lowerIntMulOverflowCheck( + left: PrimitiveValue, + right: PrimitiveValue, + minValue: Int, + maxValue: Int, + ): UtBoolExpression { + val castedLeft = UtCastExpression(left, UtIntSort.type).toIntValue() + val castedRight = UtCastExpression(right, UtIntSort.type).toIntValue() + + val res = Mul(castedLeft, castedRight).toIntValue() + + val lessThanMinValue = Lt( + res, + minValue.toPrimitiveValue(), + ).toBoolValue() + + val greaterThanMaxValue = Gt( + res, + maxValue.toPrimitiveValue(), + ).toBoolValue() + + return Ne( + Or( + lessThanMinValue, + greaterThanMaxValue, + ).toBoolValue(), + 0.toPrimitiveValue() + ) + } + + + // Z3 internal operator for MulNoOverflow is currently bugged. + // Use formulae from Math.mulExact to detect mul overflow for Int and Long. + private fun higherIntMulOverflowCheck( + left: PrimitiveValue, + right: PrimitiveValue, + bits: Int, + minValue: Long, + toValue: (it: UtExpression) -> PrimitiveValue, + ): UtBoolExpression { + // https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/lang/Math.java#l882 + val leftValue = toValue(left.expr) + val rightValue = toValue(right.expr) + val res = toValue(Mul(leftValue, rightValue)) + + // extract absolute values + // https://www.geeksforgeeks.org/compute-the-integer-absolute-value-abs-without-branching/ + val leftAbsMask = toValue(Ushr(leftValue, (bits - 1).toPrimitiveValue())) + val leftAbs = toValue( + Xor( + toValue(Add(leftAbsMask, leftValue)), + leftAbsMask + ) + ) + val rightAbsMask = toValue(Ushr(rightValue, (bits - 1).toPrimitiveValue())) + val rightAbs = toValue( + Xor( + toValue(Add(rightAbsMask, rightValue)), + rightAbsMask + ) + ) + + // (((ax | ay) >>> 31 != 0)) + val bigEnough = Ne( + toValue( + Ushr( + toValue(Or(leftAbs, rightAbs)), + (bits ushr 1 - 1).toPrimitiveValue() + ) + ), + 0.toPrimitiveValue() + ) + + // (((y != 0) && (r / y != x)) + val incorrectDiv = And( + Ne(rightValue, 0.toPrimitiveValue()).toBoolValue(), + Ne(toValue(Div(res, rightValue)), leftValue).toBoolValue(), + ) + + // (x == Long.MIN_VALUE && y == -1)) + val minValueEdgeCase = And( + Eq(leftValue, minValue.toPrimitiveValue()).toBoolValue(), + Eq(rightValue, (-1).toPrimitiveValue()).toBoolValue() + ) + + return Ne( + And( + bigEnough.toBoolValue(), + Or( + incorrectDiv.toBoolValue(), + minValueEdgeCase.toBoolValue(), + ).toBoolValue() + ).toBoolValue(), + 0.toPrimitiveValue() + ) + } + + private fun TraversalContext.intOverflowCheck(op: BinopExpr, leftRaw: PrimitiveValue, rightRaw: PrimitiveValue) { + // cast to the bigger type + val sort = simpleMaxSort(leftRaw, rightRaw) as UtPrimitiveSort + val left = UtCastExpression(leftRaw, sort.type) + val right = UtCastExpression(rightRaw, sort.type) + + val leftPrimValue = left.toPrimitiveValue(left.type) + val rightPrimValue = right.toPrimitiveValue(right.type) + + val overflow = when (op) { + is JAddExpr -> mkNot(UtAddNoOverflowExpression(left, right)) + is JSubExpr -> mkNot(UtSubNoOverflowExpression(left, right)) + is JMulExpr -> when (sort.type) { + is ByteType -> lowerIntMulOverflowCheck( + leftPrimValue, + rightPrimValue, + Byte.MIN_VALUE.toInt(), + Byte.MAX_VALUE.toInt() + ) + is ShortType -> lowerIntMulOverflowCheck( + leftPrimValue, + rightPrimValue, + Short.MIN_VALUE.toInt(), + Short.MAX_VALUE.toInt() + ) + is IntType -> higherIntMulOverflowCheck( + leftPrimValue, + rightPrimValue, + Int.SIZE_BITS, + Int.MIN_VALUE.toLong() + ) { it: UtExpression -> it.toIntValue() } + is LongType -> higherIntMulOverflowCheck( + leftPrimValue, + rightPrimValue, + Long.SIZE_BITS, + Long.MIN_VALUE + ) { it: UtExpression -> it.toLongValue() } + else -> null + } + else -> null + } + + if (overflow != null) { + implicitlyThrowException(OverflowDetectionError("${left.type} ${op.symbol} overflow"), setOf(overflow)) + queuedSymbolicStateUpdates += mkNot(overflow).asHardConstraint() + } + } + + private fun TraversalContext.indexOutOfBoundsChecks(index: PrimitiveValue, length: PrimitiveValue) { + val ltZero = Lt(index, 0) + implicitlyThrowException(IndexOutOfBoundsException("Less than zero"), setOf(ltZero)) + + val geLength = Ge(index, length) + implicitlyThrowException(IndexOutOfBoundsException("Greater or equal than length"), setOf(geLength)) + + queuedSymbolicStateUpdates += mkNot(ltZero).asHardConstraint() + queuedSymbolicStateUpdates += mkNot(geLength).asHardConstraint() + } + + private fun TraversalContext.negativeArraySizeCheck(vararg sizes: PrimitiveValue) { + val ltZero = mkOr(sizes.map { Lt(it, 0) }) + implicitlyThrowException(NegativeArraySizeException("Less than zero"), setOf(ltZero)) + queuedSymbolicStateUpdates += mkNot(ltZero).asHardConstraint() + } + + /** + * Checks for ClassCastException. + * + * Note: if we have the valueToCast.addr related to some parameter with addr p_0, and that parameter's type is a parameterizedType, + * we ignore potential exception throwing if the typeAfterCast is one of the generics included in that type. + */ + private fun TraversalContext.classCastExceptionCheck(valueToCast: ReferenceValue, typeAfterCast: Type) { + val baseTypeAfterCast = if (typeAfterCast is ArrayType) typeAfterCast.baseType else typeAfterCast + val addr = valueToCast.addr + + // Expected in the parameters baseType is an RefType because it is either an RefType itself or an array of RefType values + if (baseTypeAfterCast is RefType) { + // Find parameterized type for the object if it is a parameter of the method under test and it has generic type + val newAddr = addr.accept(solver.simplificator) as UtAddrExpression + val parameterizedTypes = when (newAddr.internal) { + is UtArraySelectExpression -> instanceAddrToGenericType[findTheMostNestedAddr(newAddr.internal)] + is UtBvConst -> instanceAddrToGenericType[newAddr] + else -> null + } + + parameterizedTypes?.forEach { parameterizedType -> + val genericTypes = generateSequence(parameterizedType) { it.ownerType as? ParameterizedType } + .flatMapTo(mutableSetOf()) { it.actualTypeArguments.map { arg -> arg.typeName } } + + if (baseTypeAfterCast.className in genericTypes) { + return + } + } + } + + val inheritorsTypeStorage = typeResolver.constructTypeStorage(typeAfterCast, useConcreteType = false) + val preferredTypesForCastException = valueToCast.possibleConcreteTypes.filterNot { it in inheritorsTypeStorage.possibleConcreteTypes } + + val isExpression = typeRegistry.typeConstraint(addr, inheritorsTypeStorage).isConstraint() + val notIsExpression = mkNot(isExpression) + + val nullEqualityConstraint = addrEq(addr, nullObjectAddr) + val notNull = mkNot(nullEqualityConstraint) + + val classCastExceptionAllowed = mkEq(UtTrue, typeRegistry.isClassCastExceptionAllowed(addr)) + + implicitlyThrowException( + ClassCastException("The object with type ${valueToCast.type} can not be casted to $typeAfterCast"), + setOf(notIsExpression, notNull, classCastExceptionAllowed), + setOf(constructConstraintForType(valueToCast, preferredTypesForCastException)) + ) + + queuedSymbolicStateUpdates += mkOr(isExpression, nullEqualityConstraint).asHardConstraint() + } + + private fun TraversalContext.implicitlyThrowException( + throwable: Throwable, + conditions: Set, + softConditions: Set = emptySet() + ) { + if (environment.state.executionStack.last().doesntThrow) return + + val symException = implicitThrown(throwable, findNewAddr(), environment.state.isInNestedMethod()) + if (!traverseCatchBlock(environment.state.stmt, symException, conditions)) { + environment.state.expectUndefined() + val nextState = createExceptionStateQueued( + symException, + SymbolicStateUpdate(conditions.asHardConstraint(), softConditions.asSoftConstraint()) + ) + globalGraph.registerImplicitEdge(nextState.lastEdge!!) + offerState(nextState) + } + } + + + private val symbolicStackTrace: String + get() { + val methods = environment.state.executionStack.mapNotNull { it.caller } + .map { globalGraph.method(it) } + environment.method + return methods.reversed().joinToString(separator = "\n") { method -> + if (method.isDeclared) "$method" else method.subSignature + } + } + + private fun constructConstraintForType(value: ReferenceValue, possibleTypes: Collection): UtBoolExpression { + val preferredTypes = typeResolver.findTopRatedTypes(possibleTypes, take = NUMBER_OF_PREFERRED_TYPES) + val mostCommonType = preferredTypes.singleOrNull() ?: OBJECT_TYPE + val typeStorage = typeResolver.constructTypeStorage(mostCommonType, preferredTypes) + + return typeRegistry.typeConstraint(value.addr, typeStorage).isOrNullConstraint() + } + + /** + * Adds soft default values for the initial values of all the arrays that exist in the program. + * + * Almost all of them can be found in the local memory, but there are three "common" + * arrays that we need to add + * + * + * @see Memory.initialArrays + * @see Memory.softZeroArraysLengths + * @see TypeRegistry.softEmptyTypes + * @see TypeRegistry.softZeroNumDimensions + */ + private fun addSoftDefaults() { + memory.initialArrays.forEach { queuedSymbolicStateUpdates += UtMkTermArrayExpression(it).asHardConstraint() } + queuedSymbolicStateUpdates += memory.softZeroArraysLengths().asHardConstraint() + queuedSymbolicStateUpdates += typeRegistry.softZeroNumDimensions().asHardConstraint() + queuedSymbolicStateUpdates += typeRegistry.softEmptyTypes().asHardConstraint() + } + + /** + * Takes queued [updates] at the end of static initializer processing, extracts information about + * updated static fields and substitutes them with unbounded symbolic variables. + * + * @return updated memory updates. + */ + private fun substituteStaticFieldsWithSymbolicVariables( + declaringClass: SootClass, + updates: MemoryUpdate + ): MemoryUpdate { + val declaringClassId = declaringClass.id + + val staticFieldsUpdates = updates.staticFieldsUpdates.toMutableList() + val fieldValuesUpdates = updates.fieldValues.toMutableMap() + val updatedFields = staticFieldsUpdates.mapTo(mutableSetOf()) { it.fieldId } + val objectUpdates = mutableListOf() + + // we assign unbounded symbolic variables for every non-final meaningful field of the class + // fields from predefined library classes are excluded, because there are not meaningful + typeResolver + .findFields(declaringClass.type) + .filter { !it.isFinal && it.fieldId in updatedFields && isStaticFieldMeaningful(it) } + .forEach { + // remove updates from clinit, because we'll replace those values + // with new unbounded symbolic variable + staticFieldsUpdates.removeAll { update -> update.fieldId == it.fieldId } + fieldValuesUpdates.keys.removeAll { key -> key.fieldId == it.fieldId } + + val generator = UtMockInfoGenerator { mockAddr -> + val fieldId = FieldId(it.declaringClass.id, it.name) + UtFieldMockInfo(it.type.classId, mockAddr, fieldId, ownerAddr = null) + } + + val value = createConst(it.type, it.name, generator) + val valueToStore = if (value is ReferenceValue) { + value.addr + } else { + (value as PrimitiveValue).expr + } + + // we always know that this instance exists because we've just returned from its clinit method + // in which we had to create such instance + val staticObject = updates.staticInstanceStorage.getValue(declaringClassId) + staticFieldsUpdates += StaticFieldMemoryUpdateInfo(it.fieldId, value) + + objectUpdates += objectUpdate(staticObject, it, valueToStore).stores + } + + return updates.copy( + stores = updates.stores.addAll(objectUpdates), + staticFieldsUpdates = staticFieldsUpdates.toPersistentList(), + fieldValues = fieldValuesUpdates.toPersistentMap(), + classIdToClearStatics = declaringClassId + ) + } + + private fun TraversalContext.processResult(symbolicResult: SymbolicResult) { + val resolvedParameters = environment.state.parameters.map { it.value } + + //choose types that have biggest priority + resolvedParameters + .filterIsInstance() + .forEach { queuedSymbolicStateUpdates += constructConstraintForType(it, it.possibleConcreteTypes).asSoftConstraint() } + + val returnValue = (symbolicResult as? SymbolicSuccess)?.value as? ObjectValue + if (returnValue != null) { + queuedSymbolicStateUpdates += constructConstraintForType(returnValue, returnValue.possibleConcreteTypes).asSoftConstraint() + } + + //fill arrays with default 0/null and other stuff + addSoftDefaults() + + //deal with @NotNull annotation + val isNotNullableResult = environment.method.returnValueHasNotNullAnnotation() + if (symbolicResult is SymbolicSuccess && symbolicResult.value is ReferenceValue && isNotNullableResult) { + queuedSymbolicStateUpdates += mkNot(mkEq(symbolicResult.value.addr, nullObjectAddr)).asHardConstraint() + } + + val symbolicState = environment.state.symbolicState + queuedSymbolicStateUpdates + val memory = symbolicState.memory + val solver = symbolicState.solver + + if (!UtSettings.disableUnsatChecking) { + //no need to respect soft constraints in NestedMethod + val holder = solver.check(respectSoft = !environment.state.isInNestedMethod()) + + if (holder !is UtSolverStatusSAT) { + logger.trace { "processResult<${environment.method.signature}> UNSAT" } + return + } + } + val methodResult = MethodResult(symbolicResult) + + //execution frame from level 2 or above + if (environment.state.isInNestedMethod()) { + // static fields substitution + // TODO: JIRA:1610 -- better way of working with statics + val updates = if (environment.method.name == STATIC_INITIALIZER && substituteStaticsWithSymbolicVariable) { + substituteStaticFieldsWithSymbolicVariables( + environment.method.declaringClass, + memory.queuedStaticMemoryUpdates() + ) + } else { + MemoryUpdate() // all memory updates are already added in [environment.state] + } + val methodResultWithUpdates = methodResult.copy(symbolicStateUpdate = queuedSymbolicStateUpdates + updates) + val stateToOffer = pop(methodResultWithUpdates) + offerState(stateToOffer) + + logger.trace { "processResult<${environment.method.signature}> return from nested method" } + return + } + + // toplevel method + // TODO: investigate very strange behavior when some constraints are not added leading to failing CodegenExampleTest::firstExampleTest fails + val terminalExecutionState = environment.state.copy( + symbolicState = symbolicState, + methodResult = methodResult, // the way to put SymbolicResult into terminal state + label = StateLabel.TERMINAL + ) + offerState(terminalExecutionState) + } + + private fun TraversalContext.symbolicSuccess(stmt: ReturnStmt): SymbolicSuccess { + val type = environment.method.returnType + val value = when (val instance = resolve(stmt.op, type)) { + is PrimitiveValue -> instance.cast(type) + else -> instance + } + return SymbolicSuccess(value) + } + + internal fun asMethodResult(function: Traverser.() -> SymbolicValue): MethodResult { + val prevSymbolicStateUpdate = queuedSymbolicStateUpdates.copy() + // TODO: refactor this `try` with `finally` later + queuedSymbolicStateUpdates = SymbolicStateUpdate() + try { + val result = function() + return MethodResult( + SymbolicSuccess(result), + queuedSymbolicStateUpdates + ) + } finally { + queuedSymbolicStateUpdates = prevSymbolicStateUpdate + } + } + + private fun createExceptionStateQueued(exception: SymbolicFailure, update: SymbolicStateUpdate): ExecutionState { + val simplifiedUpdates = with (memoryUpdateSimplificator) { + simplifySymbolicStateUpdate(queuedSymbolicStateUpdates + update) + } + val simplifiedResult = with(simplificator) { + simplifySymbolicValue(exception.symbolic) + } + return environment.state.createExceptionState( + exception.copy(symbolic = simplifiedResult), + update = simplifiedUpdates + ) + } + + private fun updateQueued(update: SymbolicStateUpdate): ExecutionState { + val symbolicStateUpdate = with(memoryUpdateSimplificator) { + simplifySymbolicStateUpdate(queuedSymbolicStateUpdates + update) + } + + return environment.state.update( + symbolicStateUpdate, + ) + } + + private fun updateQueued( + edge: Edge, + update: SymbolicStateUpdate = SymbolicStateUpdate(), + doesntThrow: Boolean = false + ): ExecutionState { + val simplifiedUpdates = + with(memoryUpdateSimplificator) { + simplifySymbolicStateUpdate(queuedSymbolicStateUpdates + update) + } + + return environment.state.update( + edge, + simplifiedUpdates, + doesntThrow + ) + } + + private fun pushQueued( + graph: ExceptionalUnitGraph, + parametersWithThis: List, + hardConstraint: HardConstraint + ): ExecutionState { + val simplifiedUpdates = with(memoryUpdateSimplificator) { + simplifySymbolicStateUpdate(queuedSymbolicStateUpdates + hardConstraint) + } + return environment.state.push( + graph.head, + inputArguments = ArrayDeque(parametersWithThis), + simplifiedUpdates, + graph.body.method + ) + } + + private fun pop(methodResultWithUpdates: MethodResult): ExecutionState { + return environment.state.pop(methodResultWithUpdates) + } + + // taint analysis + + private fun TraversalContext.resolveMethodId( + invokeExpr: InvokeExpr, + methodResult: MethodResult + ): SymbolicMethodData { + val methodId = invokeExpr.method.executableId + // The method not in soot for some reasons. + // For example, it is synthetic and so it should not be processed in taint analysis. + val sootMethod = methodId.sootMethodOrNull ?: return SymbolicMethodData.constructInvalid(methodId) + + val symbolicBase = invokeExpr.baseOrNull()?.let { resolve(it, sootMethod.declaringClass.type) } + val symbolicArgs = resolveParameters(invokeExpr.args, sootMethod.parameterTypes) + val symbolicResult = (methodResult.symbolicResult as? SymbolicSuccess)?.value + + return SymbolicMethodData(methodId, symbolicBase, symbolicArgs, symbolicResult) + } + + private fun TraversalContext.processTaintAnalysis( + invokeExpr: InvokeExpr, + methodResult: MethodResult + ): SymbolicStateUpdate { + var result = SymbolicStateUpdate() + + val methodData = resolveMethodId(invokeExpr, methodResult) + result += processTaintSource(methodData) + result += processTaintCleaner(methodData) + result += processTaintPass(methodData) + + return result + } + + private fun processTaintSource(methodData: SymbolicMethodData): SymbolicStateUpdate { + val sourceConfigurations = taintContext.configuration.getSourcesBy(methodData.methodId) + val symbolicStateUpdates = sourceConfigurations.flatMap { source -> + val allAddresses = source.addTo.entities.mapNotNull { entity -> + methodData.choose(entity)?.addrOrNull + } + val condition = source.condition.toBoolExpr(this@Traverser, methodData) + + allAddresses.map { addr -> + taintContext.markManager.setMarks(memory, addr, source.marks, condition) + } + } + + return symbolicStateUpdates.fold(SymbolicStateUpdate()) { acc, update -> acc + update } + } + + private fun processTaintCleaner(methodData: SymbolicMethodData): SymbolicStateUpdate { + val cleanerConfigurations = taintContext.configuration.getCleanersBy(methodData.methodId) + val symbolicStateUpdates = cleanerConfigurations.flatMap { cleaner -> + val allAddresses = cleaner.removeFrom.entities.mapNotNull { entity -> + methodData.choose(entity)?.addrOrNull + } + val condition = cleaner.condition.toBoolExpr(this@Traverser, methodData) + + allAddresses.map { addr -> + taintContext.markManager.clearMarks(memory, addr, cleaner.marks, condition) + } + } + + return symbolicStateUpdates.fold(SymbolicStateUpdate()) { acc, update -> acc + update } + } + + private fun processTaintPass(methodData: SymbolicMethodData): SymbolicStateUpdate { + val passConfigurations = taintContext.configuration.getPassesBy(methodData.methodId) + val symbolicStateUpdates = passConfigurations.flatMap { pass -> + val getFromAddresses = pass.getFrom.entities.mapNotNull { entity -> + methodData.choose(entity)?.addrOrNull + } + val addToAddresses = pass.addTo.entities.mapNotNull { entity -> + methodData.choose(entity)?.addrOrNull + } + val condition = pass.condition.toBoolExpr(this@Traverser, methodData) + + getFromAddresses.flatMap { fromAddr -> + addToAddresses.map { toAddr -> + taintContext.markManager.passMarks(memory, fromAddr, toAddr, pass.marks, condition) + } + } + } + + return symbolicStateUpdates.fold(SymbolicStateUpdate()) { acc, update -> acc + update } + } + + private fun TraversalContext.processTaintSink(methodData: SymbolicMethodData) { + val sinkConfigurations = taintContext.configuration.getSinksBy(methodData.methodId) + sinkConfigurations.forEach { sink -> + val condition = sink.condition.toBoolExpr(this@Traverser, methodData) + sink.check.entities.forEach { entity -> + implicitlyThrowTaintError(methodData, entity, sink.marks, condition) + } + } + } + + private fun TraversalContext.implicitlyThrowTaintError( + methodData: SymbolicMethodData, + entity: TaintEntity, + marks: TaintMarks, + condition: UtBoolExpression, + ) { + val symbolicEntity = methodData.choose(entity) ?: return + val entityAddr = symbolicEntity.addrOrNull ?: return + + val methodName = methodData.methodId.simpleNameWithClass + val taintedVarType = symbolicEntity.type.toQuotedString() + + when (marks) { + is TaintMarksAll -> { + val containsAnyMark = taintContext.markManager.containsAnyMark(memory, entityAddr) + implicitlyThrowException( + TaintAnalysisError(methodName, taintedVarType, "tainted"), + setOf(mkAnd(containsAnyMark, condition)) + ) + } + is TaintMarksSet -> { + if (UtSettings.throwTaintErrorForEachMarkSeparately) { + marks.marks.forEach { mark -> + val containsMark = taintContext.markManager.containsMark(memory, entityAddr, mark) + implicitlyThrowException( + TaintAnalysisError(methodName, taintedVarType, mark.name), + setOf(mkAnd(containsMark, condition)) + ) + } + } else { + val containsAnySpecifiedMark = marks.marks.map { mark -> + taintContext.markManager.containsMark(memory, entityAddr, mark) + } + implicitlyThrowException( + TaintAnalysisError(methodName, taintedVarType, "tainted"), + setOf(mkAnd(mkOr(containsAnySpecifiedMark), condition)) + ) + } + } + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt deleted file mode 100644 index 95565eb600..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/TypeResolver.kt +++ /dev/null @@ -1,357 +0,0 @@ -package org.utbot.engine - -import org.utbot.common.WorkaroundReason -import org.utbot.common.heuristic -import org.utbot.common.workaround -import org.utbot.engine.pc.UtAddrExpression -import org.utbot.engine.pc.UtBoolExpression -import org.utbot.engine.pc.mkAnd -import org.utbot.engine.pc.mkEq -import org.utbot.framework.plugin.api.id -import org.utbot.framework.plugin.api.util.id -import soot.ArrayType -import soot.IntType -import soot.NullType -import soot.PrimType -import soot.RefType -import soot.Scene -import soot.SootField -import soot.Type -import soot.VoidType - -class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy: Hierarchy) { - - fun findOrConstructInheritorsIncludingTypes(type: RefType) = typeRegistry.findInheritorsIncludingTypes(type) { - hierarchy.inheritors(type.sootClass.id).mapTo(mutableSetOf()) { it.type } - } - - fun findOrConstructAncestorsIncludingTypes(type: RefType) = typeRegistry.findAncestorsIncludingTypes(type) { - hierarchy.ancestors(type.sootClass.id).mapTo(mutableSetOf()) { it.type } - } - - /** - * Finds all the inheritors for each type from the [types] and returns their intersection. - * - * Note: every type from the result satisfies [isAppropriate] condition. - */ - fun intersectInheritors(types: Array): Set = intersectTypes(types) { - findOrConstructInheritorsIncludingTypes(it) - } - - /** - * Finds all the ancestors for each type from the [types] and return their intersection. - * - * Note: every type from the result satisfies [isAppropriate] condition. - */ - fun intersectAncestors(types: Array): Set = intersectTypes(types) { - findOrConstructAncestorsIncludingTypes(it) - } - - private fun intersectTypes( - types: Array, - retrieveFunction: (RefType) -> Set - ): Set { - val allObjects = findOrConstructInheritorsIncludingTypes(OBJECT_TYPE) - - // TODO we do not support constructions like List here, be aware of it - // TODO JIRA:1446 - - return types - .map { classOrDefault(it.rawType.typeName) } - .fold(allObjects) { acc, value -> acc.intersect(retrieveFunction(value)) } - .filter { it.sootClass.isAppropriate } - .toSet() - } - - private fun classOrDefault(typeName: String): RefType = - runCatching { Scene.v().getRefType(typeName) }.getOrDefault(OBJECT_TYPE) - - fun findFields(type: RefType) = typeRegistry.findFields(type) { - hierarchy - .ancestors(type.sootClass.id) - .flatMap { it.fields } - .asReversed() // to take fields of the farthest parent in the distinctBy - .distinctBy { it.name } // TODO we lose hidden fields here JIRA:315 - } - - /** - * Returns given number of appropriate types that have the highest rating. - * - * @param types Collection of types to sort - * @param take Number of types to take - * - * @see TypeRegistry.findRating - * @see appropriateClasses - */ - fun findTopRatedTypes(types: Collection, take: Int = Int.MAX_VALUE) = - types.appropriateClasses() - .sortedByDescending { type -> - val baseType = if (type is ArrayType) type.baseType else type - // primitive baseType has the highest possible rating - if (baseType is RefType) typeRegistry.findRating(baseType) else Int.MAX_VALUE - } - .take(take) - - /** - * Constructs a [TypeStorage] instance containing [type] as its most common type and - * appropriate types from [possibleTypes] in its [TypeStorage.possibleConcreteTypes]. - * - * @param type the most common type of the constructed type storage. - * @param possibleTypes a list of types to be contained in the constructed type storage. - * - * @return constructed type storage. - * - * Note: [TypeStorage.possibleConcreteTypes] of the type storage returned by this method contains only - * classes we can instantiate: there will be no interfaces, abstract or local classes. - * If there are no such classes, [TypeStorage.possibleConcreteTypes] is an empty set. - * - * @see isAppropriate - */ - fun constructTypeStorage(type: Type, possibleTypes: Collection): TypeStorage { - val concretePossibleTypes = possibleTypes - .map { (if (it is ArrayType) it.baseType else it) to it.numDimensions } - .filterNot { (baseType, numDimensions) -> isInappropriateOrArrayOfMocksOrLocals(numDimensions, baseType) } - .mapTo(mutableSetOf()) { (baseType, numDimensions) -> - if (numDimensions == 0) baseType else baseType.makeArrayType(numDimensions) - } - - return TypeStorage(type, concretePossibleTypes).filterInappropriateClassesForCodeGeneration() - } - - private fun isInappropriateOrArrayOfMocksOrLocals(numDimensions: Int, baseType: Type?): Boolean { - if (baseType !is RefType) { - return false - } - - val baseSootClass = baseType.sootClass - - if (numDimensions == 0 && baseSootClass.isInappropriate) { - // interface, abstract class, or local, or mock could not be constructed - return true - } - - if (numDimensions > 0 && (baseSootClass.isLocal || baseSootClass.findMockAnnotationOrNull != null)) { - // array of mocks or locals could not be constructed, but array of interfaces or abstract classes could be - return true - } - - return false - } - - /** - * Constructs a [TypeStorage] instance for given [type]. - * Depending on [useConcreteType] it will or will not contain type's inheritors. - * - * @param type a type for which we want to construct type storage. - * @param useConcreteType a boolean parameter to determine whether we want to include inheritors of the type or not. - * - * @return constructed type storage. - * - * Note: [TypeStorage.possibleConcreteTypes] of the type storage returned by this method contains only - * classes we can instantiate: there will be no interfaces, abstract or local classes. - * If there are no such classes, [TypeStorage.possibleConcreteTypes] is an empty set. - * - * @see isAppropriate - */ - fun constructTypeStorage(type: Type, useConcreteType: Boolean): TypeStorage { - // create a typeStorage with concreteType even if the type belongs to an interface or an abstract class - if (useConcreteType) return TypeStorage(type) - - val baseType = type.baseType - - val inheritors = if (baseType !is RefType) { - setOf(baseType) - } else { - // use only 'appropriate' classes in the TypeStorage construction - val allInheritors = findOrConstructInheritorsIncludingTypes(baseType) - - // if the type is ArrayType, we don't have to filter abstract classes and interfaces from the inheritors - // because we still can instantiate, i.e., Number[]. - if (type is ArrayType) { - allInheritors - } else { - allInheritors.filterTo(mutableSetOf()) { it.sootClass.isAppropriate } - } - } - - val extendedInheritors = if (baseType.isJavaLangObject()) inheritors + TypeRegistry.primTypes else inheritors - - val possibleTypes = when (type) { - is RefType, is PrimType -> extendedInheritors - is ArrayType -> when (baseType) { - is RefType -> extendedInheritors.map { it.makeArrayType(type.numDimensions) }.toSet() - else -> setOf(baseType.makeArrayType(type.numDimensions)) - } - else -> error("Unexpected type $type") - } - - return TypeStorage(type, possibleTypes).filterInappropriateClassesForCodeGeneration() - } - - /** - * Remove anonymous and artificial types from the [TypeStorage] if [TypeStorage.possibleConcreteTypes] - * contains non-anonymous and non-artificial types. - * However, if the typeStorage contains only artificial and anonymous types, it becomes much more complicated. - * If leastCommonType of the typeStorage is an artificialEntity, result will contain both artificial and anonymous - * types, otherwise only anonymous types. It is required for some classes, e.g., `forEach__145`. - */ - private fun TypeStorage.filterInappropriateClassesForCodeGeneration(): TypeStorage { - val unwantedTypes = mutableSetOf() - val concreteTypes = mutableSetOf() - - val leastCommonSootClass = (leastCommonType as? RefType)?.sootClass - val keepArtificialEntities = leastCommonSootClass?.isArtificialEntity == true - - heuristic(WorkaroundReason.REMOVE_ANONYMOUS_CLASSES) { - possibleConcreteTypes.forEach { - val sootClass = (it.baseType as? RefType)?.sootClass ?: run { - // All not RefType should be included in the concreteTypes, e.g., arrays - concreteTypes += it - return@forEach - } - when { - sootClass.isAnonymous || sootClass.isUtMock -> unwantedTypes += it - sootClass.isArtificialEntity -> if (keepArtificialEntities) concreteTypes += it else Unit - workaround(WorkaroundReason.HACK) { leastCommonSootClass == OBJECT_TYPE && sootClass.isOverridden } -> Unit - else -> concreteTypes += it - } - } - } - - return if (concreteTypes.isEmpty()) { - copy(possibleConcreteTypes = unwantedTypes) - } else { - copy(possibleConcreteTypes = concreteTypes) - } - } - - /** - * Constructs a nullObject with TypeStorage containing all the inheritors for the given type - */ - fun nullObject(type: Type) = when (type) { - is RefType, is NullType, is VoidType -> ObjectValue(TypeStorage(type), nullObjectAddr) - is ArrayType -> ArrayValue(TypeStorage(type), nullObjectAddr) - else -> error("Unsupported nullType $type") - } - - fun downCast(arrayValue: ArrayValue, typeToCast: ArrayType): ArrayValue { - val typesBeforeCast = findOrConstructInheritorsIncludingTypes(arrayValue.type.baseType as RefType) - val typesAfterCast = findOrConstructInheritorsIncludingTypes(typeToCast.baseType as RefType) - val possibleTypes = typesBeforeCast.filter { it in typesAfterCast }.map { - it.makeArrayType(arrayValue.type.numDimensions) - } - - return arrayValue.copy(typeStorage = constructTypeStorage(typeToCast, possibleTypes)) - } - - fun downCast(objectValue: ObjectValue, typeToCast: RefType): ObjectValue { - val inheritorsTypes = findOrConstructInheritorsIncludingTypes(typeToCast) - val possibleTypes = objectValue.possibleConcreteTypes.filter { it in inheritorsTypes } - - return wrapper(typeToCast, objectValue.addr) ?: objectValue.copy( - typeStorage = constructTypeStorage( - typeToCast, - possibleTypes - ) - ) - } - - /** - * Connects types and number of dimensions for the two given addresses. Uses for reading from arrays: - * cell should have the same type and number of dimensions as the objects taken/put from/in it. - * It is a simplification, because the object can be subtype of the type of the cell, but it is ignored for now. - */ - fun connectArrayCeilType(ceilAddr: UtAddrExpression, valueAddr: UtAddrExpression): UtBoolExpression { - val ceilSymType = typeRegistry.symTypeId(ceilAddr) - val valueSymType = typeRegistry.symTypeId(valueAddr) - val ceilSymDimension = typeRegistry.symNumDimensions(ceilAddr) - val valueSymDimension = typeRegistry.symNumDimensions(valueAddr) - - return mkAnd(mkEq(ceilSymType, valueSymType), mkEq(ceilSymDimension, valueSymDimension)) - } - - fun findAnyConcreteInheritorIncludingOrDefaultUnsafe(evaluatedType: RefType, defaultType: RefType): RefType = - findAnyConcreteInheritorIncluding(evaluatedType) ?: findAnyConcreteInheritorIncluding(defaultType) - ?: error("No concrete types found neither for $evaluatedType, nor for $defaultType") - - fun findAnyConcreteInheritorIncludingOrDefault(evaluatedType: RefType, defaultType: RefType): RefType? = - findAnyConcreteInheritorIncluding(evaluatedType) ?: findAnyConcreteInheritorIncluding(defaultType) - - private fun findAnyConcreteInheritorIncluding(type: RefType): RefType? = - if (type.sootClass.isAppropriate) { - type - } else { - findOrConstructInheritorsIncludingTypes(type) - .filterNot { !it.hasSootClass() && (it.sootClass.isOverridden || it.sootClass.isUtMock) } - .sortedByDescending { typeRegistry.findRating(it) } - .firstOrNull { it.sootClass.isAppropriate } - } -} - -internal const val NUMBER_OF_PREFERRED_TYPES = 3 - -internal val SootField.isEnumOrdinal - get() = this.name == "ordinal" && this.declaringClass.name == ENUM_CLASSNAME - -internal val ENUM_CLASSNAME: String = java.lang.Enum::class.java.canonicalName -internal val ENUM_ORDINAL = ChunkId(ENUM_CLASSNAME, "ordinal") -internal val CLASS_REF_CLASSNAME: String = Class::class.java.canonicalName -internal val CLASS_REF_CLASS_ID = Class::class.java.id - -internal val CLASS_REF_TYPE_DESCRIPTOR: MemoryChunkDescriptor - get() = MemoryChunkDescriptor( - ChunkId(CLASS_REF_CLASSNAME, "modeledType"), - CLASS_REF_TYPE, - IntType.v() - ) - -internal val CLASS_REF_NUM_DIMENSIONS_DESCRIPTOR: MemoryChunkDescriptor - get() = MemoryChunkDescriptor( - ChunkId(CLASS_REF_CLASSNAME, "modeledNumDimensions"), - CLASS_REF_TYPE, - IntType.v() - ) - -internal val OBJECT_TYPE: RefType - get() = Scene.v().getSootClass(Object::class.java.canonicalName).type -internal val STRING_TYPE: RefType - get() = Scene.v().getSootClass(String::class.java.canonicalName).type -internal val CLASS_REF_TYPE: RefType - get() = Scene.v().getSootClass(CLASS_REF_CLASSNAME).type - -internal val HASHCODE_SIGNATURE: String = - Scene.v() - .getSootClass(Object::class.java.canonicalName) - .getMethodByName(Object::hashCode.name) - .subSignature - -internal val EQUALS_SIGNATURE: String = - Scene.v() - .getSootClass(Object::class.java.canonicalName) - .getMethodByName(Object::equals.name) - .subSignature - -/** - * Represents [java.lang.System.security] field signature. - * Hardcoded string literal because it is differently processed in Android. - */ -internal const val SECURITY_FIELD_SIGNATURE: String = "" - -/** - * Represents [sun.reflect.Reflection.fieldFilterMap] field signature. - * Hardcoded string literal because [sun.reflect.Reflection] is removed in Java 11. - */ -internal const val FIELD_FILTER_MAP_FIELD_SIGNATURE: String = "" - -/** - * Represents [sun.reflect.Reflection.methodFilterMap] field signature. - * Hardcoded string literal because [sun.reflect.Reflection] is removed in Java 11. - */ -internal const val METHOD_FILTER_MAP_FIELD_SIGNATURE: String = "" - -/** - * Special type represents string literal, which is not String Java object - */ -object SeqType : Type() { - override fun toString() = "SeqType" -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 053e1228f4..49ec333e76 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -1,275 +1,67 @@ package org.utbot.engine -import kotlinx.collections.immutable.persistentHashMapOf import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.persistentSetOf -import kotlinx.collections.immutable.toPersistentList -import kotlinx.collections.immutable.toPersistentMap -import kotlinx.collections.immutable.toPersistentSet -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Job -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.ensureActive -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.isActive -import kotlinx.coroutines.job -import kotlinx.coroutines.yield +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* import mu.KotlinLogging import org.utbot.analytics.EngineAnalyticsContext import org.utbot.analytics.FeatureProcessor import org.utbot.analytics.Predictors -import org.utbot.common.WorkaroundReason.HACK -import org.utbot.common.WorkaroundReason.REMOVE_ANONYMOUS_CLASSES -import org.utbot.common.bracket +import org.utbot.api.exception.UtMockAssumptionViolatedException import org.utbot.common.debug -import org.utbot.common.findField -import org.utbot.common.unreachableBranch -import org.utbot.common.withAccessibility -import org.utbot.common.workaround +import org.utbot.common.measureTime import org.utbot.engine.MockStrategy.NO_MOCKS -import org.utbot.engine.overrides.UtArrayMock -import org.utbot.engine.overrides.UtLogicMock -import org.utbot.engine.overrides.UtOverrideMock -import org.utbot.engine.pc.NotBoolExpression -import org.utbot.engine.pc.UtAddNoOverflowExpression -import org.utbot.engine.pc.UtAddrExpression -import org.utbot.engine.pc.UtAndBoolExpression -import org.utbot.engine.pc.UtArrayApplyForAll -import org.utbot.engine.pc.UtArrayExpressionBase -import org.utbot.engine.pc.UtArraySelectExpression -import org.utbot.engine.pc.UtArraySetRange -import org.utbot.engine.pc.UtArraySort -import org.utbot.engine.pc.UtBoolExpression -import org.utbot.engine.pc.UtBoolOpExpression -import org.utbot.engine.pc.UtBvConst -import org.utbot.engine.pc.UtBvLiteral -import org.utbot.engine.pc.UtByteSort -import org.utbot.engine.pc.UtCastExpression -import org.utbot.engine.pc.UtCharSort -import org.utbot.engine.pc.UtContextInitializer -import org.utbot.engine.pc.UtExpression -import org.utbot.engine.pc.UtFalse -import org.utbot.engine.pc.UtInstanceOfExpression -import org.utbot.engine.pc.UtIntSort -import org.utbot.engine.pc.UtIsExpression -import org.utbot.engine.pc.UtIteExpression -import org.utbot.engine.pc.UtLongSort -import org.utbot.engine.pc.UtMkTermArrayExpression -import org.utbot.engine.pc.UtNegExpression -import org.utbot.engine.pc.UtOrBoolExpression -import org.utbot.engine.pc.UtPrimitiveSort -import org.utbot.engine.pc.UtShortSort -import org.utbot.engine.pc.UtSolver -import org.utbot.engine.pc.UtSolverStatusSAT -import org.utbot.engine.pc.UtSubNoOverflowExpression -import org.utbot.engine.pc.UtTrue -import org.utbot.engine.pc.addrEq -import org.utbot.engine.pc.align -import org.utbot.engine.pc.cast -import org.utbot.engine.pc.findTheMostNestedAddr -import org.utbot.engine.pc.isInteger -import org.utbot.engine.pc.mkAnd -import org.utbot.engine.pc.mkBVConst -import org.utbot.engine.pc.mkBoolConst -import org.utbot.engine.pc.mkChar -import org.utbot.engine.pc.mkEq -import org.utbot.engine.pc.mkFalse -import org.utbot.engine.pc.mkFpConst -import org.utbot.engine.pc.mkInt -import org.utbot.engine.pc.mkNot -import org.utbot.engine.pc.mkOr -import org.utbot.engine.pc.select -import org.utbot.engine.pc.store -import org.utbot.engine.selectors.PathSelector -import org.utbot.engine.selectors.StrategyOption -import org.utbot.engine.selectors.coveredNewSelector -import org.utbot.engine.selectors.cpInstSelector -import org.utbot.engine.selectors.forkDepthSelector -import org.utbot.engine.selectors.inheritorsSelector -import org.utbot.engine.selectors.nnRewardGuidedSelector +import org.utbot.engine.pc.* +import org.utbot.engine.selectors.* import org.utbot.engine.selectors.nurs.NonUniformRandomSearch -import org.utbot.engine.selectors.pollUntilFastSAT -import org.utbot.engine.selectors.randomPathSelector -import org.utbot.engine.selectors.randomSelector import org.utbot.engine.selectors.strategies.GraphViz -import org.utbot.engine.selectors.subpathGuidedSelector -import org.utbot.engine.symbolic.HardConstraint -import org.utbot.engine.symbolic.SoftConstraint -import org.utbot.engine.symbolic.Assumption +import org.utbot.engine.state.ExecutionStackElement +import org.utbot.engine.state.ExecutionState +import org.utbot.engine.state.StateLabel import org.utbot.engine.symbolic.SymbolicState -import org.utbot.engine.symbolic.SymbolicStateUpdate import org.utbot.engine.symbolic.asHardConstraint -import org.utbot.engine.symbolic.asSoftConstraint -import org.utbot.engine.symbolic.asAssumption -import org.utbot.engine.symbolic.asUpdate +import org.utbot.engine.types.TypeRegistry +import org.utbot.engine.types.TypeResolver import org.utbot.engine.util.mockListeners.MockListener import org.utbot.engine.util.mockListeners.MockListenerController -import org.utbot.engine.util.statics.concrete.associateEnumSootFieldsWithConcreteValues -import org.utbot.engine.util.statics.concrete.isEnumAffectingExternalStatics -import org.utbot.engine.util.statics.concrete.isEnumValuesFieldName -import org.utbot.engine.util.statics.concrete.makeEnumNonStaticFieldsUpdates -import org.utbot.engine.util.statics.concrete.makeEnumStaticFieldsUpdates -import org.utbot.engine.util.statics.concrete.makeSymbolicValuesFromEnumConcreteValues import org.utbot.framework.PathSelectorType import org.utbot.framework.UtSettings -import org.utbot.framework.UtSettings.checkNpeForFinalFields import org.utbot.framework.UtSettings.checkSolverTimeoutMillis import org.utbot.framework.UtSettings.enableFeatureProcess import org.utbot.framework.UtSettings.pathSelectorStepsLimit import org.utbot.framework.UtSettings.pathSelectorType -import org.utbot.framework.UtSettings.preferredCexOption -import org.utbot.framework.UtSettings.substituteStaticsWithSymbolicVariable -import org.utbot.framework.UtSettings.useDebugVisualization import org.utbot.framework.UtSettings.processUnknownStatesDuringConcreteExecution -import org.utbot.framework.concrete.UtConcreteExecutionData -import org.utbot.framework.concrete.UtConcreteExecutionResult -import org.utbot.framework.concrete.UtExecutionInstrumentation -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.ConcreteExecutionFailureException -import org.utbot.framework.plugin.api.EnvironmentModels -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.Instruction -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.MissingState +import org.utbot.framework.UtSettings.useDebugVisualization +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.context.ConcreteExecutionContext.FuzzingContextParams +import org.utbot.framework.fuzzer.ReferencePreservingIntIdGenerator +import org.utbot.framework.plugin.api.* import org.utbot.framework.plugin.api.Step -import org.utbot.framework.plugin.api.UtConcreteExecutionFailure -import org.utbot.framework.plugin.api.UtError -import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtInstrumentation -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.framework.plugin.api.UtOverflowFailure -import org.utbot.framework.plugin.api.UtResult -import org.utbot.framework.plugin.api.classId -import org.utbot.framework.plugin.api.graph -import org.utbot.framework.plugin.api.id -import org.utbot.framework.plugin.api.onSuccess -import org.utbot.framework.plugin.api.util.executableId -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.signature -import org.utbot.framework.plugin.api.util.utContext -import org.utbot.framework.util.description -import org.utbot.framework.util.executableId -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.ModelProvider -import org.utbot.fuzzer.FallbackModelProvider -import org.utbot.fuzzer.collectConstantsForFuzzer -import org.utbot.fuzzer.defaultModelProviders -import org.utbot.fuzzer.fuzz -import org.utbot.fuzzer.names.MethodBasedNameSuggester -import org.utbot.fuzzer.names.ModelBasedNameSuggester +import org.utbot.framework.plugin.api.util.* +import org.utbot.framework.util.calculateSize +import org.utbot.framework.util.convertToAssemble +import org.utbot.framework.util.graph +import org.utbot.framework.util.sootMethod +import org.utbot.fuzzer.* +import org.utbot.fuzzing.* +import org.utbot.fuzzing.utils.Trie import org.utbot.instrumentation.ConcreteExecutor -import java.lang.reflect.ParameterizedType -import kotlin.collections.plus -import kotlin.collections.plusAssign -import kotlin.math.max -import kotlin.math.min -import kotlin.reflect.full.instanceParameter -import kotlin.reflect.full.valueParameters -import kotlin.reflect.jvm.javaType -import kotlin.system.measureTimeMillis -import soot.ArrayType -import soot.BooleanType -import soot.ByteType -import soot.CharType -import soot.DoubleType -import soot.FloatType -import soot.IntType -import soot.LongType -import soot.PrimType -import soot.RefLikeType -import soot.RefType -import soot.Scene -import soot.ShortType -import soot.SootClass -import soot.SootField -import soot.SootMethod -import soot.SootMethodRef -import soot.Type -import soot.Value -import soot.VoidType -import soot.jimple.ArrayRef -import soot.jimple.BinopExpr -import soot.jimple.ClassConstant -import soot.jimple.Constant -import soot.jimple.DefinitionStmt -import soot.jimple.DoubleConstant -import soot.jimple.Expr -import soot.jimple.FieldRef -import soot.jimple.FloatConstant -import soot.jimple.IdentityRef -import soot.jimple.IntConstant -import soot.jimple.InvokeExpr -import soot.jimple.LongConstant -import soot.jimple.MonitorStmt -import soot.jimple.NeExpr -import soot.jimple.NullConstant -import soot.jimple.ParameterRef -import soot.jimple.ReturnStmt -import soot.jimple.StaticFieldRef +import org.utbot.instrumentation.instrumentation.Instrumentation +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import org.utbot.taint.* +import org.utbot.taint.model.TaintConfiguration import soot.jimple.Stmt -import soot.jimple.StringConstant -import soot.jimple.SwitchStmt -import soot.jimple.ThisRef -import soot.jimple.internal.JAddExpr -import soot.jimple.internal.JArrayRef -import soot.jimple.internal.JAssignStmt -import soot.jimple.internal.JBreakpointStmt -import soot.jimple.internal.JCastExpr -import soot.jimple.internal.JCaughtExceptionRef -import soot.jimple.internal.JDivExpr -import soot.jimple.internal.JDynamicInvokeExpr -import soot.jimple.internal.JEqExpr -import soot.jimple.internal.JGeExpr -import soot.jimple.internal.JGotoStmt -import soot.jimple.internal.JGtExpr -import soot.jimple.internal.JIdentityStmt -import soot.jimple.internal.JIfStmt -import soot.jimple.internal.JInstanceFieldRef -import soot.jimple.internal.JInstanceOfExpr -import soot.jimple.internal.JInterfaceInvokeExpr -import soot.jimple.internal.JInvokeStmt -import soot.jimple.internal.JLeExpr -import soot.jimple.internal.JLengthExpr -import soot.jimple.internal.JLookupSwitchStmt -import soot.jimple.internal.JLtExpr -import soot.jimple.internal.JMulExpr -import soot.jimple.internal.JNeExpr -import soot.jimple.internal.JNegExpr -import soot.jimple.internal.JNewArrayExpr -import soot.jimple.internal.JNewExpr -import soot.jimple.internal.JNewMultiArrayExpr -import soot.jimple.internal.JNopStmt -import soot.jimple.internal.JRemExpr -import soot.jimple.internal.JRetStmt -import soot.jimple.internal.JReturnStmt -import soot.jimple.internal.JReturnVoidStmt -import soot.jimple.internal.JSpecialInvokeExpr -import soot.jimple.internal.JStaticInvokeExpr -import soot.jimple.internal.JSubExpr -import soot.jimple.internal.JTableSwitchStmt -import soot.jimple.internal.JThrowStmt -import soot.jimple.internal.JVirtualInvokeExpr -import soot.jimple.internal.JimpleLocal import soot.tagkit.ParamNamesTag -import soot.toolkits.graph.ExceptionalUnitGraph -import sun.reflect.Reflection -import sun.reflect.generics.reflectiveObjects.GenericArrayTypeImpl -import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl -import sun.reflect.generics.reflectiveObjects.TypeVariableImpl -import sun.reflect.generics.reflectiveObjects.WildcardTypeImpl import java.lang.reflect.Method +import java.util.function.Consumer +import kotlin.math.min +import kotlin.system.measureTimeMillis private val logger = KotlinLogging.logger {} val pathLogger = KotlinLogging.logger(logger.name + ".path") -private val CAUGHT_EXCEPTION = LocalVariable("@caughtexception") - //in future we should put all timeouts here class EngineController { var paused: Boolean = false @@ -281,8 +73,7 @@ class EngineController { //for debugging purpose only private var stateSelectedCount = 0 -//all id values of synthetic default models must be greater that for real ones -private var nextDefaultModelId = 1500_000_000 +private val defaultIdGenerator = ReferencePreservingIntIdGenerator() private fun pathSelector(graph: InterProceduralUnitGraph, typeRegistry: TypeRegistry) = when (pathSelectorType) { @@ -292,6 +83,9 @@ private fun pathSelector(graph: InterProceduralUnitGraph, typeRegistry: TypeRegi PathSelectorType.INHERITORS_SELECTOR -> inheritorsSelector(graph, typeRegistry) { withStepsLimit(pathSelectorStepsLimit) } + PathSelectorType.BFS_SELECTOR -> bfsSelector(graph, StrategyOption.VISIT_COUNTING) { + withStepsLimit(pathSelectorStepsLimit) + } PathSelectorType.SUBPATH_GUIDED_SELECTOR -> subpathGuidedSelector(graph, StrategyOption.DISTANCE) { withStepsLimit(pathSelectorStepsLimit) } @@ -301,7 +95,10 @@ private fun pathSelector(graph: InterProceduralUnitGraph, typeRegistry: TypeRegi PathSelectorType.FORK_DEPTH_SELECTOR -> forkDepthSelector(graph, StrategyOption.DISTANCE) { withStepsLimit(pathSelectorStepsLimit) } - PathSelectorType.NN_REWARD_GUIDED_SELECTOR -> nnRewardGuidedSelector(graph, StrategyOption.DISTANCE) { + PathSelectorType.ML_SELECTOR -> mlSelector(graph, StrategyOption.DISTANCE) { + withStepsLimit(pathSelectorStepsLimit) + } + PathSelectorType.TORCH_SELECTOR -> mlSelector(graph, StrategyOption.DISTANCE) { withStepsLimit(pathSelectorStepsLimit) } PathSelectorType.RANDOM_SELECTOR -> randomSelector(graph, StrategyOption.DISTANCE) { @@ -314,80 +111,104 @@ private fun pathSelector(graph: InterProceduralUnitGraph, typeRegistry: TypeRegi class UtBotSymbolicEngine( private val controller: EngineController, - private val methodUnderTest: UtMethod<*>, - private val graph: ExceptionalUnitGraph, + private val methodUnderTest: ExecutableId, classpath: String, dependencyPaths: String, - mockStrategy: MockStrategy = NO_MOCKS, + val mockStrategy: MockStrategy = NO_MOCKS, chosenClassesToMockAlways: Set, - private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis + val applicationContext: ApplicationContext, + val concreteExecutionContext: ConcreteExecutionContext, + userTaintConfigurationProvider: TaintConfigurationProvider? = null, + private val solverTimeoutInMillis: Int = checkSolverTimeoutMillis, ) : UtContextInitializer() { + private val graph = methodUnderTest.sootMethod.jimpleBody().apply { + logger.trace { "JIMPLE for $methodUnderTest:\n$this" } + }.graph() + private val methodUnderAnalysisStmts: Set = graph.stmts.toSet() - private val visitedStmts: MutableSet = mutableSetOf() private val globalGraph = InterProceduralUnitGraph(graph) - internal val typeRegistry: TypeRegistry = TypeRegistry() + private val typeRegistry: TypeRegistry = TypeRegistry() private val pathSelector: PathSelector = pathSelector(globalGraph, typeRegistry) - private val classLoader: ClassLoader - get() = utContext.classLoader - internal val hierarchy: Hierarchy = Hierarchy(typeRegistry) // TODO HACK violation of encapsulation internal val typeResolver: TypeResolver = TypeResolver(typeRegistry, hierarchy) - private val classUnderTest: ClassId = methodUnderTest.clazz.id + private val classUnderTest: ClassId = methodUnderTest.classId + + private val mocker: Mocker = Mocker( + mockStrategy, + classUnderTest, + hierarchy, + chosenClassesToMockAlways, + MockListenerController(controller), + mockerContext = applicationContext.mockerContext, + ) - private val mocker: Mocker = Mocker(mockStrategy, classUnderTest, hierarchy, chosenClassesToMockAlways, MockListenerController(controller)) + private val stateListeners: MutableList = mutableListOf(); - private val statesForConcreteExecution: MutableList = mutableListOf() + fun addListener(listener: ExecutionStateListener): UtBotSymbolicEngine { + stateListeners += listener + return this + } + + fun removeListener(listener: ExecutionStateListener): UtBotSymbolicEngine { + stateListeners -= listener + return this + } - lateinit var environment: Environment - private val solver: UtSolver - get() = environment.state.solver + fun attachMockListener(mockListener: MockListener) = mocker.mockListenerController?.attach(mockListener) - // TODO HACK violation of encapsulation - val memory: Memory - get() = environment.state.memory + fun detachMockListener(mockListener: MockListener) = mocker.mockListenerController?.detach(mockListener) - private val localVariableMemory: LocalVariableMemory - get() = environment.state.localVariableMemory + private val statesForConcreteExecution: MutableList = mutableListOf() + private val taintConfigurationProvider = if (UtSettings.useTaintAnalysis) { + TaintConfigurationProviderCombiner( + listOf( + userTaintConfigurationProvider ?: TaintConfigurationProviderEmpty, + TaintConfigurationProviderCached("resources", TaintConfigurationProviderResources()) + ) + ) + } else { + TaintConfigurationProviderEmpty + } + private val taintConfiguration: TaintConfiguration = run { + val config = taintConfigurationProvider.getConfiguration() + logger.debug { "Taint analysis configuration: $config" } + config + } + + private val taintMarkRegistry: TaintMarkRegistry = TaintMarkRegistry() + private val taintMarkManager: TaintMarkManager = TaintMarkManager(taintMarkRegistry) + private val taintContext: TaintContext = TaintContext(taintMarkManager, taintConfiguration) + + private val traverser = Traverser( + methodUnderTest, + typeRegistry, + hierarchy, + typeResolver, + globalGraph, + mocker, + applicationContext.typeReplacer, + applicationContext.nonNullSpeculator, + taintContext, + ) //HACK (long strings) internal var softMaxArraySize = 40 private val concreteExecutor = ConcreteExecutor( - UtExecutionInstrumentation, + concreteExecutionContext.instrumentationFactory, classpath, - dependencyPaths ).apply { this.classLoader = utContext.classLoader } - /** - * Contains information about the generic types used in the parameters of the method under test. - */ - private val parameterAddrToGenericType = mutableMapOf() - - private val preferredCexInstanceCache = mutableMapOf>() - - private var queuedSymbolicStateUpdates = SymbolicStateUpdate() - private val featureProcessor: FeatureProcessor? = if (enableFeatureProcess) EngineAnalyticsContext.featureProcessorFactory(globalGraph) else null - private val insideStaticInitializer - get() = environment.state.executionStack.any { it.method.isStaticInitializer } - - internal fun findNewAddr() = typeRegistry.findNewAddr(insideStaticInitializer).also { touchAddress(it) } - - // Counter used for a creation symbolic results of "hashcode" and "equals" methods. - private var equalsCounter = 0 - private var hashcodeCounter = 0 - - // A counter for objects created as native method call result. - private var unboundedConstCounter = 0 private val trackableResources: MutableSet = mutableSetOf() @@ -413,6 +234,22 @@ class UtBotSymbolicEngine( .onStart { preTraverse() } .onCompletion { postTraverse() } + /** + * Traverse through all states and get results. + * + * This method is supposed to used when calling [traverse] is not suitable, + * e.g. from Java programs. It runs traversing with blocking style using callback + * to provide [UtResult]. + */ + @JvmOverloads + fun traverseAll(consumer: Consumer = Consumer { }) { + runBlocking { + traverse().collect { + consumer.accept(it) + } + } + } + private fun traverseImpl(): Flow = flow { require(trackableResources.isEmpty()) @@ -428,10 +265,6 @@ class UtBotSymbolicEngine( pathSelector.offer(initState) - environment = Environment( - method = globalGraph.method(initStmt), - state = initState - ) pathSelector.use { while (currentCoroutineContext().isActive) { @@ -448,11 +281,12 @@ class UtBotSymbolicEngine( } stateSelectedCount++ - pathLogger.trace { "traverse<$methodUnderTest>: choosing next state($stateSelectedCount), " + + pathLogger.trace { + "traverse<$methodUnderTest>: choosing next state($stateSelectedCount), " + "queue size=${(pathSelector as? NonUniformRandomSearch)?.size ?: -1}" } - if (controller.executeConcretely || statesForConcreteExecution.isNotEmpty()) { + if (UtSettings.useConcreteExecution && (controller.executeConcretely || statesForConcreteExecution.isNotEmpty())) { val state = pathSelector.pollUntilFastSAT() ?: statesForConcreteExecution.pollUntilSat(processUnknownStatesDuringConcreteExecution) ?: break @@ -462,18 +296,17 @@ class UtBotSymbolicEngine( logger.trace { "executing $state concretely..." } - environment.state = state - - logger.debug().bracket("concolicStrategy<$methodUnderTest>: execute concretely") { + logger.debug().measureTime({ "concolicStrategy<$methodUnderTest>: execute concretely"} ) { val resolver = Resolver( hierarchy, - memory, + state.memory, typeRegistry, typeResolver, state.solver.lastStatus as UtSolverStatusSAT, methodUnderTest, - softMaxArraySize + softMaxArraySize, + traverser.objectCounter ) val resolvedParameters = state.methodUnderTestParameters @@ -482,13 +315,22 @@ class UtBotSymbolicEngine( try { val concreteExecutionResult = - concreteExecutor.executeConcretely(methodUnderTest, stateBefore, instrumentation) + concreteExecutor.executeConcretely(methodUnderTest, stateBefore, instrumentation, UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis) + + if (failureCanBeProcessedGracefully(concreteExecutionResult, executionToRollbackOn = null)) { + return@measureTime + } + + if (concreteExecutionResult.violatesUtMockAssumption()) { + logger.debug { "Generated test case violates the UtMock assumption: $concreteExecutionResult" } + return@measureTime + } - val concreteUtExecution = UtExecution( + val concreteUtExecution = UtSymbolicExecution( stateBefore, concreteExecutionResult.stateAfter, concreteExecutionResult.result, - instrumentation, + concreteExecutionResult.newInstrumentation ?: instrumentation, mutableListOf(), listOf(), concreteExecutionResult.coverage @@ -498,13 +340,21 @@ class UtBotSymbolicEngine( logger.debug { "concolicStrategy<${methodUnderTest}>: returned $concreteUtExecution" } } catch (e: CancellationException) { logger.debug(e) { "Cancellation happened" } - } catch (e: ConcreteExecutionFailureException) { + } catch (e: InstrumentedProcessDeathException) { emitFailedConcreteExecutionResult(stateBefore, e) } catch (e: Throwable) { emit(UtError("Concrete execution failed", e)) } } + // I am not sure this part works correctly when concrete execution is enabled. + // todo test this part more accurate + try { + fireExecutionStateEvent(state) + } catch (ce: CancellationException) { + break + } + } else { val state = pathSelector.poll() @@ -528,47 +378,44 @@ class UtBotSymbolicEngine( } state.executingTime += measureTimeMillis { - environment.state = state - - val currentStmt = environment.state.stmt - - if (currentStmt !in visitedStmts) { - environment.state.updateIsVisitedNew() - visitedStmts += currentStmt + val newStates = try { + traverser.traverse(state) + } catch (ex: Throwable) { + emit(UtError(ex.description, ex)) + return@measureTimeMillis } - - environment.method = globalGraph.method(currentStmt) - - environment.state.lastEdge?.let { - globalGraph.visitEdge(it) + for (newState in newStates) { + when (newState.label) { + StateLabel.INTERMEDIATE -> pathSelector.offer(newState) + StateLabel.CONCRETE -> statesForConcreteExecution.add(newState) + StateLabel.TERMINAL -> consumeTerminalState(newState) + } } - try { - val exception = environment.state.exception - if (exception != null) { - traverseException(currentStmt, exception) - } else { - traverseStmt(currentStmt) - } + // Here job can be cancelled from within traverse, e.g. by using force mocking without Mockito. + // So we need to make it throw CancelledException by method below: + currentCoroutineContext().job.ensureActive() + } - // Here job can be cancelled from within traverse, e.g. by using force mocking without Mockito. - // So we need to make it throw CancelledException by method below: - currentCoroutineContext().job.ensureActive() - } catch (ex: Throwable) { - environment.state.close() - - if (ex !is CancellationException) { - logger.error(ex) { "Test generation failed on stmt $currentStmt, symbolic stack trace:\n$symbolicStackTrace" } - // TODO: enrich with nice description for known issues - emit(UtError(ex.description, ex)) - } else { - logger.debug(ex) { "Cancellation happened" } - } - } + // TODO: think about concise modifying globalGraph in Traverser and UtBotSymbolicEngine + globalGraph.visitNode(state) + + try { + fireExecutionStateEvent(state) + } catch (ce: CancellationException) { + break } } - queuedSymbolicStateUpdates = SymbolicStateUpdate() - globalGraph.visitNode(environment.state) + } + } + } + + private fun fireExecutionStateEvent(state: ExecutionState) { + stateListeners.forEach { l -> + try { + l.visit(globalGraph, state) + } catch (t: Throwable) { + logger.error(t) { "$l failed with error" } } } } @@ -578,3292 +425,390 @@ class UtBotSymbolicEngine( * Run fuzzing flow. * * @param until is used by fuzzer to cancel all tasks if the current time is over this value - * @param modelProvider provides model values for a method */ - fun fuzzing(until: Long = Long.MAX_VALUE, modelProvider: (ModelProvider) -> ModelProvider = { it }) = flow { - val executableId = if (methodUnderTest.isConstructor) { - methodUnderTest.javaConstructor!!.executableId - } else { - methodUnderTest.javaMethod!!.executableId - } - - val isFuzzable = executableId.parameters.all { classId -> - classId != Method::class.java.id && // causes the child process crash at invocation - classId != Class::class.java.id // causes java.lang.IllegalAccessException: java.lang.Class at sun.misc.Unsafe.allocateInstance(Native Method) + fun fuzzing(until: Long = Long.MAX_VALUE) = flow { + val isFuzzable = methodUnderTest.parameters.all { classId -> + classId != Method::class.java.id && // causes the instrumented process crash at invocation + classId != Class::class.java.id // causes java.lang.IllegalAccessException: java.lang.Class at sun.misc.Unsafe.allocateInstance(Native Method) } if (!isFuzzable) { return@flow } + val errorStackTraceTracker = Trie(StackTraceElement::toString) + var attempts = 0 + val attemptsLimit = UtSettings.fuzzingMaxAttempts + val names = graph.body.method.tags.filterIsInstance().firstOrNull()?.names ?: emptyList() + var testEmittedByFuzzer = 0 + + val fuzzingContext = try { + concreteExecutionContext.tryCreateFuzzingContext( + FuzzingContextParams( + concreteExecutor = concreteExecutor, + classUnderTest = classUnderTest, + idGenerator = defaultIdGenerator, + fuzzingStartTimeMillis = System.currentTimeMillis(), + fuzzingEndTimeMillis = until, + mockStrategy = mockStrategy, + ) + ) + } catch (e: Exception) { + emit(UtError(e.message ?: "Failed to create ValueProvider", e)) + return@flow + } - val fallbackModelProvider = FallbackModelProvider { nextDefaultModelId++ } + val coverageToMinStateBeforeSize = mutableMapOf, Int>() + + runJavaFuzzing( + defaultIdGenerator, + methodUnderTest, + constants = collectConstantsForFuzzer(graph), + names = names, + providers = listOf(fuzzingContext.valueProvider), + ) { thisInstance, descr, values -> + val diff = until - System.currentTimeMillis() + val thresholdMillisForFuzzingOperation = 0 // may be better use 10-20 millis as it might not be possible + // to concretely execute that values because request to instrumentation process involves + // 1. serializing/deserializing it with kryo + // 2. sending over rd + // 3. concrete execution itself + // 4. analyzing concrete result + if (controller.job?.isActive == false || diff <= thresholdMillisForFuzzingOperation) { + logger.info { "Fuzzing overtime: $methodUnderTest" } + logger.info { "Test created by fuzzer: $testEmittedByFuzzer" } + return@runJavaFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.STOP) + } - val thisInstance = when { - methodUnderTest.isStatic -> null - methodUnderTest.isConstructor -> if ( - methodUnderTest.clazz.isAbstract || // can't instantiate abstract class - methodUnderTest.clazz.java.isEnum // can't reflectively create enum objects - ) { - return@flow - } else { - null + if (thisInstance?.model is UtNullModel) { + // We should not try to run concretely any models with null-this. + // But fuzzer does generate such values, because it can fail to generate any "good" values. + return@runJavaFuzzing BaseFeedback(Trie.emptyNode(), Control.PASS) } - else -> { - fallbackModelProvider.toModel(methodUnderTest.clazz).apply { - if (this is UtNullModel) { // it will definitely fail because of NPE, - return@flow - } - } + + val stateBefore = fuzzingContext.createStateBefore( + thisInstance = thisInstance?.model, + parameters = values.map { it.model }, + statics = emptyMap(), + executableToCall = methodUnderTest, + ) + + val concreteExecutionResult: UtConcreteExecutionResult? = try { + val timeoutMillis = min(UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis, diff) + concreteExecutor.executeConcretely(methodUnderTest, stateBefore, listOf(), timeoutMillis) + } catch (e: CancellationException) { + logger.debug { "Cancelled by timeout" }; null + } catch (e: InstrumentedProcessDeathException) { + emitFailedConcreteExecutionResult(stateBefore, e); null + } catch (e: Throwable) { + emit(UtError("Default concrete execution failed", e)); null } - } - val methodUnderTestDescription = FuzzedMethodDescription(executableId, collectConstantsForFuzzer(graph)).apply { - compilableName = if (methodUnderTest.isMethod) executableId.name else null - val names = graph.body.method.tags.filterIsInstance().firstOrNull()?.names - parameterNameMap = { index -> names?.getOrNull(index) } - } - val modelProviderWithFallback = modelProvider(defaultModelProviders { nextDefaultModelId++ }).withFallback(fallbackModelProvider::toModel) - val coveredInstructionTracker = mutableSetOf() - var attempts = UtSettings.fuzzingMaxAttempts - fuzz(methodUnderTestDescription, modelProviderWithFallback).forEach { values -> - if (System.currentTimeMillis() >= until) { - logger.info { "Fuzzing overtime: $methodUnderTest" } - return@flow + // in case an exception occurred from the concrete execution + concreteExecutionResult ?: return@runJavaFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.PASS) + + fuzzingContext.handleFuzzedConcreteExecutionResult(methodUnderTest, concreteExecutionResult) + + // in case of processed failure in the concrete execution + concreteExecutionResult.processedFailure()?.let { failure -> + logger.debug { "Instrumented process failed with exception ${failure.exception} before concrete execution started" } + return@runJavaFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.PASS) } - val initialEnvironmentModels = EnvironmentModels(thisInstance, values.map { it.model }, mapOf()) + if (concreteExecutionResult.violatesUtMockAssumption()) { + logger.debug { "Generated test case by fuzzer violates the UtMock assumption: $concreteExecutionResult" } + return@runJavaFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.PASS) + } - try { - val concreteExecutionResult = - concreteExecutor.executeConcretely(methodUnderTest, initialEnvironmentModels, listOf()) - - workaround(REMOVE_ANONYMOUS_CLASSES) { - concreteExecutionResult.result.onSuccess { - if (it.classId.isAnonymous) { - logger.debug("Anonymous class found as a concrete result, symbolic one will be returned") - return@flow - } - } - } + val result = concreteExecutionResult.result + val coveredInstructions = concreteExecutionResult.coverage.coveredInstructions + var trieNode: Trie.Node? = null + + if (coveredInstructions.isNotEmpty()) { + trieNode = descr.tracer.add(coveredInstructions) - if (!coveredInstructionTracker.addAll(concreteExecutionResult.coverage.coveredInstructions)) { - if (--attempts < 0) { - return@flow + val earlierStateBeforeSize = coverageToMinStateBeforeSize[trieNode] + val curStateBeforeSize = concreteExecutionResult.stateBefore.calculateSize() + + if (earlierStateBeforeSize == null || curStateBeforeSize < earlierStateBeforeSize) + coverageToMinStateBeforeSize[trieNode] = curStateBeforeSize + else { + if (++attempts >= attemptsLimit) { + return@runJavaFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.STOP) } + return@runJavaFuzzing BaseFeedback(result = trieNode, control = Control.CONTINUE) } - - val nameSuggester = sequenceOf(ModelBasedNameSuggester(), MethodBasedNameSuggester()) - val testMethodName = try { - nameSuggester.flatMap { it.suggest(methodUnderTestDescription, values, concreteExecutionResult.result) }.firstOrNull() - } catch (t: Throwable) { - logger.error(t) { "Cannot create suggested test name for ${methodUnderTest.displayName}" } - null + } else { + logger.error { "Coverage is empty for $methodUnderTest with $values" } + if (result is UtSandboxFailure) { + val stackTraceElements = result.exception.stackTrace.reversed() + if (errorStackTraceTracker.add(stackTraceElements).count > 1) { + return@runJavaFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.PASS) + } } + } - emit( - UtExecution( - stateBefore = initialEnvironmentModels, - stateAfter = concreteExecutionResult.stateAfter, - result = concreteExecutionResult.result, - instrumentation = emptyList(), - path = mutableListOf(), - fullPath = emptyList(), - coverage = concreteExecutionResult.coverage, - testMethodName = testMethodName?.testName, - displayName = testMethodName?.displayName - ) + emit( + UtFuzzedExecution( + stateBefore = concreteExecutionResult.stateBefore, + stateAfter = concreteExecutionResult.stateAfter, + result = concreteExecutionResult.result, + coverage = concreteExecutionResult.coverage, + fuzzingValues = values, + fuzzedMethodDescription = descr.description, + instrumentation = concreteExecutionResult.newInstrumentation ?: emptyList() ) - } catch (e: CancellationException) { - logger.debug { "Cancelled by timeout" } - } catch (e: ConcreteExecutionFailureException) { - emitFailedConcreteExecutionResult(initialEnvironmentModels, e) - } catch (e: Throwable) { - emit(UtError("Default concrete execution failed", e)) - } + ) + + testEmittedByFuzzer++ + BaseFeedback(result = trieNode ?: Trie.emptyNode(), control = Control.CONTINUE) } } private suspend fun FlowCollector.emitFailedConcreteExecutionResult( stateBefore: EnvironmentModels, - e: ConcreteExecutionFailureException + e: Throwable ) { - val failedConcreteExecution = UtExecution( + val failedConcreteExecution = UtFailedExecution( stateBefore = stateBefore, - stateAfter = MissingState, - result = UtConcreteExecutionFailure(e), - instrumentation = emptyList(), - path = mutableListOf(), - fullPath = listOf() + result = UtConcreteExecutionFailure(e) ) emit(failedConcreteExecution) } + private suspend fun FlowCollector.consumeTerminalState( + state: ExecutionState, + ) { + // some checks to be sure the state is correct + require(state.label == StateLabel.TERMINAL) { "Can't process non-terminal state!" } + require(!state.isInNestedMethod()) { "The state has to correspond to the MUT" } + + val memory = state.memory + val solver = state.solver + val parameters = state.parameters.map { it.value } + val symbolicResult = requireNotNull(state.methodResult?.symbolicResult) { "The state must have symbolicResult" } + val holder = if (UtSettings.disableUnsatChecking) { + (solver.lastStatus as? UtSolverStatusSAT) ?: return + } else { + requireNotNull(solver.lastStatus as? UtSolverStatusSAT) { "The state must be SAT!" } + } + + val predictedTestName = Predictors.testName.predict(state.path) + Predictors.testName.provide(state.path, predictedTestName, "") + + // resolving + val resolver = Resolver( + hierarchy, + memory, + typeRegistry, + typeResolver, + holder, + methodUnderTest, + softMaxArraySize, + traverser.objectCounter + ) - private suspend fun FlowCollector.traverseStmt(current: Stmt) { - if (doPreparatoryWorkIfRequired(current)) return - - when (current) { - is JAssignStmt -> traverseAssignStmt(current) - is JIdentityStmt -> traverseIdentityStmt(current) - is JIfStmt -> traverseIfStmt(current) - is JInvokeStmt -> traverseInvokeStmt(current) - is SwitchStmt -> traverseSwitchStmt(current) - is JReturnStmt -> processResult(current.symbolicSuccess()) - is JReturnVoidStmt -> processResult(null) - is JRetStmt -> error("This one should be already removed by Soot: $current") - is JThrowStmt -> traverseThrowStmt(current) - is JBreakpointStmt -> pathSelector.offer(environment.state.updateQueued(globalGraph.succ(current))) - is JGotoStmt -> pathSelector.offer(environment.state.updateQueued(globalGraph.succ(current))) - is JNopStmt -> pathSelector.offer(environment.state.updateQueued(globalGraph.succ(current))) - is MonitorStmt -> pathSelector.offer(environment.state.updateQueued(globalGraph.succ(current))) - is DefinitionStmt -> TODO("$current") - else -> error("Unsupported: ${current::class}") - } - } - - /** - * Handles preparatory work for static initializers and multi-dimensional arrays creation. - * - * For instance, it could push handmade graph with preparation statements to the path selector. - * - * Returns: - * - True if work is required and the constructed graph was pushed. In this case current - * traverse stops and continues after the graph processing; - * - False if preparatory work is not required or it is already done. - * environment.state.methodResult can contain the work result. - */ - private suspend fun FlowCollector.doPreparatoryWorkIfRequired(current: Stmt): Boolean { - if (current !is JAssignStmt) return false - - return when { - processStaticInitializerIfRequired(current) -> true - unfoldMultiArrayExprIfRequired(current) -> true - else -> false - } - } - - /** - * Handles preparatory work for static initializers. To do it, this method checks if any parts of the given - * statement is StaticRefField and the class this field belongs to hasn't been initialized yet. - * If so, it pushes a graph of the corresponding `` to the path selector. - * - * Returns: - * - True if the work is required and the graph was pushed. In this case current - * traversal stops and continues after the graph processing; - * - False if preparatory work is not required or it is already done. In this case a result from the - * environment.state.methodResult already processed and applied. - * - * Note: similar but more granular approach used if Engine decides to process static field concretely. - */ - private suspend fun FlowCollector.processStaticInitializerIfRequired(stmt: JAssignStmt): Boolean { - val right = stmt.rightOp - val left = stmt.leftOp - val method = environment.method - val declaringClass = method.declaringClass - val result = listOf(right, left) - .filterIsInstance() - .filterNot { insideStaticInitializer(it, method, declaringClass) } - .firstOrNull { processStaticInitializer(it, stmt) } - - return result != null - } - - /** - * Handles preparatory work for multi-dimensional arrays. Constructs unfolded representation for - * JNewMultiArrayExpr in the [unfoldMultiArrayExpr]. - * - * Returns: - * - True if right part of the JAssignStmt contains JNewMultiArrayExpr and there is no calculated result in the - * environment.state.methodResult. - * - False otherwise - */ - private fun unfoldMultiArrayExprIfRequired(stmt: JAssignStmt): Boolean { - // We have already unfolded the statement and processed constructed graph, have the calculated result - if (environment.state.methodResult != null) return false - - val right = stmt.rightOp - if (right !is JNewMultiArrayExpr) return false + val (modelsBefore, modelsAfter, instrumentation) = resolver.resolveModels(parameters) - val graph = unfoldMultiArrayExpr(stmt) - val resolvedSizes = right.sizes.map { (it.resolve(IntType.v()) as PrimitiveValue).align() } + val symbolicExecutionResult = resolver.resolveResult(symbolicResult) - negativeArraySizeCheck(*resolvedSizes.toTypedArray()) + val stateBefore = modelsBefore.constructStateForMethod(methodUnderTest) + val stateAfter = modelsAfter.constructStateForMethod(methodUnderTest) + require(stateBefore.parameters.size == stateAfter.parameters.size) - pushToPathSelector(graph, caller = null, resolvedSizes) - return true - } + val symbolicUtExecution = UtSymbolicExecution( + stateBefore = stateBefore, + stateAfter = stateAfter, + result = symbolicExecutionResult, + instrumentation = instrumentation, + path = entryMethodPath(state), + fullPath = state.fullPath(), + symbolicSteps = getSymbolicPath(state, symbolicResult) + ) - /** - * Processes static initialization for class. - * - * If class is not initialized yet, creates graph for that and pushes to the path selector; - * otherwise class is initialized and environment.state.methodResult can contain initialization result. - * - * If contains, adds state with the last edge to the path selector; - * if doesn't contain, it's already processed few steps before, nothing to do. - * - * Returns true if processing takes place and Engine should end traversal of current statement. - */ - private suspend fun FlowCollector.processStaticInitializer( - fieldRef: StaticFieldRef, - stmt: Stmt - ): Boolean { - if (shouldProcessStaticFieldConcretely(fieldRef)) { - return processStaticFieldConcretely(fieldRef, stmt) - } + globalGraph.traversed(state) - val field = fieldRef.field - val declaringClass = field.declaringClass - val declaringClassId = declaringClass.id - val methodResult = environment.state.methodResult - if (!memory.isInitialized(declaringClassId) && - !isStaticInstanceInMethodResult(declaringClassId, methodResult) + if (!UtSettings.useConcreteExecution || + // Can't execute concretely because overflows do not cause actual exceptions. + // Still, we need overflows to act as implicit exceptions. + (UtSettings.treatOverflowAsError && symbolicExecutionResult is UtOverflowFailure) || + // the same for taint analysis errors + (UtSettings.useTaintAnalysis && symbolicExecutionResult is UtTaintAnalysisFailure) ) { - val initializer = declaringClass.staticInitializerOrNull() - return if (initializer == null) { - false - } else { - val graph = classInitGraph(initializer) - pushToPathSelector(graph, null, emptyList()) - true + logger.debug { + "processResult<${methodUnderTest}>: no concrete execution allowed, " + + "emit purely symbolic result $symbolicUtExecution" } + emit(symbolicUtExecution) + return } - val result = methodResult ?: return false - - when (result.symbolicResult) { - // This branch could be useful if we have a static field, i.e. x = 5 / 0 - is SymbolicFailure -> traverseException(stmt, result.symbolicResult) - is SymbolicSuccess -> pathSelector.offer( - environment.state.updateQueued( - environment.state.lastEdge!!, - result.symbolicStateUpdate - ) - ) - } - return true - } - - /** - * Decides should we read this static field concretely or not. - */ - private fun shouldProcessStaticFieldConcretely(fieldRef: StaticFieldRef): Boolean { - workaround(HACK) { - val className = fieldRef.field.declaringClass.name - - // We should process clinit sections for classes from these packages. - // Note that this list is not exhaustive, so it may be supplemented in the future. - val packagesToProcessConcretely = javaPackagesToProcessConcretely + sunPackagesToProcessConcretely - - val declaringClass = fieldRef.field.declaringClass - - val isFromPackageToProcessConcretely = packagesToProcessConcretely.any { className.startsWith(it) } - // it is required to remove classes we override, since - // we could accidentally initialize their final fields - // with values that will later affect our overridden classes - && fieldRef.field.declaringClass.type !in classToWrapper.keys - // because of the same reason we should not use - // concrete information from clinit sections for enums - && !fieldRef.field.declaringClass.isEnum - //hardcoded string for class name is used cause class is not public - //this is a hack to avoid crashing on code with Math.random() - && !className.endsWith("RandomNumberGeneratorHolder") - - // we can process concretely only enums that does not affect the external system - val isEnumNotAffectingExternalStatics = declaringClass.let { - it.isEnum && !it.isEnumAffectingExternalStatics(typeResolver) + // Check for lambda result as it cannot be emitted by concrete execution + (symbolicExecutionResult as? UtExecutionSuccess)?.takeIf { it.model is UtLambdaModel }?.run { + logger.debug { + "processResult<${methodUnderTest}>: impossible to create concrete value for lambda result ($model), " + + "emit purely symbolic result $symbolicUtExecution" } - return isEnumNotAffectingExternalStatics || isFromPackageToProcessConcretely - } - } - - private val javaPackagesToProcessConcretely = listOf( - "applet", "awt", "beans", "io", "lang", "math", "net", - "nio", "rmi", "security", "sql", "text", "time", "util" - ).map { "java.$it" } - - private val sunPackagesToProcessConcretely = listOf( - "applet", "audio", "awt", "corba", "font", "instrument", - "invoke", "io", "java2d", "launcher", "management", "misc", - "net", "nio", "print", "reflect", "rmi", "security", - "swing", "text", "tools.jar", "tracing", "util" - ).map { "sun.$it" } - - /** - * Checks if field was processed (read) already. - * Otherwise offers to path selector the same statement, but with memory and constraints updates for this field. - * - * Returns true if processing takes place and Engine should end traversal of current statement. - */ - private fun processStaticFieldConcretely(fieldRef: StaticFieldRef, stmt: Stmt): Boolean { - val field = fieldRef.field - val fieldId = field.fieldId - if (memory.isInitialized(fieldId)) { - return false + emit(symbolicUtExecution) + return } - // Gets concrete value, converts to symbolic value - val declaringClass = field.declaringClass + if (checkStaticMethodsMock(symbolicUtExecution)) { + logger.debug { + buildString { + append("processResult<${methodUnderTest}>: library static methods mock found ") + append("(we do not support it in concrete execution yet), ") + append("emit purely symbolic result $symbolicUtExecution") + } + } - val (edge, updates) = if (declaringClass.isEnum) { - makeConcreteUpdatesForEnums(fieldId, declaringClass, stmt) - } else { - makeConcreteUpdatesForNonEnumStaticField(field, fieldId, declaringClass) + emit(symbolicUtExecution) + return } - val newState = environment.state.updateQueued(edge, updates) - pathSelector.offer(newState) - - return true - } - - private fun makeConcreteUpdatesForEnums( - fieldId: FieldId, - declaringClass: SootClass, - stmt: Stmt - ): Pair { - val type = declaringClass.type - val jClass = type.id.jClass - - // symbolic value for enum class itself - val enumClassValue = findOrCreateStaticObject(type) - - // values for enum constants - val enumConstantConcreteValues = jClass.enumConstants.filterIsInstance>() - - val (enumConstantSymbolicValues, enumConstantSymbolicResultsByName) = - makeSymbolicValuesFromEnumConcreteValues(type, enumConstantConcreteValues) - - val enumFields = typeResolver.findFields(type) - val sootFieldsWithRuntimeValues = - associateEnumSootFieldsWithConcreteValues(enumFields, enumConstantConcreteValues) - - val (staticFields, nonStaticFields) = sootFieldsWithRuntimeValues.partition { it.first.isStatic } - - val (staticFieldUpdates, curFieldSymbolicValueForLocalVariable) = makeEnumStaticFieldsUpdates( - staticFields, - declaringClass, - enumConstantSymbolicResultsByName, - enumConstantSymbolicValues, - enumClassValue, - fieldId - ) - - val nonStaticFieldsUpdates = makeEnumNonStaticFieldsUpdates(enumConstantSymbolicValues, nonStaticFields) + //It's possible that symbolic and concrete stateAfter/results are diverged. + //So we trust concrete results more. + try { + logger.debug().measureTime({ "processResult<$methodUnderTest>: concrete execution" } ) { - // we do not mark static fields for enum constants and $VALUES as meaningful - // because we should not set them in generated code - val meaningfulStaticFields = staticFields.filterNot { - val name = it.first.name + //this can throw CancellationException + val concreteExecutionResult = concreteExecutor.executeConcretely( + methodUnderTest, + stateBefore, + instrumentation, + UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis + ) - name in enumConstantSymbolicResultsByName.keys || isEnumValuesFieldName(name) - } + if (failureCanBeProcessedGracefully(concreteExecutionResult, symbolicUtExecution)) { + return + } - val initializedStaticFieldsMemoryUpdate = MemoryUpdate( - initializedStaticFields = staticFields.associate { it.first.fieldId to it.second.single() }.toPersistentMap(), - meaningfulStaticFields = meaningfulStaticFields.map { it.first.fieldId }.toPersistentSet() - ) + if (concreteExecutionResult.violatesUtMockAssumption()) { + logger.debug { "Generated test case violates the UtMock assumption: $concreteExecutionResult" } + return + } - var allUpdates = staticFieldUpdates + nonStaticFieldsUpdates + initializedStaticFieldsMemoryUpdate - - // we need to make locals update if it is an assignment statement - // for enums we have only two types for assignment with enums — enum constant or $VALUES field - // for example, a jimple body for Enum::values method starts with the following lines: - // public static ClassWithEnum$StatusEnum[] values() - // { - // ClassWithEnum$StatusEnum[] $r0, $r2; - // java.lang.Object $r1; - // $r0 = ; - // $r1 = virtualinvoke $r0.(); - - // so, we have to make an update for the local $r0 - if (stmt is JAssignStmt) { - val local = stmt.leftOp as JimpleLocal - val localUpdate = localMemoryUpdate( - local.variable to curFieldSymbolicValueForLocalVariable - ) + val concolicUtExecution = symbolicUtExecution.copy( + stateAfter = concreteExecutionResult.stateAfter, + result = concreteExecutionResult.result, + coverage = concreteExecutionResult.coverage, + instrumentation = concreteExecutionResult.newInstrumentation ?: instrumentation + ) - allUpdates += localUpdate + emit(concolicUtExecution) + logger.debug { "processResult<${methodUnderTest}>: returned $concolicUtExecution" } + } + } catch (e: InstrumentedProcessDeathException) { + emitFailedConcreteExecutionResult(stateBefore, e) + } catch (e: CancellationException) { + logger.debug(e) { "Cancellation happened" } + } catch (e: Throwable) { + emit(UtError("Default concrete execution failed", e)) } - - // enum static initializer can be the first statement in method so there will be no last edge - // for example, as it is during Enum::values method analysis: - // public static ClassWithEnum$StatusEnum[] values() - // { - // ClassWithEnum$StatusEnum[] $r0, $r2; - // java.lang.Object $r1; - - // $r0 = ; - val edge = environment.state.lastEdge ?: globalGraph.succ(stmt) - - return edge to allUpdates } - private fun makeConcreteUpdatesForNonEnumStaticField( - field: SootField, - fieldId: FieldId, - declaringClass: SootClass - ): Pair { - val concreteValue = extractConcreteValue(field, declaringClass) - val (symbolicResult, symbolicStateUpdate) = toMethodResult(concreteValue, field.type) - val symbolicValue = (symbolicResult as SymbolicSuccess).value - - // Collects memory updates - val initializedFieldUpdate = - MemoryUpdate(initializedStaticFields = persistentHashMapOf(fieldId to concreteValue)) - - val objectUpdate = objectUpdate( - instance = findOrCreateStaticObject(declaringClass.type), - field = field, - value = valueToExpression(symbolicValue, field.type) - ) - val allUpdates = symbolicStateUpdate + initializedFieldUpdate + objectUpdate + private suspend fun FlowCollector.failureCanBeProcessedGracefully( + concreteExecutionResult: UtConcreteExecutionResult, + executionToRollbackOn: UtExecution?, + ): Boolean { + concreteExecutionResult.processedFailure()?.let { failure -> + // If concrete execution failed to some reasons that are not process death or cancellation + // when we call something that is processed successfully by symbolic engine, + // we should: + // - roll back to symbolic execution data ignoring failing concrete (is symbolic execution exists); + // - do not emit an execution if there is nothing to roll back on. - return environment.state.lastEdge!! to allUpdates - } + // Note that this situation is suspicious anyway, so we log a WARN message about the failure. + executionToRollbackOn?.let { + emit(it) + } - // Some fields are inaccessible with reflection, so we have to instantiate it by ourselves. - // Otherwise, extract it from the class. - // TODO JIRA:1593 - private fun extractConcreteValue(field: SootField, declaringClass: SootClass): Any? = - when (field.signature) { - SECURITY_FIELD_SIGNATURE -> SecurityManager() - FIELD_FILTER_MAP_FIELD_SIGNATURE -> mapOf(Reflection::class to arrayOf("fieldFilterMap", "methodFilterMap")) - METHOD_FILTER_MAP_FIELD_SIGNATURE -> emptyMap, Array>() - else -> declaringClass.id.jClass.findField(field.name).let { it.withAccessibility { it.get(null) } } + logger.warn { "Instrumented process failed with exception ${failure.exception} before concrete execution started" } + return true } - private fun isStaticInstanceInMethodResult(id: ClassId, methodResult: MethodResult?) = - methodResult != null && id in methodResult.memoryUpdates.staticInstanceStorage - - private fun skipVerticesForThrowableCreation(current: JAssignStmt) { - val rightType = current.rightOp.type as RefType - val exceptionType = Scene.v().getSootClass(rightType.className).type - val createdException = createObject(findNewAddr(), exceptionType, true) - val currentExceptionJimpleLocal = current.leftOp as JimpleLocal - - queuedSymbolicStateUpdates += localMemoryUpdate(currentExceptionJimpleLocal.variable to createdException) - - // mark the rest of the path leading to the '' statement as covered - do { - environment.state = environment.state.updateQueued(globalGraph.succ(environment.state.stmt)) - globalGraph.visitEdge(environment.state.lastEdge!!) - globalGraph.visitNode(environment.state) - } while (!environment.state.stmt.isConstructorCall(currentExceptionJimpleLocal)) - - pathSelector.offer(environment.state.updateQueued(globalGraph.succ(environment.state.stmt))) + return false } - private fun traverseAssignStmt(current: JAssignStmt) { - val rightValue = current.rightOp - - workaround(HACK) { - val rightType = rightValue.type - if (rightValue is JNewExpr && rightType is RefType) { - val throwableType = Scene.v().getSootClass("java.lang.Throwable").type - val throwableInheritors = typeResolver.findOrConstructInheritorsIncludingTypes(throwableType) - - // skip all the vertices in the CFG between `new` and `` statements - if (rightType in throwableInheritors) { - skipVerticesForThrowableCreation(current) - return - } + /** + * Collects entry method statement path for ML. Eliminates duplicated statements, e.g. assignment with invocation + * in right part. + */ + private fun entryMethodPath(state: ExecutionState): MutableList { + val entryPath = mutableListOf() + state.fullPath().forEach { step -> + // TODO: replace step.stmt in methodUnderAnalysisStmts with step.depth == 0 + // when fix SAT-812: [JAVA] Wrong depth when exception thrown + if (step.stmt in methodUnderAnalysisStmts && step.stmt !== entryPath.lastOrNull()?.stmt) { + entryPath += step } } + return entryPath + } - val rightPartWrappedAsMethodResults = if (rightValue is InvokeExpr) { - invokeResult(rightValue) + private fun getSymbolicPath(state: ExecutionState, symbolicResult: SymbolicResult): List { + val pathWithLines = state.fullPath().filter { step -> + step.stmt.javaSourceStartLineNumber != -1 + } + + val symbolicSteps = pathWithLines.map { step -> + val method = globalGraph.method(step.stmt) + SymbolicStep(method, step.stmt.javaSourceStartLineNumber, step.depth) + }.filter { step -> + step.method.declaringClass.packageName == methodUnderTest.classId.packageName + } + + return if (symbolicResult is SymbolicFailure && symbolicSteps.last().callDepth != 0) { + // If we have the following case: + // - method m1 calls method m2 + // - m2 calls m3 + // - m3 throws exception + // then `symbolicSteps` suffix looks like: + // - ... + // - method = m3, lineNumber = .., callDepth = 2 + // - method = m3, lineNumber = 30, callDepth = 2 <- line with thrown exception + // - method = m2, lineNumber = 20, callDepth = 1 + // - method = m1, lineNumber = 10, callDepth = 0 + // So, we want to remove 2 last entries (m1 and m2) because the execution finished at the line 30, + // but `state.fullPath()` contains also reverse exits from methods after exception. + // So, we need to remove the elements from the end of the list until the depth of the neighbors is the same. + symbolicSteps + .zipWithNext() + .dropLastWhile { (cur, next) -> cur.callDepth != next.callDepth } + .map { (cur, _) -> cur } } else { - val value = rightValue.resolve(current.leftOp.type) - listOf(MethodResult(value)) - } - - rightPartWrappedAsMethodResults.forEach { methodResult -> - when (methodResult.symbolicResult) { - - is SymbolicFailure -> { //exception thrown - if (environment.state.executionStack.last().doesntThrow) return@forEach - - val nextState = environment.state.createExceptionState( - methodResult.symbolicResult, - queuedSymbolicStateUpdates + methodResult.symbolicStateUpdate - ) - globalGraph.registerImplicitEdge(nextState.lastEdge!!) - pathSelector.offer(nextState) - } - - is SymbolicSuccess -> { - val update = traverseAssignLeftPart( - current.leftOp, - methodResult.symbolicResult.value - ) - pathSelector.offer( - environment.state.updateQueued( - globalGraph.succ(current), - update + methodResult.symbolicStateUpdate - ) - ) - } - } + symbolicSteps } } +} - /** - * This hack solves the problem with static final fields, which are equal by reference with parameter. - * - * Let be the address of a parameter and correspondingly the address of final field be p0. - * The initial state of a chunk array for this static field is always (mkArray Class_field Int -> Int) - * And the current state of this chunk array is (store (mkArray Class_field Int -> Int) (p0: someValue)) - * At initial chunk array under address p0 can be placed any value, because during symbolic execution we - * always refer only to current state. - * - * At the resolving stage, to resolve model of parameter before invoke, we get it from initial chunk array - * by address p0, where can be placed any value. However, resolved model for parameter after execution will - * be correct, as current state has correct value in chunk array under p0 address. - */ - private fun addConstraintsForFinalAssign(left: SymbolicValue, value: SymbolicValue) { - if (left is PrimitiveValue) { - if (left.type is DoubleType) { - queuedSymbolicStateUpdates += mkOr( - Eq(left, value as PrimitiveValue), - Ne(left, left), - Ne(value, value) - ).asHardConstraint() - } else { - queuedSymbolicStateUpdates += Eq(left, value as PrimitiveValue).asHardConstraint() - } - } else if (left is ReferenceValue) { - queuedSymbolicStateUpdates += addrEq(left.addr, (value as ReferenceValue).addr).asHardConstraint() - } - } - - /** - * Traverses left part of assignment i.e. where to store resolved value. - */ - private fun traverseAssignLeftPart(left: Value, value: SymbolicValue): SymbolicStateUpdate = when (left) { - is ArrayRef -> { - val arrayInstance = left.base.resolve() as ArrayValue - val addr = arrayInstance.addr - nullPointerExceptionCheck(addr) - - val index = (left.index.resolve() as PrimitiveValue).align() - val length = memory.findArrayLength(addr) - indexOutOfBoundsChecks(index, length) - - queuedSymbolicStateUpdates += Le(length, softMaxArraySize).asHardConstraint() // TODO: fix big array length - - // TODO array store exception - - // add constraint for possible array type - val valueType = value.type - val valueBaseType = valueType.baseType - - if (valueBaseType is RefType) { - val valueTypeAncestors = typeResolver.findOrConstructAncestorsIncludingTypes(valueBaseType) - val valuePossibleBaseTypes = value.typeStorage.possibleConcreteTypes.map { it.baseType } - // Either one of the possible types or one of their ancestor (to add interfaces and abstract classes) - val arrayPossibleBaseTypes = valueTypeAncestors + valuePossibleBaseTypes - - val arrayPossibleTypes = arrayPossibleBaseTypes.map { - it.makeArrayType(arrayInstance.type.numDimensions) - } - val typeStorage = typeResolver.constructTypeStorage(OBJECT_TYPE, arrayPossibleTypes) - - queuedSymbolicStateUpdates += typeRegistry.typeConstraint(arrayInstance.addr, typeStorage) - .isConstraint().asHardConstraint() - } - - val elementType = arrayInstance.type.elementType - val valueExpression = valueToExpression(value, elementType) - SymbolicStateUpdate(memoryUpdates = arrayUpdate(arrayInstance, index, valueExpression)) - } - is FieldRef -> { - val instanceForField = resolveInstanceForField(left) - - val objectUpdate = objectUpdate( - instance = instanceForField, - field = left.field, - value = valueToExpression(value, left.field.type) - ) - - // This hack solves the problem with static final fields, which are equal by reference with parameter - workaround(HACK) { - if (left.field.isFinal) { - addConstraintsForFinalAssign(left.resolve(), value) - } - } - - if (left is StaticFieldRef) { - val fieldId = left.field.fieldId - val staticFieldMemoryUpdate = StaticFieldMemoryUpdateInfo(fieldId, value) - val touchedStaticFields = persistentListOf(staticFieldMemoryUpdate) - queuedSymbolicStateUpdates += MemoryUpdate(staticFieldsUpdates = touchedStaticFields) - if (!environment.method.isStaticInitializer && !fieldId.isSynthetic) { - queuedSymbolicStateUpdates += MemoryUpdate(meaningfulStaticFields = persistentSetOf(fieldId)) - } - } - - SymbolicStateUpdate(memoryUpdates = objectUpdate) - } - is JimpleLocal -> SymbolicStateUpdate(localMemoryUpdates = localMemoryUpdate(left.variable to value)) - is InvokeExpr -> TODO("Not implemented: $left") - else -> error("${left::class} is not implemented") - } - - /** - * Resolves instance for field. For static field it's a special object represents static fields of particular class. - */ - private fun resolveInstanceForField(fieldRef: FieldRef) = when (fieldRef) { - is JInstanceFieldRef -> { - // Runs resolve() to check possible NPE and create required arrays related to the field. - // Ignores the result of resolve(). - fieldRef.resolve() - val baseObject = fieldRef.base.resolve() as ObjectValue - val typeStorage = TypeStorage(fieldRef.field.declaringClass.type) - baseObject.copy(typeStorage = typeStorage) - } - is StaticFieldRef -> { - val declaringClassType = fieldRef.field.declaringClass.type - val fieldTypeId = fieldRef.field.type.classId - val generator = UtMockInfoGenerator { mockAddr -> - val fieldId = FieldId(declaringClassType.id, fieldRef.field.name) - UtFieldMockInfo(fieldTypeId, mockAddr, fieldId, ownerAddr = null) - } - findOrCreateStaticObject(declaringClassType, generator) - } - else -> error("Unreachable branch") - } - - /** - * Converts value to expression with cast to target type for primitives. - */ - fun valueToExpression(value: SymbolicValue, type: Type): UtExpression = when (value) { - is ReferenceValue -> value.addr - // TODO: shall we add additional constraint that aligned expression still equals original? - // BitVector can lose valuable bites during extraction - is PrimitiveValue -> UtCastExpression(value, type) - } - - private fun traverseIdentityStmt(current: JIdentityStmt) { - val localVariable = (current.leftOp as? JimpleLocal)?.variable ?: error("Unknown op: ${current.leftOp}") - when (val identityRef = current.rightOp as IdentityRef) { - is ParameterRef, is ThisRef -> { - // Nested method calls already have input arguments in state - val value = if (environment.state.inputArguments.isNotEmpty()) { - environment.state.inputArguments.removeFirst().let { - // implicit cast, if we pass to function with - // int parameter a value with e.g. byte type - if (it is PrimitiveValue && it.type != identityRef.type) { - it.cast(identityRef.type) - } else { - it - } - } - } else { - val suffix = if (identityRef is ParameterRef) "${identityRef.index}" else "_this" - val pName = "p$suffix" - val mockInfoGenerator = parameterMockInfoGenerator(identityRef) - - val isNonNullable = if (identityRef is ParameterRef) { - environment.method.paramHasNotNullAnnotation(identityRef.index) - } else { - true // "this" must be not null - } - - val createdValue = identityRef.createConst(pName, mockInfoGenerator) - - if (createdValue is ReferenceValue) { - // Update generic type info for method under test' parameters - updateGenericTypeInfo(identityRef, createdValue) - - if (isNonNullable) { - queuedSymbolicStateUpdates += mkNot( - addrEq( - createdValue.addr, - nullObjectAddr - ) - ).asHardConstraint() - } - } - if (preferredCexOption) { - applyPreferredConstraints(createdValue) - } - createdValue - } - - environment.state.parameters += Parameter(localVariable, identityRef.type, value) - - val nextState = environment.state.updateQueued( - globalGraph.succ(current), - SymbolicStateUpdate(localMemoryUpdates = localMemoryUpdate(localVariable to value)) - ) - pathSelector.offer(nextState) - } - is JCaughtExceptionRef -> { - val value = localVariableMemory.local(CAUGHT_EXCEPTION) - ?: error("Exception wasn't caught, stmt: $current, line: ${current.lines}") - val nextState = environment.state.updateQueued( - globalGraph.succ(current), - SymbolicStateUpdate(localMemoryUpdates = localMemoryUpdate(localVariable to value, CAUGHT_EXCEPTION to null)) - ) - pathSelector.offer(nextState) - } - else -> error("Unsupported $identityRef") - } - } - - /** - * Creates mock info for method under test' non-primitive parameter. - * - * Returns null if mock is not allowed - Engine traverses nested method call or parameter type is not RefType. - */ - private fun parameterMockInfoGenerator(parameterRef: IdentityRef): UtMockInfoGenerator? { - if (isInNestedMethod()) return null - if (parameterRef !is ParameterRef) return null - val type = parameterRef.type - if (type !is RefType) return null - return UtMockInfoGenerator { mockAddr -> UtObjectMockInfo(type.id, mockAddr) } - } - - /** - * Stores information about the generic types used in the parameters of the method under test. - */ - private fun updateGenericTypeInfo(identityRef: IdentityRef, value: ReferenceValue) { - val callable = methodUnderTest.callable - val type = if (identityRef is ThisRef) { - // TODO: for ThisRef both methods don't return parameterized type - if (methodUnderTest.isConstructor) { - methodUnderTest.javaConstructor?.annotatedReturnType?.type - } else { - callable.instanceParameter?.type?.javaType - ?: error("No instanceParameter for ${callable.signature} found") - } - } else { - // Sometimes out of bound exception occurred here, e.g., com.alibaba.fescar.core.model.GlobalStatus. - workaround(HACK) { - val index = (identityRef as ParameterRef).index - val valueParameters = callable.valueParameters - - if (index > valueParameters.lastIndex) return - valueParameters[index].type.javaType - } - } - - if (type is ParameterizedType) { - val typeStorages = type.actualTypeArguments.map { actualTypeArgument -> - when (actualTypeArgument) { - is WildcardTypeImpl -> { - val upperBounds = actualTypeArgument.upperBounds - val lowerBounds = actualTypeArgument.lowerBounds - val allTypes = upperBounds + lowerBounds - - if (allTypes.any { it is GenericArrayTypeImpl }) { - val errorTypes = allTypes.filterIsInstance() - TODO("we do not support GenericArrayTypeImpl yet, and $errorTypes found. SAT-1446") - } - - val upperBoundsTypes = typeResolver.intersectInheritors(upperBounds) - val lowerBoundsTypes = typeResolver.intersectAncestors(lowerBounds) - - typeResolver.constructTypeStorage(OBJECT_TYPE, upperBoundsTypes.intersect(lowerBoundsTypes)) - } - is TypeVariableImpl<*> -> { // it is a type variable for the whole class, not the function type variable - val upperBounds = actualTypeArgument.bounds - - if (upperBounds.any { it is GenericArrayTypeImpl }) { - val errorTypes = upperBounds.filterIsInstance() - TODO("we do not support GenericArrayTypeImpl yet, and $errorTypes found. SAT-1446") - } - - val upperBoundsTypes = typeResolver.intersectInheritors(upperBounds) - - typeResolver.constructTypeStorage(OBJECT_TYPE, upperBoundsTypes) - } - is GenericArrayTypeImpl -> { - // TODO bug with T[][], because there is no such time T JIRA:1446 - typeResolver.constructTypeStorage(OBJECT_TYPE, useConcreteType = false) - } - is ParameterizedTypeImpl, is Class<*> -> { - val sootType = Scene.v().getType(actualTypeArgument.rawType.typeName) - - typeResolver.constructTypeStorage(sootType, useConcreteType = false) - } - else -> error("Unsupported argument type ${actualTypeArgument::class}") - } - } - - queuedSymbolicStateUpdates += typeRegistry.genericTypeParameterConstraint(value.addr, typeStorages).asHardConstraint() - parameterAddrToGenericType += value.addr to type - - typeRegistry.saveObjectParameterTypeStorages(value.addr, typeStorages) - } - } - - private fun traverseIfStmt(current: JIfStmt) { - // positiveCaseEdge could be null - see Conditions::emptyBranches - val (negativeCaseEdge, positiveCaseEdge) = globalGraph.succs(current).let { it[0] to it.getOrNull(1) } - val cond = current.condition - val resolvedCondition = resolveIfCondition(cond as BinopExpr) - val positiveCasePathConstraint = resolvedCondition.condition - val (positiveCaseSoftConstraint, negativeCaseSoftConstraint) = resolvedCondition.softConstraints - val negativeCasePathConstraint = mkNot(positiveCasePathConstraint) - - if (positiveCaseEdge != null) { - environment.state.definitelyFork() - } - - /* assumeOrExecuteConcrete in jimple looks like: - ``` z0 = a > 5 - if (z0 == 1) goto label1 - assumeOrExecuteConcretely(z0) - - label1: - assumeOrExecuteConcretely(z0) - ``` - - We have to detect such situations to avoid addition `a > 5` into hardConstraints, - because we want to add them into Assumptions. - - Note: we support only simple predicates right now (one logical operation), - otherwise they will be added as hard constraints, and we will not execute - the state concretely if there will be UNSAT because of assumptions. - */ - val isAssumeExpr = positiveCaseEdge?.let { isConditionForAssumeOrExecuteConcretely(it.dst) } ?: false - - // in case of assume we want to have the only branch where $z = 1 (it is a negative case) - if (!isAssumeExpr) { - positiveCaseEdge?.let { edge -> - environment.state.expectUndefined() - val positiveCaseState = environment.state.updateQueued( - edge, - SymbolicStateUpdate( - hardConstraints = positiveCasePathConstraint.asHardConstraint(), - softConstraints = setOfNotNull(positiveCaseSoftConstraint).asSoftConstraint() - ) + resolvedCondition.symbolicStateUpdates.positiveCase - ) - pathSelector.offer(positiveCaseState) - } - } - - // Depending on existance of assumeExpr we have to add corresponding hardConstraints and assumptions - val hardConstraints = if (!isAssumeExpr) negativeCasePathConstraint.asHardConstraint() else HardConstraint() - val assumption = if (isAssumeExpr) negativeCasePathConstraint.asAssumption() else Assumption() - - val negativeCaseState = environment.state.updateQueued( - negativeCaseEdge, - SymbolicStateUpdate( - hardConstraints = hardConstraints, - softConstraints = setOfNotNull(negativeCaseSoftConstraint).asSoftConstraint(), - assumptions = assumption - ) + resolvedCondition.symbolicStateUpdates.negativeCase - ) - pathSelector.offer(negativeCaseState) - } - - /** - * Returns true if the next stmt is an [assumeOrExecuteConcretelyMethod] invocation, false otherwise. - */ - private fun isConditionForAssumeOrExecuteConcretely(stmt: Stmt): Boolean { - val successor = globalGraph.succStmts(stmt).singleOrNull() as? JInvokeStmt ?: return false - val invokeExpression = successor.invokeExpr as? JStaticInvokeExpr ?: return false - return invokeExpression.method.isUtMockAssumeOrExecuteConcretely - } - - private fun traverseInvokeStmt(current: JInvokeStmt) { - val results = invokeResult(current.invokeExpr) - - results.forEach { result -> - if (result.symbolicResult is SymbolicFailure && environment.state.executionStack.last().doesntThrow) { - return@forEach - } - - pathSelector.offer( - when (result.symbolicResult) { - is SymbolicFailure -> environment.state.createExceptionState( - result.symbolicResult, - queuedSymbolicStateUpdates + result.symbolicStateUpdate - ) - is SymbolicSuccess -> environment.state.updateQueued( - globalGraph.succ(current), - result.symbolicStateUpdate - ) - } - ) - } - } - - private fun traverseSwitchStmt(current: SwitchStmt) { - val valueExpr = current.key.resolve() as PrimitiveValue - val successors = when (current) { - is JTableSwitchStmt -> { - val indexed = (current.lowIndex..current.highIndex).mapIndexed { i, index -> - Edge(current, current.getTarget(i) as Stmt, i) to Eq(valueExpr, index) - } - val targetExpr = mkOr( - Lt(valueExpr, current.lowIndex), - Gt(valueExpr, current.highIndex) - ) - indexed + (Edge(current, current.defaultTarget as Stmt, indexed.size) to targetExpr) - } - is JLookupSwitchStmt -> { - val lookups = current.lookupValues.mapIndexed { i, value -> - Edge(current, current.getTarget(i) as Stmt, i) to Eq(valueExpr, value.value) - } - val targetExpr = mkNot(mkOr(lookups.map { it.second })) - lookups + (Edge(current, current.defaultTarget as Stmt, lookups.size) to targetExpr) - } - else -> error("Unknown switch $current") - } - if (successors.size > 1) { - environment.state.expectUndefined() - environment.state.definitelyFork() - } - - successors.forEach { (target, expr) -> - pathSelector.offer( - environment.state.updateQueued( - target, - SymbolicStateUpdate(hardConstraints = expr.asHardConstraint()), - ) - ) - } - } - - private suspend fun FlowCollector.traverseThrowStmt(current: JThrowStmt) { - val symException = explicitThrown(current.op.resolve(), isInNestedMethod()) - traverseException(current, symException) - } - - // TODO HACK violation of encapsulation - fun createObject( - addr: UtAddrExpression, - type: RefType, - useConcreteType: Boolean, - mockInfoGenerator: UtMockInfoGenerator? = null - ): ObjectValue { - touchAddress(addr) - - if (mockInfoGenerator != null) { - val mockInfo = mockInfoGenerator.generate(addr) - - queuedSymbolicStateUpdates += MemoryUpdate(addrToMockInfo = persistentHashMapOf(addr to mockInfo)) - - val mockedObject = mocker.mock(type, mockInfo) - - if (mockedObject != null) { - queuedSymbolicStateUpdates += MemoryUpdate(mockInfos = persistentListOf(MockInfoEnriched(mockInfo))) - - // add typeConstraint for mocked object. It's a declared type of the object. - queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, mockedObject.typeStorage).all().asHardConstraint() - queuedSymbolicStateUpdates += mkEq(typeRegistry.isMock(mockedObject.addr), UtTrue).asHardConstraint() - - return mockedObject - } - } - - // construct a type storage that might contain our own types, i.e., UtArrayList - val typeStoragePossiblyWithOverriddenTypes = typeResolver.constructTypeStorage(type, useConcreteType) - val leastCommonType = typeStoragePossiblyWithOverriddenTypes.leastCommonType as RefType - - // If the leastCommonType of the created typeStorage is one of our own classes, - // we must create a copy of the typeStorage with the real classes instead of wrappers. - // It is required because we do not want to have situations when some object might have - // only artificial classes as their possible, that would cause problems in the type constraints. - val typeStorage = if (leastCommonType in wrapperToClass.keys) { - typeStoragePossiblyWithOverriddenTypes.copy(possibleConcreteTypes = wrapperToClass.getValue(leastCommonType)) - } else { - typeStoragePossiblyWithOverriddenTypes - } - - wrapper(type, addr)?.let { - queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() - return it - } - - if (typeStorage.possibleConcreteTypes.isEmpty()) { - requireNotNull(mockInfoGenerator) { - "An object with $addr and $type doesn't have concrete possible types," + - "but there is no mock info generator provided to construct a mock value." - } - - val mockInfo = mockInfoGenerator.generate(addr) - val mockedObject = mocker.forceMock(type, mockInfoGenerator.generate(addr)) - - queuedSymbolicStateUpdates += MemoryUpdate(mockInfos = persistentListOf(MockInfoEnriched(mockInfo))) - - // add typeConstraint for mocked object. It's a declared type of the object. - queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, mockedObject.typeStorage).all().asHardConstraint() - queuedSymbolicStateUpdates += mkEq(typeRegistry.isMock(mockedObject.addr), UtTrue).asHardConstraint() - - return mockedObject - } - - // If we have this$0 with UtArrayList type, we have to create such instance. - // We should create an object with typeStorage of all possible real types and concrete implementation - // Otherwise we'd have either a wrong type in the resolver, or missing method like 'preconditionCheck'. - val concreteImplementation = wrapperToClass[type]?.first()?.let { wrapper(it, addr) }?.concrete - - queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() - queuedSymbolicStateUpdates += mkEq(typeRegistry.isMock(addr), UtFalse).asHardConstraint() - - return ObjectValue(typeStorage, addr, concreteImplementation) - } - - private fun Constant.resolve(): SymbolicValue = - when (this) { - is IntConstant -> this.value.toPrimitiveValue() - is LongConstant -> this.value.toPrimitiveValue() - is FloatConstant -> this.value.toPrimitiveValue() - is DoubleConstant -> this.value.toPrimitiveValue() - is StringConstant -> { - val addr = findNewAddr() - val refType = this.type as RefType - - // We disable creation of string literals to avoid unsats because of too long lines - if (UtSettings.ignoreStringLiterals && value.length > MAX_STRING_SIZE) { - // instead of it we create an unbounded symbolic variable - workaround(HACK) { - statesForConcreteExecution += environment.state - createObject(addr, refType, useConcreteType = true) - } - } else { - queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, TypeStorage(refType)).all().asHardConstraint() - - objectValue(refType, addr, StringWrapper()).also { - initStringLiteral(it, this.value) - } - } - } - is ClassConstant -> { - val sootType = toSootType() - val result = if (sootType is RefLikeType) { - typeRegistry.createClassRef(sootType.baseType, sootType.numDimensions) - } else { - error("Can't get class constant for $value") - } - queuedSymbolicStateUpdates += result.symbolicStateUpdate - (result.symbolicResult as SymbolicSuccess).value - } - else -> error("Unsupported type: $this") - } - - private fun Expr.resolve(valueType: Type = this.type): SymbolicValue = when (this) { - is BinopExpr -> { - val left = this.op1.resolve() - val right = this.op2.resolve() - when { - left is ReferenceValue && right is ReferenceValue -> { - when (this) { - is JEqExpr -> addrEq(left.addr, right.addr).toBoolValue() - is JNeExpr -> mkNot(addrEq(left.addr, right.addr)).toBoolValue() - else -> TODO("Unknown op $this for $left and $right") - } - } - left is PrimitiveValue && right is PrimitiveValue -> { - // division by zero special case - if ((this is JDivExpr || this is JRemExpr) && left.expr.isInteger() && right.expr.isInteger()) { - divisionByZeroCheck(right) - } - - if (UtSettings.treatOverflowAsError) { - // overflow detection - if (left.expr.isInteger() && right.expr.isInteger()) { - intOverflowCheck(this, left, right) - } - } - - doOperation(this, left, right).toPrimitiveValue(this.type) - } - else -> TODO("Unknown op $this for $left and $right") - } - } - is JNegExpr -> UtNegExpression(op.resolve() as PrimitiveValue).toPrimitiveValue(this.type) - is JNewExpr -> { - val addr = findNewAddr() - val generator = UtMockInfoGenerator { mockAddr -> - UtNewInstanceMockInfo( - baseType.id, - mockAddr, - environment.method.declaringClass.id - ) - } - val objectValue = createObject(addr, baseType, useConcreteType = true, generator) - addConstraintsForDefaultValues(objectValue) - objectValue - } - is JNewArrayExpr -> { - val size = (this.size.resolve() as PrimitiveValue).align() - val type = this.type as ArrayType - createNewArray(size, type, type.elementType).also { - val defaultValue = type.defaultSymValue - queuedSymbolicStateUpdates += arrayUpdateWithValue(it.addr, type, defaultValue as UtArrayExpressionBase) - } - } - is JNewMultiArrayExpr -> { - val result = environment.state.methodResult - ?: error("There is no unfolded JNewMultiArrayExpr found in the methodResult") - queuedSymbolicStateUpdates += result.symbolicStateUpdate - (result.symbolicResult as SymbolicSuccess).value - } - is JLengthExpr -> { - val operand = op as? JimpleLocal ?: error("Unknown op: $op") - when (operand.type) { - is ArrayType -> { - val arrayInstance = localVariableMemory.local(operand.variable) as ArrayValue? - ?: error("$op not found in the locals") - nullPointerExceptionCheck(arrayInstance.addr) - memory.findArrayLength(arrayInstance.addr).also { length -> - queuedSymbolicStateUpdates += Ge(length, 0).asHardConstraint() - } - } - else -> error("Unknown op: $op") - } - } - is JCastExpr -> when (val value = op.resolve(valueType)) { - is PrimitiveValue -> value.cast(type) - is ObjectValue -> { - castObject(value, type, op) - } - is ArrayValue -> castArray(value, type) - } - is JInstanceOfExpr -> when (val value = op.resolve(valueType)) { - is PrimitiveValue -> error("Unexpected instanceof on primitive $value") - is ObjectValue -> objectInstanceOf(value, checkType, op) - is ArrayValue -> arrayInstanceOf(value, checkType) - } - else -> TODO("$this") - } - - private fun initStringLiteral(stringWrapper: ObjectValue, value: String) { - queuedSymbolicStateUpdates += objectUpdate( - stringWrapper.copy(typeStorage = TypeStorage(utStringClass.type)), - STRING_LENGTH, - mkInt(value.length) - ) - queuedSymbolicStateUpdates += MemoryUpdate(visitedValues = persistentListOf(stringWrapper.addr)) - - val type = CharType.v() - val arrayType = type.arrayType - val arrayValue = createNewArray(value.length.toPrimitiveValue(), arrayType, type).also { - val defaultValue = arrayType.defaultSymValue - queuedSymbolicStateUpdates += arrayUpdateWithValue(it.addr, arrayType, defaultValue as UtArrayExpressionBase) - } - queuedSymbolicStateUpdates += objectUpdate( - stringWrapper.copy(typeStorage = TypeStorage(utStringClass.type)), - STRING_VALUE, - arrayValue.addr - ) - val newArray = value.indices.fold(selectArrayExpressionFromMemory(arrayValue)) { array, index -> - array.store(mkInt(index), mkChar(value[index])) - } - - queuedSymbolicStateUpdates += arrayUpdateWithValue(arrayValue.addr, CharType.v().arrayType, newArray) - environment.state = environment.state.updateMemory(queuedSymbolicStateUpdates) - queuedSymbolicStateUpdates = queuedSymbolicStateUpdates.copy(memoryUpdates = MemoryUpdate()) - } - - private fun arrayInstanceOf(value: ArrayValue, checkType: Type): PrimitiveValue { - val notNullConstraint = mkNot(addrEq(value.addr, nullObjectAddr)) - - if (checkType.isJavaLangObject()) { - return UtInstanceOfExpression(notNullConstraint.asHardConstraint().asUpdate()).toBoolValue() - } - - require(checkType is ArrayType) - - val checkBaseType = checkType.baseType - - // i.e., int[][] instanceof Object[] - if (checkBaseType.isJavaLangObject()) { - return UtInstanceOfExpression(notNullConstraint.asHardConstraint().asUpdate()).toBoolValue() - } - - // Object[] instanceof int[][] - if (value.type.baseType.isJavaLangObject() && checkBaseType is PrimType) { - val updatedTypeStorage = typeResolver.constructTypeStorage(checkType, useConcreteType = false) - val typeConstraint = typeRegistry.typeConstraint(value.addr, updatedTypeStorage).isConstraint() - - val constraint = mkAnd(notNullConstraint, typeConstraint) - val memoryUpdate = arrayTypeUpdate(value.addr, checkType) - val symbolicStateUpdate = SymbolicStateUpdate( - hardConstraints = constraint.asHardConstraint(), - memoryUpdates = memoryUpdate - ) - - return UtInstanceOfExpression(symbolicStateUpdate).toBoolValue() - } - - // We must create a new typeStorage containing ALL the inheritors for checkType, - // because later we will create a negation for the typeConstraint - val updatedTypeStorage = typeResolver.constructTypeStorage(checkType, useConcreteType = false) - - val typesIntersection = updatedTypeStorage.possibleConcreteTypes.intersect(value.possibleConcreteTypes) - if (typesIntersection.isEmpty()) return UtFalse.toBoolValue() - - val typeConstraint = typeRegistry.typeConstraint(value.addr, updatedTypeStorage).isConstraint() - val constraint = mkAnd(notNullConstraint, typeConstraint) - - val arrayType = updatedTypeStorage.leastCommonType as ArrayType - val memoryUpdate = arrayTypeUpdate(value.addr, arrayType) - val symbolicStateUpdate = SymbolicStateUpdate( - hardConstraints = constraint.asHardConstraint(), - memoryUpdates = memoryUpdate - ) - - return UtInstanceOfExpression(symbolicStateUpdate).toBoolValue() - } - - private fun objectInstanceOf(value: ObjectValue, checkType: Type, op: Value): PrimitiveValue { - val notNullConstraint = mkNot(addrEq(value.addr, nullObjectAddr)) - - // the only way to get false here is for the value to be null - if (checkType.isJavaLangObject()) { - return UtInstanceOfExpression(notNullConstraint.asHardConstraint().asUpdate()).toBoolValue() - } - - if (value.type.isJavaLangObject() && checkType is ArrayType) { - val castedArray = - createArray(value.addr, checkType, useConcreteType = false, addQueuedTypeConstraints = false) - val localVariable = (op as? JimpleLocal)?.variable ?: error("Unexpected op in the instanceof expr: $op") - - val typeMemoryUpdate = arrayTypeUpdate(value.addr, castedArray.type) - val localMemoryUpdate = localMemoryUpdate(localVariable to castedArray) - - val typeConstraint = typeRegistry.typeConstraint(value.addr, castedArray.typeStorage).isConstraint() - val constraint = mkAnd(notNullConstraint, typeConstraint) - val symbolicStateUpdate = SymbolicStateUpdate( - hardConstraints = constraint.asHardConstraint(), - memoryUpdates = typeMemoryUpdate, - localMemoryUpdates = localMemoryUpdate - ) - - return UtInstanceOfExpression(symbolicStateUpdate).toBoolValue() - } - - require(checkType is RefType) - - // We must create a new typeStorage containing ALL the inheritors for checkType, - // because later we will create a negation for the typeConstraint - val updatedTypeStorage = typeResolver.constructTypeStorage(checkType, useConcreteType = false) - - // drop this branch if we don't have an appropriate type in the possibleTypes - val typesIntersection = updatedTypeStorage.possibleConcreteTypes.intersect(value.possibleConcreteTypes) - if (typesIntersection.isEmpty()) return UtFalse.toBoolValue() - - val typeConstraint = typeRegistry.typeConstraint(value.addr, updatedTypeStorage).isConstraint() - val constraint = mkAnd(notNullConstraint, typeConstraint) - - return UtInstanceOfExpression(constraint.asHardConstraint().asUpdate()).toBoolValue() - } - - private fun addConstraintsForDefaultValues(objectValue: ObjectValue) { - val type = objectValue.type - for (field in typeResolver.findFields(type)) { - // final fields must be initialized inside the body of a constructor - if (field.isFinal) continue - val chunkId = hierarchy.chunkIdForField(type, field) - val memoryChunkDescriptor = MemoryChunkDescriptor(chunkId, type, field.type) - val array = memory.findArray(memoryChunkDescriptor) - val defaultValue = if (field.type is RefLikeType) nullObjectAddr else field.type.defaultSymValue - queuedSymbolicStateUpdates += mkEq(array.select(objectValue.addr), defaultValue).asHardConstraint() - } - } - - private fun castObject(objectValue: ObjectValue, typeAfterCast: Type, op: Value): SymbolicValue { - classCastExceptionCheck(objectValue, typeAfterCast) - - val currentType = objectValue.type - val nullConstraint = addrEq(objectValue.addr, nullObjectAddr) - - // If we're trying to cast type A to the same type A - if (currentType == typeAfterCast) return objectValue - - // java.lang.Object -> array - if (currentType.isJavaLangObject() && typeAfterCast is ArrayType) { - val array = createArray(objectValue.addr, typeAfterCast, useConcreteType = false) - - val localVariable = (op as? JimpleLocal)?.variable ?: error("Unexpected op in the cast: $op") - -/* - val typeConstraint = typeRegistry.typeConstraint(array.addr, array.typeStorage).isOrNullConstraint() - - queuedSymbolicStateUpdates += typeConstraint.asHardConstraint() -*/ - - queuedSymbolicStateUpdates += localMemoryUpdate(localVariable to array) - queuedSymbolicStateUpdates += arrayTypeUpdate(array.addr, array.type) - - return array - } - - val ancestors = typeResolver.findOrConstructAncestorsIncludingTypes(currentType) - // if we're trying to cast type A to it's predecessor - if (typeAfterCast in ancestors) return objectValue - - require(typeAfterCast is RefType) - - val castedObject = typeResolver.downCast(objectValue, typeAfterCast) - - // The objectValue must be null to be casted to an impossible type - if (castedObject.possibleConcreteTypes.isEmpty()) { - queuedSymbolicStateUpdates += nullConstraint.asHardConstraint() - return objectValue.copy(addr = nullObjectAddr) - } - - val typeConstraint = typeRegistry.typeConstraint(castedObject.addr, castedObject.typeStorage).isOrNullConstraint() - - // When we do downCast, we should add possible equality to null - // to avoid situation like this: - // we have class A, class B extends A, class C extends A - // void foo(a A) { (B) a; (C) a; } -> a is null - queuedSymbolicStateUpdates += typeConstraint.asHardConstraint() - queuedSymbolicStateUpdates += typeRegistry.zeroDimensionConstraint(objectValue.addr).asHardConstraint() - - // TODO add memory constraints JIRA:1523 - return castedObject - } - - private fun castArray(arrayValue: ArrayValue, typeAfterCast: Type): ArrayValue { - classCastExceptionCheck(arrayValue, typeAfterCast) - - if (typeAfterCast.isJavaLangObject()) return arrayValue - - require(typeAfterCast is ArrayType) - - // cast A[] to A[] - if (arrayValue.type == typeAfterCast) return arrayValue - - val baseTypeBeforeCast = arrayValue.type.baseType - val baseTypeAfterCast = typeAfterCast.baseType - - val nullConstraint = addrEq(arrayValue.addr, nullObjectAddr) - - // i.e. cast Object[] -> int[][] - if (baseTypeBeforeCast.isJavaLangObject() && baseTypeAfterCast is PrimType) { - val castedArray = createArray(arrayValue.addr, typeAfterCast) - - val memoryUpdate = arrayTypeUpdate(castedArray.addr, castedArray.type) - - queuedSymbolicStateUpdates += memoryUpdate - - return castedArray - } - - // int[][] -> Object[] - if (baseTypeBeforeCast is PrimType && baseTypeAfterCast.isJavaLangObject()) return arrayValue - - require(baseTypeBeforeCast is RefType) - require(baseTypeAfterCast is RefType) - - // Integer[] -> Number[] - val ancestors = typeResolver.findOrConstructAncestorsIncludingTypes(baseTypeBeforeCast) - if (baseTypeAfterCast in ancestors) return arrayValue - - val castedArray = typeResolver.downCast(arrayValue, typeAfterCast) - - // cast to an unreachable type - if (castedArray.possibleConcreteTypes.isEmpty()) { - queuedSymbolicStateUpdates += nullConstraint.asHardConstraint() - return arrayValue.copy(addr = nullObjectAddr) - } - - val typeConstraint = typeRegistry.typeConstraint(castedArray.addr, castedArray.typeStorage).isOrNullConstraint() - val memoryUpdate = arrayTypeUpdate(castedArray.addr, castedArray.type) - - queuedSymbolicStateUpdates += typeConstraint.asHardConstraint() - queuedSymbolicStateUpdates += memoryUpdate - - return castedArray - } - - internal fun createNewArray(size: PrimitiveValue, type: ArrayType, elementType: Type): ArrayValue { - negativeArraySizeCheck(size) - val addr = findNewAddr() - val length = memory.findArrayLength(addr) - - queuedSymbolicStateUpdates += Eq(length, size).asHardConstraint() - queuedSymbolicStateUpdates += Ge(length, 0).asHardConstraint() - workaround(HACK) { - if (size.expr is UtBvLiteral) { - softMaxArraySize = min(HARD_MAX_ARRAY_SIZE, max(size.expr.value.toInt(), softMaxArraySize)) - } - } - queuedSymbolicStateUpdates += Le(length, softMaxArraySize).asHardConstraint() // TODO: fix big array length - - if (preferredCexOption) { - queuedSymbolicStateUpdates += Le(length, PREFERRED_ARRAY_SIZE).asSoftConstraint() - } - val chunkId = typeRegistry.arrayChunkId(type) - touchMemoryChunk(MemoryChunkDescriptor(chunkId, type, elementType)) - - return ArrayValue(TypeStorage(type), addr).also { - queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, it.typeStorage).all().asHardConstraint() - } - } - - private fun SymbolicValue.simplify(): SymbolicValue = - when (this) { - is PrimitiveValue -> copy(expr = expr.accept(solver.rewriter)) - is ObjectValue -> copy(addr = addr.accept(solver.rewriter) as UtAddrExpression) - is ArrayValue -> copy(addr = addr.accept(solver.rewriter) as UtAddrExpression) - } - - - // Type is needed for null values: we should know, which null do we require. - // If valueType is NullType, return typelessNullObject. It can happen in a situation, - // where we cannot find the type, for example in condition (null == null) - private fun Value.resolve(valueType: Type = this.type): SymbolicValue = when (this) { - is JimpleLocal -> localVariableMemory.local(this.variable) ?: error("$name not found in the locals") - is Constant -> if (this is NullConstant) typeResolver.nullObject(valueType) else resolve() - is Expr -> resolve(valueType).simplify() - is JInstanceFieldRef -> { - val instance = (base.resolve() as ObjectValue) - recordInstanceFieldRead(instance.addr, field) - nullPointerExceptionCheck(instance.addr) - - val objectType = if (instance.concrete?.value is BaseOverriddenWrapper) { - instance.concrete.value.overriddenClass.type - } else { - field.declaringClass.type as RefType - } - val generator = (field.type as? RefType)?.let { refType -> - UtMockInfoGenerator { mockAddr -> - val fieldId = FieldId(objectType.id, field.name) - UtFieldMockInfo(refType.id, mockAddr, fieldId, instance.addr) - } - } - createFieldOrMock(objectType, instance.addr, field, generator).also { value -> - preferredCexInstanceCache[instance]?.let { usedCache -> - if (usedCache.add(field)) { - applyPreferredConstraints(value) - } - } - } - } - is JArrayRef -> { - val arrayInstance = base.resolve() as ArrayValue - nullPointerExceptionCheck(arrayInstance.addr) - - val index = (index.resolve() as PrimitiveValue).align() - val length = memory.findArrayLength(arrayInstance.addr) - indexOutOfBoundsChecks(index, length) - - val type = arrayInstance.type - val elementType = type.elementType - val chunkId = typeRegistry.arrayChunkId(type) - val descriptor = MemoryChunkDescriptor(chunkId, type, elementType).also { touchMemoryChunk(it) } - val array = memory.findArray(descriptor) - - when (elementType) { - is RefType -> { - val generator = UtMockInfoGenerator { mockAddr -> UtObjectMockInfo(elementType.id, mockAddr) } - - val objectValue = createObject( - UtAddrExpression(array.select(arrayInstance.addr, index.expr)), - elementType, - useConcreteType = false, - generator - ) - - if (objectValue.type.isJavaLangObject()) { - queuedSymbolicStateUpdates += typeRegistry.zeroDimensionConstraint(objectValue.addr).asSoftConstraint() - } - - objectValue - } - is ArrayType -> createArray( - UtAddrExpression(array.select(arrayInstance.addr, index.expr)), - elementType, - useConcreteType = false - ) - else -> PrimitiveValue(elementType, array.select(arrayInstance.addr, index.expr)) - } - } - is StaticFieldRef -> readStaticField(this) - else -> error("${this::class} is not supported") - } - - private fun readStaticField(fieldRef: StaticFieldRef): SymbolicValue { - val field = fieldRef.field - val declaringClassType = field.declaringClass.type - val staticObject = findOrCreateStaticObject(declaringClassType) - - val generator = (field.type as? RefType)?.let { refType -> - UtMockInfoGenerator { mockAddr -> - val fieldId = FieldId(declaringClassType.id, field.name) - UtFieldMockInfo(refType.id, mockAddr, fieldId, ownerAddr = null) - } - } - val createdField = createFieldOrMock(declaringClassType, staticObject.addr, field, generator).also { value -> - preferredCexInstanceCache.entries - .firstOrNull { declaringClassType == it.key.type }?.let { - if (it.value.add(field)) { - applyPreferredConstraints(value) - } - } - } - - val fieldId = field.fieldId - val staticFieldMemoryUpdate = StaticFieldMemoryUpdateInfo(fieldId, createdField) - val touchedStaticFields = persistentListOf(staticFieldMemoryUpdate) - queuedSymbolicStateUpdates += MemoryUpdate(staticFieldsUpdates = touchedStaticFields) - - // TODO filter enum constant static fields JIRA:1681 - if (!environment.method.isStaticInitializer && !fieldId.isSynthetic) { - queuedSymbolicStateUpdates += MemoryUpdate(meaningfulStaticFields = persistentSetOf(fieldId)) - } - - return createdField - } - - /** - * Locates object represents static fields of particular class. - * - * If object does not exist in the memory, returns null. - */ - fun locateStaticObject(classType: RefType): ObjectValue? = memory.findStaticInstanceOrNull(classType.id) - - /** - * Locates object represents static fields of particular class. - * - * If object is not exist in memory, creates a new one and put it into memory updates. - */ - private fun findOrCreateStaticObject( - classType: RefType, - mockInfoGenerator: UtMockInfoGenerator? = null - ): ObjectValue { - val fromMemory = locateStaticObject(classType) - - // true if the object exists in the memory and he already has concrete value or mockInfoGenerator is null - // It's important to avoid situations when we've already created object earlier without mock, and now - // we want to mock this object - if (fromMemory != null && (fromMemory.concrete != null || mockInfoGenerator == null)) { - return fromMemory - } - val addr = fromMemory?.addr ?: findNewAddr() - val created = createObject(addr, classType, useConcreteType = false, mockInfoGenerator) - queuedSymbolicStateUpdates += MemoryUpdate(staticInstanceStorage = persistentHashMapOf(classType.id to created)) - return created - } - - private fun resolveParameters(parameters: List, types: List) = - parameters.zip(types).map { (value, type) -> value.resolve(type) } - - private fun applyPreferredConstraints(value: SymbolicValue) { - when (value) { - is PrimitiveValue, is ArrayValue -> queuedSymbolicStateUpdates += preferredConstraints(value).asSoftConstraint() - is ObjectValue -> preferredCexInstanceCache.putIfAbsent(value, mutableSetOf()) - } - } - - private fun preferredConstraints(variable: SymbolicValue): List = - when (variable) { - is PrimitiveValue -> - when (variable.type) { - is ByteType, is ShortType, is IntType, is LongType -> { - listOf(Ge(variable, MIN_PREFERRED_INTEGER), Le(variable, MAX_PREFERRED_INTEGER)) - } - is CharType -> { - listOf(Ge(variable, MIN_PREFERRED_CHARACTER), Le(variable, MAX_PREFERRED_CHARACTER)) - } - else -> emptyList() - } - is ArrayValue -> { - val type = variable.type - val elementType = type.elementType - val constraints = mutableListOf() - val array = memory.findArray( - MemoryChunkDescriptor( - typeRegistry.arrayChunkId(variable.type), - variable.type, - elementType - ) - ) - constraints += Le(memory.findArrayLength(variable.addr), PREFERRED_ARRAY_SIZE) - for (i in 0 until softMaxArraySize) { - constraints += preferredConstraints( - array.select(variable.addr, mkInt(i)).toPrimitiveValue(elementType) - ) - } - constraints - } - is ObjectValue -> error("Unsupported type of $variable for preferredConstraints option") - } - - private fun createField( - objectType: RefType, - addr: UtAddrExpression, - fieldType: Type, - chunkId: ChunkId, - mockInfoGenerator: UtMockInfoGenerator? = null - ): SymbolicValue { - val descriptor = MemoryChunkDescriptor(chunkId, objectType, fieldType) - val array = memory.findArray(descriptor) - val value = array.select(addr) - touchMemoryChunk(descriptor) - return when (fieldType) { - is RefType -> createObject( - UtAddrExpression(value), - fieldType, - useConcreteType = false, - mockInfoGenerator - ) - is ArrayType -> createArray(UtAddrExpression(value), fieldType, useConcreteType = false) - else -> PrimitiveValue(fieldType, value) - } - } - - /** - * Creates field that can be mock. Mock strategy to decide. - */ - fun createFieldOrMock( - objectType: RefType, - addr: UtAddrExpression, - field: SootField, - mockInfoGenerator: UtMockInfoGenerator? - ): SymbolicValue { - val chunkId = hierarchy.chunkIdForField(objectType, field) - val createdField = createField(objectType, addr, field.type, chunkId, mockInfoGenerator) - - if (field.type is RefLikeType) { - if (field.shouldBeNotNull()) { - queuedSymbolicStateUpdates += mkNot(mkEq(createdField.addr, nullObjectAddr)).asHardConstraint() - } - - // See docs/SpeculativeFieldNonNullability.md for details - if (field.isFinal && field.declaringClass.isLibraryClass && !checkNpeForFinalFields) { - markAsSpeculativelyNotNull(createdField.addr) - } - } - - return createdField - } - - private fun createArray(pName: String, type: ArrayType): ArrayValue { - val addr = UtAddrExpression(mkBVConst(pName, UtIntSort)) - return createArray(addr, type, useConcreteType = false) - } - - /** - * Creates an array with given [addr] and [type]. - * - * [addQueuedTypeConstraints] is used to indicate whether we want to create array and work with its information - * by ourselves (i.e. in the instanceof) or to create an array and add type information - * into the [queuedSymbolicStateUpdates] right here. - */ - internal fun createArray( - addr: UtAddrExpression, type: ArrayType, - @Suppress("SameParameterValue") useConcreteType: Boolean = false, - addQueuedTypeConstraints: Boolean = true - ): ArrayValue { - touchAddress(addr) - - val length = memory.findArrayLength(addr) - - queuedSymbolicStateUpdates += Ge(length, 0).asHardConstraint() - queuedSymbolicStateUpdates += Le(length, softMaxArraySize).asHardConstraint() // TODO: fix big array length - - if (preferredCexOption) { - queuedSymbolicStateUpdates += Le(length, PREFERRED_ARRAY_SIZE).asSoftConstraint() - if (type.elementType is RefType) { - val descriptor = MemoryChunkDescriptor(typeRegistry.arrayChunkId(type), type, type.elementType) - val array = memory.findArray(descriptor) - queuedSymbolicStateUpdates += (0 until softMaxArraySize).flatMap { - val innerAddr = UtAddrExpression(array.select(addr, mkInt(it))) - mutableListOf().apply { - add(addrEq(innerAddr, nullObjectAddr)) - - // if we have an array of Object, assume that all of them have zero number of dimensions - if (type.elementType.isJavaLangObject()) { - add(typeRegistry.zeroDimensionConstraint(UtAddrExpression(innerAddr))) - } - } - }.asSoftConstraint() - } - - } - val typeStorage = typeResolver.constructTypeStorage(type, useConcreteType) - - if (addQueuedTypeConstraints) { - queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() - } - - touchMemoryChunk(MemoryChunkDescriptor(typeRegistry.arrayChunkId(type), type, type.elementType)) - return ArrayValue(typeStorage, addr) - } - - /** - * RefType and ArrayType consts have addresses less or equals to NULL_ADDR in order to separate objects - * created inside our program and given from outside. All given objects have negative addr or equal to NULL_ADDR. - * Since createConst called only for objects from outside at the beginning of the analysis, - * we can set Le(addr, NULL_ADDR) for all RefValue objects. - */ - private fun Value.createConst(pName: String, mockInfoGenerator: UtMockInfoGenerator? = null): SymbolicValue = - createConst(type, pName, mockInfoGenerator) - - fun createConst(type: Type, pName: String, mockInfoGenerator: UtMockInfoGenerator? = null): SymbolicValue = - when (type) { - is ByteType -> mkBVConst(pName, UtByteSort).toByteValue() - is ShortType -> mkBVConst(pName, UtShortSort).toShortValue() - is IntType -> mkBVConst(pName, UtIntSort).toIntValue() - is LongType -> mkBVConst(pName, UtLongSort).toLongValue() - is FloatType -> mkFpConst(pName, Float.SIZE_BITS).toFloatValue() - is DoubleType -> mkFpConst(pName, Double.SIZE_BITS).toDoubleValue() - is BooleanType -> mkBoolConst(pName).toBoolValue() - is CharType -> mkBVConst(pName, UtCharSort).toCharValue() - is ArrayType -> createArray(pName, type).also { - val addr = it.addr.toIntValue() - queuedSymbolicStateUpdates += Le(addr, nullObjectAddr.toIntValue()).asHardConstraint() - // if we don't 'touch' this array during the execution, it should be null - queuedSymbolicStateUpdates += addrEq(it.addr, nullObjectAddr).asSoftConstraint() - } - is RefType -> { - val addr = UtAddrExpression(mkBVConst(pName, UtIntSort)) - queuedSymbolicStateUpdates += Le(addr.toIntValue(), nullObjectAddr.toIntValue()).asHardConstraint() - // if we don't 'touch' this object during the execution, it should be null - queuedSymbolicStateUpdates += addrEq(addr, nullObjectAddr).asSoftConstraint() - - if (type.sootClass.isEnum) { - createEnum(type, addr) - } else { - createObject(addr, type, useConcreteType = addr.isThisAddr, mockInfoGenerator) - } - } - is VoidType -> voidValue - else -> error("Can't create const from ${type::class}") - } - - private fun createEnum(type: RefType, addr: UtAddrExpression): ObjectValue { - val typeStorage = typeResolver.constructTypeStorage(type, useConcreteType = true) - - queuedSymbolicStateUpdates += typeRegistry.typeConstraint(addr, typeStorage).all().asHardConstraint() - - val array = memory.findArray(MemoryChunkDescriptor(ENUM_ORDINAL, type, IntType.v())) - val ordinal = array.select(addr).toIntValue() - val enumSize = classLoader.loadClass(type.sootClass.name).enumConstants.size - - queuedSymbolicStateUpdates += mkOr(Ge(ordinal, 0), addrEq(addr, nullObjectAddr)).asHardConstraint() - queuedSymbolicStateUpdates += mkOr(Lt(ordinal, enumSize), addrEq(addr, nullObjectAddr)).asHardConstraint() - - touchAddress(addr) - - return ObjectValue(typeStorage, addr) - } - - private fun arrayUpdate(array: ArrayValue, index: PrimitiveValue, value: UtExpression): MemoryUpdate { - val type = array.type - val chunkId = typeRegistry.arrayChunkId(type) - val descriptor = MemoryChunkDescriptor(chunkId, type, type.elementType) - - val updatedNestedArray = memory.findArray(descriptor) - .select(array.addr) - .store(index.expr, value) - - return MemoryUpdate(persistentListOf(simplifiedNamedStore(descriptor, array.addr, updatedNestedArray))) - } - - fun objectUpdate( - instance: ObjectValue, - field: SootField, - value: UtExpression - ): MemoryUpdate { - val chunkId = hierarchy.chunkIdForField(instance.type, field) - val descriptor = MemoryChunkDescriptor(chunkId, instance.type, field.type) - return MemoryUpdate(persistentListOf(simplifiedNamedStore(descriptor, instance.addr, value))) - } - - fun arrayUpdateWithValue( - addr: UtAddrExpression, - type: ArrayType, - newValue: UtExpression - ): MemoryUpdate { - require(newValue.sort is UtArraySort) { "Expected UtArraySort, but ${newValue.sort} was found" } - - val chunkId = typeRegistry.arrayChunkId(type) - val descriptor = MemoryChunkDescriptor(chunkId, type, type.elementType) - - return MemoryUpdate(persistentListOf(simplifiedNamedStore(descriptor, addr, newValue))) - } - - fun selectArrayExpressionFromMemory( - array: ArrayValue - ): UtExpression { - val addr = array.addr - val arrayType = array.type - val chunkId = typeRegistry.arrayChunkId(arrayType) - val descriptor = MemoryChunkDescriptor(chunkId, arrayType, arrayType.elementType) - return memory.findArray(descriptor).select(addr) - } - - private fun touchMemoryChunk(chunkDescriptor: MemoryChunkDescriptor) { - queuedSymbolicStateUpdates += MemoryUpdate(touchedChunkDescriptors = persistentSetOf(chunkDescriptor)) - } - - private fun touchAddress(addr: UtAddrExpression) { - queuedSymbolicStateUpdates += MemoryUpdate(touchedAddresses = persistentListOf(addr)) - } - - private fun markAsSpeculativelyNotNull(addr: UtAddrExpression) { - queuedSymbolicStateUpdates += MemoryUpdate(speculativelyNotNullAddresses = persistentListOf(addr)) - } - - /** - * Add a memory update to reflect that a field was read. - * - * If the field belongs to a substitute object, record the read access for the real type instead. - */ - private fun recordInstanceFieldRead(addr: UtAddrExpression, field: SootField) { - val realType = typeRegistry.findRealType(field.declaringClass.type) - if (realType is RefType) { - val readOperation = InstanceFieldReadOperation(addr, FieldId(realType.id, field.name)) - queuedSymbolicStateUpdates += MemoryUpdate(instanceFieldReads = persistentSetOf(readOperation)) - } - } - - private suspend fun FlowCollector.traverseException(current: Stmt, exception: SymbolicFailure) { - if (!traverseCatchBlock(current, exception, emptySet())) { - processResult(exception) - } - } - - /** - * Finds appropriate catch block and adds it as next state to path selector. - * - * Returns true if found, false otherwise. - */ - private fun traverseCatchBlock( - current: Stmt, - exception: SymbolicFailure, - conditions: Set - ): Boolean { - val classId = exception.fold( - { it.javaClass.id }, - { (exception.symbolic as ObjectValue).type.id } - ) - val edge = findCatchBlock(current, classId) ?: return false - - pathSelector.offer( - environment.state.updateQueued( - edge, - SymbolicStateUpdate( - hardConstraints = conditions.asHardConstraint(), - localMemoryUpdates = localMemoryUpdate(CAUGHT_EXCEPTION to exception.symbolic) - ) - ) - ) - return true - } - - private fun findCatchBlock(current: Stmt, classId: ClassId): Edge? { - val stmtToEdge = globalGraph.exceptionalSuccs(current).associateBy { it.dst } - return globalGraph.traps.asSequence().mapNotNull { (stmt, exceptionClass) -> - stmtToEdge[stmt]?.let { it to exceptionClass } - }.firstOrNull { it.second in hierarchy.ancestors(classId) }?.first - } - - private fun invokeResult(invokeExpr: Expr): List = - environment.state.methodResult?.let { - listOf(it) - } ?: when (invokeExpr) { - is JStaticInvokeExpr -> staticInvoke(invokeExpr) - is JInterfaceInvokeExpr -> virtualAndInterfaceInvoke(invokeExpr.base, invokeExpr.methodRef, invokeExpr.args) - is JVirtualInvokeExpr -> virtualAndInterfaceInvoke(invokeExpr.base, invokeExpr.methodRef, invokeExpr.args) - is JSpecialInvokeExpr -> specialInvoke(invokeExpr) - is JDynamicInvokeExpr -> dynamicInvoke(invokeExpr) - else -> error("Unknown class ${invokeExpr::class}") - } - - /** - * Returns a [MethodResult] containing a mock for a static method call - * of the [method] if it should be mocked, null otherwise. - * - * @see Mocker.shouldMock - * @see UtStaticMethodMockInfo - */ - private fun mockStaticMethod(method: SootMethod, args: List): List? { - val methodId = method.executableId as MethodId - val declaringClassType = method.declaringClass.type - - val generator = UtMockInfoGenerator { addr -> UtStaticObjectMockInfo(declaringClassType.classId, addr) } - // It is important to save the previous state of the queuedMemoryUpdates, because `findOrCreateStaticObject` - // will change it. If we should not mock the object, we must `reset` memory updates to the previous state. - val prevMemoryUpdate = queuedSymbolicStateUpdates.memoryUpdates - val static = findOrCreateStaticObject(declaringClassType, generator) - - val mockInfo = UtStaticMethodMockInfo(static.addr, methodId) - - // We don't want to mock synthetic, private and protected methods - val isUnwantedToMockMethod = method.isSynthetic || method.isPrivate || method.isProtected - val shouldMock = mocker.shouldMock(declaringClassType, mockInfo) - val privateAndProtectedMethodsInArgs = parametersContainPrivateAndProtectedTypes(method) - - if (!shouldMock || method.isStaticInitializer) { - queuedSymbolicStateUpdates = queuedSymbolicStateUpdates.copy(memoryUpdates = prevMemoryUpdate) - return null - } - - // TODO temporary we return unbounded symbolic variable with a wrong name. - // TODO Probably it'll lead us to the path divergence - workaround(HACK) { - if (isUnwantedToMockMethod || privateAndProtectedMethodsInArgs) { - queuedSymbolicStateUpdates = queuedSymbolicStateUpdates.copy(memoryUpdates = prevMemoryUpdate) - return listOf(unboundedVariable(name = "staticMethod", method)) - } - } - - return static.asWrapperOrNull?.run { - invoke(static, method, args).map { it as MethodResult } - } - } - - private fun parametersContainPrivateAndProtectedTypes(method: SootMethod) = - method.parameterTypes.any { paramType -> - (paramType.baseType as? RefType)?.let { - it.sootClass.isPrivate || it.sootClass.isProtected - } == true - } - - /** - * Returns [MethodResult] with a mock for [org.utbot.api.mock.UtMock.makeSymbolic] call, - * if the [invokeExpr] contains it, null otherwise. - * - * @see mockStaticMethod - */ - private fun mockMakeSymbolic(invokeExpr: JStaticInvokeExpr): List? { - val methodSignature = invokeExpr.method.signature - if (methodSignature != makeSymbolicMethod.signature && methodSignature != nonNullableMakeSymbolic.signature) return null - - val method = environment.method - val declaringClass = method.declaringClass - val isInternalMock = method.hasInternalMockAnnotation || declaringClass.allMethodsAreInternalMocks || declaringClass.isOverridden - val parameters = resolveParameters(invokeExpr.args, invokeExpr.method.parameterTypes) - val mockMethodResult = mockStaticMethod(invokeExpr.method, parameters)?.single() - ?: error("Unsuccessful mock attempt of the `makeSymbolic` method call: $invokeExpr") - val mockResult = mockMethodResult.symbolicResult as SymbolicSuccess - val mockValue = mockResult.value - - // the last parameter of the makeSymbolic is responsible for nullability - val isNullable = if (parameters.isEmpty()) UtFalse else UtCastExpression( - parameters.last() as PrimitiveValue, - BooleanType.v() - ) - - // isNullable || mockValue != null - val additionalConstraint = mkOr( - mkEq(isNullable, UtTrue), - mkNot(mkEq(mockValue.addr, nullObjectAddr)), - ) - - // since makeSymbolic returns Object and casts it during the next instruction, we should - // disable ClassCastException for it to avoid redundant ClassCastException - typeRegistry.disableCastClassExceptionCheck(mockValue.addr) - - return listOf( - MethodResult( - mockValue, - hardConstraints = additionalConstraint.asHardConstraint(), - memoryUpdates = if (isInternalMock) MemoryUpdate() else mockMethodResult.memoryUpdates - ) - ) - } - - fun attachMockListener(mockListener: MockListener) = mocker.mockListenerController?.attach(mockListener) - - private fun staticInvoke(invokeExpr: JStaticInvokeExpr): List { - val parameters = resolveParameters(invokeExpr.args, invokeExpr.method.parameterTypes) - val result = mockMakeSymbolic(invokeExpr) ?: mockStaticMethod(invokeExpr.method, parameters) - - if (result != null) return result - - val method = invokeExpr.retrieveMethod() - val invocation = Invocation(null, method, parameters, InvocationTarget(null, method)) - return commonInvokePart(invocation) - } - - /** - * Identifies different invocation targets by finding all overloads of invoked method. - * Each target defines/reduces object type to set of concrete (not abstract, not interface) - * classes with particular method implementation. - */ - private fun virtualAndInterfaceInvoke( - base: Value, - methodRef: SootMethodRef, - parameters: List - ): List { - val instance = base.resolve() - if (instance !is ReferenceValue) error("We cannot run $methodRef on $instance") - - nullPointerExceptionCheck(instance.addr) - - if (instance.isNullObject()) return emptyList() // Nothing to call - - val method = methodRef.resolve() - val resolvedParameters = resolveParameters(parameters, method.parameterTypes) - - val invocation = Invocation(instance, method, resolvedParameters) { - when (instance) { - is ObjectValue -> findInvocationTargets(instance, methodRef.subSignature.string) - is ArrayValue -> listOf(InvocationTarget(instance, method)) - } - } - return commonInvokePart(invocation) - } - - /** - * Returns invocation targets for particular method implementation. - * - * Note: for some well known classes returns hardcoded choices. - */ - private fun findInvocationTargets( - instance: ObjectValue, - methodSubSignature: String - ): List { - val visitor = solver.rewriter.axiomInstantiationVisitor - val simplifiedAddr = instance.addr.accept(visitor) - // UtIsExpression for object with address the same as instance.addr - val instanceOfConstraint = solver.assertions.singleOrNull { - it is UtIsExpression && it.addr == simplifiedAddr - } as? UtIsExpression - // if we have UtIsExpression constraint for [instance], then find invocation targets - // for possibleTypes from this constraints, instead of the type maintained by solver. - - // While using simplifications with RewritingVisitor, assertions can maintain types - // for objects (especially objects with type equals to type parameter of generic) - // better than engine. - val types = instanceOfConstraint?.typeStorage?.possibleConcreteTypes ?: instance.possibleConcreteTypes - val methodInvocationTargets = findLibraryTargets(instance.type, methodSubSignature) - ?: findMethodInvocationTargets(types, methodSubSignature) - - return methodInvocationTargets - .map { (method, implementationClass, possibleTypes) -> - val typeStorage = typeResolver.constructTypeStorage(implementationClass, possibleTypes) - val mockInfo = memory.mockInfoByAddr(instance.addr) - val mockedObject = mockInfo?.let { - // TODO rewrite to fix JIRA:1611 - val type = Scene.v().getSootClass(mockInfo.classId.name).type - val ancestorTypes = typeResolver.findOrConstructAncestorsIncludingTypes(type) - val updatedMockInfo = if (implementationClass in ancestorTypes) { - it - } else { - it.copyWithClassId(classId = implementationClass.id) - } - mocker.mock(implementationClass, updatedMockInfo) - } - - if (mockedObject == null) { - // Above we might get implementationClass that has to be substituted. - // For example, for a call "Collection.size()" such classes will be produced. - val wrapperOrInstance = wrapper(implementationClass, instance.addr) - ?: instance.copy(typeStorage = typeStorage) - - val typeConstraint = typeRegistry.typeConstraint(instance.addr, wrapperOrInstance.typeStorage) - val constraints = setOf(typeConstraint.isOrNullConstraint()) - - // TODO add memory updated for types JIRA:1523 - - InvocationTarget(wrapperOrInstance, method, constraints) - } else { - val typeConstraint = typeRegistry.typeConstraint(mockedObject.addr, mockedObject.typeStorage) - val constraints = setOf(typeConstraint.isOrNullConstraint()) - - // TODO add memory updated for types JIRA:1523 - // TODO isMock???? - InvocationTarget(mockedObject, method, constraints) - } - } - } - - private fun findLibraryTargets(type: RefType, methodSubSignature: String): List? { - val libraryTargets = libraryTargets[type.className] ?: return null - return libraryTargets.mapNotNull { className -> - val implementationClass = Scene.v().getSootClass(className) - val method = implementationClass.findMethodOrNull(methodSubSignature) - method?.let { - MethodInvocationTarget(method, implementationClass.type, listOf(implementationClass.type)) - } - } - } - - /** - * Returns sorted list of particular method implementations (invocation targets). - */ - private fun findMethodInvocationTargets( - concretePossibleTypes: Set, - methodSubSignature: String - ): List { - val implementationToClasses = concretePossibleTypes - .filterIsInstance() - .groupBy { it.sootClass.findMethodOrNull(methodSubSignature)?.declaringClass } - .filterValues { it.appropriateClasses().isNotEmpty() } - - val targets = mutableListOf() - for ((sootClass, types) in implementationToClasses) { - if (sootClass != null) { - targets += MethodInvocationTarget(sootClass.getMethod(methodSubSignature), sootClass.type, types) - } - } - - // do some hopeless sorting - return targets - .asSequence() - .sortedByDescending { typeRegistry.findRating(it.implementationClass) } - .take(10) - .sortedByDescending { it.possibleTypes.size } - .sortedBy { it.method.isNative } - .take(5) - .sortedByDescending { typeRegistry.findRating(it.implementationClass) } - .toList() - } - - private fun specialInvoke(invokeExpr: JSpecialInvokeExpr): List { - val instance = invokeExpr.base.resolve() - if (instance !is ReferenceValue) error("We cannot run ${invokeExpr.methodRef} on $instance") - - nullPointerExceptionCheck(instance.addr) - - if (instance.isNullObject()) return emptyList() // Nothing to call - - val method = invokeExpr.retrieveMethod() - val parameters = resolveParameters(invokeExpr.args, method.parameterTypes) - val invocation = Invocation(instance, method, parameters, InvocationTarget(instance, method)) - return commonInvokePart(invocation) - } - - private fun dynamicInvoke(invokeExpr: JDynamicInvokeExpr): List { - workaround(HACK) { - // The engine does not yet support JDynamicInvokeExpr, so switch to concrete execution if we encounter it - statesForConcreteExecution += environment.state - queuedSymbolicStateUpdates += UtFalse.asHardConstraint() - return emptyList() - } - } - - /** - * Runs common invocation part for object wrapper or object instance. - * - * Returns results of native calls cause other calls push changes directly to path selector. - */ - private fun commonInvokePart(invocation: Invocation): List { - // First, check if there is override for the invocation itself, not for the targets - val artificialMethodOverride = overrideInvocation(invocation, target = null) - - // If so, return the result of the override - if (artificialMethodOverride.success) { - if (artificialMethodOverride.results.size > 1) { - environment.state.definitelyFork() - } - - return mutableListOf().apply { - for (result in artificialMethodOverride.results) { - when (result) { - is MethodResult -> add(result) - is GraphResult -> pushToPathSelector( - result.graph, - invocation.instance, - invocation.parameters, - result.constraints, - isLibraryMethod = true - ) - } - } - } - } - - // If there is no such invocation, use the generator to produce invocation targets - val targets = invocation.generator.invoke() - - // Take all the targets and run them, at least one target must exist - require(targets.isNotEmpty()) { "No targets for $invocation" } - - // Note that sometimes invocation on the particular targets should be overridden as well. - // For example, Collection.size will produce two targets (ArrayList and HashSet) - // that will override the invocation. - val overrideResults = targets.map { it to overrideInvocation(invocation, it) } - - if (overrideResults.sumOf { (_, overriddenResult) -> overriddenResult.results.size } > 1) { - environment.state.definitelyFork() - } - - // Separate targets for which invocation should be overridden - // from the targets that should be processed regularly. - val (overridden, original) = overrideResults.partition { it.second.success } - - val overriddenResults = overridden - .flatMap { (target, overriddenResult) -> - mutableListOf().apply { - for (result in overriddenResult.results) { - when (result) { - is MethodResult -> add(result) - is GraphResult -> pushToPathSelector( - result.graph, - // take the instance from the target - target.instance, - invocation.parameters, - // It is important to add constraints for the target as well, because - // those constraints contain information about the type of the - // instance from the target - target.constraints + result.constraints, - // Since we override methods of the classes from the standard library - isLibraryMethod = true - ) - } - } - } - } - - // Add results for the targets that should be processed without override - val originResults = original.flatMap { (target: InvocationTarget, _) -> - invoke(target, invocation.parameters) - } - - // Return their concatenation - return overriddenResults + originResults - } - - private fun invoke( - target: InvocationTarget, - parameters: List - ): List = with(target.method) { - val substitutedMethod = typeRegistry.findSubstitutionOrNull(this) - - if (isNative && substitutedMethod == null) return processNativeMethod(target) - - // If we face UtMock.assume call, we should continue only with the branch - // where the predicate from the parameters is equal true - when { - isUtMockAssume || isUtMockAssumeOrExecuteConcretely -> { - val param = UtCastExpression(parameters.single() as PrimitiveValue, BooleanType.v()) - - val assumptionStmt = mkEq(param, UtTrue) - val (hardConstraints, assumptions) = if (isUtMockAssume) { - // For UtMock.assume we must add the assumeStmt to the hard constraints - setOf(assumptionStmt) to emptySet() - } else { - // For assumeOrExecuteConcretely we must add the statement to the assumptions. - // It is required to have opportunity to remove it later in case of unsat state - // because of it and execute the state concretely. - emptySet() to setOf(assumptionStmt) - } - - val symbolicStateUpdate = SymbolicStateUpdate( - hardConstraints = hardConstraints.asHardConstraint(), - assumptions = assumptions.asAssumption() - ) - - val stateToContinue = environment.state.updateQueued( - globalGraph.succ(environment.state.stmt), - symbolicStateUpdate - ) - pathSelector.offer(stateToContinue) - - // we already pushed state with the fulfilled predicate, so we can just drop our branch here by - // adding UtFalse to the constraints. - queuedSymbolicStateUpdates += UtFalse.asHardConstraint() - emptyList() - } - declaringClass == utOverrideMockClass -> utOverrideMockInvoke(target, parameters) - declaringClass == utLogicMockClass -> utLogicMockInvoke(target, parameters) - declaringClass == utArrayMockClass -> utArrayMockInvoke(target, parameters) - else -> { - val graph = substitutedMethod?.jimpleBody()?.graph() ?: jimpleBody().graph() - pushToPathSelector(graph, target.instance, parameters, target.constraints, isLibraryMethod) - emptyList() - } - } - } - - private fun utOverrideMockInvoke(target: InvocationTarget, parameters: List): List { - when (target.method.name) { - utOverrideMockAlreadyVisitedMethodName -> { - return listOf(MethodResult(memory.isVisited(parameters[0].addr).toBoolValue())) - } - utOverrideMockVisitMethodName -> { - return listOf( - MethodResult( - voidValue, - memoryUpdates = MemoryUpdate(visitedValues = persistentListOf(parameters[0].addr)) - ) - ) - } - utOverrideMockDoesntThrowMethodName -> { - val stateToContinue = environment.state.updateQueued( - globalGraph.succ(environment.state.stmt), - doesntThrow = true - ) - pathSelector.offer(stateToContinue) - queuedSymbolicStateUpdates += UtFalse.asHardConstraint() - return emptyList() - } - utOverrideMockParameterMethodName -> { - when (val param = parameters.single() as ReferenceValue) { - is ObjectValue -> { - val addr = param.addr.toIntValue() - val stateToContinue = environment.state.updateQueued( - globalGraph.succ(environment.state.stmt), - SymbolicStateUpdate( - hardConstraints = Le(addr, nullObjectAddr.toIntValue()).asHardConstraint() - ) - ) - pathSelector.offer(stateToContinue) - } - is ArrayValue -> { - val addr = param.addr - val descriptor = - MemoryChunkDescriptor( - typeRegistry.arrayChunkId(OBJECT_TYPE.arrayType), - OBJECT_TYPE.arrayType, - OBJECT_TYPE - ) - - val update = MemoryUpdate( - persistentListOf( - simplifiedNamedStore( - descriptor, - addr, - UtArrayApplyForAll(memory.findArray(descriptor).select(addr)) { array, i -> - Le(array.select(i.expr).toIntValue(), nullObjectAddr.toIntValue()) - } - ) - ) - ) - val stateToContinue = environment.state.updateQueued( - edge = globalGraph.succ(environment.state.stmt), - SymbolicStateUpdate( - hardConstraints = Le(addr.toIntValue(), nullObjectAddr.toIntValue()).asHardConstraint(), - memoryUpdates = update - ) - ) - pathSelector.offer(stateToContinue) - } - } - - - // we already pushed state with the fulfilled predicate, so we can just drop our branch here by - // adding UtFalse to the constraints. - queuedSymbolicStateUpdates += UtFalse.asHardConstraint() - return emptyList() - } - utOverrideMockExecuteConcretelyMethodName -> { - statesForConcreteExecution += environment.state - queuedSymbolicStateUpdates += UtFalse.asHardConstraint() - return emptyList() - } - else -> unreachableBranch("unknown method ${target.method.signature} in ${UtOverrideMock::class.qualifiedName}") - } - } - - private fun utArrayMockInvoke(target: InvocationTarget, parameters: List): List { - when (target.method.name) { - utArrayMockArraycopyMethodName -> { - val src = parameters[0] as ArrayValue - val dst = parameters[2] as ArrayValue - val copyValue = UtArraySetRange( - selectArrayExpressionFromMemory(dst), - parameters[3] as PrimitiveValue, - selectArrayExpressionFromMemory(src), - parameters[1] as PrimitiveValue, - parameters[4] as PrimitiveValue - ) - return listOf( - MethodResult( - voidValue, - memoryUpdates = arrayUpdateWithValue(dst.addr, dst.type, copyValue) - ) - ) - } - utArrayMockCopyOfMethodName -> { - val src = parameters[0] as ArrayValue - val length = parameters[1] as PrimitiveValue - val arrayType = target.method.returnType as ArrayType - val newArray = createNewArray(length, arrayType, arrayType.elementType) - return listOf( - MethodResult( - newArray, - memoryUpdates = arrayUpdateWithValue(newArray.addr, arrayType, selectArrayExpressionFromMemory(src)) - ) - ) - } - else -> unreachableBranch("unknown method ${target.method.signature} for ${UtArrayMock::class.qualifiedName}") - } - } - - private fun utLogicMockInvoke(target: InvocationTarget, parameters: List): List { - when (target.method.name) { - utLogicMockLessMethodName -> { - val a = parameters[0] as PrimitiveValue - val b = parameters[1] as PrimitiveValue - return listOf(MethodResult(Lt(a, b).toBoolValue())) - } - utLogicMockIteMethodName -> { - var isPrimitive = false - val thenExpr = parameters[1].let { - if (it is PrimitiveValue) { - isPrimitive = true - it.expr - } else { - it.addr.internal - } - } - val elseExpr = parameters[2].let { - if (it is PrimitiveValue) { - isPrimitive = true - it.expr - } else { - it.addr.internal - } - } - val condition = (parameters[0] as PrimitiveValue).expr as UtBoolExpression - val iteExpr = UtIteExpression(condition, thenExpr, elseExpr) - val result = if (isPrimitive) { - PrimitiveValue(target.method.returnType, iteExpr) - } else { - ObjectValue( - typeResolver.constructTypeStorage(target.method.returnType, useConcreteType = false), - UtAddrExpression(iteExpr) - ) - } - return listOf(MethodResult(result)) - } - else -> unreachableBranch("unknown method ${target.method.signature} in ${UtLogicMock::class.qualifiedName}") - } - } - - /** - * Tries to override method. Override can be object wrapper or similar implementation. - * - * Proceeds overridden method as non-library. - */ - private fun overrideInvocation(invocation: Invocation, target: InvocationTarget?): OverrideResult { - // If we try to override invocation itself, the target is null, and we have to process - // the instance from the invocation, otherwise take the one from the target - val instance = if (target == null) invocation.instance else target.instance - val subSignature = invocation.method.subSignature - - if (subSignature == "java.lang.Class getClass()") { - return when (instance) { - is ReferenceValue -> { - val type = instance.type - val createClassRef = if (type is RefLikeType) { - typeRegistry.createClassRef(type.baseType, type.numDimensions) - } else { - error("Can't get class name for $type") - } - OverrideResult(success = true, createClassRef) - } - null -> unreachableBranch("Static getClass call: $invocation") - } - } - - val instanceAsWrapperOrNull = instance?.asWrapperOrNull - - if (instanceAsWrapperOrNull is UtMockWrapper && subSignature == HASHCODE_SIGNATURE) { - val result = MethodResult(mkBVConst("hashcode${hashcodeCounter++}", UtIntSort).toIntValue()) - return OverrideResult(success = true, result) - } - - if (instanceAsWrapperOrNull is UtMockWrapper && subSignature == EQUALS_SIGNATURE) { - val result = MethodResult(mkBoolConst("equals${equalsCounter++}").toBoolValue()) - return OverrideResult(success = true, result) - } - - // we cannot mock synthetic methods and methods that have private or protected arguments - val impossibleToMock = - invocation.method.isSynthetic || invocation.method.isProtected || parametersContainPrivateAndProtectedTypes( - invocation.method - ) - - if (instanceAsWrapperOrNull is UtMockWrapper && impossibleToMock) { - // TODO temporary we return unbounded symbolic variable with a wrong name. - // TODO Probably it'll lead us to the path divergence - workaround(HACK) { - val result = unboundedVariable("unbounded", invocation.method) - return OverrideResult(success = true, result) - } - } - - if (instance is ArrayValue && invocation.method.name == "clone") { - return OverrideResult(success = true, cloneArray(instance)) - } - - instanceAsWrapperOrNull?.run { - val results = invoke(instance as ObjectValue, invocation.method, invocation.parameters) - if (results.isEmpty()) { - // Drop the branch and switch to concrete execution - statesForConcreteExecution += environment.state - queuedSymbolicStateUpdates += UtFalse.asHardConstraint() - } - return OverrideResult(success = true, results) - } - - return OverrideResult(success = false) - } - - private fun cloneArray(array: ArrayValue): MethodResult { - val addr = findNewAddr() - - val type = array.type - val elementType = type.elementType - val chunkId = typeRegistry.arrayChunkId(type) - val descriptor = MemoryChunkDescriptor(chunkId, type, elementType) - val arrays = memory.findArray(descriptor) - - val arrayLength = memory.findArrayLength(array.addr) - val cloneLength = memory.findArrayLength(addr) - - val constraints = setOf( - mkEq(typeRegistry.symTypeId(array.addr), typeRegistry.symTypeId(addr)), - mkEq(typeRegistry.symNumDimensions(array.addr), typeRegistry.symNumDimensions(addr)), - mkEq(cloneLength, arrayLength) - ) + (0 until softMaxArraySize).map { - val index = mkInt(it) - mkEq( - arrays.select(array.addr, index).toPrimitiveValue(elementType), - arrays.select(addr, index).toPrimitiveValue(elementType) - ) - } - -// TODO: add preferred cex to: val softConstraints = preferredConstraints(clone) - - val memoryUpdate = MemoryUpdate(touchedChunkDescriptors = persistentSetOf(descriptor)) - - val clone = ArrayValue(TypeStorage(array.type), addr) - return MethodResult(clone, constraints.asHardConstraint(), memoryUpdates = memoryUpdate) - } - - // For now, we just create unbounded symbolic variable as a result. - private fun processNativeMethod(target: InvocationTarget): List = - listOf(unboundedVariable(name = "nativeConst", target.method)) - - private fun unboundedVariable(name: String, method: SootMethod): MethodResult { - val value = when (val returnType = method.returnType) { - is RefType -> createObject(findNewAddr(), returnType, useConcreteType = true) - is ArrayType -> createArray(findNewAddr(), returnType, useConcreteType = true) - else -> createConst(returnType, "$name${unboundedConstCounter++}") - } - - return MethodResult(value) - } - - fun SootClass.findMethodOrNull(subSignature: String): SootMethod? { - adjustLevel(SootClass.SIGNATURES) - - val classes = generateSequence(this) { it.superClassOrNull() } - val interfaces = generateSequence(this) { it.superClassOrNull() }.flatMap { sootClass -> - sootClass.interfaces.flatMap { hierarchy.ancestors(it.id) } - }.distinct() - return (classes + interfaces) - .filter { - it.adjustLevel(SootClass.SIGNATURES) - it.declaresMethod(subSignature) - } - .mapNotNull { it.getMethod(subSignature) } - .firstOrNull { it.canRetrieveBody() || it.isNative } - } - - private fun pushToPathSelector( - graph: ExceptionalUnitGraph, - caller: ReferenceValue?, - callParameters: List, - constraints: Set = emptySet(), - isLibraryMethod: Boolean = false - ) { - globalGraph.join(environment.state.stmt, graph, !isLibraryMethod) - val parametersWithThis = listOfNotNull(caller) + callParameters - pathSelector.offer( - environment.state.push( - graph.head, - inputArguments = ArrayDeque(parametersWithThis), - queuedSymbolicStateUpdates + constraints.asHardConstraint(), - graph.body.method - ) - ) - } - - private fun ExecutionState.updateQueued( - edge: Edge, - update: SymbolicStateUpdate = SymbolicStateUpdate(), - doesntThrow: Boolean = false - ) = this.update( - edge, - queuedSymbolicStateUpdates + update, - doesntThrow - ) - - private fun resolveIfCondition(cond: BinopExpr): ResolvedCondition { - // We add cond.op.type for null values only. If we have condition like "null == r1" - // we'll have ObjectInstance(r1::type) and ObjectInstance(r1::type) for now - // For non-null values type is ignored. - val lhs = cond.op1.resolve(cond.op2.type) - val rhs = cond.op2.resolve(cond.op1.type) - return when { - lhs.isNullObject() || rhs.isNullObject() -> { - val eq = addrEq(lhs.addr, rhs.addr) - if (cond is NeExpr) ResolvedCondition(mkNot(eq)) else ResolvedCondition(eq) - } - lhs is ReferenceValue && rhs is ReferenceValue -> { - ResolvedCondition(compareReferenceValues(lhs, rhs, cond is NeExpr)) - } - else -> { - val expr = cond.resolve().asPrimitiveValueOrError as UtBoolExpression - val memoryUpdates = collectSymbolicStateUpdates(expr) - ResolvedCondition( - expr, - constructSoftConstraintsForCondition(cond), - symbolicStateUpdates = memoryUpdates - ) - } - } - } - - /** - * Tries to collect all memory updates from nested [UtInstanceOfExpression]s in the [expr]. - * Resolves only basic cases: `not`, `and`, `z0 == 0`, `z0 == 1`, `z0 != 0`, `z0 != 1`. - * - * It's impossible now to make this function complete, because our [Memory] can't deal with some expressions - * (e.g. [UtOrBoolExpression] consisted of [UtInstanceOfExpression]s). - */ - private fun collectSymbolicStateUpdates(expr: UtBoolExpression): SymbolicStateUpdateForResolvedCondition { - return when (expr) { - is UtInstanceOfExpression -> { // for now only this type of expression produces deferred updates - val onlyMemoryUpdates = expr.symbolicStateUpdate.copy( - hardConstraints = HardConstraint(), - softConstraints = SoftConstraint() - ) - SymbolicStateUpdateForResolvedCondition(onlyMemoryUpdates) - } - is UtAndBoolExpression -> { - expr.exprs.fold(SymbolicStateUpdateForResolvedCondition()) { updates, nestedExpr -> - val nextPosUpdates = updates.positiveCase + collectSymbolicStateUpdates(nestedExpr).positiveCase - val nextNegUpdates = updates.negativeCase + collectSymbolicStateUpdates(nestedExpr).negativeCase - SymbolicStateUpdateForResolvedCondition(nextPosUpdates, nextNegUpdates) - } - } - // TODO: JIRA:1667 -- Engine can't apply memory updates for some expressions - is UtOrBoolExpression -> SymbolicStateUpdateForResolvedCondition() // Which clause should we apply? - is NotBoolExpression -> collectSymbolicStateUpdates(expr.expr).swap() - is UtBoolOpExpression -> { - // Java `instanceof` in `if` translates to UtBoolOpExpression. - // More precisely, something like this will be generated: - // ... - // z0: bool = obj instanceof A - // if z0 == 0 goto ... - // ... - // while traversing the condition, `BinopExpr` resolves to `UtBoolOpExpression` with the left part - // equals to `UtBoolExpession` (usually `UtInstanceOfExpression`), because it is stored in local `z0` - // and the right part equals to UtBvLiteral with the integer constant. - // - // If something more complex is written in the original `if`, these matches could not success. - // TODO: JIRA:1667 - val lhs = expr.left.expr as? UtBoolExpression - ?: return SymbolicStateUpdateForResolvedCondition() - val rhsAsIntValue = (expr.right.expr as? UtBvLiteral)?.value?.toInt() - ?: return SymbolicStateUpdateForResolvedCondition() - val updates = collectSymbolicStateUpdates(lhs) - - when (expr.operator) { - is Eq -> { - when (rhsAsIntValue) { - 1 -> updates // z0 == 1 - 0 -> updates.swap() // z0 == 0 - else -> SymbolicStateUpdateForResolvedCondition() - } - } - is Ne -> { - when (rhsAsIntValue) { - 1 -> updates.swap() // z0 != 1 - 0 -> updates // z0 != 0 - else -> SymbolicStateUpdateForResolvedCondition() - } - } - else -> SymbolicStateUpdateForResolvedCondition() - } - } - // TODO: JIRA:1667 -- Engine can't apply memory updates for some expressions - else -> SymbolicStateUpdateForResolvedCondition() - } - } - - private fun constructSoftConstraintsForCondition(cond: BinopExpr): SoftConstraintsForResolvedCondition { - var positiveCaseConstraint: UtBoolExpression? = null - var negativeCaseConstraint: UtBoolExpression? = null - - val left = cond.op1.resolve(cond.op2.type) - val right = cond.op2.resolve(cond.op1.type) - - if (left !is PrimitiveValue || right !is PrimitiveValue) return SoftConstraintsForResolvedCondition() - - val one = 1.toPrimitiveValue() - - when (cond) { - is JLtExpr -> { - positiveCaseConstraint = mkEq(left, Sub(right, one).toIntValue()) - negativeCaseConstraint = mkEq(left, right) - } - is JLeExpr -> { - positiveCaseConstraint = mkEq(left, right) - negativeCaseConstraint = mkEq(Sub(left, one).toIntValue(), right) - } - is JGeExpr -> { - positiveCaseConstraint = mkEq(left, right) - negativeCaseConstraint = mkEq(left, Sub(right, one).toIntValue()) - } - is JGtExpr -> { - positiveCaseConstraint = mkEq(Sub(left, one).toIntValue(), right) - negativeCaseConstraint = mkEq(left, right) - - } - else -> Unit - } - - return SoftConstraintsForResolvedCondition(positiveCaseConstraint, negativeCaseConstraint) - } - - /** - * Compares two objects with types, lhs :: lhsType and rhs :: rhsType. - * - * Does it by checking types equality, then addresses equality. - * - * Notes: - * - Content (assertions on fields) comparison is not necessary cause solver finds on its own and provides - * different object addresses in such case - * - We do not compare null addresses here, it happens in resolveIfCondition - * - * @see UtBotSymbolicEngine.resolveIfCondition - */ - private fun compareReferenceValues( - lhs: ReferenceValue, - rhs: ReferenceValue, - negate: Boolean - ): UtBoolExpression { - val eq = addrEq(lhs.addr, rhs.addr) - return if (negate) mkNot(eq) else eq - } - - private fun nullPointerExceptionCheck(addr: UtAddrExpression) { - val canBeNull = addrEq(addr, nullObjectAddr) - val canNotBeNull = mkNot(canBeNull) - val notMarked = mkEq(memory.isSpeculativelyNotNull(addr), mkFalse()) - val notMarkedAndNull = mkAnd(notMarked, canBeNull) - - if (environment.method.checkForNPE(environment.state.executionStack.size)) { - implicitlyThrowException(NullPointerException(), setOf(notMarkedAndNull)) - } - - queuedSymbolicStateUpdates += canNotBeNull.asHardConstraint() - } - - private fun divisionByZeroCheck(denom: PrimitiveValue) { - val equalsToZero = Eq(denom, 0) - implicitlyThrowException(ArithmeticException("/ by zero"), setOf(equalsToZero)) - queuedSymbolicStateUpdates += mkNot(equalsToZero).asHardConstraint() - } - - // Use cast to Int and cmp with min/max for Byte and Short. - // Formulae for Int and Long does not work for lower integers because of sign_extend ops in SMT. - private fun lowerIntMulOverflowCheck( - left: PrimitiveValue, - right: PrimitiveValue, - minValue: Int, - maxValue: Int, - ): UtBoolExpression { - val castedLeft = UtCastExpression(left, UtIntSort.type).toIntValue() - val castedRight = UtCastExpression(right, UtIntSort.type).toIntValue() - - val res = Mul(castedLeft, castedRight).toIntValue() - - val lessThanMinValue = Lt( - res, - minValue.toPrimitiveValue(), - ).toBoolValue() - - val greaterThanMaxValue = Gt( - res, - maxValue.toPrimitiveValue(), - ).toBoolValue() - - return Ne( - Or( - lessThanMinValue, - greaterThanMaxValue, - ).toBoolValue(), - 0.toPrimitiveValue() - ) - } - - - // Z3 internal operator for MulNoOverflow is currently bugged. - // Use formulae from Math.mulExact to detect mul overflow for Int and Long. - private fun higherIntMulOverflowCheck( - left: PrimitiveValue, - right: PrimitiveValue, - bits: Int, - minValue: Long, - toValue: (it: UtExpression) -> PrimitiveValue, - ): UtBoolExpression { - // https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/lang/Math.java#l882 - val leftValue = toValue(left.expr) - val rightValue = toValue(right.expr) - val res = toValue(Mul(leftValue, rightValue)) - - // extract absolute values - // https://www.geeksforgeeks.org/compute-the-integer-absolute-value-abs-without-branching/ - val leftAbsMask = toValue(Ushr(leftValue, (bits - 1).toPrimitiveValue())) - val leftAbs = toValue( - Xor( - toValue(Add(leftAbsMask, leftValue)), - leftAbsMask - ) - ) - val rightAbsMask = toValue(Ushr(rightValue, (bits - 1).toPrimitiveValue())) - val rightAbs = toValue( - Xor( - toValue(Add(rightAbsMask, rightValue)), - rightAbsMask - ) - ) - - // (((ax | ay) >>> 31 != 0)) - val bigEnough = Ne( - toValue( - Ushr( - toValue(Or(leftAbs, rightAbs)), - (bits ushr 1 - 1).toPrimitiveValue() - ) - ), - 0.toPrimitiveValue() - ) - - // (((y != 0) && (r / y != x)) - val incorrectDiv = And( - Ne(rightValue, 0.toPrimitiveValue()).toBoolValue(), - Ne(toValue(Div(res, rightValue)), leftValue).toBoolValue(), - ) - - // (x == Long.MIN_VALUE && y == -1)) - val minValueEdgeCase = And( - Eq(leftValue, minValue.toPrimitiveValue()).toBoolValue(), - Eq(rightValue, (-1).toPrimitiveValue()).toBoolValue() - ) - - return Ne( - And( - bigEnough.toBoolValue(), - Or( - incorrectDiv.toBoolValue(), - minValueEdgeCase.toBoolValue(), - ).toBoolValue() - ).toBoolValue(), - 0.toPrimitiveValue() - ) - } - - private fun intOverflowCheck(op: BinopExpr, leftRaw: PrimitiveValue, rightRaw: PrimitiveValue) { - // cast to the bigger type - val sort = simpleMaxSort(leftRaw, rightRaw) as UtPrimitiveSort - val left = leftRaw.expr.toPrimitiveValue(sort.type) - val right = rightRaw.expr.toPrimitiveValue(sort.type) - - val overflow = when (op) { - is JAddExpr -> { - mkNot(UtAddNoOverflowExpression(left.expr, right.expr)) - } - is JSubExpr -> { - mkNot(UtSubNoOverflowExpression(left.expr, right.expr)) - } - is JMulExpr -> when (sort.type) { - is ByteType -> lowerIntMulOverflowCheck(left, right, Byte.MIN_VALUE.toInt(), Byte.MAX_VALUE.toInt()) - is ShortType -> lowerIntMulOverflowCheck(left, right, Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()) - is IntType -> higherIntMulOverflowCheck(left, right, Int.SIZE_BITS, Int.MIN_VALUE.toLong()) { it: UtExpression -> it.toIntValue() } - is LongType -> higherIntMulOverflowCheck(left, right, Long.SIZE_BITS, Long.MIN_VALUE) { it: UtExpression -> it.toLongValue() } - else -> null - } - else -> null - } - - if (overflow != null) { - implicitlyThrowException(ArithmeticException("${left.type} ${op.symbol} overflow"), setOf(overflow)) - queuedSymbolicStateUpdates += mkNot(overflow).asHardConstraint() - } - } - - private fun indexOutOfBoundsChecks(index: PrimitiveValue, length: PrimitiveValue) { - val ltZero = Lt(index, 0) - implicitlyThrowException(IndexOutOfBoundsException("Less than zero"), setOf(ltZero)) - - val geLength = Ge(index, length) - implicitlyThrowException(IndexOutOfBoundsException("Greater or equal than length"), setOf(geLength)) - - queuedSymbolicStateUpdates += mkNot(ltZero).asHardConstraint() - queuedSymbolicStateUpdates += mkNot(geLength).asHardConstraint() - } - - private fun negativeArraySizeCheck(vararg sizes: PrimitiveValue) { - val ltZero = mkOr(sizes.map { Lt(it, 0) }) - implicitlyThrowException(NegativeArraySizeException("Less than zero"), setOf(ltZero)) - queuedSymbolicStateUpdates += mkNot(ltZero).asHardConstraint() - } - - /** - * Checks for ClassCastException. - * - * Note: if we have the valueToCast.addr related to some parameter with addr p_0, and that parameter's type is a parameterizedType, - * we ignore potential exception throwing if the typeAfterCast is one of the generics included in that type. - */ - private fun classCastExceptionCheck(valueToCast: ReferenceValue, typeAfterCast: Type) { - val baseTypeAfterCast = if (typeAfterCast is ArrayType) typeAfterCast.baseType else typeAfterCast - val addr = valueToCast.addr - - // Expected in the parameters baseType is an RefType because it is either an RefType itself or an array of RefType values - if (baseTypeAfterCast is RefType) { - // Find parameterized type for the object if it is a parameter of the method under test and it has generic type - val newAddr = addr.accept(solver.rewriter) as UtAddrExpression - val parameterizedType = when (newAddr.internal) { - is UtArraySelectExpression -> parameterAddrToGenericType[findTheMostNestedAddr(newAddr.internal)] - is UtBvConst -> parameterAddrToGenericType[newAddr] - else -> null - } - - if (parameterizedType != null) { - // Find all generics used in the type of the parameter and it's superclasses - // If we're trying to cast something related to the parameter and typeAfterCast is equal to one of the generic - // types used in it, don't throw ClassCastException - val genericTypes = generateSequence(parameterizedType) { it.ownerType as? ParameterizedType } - .flatMapTo(mutableSetOf()) { it.actualTypeArguments.map { arg -> arg.typeName } } - - if (baseTypeAfterCast.className in genericTypes) { - return - } - } - } - - val inheritorsTypeStorage = typeResolver.constructTypeStorage(typeAfterCast, useConcreteType = false) - val preferredTypesForCastException = valueToCast.possibleConcreteTypes.filterNot { it in inheritorsTypeStorage.possibleConcreteTypes } - - val isExpression = typeRegistry.typeConstraint(addr, inheritorsTypeStorage).isConstraint() - val notIsExpression = mkNot(isExpression) - - val nullEqualityConstraint = addrEq(addr, nullObjectAddr) - val notNull = mkNot(nullEqualityConstraint) - - val classCastExceptionAllowed = mkEq(UtTrue, typeRegistry.isClassCastExceptionAllowed(addr)) - - implicitlyThrowException( - ClassCastException("The object with type ${valueToCast.type} can not be casted to $typeAfterCast"), - setOf(notIsExpression, notNull, classCastExceptionAllowed), - setOf(constructConstraintForType(valueToCast, preferredTypesForCastException)) - ) - - queuedSymbolicStateUpdates += mkOr(isExpression, nullEqualityConstraint).asHardConstraint() - } - - private fun implicitlyThrowException( - exception: Exception, - conditions: Set, - softConditions: Set = emptySet() - ) { - if (environment.state.executionStack.last().doesntThrow) return - - val symException = implicitThrown(exception, findNewAddr(), isInNestedMethod()) - if (!traverseCatchBlock(environment.state.stmt, symException, conditions)) { - environment.state.expectUndefined() - val nextState = environment.state.createExceptionState( - symException, - queuedSymbolicStateUpdates - + conditions.asHardConstraint() - + softConditions.asSoftConstraint() - ) - globalGraph.registerImplicitEdge(nextState.lastEdge!!) - pathSelector.offer(nextState) - } - } - - - private val symbolicStackTrace: String - get() { - val methods = environment.state.executionStack.mapNotNull { it.caller } - .map { globalGraph.method(it) } + environment.method - return methods.reversed().joinToString(separator = "\n") { method -> - if (method.isDeclared) "$method" else method.subSignature - } - } - - /** - * Collects entry method statement path for ML. Eliminates duplicated statements, e.g. assignment with invocation - * in right part. - */ - private fun entryMethodPath(): MutableList { - val entryPath = mutableListOf() - environment.state.fullPath().forEach { step -> - // TODO: replace step.stmt in methodUnderAnalysisStmts with step.depth == 0 - // when fix SAT-812: [JAVA] Wrong depth when exception thrown - if (step.stmt in methodUnderAnalysisStmts && step.stmt !== entryPath.lastOrNull()?.stmt) { - entryPath += step - } - } - return entryPath - } - - private fun constructConstraintForType(value: ReferenceValue, possibleTypes: Collection): UtBoolExpression { - val preferredTypes = typeResolver.findTopRatedTypes(possibleTypes, take = NUMBER_OF_PREFERRED_TYPES) - val mostCommonType = preferredTypes.singleOrNull() ?: OBJECT_TYPE - val typeStorage = typeResolver.constructTypeStorage(mostCommonType, preferredTypes) - return typeRegistry.typeConstraint(value.addr, typeStorage).isOrNullConstraint() - } - - /** - * Adds soft default values for the initial values of all the arrays that exist in the program. - * - * Almost all of them can be found in the local memory, but there are three "common" - * arrays that we need to add - * - * - * @see Memory.initialArrays - * @see Memory.softZeroArraysLengths - * @see TypeRegistry.softEmptyTypes - * @see TypeRegistry.softZeroNumDimensions - */ - private fun addSoftDefaults() { - memory.initialArrays.forEach { queuedSymbolicStateUpdates += UtMkTermArrayExpression(it).asHardConstraint() } - queuedSymbolicStateUpdates += memory.softZeroArraysLengths().asHardConstraint() - queuedSymbolicStateUpdates += typeRegistry.softZeroNumDimensions().asHardConstraint() - queuedSymbolicStateUpdates += typeRegistry.softEmptyTypes().asHardConstraint() - } - - /** - * Takes queued [updates] at the end of static initializer processing, extracts information about - * updated static fields and substitutes them with unbounded symbolic variables. - * - * @return updated memory updates. - */ - private fun substituteStaticFieldsWithSymbolicVariables( - declaringClass: SootClass, - updates: MemoryUpdate - ): MemoryUpdate { - val declaringClassId = declaringClass.id - - val staticFieldsUpdates = updates.staticFieldsUpdates.toMutableList() - val updatedFields = staticFieldsUpdates.mapTo(mutableSetOf()) { it.fieldId } - val objectUpdates = mutableListOf() - - // we assign unbounded symbolic variables for every non-final field of the class - typeResolver - .findFields(declaringClass.type) - .filter { !it.isFinal && it.fieldId in updatedFields } - .forEach { - // remove updates from clinit, because we'll replace those values - // with new unbounded symbolic variable - staticFieldsUpdates.removeAll { update -> update.fieldId == it.fieldId } - - val value = createConst(it.type, it.name) - val valueToStore = if (value is ReferenceValue) { - value.addr - } else { - (value as PrimitiveValue).expr - } - - // we always know that this instance exists because we've just returned from its clinit method - // in which we had to create such instance - val staticObject = updates.staticInstanceStorage.getValue(declaringClassId) - staticFieldsUpdates += StaticFieldMemoryUpdateInfo(it.fieldId, value) - - objectUpdates += objectUpdate(staticObject, it, valueToStore).stores - } - - return updates.copy( - stores = updates.stores.addAll(objectUpdates), - staticFieldsUpdates = staticFieldsUpdates.toPersistentList(), - classIdToClearStatics = declaringClassId - ) - } - - private suspend fun FlowCollector.processResult(symbolicResult: SymbolicResult? /* null for void only: strange hack */) { - val resolvedParameters = environment.state.parameters.map { it.value } - - //choose types that have biggest priority - resolvedParameters - .filterIsInstance() - .forEach { queuedSymbolicStateUpdates += constructConstraintForType(it, it.possibleConcreteTypes).asSoftConstraint() } - - val returnValue = (symbolicResult as? SymbolicSuccess)?.value as? ObjectValue - if (returnValue != null) { - queuedSymbolicStateUpdates += constructConstraintForType(returnValue, returnValue.possibleConcreteTypes).asSoftConstraint() - - workaround(REMOVE_ANONYMOUS_CLASSES) { - val sootClass = returnValue.type.sootClass - if (!isInNestedMethod() && (sootClass.isAnonymous || sootClass.isArtificialEntity)) return - } - } - - //fill arrays with default 0/null and other stuff - addSoftDefaults() - - //deal with @NotNull annotation - val isNotNullableResult = environment.method.returnValueHasNotNullAnnotation() - if (symbolicResult is SymbolicSuccess && symbolicResult.value is ReferenceValue && isNotNullableResult) { - queuedSymbolicStateUpdates += mkNot(mkEq(symbolicResult.value.addr, nullObjectAddr)).asHardConstraint() - } - - val newSolver = solver.add( - hard = queuedSymbolicStateUpdates.hardConstraints, - soft = queuedSymbolicStateUpdates.softConstraints - ) - - val updatedMemory = memory.update(queuedSymbolicStateUpdates.memoryUpdates) - - //no need to respect soft constraints in NestedMethod - val holder = newSolver.check(respectSoft = !isInNestedMethod()) - - if (holder !is UtSolverStatusSAT) { - logger.trace { "processResult<${environment.method.signature}> UNSAT" } - return - } - - //execution frame from level 2 or above - if (isInNestedMethod()) { - // static fields substitution - // TODO: JIRA:1610 -- better way of working with statics - val updates = if (environment.method.name == STATIC_INITIALIZER && substituteStaticsWithSymbolicVariable) { - substituteStaticFieldsWithSymbolicVariables( - environment.method.declaringClass, - updatedMemory.queuedStaticMemoryUpdates() - ) - } else { - MemoryUpdate() // all memory updates are already added in [environment.state] - } - val symbolicResultOrVoid = symbolicResult ?: SymbolicSuccess(typeResolver.nullObject(VoidType.v())) - val methodResult = MethodResult( - symbolicResultOrVoid, - queuedSymbolicStateUpdates + updates - ) - val stateToOffer = environment.state.pop(methodResult) - pathSelector.offer(stateToOffer) - - logger.trace { "processResult<${environment.method.signature}> return from nested method" } - return - } - - //toplevel method - val predictedTestName = Predictors.testName.predict(environment.state.path) - Predictors.testName.provide(environment.state.path, predictedTestName, "") - - val resolver = - Resolver(hierarchy, updatedMemory, typeRegistry, typeResolver, holder, methodUnderTest, softMaxArraySize) - - val (modelsBefore, modelsAfter, instrumentation) = resolver.resolveModels(resolvedParameters) - - val symbolicExecutionResult = resolver.resolveResult(symbolicResult) - - val stateBefore = modelsBefore.constructStateForMethod(methodUnderTest) - val stateAfter = modelsAfter.constructStateForMethod(methodUnderTest) - require(stateBefore.parameters.size == stateAfter.parameters.size) - - val symbolicUtExecution = UtExecution( - stateBefore, - stateAfter, - symbolicExecutionResult, - instrumentation, - entryMethodPath(), - environment.state.fullPath() - ) - - globalGraph.traversed(environment.state) - - if (!UtSettings.useConcreteExecution || - // Can't execute concretely because overflows do not cause actual exceptions. - // Still, we need overflows to act as implicit exceptions. - (UtSettings.treatOverflowAsError && symbolicExecutionResult is UtOverflowFailure) - ) { - logger.debug { - "processResult<${methodUnderTest}>: no concrete execution allowed, " + - "emit purely symbolic result $symbolicUtExecution" - } - emit(symbolicUtExecution) - return - } - - //It's possible that symbolic and concrete stateAfter/results are diverged. - //So we trust concrete results more. - try { - logger.debug().bracket("processResult<$methodUnderTest>: concrete execution") { - - //this can throw CancellationException - val concreteExecutionResult = concreteExecutor.executeConcretely( - methodUnderTest, - stateBefore, - instrumentation - ) - - workaround(REMOVE_ANONYMOUS_CLASSES) { - concreteExecutionResult.result.onSuccess { - if (it.classId.isAnonymous) { - logger.debug("Anonymous class found as a concrete result, symbolic one will be returned") - emit(symbolicUtExecution) - return - } - } - } - - val concolicUtExecution = symbolicUtExecution.copy( - stateAfter = concreteExecutionResult.stateAfter, - result = concreteExecutionResult.result, - coverage = concreteExecutionResult.coverage - ) - - emit(concolicUtExecution) - logger.debug { "processResult<${methodUnderTest}>: returned $concolicUtExecution" } - } - } catch (e: ConcreteExecutionFailureException) { - emitFailedConcreteExecutionResult(stateBefore, e) - } - } - - internal fun isInNestedMethod() = environment.state.isInNestedMethod() - - private fun ReturnStmt.symbolicSuccess(): SymbolicSuccess { - val type = environment.method.returnType - val value = when (val instance = op.resolve(type)) { - is PrimitiveValue -> instance.cast(type) - else -> instance - } - return SymbolicSuccess(value) - } - - internal fun asMethodResult(function: UtBotSymbolicEngine.() -> SymbolicValue): MethodResult { - val prevSymbolicStateUpdate = queuedSymbolicStateUpdates.copy() - // TODO: refactor this `try` with `finally` later - queuedSymbolicStateUpdates = SymbolicStateUpdate() - try { - val result = function() - return MethodResult( - SymbolicSuccess(result), - queuedSymbolicStateUpdates - ) - } finally { - queuedSymbolicStateUpdates = prevSymbolicStateUpdate - } - } -} - -private fun ResolvedModels.constructStateForMethod(methodUnderTest: UtMethod<*>): EnvironmentModels { +private fun ResolvedModels.constructStateForMethod(methodUnderTest: ExecutableId): EnvironmentModels { val (thisInstanceBefore, paramsBefore) = when { methodUnderTest.isStatic -> null to parameters methodUnderTest.isConstructor -> null to parameters.drop(1) else -> parameters.first() to parameters.drop(1) } - return EnvironmentModels(thisInstanceBefore, paramsBefore, statics) + return EnvironmentModels(thisInstanceBefore, paramsBefore, statics, methodUnderTest) } -private suspend fun ConcreteExecutor.executeConcretely( - methodUnderTest: UtMethod<*>, +suspend fun ConcreteExecutor>.executeConcretely( + methodUnderTest: ExecutableId, stateBefore: EnvironmentModels, - instrumentation: List + instrumentation: List, + timeoutInMillis: Long, + isRerun: Boolean = false, ): UtConcreteExecutionResult = executeAsync( - methodUnderTest.callable, + methodUnderTest.classId.name, + methodUnderTest.signature, arrayOf(), - parameters = UtConcreteExecutionData(stateBefore, instrumentation) -).convertToAssemble(methodUnderTest) + parameters = UtConcreteExecutionData( + stateBefore, + instrumentation, + timeoutInMillis, + isRerun, + ) +).convertToAssemble(methodUnderTest.classId.packageName) /** * Before pushing our states for concrete execution, we have to be sure that every state is consistent. @@ -3910,3 +855,17 @@ private fun makeWrapperConsistencyCheck( val visitedSelectExpression = memory.isVisited(symbolicValue.addr) visitedConstraints += mkEq(visitedSelectExpression, mkInt(1)) } + +private fun UtConcreteExecutionResult.violatesUtMockAssumption(): Boolean { + // We should compare FQNs instead of `if (... is UtMockAssumptionViolatedException)` + // because the exception from the `concreteExecutionResult` is loaded by user's ClassLoader, + // but the `UtMockAssumptionViolatedException` is loaded by the current ClassLoader, + // so we can't cast them to each other. + return result.exceptionOrNull()?.javaClass?.name == UtMockAssumptionViolatedException::class.java.name +} + +private fun UtConcreteExecutionResult.processedFailure(): UtConcreteExecutionProcessedFailure? + = result as? UtConcreteExecutionProcessedFailure + +private fun checkStaticMethodsMock(execution: UtSymbolicExecution) = + execution.instrumentation.any { it is UtStaticMethodInstrumentation} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtOperators.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtOperators.kt index da1debc6b7..dfd00fb6ef 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtOperators.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtOperators.kt @@ -87,26 +87,26 @@ sealed class UtBoolOperator(delegate: BoolOperator) : UtOperatorconstructed object reference-equality cache. - */ -// TODO: JIRA:1379 -- Refactor ValueConstructor and MockValueConstructor -class ValueConstructor { - private val classLoader: ClassLoader - get() = utContext.classLoader - - // TODO: JIRA:1379 -- replace UtReferenceModel with Int - private val constructedObjects = HashMap() - private val resultsCache = HashMap() - private val mockInfo = mutableListOf() - private var mockTarget: MockTarget? = null - private var mockCounter = 0 - - private fun clearState() { - constructedObjects.clear() - resultsCache.clear() - mockInfo.clear() - mockTarget = null - mockCounter = 0 - } - - /** - * Clears state before and after block execution. State contains caches for reference equality. - */ - private inline fun withCleanState(block: () -> T): T { - try { - clearState() - return block() - } finally { - clearState() - } - } - - /** - * Sets mock context (possible mock target) before block execution and restores previous one after block execution. - */ - private inline fun withMockTarget(target: MockTarget?, block: () -> T): T { - val old = mockTarget - try { - mockTarget = target - return block() - } finally { - mockTarget = old - } - } - - /** - * Constructs values from models. - */ - fun construct(models: List): List> = - withCleanState { models.map { construct(it, null) } } - - private fun constructState(state: EnvironmentModels): Pair> { - val (thisInstance, params, statics) = state - val allParams = listOfNotNull(thisInstance) + params - - val (valuesBefore, mocks, staticValues) = constructParamsAndMocks(allParams, statics) - - val (caller, paramsValues) = if (thisInstance != null) { - valuesBefore.first() to valuesBefore.drop(1) - } else { - null to valuesBefore - } - - return UtValueExecutionState(caller, paramsValues, staticValues) to mocks - } - - /** - * Constructs value based execution from model based. - */ - fun construct(execution: UtExecution): UtValueExecution<*> { - val (stateBefore, mocks) = constructState(execution.stateBefore) - val (stateAfter, _) = constructState(execution.stateAfter) - val returnValue = execution.result.map { construct(listOf(it)).single().value } - - return UtValueExecution( - stateBefore, - stateAfter, - returnValue, - execution.path, - mocks, - execution.instrumentation, - execution.summary, - execution.testMethodName, - execution.displayName - ) - } - - private fun constructParamsAndMocks( - models: List, - statics: Map - ): ConstructedValues = - withCleanState { - val values = models.mapIndexed { index, model -> - val target = mockTarget(model) { ParameterMockTarget(model.classId.name, index) } - construct(model, target) - } - - val staticValues = mutableMapOf>() - - statics.forEach { (field, model) -> - val target = FieldMockTarget(model.classId.name, field.declaringClass.name, owner = null, field.name) - staticValues += field to construct(model, target) - } - - ConstructedValues(values, mockInfo.toList(), staticValues) - } - - /** - * Main construction method. - * - * Takes care of nulls. Does not use cache, instead construct(Object/Array/List/FromAssembleModel) method - * uses cache directly. - * - * Takes mock creation context (possible mock target) to create mock if required. - */ - private fun construct(model: UtModel, target: MockTarget?): UtConcreteValue<*> = withMockTarget(target) { - when (model) { - is UtNullModel -> UtConcreteValue(null, model.classId.jClass) - is UtPrimitiveModel -> UtConcreteValue(model.value, model.classId.jClass) - is UtEnumConstantModel -> UtConcreteValue(model.value) - is UtClassRefModel -> UtConcreteValue(model.value) - is UtCompositeModel -> UtConcreteValue(constructObject(model), model.classId.jClass) - is UtArrayModel -> UtConcreteValue(constructArray(model)) - is UtAssembleModel -> UtConcreteValue(constructFromAssembleModel(model)) - is UtVoidModel -> UtConcreteValue(Unit) - } - } - - /** - * Constructs object by model, uses reference-equality cache. - * - * Returns null for mock cause cannot instantiate it. - */ - private fun constructObject(model: UtCompositeModel): Any? { - val constructed = constructedObjects[model] - if (constructed != null) { - return constructed - } - - this.mockTarget?.let { mockTarget -> - model.mocks.forEach { (methodId, models) -> - mockInfo += MockInfo(mockTarget, methodId, models.map { model -> - if (model.isMockModel()) { - val mockId = MockId("mock${++mockCounter}") - // Call to "construct" method still required to collect mock interaction - construct(model, ObjectMockTarget(model.classId.name, mockId)) - UtMockValue(mockId, model.classId.name) - } else { - construct(model, null) - } - }) - } - } - - if (model.isMock) { - return null - } - - val javaClass = javaClass(model.classId) - val classInstance = javaClass.anyInstance - constructedObjects[model] = classInstance - - model.fields.forEach { (field, fieldModel) -> - val declaredField = - javaClass.findFieldOrNull(field.name) ?: error("Can't find field: $field for $javaClass") - val accessible = declaredField.isAccessible - - try { - declaredField.isAccessible = true - - val modifiersField = Field::class.java.getDeclaredField("modifiers") - modifiersField.isAccessible = true - - val target = mockTarget(fieldModel) { - FieldMockTarget( - fieldModel.classId.name, - model.classId.name, - UtConcreteValue(classInstance), - field.name - ) - } - val value = construct(fieldModel, target).value - val instance = if (Modifier.isStatic(declaredField.modifiers)) null else classInstance - declaredField.set(instance, value) - } finally { - declaredField.isAccessible = accessible - } - } - - return classInstance - } - - /** - * Constructs array by model. - * - * Supports arrays of primitive, arrays of arrays and arrays of objects. - * - * Note: does not check isNull, but if isNull set returns empty array because for null array length set to 0. - */ - private fun constructArray(model: UtArrayModel): Any { - val constructed = constructedObjects[model] - if (constructed != null) { - return constructed - } - - with(model) { - val elementClassId = classId.elementClassId!! - return when (elementClassId.jvmName) { - "B" -> ByteArray(length) { primitive(constModel) }.apply { - constructedObjects[model] = this - stores.forEach { (index, model) -> this[index] = primitive(model) } - } - "S" -> ShortArray(length) { primitive(constModel) }.apply { - constructedObjects[model] = this - stores.forEach { (index, model) -> this[index] = primitive(model) } - } - "C" -> CharArray(length) { primitive(constModel) }.apply { - constructedObjects[model] = this - stores.forEach { (index, model) -> this[index] = primitive(model) } - } - "I" -> IntArray(length) { primitive(constModel) }.apply { - constructedObjects[model] = this - stores.forEach { (index, model) -> this[index] = primitive(model) } - } - "J" -> LongArray(length) { primitive(constModel) }.apply { - constructedObjects[model] = this - stores.forEach { (index, model) -> this[index] = primitive(model) } - } - "F" -> FloatArray(length) { primitive(constModel) }.apply { - constructedObjects[model] = this - stores.forEach { (index, model) -> this[index] = primitive(model) } - } - "D" -> DoubleArray(length) { primitive(constModel) }.apply { - constructedObjects[model] = this - stores.forEach { (index, model) -> this[index] = primitive(model) } - } - "Z" -> BooleanArray(length) { primitive(constModel) }.apply { - constructedObjects[model] = this - stores.forEach { (index, model) -> this[index] = primitive(model) } - } - else -> { - val javaClass = javaClass(elementClassId) - val instance = java.lang.reflect.Array.newInstance(javaClass, length) as Array<*> - constructedObjects[model] = instance - for (i in instance.indices) { - val elementModel = stores[i] ?: constModel - val value = construct(elementModel, null).value - java.lang.reflect.Array.set(instance, i, value) - } - instance - } - } - } - } - - /** - * Constructs object with [UtAssembleModel]. - */ - private fun constructFromAssembleModel(assembleModel: UtAssembleModel): Any { - constructedObjects[assembleModel]?.let { return it } - - assembleModel.allStatementsChain.forEach { statementModel -> - when (statementModel) { - is UtExecutableCallModel -> updateWithExecutableCallModel(statementModel, assembleModel) - is UtDirectSetFieldModel -> updateWithDirectSetFieldModel(statementModel) - } - } - - return resultsCache[assembleModel] - ?: error("Can't assemble model: $assembleModel") - } - - /** - * Updates instance state with [UtExecutableCallModel] invocation. - */ - private fun updateWithExecutableCallModel( - callModel: UtExecutableCallModel, - assembleModel: UtAssembleModel, - ) { - val executable = callModel.executable - val instanceValue = resultsCache[callModel.instance] - val params = callModel.params.map { value(it) } - - val result = when (executable) { - is MethodId -> executable.call(params, instanceValue) - is ConstructorId -> executable.call(params) - } - - // Ignore result if returnId is null. Otherwise add it to instance cache. - callModel.returnValue?.let { - checkNotNull(result) { "Tracked instance can't be null for call $executable in model $assembleModel" } - resultsCache[it] = result - - //If statement is final instantiating, add result to constructed objects cache - if (callModel == assembleModel.finalInstantiationModel) { - constructedObjects[assembleModel] = result - } - } - } - - /** - * Updates instance with [UtDirectSetFieldModel] execution. - */ - private fun updateWithDirectSetFieldModel(directSetterModel: UtDirectSetFieldModel) { - val instanceModel = directSetterModel.instance - val instance = resultsCache[instanceModel] ?: error("Model $instanceModel is not instantiated") - - val instanceClassId = instanceModel.classId - val fieldModel = directSetterModel.fieldModel - - val field = instance::class.java.findField(directSetterModel.fieldId.name) - val isAccessible = field.isAccessible - - try { - //set field accessible to support protected or package-private direct setters - field.isAccessible = true - - //prepare mockTarget for field if it is a mock - val mockTarget = mockTarget(fieldModel) { - FieldMockTarget( - fieldModel.classId.name, - instanceClassId.name, - UtConcreteValue(javaClass(instanceClassId).anyInstance), - field.name - ) - } - - //construct and set the value - val fieldValue = construct(fieldModel, mockTarget).value - field.set(instance, fieldValue) - } finally { - //restore accessibility property of the field - field.isAccessible = isAccessible - } - } - - /** - * Constructs value from [UtModel]. - */ - private fun value(model: UtModel) = construct(model, null).value - - private fun MethodId.call(args: List, instance: Any?): Any? = - method.run { - withAccessibility { - invokeCatching(obj = instance, args = args).getOrThrow() - } - } - - private fun ConstructorId.call(args: List): Any? = - constructor.run { - withAccessibility { - newInstance(*args.toTypedArray()) - } - } - - /** - * Fetches primitive value from NutsModel to create array of primitives. - */ - private inline fun primitive(model: UtModel): T = (model as UtPrimitiveModel).value as T - - private fun javaClass(id: ClassId) = kClass(id).java - - private fun kClass(id: ClassId) = - if (id.elementClassId != null) { - arrayClassOf(id.elementClassId!!) - } else { - when (id.jvmName) { - "B" -> Byte::class - "S" -> Short::class - "C" -> Char::class - "I" -> Int::class - "J" -> Long::class - "F" -> Float::class - "D" -> Double::class - "Z" -> Boolean::class - else -> classLoader.loadClass(id.name).kotlin - } - } - - private fun arrayClassOf(elementClassId: ClassId): KClass<*> = - if (elementClassId.elementClassId != null) { - val elementClass = arrayClassOf(elementClassId.elementClassId!!) - java.lang.reflect.Array.newInstance(elementClass.java, 0)::class - } else { - when (elementClassId.jvmName) { - "B" -> ByteArray::class - "S" -> ShortArray::class - "C" -> CharArray::class - "I" -> IntArray::class - "J" -> LongArray::class - "F" -> FloatArray::class - "D" -> DoubleArray::class - "Z" -> BooleanArray::class - else -> { - val elementClass = classLoader.loadClass(elementClassId.name) - java.lang.reflect.Array.newInstance(elementClass, 0)::class - } - } - } -} - -private fun UtExecutionResult.map(transform: (model: UtModel) -> R): Result = when (this) { - is UtExecutionSuccess -> Result.success(transform(model)) - is UtExecutionFailure -> Result.failure(exception) -} - -/** - * Creates mock target using init lambda if model represents mock or null otherwise. - */ -private fun mockTarget(model: UtModel, init: () -> MockTarget): MockTarget? = - if (model.isMockModel()) init() else null - -data class ConstructedValues( - val values: List>, - val mocks: List, - val statics: Map> -) \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/ExtendedArrayExpressions.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/ExtendedArrayExpressions.kt index e905322647..637dc6fccd 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/ExtendedArrayExpressions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/ExtendedArrayExpressions.kt @@ -12,7 +12,6 @@ data class UtArrayInsert( ) : UtExtendedArrayExpression(arrayExpression.sort as UtArraySort) { override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - override val hashCode = Objects.hash(arrayExpression, index, element) override fun hashCode(): Int = hashCode @@ -162,29 +161,6 @@ data class UtArrayRemoveRange( } } -data class UtStringToArray( - val stringExpression: UtExpression, - val offset: PrimitiveValue -) : UtExtendedArrayExpression(UtArraySort(UtIntSort, UtCharSort)) { - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override val hashCode = Objects.hash(stringExpression, offset) - - override fun hashCode(): Int = hashCode - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtStringToArray - - if (stringExpression != other.stringExpression) return false - if (offset != other.offset) return false - - return true - } -} - data class UtArrayApplyForAll( val arrayExpression: UtExpression, val constraint: (UtExpression, PrimitiveValue) -> UtBoolExpression diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Query.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Query.kt index 51e41560ba..0b7b579360 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Query.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Query.kt @@ -31,6 +31,7 @@ import kotlinx.collections.immutable.toPersistentSet sealed class BaseQuery( open val hard: PersistentSet, open val soft: PersistentSet, + open val assumptions: PersistentSet, open val status: UtSolverStatus, open val lastAdded: Collection ) { @@ -40,7 +41,11 @@ sealed class BaseQuery( * @param hard - new constraints that must be satisfied. * @param soft - new constraints that are suggested to be satisfied if possible. */ - abstract fun with(hard: Collection, soft: Collection): BaseQuery + abstract fun with( + hard: Collection, + soft: Collection, + assumptions: Collection + ): BaseQuery /** * Set [status] of the query. @@ -55,11 +60,19 @@ sealed class BaseQuery( * UnsatQuery.[status] is [UtSolverStatusUNSAT]. * UnsatQuery.[lastAdded] = [emptyList] */ -class UnsatQuery(hard: PersistentSet) : - BaseQuery(hard, persistentHashSetOf(), UtSolverStatusUNSAT(UtSolverStatusKind.UNSAT), emptyList()) { +class UnsatQuery(hard: PersistentSet) : BaseQuery( + hard, + soft = persistentHashSetOf(), + assumptions = persistentHashSetOf(), + UtSolverStatusUNSAT(UtSolverStatusKind.UNSAT), + lastAdded = emptyList() +) { - override fun with(hard: Collection, soft: Collection): BaseQuery = - error("State with UnsatQuery isn't eliminated. Adding constraints to $this isn't allowed.") + override fun with( + hard: Collection, + soft: Collection, + assumptions: Collection + ): BaseQuery = error("State with UnsatQuery isn't eliminated. Adding constraints to $this isn't allowed.") override fun withStatus(newStatus: UtSolverStatus) = this @@ -69,7 +82,7 @@ class UnsatQuery(hard: PersistentSet) : /** * Query of UtExpressions with applying simplifications if [useExpressionSimplification] is true. * - * @see RewritingVisitor + * @see Simplificator * * @param eqs - map that matches symbolic expressions with concrete values. * @param lts - map of upper bounds of integral symbolic expressions. @@ -78,17 +91,18 @@ class UnsatQuery(hard: PersistentSet) : data class Query( override val hard: PersistentSet = persistentHashSetOf(), override val soft: PersistentSet = persistentHashSetOf(), + override val assumptions: PersistentSet = persistentHashSetOf(), override val status: UtSolverStatus = UtSolverStatusUNDEFINED, override val lastAdded: Collection = emptyList(), private val eqs: PersistentMap = persistentHashMapOf(), private val lts: PersistentMap = persistentHashMapOf(), private val gts: PersistentMap = persistentHashMapOf() -) : BaseQuery(hard, soft, status, lastAdded) { +) : BaseQuery(hard, soft, assumptions, status, lastAdded) { - val rewriter: RewritingVisitor - get() = RewritingVisitor(eqs, lts, gts) + val simplificator: Simplificator + get() = Simplificator(eqs, lts, gts) - private fun UtBoolExpression.simplify(visitor: RewritingVisitor): UtBoolExpression = + private fun UtBoolExpression.simplify(visitor: Simplificator): UtBoolExpression = this.accept(visitor) as UtBoolExpression private fun simplifyGeneric(expr: UtBoolExpression): UtBoolExpression = @@ -118,7 +132,7 @@ data class Query( eqs: Map = this@Query.eqs, lts: Map = this@Query.lts, gts: Map = this@Query.gts - ) = AxiomInstantiationRewritingVisitor(eqs, lts, gts).let { visitor -> + ) = AxiomInstantiationSimplificator(eqs, lts, gts).let { visitor -> this.map { it.simplify(visitor) } .map { simplifyGeneric(it) } .flatMap { splitAnd(it) } + visitor.instantiatedArrayAxioms @@ -151,19 +165,6 @@ data class Query( } } - /** - * Mark that part of UtStringEq is equal to concrete part to substitute it in constraints added later. - * - * Eq expressions with both concrete parts are simplified in RewritingVisitor.visit(UtStringEq) - */ - private fun MutableMap.putEq(eqExpr: UtStringEq) { - when { - eqExpr.left.isConcrete -> this[eqExpr.right] = eqExpr.left - eqExpr.right.isConcrete -> this[eqExpr.left] = eqExpr.right - else -> this[eqExpr] = UtTrue - } - } - /** * @return * [this] if constraints are satisfied under this model. @@ -179,6 +180,7 @@ data class Query( private fun addSimplified( hard: Collection, soft: Collection, + assumptions: Collection ): BaseQuery { val addedHard = hard.simplify().filterNot { it is UtTrue } if (addedHard.isEmpty() && soft.isEmpty()) { @@ -193,7 +195,6 @@ data class Query( for (expr in addedHard) { when (expr) { is UtEqExpression -> addedEqs.putEq(expr) - is UtStringEq -> addedEqs.putEq(expr) is UtBoolOpExpression -> when (expr.operator) { Eq -> addedEqs.putEq(expr) Lt -> if (expr.right.expr.isConcrete && expr.right.expr.isInteger()) { @@ -266,8 +267,25 @@ data class Query( .filterNot { it is UtBoolLiteral } .toPersistentSet() + // Apply simplifications to assumptions in query. + // We do not filter out UtFalse here because we need them to get UNSAT in corresponding cases and run concrete instead. + val newAssumptions = this.assumptions + .addAll(assumptions) + .simplify(newEqs, newLts, newGts) + .toPersistentSet() + val diffHard = newHard - this.hard - return Query(newHard, newSoft, status.checkFastSatAndReturnStatus(diffHard), diffHard, newEqs, newLts, newGts) + + return Query( + newHard, + newSoft, + newAssumptions, + status.checkFastSatAndReturnStatus(diffHard), + lastAdded = diffHard, + newEqs, + newLts, + newGts + ) } /** @@ -275,11 +293,22 @@ data class Query( * @param hard - set of constraints that must be satisfied. * @param soft - set of constraints that should be satisfied if possible. */ - override fun with(hard: Collection, soft: Collection): BaseQuery { + override fun with( + hard: Collection, + soft: Collection, + assumptions: Collection + ): BaseQuery { return if (useExpressionSimplification) { - addSimplified(hard, soft) + addSimplified(hard, soft, assumptions) } else { - Query(this.hard.addAll(hard), this.soft.addAll(soft), status.checkFastSatAndReturnStatus(hard), hard, this.eqs) + Query( + this.hard.addAll(hard), + this.soft.addAll(soft), + this.assumptions.addAll(assumptions), + status.checkFastSatAndReturnStatus(hard), + lastAdded = hard, + this.eqs + ) } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/RewritingVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/RewritingVisitor.kt deleted file mode 100644 index 9453aa393e..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/RewritingVisitor.kt +++ /dev/null @@ -1,1414 +0,0 @@ -package org.utbot.engine.pc - -import org.utbot.common.unreachableBranch -import org.utbot.engine.Add -import org.utbot.engine.And -import org.utbot.engine.Cmp -import org.utbot.engine.Cmpg -import org.utbot.engine.Cmpl -import org.utbot.engine.Div -import org.utbot.engine.Eq -import org.utbot.engine.Ge -import org.utbot.engine.Gt -import org.utbot.engine.Le -import org.utbot.engine.Lt -import org.utbot.engine.Mul -import org.utbot.engine.Ne -import org.utbot.engine.Or -import org.utbot.engine.PrimitiveValue -import org.utbot.engine.Rem -import org.utbot.engine.Shl -import org.utbot.engine.Shr -import org.utbot.engine.Sub -import org.utbot.engine.Ushr -import org.utbot.engine.Xor -import org.utbot.engine.maxSort -import org.utbot.engine.primitiveToLiteral -import org.utbot.engine.primitiveToSymbolic -import org.utbot.engine.symbolic.asHardConstraint -import org.utbot.engine.toIntValue -import org.utbot.engine.toPrimitiveValue -import org.utbot.framework.UtSettings.useExpressionSimplification -import java.util.IdentityHashMap -import java.util.concurrent.atomic.AtomicInteger -import kotlinx.collections.immutable.toPersistentList -import soot.DoubleType -import soot.FloatType -import soot.IntType -import soot.IntegerType -import soot.LongType -import soot.Type - - -/** - * UtExpressionVisitor that performs simple rewritings on expressions with concrete values. - * - */ -open class RewritingVisitor( - private val eqs: Map = emptyMap(), - private val lts: Map = emptyMap(), - private val gts: Map = emptyMap() -) : UtExpressionVisitor { - val axiomInstantiationVisitor: RewritingVisitor - get() = AxiomInstantiationRewritingVisitor(eqs, lts, gts) - protected val selectIndexStack = mutableListOf() - private val simplificationCache = IdentityHashMap() - - private fun allConcrete(vararg exprs: UtExpression) = exprs.all { it.isConcrete } - - protected inline fun withNewSelect(index: UtExpression, block: () -> R): R { - selectIndexStack += index.toIntValue() - return block().also { - selectIndexStack.removeLast() - } - } - - @Suppress("MemberVisibilityCanBePrivate") - protected inline fun withEmptySelects(block: () -> R): R { - val currentSelectStack = selectIndexStack.toList() - selectIndexStack.clear() - return block().also { - selectIndexStack.addAll(currentSelectStack) - } - } - - @Suppress("MemberVisibilityCanBePrivate") - protected inline fun withRemoveSelect(block: () -> R): R { - val lastIndex = selectIndexStack.removeLastOrNull() - return block().also { - if (lastIndex != null) { - selectIndexStack += lastIndex - } - } - } - - /** - * if [useExpressionSimplification] is true then rewrite [expr] and - * substitute concrete values from [eqs] map for equal symbolic expressions. - * - * @param expr - simplified expression - * @param block - simplification function. Identity function by default - * @param substituteResult - if true then try to substitute result of block(expr) - * with equal concrete value from [eqs] map. - */ - private inline fun applySimplification( - expr: UtExpression, - substituteResult: Boolean = true, - crossinline block: (() -> UtExpression) = { expr }, - ): UtExpression { - val value = { - if (!useExpressionSimplification) { - expr - } else { - eqs[expr] ?: block().let { if (substituteResult) eqs[it] ?: it else it } - } - } - return if (expr.sort is UtArraySort && selectIndexStack.isNotEmpty()) { - checkSortsEquality(value, expr) - } else { - simplificationCache.getOrPut(expr) { - checkSortsEquality(value, expr) - } - } - } - - private fun checkSortsEquality(value: () -> UtExpression, sourceExpr: UtExpression): UtExpression { - val result = value() - - val sourceSort = sourceExpr.sort - val resultSort = result.sort - - // We should return simplified expression with the same sort as the original one. - // The only exception is a case with two primitive sorts and cast expression below. - // So, expression with, e.g., UtArraySort must be returned here. - if (resultSort == sourceSort) return result - - // There we must have the following situation: original expression is `x = Add(a, b)`, where - // x :: Int32, a = 0 :: Int32, b :: Short16. Simplifier removes the left part of the addition since it is - // equal to zero, now we have just b with a different from the original expression sort. - // We must cast it to the required sort, so the result will be `Cast(b, Int32)`. - require(sourceSort is UtPrimitiveSort && resultSort is UtPrimitiveSort) { - val messageParts = listOf( - "Expr to simplify had sort ${sourceSort}:", sourceExpr, "", - "After simplification expression got sort ${resultSort}:", result - ) - messageParts.joinToString(System.lineSeparator(), prefix = System.lineSeparator()) - } - - val expectedType = sourceSort.type - val resultType = resultSort.type - - return UtCastExpression(result.toPrimitiveValue(resultType), expectedType) - } - - override fun visit(expr: UtArraySelectExpression): UtExpression = - applySimplification(expr) { - val index = withEmptySelects { expr.index.accept(this) } - when (val array = withNewSelect(index) { expr.arrayExpression.accept(this) }) { - // select(constArray(a), i) --> a - is UtConstArrayExpression -> array.constValue - is UtArrayMultiStoreExpression -> { - // select(store(..(store(array, Concrete a, b), Concrete x, _)..), Concrete a) -> b - val newStores = array.stores.filter { UtEqExpression(index, it.index).accept(this) != UtFalse } - - when { - newStores.isEmpty() -> array.initial.select(index).accept(this) - UtEqExpression(index, newStores.last().index).accept(this) == UtTrue -> newStores.last().value - else -> UtArrayMultiStoreExpression(array.initial, newStores.toPersistentList()).select(index) - } - } - else -> array.select(index) - } - } - - override fun visit(expr: UtMkArrayExpression): UtExpression = expr - - // store(store(store(array, 1, b), 2, a), 1, a) ---> store(store(array, 2, a), 1, a) - override fun visit(expr: UtArrayMultiStoreExpression): UtExpression = - applySimplification(expr) { - val initial = expr.initial.accept(this) - val stores = withRemoveSelect { - expr.stores.map { - val index = it.index.accept(this) - val value = it.value.accept(this) - - require(value.sort == expr.sort.itemSort) { - "Unequal sorts occurred during rewriting UtArrayMultiStoreExpression:\n" + - "value with index $index has sort ${value.sort} while " + - "${expr.sort.itemSort} was expected." - } - - UtStore(index, value) - } - }.filterNot { it.value == initial.select(it.index) } - when { - stores.isEmpty() -> initial - initial is UtArrayMultiStoreExpression -> UtArrayMultiStoreExpression( - initial.initial, - (initial.stores + stores).asReversed().distinctBy { it.index }.asReversed().toPersistentList() - ) - else -> UtArrayMultiStoreExpression( - initial, - stores.asReversed().distinctBy { it.index }.asReversed().toPersistentList() - ) - } - } - - override fun visit(expr: UtBvLiteral): UtExpression = expr - - override fun visit(expr: UtBvConst): UtExpression = applySimplification(expr, true) - - override fun visit(expr: UtAddrExpression): UtExpression = - applySimplification(expr) { - UtAddrExpression( - expr.internal.accept(this) - ) - } - - override fun visit(expr: UtFpLiteral): UtExpression = expr - - override fun visit(expr: UtFpConst): UtExpression = applySimplification(expr) - - private fun evalIntegralLiterals(literal: UtExpression, sort: UtSort, block: (Long) -> Number): UtExpression = - block(literal.toLong()).toValueWithSort(sort).primitiveToLiteral() - - private fun evalIntegralLiterals( - left: UtExpression, - right: UtExpression, - sort: UtSort, - block: (Long, Long) -> Number - ): UtExpression = - block(left.toLong(), right.toLong()).toValueWithSort(sort).primitiveToLiteral() - - private fun evalIntegralLiterals( - left: UtExpression, - right: UtExpression, - block: (Long, Long) -> Boolean - ): UtExpression = - block(left.toLong(), right.toLong()).primitiveToLiteral() - - private fun Number.toValueWithSort(sort: UtSort): Any = - when (sort) { - UtBoolSort -> this != UtLongFalse - UtByteSort -> this.toByte() - UtShortSort -> this.toShort() - UtCharSort -> this.toChar() - UtIntSort -> this.toInt() - UtLongSort -> this.toLong() - UtFp32Sort -> this.toFloat() - UtFp64Sort -> this.toDouble() - else -> error("Can convert Number values only to primitive sorts. Sort $sort isn't primitive") - } - - private fun UtBoolLiteral.toLong(): Long = if (value) UtLongTrue else UtLongFalse - - private fun UtBvLiteral.toLong(): Long = value.toLong() - - private fun UtExpression.toLong(): Long = when (this) { - is UtBvLiteral -> toLong() - is UtBoolLiteral -> toLong() - is UtAddrExpression -> internal.toLong() - else -> error("$this isn't IntegralLiteral") - } - - private val UtExpression.isIntegralLiteral: Boolean - get() = when (this) { - is UtBvLiteral -> true - is UtBoolLiteral -> true - is UtAddrExpression -> internal.isIntegralLiteral - else -> false - } - - override fun visit(expr: UtOpExpression): UtExpression = - applySimplification(expr) { - val left = expr.left.expr.accept(this) - val right = expr.right.expr.accept(this) - val leftPrimitive = left.toPrimitiveValue(expr.left.type) - val rightPrimitive = right.toPrimitiveValue(expr.right.type) - - when (expr.operator) { - // Sort of pattern matching... - Add -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-addConcrete - // Add(Integral a, Integral b) ---> Integral (a + b) - left.isIntegralLiteral && right.isIntegralLiteral -> - evalIntegralLiterals(left, right, expr.resultSort, Long::plus) - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-addA0 - // Add(a, Integral 0) ---> a - right.isIntegralLiteral && right.toLong() == 0L -> left - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-addLiteralToRight - // Add(Concrete a, b) ---> Add(b, Concrete a).accept(this) - left.isIntegralLiteral -> Add( - rightPrimitive, - leftPrimitive - ).accept(this) - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-addAddOpenLiteral - // Add(Add(x, Integral a), Integral b)) -> Add(x, Integral (a + b)) - right.isIntegralLiteral && left is UtOpExpression && left.operator is Add && left.right.expr.isIntegralLiteral -> - Add( - left.left, - evalIntegralLiterals(left.right.expr, right, expr.right.expr.sort, Long::plus) - .toPrimitiveValue(expr.right.type) - ) - else -> Add(leftPrimitive, rightPrimitive) - } - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-subToAdd - // Sub(a, b) ---> Add(a, Neg b) - Sub -> Add( - leftPrimitive, - UtNegExpression(rightPrimitive).toPrimitiveValue(expr.right.type) - ).accept(this) - Mul -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulConcrete - // Mul(Integral a, Integral b) ---> Integral (a * b) - left.isIntegralLiteral && right.isIntegralLiteral -> - evalIntegralLiterals(left, right, expr.resultSort, Long::times) - right.isIntegralLiteral -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulA0 - // Mul(a, b@(Integral 0)) ---> b - right.toLong() == 0L -> right - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulA1 - // Mul(a, Integral 1) ---> a - right.toLong() == 1L -> left - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulAMinus1 - // Mul(a, Integral -1) ---> Neg a - right.toLong() == -1L -> UtNegExpression(leftPrimitive) - - // Mul(Op(_, Integral _), Integral _) - left is UtOpExpression && left.right.expr.isIntegralLiteral -> when (left.operator) { - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulDistr - // Mul(Add(x, Integral a), Integral b) ---> Add(Mul(x, b), Integral (a * b)) - is Add -> Add( - Mul( - left.left, - rightPrimitive - ).toPrimitiveValue(expr.left.type), - evalIntegralLiterals(left.right.expr, right, expr.resultSort, Long::times) - .toPrimitiveValue(expr.right.type) - ) - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulMulOpenLiteral - // Mul(Mul(x, Integral a), Integral b) ---> Mul(x, Integral (a * b)) - is Mul -> Mul( - left.left, - evalIntegralLiterals(left.right.expr, right, expr.right.expr.sort, Long::times) - .toPrimitiveValue(expr.right.type) - ) - else -> Mul(leftPrimitive, rightPrimitive) - } - else -> Mul(leftPrimitive, rightPrimitive) - } - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulLiteralToRight - // Mul(a@(Integral _), b) ---> Mul(b, a) - left.isIntegralLiteral -> Mul( - rightPrimitive, - leftPrimitive - ).accept(this) - else -> Mul(leftPrimitive, rightPrimitive) - } - Div -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-divConcrete - // Div(Integral a, Integral b) ---> Integral (a / b) - left.isIntegralLiteral && right.isIntegralLiteral && right.toLong() != 0L -> - evalIntegralLiterals(left, right, expr.resultSort, Long::div) - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-divA1 - // Div(a, Concrete 1) ---> a - right.isIntegralLiteral && right.toLong() == 1L -> left - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-divAMinus1 - // Div(a, Concrete -1) ---> Neg a - right.isIntegralLiteral && right.toLong() == -1L -> UtNegExpression(leftPrimitive) - else -> Div(leftPrimitive, rightPrimitive) - } - And -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andConcrete - // And(Integral a, Integral b) ---> Integral (a `and` b) - left.isIntegralLiteral && right.isIntegralLiteral -> - evalIntegralLiterals(left, right, expr.resultSort, Long::and) - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andA0 - // And(a@(Integral 0), b) ---> a - left.isIntegralLiteral && left.toLong() == 0L -> - evalIntegralLiterals(left, expr.resultSort) { it } - right.isIntegralLiteral && right.toLong() == 0L -> - evalIntegralLiterals(right, expr.resultSort) { it } - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andAtoRight - left.isIntegralLiteral -> And( - rightPrimitive, - leftPrimitive - ) - else -> And(leftPrimitive, rightPrimitive) - } - Cmp, Cmpg, Cmpl -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-cmpConcrete - // Cmp(Integral a, Integral b) ---> Integral (cmp a b) - left.isIntegralLiteral && right.isIntegralLiteral -> - evalIntegralLiterals(left, right, expr.resultSort) { a, b -> - when { - a < b -> -1 - a == b -> 0 - else -> 1 - } - } - else -> expr.operator(leftPrimitive, rightPrimitive) - } - Or -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-orConcrete - // Or(Integral a, Integral b) ---> Integral (a `or` b) - left.isIntegralLiteral && right.isIntegralLiteral -> - evalIntegralLiterals(left, right, expr.resultSort, Long::or) - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-orA0 - // Or(a, b@(Integral 0)) ---> a - left.isIntegralLiteral && left.toLong() == 0L -> right - right.isIntegralLiteral && right.toLong() == 0L -> left - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-orLiteralToRight - // Or(a@(Integral _),b) ---> Or(b, a) - left.isIntegralLiteral -> Or( - rightPrimitive, - leftPrimitive - ) - else -> Or(leftPrimitive, rightPrimitive) - } - Shl -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-shlConcrete - // Shl(Integral a, Integral b) ---> Integral (a `shl` b) - left.isIntegralLiteral && right.isIntegralLiteral -> - evalIntegralLiterals(left, right, expr.resultSort) { a, b -> - val size: Int = (expr.sort as UtBvSort).size - a shl (b.toInt() % size) - } - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-shlCycle - // Shl(a, Integral b) && b % a.bit_size == 0 ---> a - right.isIntegralLiteral && right.toLong() % (expr.resultSort as UtBvSort).size == 0L -> left - else -> Shl(leftPrimitive, rightPrimitive) - } - Shr -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-shrConcrete - // Shr(Integral a, Integral b) ---> Integral (a `shr` b) - left.isIntegralLiteral && right.isIntegralLiteral -> - evalIntegralLiterals(left, right, expr.resultSort) { a, b -> - val size: Int = (expr.sort as UtBvSort).size - a shr (b.toInt() % size) - } - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-shrCycle - // Shr(a, Integral b) && b % a.bit_size == 0 ---> a - right.isIntegralLiteral && right.toLong() % (expr.resultSort as UtBvSort).size == 0L -> left - else -> Shr(leftPrimitive, rightPrimitive) - } - Ushr -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-ushrConcrete - // Ushr(Integral a, Integral b) ---> Integral (a `ushr` b) - left.isIntegralLiteral && right.isIntegralLiteral -> - evalIntegralLiterals(left, right, expr.resultSort) { a, b -> - val size: Int = (expr.sort as UtBvSort).size - if (a >= 0) { - a ushr (b.toInt() % size) - } else { - // 0b0..01..1 where mask.countOneBits() = size - val mask: Long = ((1L shl size) - 1L) - (a and mask) ushr (b.toInt() % size) - } - } - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-ushrCycle - // Ushr(a, Integral b) && b % a.bit_size == 0 ---> a - right.isIntegralLiteral && right.toLong() % (expr.resultSort as UtBvSort).size == 0L -> left - else -> Ushr(leftPrimitive, rightPrimitive) - } - Xor -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-xorConcrete - // Xor(Integral a, Integral b) ---> Integral (a `xor` b) - left.isIntegralLiteral && right.isIntegralLiteral -> - evalIntegralLiterals(left, right, expr.resultSort, Long::xor) - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-xorA0 - // Xor(a, Integral 0) ---> a - right.isIntegralLiteral && right.toLong() == 0L -> left - left.isIntegralLiteral && left.toLong() == 0L -> right - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-xorEqual - // Xor(a, a) ---> Integral 0 - left == right -> (0L).toValueWithSort(expr.resultSort).primitiveToSymbolic().expr - else -> Xor(leftPrimitive, rightPrimitive) - } - Rem -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-remConcrete - // Rem(Integral a, Integral b) ---> Integral (a % b) - left.isIntegralLiteral && right.isIntegralLiteral && right.toLong() != 0L -> - evalIntegralLiterals(left, right, expr.resultSort, Long::rem) - else -> Rem(leftPrimitive, rightPrimitive) - } - } - } - - - override fun visit(expr: UtTrue): UtExpression = expr - - override fun visit(expr: UtFalse): UtExpression = expr - - override fun visit(expr: UtEqExpression): UtExpression = - applySimplification(expr) { - val left = expr.left.accept(this) - val right = expr.right.accept(this) - when { - // Eq(Integral a, Integral b) ---> a == b - left.isIntegralLiteral && right.isIntegralLiteral -> evalIntegralLiterals(left, right, Long::equals) - // Eq(Fp a, Fp b) ---> a == b - left is UtFpLiteral && right is UtFpLiteral -> mkBool(left.value == right.value) - // Eq(Expr(sort = Boolean), Integral _) - left is UtIteExpression -> { - val thenEq = UtEqExpression(left.thenExpr, right).accept(this) - val elseEq = UtEqExpression(left.elseExpr, right).accept(this) - if (thenEq is UtFalse && elseEq is UtFalse) { - UtFalse - } else if (thenEq is UtTrue && elseEq is UtFalse) { - left.condition - } else if (thenEq is UtFalse && elseEq is UtTrue) { - mkNot(left.condition) - } else { - UtEqExpression(left, right) - } - } - right is UtIteExpression -> UtEqExpression(right, left).accept(this) - left is UtBoolExpression && right.isIntegralLiteral -> when { - // Eq(a, true) ---> a - right.toLong() == UtLongTrue -> left - // Eq(a, false) ---> not a - right.toLong() == UtLongFalse -> NotBoolExpression(left).accept(this) - else -> UtEqExpression(left, right) - } - right.isIntegralLiteral -> simplifyEq(left, right) - // Eq(a, Fp NaN) ---> False - right is UtFpLiteral && right.value.toDouble().isNaN() -> UtFalse - // Eq(a@(Concrete _), b) ---> Eq(b, a) - left.isConcrete && !right.isConcrete -> UtEqExpression(right, left).accept(this) - // Eq(a a) && a.sort !is FPSort -> true - left == right && left.sort !is UtFp32Sort && left.sort !is UtFp64Sort -> UtTrue - else -> UtEqExpression(left, right) - } - } - - override fun visit(expr: UtBoolConst): UtExpression = applySimplification(expr, false) - - private fun negate(expr: UtBoolLiteral): UtBoolLiteral = mkBool(expr == UtFalse) - - override fun visit(expr: NotBoolExpression): UtExpression = - applySimplification(expr) { - when (val exp = expr.expr.accept(this) as UtBoolExpression) { - // not true ---> false - // not false ---> true - is UtBoolLiteral -> negate(exp) - is UtBoolOpExpression -> { - val left = exp.left - val right = exp.right - if (left.type is IntegerType && right.type is IntegerType) { - when (exp.operator) { - // not Ne(a, b) ---> Eq(a, b) - Ne -> Eq(left, right) - // not Ge(a, b) ---> Lt(a, b) - Ge -> Lt(left, right) - // not Gt(a, b) ---> Le(a, b) - Gt -> Le(left, right) - // not Lt(a, b) ---> Ge(a, b) - Lt -> Ge(left, right) - // not Le(a, b) ---> Gt(a, b) - Le -> Gt(left, right) - Eq -> NotBoolExpression(exp) - } - } else { - when (exp.operator) { - // simplification not Ne(a, b) ---> Eq(a, b) is always valid - Ne -> Eq(left, right) - else -> NotBoolExpression(exp) - } - } - } - // not not expr -> expr - is NotBoolExpression -> exp.expr - // not (and a_1, a_2, ..., a_n) ---> or (not a_1), (not a_2), ..., (not a_n) - is UtAndBoolExpression -> UtOrBoolExpression(exp.exprs.map { NotBoolExpression(it).accept(this) as UtBoolExpression }) - // not (or a_1, a_2, ..., a_n) ---> and (not a_1), (not a_2), ..., (not a_n) - is UtOrBoolExpression -> UtAndBoolExpression(exp.exprs.map { NotBoolExpression(it).accept(this) as UtBoolExpression }) - else -> NotBoolExpression(exp) - } - } - - override fun visit(expr: UtOrBoolExpression): UtExpression = - applySimplification(expr) { - val exprs = expr.exprs.map { it.accept(this) as UtBoolExpression }.filterNot { it == UtFalse } - .flatMap { splitOr(it) } - when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andOrEmpty - exprs.isEmpty() -> UtFalse - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andOrValue - exprs.any { it == UtTrue } -> UtTrue - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andorSingle - exprs.size == 1 -> exprs.single() - else -> UtOrBoolExpression(exprs) - } - } - - override fun visit(expr: UtAndBoolExpression): UtExpression = - applySimplification(expr) { - val exprs = expr.exprs.map { it.accept(this) as UtBoolExpression }.filterNot { it == UtTrue } - .flatMap { splitAnd(it) } - when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andOrEmpty - exprs.isEmpty() -> UtTrue - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andOrValue - exprs.any { it == UtFalse } -> UtFalse - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andOrSingle - exprs.size == 1 -> exprs.single() - else -> UtAndBoolExpression(exprs) - } - } - - override fun visit(expr: UtAddNoOverflowExpression): UtExpression = - UtAddNoOverflowExpression( - applySimplification(expr.left), - applySimplification(expr.right) - ) - - override fun visit(expr: UtSubNoOverflowExpression): UtExpression = - UtSubNoOverflowExpression( - applySimplification(expr.left), - applySimplification(expr.right) - ) - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-negConcrete - // Neg (Concrete a) ---> Concrete (-a) - override fun visit(expr: UtNegExpression): UtExpression = - applySimplification(expr) { - val variable = expr.variable.expr.accept(this) - if (variable.isConcrete) { - val value = variable.toConcrete() - when (variable.sort) { - UtByteSort -> mkInt(-(value as Byte)) - UtShortSort -> mkInt(-(value as Short)) - UtIntSort -> mkInt(-(value as Int)) - UtLongSort -> mkLong(-(value as Long)) - UtFp32Sort -> mkFloat(-(value as Float)) - UtFp64Sort -> mkDouble(-(value as Double)) - else -> UtNegExpression(variable.toPrimitiveValue(expr.variable.type)) - } - } else { - UtNegExpression(variable.toPrimitiveValue(expr.variable.type)) - } - } - - override fun visit(expr: UtCastExpression): UtExpression = - applySimplification(expr) { - val newExpr = expr.variable.expr.accept(this) - when { - // Cast(a@(PrimitiveValue type), type) ---> a - expr.type == expr.variable.type -> newExpr - newExpr.isConcrete -> when (newExpr) { - is UtBoolLiteral -> newExpr.toLong().toValueWithSort(expr.sort).primitiveToSymbolic().expr - is UtBvLiteral -> newExpr.toLong().toValueWithSort(expr.sort).primitiveToSymbolic().expr - is UtFpLiteral -> newExpr.value.toValueWithSort(expr.sort).primitiveToSymbolic().expr - else -> UtCastExpression(newExpr.toPrimitiveValue(expr.variable.type), expr.type) - } - else -> UtCastExpression(newExpr.toPrimitiveValue(expr.variable.type), expr.type) - } - } - - - /** - * Simplify expression [Eq](left, right), where [right] operand is integral literal, - * using information about lower and upper bounds of [left] operand from [lts] and [gts] maps. - * - * @return - * [UtFalse], if lower bound of [left] is greater than [right].value - * or upper bound of [left] is less than [right].value - * - * [Eq](left, right), otherwise - */ - private fun simplifyEq(left: PrimitiveValue, right: PrimitiveValue): UtBoolExpression { - val leftExpr = if (left.expr is UtAddrExpression) left.expr.internal else left.expr - val lowerBound = gts[leftExpr] - val upperBound = lts[leftExpr] - - return if (lowerBound == null && upperBound == null) { - Eq(left, right) - } else { - val rightValue = right.expr.toLong() - when { - lowerBound != null && lowerBound > rightValue -> UtFalse - upperBound != null && upperBound < rightValue -> UtFalse - else -> Eq(left, right) - } - } - } - - private fun simplifyEq(left: UtExpression, right: UtExpression): UtBoolExpression { - val leftExpr = if (left is UtAddrExpression) left.internal else left - val lowerBound = gts[leftExpr] - val upperBound = lts[leftExpr] - return if (lowerBound == null && upperBound == null) { - UtEqExpression(left, right) - } else { - val rightValue = right.toLong() - when { - lowerBound != null && lowerBound > rightValue -> UtFalse - upperBound != null && upperBound < rightValue -> UtFalse - else -> UtEqExpression(left, right) - } - } - } - - - /** - * Simplify expression [Gt](left, right) or [Ge](left, right), where [right] operand is integral literal, - * using information about lower and upper bounds of [left] operand from [lts] and [gts] maps. - * - * @param including - if true than simplified expression is Ge(left,right), else Gt(left, right) - * @return - * [UtTrue], if lower bound of [left] is greater than [right].value. - * - * [UtFalse], if upper bound of [left] is less than [right].value. - * - * [Eq](left, right), if interval [[right].value, upper bound of [left]] has length = 1. - * - * [Gt]|[Ge](left, right), otherwise, depending on value of [including] parameter. - */ - private fun simplifyGreater(left: PrimitiveValue, right: PrimitiveValue, including: Boolean): UtExpression { - val leftExpr = if (left.expr is UtAddrExpression) left.expr.internal else left.expr - val lowerBound = gts[leftExpr] - val upperBound = lts[leftExpr] - val operator = if (including) Ge else Gt - - return if (lowerBound == null && upperBound == null) { - operator(left, right) - } else { - val rightValue = right.expr.toLong() + if (including) 0 else 1 - when { - // left > Long.MAX_VALUE - rightValue == Long.MIN_VALUE && !including -> UtFalse - lowerBound != null && lowerBound >= rightValue -> UtTrue - upperBound != null && upperBound < rightValue -> UtFalse - upperBound != null && rightValue - upperBound == 0L -> Eq( - left, - rightValue.toValueWithSort(right.expr.sort).primitiveToSymbolic() - ) - else -> operator(left, right) - } - } - } - - /** - * Simplify expression [Lt](left, right) or [Le](left, right), where [right] operand is integral literal, - * using information about lower and upper bounds of [left] operand from [lts] and [gts] maps. - * - * @param including - if true than simplified expression is Le(left, right), else Lt(left, right) - * @return - * [UtTrue], if upper bound of [left] is less than [right].value. - * - * [UtFalse], if lower bound of [left] is greater than [right].value. - * - * [Eq](left, right), if interval [lower bound of [left], [right].value] has length = 1. - * - * [Lt]|[Le](left, right), otherwise, depending on value of [including] parameter. - */ - private fun simplifyLess(left: PrimitiveValue, right: PrimitiveValue, including: Boolean): UtExpression { - val leftExpr = if (left.expr is UtAddrExpression) left.expr.internal else left.expr - val lowerBound = gts[leftExpr] - val upperBound = lts[leftExpr] - val operator = if (including) Le else Lt - - return if (upperBound == null && lowerBound == null) { - operator(left, right) - } else { - val rightValue = right.expr.toLong() - if (including) 0 else 1 - when { - // left < Long.MIN_VALUE - rightValue == Long.MAX_VALUE && !including -> UtFalse - upperBound != null && upperBound <= rightValue -> UtTrue - lowerBound != null && lowerBound > rightValue -> UtFalse - lowerBound != null && rightValue - lowerBound == 0L -> Eq( - left, - rightValue.toValueWithSort(right.expr.sort).primitiveToSymbolic() - ) - else -> operator(left, right) - } - } - } - - - override fun visit(expr: UtBoolOpExpression): UtExpression = - applySimplification(expr) { - val left = expr.left.expr.accept(this) - val right = expr.right.expr.accept(this) - val leftPrimitive = left.toPrimitiveValue(expr.left.type) - val rightPrimitive = right.toPrimitiveValue(expr.right.type) - when (expr.operator) { - Eq -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqConcrete - // Eq(Integral a, Integral b) ---> a == b - left.isIntegralLiteral && right.isIntegralLiteral -> evalIntegralLiterals(left, right, Long::equals) - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqTrue - // Eq(a, true) ---> a - left is UtBoolExpression && right.isIntegralLiteral && right.toLong() == UtLongTrue -> left - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqFalse - // Eq(a, false) ---> not a - left is UtBoolExpression && right.isIntegralLiteral && right.toLong() == UtLongFalse -> - NotBoolExpression(left).accept(this) - // Eq(Op(_, Integral _), Integral _) - right.isIntegralLiteral && left is UtOpExpression && left.right.expr.isIntegralLiteral -> when (left.operator) { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqAddOpenLiteral - // Eq(Add(a, Integral b), Integral c) ---> Eq(a, Integral (c - b)) - Add -> Eq( - left.left, - evalIntegralLiterals( - right, - left.right.expr, - maxSort(expr.right, left.right), - Long::minus - ).toPrimitiveValue(maxType(expr.right.type, left.right.type)) - ) - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqXorOpenLiteral - // Eq(Xor(a, Integral b), Integral c) ---> Eq(a, Integral (c `xor` b)) - Xor -> Eq( - left.left, - evalIntegralLiterals(right, left.right.expr, maxSort(expr.right, left.right), Long::xor) - .toPrimitiveValue(maxType(expr.right.type, left.right.type)) - ) - else -> Eq(leftPrimitive, rightPrimitive) - } - /** - * @see simplifyEq - */ - right.isIntegralLiteral -> simplifyEq(leftPrimitive, rightPrimitive) - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqConcrete - // Eq(Fp a, Fp b) ---> a == b - left is UtFpLiteral && right is UtFpLiteral -> mkBool(left.value == right.value) - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqNaN - // Eq(a, Fp NaN) ---> False - left is UtFpLiteral && left.value.toDouble().isNaN() -> UtFalse - right is UtFpLiteral && right.value.toDouble().isNaN() -> UtFalse - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqEqual - // Eq(a a) && a.sort !is FPSort -> true - left == right && expr.left.type !is FloatType && expr.left.type !is DoubleType -> UtTrue - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqLiteralToRight - // Eq(a@(Concrete _), b) ---> Eq(b, a) - left.isConcrete -> Eq( - rightPrimitive, - leftPrimitive - ).accept(this) - else -> Eq(leftPrimitive, rightPrimitive) - } - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-neToNotEq - // Ne -> not Eq - Ne -> NotBoolExpression( - Eq( - leftPrimitive, - rightPrimitive - ) - ).accept(this) - Le -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-leConcrete - // Le(Integral a, Integral b) ---> a <= b - left.isIntegralLiteral && right.isIntegralLiteral -> { - evalIntegralLiterals(left, right) { a, b -> a <= b } - } - /** - * @see simplifyLess - * @see simplifyGreater - */ - right.isIntegralLiteral -> simplifyLess(leftPrimitive, rightPrimitive, true) - left.isIntegralLiteral -> simplifyGreater(rightPrimitive, leftPrimitive, true) - else -> Le(leftPrimitive, rightPrimitive) - } - Lt -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-ltConcrete - // Lt(Integral a, Integral b) ---> a < b - left.isIntegralLiteral && right.isIntegralLiteral -> evalIntegralLiterals(left, right) { a, b -> - a < b - } - /** - * @see simplifyLess - * @see simplifyGreater - */ - right.isIntegralLiteral -> simplifyLess(leftPrimitive, rightPrimitive, false) - left.isIntegralLiteral -> simplifyGreater(rightPrimitive, leftPrimitive, false) - else -> Lt(leftPrimitive, rightPrimitive) - } - Ge -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-geConcrete - // Ge(Integral a, Integral b) ---> a >= b - left.isIntegralLiteral && right.isIntegralLiteral -> evalIntegralLiterals(left, right) { a, b -> - a >= b - } - /** - * @see simplifyLess - * @see simplifyGreater - */ - right.isIntegralLiteral -> simplifyGreater(leftPrimitive, rightPrimitive, true) - left.isIntegralLiteral -> simplifyLess(rightPrimitive, leftPrimitive, true) - else -> Ge(leftPrimitive, rightPrimitive) - } - Gt -> when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-gtConcrete - // Gt(Integral a, Integral b) ---> a > b - left.isIntegralLiteral && right.isIntegralLiteral -> evalIntegralLiterals(left, right) { a, b -> - a > b - } - /** - * @see simplifyLess - * @see simplifyGreater - */ - right.isIntegralLiteral -> simplifyGreater(leftPrimitive, rightPrimitive, false) - left.isIntegralLiteral -> simplifyLess(rightPrimitive, leftPrimitive, false) - else -> Gt(leftPrimitive, rightPrimitive) - } - } - } - - private fun maxType(left: Type, right: Type): Type = when { - left is LongType -> left - right is LongType -> right - else -> IntType.v() - } - - override fun visit(expr: UtIsExpression): UtExpression = applySimplification(expr, false) { - UtIsExpression(expr.addr.accept(this) as UtAddrExpression, expr.typeStorage, expr.numberOfTypes) - } - - override fun visit(expr: UtGenericExpression): UtExpression = applySimplification(expr, false) { - UtGenericExpression(expr.addr.accept(this) as UtAddrExpression, expr.types, expr.numberOfTypes) - } - - override fun visit(expr: UtIsGenericTypeExpression): UtExpression = applySimplification(expr, false) { - UtIsGenericTypeExpression( - expr.addr.accept(this) as UtAddrExpression, - expr.baseAddr.accept(this) as UtAddrExpression, - expr.parameterTypeIndex - ) - } - - override fun visit(expr: UtEqGenericTypeParametersExpression): UtExpression = - applySimplification(expr, false) { - UtEqGenericTypeParametersExpression( - expr.firstAddr.accept(this) as UtAddrExpression, - expr.secondAddr.accept(this) as UtAddrExpression, - expr.indexMapping - ) - } - - override fun visit(expr: UtInstanceOfExpression): UtExpression = applySimplification(expr, false) { - val simplifiedHard = (expr.constraint.accept(this) as UtBoolExpression).asHardConstraint() - UtInstanceOfExpression(expr.symbolicStateUpdate.copy(hardConstraints = simplifiedHard)) - } - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-ite - // ite(true, then, _) ---> then - // ite(false, _, else) ---> else - override fun visit(expr: UtIteExpression): UtExpression = - applySimplification(expr) { - when (val condition = expr.condition.accept(this) as UtBoolExpression) { - UtTrue -> expr.thenExpr.accept(this) - UtFalse -> expr.elseExpr.accept(this) - else -> UtIteExpression(condition, expr.thenExpr.accept(this), expr.elseExpr.accept(this)) - } - } - - override fun visit(expr: UtMkTermArrayExpression): UtExpression = applySimplification(expr, false) - - override fun visit(expr: UtStringConst): UtExpression = expr - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-sConcat - // UtConcat("A_1", "A_2", ..., "A_n") ---> "A_1A_2...A_n" - override fun visit(expr: UtConcatExpression): UtExpression = - applySimplification(expr) { - val parts = expr.parts.map { it.accept(this) as UtStringExpression } - if (parts.all { it.isConcrete }) { - UtSeqLiteral(parts.joinToString { it.toConcrete() as String }) - } else { - UtConcatExpression(parts) - } - } - - override fun visit(expr: UtConvertToString): UtExpression = - applySimplification(expr) { UtConvertToString(expr.expression.accept(this)) } - - override fun visit(expr: UtStringToInt): UtExpression = - applySimplification(expr) { UtStringToInt(expr.expression.accept(this), expr.sort) } - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-sLength - // UtLength "A" ---> "A".length - override fun visit(expr: UtStringLength): UtExpression = - applySimplification(expr) { - val string = expr.string.accept(this) - when { - string is UtArrayToString -> string.length.expr - string.isConcrete -> mkInt((string.toConcrete() as String).length) - else -> UtStringLength(string) - } - } - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-sPositiveLength - // UtPositiveLength "A" ---> UtTrue - override fun visit(expr: UtStringPositiveLength): UtExpression = - applySimplification(expr) { - val string = expr.string.accept(this) - if (string.isConcrete) UtTrue else UtStringPositiveLength(string) - } - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-sCharAt - // UtCharAt "A" (Integral i) ---> "A".charAt(i) - override fun visit(expr: UtStringCharAt): UtExpression = - applySimplification(expr) { - val index = expr.index.accept(this) - val string = withNewSelect(index) { expr.string.accept(this) } - if (allConcrete(string, index)) { - (string.toConcrete() as String)[index.toConcrete() as Int].primitiveToSymbolic().expr - } else { - UtStringCharAt(string, index) - } - } - - override fun visit(expr: UtStringEq): UtExpression = - applySimplification(expr) { - val left = expr.left.accept(this) - val right = expr.right.accept(this) - when { - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqConcrete - // Eq("A", "B") ---> "A" == "B" - allConcrete(left, right) -> mkBool(left.toConcrete() == right.toConcrete()) - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqEqual - // Eq(a, a) ---> true - left == right -> UtTrue - else -> UtStringEq(left, right) - } - } - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-sSubstring - // UtSubstring "A" (Integral begin) (Integral end) ---> "A".substring(begin, end) - override fun visit(expr: UtSubstringExpression): UtExpression = - applySimplification(expr) { - val string = expr.string.accept(this) - val beginIndex = expr.beginIndex.accept(this) - val length = expr.length.accept(this) - if (allConcrete(string, beginIndex, length)) { - val begin = beginIndex.toConcrete() as Int - val end = begin + length.toConcrete() as Int - UtSeqLiteral((string.toConcrete() as String).substring(begin, end)) - } else { - UtSubstringExpression(string, beginIndex, length) - } - } - - override fun visit(expr: UtReplaceExpression): UtExpression = applySimplification(expr, false) - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-sStartsWith - // UtStartsWith "A" "P" ---> "A".startsWith("P") - override fun visit(expr: UtStartsWithExpression): UtExpression = - applySimplification(expr) { - val string = expr.string.accept(this) - val prefix = expr.prefix.accept(this) - if (allConcrete(string, prefix)) { - val concreteString = string.toConcrete() as String - val concretePrefix = prefix.toConcrete() as String - mkBool(concreteString.startsWith(concretePrefix)) - } else { - UtStartsWithExpression(string, prefix) - } - } - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-sEndsWith - // UtEndsWith "A" "S" ---> "A".endsWith("S") - override fun visit(expr: UtEndsWithExpression): UtExpression = - applySimplification(expr) { - val string = expr.string.accept(this) - val suffix = expr.suffix.accept(this) - if (allConcrete(string, suffix)) { - val concreteString = string.toConcrete() as String - val concreteSuffix = suffix.toConcrete() as String - mkBool(concreteString.endsWith(concreteSuffix)) - } else { - UtEndsWithExpression(string, suffix) - } - } - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-sIndexOf - // UtIndexOf "A" "S" ---> "A".indexOf("S") - override fun visit(expr: UtIndexOfExpression): UtExpression = applySimplification(expr) { - val string = expr.string.accept(this) - val substring = expr.substring.accept(this) - if (allConcrete(string, substring)) { - val concreteString = string.toConcrete() as String - val concreteSubstring = substring.toConcrete() as String - mkInt(concreteString.indexOf(concreteSubstring)) - } else { - UtIndexOfExpression(string, substring) - } - } - - // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-sContains - // UtContains "A" "S" ---> "A".contains("S") - override fun visit(expr: UtContainsExpression): UtExpression = - applySimplification(expr) { - val string = expr.string.accept(this) - val substring = expr.substring.accept(this) - if (allConcrete(string, substring)) { - val concreteString = string.toConcrete() as String - val concreteSubstring = substring.toConcrete() as String - mkBool(concreteString.contains(concreteSubstring)) - } else { - UtContainsExpression(string, substring) - } - } - - override fun visit(expr: UtToStringExpression): UtExpression = - applySimplification(expr) { - UtToStringExpression(expr.isNull.accept(this) as UtBoolExpression, expr.notNullExpr.accept(this)) - } - - override fun visit(expr: UtArrayToString): UtExpression = - applySimplification(expr) { - UtArrayToString( - expr.arrayExpression.accept(this), - expr.offset.expr.accept(this).toIntValue(), - expr.length.expr.accept(this).toIntValue() - ) - } - - override fun visit(expr: UtSeqLiteral): UtExpression = expr - - override fun visit(expr: UtConstArrayExpression): UtExpression = - applySimplification(expr) { - UtConstArrayExpression(expr.constValue.accept(this), expr.sort) - } - - override fun visit(expr: UtArrayInsert): UtExpression = applySimplification(expr, false) { - UtArrayInsert( - expr.arrayExpression.accept(this), - expr.index.expr.accept(this).toIntValue(), - expr.element.accept(this) - ) - } - - override fun visit(expr: UtArrayInsertRange): UtExpression = applySimplification(expr, false) { - UtArrayInsertRange( - expr.arrayExpression.accept(this), - expr.index.expr.accept(this).toIntValue(), - expr.elements.accept(this), - expr.from.expr.accept(this).toIntValue(), - expr.length.expr.accept(this).toIntValue() - ) - } - - override fun visit(expr: UtArrayRemove): UtExpression = applySimplification(expr, false) { - UtArrayRemove( - expr.arrayExpression.accept(this), - expr.index.expr.accept(this).toIntValue() - ) - } - - - override fun visit(expr: UtArrayRemoveRange): UtExpression = applySimplification(expr, false) { - UtArrayRemoveRange( - expr.arrayExpression.accept(this), - expr.index.expr.accept(this).toIntValue(), - expr.length.expr.accept(this).toIntValue() - ) - - } - - override fun visit(expr: UtArraySetRange): UtExpression = applySimplification(expr, false) { - UtArraySetRange( - expr.arrayExpression.accept(this), - expr.index.expr.accept(this).toIntValue(), - expr.elements.accept(this), - expr.from.expr.accept(this).toIntValue(), - expr.length.expr.accept(this).toIntValue() - ) - } - - override fun visit(expr: UtArrayShiftIndexes): UtExpression = applySimplification(expr, false) { - UtArrayShiftIndexes( - expr.arrayExpression.accept(this), - expr.offset.expr.accept(this).toIntValue() - ) - } - - override fun visit(expr: UtArrayApplyForAll): UtExpression = applySimplification(expr, false) { - UtArrayApplyForAll( - expr.arrayExpression.accept(this), - expr.constraint - ) - } - - override fun visit(expr: UtStringToArray): UtExpression = applySimplification(expr, false) { - UtStringToArray( - expr.stringExpression.accept(this), - expr.offset.expr.accept(this).toIntValue() - ) - } -} - - -private val arrayExpressionAxiomInstantiationCache = - IdentityHashMap() - -private val stringExpressionAxiomInstantiationCache = - IdentityHashMap() - -private val arrayExpressionAxiomIndex = AtomicInteger(0) - -private fun instantiateArrayAsNewConst(arrayExpression: UtExtendedArrayExpression) = - arrayExpressionAxiomInstantiationCache.getOrPut(arrayExpression) { - val suffix = when (arrayExpression) { - is UtArrayInsert -> "Insert" - is UtArrayInsertRange -> "InsertRange" - is UtArrayRemove -> "Remove" - is UtArrayRemoveRange -> "RemoveRange" - is UtArraySetRange -> "SetRange" - is UtArrayShiftIndexes -> "ShiftIndexes" - is UtStringToArray -> "StringToArray" - is UtArrayApplyForAll -> error("UtArrayApplyForAll cannot be instantiated as new const array") - } - UtMkArrayExpression( - "_array$suffix${arrayExpressionAxiomIndex.getAndIncrement()}", - arrayExpression.sort - ) - } - -private fun instantiateStringAsNewConst(stringExpression: UtStringExpression) = - stringExpressionAxiomInstantiationCache.getOrPut(stringExpression) { - val suffix = when (stringExpression) { - is UtArrayToString -> "ArrayToString" - else -> unreachableBranch("Cannot instantiate new string const for $stringExpression") - } - UtStringConst("_str$suffix${arrayExpressionAxiomIndex.getAndIncrement()}") - } - -/** - * Visitor that applies the same simplifications as [RewritingVisitor] and instantiate axioms for extended array theory. - * - * @see UtExtendedArrayExpression - */ -class AxiomInstantiationRewritingVisitor( - eqs: Map = emptyMap(), - lts: Map = emptyMap(), - gts: Map = emptyMap() -) : RewritingVisitor(eqs, lts, gts) { - private val instantiatedAxiomExpressions = mutableListOf() - - /** - * Select(UtArrayInsert(a, v, i), j) is equivalent to ITE(i = j, v, Select(a, ITE(j < i, j, j - 1)) - */ - override fun visit(expr: UtArrayInsert): UtExpression { - val arrayInstance = instantiateArrayAsNewConst(expr) - val index = expr.index.expr.accept(this).toPrimitiveValue(expr.index.type) - val element = expr.element.accept(this) - val selectedIndex = selectIndexStack.last() - val pushedIndex = UtIteExpression( - Lt(selectedIndex, index), - selectedIndex.expr, - Add(selectedIndex, (-1).toPrimitiveValue()) - ) - val arrayExpression = withNewSelect(pushedIndex) { expr.arrayExpression.accept(this) } - - instantiatedAxiomExpressions += UtEqExpression( - UtIteExpression( - Eq(selectedIndex, index), - element, - arrayExpression.select(pushedIndex), - ).accept(this), arrayInstance.select(selectedIndex.expr) - ) - return arrayInstance - } - - /** - * Select(UtArrayInsertRange(a, i, b, from, length), j) is equivalent to - * ITE(j >= i && j < i + length, Select(b, j - i + from), Select(a, ITE(j < i, j, j - length) - */ - override fun visit(expr: UtArrayInsertRange): UtExpression { - val arrayInstance = instantiateArrayAsNewConst(expr) - val index = expr.index.expr.accept(this).toPrimitiveValue(expr.index.type) - val from = expr.from.expr.accept(this).toPrimitiveValue(expr.index.type) - val length = expr.length.expr.accept(this).toPrimitiveValue(expr.length.type) - val selectedIndex = selectIndexStack.last() - val pushedArrayInstanceIndex = - UtIteExpression(Lt(selectedIndex, index), selectedIndex.expr, Sub(selectedIndex, length)) - val arrayExpression = withNewSelect(pushedArrayInstanceIndex) { expr.arrayExpression.accept(this) } - val pushedElementsIndex = Add(Sub(selectedIndex, index).toIntValue(), from) - val elements = withNewSelect(pushedElementsIndex) { expr.elements.accept(this) } - instantiatedAxiomExpressions += UtEqExpression( - UtIteExpression( - mkAnd(Ge(selectedIndex, index), Lt(selectedIndex, Add(index, length).toIntValue())), - elements.select(pushedElementsIndex), - arrayExpression.select(pushedArrayInstanceIndex), - ).accept(this), arrayInstance.select(selectedIndex.expr) - ) - return arrayInstance - } - - /** - * Select(UtArrayRemove(a, i), j) is equivalent to Select(a, ITE(j < i, j, j + 1)) - */ - override fun visit(expr: UtArrayRemove): UtExpression { - val arrayInstance = instantiateArrayAsNewConst(expr) - val index = expr.index.expr.accept(this).toPrimitiveValue(expr.index.type) - val selectedIndex = selectIndexStack.last() - val pushedIndex = UtIteExpression( - Lt(selectedIndex, index), - selectedIndex.expr, - Add(selectedIndex, 1.toPrimitiveValue()) - ) - val arrayExpression = withNewSelect(pushedIndex) { expr.arrayExpression.accept(this) } - - instantiatedAxiomExpressions += UtEqExpression( - arrayExpression.select(pushedIndex), - arrayInstance.select(selectedIndex.expr) - ) - return arrayInstance - } - - /** - * Select(UtArrayRemoveRange(a, i, length), j) is equivalent to Select(a, ITE(j < i, j, j + length)) - */ - override fun visit(expr: UtArrayRemoveRange): UtExpression { - val arrayInstance = instantiateArrayAsNewConst(expr) - val index = expr.index.expr.accept(this).toPrimitiveValue(expr.index.type) - val length = expr.length.expr.accept(this).toPrimitiveValue(expr.length.type) - val selectedIndex = selectIndexStack.last() - val pushedIndex = UtIteExpression( - Lt(selectedIndex, index), - selectedIndex.expr, - Add(selectedIndex, length) - ) - val arrayExpression = withNewSelect(pushedIndex) { expr.arrayExpression.accept(this) } - - instantiatedAxiomExpressions += UtEqExpression( - arrayExpression.select(pushedIndex), - arrayInstance.select(selectedIndex.expr) - ) - return arrayInstance - } - - /** - * Select(UtSetRange(a, i, b, from, length), j) is equivalent to - * ITE(j >= i && j < i + length, Select(b, j - i + from), Select(a, i)) - */ - override fun visit(expr: UtArraySetRange): UtExpression { - val arrayInstance = instantiateArrayAsNewConst(expr) - val index = expr.index.expr.accept(this).toPrimitiveValue(expr.index.type) - val from = expr.from.expr.accept(this).toPrimitiveValue(expr.index.type) - val length = expr.length.expr.accept(this).toPrimitiveValue(expr.length.type) - val selectedIndex = selectIndexStack.last() - val arrayExpression = expr.arrayExpression.accept(this) - val pushedIndex = Add(Sub(selectedIndex, index).toIntValue(), from) - val elements = withNewSelect(pushedIndex) { expr.elements.accept(this) } - instantiatedAxiomExpressions += UtEqExpression( - UtIteExpression( - mkAnd(Ge(selectedIndex, index), Lt(selectedIndex, Add(index, length).toIntValue())), - elements.select(pushedIndex), - arrayExpression.select(selectedIndex.expr) - ).accept(this), arrayInstance.select(selectedIndex.expr) - ) - return arrayInstance - } - - /** - * Select(UtShiftIndexes(a, offset), j) is equivalent to Select(a, j - offset) - */ - override fun visit(expr: UtArrayShiftIndexes): UtExpression { - val arrayInstance = instantiateArrayAsNewConst(expr) - val offset = expr.offset.expr.accept(this).toIntValue() - val selectedIndex = selectIndexStack.last() - val pushedIndex = Sub(selectedIndex, offset) - val arrayExpression = withNewSelect(pushedIndex) { expr.arrayExpression.accept(this) } - - instantiatedAxiomExpressions += UtEqExpression( - arrayExpression.select(pushedIndex), - arrayInstance.select(selectedIndex.expr) - ) - return arrayInstance - } - - /** - * instantiate expr.constraint on selecting from UtArrayApplyForAll - */ - override fun visit(expr: UtArrayApplyForAll): UtExpression { - val selectedIndex = selectIndexStack.last() - val arrayExpression = expr.arrayExpression.accept(this) - val constraint = expr.constraint(arrayExpression, selectedIndex) - instantiatedAxiomExpressions += constraint - return arrayExpression - } - - override fun visit(expr: UtStringToArray): UtExpression { - val arrayInstance = instantiateArrayAsNewConst(expr) - val offset = expr.offset.expr.accept(this).toIntValue() - val selectedIndex = selectIndexStack.last() - val pushedIndex = Add(selectedIndex, offset) - val stringExpression = withNewSelect(pushedIndex) { expr.stringExpression.accept(this) } - - instantiatedAxiomExpressions += UtEqExpression( - UtStringCharAt(stringExpression, pushedIndex), - arrayInstance.select(selectedIndex.expr) - ) - return arrayInstance - } - - override fun visit(expr: UtArrayToString): UtExpression { - val stringInstance = instantiateStringAsNewConst(expr) - val offset = expr.offset.expr.accept(this).toIntValue() - val selectedIndex = selectIndexStack.last() - val pushedIndex = Add(selectedIndex, offset) - val arrayExpression = withNewSelect(pushedIndex) { expr.arrayExpression.accept(this) } - - instantiatedAxiomExpressions += UtEqExpression( - arrayExpression.select(pushedIndex), - UtStringCharAt(stringInstance, selectedIndex.expr) - ) - return stringInstance - } - - val instantiatedArrayAxioms: List - get() = instantiatedAxiomExpressions -} - -private const val UtLongTrue = 1L -private const val UtLongFalse = 0L diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Simplificator.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Simplificator.kt new file mode 100644 index 0000000000..08845205aa --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Simplificator.kt @@ -0,0 +1,1205 @@ +package org.utbot.engine.pc + +import org.utbot.engine.Add +import org.utbot.engine.And +import org.utbot.engine.Cmp +import org.utbot.engine.Cmpg +import org.utbot.engine.Cmpl +import org.utbot.engine.Div +import org.utbot.engine.Eq +import org.utbot.engine.Ge +import org.utbot.engine.Gt +import org.utbot.engine.Le +import org.utbot.engine.Lt +import org.utbot.engine.Mul +import org.utbot.engine.Ne +import org.utbot.engine.Or +import org.utbot.engine.PrimitiveValue +import org.utbot.engine.Rem +import org.utbot.engine.Shl +import org.utbot.engine.Shr +import org.utbot.engine.Sub +import org.utbot.engine.Ushr +import org.utbot.engine.Xor +import org.utbot.engine.maxSort +import org.utbot.engine.primitiveToLiteral +import org.utbot.engine.primitiveToSymbolic +import org.utbot.engine.symbolic.asHardConstraint +import org.utbot.engine.toIntValue +import org.utbot.engine.toPrimitiveValue +import org.utbot.framework.UtSettings.useExpressionSimplification +import java.util.IdentityHashMap +import java.util.concurrent.atomic.AtomicInteger +import kotlinx.collections.immutable.toPersistentList +import soot.DoubleType +import soot.FloatType +import soot.IntType +import soot.IntegerType +import soot.LongType +import soot.Type + + +/** + * UtExpressionVisitor that performs simple rewritings on expressions with concrete values. + * + */ +open class Simplificator( + private val eqs: Map = emptyMap(), + private val lts: Map = emptyMap(), + private val gts: Map = emptyMap() +) : UtExpressionVisitor { + val axiomInstantiationVisitor: Simplificator + get() = AxiomInstantiationSimplificator(eqs, lts, gts) + protected val selectIndexStack = mutableListOf() + private val simplificationCache = IdentityHashMap() + + protected inline fun withNewSelect(index: UtExpression, block: () -> R): R { + selectIndexStack += index.toIntValue() + return block().also { + selectIndexStack.removeLast() + } + } + + @Suppress("MemberVisibilityCanBePrivate") + protected inline fun withEmptySelects(block: () -> R): R { + val currentSelectStack = selectIndexStack.toList() + selectIndexStack.clear() + return block().also { + selectIndexStack.addAll(currentSelectStack) + } + } + + @Suppress("MemberVisibilityCanBePrivate") + protected inline fun withRemoveSelect(block: () -> R): R { + val lastIndex = selectIndexStack.removeLastOrNull() + return block().also { + if (lastIndex != null) { + selectIndexStack += lastIndex + } + } + } + + /** + * if [useExpressionSimplification] is true then rewrite [expr] and + * substitute concrete values from [eqs] map for equal symbolic expressions. + * + * @param expr - simplified expression + * @param block - simplification function. Identity function by default + * @param substituteResult - if true then try to substitute result of block(expr) + * with equal concrete value from [eqs] map. + */ + private inline fun applySimplification( + expr: UtExpression, + substituteResult: Boolean = true, + crossinline block: (() -> UtExpression) = { expr }, + ): UtExpression { + val value = { + if (!useExpressionSimplification) { + expr + } else { + eqs[expr] ?: block().let { if (substituteResult) eqs[it] ?: it else it } + } + } + return simplificationCache.getOrPut(expr) { + checkSortsEquality(value, expr) + } + } + + private fun checkSortsEquality(value: () -> UtExpression, sourceExpr: UtExpression): UtExpression { + val result = value() + + val sourceSort = sourceExpr.sort + val resultSort = result.sort + + // We should return simplified expression with the same sort as the original one. + // The only exception is a case with two primitive sorts and cast expression below. + // So, expression with, e.g., UtArraySort must be returned here. + if (resultSort == sourceSort) return result + + // There we must have the following situation: original expression is `x = Add(a, b)`, where + // x :: Int32, a = 0 :: Int32, b :: Short16. Simplifier removes the left part of the addition since it is + // equal to zero, now we have just b with a different from the original expression sort. + // We must cast it to the required sort, so the result will be `Cast(b, Int32)`. + require(sourceSort is UtPrimitiveSort && resultSort is UtPrimitiveSort) { + val messageParts = listOf( + "Expr to simplify had sort ${sourceSort}:", sourceExpr, "", + "After simplification expression got sort ${resultSort}:", result + ) + messageParts.joinToString(System.lineSeparator(), prefix = System.lineSeparator()) + } + + val expectedType = sourceSort.type + val resultType = resultSort.type + + return UtCastExpression(result.toPrimitiveValue(resultType), expectedType) + } + + override fun visit(expr: UtArraySelectExpression): UtExpression = + applySimplification(expr) { + val index = withEmptySelects { expr.index.accept(this) } + when (val array = withNewSelect(index) { expr.arrayExpression.accept(this) }) { + // select(constArray(a), i) --> a + is UtConstArrayExpression -> array.constValue + is UtArrayMultiStoreExpression -> { + // select(store(..(store(array, Concrete a, b), Concrete x, _)..), Concrete a) -> b + val newStores = array.stores.filter { UtEqExpression(index, it.index).accept(this) != UtFalse } + + when { + newStores.isEmpty() -> array.initial.select(index).accept(this) + UtEqExpression(index, newStores.last().index).accept(this) == UtTrue -> newStores.last().value + else -> UtArrayMultiStoreExpression(array.initial, newStores.toPersistentList()).select(index) + } + } + else -> array.select(index) + } + } + + override fun visit(expr: UtMkArrayExpression): UtExpression = expr + + // store(store(store(array, 1, b), 2, a), 1, a) ---> store(store(array, 2, a), 1, a) + override fun visit(expr: UtArrayMultiStoreExpression): UtExpression = + applySimplification(expr) { + val initial = expr.initial.accept(this) + val stores = withRemoveSelect { + expr.stores.map { + val index = it.index.accept(this) + val value = it.value.accept(this) + + require(value.sort == expr.sort.itemSort) { + "Unequal sorts occurred during rewriting UtArrayMultiStoreExpression:\n" + + "value with index $index has sort ${value.sort} while " + + "${expr.sort.itemSort} was expected." + } + + UtStore(index, value) + } + }.filterNot { it.value == initial.select(it.index) } + when { + stores.isEmpty() -> initial + initial is UtArrayMultiStoreExpression -> UtArrayMultiStoreExpression( + initial.initial, + (initial.stores + stores).asReversed().distinctBy { it.index }.asReversed().toPersistentList() + ) + else -> UtArrayMultiStoreExpression( + initial, + stores.asReversed().distinctBy { it.index }.asReversed().toPersistentList() + ) + } + } + + override fun visit(expr: UtBvLiteral): UtExpression = expr + + override fun visit(expr: UtBvConst): UtExpression = applySimplification(expr, true) + + override fun visit(expr: UtAddrExpression): UtExpression = + applySimplification(expr) { + UtAddrExpression( + expr.internal.accept(this) + ) + } + + override fun visit(expr: UtFpLiteral): UtExpression = expr + + override fun visit(expr: UtFpConst): UtExpression = applySimplification(expr) + + private fun evalIntegralLiterals(literal: UtExpression, sort: UtSort, block: (Long) -> Number): UtExpression = + block(literal.toLong()).toValueWithSort(sort).primitiveToLiteral() + + private fun evalIntegralLiterals( + left: UtExpression, + right: UtExpression, + sort: UtSort, + block: (Long, Long) -> Number + ): UtExpression = + block(left.toLong(), right.toLong()).toValueWithSort(sort).primitiveToLiteral() + + private fun evalIntegralLiterals( + left: UtExpression, + right: UtExpression, + block: (Long, Long) -> Boolean + ): UtExpression = + block(left.toLong(), right.toLong()).primitiveToLiteral() + + private fun Number.toValueWithSort(sort: UtSort): Any = + when (sort) { + UtBoolSort -> this != UtLongFalse + UtByteSort -> this.toByte() + UtShortSort -> this.toShort() + UtCharSort -> this.toChar() + UtIntSort -> this.toInt() + UtLongSort -> this.toLong() + UtFp32Sort -> this.toFloat() + UtFp64Sort -> this.toDouble() + else -> error("Can convert Number values only to primitive sorts. Sort $sort isn't primitive") + } + + private fun UtBoolLiteral.toLong(): Long = if (value) UtLongTrue else UtLongFalse + + private fun UtBvLiteral.toLong(): Long = value.toLong() + + private fun UtExpression.toLong(): Long = when (this) { + is UtBvLiteral -> toLong() + is UtBoolLiteral -> toLong() + is UtAddrExpression -> internal.toLong() + else -> error("$this isn't IntegralLiteral") + } + + private val UtExpression.isIntegralLiteral: Boolean + get() = when (this) { + is UtBvLiteral -> true + is UtBoolLiteral -> true + is UtAddrExpression -> internal.isIntegralLiteral + else -> false + } + + override fun visit(expr: UtOpExpression): UtExpression = + applySimplification(expr) { + val left = expr.left.expr.accept(this) + val right = expr.right.expr.accept(this) + val leftPrimitive = left.toPrimitiveValue(expr.left.type) + val rightPrimitive = right.toPrimitiveValue(expr.right.type) + + when (expr.operator) { + // Sort of pattern matching... + Add -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-addConcrete + // Add(Integral a, Integral b) ---> Integral (a + b) + left.isIntegralLiteral && right.isIntegralLiteral -> + evalIntegralLiterals(left, right, expr.resultSort, Long::plus) + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-addA0 + // Add(a, Integral 0) ---> a + right.isIntegralLiteral && right.toLong() == 0L -> left + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-addLiteralToRight + // Add(Concrete a, b) ---> Add(b, Concrete a).accept(this) + left.isIntegralLiteral -> Add( + rightPrimitive, + leftPrimitive + ).accept(this) + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-addAddOpenLiteral + // Add(Add(x, Integral a), Integral b)) -> Add(x, Integral (a + b)) + right.isIntegralLiteral && left is UtOpExpression && left.operator is Add && left.right.expr.isIntegralLiteral -> + Add( + left.left, + evalIntegralLiterals(left.right.expr, right, expr.right.expr.sort, Long::plus) + .toPrimitiveValue(expr.right.type) + ) + else -> Add(leftPrimitive, rightPrimitive) + } + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-subToAdd + // Sub(a, b) ---> Add(a, Neg b) + Sub -> Add( + leftPrimitive, + UtNegExpression(rightPrimitive).toPrimitiveValue(expr.right.type) + ).accept(this) + Mul -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulConcrete + // Mul(Integral a, Integral b) ---> Integral (a * b) + left.isIntegralLiteral && right.isIntegralLiteral -> + evalIntegralLiterals(left, right, expr.resultSort, Long::times) + right.isIntegralLiteral -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulA0 + // Mul(a, b@(Integral 0)) ---> b + right.toLong() == 0L -> right + + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulA1 + // Mul(a, Integral 1) ---> a + right.toLong() == 1L -> left + + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulAMinus1 + // Mul(a, Integral -1) ---> Neg a + right.toLong() == -1L -> UtNegExpression(leftPrimitive) + + // Mul(Op(_, Integral _), Integral _) + left is UtOpExpression && left.right.expr.isIntegralLiteral -> when (left.operator) { + + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulDistr + // Mul(Add(x, Integral a), Integral b) ---> Add(Mul(x, b), Integral (a * b)) + is Add -> Add( + Mul( + left.left, + rightPrimitive + ).toPrimitiveValue(expr.left.type), + evalIntegralLiterals(left.right.expr, right, expr.resultSort, Long::times) + .toPrimitiveValue(expr.right.type) + ) + + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulMulOpenLiteral + // Mul(Mul(x, Integral a), Integral b) ---> Mul(x, Integral (a * b)) + is Mul -> Mul( + left.left, + evalIntegralLiterals(left.right.expr, right, expr.right.expr.sort, Long::times) + .toPrimitiveValue(expr.right.type) + ) + else -> Mul(leftPrimitive, rightPrimitive) + } + else -> Mul(leftPrimitive, rightPrimitive) + } + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-mulLiteralToRight + // Mul(a@(Integral _), b) ---> Mul(b, a) + left.isIntegralLiteral -> Mul( + rightPrimitive, + leftPrimitive + ).accept(this) + else -> Mul(leftPrimitive, rightPrimitive) + } + Div -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-divConcrete + // Div(Integral a, Integral b) ---> Integral (a / b) + left.isIntegralLiteral && right.isIntegralLiteral && right.toLong() != 0L -> + evalIntegralLiterals(left, right, expr.resultSort, Long::div) + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-divA1 + // Div(a, Concrete 1) ---> a + right.isIntegralLiteral && right.toLong() == 1L -> left + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-divAMinus1 + // Div(a, Concrete -1) ---> Neg a + right.isIntegralLiteral && right.toLong() == -1L -> UtNegExpression(leftPrimitive) + else -> Div(leftPrimitive, rightPrimitive) + } + And -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andConcrete + // And(Integral a, Integral b) ---> Integral (a `and` b) + left.isIntegralLiteral && right.isIntegralLiteral -> + evalIntegralLiterals(left, right, expr.resultSort, Long::and) + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andA0 + // And(a@(Integral 0), b) ---> a + left.isIntegralLiteral && left.toLong() == 0L -> + evalIntegralLiterals(left, expr.resultSort) { it } + right.isIntegralLiteral && right.toLong() == 0L -> + evalIntegralLiterals(right, expr.resultSort) { it } + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andAtoRight + left.isIntegralLiteral -> And( + rightPrimitive, + leftPrimitive + ) + else -> And(leftPrimitive, rightPrimitive) + } + Cmp, Cmpg, Cmpl -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-cmpConcrete + // Cmp(Integral a, Integral b) ---> Integral (cmp a b) + left.isIntegralLiteral && right.isIntegralLiteral -> + evalIntegralLiterals(left, right, expr.resultSort) { a, b -> + when { + a < b -> -1 + a == b -> 0 + else -> 1 + } + } + else -> expr.operator(leftPrimitive, rightPrimitive) + } + Or -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-orConcrete + // Or(Integral a, Integral b) ---> Integral (a `or` b) + left.isIntegralLiteral && right.isIntegralLiteral -> + evalIntegralLiterals(left, right, expr.resultSort, Long::or) + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-orA0 + // Or(a, b@(Integral 0)) ---> a + left.isIntegralLiteral && left.toLong() == 0L -> right + right.isIntegralLiteral && right.toLong() == 0L -> left + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-orLiteralToRight + // Or(a@(Integral _),b) ---> Or(b, a) + left.isIntegralLiteral -> Or( + rightPrimitive, + leftPrimitive + ) + else -> Or(leftPrimitive, rightPrimitive) + } + Shl -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-shlConcrete + // Shl(Integral a, Integral b) ---> Integral (a `shl` b) + left.isIntegralLiteral && right.isIntegralLiteral -> + evalIntegralLiterals(left, right, expr.resultSort) { a, b -> + val size: Int = (expr.sort as UtBvSort).size + a shl (b.toInt() % size) + } + + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-shlCycle + // Shl(a, Integral b) && b % a.bit_size == 0 ---> a + right.isIntegralLiteral && right.toLong() % (expr.resultSort as UtBvSort).size == 0L -> left + else -> Shl(leftPrimitive, rightPrimitive) + } + Shr -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-shrConcrete + // Shr(Integral a, Integral b) ---> Integral (a `shr` b) + left.isIntegralLiteral && right.isIntegralLiteral -> + evalIntegralLiterals(left, right, expr.resultSort) { a, b -> + val size: Int = (expr.sort as UtBvSort).size + a shr (b.toInt() % size) + } + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-shrCycle + // Shr(a, Integral b) && b % a.bit_size == 0 ---> a + right.isIntegralLiteral && right.toLong() % (expr.resultSort as UtBvSort).size == 0L -> left + else -> Shr(leftPrimitive, rightPrimitive) + } + Ushr -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-ushrConcrete + // Ushr(Integral a, Integral b) ---> Integral (a `ushr` b) + left.isIntegralLiteral && right.isIntegralLiteral -> + evalIntegralLiterals(left, right, expr.resultSort) { a, b -> + val size: Int = (expr.sort as UtBvSort).size + if (a >= 0) { + a ushr (b.toInt() % size) + } else { + // 0b0..01..1 where mask.countOneBits() = size + val mask: Long = ((1L shl size) - 1L) + (a and mask) ushr (b.toInt() % size) + } + } + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-ushrCycle + // Ushr(a, Integral b) && b % a.bit_size == 0 ---> a + right.isIntegralLiteral && right.toLong() % (expr.resultSort as UtBvSort).size == 0L -> left + else -> Ushr(leftPrimitive, rightPrimitive) + } + Xor -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-xorConcrete + // Xor(Integral a, Integral b) ---> Integral (a `xor` b) + left.isIntegralLiteral && right.isIntegralLiteral -> + evalIntegralLiterals(left, right, expr.resultSort, Long::xor) + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-xorA0 + // Xor(a, Integral 0) ---> a + right.isIntegralLiteral && right.toLong() == 0L -> left + left.isIntegralLiteral && left.toLong() == 0L -> right + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-xorEqual + // Xor(a, a) ---> Integral 0 + left == right -> (0L).toValueWithSort(expr.resultSort).primitiveToSymbolic().expr + else -> Xor(leftPrimitive, rightPrimitive) + } + Rem -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-remConcrete + // Rem(Integral a, Integral b) ---> Integral (a % b) + left.isIntegralLiteral && right.isIntegralLiteral && right.toLong() != 0L -> + evalIntegralLiterals(left, right, expr.resultSort, Long::rem) + else -> Rem(leftPrimitive, rightPrimitive) + } + } + } + + + override fun visit(expr: UtTrue): UtExpression = expr + + override fun visit(expr: UtFalse): UtExpression = expr + + override fun visit(expr: UtEqExpression): UtExpression = + applySimplification(expr) { + val left = expr.left.accept(this) + val right = expr.right.accept(this) + when { + // Eq(Integral a, Integral b) ---> a == b + left.isIntegralLiteral && right.isIntegralLiteral -> evalIntegralLiterals(left, right, Long::equals) + // Eq(Fp a, Fp b) ---> a == b + left is UtFpLiteral && right is UtFpLiteral -> mkBool(left.value == right.value) + // Eq(Expr(sort = Boolean), Integral _) + left is UtIteExpression -> { + val thenEq = UtEqExpression(left.thenExpr, right).accept(this) + val elseEq = UtEqExpression(left.elseExpr, right).accept(this) + if (thenEq is UtFalse && elseEq is UtFalse) { + UtFalse + } else if (thenEq is UtTrue && elseEq is UtFalse) { + left.condition + } else if (thenEq is UtFalse && elseEq is UtTrue) { + mkNot(left.condition) + } else { + UtEqExpression(left, right) + } + } + right is UtIteExpression -> UtEqExpression(right, left).accept(this) + left is UtBoolExpression && right.isIntegralLiteral -> when { + // Eq(a, true) ---> a + right.toLong() == UtLongTrue -> left + // Eq(a, false) ---> not a + right.toLong() == UtLongFalse -> NotBoolExpression(left).accept(this) + else -> UtEqExpression(left, right) + } + right.isIntegralLiteral -> simplifyEq(left, right) + // Eq(a, Fp NaN) ---> False + right is UtFpLiteral && right.value.toDouble().isNaN() -> UtFalse + // Eq(a@(Concrete _), b) ---> Eq(b, a) + left.isConcrete && !right.isConcrete -> UtEqExpression(right, left).accept(this) + // Eq(a a) && a.sort !is FPSort -> true + left == right && left.sort !is UtFp32Sort && left.sort !is UtFp64Sort -> UtTrue + else -> UtEqExpression(left, right) + } + } + + override fun visit(expr: UtBoolConst): UtExpression = applySimplification(expr, false) + + private fun negate(expr: UtBoolLiteral): UtBoolLiteral = mkBool(expr == UtFalse) + + override fun visit(expr: NotBoolExpression): UtExpression = + applySimplification(expr) { + when (val exp = expr.expr.accept(this) as UtBoolExpression) { + // not true ---> false + // not false ---> true + is UtBoolLiteral -> negate(exp) + is UtBoolOpExpression -> { + val left = exp.left + val right = exp.right + if (left.type is IntegerType && right.type is IntegerType) { + when (exp.operator) { + // not Ne(a, b) ---> Eq(a, b) + Ne -> Eq(left, right) + // not Ge(a, b) ---> Lt(a, b) + Ge -> Lt(left, right) + // not Gt(a, b) ---> Le(a, b) + Gt -> Le(left, right) + // not Lt(a, b) ---> Ge(a, b) + Lt -> Ge(left, right) + // not Le(a, b) ---> Gt(a, b) + Le -> Gt(left, right) + Eq -> NotBoolExpression(exp) + } + } else { + when (exp.operator) { + // simplification not Ne(a, b) ---> Eq(a, b) is always valid + Ne -> Eq(left, right) + else -> NotBoolExpression(exp) + } + } + } + // not not expr -> expr + is NotBoolExpression -> exp.expr + // not (and a_1, a_2, ..., a_n) ---> or (not a_1), (not a_2), ..., (not a_n) + is UtAndBoolExpression -> UtOrBoolExpression(exp.exprs.map { NotBoolExpression(it).accept(this) as UtBoolExpression }) + // not (or a_1, a_2, ..., a_n) ---> and (not a_1), (not a_2), ..., (not a_n) + is UtOrBoolExpression -> UtAndBoolExpression(exp.exprs.map { NotBoolExpression(it).accept(this) as UtBoolExpression }) + else -> NotBoolExpression(exp) + } + } + + override fun visit(expr: UtOrBoolExpression): UtExpression = + applySimplification(expr) { + val exprs = expr.exprs.map { it.accept(this) as UtBoolExpression }.filterNot { it == UtFalse } + .flatMap { splitOr(it) } + when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andOrEmpty + exprs.isEmpty() -> UtFalse + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andOrValue + exprs.any { it == UtTrue } -> UtTrue + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andorSingle + exprs.size == 1 -> exprs.single() + else -> UtOrBoolExpression(exprs) + } + } + + override fun visit(expr: UtAndBoolExpression): UtExpression = + applySimplification(expr) { + val exprs = expr.exprs.map { it.accept(this) as UtBoolExpression }.filterNot { it == UtTrue } + .flatMap { splitAnd(it) } + when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andOrEmpty + exprs.isEmpty() -> UtTrue + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andOrValue + exprs.any { it == UtFalse } -> UtFalse + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-andOrSingle + exprs.size == 1 -> exprs.single() + else -> UtAndBoolExpression(exprs) + } + } + + override fun visit(expr: UtAddNoOverflowExpression): UtExpression = + UtAddNoOverflowExpression( + applySimplification(expr.left), + applySimplification(expr.right) + ) + + override fun visit(expr: UtSubNoOverflowExpression): UtExpression = + UtSubNoOverflowExpression( + applySimplification(expr.left), + applySimplification(expr.right) + ) + + override fun visit(expr: UtMulNoOverflowExpression): UtExpression = + UtMulNoOverflowExpression( + applySimplification(expr.left), + applySimplification(expr.right) + ) + + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-negConcrete + // Neg (Concrete a) ---> Concrete (-a) + override fun visit(expr: UtNegExpression): UtExpression = + applySimplification(expr) { + val variable = expr.variable.expr.accept(this) + if (variable.isConcrete) { + val value = variable.toConcrete() + when (variable.sort) { + UtByteSort -> mkInt(-(value as Byte)) + UtShortSort -> mkInt(-(value as Short)) + UtIntSort -> mkInt(-(value as Int)) + UtLongSort -> mkLong(-(value as Long)) + UtFp32Sort -> mkFloat(-(value as Float)) + UtFp64Sort -> mkDouble(-(value as Double)) + else -> UtNegExpression(variable.toPrimitiveValue(expr.variable.type)) + } + } else { + UtNegExpression(variable.toPrimitiveValue(expr.variable.type)) + } + } + + override fun visit(expr: UtBvNotExpression): UtExpression = expr + + override fun visit(expr: UtCastExpression): UtExpression = + applySimplification(expr) { + val newExpr = expr.variable.expr.accept(this) + when { + // Cast(a@(PrimitiveValue type), type) ---> a + expr.type == expr.variable.type -> newExpr + newExpr.isConcrete -> when (newExpr) { + is UtBoolLiteral -> newExpr.toLong().toValueWithSort(expr.sort).primitiveToSymbolic().expr + is UtBvLiteral -> newExpr.toLong().toValueWithSort(expr.sort).primitiveToSymbolic().expr + is UtFpLiteral -> newExpr.value.toValueWithSort(expr.sort).primitiveToSymbolic().expr + else -> UtCastExpression(newExpr.toPrimitiveValue(expr.variable.type), expr.type) + } + else -> UtCastExpression(newExpr.toPrimitiveValue(expr.variable.type), expr.type) + } + } + + + /** + * Simplify expression [Eq](left, right), where [right] operand is integral literal, + * using information about lower and upper bounds of [left] operand from [lts] and [gts] maps. + * + * @return + * [UtFalse], if lower bound of [left] is greater than [right].value + * or upper bound of [left] is less than [right].value + * + * [Eq](left, right), otherwise + */ + private fun simplifyEq(left: PrimitiveValue, right: PrimitiveValue): UtBoolExpression { + val leftExpr = if (left.expr is UtAddrExpression) left.expr.internal else left.expr + val lowerBound = gts[leftExpr] + val upperBound = lts[leftExpr] + + return if (lowerBound == null && upperBound == null) { + Eq(left, right) + } else { + val rightValue = right.expr.toLong() + when { + lowerBound != null && lowerBound > rightValue -> UtFalse + upperBound != null && upperBound < rightValue -> UtFalse + else -> Eq(left, right) + } + } + } + + private fun simplifyEq(left: UtExpression, right: UtExpression): UtBoolExpression { + val leftExpr = if (left is UtAddrExpression) left.internal else left + val lowerBound = gts[leftExpr] + val upperBound = lts[leftExpr] + return if (lowerBound == null && upperBound == null) { + UtEqExpression(left, right) + } else { + val rightValue = right.toLong() + when { + lowerBound != null && lowerBound > rightValue -> UtFalse + upperBound != null && upperBound < rightValue -> UtFalse + else -> UtEqExpression(left, right) + } + } + } + + + /** + * Simplify expression [Gt](left, right) or [Ge](left, right), where [right] operand is integral literal, + * using information about lower and upper bounds of [left] operand from [lts] and [gts] maps. + * + * @param including - if true than simplified expression is Ge(left,right), else Gt(left, right) + * @return + * [UtTrue], if lower bound of [left] is greater than [right].value. + * + * [UtFalse], if upper bound of [left] is less than [right].value. + * + * [Eq](left, right), if interval [[right].value, upper bound of [left]] has length = 1. + * + * [Gt]|[Ge](left, right), otherwise, depending on value of [including] parameter. + */ + private fun simplifyGreater(left: PrimitiveValue, right: PrimitiveValue, including: Boolean): UtExpression { + val leftExpr = if (left.expr is UtAddrExpression) left.expr.internal else left.expr + val lowerBound = gts[leftExpr] + val upperBound = lts[leftExpr] + val operator = if (including) Ge else Gt + + return if (lowerBound == null && upperBound == null) { + operator(left, right) + } else { + val rightValue = right.expr.toLong() + if (including) 0 else 1 + when { + // left > Long.MAX_VALUE + rightValue == Long.MIN_VALUE && !including -> UtFalse + lowerBound != null && lowerBound >= rightValue -> UtTrue + upperBound != null && upperBound < rightValue -> UtFalse + upperBound != null && rightValue - upperBound == 0L -> Eq( + left, + rightValue.toValueWithSort(right.expr.sort).primitiveToSymbolic() + ) + else -> operator(left, right) + } + } + } + + /** + * Simplify expression [Lt](left, right) or [Le](left, right), where [right] operand is integral literal, + * using information about lower and upper bounds of [left] operand from [lts] and [gts] maps. + * + * @param including - if true than simplified expression is Le(left, right), else Lt(left, right) + * @return + * [UtTrue], if upper bound of [left] is less than [right].value. + * + * [UtFalse], if lower bound of [left] is greater than [right].value. + * + * [Eq](left, right), if interval [lower bound of [left], [right].value] has length = 1. + * + * [Lt]|[Le](left, right), otherwise, depending on value of [including] parameter. + */ + private fun simplifyLess(left: PrimitiveValue, right: PrimitiveValue, including: Boolean): UtExpression { + val leftExpr = if (left.expr is UtAddrExpression) left.expr.internal else left.expr + val lowerBound = gts[leftExpr] + val upperBound = lts[leftExpr] + val operator = if (including) Le else Lt + + return if (upperBound == null && lowerBound == null) { + operator(left, right) + } else { + val rightValue = right.expr.toLong() - if (including) 0 else 1 + when { + // left < Long.MIN_VALUE + rightValue == Long.MAX_VALUE && !including -> UtFalse + upperBound != null && upperBound <= rightValue -> UtTrue + lowerBound != null && lowerBound > rightValue -> UtFalse + lowerBound != null && rightValue - lowerBound == 0L -> Eq( + left, + rightValue.toValueWithSort(right.expr.sort).primitiveToSymbolic() + ) + else -> operator(left, right) + } + } + } + + + override fun visit(expr: UtBoolOpExpression): UtExpression = + applySimplification(expr) { + val left = expr.left.expr.accept(this) + val right = expr.right.expr.accept(this) + val leftPrimitive = left.toPrimitiveValue(expr.left.type) + val rightPrimitive = right.toPrimitiveValue(expr.right.type) + when (expr.operator) { + Eq -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqConcrete + // Eq(Integral a, Integral b) ---> a == b + left.isIntegralLiteral && right.isIntegralLiteral -> evalIntegralLiterals(left, right, Long::equals) + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqTrue + // Eq(a, true) ---> a + left is UtBoolExpression && right.isIntegralLiteral && right.toLong() == UtLongTrue -> left + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqFalse + // Eq(a, false) ---> not a + left is UtBoolExpression && right.isIntegralLiteral && right.toLong() == UtLongFalse -> + NotBoolExpression(left).accept(this) + // Eq(Op(_, Integral _), Integral _) + right.isIntegralLiteral && left is UtOpExpression && left.right.expr.isIntegralLiteral -> when (left.operator) { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqAddOpenLiteral + // Eq(Add(a, Integral b), Integral c) ---> Eq(a, Integral (c - b)) + Add -> Eq( + left.left, + evalIntegralLiterals( + right, + left.right.expr, + maxSort(expr.right, left.right), + Long::minus + ).toPrimitiveValue(maxType(expr.right.type, left.right.type)) + ) + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqXorOpenLiteral + // Eq(Xor(a, Integral b), Integral c) ---> Eq(a, Integral (c `xor` b)) + Xor -> Eq( + left.left, + evalIntegralLiterals(right, left.right.expr, maxSort(expr.right, left.right), Long::xor) + .toPrimitiveValue(maxType(expr.right.type, left.right.type)) + ) + else -> Eq(leftPrimitive, rightPrimitive) + } + /** + * @see simplifyEq + */ + right.isIntegralLiteral -> simplifyEq(leftPrimitive, rightPrimitive) + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqConcrete + // Eq(Fp a, Fp b) ---> a == b + left is UtFpLiteral && right is UtFpLiteral -> mkBool(left.value == right.value) + + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqNaN + // Eq(a, Fp NaN) ---> False + left is UtFpLiteral && left.value.toDouble().isNaN() -> UtFalse + right is UtFpLiteral && right.value.toDouble().isNaN() -> UtFalse + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqEqual + // Eq(a a) && a.sort !is FPSort -> true + left == right && expr.left.type !is FloatType && expr.left.type !is DoubleType -> UtTrue + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-eqLiteralToRight + // Eq(a@(Concrete _), b) ---> Eq(b, a) + left.isConcrete -> Eq( + rightPrimitive, + leftPrimitive + ).accept(this) + else -> Eq(leftPrimitive, rightPrimitive) + } + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-neToNotEq + // Ne -> not Eq + Ne -> NotBoolExpression( + Eq( + leftPrimitive, + rightPrimitive + ) + ).accept(this) + Le -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-leConcrete + // Le(Integral a, Integral b) ---> a <= b + left.isIntegralLiteral && right.isIntegralLiteral -> { + evalIntegralLiterals(left, right) { a, b -> a <= b } + } + /** + * @see simplifyLess + * @see simplifyGreater + */ + right.isIntegralLiteral -> simplifyLess(leftPrimitive, rightPrimitive, true) + left.isIntegralLiteral -> simplifyGreater(rightPrimitive, leftPrimitive, true) + else -> Le(leftPrimitive, rightPrimitive) + } + Lt -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-ltConcrete + // Lt(Integral a, Integral b) ---> a < b + left.isIntegralLiteral && right.isIntegralLiteral -> evalIntegralLiterals(left, right) { a, b -> + a < b + } + /** + * @see simplifyLess + * @see simplifyGreater + */ + right.isIntegralLiteral -> simplifyLess(leftPrimitive, rightPrimitive, false) + left.isIntegralLiteral -> simplifyGreater(rightPrimitive, leftPrimitive, false) + else -> Lt(leftPrimitive, rightPrimitive) + } + Ge -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-geConcrete + // Ge(Integral a, Integral b) ---> a >= b + left.isIntegralLiteral && right.isIntegralLiteral -> evalIntegralLiterals(left, right) { a, b -> + a >= b + } + /** + * @see simplifyLess + * @see simplifyGreater + */ + right.isIntegralLiteral -> simplifyGreater(leftPrimitive, rightPrimitive, true) + left.isIntegralLiteral -> simplifyLess(rightPrimitive, leftPrimitive, true) + else -> Ge(leftPrimitive, rightPrimitive) + } + Gt -> when { + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-gtConcrete + // Gt(Integral a, Integral b) ---> a > b + left.isIntegralLiteral && right.isIntegralLiteral -> evalIntegralLiterals(left, right) { a, b -> + a > b + } + /** + * @see simplifyLess + * @see simplifyGreater + */ + right.isIntegralLiteral -> simplifyGreater(leftPrimitive, rightPrimitive, false) + left.isIntegralLiteral -> simplifyLess(rightPrimitive, leftPrimitive, false) + else -> Gt(leftPrimitive, rightPrimitive) + } + } + } + + private fun maxType(left: Type, right: Type): Type = when { + left is LongType -> left + right is LongType -> right + else -> IntType.v() + } + + override fun visit(expr: UtIsExpression): UtExpression = applySimplification(expr, false) { + UtIsExpression(expr.addr.accept(this) as UtAddrExpression, expr.typeStorage, expr.numberOfTypes) + } + + override fun visit(expr: UtGenericExpression): UtExpression = applySimplification(expr, false) { + UtGenericExpression(expr.addr.accept(this) as UtAddrExpression, expr.types, expr.numberOfTypes) + } + + override fun visit(expr: UtIsGenericTypeExpression): UtExpression = applySimplification(expr, false) { + UtIsGenericTypeExpression( + expr.addr.accept(this) as UtAddrExpression, + expr.baseAddr.accept(this) as UtAddrExpression, + expr.parameterTypeIndex + ) + } + + override fun visit(expr: UtEqGenericTypeParametersExpression): UtExpression = + applySimplification(expr, false) { + UtEqGenericTypeParametersExpression( + expr.firstAddr.accept(this) as UtAddrExpression, + expr.secondAddr.accept(this) as UtAddrExpression, + expr.indexMapping + ) + } + + override fun visit(expr: UtInstanceOfExpression): UtExpression = applySimplification(expr, false) { + val simplifiedHard = (expr.constraint.accept(this) as UtBoolExpression).asHardConstraint() + UtInstanceOfExpression(expr.symbolicStateUpdate.copy(hardConstraints = simplifiedHard)) + } + + // CONFLUENCE:UtBot+Expression+Optimizations#UtBotExpressionOptimizations-ite + // ite(true, then, _) ---> then + // ite(false, _, else) ---> else + override fun visit(expr: UtIteExpression): UtExpression = + applySimplification(expr) { + when (val condition = expr.condition.accept(this) as UtBoolExpression) { + UtTrue -> expr.thenExpr.accept(this) + UtFalse -> expr.elseExpr.accept(this) + else -> UtIteExpression(condition, expr.thenExpr.accept(this), expr.elseExpr.accept(this)) + } + } + + override fun visit(expr: UtMkTermArrayExpression): UtExpression = applySimplification(expr, false) + + override fun visit(expr: UtConstArrayExpression): UtExpression = + applySimplification(expr) { + UtConstArrayExpression(expr.constValue.accept(this), expr.sort) + } + + override fun visit(expr: UtArrayInsert): UtExpression = applySimplification(expr, false) { + UtArrayInsert( + expr.arrayExpression.accept(this), + expr.index.expr.accept(this).toIntValue(), + expr.element.accept(this) + ) + } + + override fun visit(expr: UtArrayInsertRange): UtExpression = applySimplification(expr, false) { + UtArrayInsertRange( + expr.arrayExpression.accept(this), + expr.index.expr.accept(this).toIntValue(), + expr.elements.accept(this), + expr.from.expr.accept(this).toIntValue(), + expr.length.expr.accept(this).toIntValue() + ) + } + + override fun visit(expr: UtArrayRemove): UtExpression = applySimplification(expr, false) { + UtArrayRemove( + expr.arrayExpression.accept(this), + expr.index.expr.accept(this).toIntValue() + ) + } + + + override fun visit(expr: UtArrayRemoveRange): UtExpression = applySimplification(expr, false) { + UtArrayRemoveRange( + expr.arrayExpression.accept(this), + expr.index.expr.accept(this).toIntValue(), + expr.length.expr.accept(this).toIntValue() + ) + + } + + override fun visit(expr: UtArraySetRange): UtExpression = applySimplification(expr, false) { + UtArraySetRange( + expr.arrayExpression.accept(this), + expr.index.expr.accept(this).toIntValue(), + expr.elements.accept(this), + expr.from.expr.accept(this).toIntValue(), + expr.length.expr.accept(this).toIntValue() + ) + } + + override fun visit(expr: UtArrayShiftIndexes): UtExpression = applySimplification(expr, false) { + UtArrayShiftIndexes( + expr.arrayExpression.accept(this), + expr.offset.expr.accept(this).toIntValue() + ) + } + + override fun visit(expr: UtArrayApplyForAll): UtExpression = applySimplification(expr, false) { + UtArrayApplyForAll( + expr.arrayExpression.accept(this), + expr.constraint + ) + } +} + +private val arrayExpressionAxiomInstantiationCache = + IdentityHashMap() + +private val arrayExpressionAxiomIndex = AtomicInteger(0) + +private fun instantiateArrayAsNewConst(arrayExpression: UtExtendedArrayExpression) = + arrayExpressionAxiomInstantiationCache.getOrPut(arrayExpression) { + val suffix = when (arrayExpression) { + is UtArrayInsert -> "Insert" + is UtArrayInsertRange -> "InsertRange" + is UtArrayRemove -> "Remove" + is UtArrayRemoveRange -> "RemoveRange" + is UtArraySetRange -> "SetRange" + is UtArrayShiftIndexes -> "ShiftIndexes" + is UtArrayApplyForAll -> error("UtArrayApplyForAll cannot be instantiated as new const array") + } + UtMkArrayExpression( + "_array$suffix${arrayExpressionAxiomIndex.getAndIncrement()}", + arrayExpression.sort + ) + } + +/** + * Visitor that applies the same simplifications as [Simplificator] and instantiate axioms for extended array theory. + * + * @see UtExtendedArrayExpression + */ +class AxiomInstantiationSimplificator( + eqs: Map = emptyMap(), + lts: Map = emptyMap(), + gts: Map = emptyMap() +) : Simplificator(eqs, lts, gts) { + private val instantiatedAxiomExpressions = mutableListOf() + + /** + * Select(UtArrayInsert(a, v, i), j) is equivalent to ITE(i = j, v, Select(a, ITE(j < i, j, j - 1)) + */ + override fun visit(expr: UtArrayInsert): UtExpression { + val arrayInstance = instantiateArrayAsNewConst(expr) + val index = expr.index.expr.accept(this).toPrimitiveValue(expr.index.type) + val element = expr.element.accept(this) + val selectedIndex = selectIndexStack.last() + val pushedIndex = UtIteExpression( + Lt(selectedIndex, index), + selectedIndex.expr, + Add(selectedIndex, (-1).toPrimitiveValue()) + ) + val arrayExpression = withNewSelect(pushedIndex) { expr.arrayExpression.accept(this) } + + instantiatedAxiomExpressions += UtEqExpression( + UtIteExpression( + Eq(selectedIndex, index), + element, + arrayExpression.select(pushedIndex), + ).accept(this), arrayInstance.select(selectedIndex.expr) + ) + return arrayInstance + } + + /** + * Select(UtArrayInsertRange(a, i, b, from, length), j) is equivalent to + * ITE(j >= i && j < i + length, Select(b, j - i + from), Select(a, ITE(j < i, j, j - length) + */ + override fun visit(expr: UtArrayInsertRange): UtExpression { + val arrayInstance = instantiateArrayAsNewConst(expr) + val index = expr.index.expr.accept(this).toPrimitiveValue(expr.index.type) + val from = expr.from.expr.accept(this).toPrimitiveValue(expr.index.type) + val length = expr.length.expr.accept(this).toPrimitiveValue(expr.length.type) + val selectedIndex = selectIndexStack.last() + val pushedArrayInstanceIndex = + UtIteExpression(Lt(selectedIndex, index), selectedIndex.expr, Sub(selectedIndex, length)) + val arrayExpression = withNewSelect(pushedArrayInstanceIndex) { expr.arrayExpression.accept(this) } + val pushedElementsIndex = Add(Sub(selectedIndex, index).toIntValue(), from) + val elements = withNewSelect(pushedElementsIndex) { expr.elements.accept(this) } + instantiatedAxiomExpressions += UtEqExpression( + UtIteExpression( + mkAnd(Ge(selectedIndex, index), Lt(selectedIndex, Add(index, length).toIntValue())), + elements.select(pushedElementsIndex), + arrayExpression.select(pushedArrayInstanceIndex), + ).accept(this), arrayInstance.select(selectedIndex.expr) + ) + return arrayInstance + } + + /** + * Select(UtArrayRemove(a, i), j) is equivalent to Select(a, ITE(j < i, j, j + 1)) + */ + override fun visit(expr: UtArrayRemove): UtExpression { + val arrayInstance = instantiateArrayAsNewConst(expr) + val index = expr.index.expr.accept(this).toPrimitiveValue(expr.index.type) + val selectedIndex = selectIndexStack.last() + val pushedIndex = UtIteExpression( + Lt(selectedIndex, index), + selectedIndex.expr, + Add(selectedIndex, 1.toPrimitiveValue()) + ) + val arrayExpression = withNewSelect(pushedIndex) { expr.arrayExpression.accept(this) } + + instantiatedAxiomExpressions += UtEqExpression( + arrayExpression.select(pushedIndex), + arrayInstance.select(selectedIndex.expr) + ) + return arrayInstance + } + + /** + * Select(UtArrayRemoveRange(a, i, length), j) is equivalent to Select(a, ITE(j < i, j, j + length)) + */ + override fun visit(expr: UtArrayRemoveRange): UtExpression { + val arrayInstance = instantiateArrayAsNewConst(expr) + val index = expr.index.expr.accept(this).toPrimitiveValue(expr.index.type) + val length = expr.length.expr.accept(this).toPrimitiveValue(expr.length.type) + val selectedIndex = selectIndexStack.last() + val pushedIndex = UtIteExpression( + Lt(selectedIndex, index), + selectedIndex.expr, + Add(selectedIndex, length) + ) + val arrayExpression = withNewSelect(pushedIndex) { expr.arrayExpression.accept(this) } + + instantiatedAxiomExpressions += UtEqExpression( + arrayExpression.select(pushedIndex), + arrayInstance.select(selectedIndex.expr) + ) + return arrayInstance + } + + /** + * Select(UtSetRange(a, i, b, from, length), j) is equivalent to + * ITE(j >= i && j < i + length, Select(b, j - i + from), Select(a, i)) + */ + override fun visit(expr: UtArraySetRange): UtExpression { + val arrayInstance = instantiateArrayAsNewConst(expr) + val index = expr.index.expr.accept(this).toPrimitiveValue(expr.index.type) + val from = expr.from.expr.accept(this).toPrimitiveValue(expr.index.type) + val length = expr.length.expr.accept(this).toPrimitiveValue(expr.length.type) + val selectedIndex = selectIndexStack.last() + val arrayExpression = expr.arrayExpression.accept(this) + val pushedIndex = Add(Sub(selectedIndex, index).toIntValue(), from) + val elements = withNewSelect(pushedIndex) { expr.elements.accept(this) } + instantiatedAxiomExpressions += UtEqExpression( + UtIteExpression( + mkAnd(Ge(selectedIndex, index), Lt(selectedIndex, Add(index, length).toIntValue())), + elements.select(pushedIndex), + arrayExpression.select(selectedIndex.expr) + ).accept(this), arrayInstance.select(selectedIndex.expr) + ) + return arrayInstance + } + + /** + * Select(UtShiftIndexes(a, offset), j) is equivalent to Select(a, j - offset) + */ + override fun visit(expr: UtArrayShiftIndexes): UtExpression { + val arrayInstance = instantiateArrayAsNewConst(expr) + val offset = expr.offset.expr.accept(this).toIntValue() + val selectedIndex = selectIndexStack.last() + val pushedIndex = Sub(selectedIndex, offset) + val arrayExpression = withNewSelect(pushedIndex) { expr.arrayExpression.accept(this) } + + instantiatedAxiomExpressions += UtEqExpression( + arrayExpression.select(pushedIndex), + arrayInstance.select(selectedIndex.expr) + ) + return arrayInstance + } + + /** + * instantiate expr.constraint on selecting from UtArrayApplyForAll + */ + override fun visit(expr: UtArrayApplyForAll): UtExpression { + val selectedIndex = selectIndexStack.last() + val arrayExpression = expr.arrayExpression.accept(this) + val constraint = expr.constraint(arrayExpression, selectedIndex) + instantiatedAxiomExpressions += constraint + return arrayExpression + } + + val instantiatedArrayAxioms: List + get() = instantiatedAxiomExpressions +} + +private const val UtLongTrue = 1L +private const val UtLongFalse = 0L diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/StringExpressions.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/StringExpressions.kt deleted file mode 100644 index a06de568a3..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/StringExpressions.kt +++ /dev/null @@ -1,422 +0,0 @@ -package org.utbot.engine.pc - -import org.utbot.common.WorkaroundReason.HACK -import org.utbot.common.workaround -import org.utbot.engine.PrimitiveValue -import java.util.Objects - -sealed class UtStringExpression : UtExpression(UtSeqSort) - -data class UtStringConst(val name: String) : UtStringExpression() { - - override val hashCode = Objects.hash(name) - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(String$sort $name)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtStringConst - - if (name != other.name) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtConcatExpression(val parts: List) : UtStringExpression() { - override val hashCode = parts.hashCode() - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.concat ${parts.joinToString()})" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtConcatExpression - - if (parts != other.parts) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtConvertToString(val expression: UtExpression) : UtStringExpression() { - init { - require(expression.sort is UtBvSort) - } - - override val hashCode = expression.hashCode() - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.tostr $expression)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtConvertToString - - if (expression != other.expression) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtStringToInt(val expression: UtExpression, override val sort: UtBvSort) : UtBvExpression(sort) { - init { - require(expression.sort is UtSeqSort) - } - - override val hashCode = Objects.hash(expression, sort) - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.toint $expression)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtStringToInt - - if (expression != other.expression) return false - if (sort != other.sort) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtStringLength(val string: UtExpression) : UtBvExpression(UtIntSort) { - override val hashCode = string.hashCode() - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.len $string)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtStringLength - - if (string != other.string) return false - - return true - } - - override fun hashCode() = hashCode -} - -/** - * Creates constraint to get rid of negative string length (z3 bug?) - */ -data class UtStringPositiveLength(val string: UtExpression) : UtBoolExpression() { - override val hashCode = string.hashCode() - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(positive.str.len $string)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtStringPositiveLength - - if (string != other.string) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtStringCharAt( - val string: UtExpression, - val index: UtExpression -) : UtExpression(UtCharSort) { - override val hashCode = Objects.hash(string, index) - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.at $string $index)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtStringCharAt - - if (string != other.string) return false - if (index != other.index) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtStringEq(val left: UtExpression, val right: UtExpression) : UtBoolExpression() { - override val hashCode = Objects.hash(left, right) - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.eq $left $right)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtStringEq - - if (left != other.left) return false - if (right != other.right) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtSubstringExpression( - val string: UtExpression, - val beginIndex: UtExpression, - val length: UtExpression -) : UtStringExpression() { - override val hashCode = Objects.hash(string, beginIndex, length) - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.substr $string $beginIndex $length)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtSubstringExpression - - if (string != other.string) return false - if (beginIndex != other.beginIndex) return false - if (length != other.length) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtReplaceExpression( - val string: UtExpression, - val regex: UtExpression, - val replacement: UtExpression -) : UtStringExpression() { - override val hashCode = Objects.hash(string, regex, replacement) - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.replace $string $regex $replacement)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtReplaceExpression - - if (string != other.string) return false - if (regex != other.regex) return false - if (replacement != other.replacement) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtStartsWithExpression( - val string: UtExpression, - val prefix: UtExpression -) : UtBoolExpression() { - override val hashCode = Objects.hash(string, prefix) - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.prefixof $string $prefix)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtStartsWithExpression - - if (string != other.string) return false - if (prefix != other.prefix) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtEndsWithExpression( - val string: UtExpression, - val suffix: UtExpression -) : UtBoolExpression() { - override val hashCode = Objects.hash(string, suffix) - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.suffixof $string $suffix)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtEndsWithExpression - - if (string != other.string) return false - if (suffix != other.suffix) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtIndexOfExpression( - val string: UtExpression, - val substring: UtExpression -) : UtBoolExpression() { - override val hashCode = Objects.hash(string, substring) - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.indexof $string $substring)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtIndexOfExpression - - if (string != other.string) return false - if (substring != other.substring) return false - - return true - } - - override fun hashCode() = hashCode - -} - -data class UtContainsExpression( - val string: UtExpression, - val substring: UtExpression -) : UtBoolExpression() { - override val hashCode = Objects.hash(string, substring) - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.contains $string $substring)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtContainsExpression - - if (string != other.string) return false - if (substring != other.substring) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtToStringExpression( - val isNull: UtBoolExpression, - val notNullExpr: UtExpression -) : UtStringExpression() { - override val hashCode = Objects.hash(isNull, notNullExpr) - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(ite $isNull 'null' $notNullExpr)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtToStringExpression - - if (isNull != other.isNull) return false - if (notNullExpr != other.notNullExpr) return false - - return true - } - - override fun hashCode() = hashCode -} - -data class UtArrayToString( - val arrayExpression: UtExpression, - val offset: PrimitiveValue, - val length: PrimitiveValue -) : UtStringExpression() { - override val hashCode = Objects.hash(arrayExpression, offset) - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = "(str.array_to_str $arrayExpression, offset = $offset)" - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtArrayToString - - if (arrayExpression == other.arrayExpression) return false - if (offset == other.offset) return false - - return true - } - - override fun hashCode() = hashCode -} - -// String literal (not a String Java object!) -data class UtSeqLiteral(val value: String) : UtExpression(UtSeqSort) { - override val hashCode = value.hashCode() - - override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) - - override fun toString() = value - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as UtSeqLiteral - - if (value != other.value) return false - - return true - } - - override fun hashCode() = hashCode -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpression.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpression.kt index 4d4cefd8b2..ebd6bcdec5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpression.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpression.kt @@ -27,7 +27,6 @@ val UtExpression.isConcrete: Boolean get() = when (this) { is UtBvLiteral -> true is UtFpLiteral -> true - is UtSeqLiteral -> true is UtBoolLiteral -> true is UtAddrExpression -> internal.isConcrete else -> false @@ -36,7 +35,6 @@ val UtExpression.isConcrete: Boolean fun UtExpression.toConcrete(): Any = when (this) { is UtBvLiteral -> this.value is UtFpLiteral -> this.value - is UtSeqLiteral -> this.value UtTrue -> true UtFalse -> false is UtAddrExpression -> internal.toConcrete() @@ -243,7 +241,7 @@ data class UtArraySelectExpression(val arrayExpression: UtExpression, val index: } /** - * Uses in [org.utbot.engine.UtBotSymbolicEngine.classCastExceptionCheck]. + * Uses in [org.utbot.engine.Traverser.classCastExceptionCheck]. * Returns the most nested index in the [UtArraySelectExpression]. * * I.e. for (select a i) it returns i, for (select (select (select a i) j) k) it still returns i @@ -457,7 +455,7 @@ class UtIsGenericTypeExpression( override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) override fun toString(): String { - return "(generic-is $addr $baseAddr<\$$parameterTypeIndex>)" + return "(generic-is $addr baseAddr: $baseAddr<\$$parameterTypeIndex>)" } override fun equals(other: Any?): Boolean { @@ -807,6 +805,31 @@ data class UtSubNoOverflowExpression( override fun hashCode() = hashCode } +data class UtMulNoOverflowExpression( + val left: UtExpression, + val right: UtExpression, +) : UtBoolExpression() { + override val hashCode = Objects.hash(left, right) + + override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) + + override fun toString() = "(mulNoOverflow $left $right)" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UtMulNoOverflowExpression + + if (left != other.left) return false + if (right != other.right) return false + + return true + } + + override fun hashCode() = hashCode +} + data class UtNegExpression(val variable: PrimitiveValue) : UtExpression(alignSort(variable.type.toSort())) { override val hashCode = variable.hashCode @@ -828,6 +851,27 @@ data class UtNegExpression(val variable: PrimitiveValue) : UtExpression(alignSor override fun hashCode() = hashCode } +data class UtBvNotExpression(val variable: PrimitiveValue) : UtExpression(alignSort(variable.type.toSort())) { + override val hashCode = variable.hashCode + + override fun accept(visitor: UtExpressionVisitor): TResult = visitor.visit(this) + + override fun toString() = "(bvNot ${variable.expr})" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as UtBvNotExpression + + if (variable != other.variable) return false + + return true + } + + override fun hashCode() = hashCode +} + /** * Implements Unary Numeric Promotion. * Converts byte, short and char sort to int sort. @@ -934,7 +978,7 @@ data class UtMkTermArrayExpression(val array: UtArrayExpressionBase, val default override fun hashCode() = hashCode } -data class Z3Variable(val type: Type, val expr: Expr) { +data class Z3Variable(val type: Type, val expr: Expr<*>) { private val hashCode = Objects.hash(type, expr) override fun equals(other: Any?): Boolean { @@ -952,6 +996,6 @@ data class Z3Variable(val type: Type, val expr: Expr) { override fun hashCode() = hashCode } -fun Expr.z3Variable(type: Type) = Z3Variable(type, this) +fun Expr<*>.z3Variable(type: Type) = Z3Variable(type, this) fun UtExpression.isInteger() = this.sort is UtBvSort \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpressionVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpressionVisitor.kt index 760633ff37..4f22095860 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpressionVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtExpressionVisitor.kt @@ -19,6 +19,7 @@ interface UtExpressionVisitor { fun visit(expr: UtOrBoolExpression): TResult fun visit(expr: UtAndBoolExpression): TResult fun visit(expr: UtNegExpression): TResult + fun visit(expr: UtBvNotExpression): TResult fun visit(expr: UtCastExpression): TResult fun visit(expr: UtBoolOpExpression): TResult fun visit(expr: UtIsExpression): TResult @@ -29,25 +30,6 @@ interface UtExpressionVisitor { fun visit(expr: UtIteExpression): TResult fun visit(expr: UtMkTermArrayExpression): TResult - // UtString expressions - fun visit(expr: UtStringConst): TResult - fun visit(expr: UtConcatExpression): TResult - fun visit(expr: UtConvertToString): TResult - fun visit(expr: UtStringToInt): TResult - fun visit(expr: UtStringLength): TResult - fun visit(expr: UtStringPositiveLength): TResult - fun visit(expr: UtStringCharAt): TResult - fun visit(expr: UtStringEq): TResult - fun visit(expr: UtSubstringExpression): TResult - fun visit(expr: UtReplaceExpression): TResult - fun visit(expr: UtStartsWithExpression): TResult - fun visit(expr: UtEndsWithExpression): TResult - fun visit(expr: UtIndexOfExpression): TResult - fun visit(expr: UtContainsExpression): TResult - fun visit(expr: UtToStringExpression): TResult - fun visit(expr: UtSeqLiteral): TResult - fun visit(expr: UtArrayToString): TResult - // UtArray expressions from extended array theory fun visit(expr: UtArrayInsert): TResult fun visit(expr: UtArrayInsertRange): TResult @@ -56,9 +38,9 @@ interface UtExpressionVisitor { fun visit(expr: UtArraySetRange): TResult fun visit(expr: UtArrayShiftIndexes): TResult fun visit(expr: UtArrayApplyForAll): TResult - fun visit(expr: UtStringToArray): TResult // Add and Sub with overflow detection fun visit(expr: UtAddNoOverflowExpression): TResult fun visit(expr: UtSubNoOverflowExpression): TResult + fun visit(expr: UtMulNoOverflowExpression): TResult } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSolver.kt index d8dd4d0682..7d98030f0c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSolver.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSolver.kt @@ -3,12 +3,12 @@ package org.utbot.engine.pc import org.utbot.analytics.IncrementalData import org.utbot.analytics.Predictors import org.utbot.analytics.learnOn -import org.utbot.common.bracket +import org.utbot.common.measureTime import org.utbot.common.md5 import org.utbot.common.trace import org.utbot.engine.Eq import org.utbot.engine.PrimitiveValue -import org.utbot.engine.TypeRegistry +import org.utbot.engine.types.TypeRegistry import org.utbot.engine.pc.UtSolverStatusKind.SAT import org.utbot.engine.pc.UtSolverStatusKind.UNKNOWN import org.utbot.engine.pc.UtSolverStatusKind.UNSAT @@ -16,7 +16,6 @@ import org.utbot.engine.prettify import org.utbot.engine.symbolic.Assumption import org.utbot.engine.symbolic.HardConstraint import org.utbot.engine.symbolic.SoftConstraint -import org.utbot.engine.prettify import org.utbot.engine.toIntValue import org.utbot.engine.z3.Z3Initializer import org.utbot.framework.UtSettings @@ -31,6 +30,8 @@ import com.microsoft.z3.Status.UNSATISFIABLE import kotlinx.collections.immutable.PersistentSet import kotlinx.collections.immutable.persistentHashSetOf import mu.KotlinLogging +import org.utbot.engine.symbolic.asAssumption +import org.utbot.engine.symbolic.emptyAssumption import soot.ByteType import soot.CharType import soot.IntType @@ -95,8 +96,6 @@ fun UtExpression.select(outerIndex: UtExpression, nestedIndex: UtExpression) = fun UtExpression.store(index: UtExpression, elem: UtExpression) = UtArrayMultiStoreExpression(this, index, elem) -fun mkString(value: String): UtStringConst = UtStringConst(value) - fun PrimitiveValue.align(): PrimitiveValue = when (type) { is ByteType, is ShortType, is CharType -> UtCastExpression(this, IntType.v()).toIntValue() else -> this @@ -131,7 +130,7 @@ data class UtSolver constructor( // Constraints that should not be added in the solver as hypothesis. // Instead, we use `check` to find out if they are satisfiable. // It is required to have unsat cores with them. - var assumption: Assumption = Assumption(), + var assumption: Assumption = emptyAssumption(), //new constraints for solver (kind of incremental behavior) private var hardConstraintsNotYetAddedToZ3Solver: PersistentSet = persistentHashSetOf(), @@ -152,8 +151,8 @@ data class UtSolver constructor( //protection against solver reusage private var canBeCloned: Boolean = true - val rewriter: RewritingVisitor - get() = constraints.let { if (it is Query) it.rewriter else RewritingVisitor() } + val simplificator: Simplificator + get() = constraints.let { if (it is Query) it.simplificator else Simplificator() } /** * Returns the current status of the constraints. @@ -175,9 +174,9 @@ data class UtSolver constructor( var expectUndefined: Boolean = false - fun add(hard: HardConstraint, soft: SoftConstraint, assumption: Assumption = Assumption()): UtSolver { + fun add(hard: HardConstraint, soft: SoftConstraint, assumption: Assumption): UtSolver { // status can implicitly change here to UNDEFINED or UNSAT - val newConstraints = constraints.with(hard.constraints, soft.constraints) + val newConstraints = constraints.with(hard.constraints, soft.constraints, assumption.constraints) val wantClone = (expectUndefined && newConstraints.status is UtSolverStatusUNDEFINED) || (!expectUndefined && newConstraints.status !is UtSolverStatusUNSAT) @@ -204,7 +203,7 @@ data class UtSolver constructor( copy( constraints = constraintsWithStatus, hardConstraintsNotYetAddedToZ3Solver = newConstraints.hard, - assumption = this.assumption + assumption, + assumption = newConstraints.assumptions.asAssumption(), z3Solver = context.mkSolver().also { it.setParameters(params) }, ) } @@ -223,7 +222,7 @@ data class UtSolver constructor( val translatedAssumes = assumption.constraints.translate() - val statusHolder = logger.trace().bracket("High level check(): ", { it }) { + val statusHolder = logger.trace().measureTime({ "High level check(): " }, { it }) { Predictors.smtIncremental.learnOn(IncrementalData(constraints.hard, hardConstraintsNotYetAddedToZ3Solver)) { hardConstraintsNotYetAddedToZ3Solver.forEach { z3Solver.add(translator.translate(it) as BoolExpr) } @@ -256,7 +255,7 @@ data class UtSolver constructor( val assumptionsInUnsatCore = mutableListOf() while (true) { - val res = logger.trace().bracket("Low level check(): ", { it }) { + val res = logger.trace().measureTime({ "Low level check(): " }, { it }) { val constraintsToCheck = translatedSoft.keys + translatedAssumptions.keys z3Solver.check(*constraintsToCheck.toTypedArray()) } @@ -271,9 +270,12 @@ data class UtSolver constructor( UNSATISFIABLE -> { val unsatCore = z3Solver.unsatCore + val failedSoftConstraints = unsatCore.filter { it in translatedSoft.keys } + val failedAssumptions = unsatCore.filter { it in translatedAssumptions.keys } + // if we don't have any soft constraints and enabled unsat cores // for hard constraints, then calculate it and print the result using the logger - if (translatedSoft.isEmpty() && translatedAssumptions.isEmpty() && UtSettings.enableUnsatCoreCalculationForHardConstraints) { + if (failedSoftConstraints.isEmpty() && failedAssumptions.isEmpty() && UtSettings.enableUnsatCoreCalculationForHardConstraints) { with(context.mkSolver()) { check(*z3Solver.assertions) val constraintsInUnsatCore = this.unsatCore.toList() @@ -287,20 +289,16 @@ data class UtSolver constructor( // an unsat core for hard constraints if (unsatCore.isEmpty()) return UNSAT - val failedSoftConstraints = unsatCore.filter { it in translatedSoft.keys } - if (failedSoftConstraints.isNotEmpty()) { failedSoftConstraints.forEach { translatedSoft.remove(it) } // remove soft constraints first, only then try to remove assumptions continue } - unsatCore - .filter { it in translatedAssumptions.keys } - .forEach { - assumptionsInUnsatCore += translatedAssumptions.getValue(it) - translatedAssumptions.remove(it) - } + failedAssumptions.forEach { + assumptionsInUnsatCore += translatedAssumptions.getValue(it) + translatedAssumptions.remove(it) + } } else -> { logger.debug { "Reason of UNKNOWN: ${z3Solver.reasonUnknown}" } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSolverStatus.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSolverStatus.kt index 0df5e2b37c..f0b6def89e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSolverStatus.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSolverStatus.kt @@ -40,18 +40,14 @@ data class UtSolverStatusUNSAT(override val statusKind: UtSolverStatusKind) : Ut } class UtSolverStatusSAT( - private val translator: Z3TranslatorVisitor, + translator: Z3TranslatorVisitor, z3Solver: Solver ) : UtSolverStatus(SAT) { private val model = z3Solver.model private val evaluator: Z3EvaluatorVisitor = translator.evaluator(model) - //TODO put special markers inside expressionTranslationCache for detecting circular dependencies - fun translate(expression: UtExpression): Expr = - translator.translate(expression) - - fun eval(expression: UtExpression): Expr = evaluator.eval(expression) + fun eval(expression: UtExpression): Expr<*> = evaluator.eval(expression) fun concreteAddr(expression: UtAddrExpression): Address = eval(expression).intValue() @@ -69,7 +65,7 @@ class UtSolverStatusSAT( * - val arrayInterpretationFuncDecl: FuncDecl = mfd.parameters[[0]].funcDecl * - val interpretation: FuncInterp = z3Solver.model.getFuncInterp(arrayInterpretationFuncDecl) */ - internal fun evalArrayDescriptor(mval: Expr, unsigned: Boolean, filter: (Int) -> Boolean): ArrayDescriptor { + internal fun evalArrayDescriptor(mval: Expr<*>, unsigned: Boolean, filter: (Int) -> Boolean): ArrayDescriptor { var next = mval val stores = mutableMapOf() var const: Any? = null @@ -83,7 +79,7 @@ class UtSolverStatusSAT( next = next.args[0] } Z3_OP_UNINTERPRETED -> next = model.eval(next) - Z3_OP_CONST_ARRAY -> const = if (next.args[0] is ArrayExpr) { + Z3_OP_CONST_ARRAY -> const = if (next.args[0] is ArrayExpr<*, *>) { // if we have an array as const value, create a corresponding descriptor for it evalArrayDescriptor(next.args[0], unsigned, filter) } else { diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSort.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSort.kt index c30c20eb12..f5af719fb8 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSort.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/UtSort.kt @@ -1,6 +1,6 @@ package org.utbot.engine.pc -import org.utbot.engine.SeqType +import org.utbot.engine.types.SeqType import com.microsoft.z3.Context import com.microsoft.z3.Sort import java.util.Objects diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Util.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Util.kt new file mode 100644 index 0000000000..ad8f055be7 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Util.kt @@ -0,0 +1,7 @@ +package org.utbot.engine.pc + +import com.microsoft.z3.Expr +import com.microsoft.z3.Sort + +@Suppress("UNCHECKED_CAST") +fun Expr.cast(): Expr = this as Expr diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3EvaluatorVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3EvaluatorVisitor.kt index 1f0e886fd8..5ac6df2e16 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3EvaluatorVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3EvaluatorVisitor.kt @@ -20,38 +20,38 @@ import soot.ByteType import soot.CharType class Z3EvaluatorVisitor(private val model: Model, private val translator: Z3TranslatorVisitor) : - UtExpressionVisitor by translator { + UtExpressionVisitor> by translator { // stack of indexes that are visited in expression in derivation tree. // For example, we call eval(select(select(a, i), j) // On visiting term a, the stack will be equals to [i, j] - private val selectIndexStack = mutableListOf() + private val selectIndexStack = mutableListOf>() - private inline fun withPushedSelectIndex(index: UtExpression, block: () -> Expr): Expr { + private inline fun withPushedSelectIndex(index: UtExpression, block: () -> Expr<*>): Expr<*> { selectIndexStack += eval(index) return block().also { selectIndexStack.removeLast() } } - private inline fun withPoppedSelectIndex(block: () -> Expr): Expr { + private inline fun withPoppedSelectIndex(block: () -> Expr<*>): Expr<*> { val lastIndex = selectIndexStack.removeLast() return block().also { selectIndexStack += lastIndex } } - private fun foldSelectsFromStack(expr: Expr): Expr { + private fun foldSelectsFromStack(expr: Expr<*>): Expr<*> { var result = expr var i = selectIndexStack.size - while (i > 0 && result.sort is ArraySort) { - result = translator.withContext { mkSelect(result as ArrayExpr, selectIndexStack[--i]) } + while (i > 0 && result.sort is ArraySort<*, *>) { + result = translator.withContext { mkSelect(result as ArrayExpr<*, *>, selectIndexStack[--i].cast()) } } return result } - fun eval(expr: UtExpression): Expr { + fun eval(expr: UtExpression): Expr<*> { val translated = if (expr.sort is UtArraySort) { translator.lookUpCache(expr)?.let { foldSelectsFromStack(it) } ?: expr.accept(this) } else { @@ -60,14 +60,14 @@ class Z3EvaluatorVisitor(private val model: Model, private val translator: Z3Tra return model.eval(translated) } - override fun visit(expr: UtArraySelectExpression): Expr = expr.run { + override fun visit(expr: UtArraySelectExpression): Expr<*> = expr.run { // translate arrayExpression here will return evaluation of arrayExpression.select(index) withPushedSelectIndex(index) { eval(arrayExpression) } } - override fun visit(expr: UtConstArrayExpression): Expr = expr.run { + override fun visit(expr: UtConstArrayExpression): Expr<*> = expr.run { // expr.select(index) = constValue for any index if (selectIndexStack.size == 0) { translator.withContext { mkConstArray(sort.indexSort.toZ3Sort(this), eval(constValue)) } @@ -78,17 +78,17 @@ class Z3EvaluatorVisitor(private val model: Model, private val translator: Z3Tra } } - override fun visit(expr: UtMkArrayExpression): Expr = + override fun visit(expr: UtMkArrayExpression): Expr<*> = // mkArray expression can have more than one dimension // so for such case select(select(mkArray(...), i), j)) // indices i and j are currently in the stack and we need to fold them. foldSelectsFromStack(translator.translate(expr)) - override fun visit(expr: UtArrayMultiStoreExpression): Expr = expr.run { + override fun visit(expr: UtArrayMultiStoreExpression): Expr<*> = expr.run { val lastIndex = selectIndexStack.lastOrNull() ?: return translator.withContext { - stores.fold(translator.translate(initial) as ArrayExpr) { acc, (index, item) -> - mkStore(acc, eval(index), eval(item)) + stores.fold(translator.translate(initial) as ArrayExpr<*, *>) { acc, (index, item) -> + mkStore(acc, eval(index).cast(), eval(item).cast()) } } for (store in stores.asReversed()) { @@ -99,12 +99,12 @@ class Z3EvaluatorVisitor(private val model: Model, private val translator: Z3Tra eval(initial) } - override fun visit(expr: UtMkTermArrayExpression): Expr = expr.run { + override fun visit(expr: UtMkTermArrayExpression): Expr<*> = expr.run { // should we make eval(mkTerm) always true?? translator.translate(UtTrue) } - override fun visit(expr: UtArrayInsert): Expr = expr.run { + override fun visit(expr: UtArrayInsert): Expr<*> = expr.run { val lastIndex = selectIndexStack.last().intValue() val index = eval(index.expr).intValue() when { @@ -114,7 +114,7 @@ class Z3EvaluatorVisitor(private val model: Model, private val translator: Z3Tra } } - override fun visit(expr: UtArrayInsertRange): Expr = expr.run { + override fun visit(expr: UtArrayInsertRange): Expr<*> = expr.run { val lastIndex = selectIndexStack.last().intValue() val index = eval(index.expr).intValue() val length = eval(length.expr).intValue() @@ -128,7 +128,7 @@ class Z3EvaluatorVisitor(private val model: Model, private val translator: Z3Tra } } - override fun visit(expr: UtArrayRemove): Expr = expr.run { + override fun visit(expr: UtArrayRemove): Expr<*> = expr.run { val lastIndex = selectIndexStack.last().intValue() val index = eval(index.expr).intValue() if (lastIndex < index) { @@ -138,7 +138,7 @@ class Z3EvaluatorVisitor(private val model: Model, private val translator: Z3Tra } } - override fun visit(expr: UtArrayRemoveRange): Expr = expr.run { + override fun visit(expr: UtArrayRemoveRange): Expr<*> = expr.run { val lastIndex = selectIndexStack.last().intValue() val index = eval(index.expr).intValue() if (lastIndex < index) { @@ -149,7 +149,7 @@ class Z3EvaluatorVisitor(private val model: Model, private val translator: Z3Tra } } - override fun visit(expr: UtArraySetRange): Expr = expr.run { + override fun visit(expr: UtArraySetRange): Expr<*> = expr.run { val lastIndex = selectIndexStack.last().intValue() val index = eval(index.expr).intValue() val length = eval(length.expr).intValue() @@ -162,21 +162,15 @@ class Z3EvaluatorVisitor(private val model: Model, private val translator: Z3Tra } } - override fun visit(expr: UtArrayShiftIndexes): Expr = expr.run { + override fun visit(expr: UtArrayShiftIndexes): Expr<*> = expr.run { val lastIndex = selectIndexStack.last().intValue() val offset = eval(offset.expr).intValue() eval(arrayExpression.select(mkInt(lastIndex - offset))) } - override fun visit(expr: UtStringToArray): Expr = expr.run { - val lastIndex = selectIndexStack.last().intValue() - val offset = eval(offset.expr).intValue() - eval(UtStringCharAt(stringExpression, mkInt(lastIndex + offset))) - } - - override fun visit(expr: UtAddrExpression): Expr = eval(expr.internal) + override fun visit(expr: UtAddrExpression): Expr<*> = eval(expr.internal) - override fun visit(expr: UtOpExpression): Expr = expr.run { + override fun visit(expr: UtOpExpression): Expr<*> = expr.run { val leftResolve = eval(left.expr).z3Variable(left.type) val rightResolve = eval(right.expr).z3Variable(right.type) translator.withContext { @@ -184,160 +178,79 @@ class Z3EvaluatorVisitor(private val model: Model, private val translator: Z3Tra } } - override fun visit(expr: UtEqExpression): Expr = expr.run { + override fun visit(expr: UtEqExpression): Expr<*> = expr.run { translator.withContext { mkEq(eval(left), eval(right)) } } - override fun visit(expr: NotBoolExpression): Expr = + override fun visit(expr: NotBoolExpression): Expr<*> = translator.withContext { mkNot(eval(expr.expr) as BoolExpr) } - override fun visit(expr: UtOrBoolExpression): Expr = expr.run { + override fun visit(expr: UtOrBoolExpression): Expr<*> = expr.run { translator.withContext { mkOr(*expr.exprs.map { eval(it) as BoolExpr }.toTypedArray()) } } - override fun visit(expr: UtAndBoolExpression): Expr = expr.run { + override fun visit(expr: UtAndBoolExpression): Expr<*> = expr.run { translator.withContext { mkAnd(*expr.exprs.map { eval(it) as BoolExpr }.toTypedArray()) } } - override fun visit(expr: UtAddNoOverflowExpression): Expr = expr.run { + override fun visit(expr: UtAddNoOverflowExpression): Expr<*> = expr.run { translator.withContext { mkBVAddNoOverflow(eval(expr.left) as BitVecExpr, eval(expr.right) as BitVecExpr, true) } } - override fun visit(expr: UtSubNoOverflowExpression): Expr = expr.run { + override fun visit(expr: UtSubNoOverflowExpression): Expr<*> = expr.run { translator.withContext { // For some reason mkBVSubNoOverflow does not take "signed" as an argument, yet still works for signed integers mkBVSubNoOverflow(eval(expr.left) as BitVecExpr, eval(expr.right) as BitVecExpr) //, true) } } - override fun visit(expr: UtNegExpression): Expr = expr.run { - translator.withContext { - negate(this, eval(variable.expr).z3Variable(variable.type)) - } - } - - override fun visit(expr: UtCastExpression): Expr = expr.run { - val z3var = eval(variable.expr).z3Variable(variable.type) - translator.withContext { convertVar(z3var, type).expr } - } - - override fun visit(expr: UtBoolOpExpression): Expr = expr.run { - val leftResolve = eval(left.expr).z3Variable(left.type) - val rightResolve = eval(right.expr).z3Variable(right.type) + override fun visit(expr: UtMulNoOverflowExpression): Expr<*> = expr.run { translator.withContext { - operator.delegate(this, leftResolve, rightResolve) - } - } - - override fun visit(expr: UtIsExpression): Expr = translator.translate(expr) - - override fun visit(expr: UtInstanceOfExpression): Expr = - expr.run { eval(expr.constraint) } - - override fun visit(expr: UtIteExpression): Expr = expr.run { - if (eval(condition).value() as Boolean) eval(thenExpr) else eval(elseExpr) - } - - override fun visit(expr: UtConcatExpression): Expr = expr.run { - translator.withContext { mkConcat(*parts.map { eval(it) as SeqExpr }.toTypedArray()) } - } - - override fun visit(expr: UtStringLength): Expr = expr.run { - translator.withContext { - if (string is UtArrayToString) { - eval(string.length.expr) - } else { - mkInt2BV(MAX_STRING_LENGTH_SIZE_BITS, mkLength(eval(string) as SeqExpr)) - } + mkBVMulNoOverflow(eval(expr.left) as BitVecExpr, eval(expr.right) as BitVecExpr , true) } } - override fun visit(expr: UtStringPositiveLength): Expr = expr.run { + override fun visit(expr: UtNegExpression): Expr<*> = expr.run { translator.withContext { - mkGe(mkLength(eval(string) as SeqExpr), mkInt(0)) - } - } - - override fun visit(expr: UtStringCharAt): Expr = expr.run { - translator.withContext { - val charAtExpr = mkSeqNth(eval(string) as SeqExpr, mkBV2Int(eval(index) as BitVecExpr, true)) - val z3var = charAtExpr.z3Variable(ByteType.v()) - convertVar(z3var, CharType.v()).expr - } - } - - override fun visit(expr: UtStringEq): Expr = expr.run { - translator.withContext { - mkEq(eval(left), eval(right)) + negate(this, eval(variable.expr).z3Variable(variable.type)) } } - override fun visit(expr: UtSubstringExpression): Expr = expr.run { + override fun visit(expr: UtBvNotExpression): Expr<*> = expr.run { translator.withContext { - mkExtract( - eval(string) as SeqExpr, - mkBV2Int(eval(beginIndex) as BitVecExpr, true), - mkBV2Int(eval(length) as BitVecExpr, true) - ) + mkBVNot(eval(variable.expr) as BitVecExpr) } } - override fun visit(expr: UtReplaceExpression): Expr = expr.run { - workaround(WorkaroundReason.HACK) { // mkReplace replaces first occasion only - translator.withContext { - mkReplace( - eval(string) as SeqExpr, - eval(regex) as SeqExpr, - eval(replacement) as SeqExpr - ) - } - } - } - - // Attention, prefix is a first argument! - override fun visit(expr: UtStartsWithExpression): Expr = expr.run { - translator.withContext { - mkPrefixOf(eval(prefix) as SeqExpr, eval(string) as SeqExpr) - } + override fun visit(expr: UtCastExpression): Expr<*> = expr.run { + val z3var = eval(variable.expr).z3Variable(variable.type) + translator.withContext { convertVar(z3var, type).expr } } - // Attention, suffix is a first argument! - override fun visit(expr: UtEndsWithExpression): Expr = expr.run { + override fun visit(expr: UtBoolOpExpression): Expr<*> = expr.run { + val leftResolve = eval(left.expr).z3Variable(left.type) + val rightResolve = eval(right.expr).z3Variable(right.type) translator.withContext { - mkSuffixOf(eval(suffix) as SeqExpr, eval(string) as SeqExpr) + operator.delegate(this, leftResolve, rightResolve) } } - override fun visit(expr: UtIndexOfExpression): Expr = expr.run { - val string = eval(string) as SeqExpr - val substring = eval(substring) as SeqExpr - translator.withContext { - mkInt2BV( - MAX_STRING_LENGTH_SIZE_BITS, - mkIndexOf(string, substring, mkInt(0)) - ) - } - } + override fun visit(expr: UtIsExpression): Expr<*> = translator.translate(expr) - override fun visit(expr: UtContainsExpression): Expr = expr.run { - val substring = eval(substring) as SeqExpr - val string = eval(string) as SeqExpr - translator.withContext { - mkGe(mkIndexOf(string, substring, mkInt(0)), mkInt(0)) - } - } + override fun visit(expr: UtInstanceOfExpression): Expr<*> = + expr.run { eval(expr.constraint) } - override fun visit(expr: UtToStringExpression): Expr = expr.run { - if (eval(isNull).value() as Boolean) translator.withContext { mkString("null") } else eval(notNullExpr) + override fun visit(expr: UtIteExpression): Expr<*> = expr.run { + if (eval(condition).value() as Boolean) eval(thenExpr) else eval(elseExpr) } - override fun visit(expr: UtArrayApplyForAll): Expr = expr.run { + override fun visit(expr: UtArrayApplyForAll): Expr<*> = expr.run { eval(expr.arrayExpression.select(mkInt(selectIndexStack.last().intValue()))) } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt index 4f0d7516d4..638ebb1419 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/pc/Z3TranslatorVisitor.kt @@ -1,9 +1,9 @@ package org.utbot.engine.pc +import com.microsoft.z3.* import org.utbot.common.WorkaroundReason import org.utbot.common.workaround -import org.utbot.engine.MAX_STRING_LENGTH_SIZE_BITS -import org.utbot.engine.TypeRegistry +import org.utbot.engine.types.TypeRegistry import org.utbot.engine.TypeStorage import org.utbot.engine.baseType import org.utbot.engine.defaultValue @@ -14,19 +14,11 @@ import org.utbot.engine.z3.convertVar import org.utbot.engine.z3.makeBV import org.utbot.engine.z3.makeFP import org.utbot.engine.z3.negate -import com.microsoft.z3.ArrayExpr -import com.microsoft.z3.BitVecExpr -import com.microsoft.z3.BoolExpr -import com.microsoft.z3.Context -import com.microsoft.z3.Expr -import com.microsoft.z3.FPSort -import com.microsoft.z3.IntExpr -import com.microsoft.z3.Model -import com.microsoft.z3.SeqExpr -import com.microsoft.z3.mkSeqNth +import org.utbot.engine.types.TypeRegistry.Companion.numberOfTypes +import org.utbot.framework.UtSettings +import org.utbot.framework.UtSettings.maxTypeNumberForEnumeration +import org.utbot.framework.UtSettings.useBitVecBasedTypeSystem import java.util.IdentityHashMap -import soot.ByteType -import soot.CharType import soot.PrimType import soot.RefType import soot.Type @@ -35,101 +27,110 @@ import soot.Type open class Z3TranslatorVisitor( private val z3Context: Context, private val typeRegistry: TypeRegistry -) : UtExpressionVisitor { +) : UtExpressionVisitor> { //thread-safe - private val expressionTranslationCache = IdentityHashMap() + private val expressionTranslationCache = IdentityHashMap>() fun evaluator(model: Model): Z3EvaluatorVisitor = Z3EvaluatorVisitor(model, this) /** * Translate [expr] from [UtExpression] to [Expr] */ - fun translate(expr: UtExpression): Expr = + fun translate(expr: UtExpression): Expr<*> = expressionTranslationCache.getOrPut(expr) { expr.accept(this) } - fun lookUpCache(expr: UtExpression): Expr? = expressionTranslationCache[expr] + fun lookUpCache(expr: UtExpression): Expr<*>? = expressionTranslationCache[expr] - fun withContext(block: Context.() -> Expr): Expr { + fun withContext(block: Context.() -> Expr<*>): Expr<*> { return z3Context.block() } - override fun visit(expr: UtArraySelectExpression): Expr = expr.run { - z3Context.mkSelect(translate(arrayExpression) as ArrayExpr, translate(index)) + override fun visit(expr: UtArraySelectExpression): Expr<*> = expr.run { + z3Context.mkSelect(translate(arrayExpression) as ArrayExpr<*, *>, translate(index).cast()) } + /** * Creates a const array filled with a fixed value */ - override fun visit(expr: UtConstArrayExpression): Expr = expr.run { + override fun visit(expr: UtConstArrayExpression): Expr<*> = expr.run { z3Context.mkConstArray(sort.indexSort.toZ3Sort(z3Context), translate(constValue)) } - override fun visit(expr: UtMkArrayExpression): Expr = expr.run { - z3Context.run { mkConst(name, sort.toZ3Sort(this)) } as ArrayExpr + override fun visit(expr: UtMkArrayExpression): Expr<*> = expr.run { + z3Context.run { mkConst(name, sort.toZ3Sort(this)) } as ArrayExpr<*, *> } - override fun visit(expr: UtArrayMultiStoreExpression): Expr = expr.run { - stores.fold(translate(initial) as ArrayExpr) { acc, (index, item) -> - z3Context.mkStore(acc, translate(index), translate(item)) + override fun visit(expr: UtArrayMultiStoreExpression): Expr<*> = expr.run { + stores.fold(translate(initial) as ArrayExpr<*, *>) { acc, (index, item) -> + z3Context.mkStore(acc, translate(index).cast(), translate(item).cast()) } } - override fun visit(expr: UtBvLiteral): Expr = expr.run { z3Context.makeBV(value, size) } + override fun visit(expr: UtBvLiteral): Expr<*> = expr.run { z3Context.makeBV(value, size) } - override fun visit(expr: UtBvConst): Expr = expr.run { z3Context.mkBVConst(name, size) } + override fun visit(expr: UtBvConst): Expr<*> = expr.run { z3Context.mkBVConst(name, size) } - override fun visit(expr: UtAddrExpression): Expr = expr.run { translate(internal) } + override fun visit(expr: UtAddrExpression): Expr<*> = expr.run { translate(internal) } - override fun visit(expr: UtFpLiteral): Expr = + override fun visit(expr: UtFpLiteral): Expr<*> = expr.run { z3Context.makeFP(value, sort.toZ3Sort(z3Context) as FPSort) } - override fun visit(expr: UtFpConst): Expr = expr.run { z3Context.mkConst(name, sort.toZ3Sort(z3Context)) } + override fun visit(expr: UtFpConst): Expr<*> = expr.run { z3Context.mkConst(name, sort.toZ3Sort(z3Context)) } - override fun visit(expr: UtOpExpression): Expr = expr.run { + override fun visit(expr: UtOpExpression): Expr<*> = expr.run { val leftResolve = translate(left.expr).z3Variable(left.type) val rightResolve = translate(right.expr).z3Variable(right.type) operator.delegate(z3Context, leftResolve, rightResolve) } - override fun visit(expr: UtTrue): Expr = expr.run { z3Context.mkBool(true) } + override fun visit(expr: UtTrue): Expr<*> = expr.run { z3Context.mkBool(true) } - override fun visit(expr: UtFalse): Expr = expr.run { z3Context.mkBool(false) } + override fun visit(expr: UtFalse): Expr<*> = expr.run { z3Context.mkBool(false) } - override fun visit(expr: UtEqExpression): Expr = expr.run { z3Context.mkEq(translate(left), translate(right)) } + override fun visit(expr: UtEqExpression): Expr<*> = expr.run { z3Context.mkEq(translate(left), translate(right)) } - override fun visit(expr: UtBoolConst): Expr = expr.run { z3Context.mkBoolConst(name) } + override fun visit(expr: UtBoolConst): Expr<*> = expr.run { z3Context.mkBoolConst(name) } - override fun visit(expr: NotBoolExpression): Expr = + override fun visit(expr: NotBoolExpression): Expr<*> = expr.run { z3Context.mkNot(translate(expr.expr) as BoolExpr) } - override fun visit(expr: UtOrBoolExpression): Expr = expr.run { + override fun visit(expr: UtOrBoolExpression): Expr<*> = expr.run { z3Context.mkOr(*exprs.map { translate(it) as BoolExpr }.toTypedArray()) } - override fun visit(expr: UtAndBoolExpression): Expr = expr.run { + override fun visit(expr: UtAndBoolExpression): Expr<*> = expr.run { z3Context.mkAnd(*exprs.map { translate(it) as BoolExpr }.toTypedArray()) } - override fun visit(expr: UtAddNoOverflowExpression): Expr = expr.run { + override fun visit(expr: UtAddNoOverflowExpression): Expr<*> = expr.run { z3Context.mkBVAddNoOverflow(translate(expr.left) as BitVecExpr, translate(expr.right) as BitVecExpr, true) } - override fun visit(expr: UtSubNoOverflowExpression): Expr = expr.run { + override fun visit(expr: UtSubNoOverflowExpression): Expr<*> = expr.run { // For some reason mkBVSubNoOverflow does not take "signed" as an argument, yet still works for signed integers z3Context.mkBVSubNoOverflow(translate(expr.left) as BitVecExpr, translate(expr.right) as BitVecExpr) // , true) } - override fun visit(expr: UtNegExpression): Expr = expr.run { + override fun visit(expr: UtMulNoOverflowExpression): Expr<*> = expr.run { + z3Context.mkBVMulNoOverflow(translate(expr.left) as BitVecExpr, translate(expr.right) as BitVecExpr , true) + } + + override fun visit(expr: UtNegExpression): Expr<*> = expr.run { negate(z3Context, translate(variable.expr).z3Variable(variable.type)) } - override fun visit(expr: UtCastExpression): Expr = expr.run { + override fun visit(expr: UtBvNotExpression): Expr<*> = expr.run { + z3Context.mkBVNot(translate(variable.expr) as BitVecExpr) + } + + override fun visit(expr: UtCastExpression): Expr<*> = expr.run { val z3var = translate(variable.expr).z3Variable(variable.type) z3Context.convertVar(z3var, type).expr } - override fun visit(expr: UtBoolOpExpression): Expr = expr.run { + override fun visit(expr: UtBoolOpExpression): Expr<*> = expr.run { val leftResolve = translate(left.expr).z3Variable(left.type) val rightResolve = translate(right.expr).z3Variable(right.type) operator.delegate(z3Context, leftResolve, rightResolve) @@ -153,9 +154,9 @@ open class Z3TranslatorVisitor( * @param expr the type expression * @return the type expression translated into z3 assertions * @see UtIsExpression - * @see MAX_TYPE_NUMBER_FOR_ENUMERATION + * @see UtSettings.maxTypeNumberForEnumeration */ - override fun visit(expr: UtIsExpression): Expr = expr.run { + override fun visit(expr: UtIsExpression): Expr<*> = expr.run { val symNumDimensions = translate(typeRegistry.symNumDimensions(addr)) as BitVecExpr val symTypeId = translate(typeRegistry.symTypeId(addr)) as BitVecExpr @@ -164,33 +165,8 @@ open class Z3TranslatorVisitor( // TODO remove it JIRA:1321 val filteredPossibleTypes = workaround(WorkaroundReason.HACK) { typeStorage.filterInappropriateTypes() } - // add constraints for typeId - if (typeStorage.possibleConcreteTypes.size < MAX_TYPE_NUMBER_FOR_ENUMERATION) { - val symType = translate(typeRegistry.symTypeId(addr)) - val possibleBaseTypes = filteredPossibleTypes.map { it.baseType } - - val typeConstraint = z3Context.mkOr( - *possibleBaseTypes - .map { z3Context.mkEq(z3Context.mkBV(typeRegistry.findTypeId(it), Int.SIZE_BITS), symType) } - .toTypedArray() - ) - - constraints += typeConstraint - } else { - val shiftedExpression = z3Context.mkBVSHL( - z3Context.mkZeroExt(numberOfTypes - 1, z3Context.mkBV(1, 1)), - z3Context.mkZeroExt(numberOfTypes - Int.SIZE_BITS, symTypeId) - ) - - val bitVecString = typeRegistry.constructBitVecString(filteredPossibleTypes) - val possibleTypesBitVector = z3Context.mkBV(bitVecString, numberOfTypes) - - val typeConstraint = z3Context.mkEq( - z3Context.mkBVAND(shiftedExpression, z3Context.mkBVNot(possibleTypesBitVector)), - z3Context.mkBV(0, numberOfTypes) - ) - - constraints += typeConstraint + if (filteredPossibleTypes.size > UtSettings.maxNumberOfTypesToEncode) { + return@run z3Context.mkTrue() } val exprBaseType = expr.type.baseType @@ -202,6 +178,8 @@ open class Z3TranslatorVisitor( z3Context.mkEq(symNumDimensions, numDimensions) } + constraints += encodePossibleTypes(symTypeId, filteredPossibleTypes) + z3Context.mkAnd(*constraints.toTypedArray()) } @@ -223,27 +201,26 @@ open class Z3TranslatorVisitor( } - override fun visit(expr: UtGenericExpression): Expr = expr.run { + override fun visit(expr: UtGenericExpression): Expr<*> = expr.run { val constraints = mutableListOf() for (i in types.indices) { val symType = translate(typeRegistry.genericTypeId(addr, i)) + val genericNumDimensions = translate(typeRegistry.genericNumDimensions(addr, i)) as BitVecExpr - if (types[i].leastCommonType.isJavaLangObject()) { - continue - } + val possibleConcreteTypes = types[i].possibleConcreteTypes + val leastCommonType = types[i].leastCommonType - val possibleBaseTypes = types[i].possibleConcreteTypes.map { it.baseType } + val numDimensions = z3Context.mkBV(leastCommonType.numDimensions, Int.SIZE_BITS) - val typeConstraint = z3Context.mkOr( - *possibleBaseTypes.map { - z3Context.mkEq( - z3Context.mkBV(typeRegistry.findTypeId(it), Int.SIZE_BITS), - symType - ) - }.toTypedArray() - ) + if (possibleConcreteTypes.size > UtSettings.maxNumberOfTypesToEncode) continue + + constraints += if (leastCommonType.isJavaLangObject()) { + z3Context.mkBVSGE(genericNumDimensions, numDimensions) + } else { + z3Context.mkEq(genericNumDimensions, numDimensions) + } - constraints += typeConstraint + constraints += encodePossibleTypes(symType, possibleConcreteTypes) } z3Context.mkOr( @@ -252,7 +229,36 @@ open class Z3TranslatorVisitor( ) } - override fun visit(expr: UtIsGenericTypeExpression): Expr = expr.run { + private fun encodePossibleTypes(symType: Expr<*>, possibleConcreteTypes: Collection): BoolExpr { + val possibleBaseTypes = possibleConcreteTypes.map { it.baseType } + + return if (!useBitVecBasedTypeSystem || possibleConcreteTypes.size < maxTypeNumberForEnumeration) { + z3Context.mkOr( + *possibleBaseTypes + .map { + val typeId = typeRegistry.findTypeId(it) + val typeIdBv = z3Context.mkBV(typeId, Int.SIZE_BITS) + z3Context.mkEq(typeIdBv, symType) + } + .toTypedArray() + ) + } else { + val shiftedExpression = z3Context.mkBVSHL( + z3Context.mkZeroExt(numberOfTypes - 1, z3Context.mkBV(1, 1)), + z3Context.mkZeroExt(numberOfTypes - Int.SIZE_BITS, symType as BitVecExpr) + ) + + val bitVecString = typeRegistry.constructBitVecString(possibleBaseTypes) + val possibleTypesBitVector = z3Context.mkBV(bitVecString, numberOfTypes) + + z3Context.mkEq( + z3Context.mkBVAND(shiftedExpression, z3Context.mkBVNot(possibleTypesBitVector)), + z3Context.mkBV(0, numberOfTypes) + ) + } + } + + override fun visit(expr: UtIsGenericTypeExpression): Expr<*> = expr.run { val symType = translate(typeRegistry.symTypeId(addr)) val symNumDimensions = translate(typeRegistry.symNumDimensions(addr)) @@ -274,7 +280,7 @@ open class Z3TranslatorVisitor( z3Context.mkAnd(typeConstraint, dimensionsConstraint) } - override fun visit(expr: UtEqGenericTypeParametersExpression): Expr = expr.run { + override fun visit(expr: UtEqGenericTypeParametersExpression): Expr<*> = expr.run { val constraints = mutableListOf() for ((i, j) in indexMapping) { val firstSymType = translate(typeRegistry.genericTypeId(firstAddr, i)) @@ -288,15 +294,15 @@ open class Z3TranslatorVisitor( z3Context.mkAnd(*constraints.toTypedArray()) } - override fun visit(expr: UtInstanceOfExpression): Expr = + override fun visit(expr: UtInstanceOfExpression): Expr<*> = expr.run { translate(expr.constraint) } - override fun visit(expr: UtIteExpression): Expr = + override fun visit(expr: UtIteExpression): Expr<*> = expr.run { z3Context.mkITE(translate(condition) as BoolExpr, translate(thenExpr), translate(elseExpr)) } - override fun visit(expr: UtMkTermArrayExpression): Expr = expr.run { + override fun visit(expr: UtMkTermArrayExpression): Expr<*> = expr.run { z3Context.run { - val ourArray = translate(expr.array) as ArrayExpr + val ourArray = translate(expr.array) as ArrayExpr<*, *> val arraySort = expr.array.sort val defaultValue = expr.defaultValue ?: arraySort.itemSort.defaultValue val translated = translate(defaultValue) @@ -305,34 +311,6 @@ open class Z3TranslatorVisitor( } } - override fun visit(expr: UtStringConst): Expr = expr.run { z3Context.mkConst(name, sort.toZ3Sort(z3Context)) } - - override fun visit(expr: UtConcatExpression): Expr = - expr.run { z3Context.mkConcat(*parts.map { translate(it) as SeqExpr }.toTypedArray()) } - - override fun visit(expr: UtConvertToString): Expr = expr.run { - when (expression) { - is UtBvLiteral -> z3Context.mkString(expression.value.toString()) - else -> { - val intValue = z3Context.mkBV2Int(translate(expression) as BitVecExpr, true) - z3Context.intToString(intValue) - } - } - } - - override fun visit(expr: UtStringToInt): Expr = expr.run { - val intValue = z3Context.stringToInt(translate(expression)) - z3Context.mkInt2BV(size, intValue) - } - - override fun visit(expr: UtStringLength): Expr = expr.run { - z3Context.mkInt2BV(MAX_STRING_LENGTH_SIZE_BITS, z3Context.mkLength(translate(string) as SeqExpr)) - } - - override fun visit(expr: UtStringPositiveLength): Expr = expr.run { - z3Context.mkGe(z3Context.mkLength(translate(string) as SeqExpr), z3Context.mkInt(0)) - } - private fun Context.mkBV2Int(expr: UtExpression): IntExpr = if (expr is UtBvLiteral) { mkInt(expr.value as Long) @@ -340,83 +318,19 @@ open class Z3TranslatorVisitor( mkBV2Int(translate(expr) as BitVecExpr, true) } + override fun visit(expr: UtArrayInsert): Expr<*> = error("translate of UtArrayInsert expression") - override fun visit(expr: UtStringCharAt): Expr = expr.run { - val charAtExpr = z3Context.mkSeqNth(translate(string) as SeqExpr, z3Context.mkBV2Int(index)) - val z3var = charAtExpr.z3Variable(ByteType.v()) - z3Context.convertVar(z3var, CharType.v()).expr - } - - override fun visit(expr: UtStringEq): Expr = expr.run { z3Context.mkEq(translate(left), translate(right)) } - - override fun visit(expr: UtSubstringExpression): Expr = expr.run { - z3Context.mkExtract( - translate(string) as SeqExpr, - z3Context.mkBV2Int(beginIndex), - z3Context.mkBV2Int(length) - ) - } - - override fun visit(expr: UtReplaceExpression): Expr = expr.run { - workaround(WorkaroundReason.HACK) { // mkReplace replaces first occasion only - z3Context.mkReplace( - translate(string) as SeqExpr, - translate(regex) as SeqExpr, - translate(replacement) as SeqExpr - ) - } - } - - // Attention, prefix is a first argument! - override fun visit(expr: UtStartsWithExpression): Expr = expr.run { - z3Context.mkPrefixOf(translate(prefix) as SeqExpr, translate(string) as SeqExpr) - } - - // Attention, suffix is a first argument! - override fun visit(expr: UtEndsWithExpression): Expr = expr.run { - z3Context.mkSuffixOf(translate(suffix) as SeqExpr, translate(string) as SeqExpr) - } - - override fun visit(expr: UtIndexOfExpression): Expr = expr.run { - z3Context.mkInt2BV( - MAX_STRING_LENGTH_SIZE_BITS, - z3Context.mkIndexOf(translate(string) as SeqExpr, translate(substring) as SeqExpr, z3Context.mkInt(0)) - ) - } - - override fun visit(expr: UtContainsExpression): Expr = expr.run { - z3Context.mkGe( - z3Context.mkIndexOf(translate(string) as SeqExpr, translate(substring) as SeqExpr, z3Context.mkInt(0)), - z3Context.mkInt(0) - ) - } - - override fun visit(expr: UtToStringExpression): Expr = expr.run { - z3Context.mkITE(translate(isNull) as BoolExpr, z3Context.mkString("null"), translate(notNullExpr)) - - } - - override fun visit(expr: UtSeqLiteral): Expr = expr.run { z3Context.mkString(value) } - - companion object { - const val MAX_TYPE_NUMBER_FOR_ENUMERATION = 64 - } - - override fun visit(expr: UtArrayInsert): Expr = error("translate of UtArrayInsert expression") - - override fun visit(expr: UtArrayInsertRange): Expr = error("translate of UtArrayInsertRange expression") + override fun visit(expr: UtArrayInsertRange): Expr<*> = error("translate of UtArrayInsertRange expression") - override fun visit(expr: UtArrayRemove): Expr = error("translate of UtArrayRemove expression") + override fun visit(expr: UtArrayRemove): Expr<*> = error("translate of UtArrayRemove expression") - override fun visit(expr: UtArrayRemoveRange): Expr = error("translate of UtArrayRemoveRange expression") + override fun visit(expr: UtArrayRemoveRange): Expr<*> = error("translate of UtArrayRemoveRange expression") - override fun visit(expr: UtArraySetRange): Expr = error("translate of UtArraySetRange expression") + override fun visit(expr: UtArraySetRange): Expr<*> = error("translate of UtArraySetRange expression") - override fun visit(expr: UtArrayShiftIndexes): Expr = error("translate of UtArrayShiftIndexes expression") + override fun visit(expr: UtArrayShiftIndexes): Expr<*> = error("translate of UtArrayShiftIndexes expression") - override fun visit(expr: UtArrayApplyForAll): Expr = error("translate of UtArrayApplyForAll expression") + override fun visit(expr: UtArrayApplyForAll): Expr<*> = error("translate of UtArrayApplyForAll expression") - override fun visit(expr: UtStringToArray): Expr = error("translate of UtStringToArray expression") - override fun visit(expr: UtArrayToString): Expr = error("translate of UtArrayToString expression") } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/BFSSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/BFSSelector.kt index 6feb62b352..f28d2449c0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/BFSSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/BFSSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.ChoosingStrategy import org.utbot.engine.selectors.strategies.StoppingStrategy diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/BasePathSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/BasePathSelector.kt index 0e400f527a..945d903945 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/BasePathSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/BasePathSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.isPreconditionCheckMethod import org.utbot.engine.pathLogger import org.utbot.engine.pc.UtSolver @@ -80,7 +80,7 @@ abstract class BasePathSelector( pathLogger.trace { "poll next state (lastStatus=${state.solver.lastStatus}): " + state.prettifiedPathLog() } current = null - if (choosingStrategy.shouldDrop(state) || checkUnsatIfFork(state)) { + if (choosingStrategy.shouldDrop(state) || (!UtSettings.disableUnsatChecking && checkUnsatIfFork(state))) { state.close() continue } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/DFSSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/DFSSelector.kt index 9e1eba3c1b..92ad407d12 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/DFSSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/DFSSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.ChoosingStrategy import org.utbot.engine.selectors.strategies.StoppingStrategy diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/InterleavedSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/InterleavedSelector.kt index b46f933d66..401d9094a7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/InterleavedSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/InterleavedSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState /** * Retrieves states from different pathSelectors in rotation. diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/MLSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/MLSelector.kt new file mode 100644 index 0000000000..78f12dceb1 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/MLSelector.kt @@ -0,0 +1,73 @@ +package org.utbot.engine.selectors + +import org.utbot.analytics.EngineAnalyticsContext +import org.utbot.analytics.Predictors +import org.utbot.engine.state.ExecutionState +import org.utbot.engine.InterProceduralUnitGraph +import org.utbot.engine.selectors.nurs.GreedySearch +import org.utbot.engine.selectors.strategies.ChoosingStrategy +import org.utbot.engine.selectors.strategies.GeneratedTestCountingStatistics +import org.utbot.engine.selectors.strategies.StoppingStrategy + +/** + * @see Learch + * + * Calculates reward using neural network, when state is offered, and then peeks state with maximum reward + * + * @see choosingStrategy [ChoosingStrategy] for [GreedySearch] + * + * [GreedySearch] + */ +abstract class MLSelector( + protected val generatedTestCountingStatistics: GeneratedTestCountingStatistics, + choosingStrategy: ChoosingStrategy, + stoppingStrategy: StoppingStrategy, + seed: Int = 42, + graph: InterProceduralUnitGraph +) : GreedySearch(choosingStrategy, stoppingStrategy, seed) { + protected val featureExtractor = EngineAnalyticsContext.featureExtractorFactory(graph) + + override val name: String + get() = "NNRewardGuidedSelector" +} + +/** + * Calculate weight of execution state only when it is offered. It has advantage, because it works faster, + * than with recalculation but disadvantage is that some features of execution state can change. + */ +class MLSelectorWithoutWeightsRecalculation( + generatedTestCountingStatistics: GeneratedTestCountingStatistics, + choosingStrategy: ChoosingStrategy, + stoppingStrategy: StoppingStrategy, + seed: Int = 42, + graph: InterProceduralUnitGraph +) : MLSelector(generatedTestCountingStatistics, choosingStrategy, stoppingStrategy, seed, graph) { + override fun offerImpl(state: ExecutionState) { + super.offerImpl(state) + featureExtractor.extractFeatures(state, generatedTestCountingStatistics.generatedTestsCount) + } + + override val ExecutionState.weight: Double + get() { + reward = reward ?: Predictors.stateRewardPredictor.predict(features) + return reward as Double + } +} + +/** + * Calculate weight of execution state every time when it needed. It works slower, + * than without recalculation but features are always relevant + */ +class MLSelectorWithWeightsRecalculation( + generatedTestCountingStatistics: GeneratedTestCountingStatistics, + choosingStrategy: ChoosingStrategy, + stoppingStrategy: StoppingStrategy, + seed: Int = 42, + graph: InterProceduralUnitGraph +) : MLSelector(generatedTestCountingStatistics, choosingStrategy, stoppingStrategy, seed, graph) { + override val ExecutionState.weight: Double + get() { + featureExtractor.extractFeatures(this, generatedTestCountingStatistics.generatedTestsCount) + return Predictors.stateRewardPredictor.predict(features) + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/MLSelectorFactory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/MLSelectorFactory.kt new file mode 100644 index 0000000000..881fab3f9c --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/MLSelectorFactory.kt @@ -0,0 +1,49 @@ +package org.utbot.engine.selectors + +import org.utbot.engine.InterProceduralUnitGraph +import org.utbot.engine.selectors.strategies.ChoosingStrategy +import org.utbot.engine.selectors.strategies.GeneratedTestCountingStatistics +import org.utbot.engine.selectors.strategies.StoppingStrategy + +/** + * Creates [MLSelector] + */ +interface MLSelectorFactory { + operator fun invoke( + generatedTestCountingStatistics: GeneratedTestCountingStatistics, + choosingStrategy: ChoosingStrategy, + stoppingStrategy: StoppingStrategy, + seed: Int = 42, + graph: InterProceduralUnitGraph + ): MLSelector +} + +/** + * Creates [MLSelectorWithWeightsRecalculation] + */ +class MLSelectorWithRecalculationFactory : MLSelectorFactory { + override fun invoke( + generatedTestCountingStatistics: GeneratedTestCountingStatistics, + choosingStrategy: ChoosingStrategy, + stoppingStrategy: StoppingStrategy, + seed: Int, + graph: InterProceduralUnitGraph + ): MLSelector = MLSelectorWithWeightsRecalculation( + generatedTestCountingStatistics, choosingStrategy, stoppingStrategy, seed, graph + ) +} + +/** + * Creates [MLSelectorWithoutWeightsRecalculation] + */ +class MLSelectorWithoutRecalculationFactory : MLSelectorFactory { + override fun invoke( + generatedTestCountingStatistics: GeneratedTestCountingStatistics, + choosingStrategy: ChoosingStrategy, + stoppingStrategy: StoppingStrategy, + seed: Int, + graph: InterProceduralUnitGraph + ): MLSelector = MLSelectorWithoutWeightsRecalculation( + generatedTestCountingStatistics, choosingStrategy, stoppingStrategy, seed, graph + ) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/NNRewardGuidedSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/NNRewardGuidedSelector.kt deleted file mode 100644 index c4f37c2924..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/NNRewardGuidedSelector.kt +++ /dev/null @@ -1,73 +0,0 @@ -package org.utbot.engine.selectors - -import org.utbot.analytics.EngineAnalyticsContext -import org.utbot.analytics.Predictors -import org.utbot.engine.ExecutionState -import org.utbot.engine.InterProceduralUnitGraph -import org.utbot.engine.selectors.nurs.GreedySearch -import org.utbot.engine.selectors.strategies.ChoosingStrategy -import org.utbot.engine.selectors.strategies.GeneratedTestCountingStatistics -import org.utbot.engine.selectors.strategies.StoppingStrategy - -/** - * @see Learch - * - * Calculates reward using neural network, when state is offered, and then peeks state with maximum reward - * - * @see choosingStrategy [ChossingStrategy] for [GreedySearch] - * - * [GreedySearch] - */ -abstract class NNRewardGuidedSelector( - protected val generatedTestCountingStatistics: GeneratedTestCountingStatistics, - choosingStrategy: ChoosingStrategy, - stoppingStrategy: StoppingStrategy, - seed: Int = 42, - graph: InterProceduralUnitGraph -) : GreedySearch(choosingStrategy, stoppingStrategy, seed) { - protected val featureExtractor = EngineAnalyticsContext.featureExtractorFactory(graph) - - override val name: String - get() = "NNRewardGuidedSelector" -} - -/** - * Calculate weight of execution state only when it is offered. It has advantage, because it works faster, - * than with recalculation but disadvantage is that some features of execution state can change. - */ -class NNRewardGuidedSelectorWithoutWeightsRecalculation( - generatedTestCountingStatistics: GeneratedTestCountingStatistics, - choosingStrategy: ChoosingStrategy, - stoppingStrategy: StoppingStrategy, - seed: Int = 42, - graph: InterProceduralUnitGraph -) : NNRewardGuidedSelector(generatedTestCountingStatistics, choosingStrategy, stoppingStrategy, seed, graph) { - override fun offerImpl(state: ExecutionState) { - super.offerImpl(state) - featureExtractor.extractFeatures(state, generatedTestCountingStatistics.generatedTestsCount) - } - - override val ExecutionState.weight: Double - get() { - reward = reward ?: Predictors.stateRewardPredictor.predict(features) - return reward as Double - } -} - -/** - * Calculate weight of execution state every time when it needed. It works slower, - * than without recalculation but features are always relevant - */ -class NNRewardGuidedSelectorWithWeightsRecalculation( - generatedTestCountingStatistics: GeneratedTestCountingStatistics, - choosingStrategy: ChoosingStrategy, - stoppingStrategy: StoppingStrategy, - seed: Int = 42, - graph: InterProceduralUnitGraph -) : NNRewardGuidedSelector(generatedTestCountingStatistics, choosingStrategy, stoppingStrategy, seed, graph) { - override val ExecutionState.weight: Double - get() { - featureExtractor.extractFeatures(this, generatedTestCountingStatistics.generatedTestsCount) - return Predictors.stateRewardPredictor.predict(features) - } -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/NNRewardGuidedSelectorFactory.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/NNRewardGuidedSelectorFactory.kt deleted file mode 100644 index cad9632381..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/NNRewardGuidedSelectorFactory.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.utbot.engine.selectors - -import org.utbot.engine.InterProceduralUnitGraph -import org.utbot.engine.selectors.strategies.ChoosingStrategy -import org.utbot.engine.selectors.strategies.GeneratedTestCountingStatistics -import org.utbot.engine.selectors.strategies.StoppingStrategy - -/** - * Creates [NNRewardGuidedSelector] - */ -interface NNRewardGuidedSelectorFactory { - operator fun invoke( - generatedTestCountingStatistics: GeneratedTestCountingStatistics, - choosingStrategy: ChoosingStrategy, - stoppingStrategy: StoppingStrategy, - seed: Int = 42, - graph: InterProceduralUnitGraph - ): NNRewardGuidedSelector -} - -/** - * Creates [NNRewardGuidedSelectorWithWeightsRecalculation] - */ -class NNRewardGuidedSelectorWithRecalculationFactory : NNRewardGuidedSelectorFactory { - override fun invoke( - generatedTestCountingStatistics: GeneratedTestCountingStatistics, - choosingStrategy: ChoosingStrategy, - stoppingStrategy: StoppingStrategy, - seed: Int, - graph: InterProceduralUnitGraph - ): NNRewardGuidedSelector = NNRewardGuidedSelectorWithWeightsRecalculation( - generatedTestCountingStatistics, choosingStrategy, stoppingStrategy, seed, graph - ) -} - -/** - * Creates [NNRewardGuidedSelectorWithoutWeightsRecalculation] - */ -class NNRewardGuidedSelectorWithoutRecalculationFactory : NNRewardGuidedSelectorFactory { - override fun invoke( - generatedTestCountingStatistics: GeneratedTestCountingStatistics, - choosingStrategy: ChoosingStrategy, - stoppingStrategy: StoppingStrategy, - seed: Int, - graph: InterProceduralUnitGraph - ): NNRewardGuidedSelector = NNRewardGuidedSelectorWithoutWeightsRecalculation( - generatedTestCountingStatistics, choosingStrategy, stoppingStrategy, seed, graph - ) -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathSelector.kt index 9e98743b20..9406d03b78 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.pc.UtSolverStatusKind /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathSelectorBuilder.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathSelectorBuilder.kt index 1d5fb681d8..5516d5480b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathSelectorBuilder.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathSelectorBuilder.kt @@ -4,7 +4,7 @@ package org.utbot.engine.selectors import org.utbot.analytics.EngineAnalyticsContext import org.utbot.engine.InterProceduralUnitGraph -import org.utbot.engine.TypeRegistry +import org.utbot.engine.types.TypeRegistry import org.utbot.engine.selectors.StrategyOption.DISTANCE import org.utbot.engine.selectors.StrategyOption.VISIT_COUNTING import org.utbot.engine.selectors.nurs.CPInstSelector @@ -169,13 +169,13 @@ fun interleavedSelector(graph: InterProceduralUnitGraph, builder: InterleavedSel InterleavedSelectorBuilder(graph).apply(builder).build() /** - * build [NNRewardGuidedSelector] using [NNRewardGuidedSelectorBuilder] + * build [MLSelector] using [MLSelectorBuilder] */ -fun nnRewardGuidedSelector( +fun mlSelector( graph: InterProceduralUnitGraph, strategy: StrategyOption, - builder: NNRewardGuidedSelectorBuilder.() -> Unit -) = NNRewardGuidedSelectorBuilder(graph, strategy).apply(builder).build() + builder: MLSelectorBuilder.() -> Unit +) = MLSelectorBuilder(graph, strategy).apply(builder).build() data class PathSelectorContext( val graph: InterProceduralUnitGraph, @@ -525,15 +525,15 @@ class InterleavedSelectorBuilder internal constructor( } /** - * Builder for [NNRewardGuidedSelector]. Used in [] + * Builder for [MLSelector]. Used in [] */ -class NNRewardGuidedSelectorBuilder internal constructor( +class MLSelectorBuilder internal constructor( graph: InterProceduralUnitGraph, private val strategy: StrategyOption, context: PathSelectorContext = PathSelectorContext(graph), -) : PathSelectorBuilder(graph, context) { +) : PathSelectorBuilder(graph, context) { private val seed = seedInPathSelector - override fun build() = EngineAnalyticsContext.nnRewardGuidedSelectorFactory( + override fun build() = EngineAnalyticsContext.mlSelectorFactory( withGeneratedTestCountingStatistics(), withChoosingStrategy(strategy), requireNotNull(context.stoppingStrategy) { "StoppingStrategy isn't specified" }, diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathsTree.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathsTree.kt index f908eb4fdb..7618877531 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathsTree.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/PathsTree.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import java.util.NoSuchElementException import kotlin.random.Random diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/RandomPathSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/RandomPathSelector.kt index 844dcce9c8..64f48f544b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/RandomPathSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/RandomPathSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.ChoosingStrategy import org.utbot.engine.selectors.strategies.StoppingStrategy import kotlin.random.Random diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/RandomSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/RandomSelector.kt index 76245cb4a7..c27156f1ad 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/RandomSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/RandomSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.ChoosingStrategy import org.utbot.engine.selectors.strategies.StoppingStrategy import kotlin.random.Random diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/CPInstSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/CPInstSelector.kt index 261dfc2fc2..e39e7456f0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/CPInstSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/CPInstSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.nurs -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.ChoosingStrategy import org.utbot.engine.selectors.strategies.StatementsStatistics import org.utbot.engine.selectors.strategies.StoppingStrategy diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/CoveredNewSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/CoveredNewSelector.kt index 01a98b4e82..5e3e4b5ffc 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/CoveredNewSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/CoveredNewSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.nurs -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.DistanceStatistics import org.utbot.engine.selectors.strategies.StoppingStrategy diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/DepthSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/DepthSelector.kt index 5962db8768..5885df02c7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/DepthSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/DepthSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.nurs -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.ChoosingStrategy import org.utbot.engine.selectors.strategies.StoppingStrategy diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/ForkDepthSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/ForkDepthSelector.kt index 7c5e27aaf7..78ed6a93c0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/ForkDepthSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/ForkDepthSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.nurs -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.ChoosingStrategy import org.utbot.engine.selectors.strategies.StoppingStrategy diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/GreedySearch.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/GreedySearch.kt index b00d67e871..1fe1985489 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/GreedySearch.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/GreedySearch.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.nurs -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.BasePathSelector import org.utbot.engine.selectors.strategies.ChoosingStrategy import org.utbot.engine.selectors.strategies.StoppingStrategy diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/InheritorsSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/InheritorsSelector.kt index 8550c3c46e..317a436736 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/InheritorsSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/InheritorsSelector.kt @@ -1,7 +1,7 @@ package org.utbot.engine.selectors.nurs -import org.utbot.engine.ExecutionState -import org.utbot.engine.TypeRegistry +import org.utbot.engine.state.ExecutionState +import org.utbot.engine.types.TypeRegistry import org.utbot.engine.selectors.strategies.DistanceStatistics import org.utbot.engine.selectors.strategies.StoppingStrategy import kotlin.math.pow diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/MinimalDistanceToUncovered.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/MinimalDistanceToUncovered.kt index 3f56367ead..a3ab7f8f58 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/MinimalDistanceToUncovered.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/MinimalDistanceToUncovered.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.nurs -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.DistanceStatistics import org.utbot.engine.selectors.strategies.StoppingStrategy diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/NeuroSatSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/NeuroSatSelector.kt index 3736227a06..596c91492d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/NeuroSatSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/NeuroSatSelector.kt @@ -1,7 +1,7 @@ package org.utbot.engine.selectors.nurs import org.utbot.analytics.Predictors -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.ChoosingStrategy import org.utbot.engine.selectors.strategies.StoppingStrategy diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/NonUniformRandomSearch.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/NonUniformRandomSearch.kt index 3123f3303d..6c10f233a0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/NonUniformRandomSearch.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/NonUniformRandomSearch.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.nurs -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.BasePathSelector import org.utbot.engine.selectors.strategies.ChoosingStrategy import org.utbot.engine.selectors.strategies.StoppingStrategy @@ -58,6 +58,13 @@ abstract class NonUniformRandomSearch( private val randomGen: Random? = seed?.let { Random(seed) } + // We use this value to avoid non-deterministic behaviour of the + // peek method. Without it, we might have different states for + // a sequence of the `peek` calls. + // Now we remember it at the first `peek` call and use it + // until a first `poll` call. The first call should reset it back to null. + private var lastTakenRandomValue: Double? = null + override fun update() { executionQueue.updateAll() } @@ -75,7 +82,11 @@ abstract class NonUniformRandomSearch( * with probability executionState.asWeight.weight / sumWeights */ override fun peekImpl(): ExecutionState? { - val rand = (randomGen?.nextDouble() ?: 1.0) * sumWeights + if (lastTakenRandomValue == null) { + lastTakenRandomValue = randomGen?.nextDouble() + } + + val rand = (lastTakenRandomValue ?: 1.0) * sumWeights return executionQueue.findLeftest(rand)?.first } @@ -83,7 +94,10 @@ abstract class NonUniformRandomSearch( override fun removeImpl(state: ExecutionState): Boolean = executionQueue.remove(state) override fun pollImpl(): ExecutionState? = - peekImpl()?.also { remove(it) } + peekImpl()?.also { + remove(it) + lastTakenRandomValue = null + } override fun isEmpty() = executionQueue.isEmpty() diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/RPSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/RPSelector.kt index d7e06e2d37..b201104802 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/RPSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/RPSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.nurs -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.ChoosingStrategy import org.utbot.engine.selectors.strategies.StoppingStrategy import kotlin.math.pow diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/SubpathGuidedSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/SubpathGuidedSelector.kt index a17e94a6a2..f6a43fa0a5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/SubpathGuidedSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/SubpathGuidedSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.nurs -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.ChoosingStrategy import org.utbot.engine.selectors.strategies.StoppingStrategy import org.utbot.engine.selectors.strategies.SubpathStatistics diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/VisitCountingSelector.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/VisitCountingSelector.kt index 6e69e29dbb..e455d2d124 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/VisitCountingSelector.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/nurs/VisitCountingSelector.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.nurs -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.selectors.strategies.EdgeVisitCountingStatistics import org.utbot.engine.selectors.strategies.StoppingStrategy diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/ChoosingStrategy.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/ChoosingStrategy.kt index f52de7e622..0d08a7f0dd 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/ChoosingStrategy.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/ChoosingStrategy.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.strategies -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.InterProceduralUnitGraph /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/DistanceStatistics.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/DistanceStatistics.kt index 6621a517d2..9c1cfc16c1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/DistanceStatistics.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/DistanceStatistics.kt @@ -1,7 +1,7 @@ package org.utbot.engine.selectors.strategies -import org.utbot.engine.Edge -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.Edge +import org.utbot.engine.state.ExecutionState import org.utbot.engine.InterProceduralUnitGraph import org.utbot.engine.isReturn import org.utbot.engine.pathLogger @@ -9,6 +9,7 @@ import org.utbot.engine.stmts import kotlin.math.min import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf +import org.utbot.framework.UtSettings.enableLoggingForDroppedStates import soot.jimple.Stmt import soot.toolkits.graph.ExceptionalUnitGraph @@ -43,8 +44,11 @@ class DistanceStatistics( val shouldDrop = state.edges.all { graph.isCoveredWithAllThrowStatements(it) } && distanceToUncovered(state) == Int.MAX_VALUE if (shouldDrop) { - pathLogger.debug { - "Dropping state (lastStatus=${state.solver.lastStatus}) by the distance statistics. MD5: ${state.md5()}" + if (enableLoggingForDroppedStates) { + pathLogger.debug { + val lastStatus = state.solver.lastStatus + "Dropping state (lastStatus=$lastStatus) by the distance statistics. MD5: ${state.md5()}" + } } } @@ -138,9 +142,6 @@ class DistanceStatistics( * minimal distance to closest uncovered statement in interprocedural graph for execution. */ fun distanceToUncovered(state: ExecutionState): Int { - var calc = 0 - var stmt: Stmt = state.stmt - val distances = mutableListOf() if (state.lastEdge != null && state.lastEdge in graph.implicitEdges) { return if (state.lastEdge in graph.coveredImplicitEdges) { Int.MAX_VALUE @@ -149,24 +150,30 @@ class DistanceStatistics( } } + var executionStackAccumulatedDistanceToReturn = 0 + var stmt: Stmt = state.stmt + var minDistance: Int? = null + for (stackElement in state.executionStack.asReversed()) { - val caller = stackElement.caller val distance = distanceToClosestUncovered[stmt] ?: Int.MAX_VALUE - val distanceToRet = closestToReturn[stmt] ?: error("$stmt is not in graph") + val distanceToReturn = closestToReturn[stmt] ?: error("$stmt is not in graph") + if (distance != Int.MAX_VALUE) { - distances += calc + distance - } - if (caller == null) { - break + minDistance = (minDistance ?: 0) + executionStackAccumulatedDistanceToReturn + distance } - if (distanceToRet != Int.MAX_VALUE) { - calc += distanceToRet - } else { + + val caller = stackElement.caller + + if (caller == null || distanceToReturn == Int.MAX_VALUE) { break } + + executionStackAccumulatedDistanceToReturn += distanceToReturn + stmt = caller } - return distances.minOrNull() ?: Int.MAX_VALUE + + return minDistance ?: Int.MAX_VALUE } /** diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/EdgeVisitCountingStatistics.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/EdgeVisitCountingStatistics.kt index 1a0ad21348..8813973475 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/EdgeVisitCountingStatistics.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/EdgeVisitCountingStatistics.kt @@ -1,9 +1,10 @@ package org.utbot.engine.selectors.strategies -import org.utbot.engine.Edge -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.Edge +import org.utbot.engine.state.ExecutionState import org.utbot.engine.InterProceduralUnitGraph import org.utbot.engine.pathLogger +import org.utbot.framework.UtSettings.enableLoggingForDroppedStates import soot.jimple.Stmt import soot.jimple.internal.JReturnStmt import soot.jimple.internal.JReturnVoidStmt @@ -35,9 +36,12 @@ class EdgeVisitCountingStatistics( val shouldDrop = state.edges.all { graph.isCoveredWithAllThrowStatements(it) } && state.isComplete() if (shouldDrop) { - pathLogger.debug { - "Dropping state (lastStatus=${state.solver.lastStatus}) " + - "by the edge visit counting statistics. MD5: ${state.md5()}" + if (enableLoggingForDroppedStates) { + pathLogger.debug { + val lastStatus = state.solver.lastStatus + val md5 = state.md5() + "Dropping state (lastStatus=$lastStatus) by the edge visit counting statistics. MD5: $md5" + } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GeneratedTestCountingStatistics.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GeneratedTestCountingStatistics.kt index 315673e042..88163e49a1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GeneratedTestCountingStatistics.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GeneratedTestCountingStatistics.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.strategies -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.InterProceduralUnitGraph class GeneratedTestCountingStatistics( diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GraphViz.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GraphViz.kt index 057fc70977..1cf9c38ec7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GraphViz.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/GraphViz.kt @@ -1,24 +1,24 @@ package org.utbot.engine.selectors.strategies -import org.utbot.engine.CALL_DECISION_NUM -import org.utbot.engine.Edge -import org.utbot.engine.ExecutionState +import mu.KotlinLogging +import org.utbot.common.FileUtil.createNewFileWithParentDirectories +import org.utbot.engine.state.CALL_DECISION_NUM +import org.utbot.engine.state.Edge +import org.utbot.engine.state.ExecutionState import org.utbot.engine.InterProceduralUnitGraph +import org.utbot.engine.isLibraryNonOverriddenClass import org.utbot.engine.isReturn import org.utbot.engine.selectors.PathSelector import org.utbot.engine.stmts import org.utbot.framework.UtSettings.copyVisualizationPathToClipboard +import org.utbot.framework.UtSettings.showLibraryClassesInVisualization +import soot.jimple.Stmt +import soot.toolkits.graph.ExceptionalUnitGraph import java.awt.Toolkit import java.awt.datatransfer.StringSelection import java.io.FileWriter -import java.nio.file.Files import java.nio.file.Paths -import mu.KotlinLogging -import org.apache.commons.io.FileUtils -import org.utbot.engine.isLibraryNonOverriddenClass -import org.utbot.engine.isOverridden -import soot.jimple.Stmt -import soot.toolkits.graph.ExceptionalUnitGraph +import org.utbot.common.FileUtil private val logger = KotlinLogging.logger {} @@ -29,7 +29,7 @@ class GraphViz( ) : TraverseGraphStatistics(globalGraph) { // Files - private val graphVisDirectory = Files.createTempDirectory("Graph-vis") + private val graphVisDirectory = FileUtil.createTempDirectory("Graph-vis") private val graphVisPathString = graphVisDirectory.toString() private val graphJs = Paths.get(graphVisPathString, "graph.js").toFile() @@ -51,10 +51,17 @@ class GraphViz( val classLoader = GraphViz::class.java.classLoader for (file in requiredFileNames) { - FileUtils.copyInputStreamToFile( - classLoader.getResourceAsStream("html/$file"), - Paths.get(graphVisDirectory.toString(), file).toFile() - ) + classLoader.getResourceAsStream("html/$file").use { inputStream -> + val path = Paths.get(graphVisDirectory.toString(), file) + val targetFile = path.toFile() + targetFile.createNewFileWithParentDirectories() + + targetFile.outputStream().use { targetOutputStream -> + inputStream?.copyTo(targetOutputStream) ?: logger.error { + "Could not start a visualization because of missing resource html/$file" + } + } + } } FileWriter(graphJs).use { it.write( @@ -97,7 +104,11 @@ class GraphViz( graph.allEdges.forEach { edge -> val (edgeSrc, edgeDst, _) = edge - if (stmtToSubgraph[edgeSrc] !in libraryGraphs && stmtToSubgraph[edgeDst] !in libraryGraphs) { + val srcInLibraryMethod = stmtToSubgraph[edgeSrc] in libraryGraphs + val dstInLibraryMethod = stmtToSubgraph[edgeDst] in libraryGraphs + val edgeIsRelatedToLibraryMethod = srcInLibraryMethod || dstInLibraryMethod + + if (!edgeIsRelatedToLibraryMethod || showLibraryClassesInVisualization) { dotGlobalGraph.addDotEdge(edge) } } @@ -137,8 +148,10 @@ class GraphViz( } // Filter library methods - uncompletedStack.removeIf { it.name in libraryGraphs } - fullStack.removeIf { it.name in libraryGraphs } + if (!showLibraryClassesInVisualization) { + uncompletedStack.removeIf { it.name in libraryGraphs } + fullStack.removeIf { it.name in libraryGraphs } + } // Update nodes and edges properties dotGlobalGraph.updateProperties(executionState) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/StatementsStatistics.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/StatementsStatistics.kt index 375462191c..88d140b6ab 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/StatementsStatistics.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/StatementsStatistics.kt @@ -1,6 +1,6 @@ package org.utbot.engine.selectors.strategies -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.ExecutionState import org.utbot.engine.InterProceduralUnitGraph import soot.SootMethod import soot.jimple.Stmt diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/StepsLimitStoppingStrategy.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/StepsLimitStoppingStrategy.kt index f17d2b6750..a520afa1d5 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/StepsLimitStoppingStrategy.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/StepsLimitStoppingStrategy.kt @@ -1,7 +1,7 @@ package org.utbot.engine.selectors.strategies -import org.utbot.engine.Edge -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.Edge +import org.utbot.engine.state.ExecutionState import org.utbot.engine.InterProceduralUnitGraph import org.utbot.engine.pathLogger diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/SubpathStatistics.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/SubpathStatistics.kt index 0110cf03c9..2ecefd06e3 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/SubpathStatistics.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/SubpathStatistics.kt @@ -1,8 +1,8 @@ package org.utbot.engine.selectors.strategies -import org.utbot.engine.CALL_DECISION_NUM -import org.utbot.engine.Edge -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.CALL_DECISION_NUM +import org.utbot.engine.state.Edge +import org.utbot.engine.state.ExecutionState import org.utbot.engine.InterProceduralUnitGraph import org.utbot.framework.UtSettings import kotlin.math.pow diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/TraverseGraphStatistics.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/TraverseGraphStatistics.kt index 7ec9abedc0..2b4feac95e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/TraverseGraphStatistics.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/selectors/strategies/TraverseGraphStatistics.kt @@ -1,7 +1,7 @@ package org.utbot.engine.selectors.strategies -import org.utbot.engine.Edge -import org.utbot.engine.ExecutionState +import org.utbot.engine.state.Edge +import org.utbot.engine.state.ExecutionState import org.utbot.engine.InterProceduralUnitGraph import soot.jimple.Stmt import soot.toolkits.graph.ExceptionalUnitGraph diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/MemoryUpdateSimplificator.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/MemoryUpdateSimplificator.kt new file mode 100644 index 0000000000..cbf4de006c --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/MemoryUpdateSimplificator.kt @@ -0,0 +1,195 @@ +package org.utbot.engine.simplificators + +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.PersistentSet +import kotlinx.collections.immutable.mutate +import kotlinx.collections.immutable.toPersistentList +import kotlinx.collections.immutable.toPersistentMap +import kotlinx.collections.immutable.toPersistentSet +import org.utbot.engine.Concrete +import org.utbot.engine.InstanceFieldReadOperation +import org.utbot.engine.MemoryChunkDescriptor +import org.utbot.engine.MemoryUpdate +import org.utbot.engine.MockInfoEnriched +import org.utbot.engine.ObjectValue +import org.utbot.engine.StaticFieldMemoryUpdateInfo +import org.utbot.engine.SymbolicValue +import org.utbot.engine.TypeStorage +import org.utbot.engine.UtMockInfo +import org.utbot.engine.UtNamedStore +import org.utbot.engine.pc.Simplificator +import org.utbot.engine.pc.UtAddrExpression +import org.utbot.engine.pc.UtExpression +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import soot.ArrayType +import soot.SootField + +typealias StoresType = PersistentList +typealias TouchedChunkDescriptorsType = PersistentSet +typealias ConcreteType = PersistentMap +typealias MockInfosType = PersistentList +typealias StaticInstanceStorageType = PersistentMap +typealias InitializedStaticFieldsType = PersistentSet +typealias StaticFieldsUpdatesType = PersistentList +typealias MeaningfulStaticFieldsType = PersistentSet +typealias FieldValuesType = PersistentMap> +typealias AddrToArrayTypeType = PersistentMap +typealias AddrToGenericTypeInfo = PersistentList>> +typealias AddrToMockInfoType = PersistentMap +typealias VisitedValuesType = PersistentList +typealias TouchedAddressesType = PersistentList +typealias ClassIdToClearStaticsType = ClassId? +typealias InstanceFieldReadsType = PersistentSet +typealias SpeculativelyNotNullAddressesType = PersistentList +typealias SymbolicEnumValuesType = PersistentList +typealias TaintArrayUpdateType = PersistentList> + +class MemoryUpdateSimplificator( + private val simplificator: Simplificator +) : CachingSimplificatorAdapter() { + override fun simplifyImpl(expression: MemoryUpdate): MemoryUpdate = with(expression) { + val stores = simplifyStores(stores) + val touchedChunkDescriptors = simplifyTouchedChunkDescriptors(touchedChunkDescriptors) + val concrete = simplifyConcrete(concrete) + val mockInfos = simplifyMockInfos(mockInfos) + val staticInstanceStorage = simplifyStaticInstanceStorage(staticInstanceStorage) + val initializedStaticFields = simplifyInitializedStaticFields(initializedStaticFields) + val staticFieldsUpdates = simplifyStaticFieldsUpdates(staticFieldsUpdates) + val meaningfulStaticFields = simplifyMeaningfulStaticFields(meaningfulStaticFields) + val fieldValues = simplifyFieldValues(fieldValues) + val addrToArrayType = simplifyAddrToArrayType(addrToArrayType) + val genericTypeStorageByAddr = simplifyGenericTypeStorageByAddr(genericTypeStorageByAddr) + val addrToMockInfo = simplifyAddrToMockInfo(addrToMockInfo) + val visitedValues = simplifyVisitedValues(visitedValues) + val touchedAddresses = simplifyTouchedAddresses(touchedAddresses) + val classIdToClearStatics = simplifyClassIdToClearStatics(classIdToClearStatics) + val instanceFieldReads = simplifyInstanceFieldReads(instanceFieldReads) + val speculativelyNotNullAddresses = + simplifySpeculativelyNotNullAddresses(speculativelyNotNullAddresses) + val symbolicEnumValues = simplifyEnumValues(symbolicEnumValues) + val taintArrayUpdate = simplifyTaintArrayUpdate(taintArrayUpdate) + return MemoryUpdate( + stores, + touchedChunkDescriptors, + concrete, + mockInfos, + staticInstanceStorage, + initializedStaticFields, + staticFieldsUpdates, + meaningfulStaticFields, + fieldValues, + addrToArrayType, + genericTypeStorageByAddr, + addrToMockInfo, + visitedValues, + touchedAddresses, + classIdToClearStatics, + instanceFieldReads, + speculativelyNotNullAddresses, + taintArrayUpdate, + symbolicEnumValues + ) + } + + private fun simplifyStores(stores: StoresType): StoresType = + stores + .mutate { prevStores -> + prevStores.replaceAll { store -> + store.copy( + index = store.index.accept(simplificator), + value = store.value.accept(simplificator) + ) + } + } + + private fun simplifyTouchedChunkDescriptors(touchedChunkDescriptors: TouchedChunkDescriptorsType): TouchedChunkDescriptorsType = + touchedChunkDescriptors + + private fun simplifyConcrete(concrete: ConcreteType): ConcreteType = + concrete + .mapKeys { (k, _) -> k.accept(simplificator) as UtAddrExpression } + .toPersistentMap() + + private fun simplifyMockInfos(mockInfos: MockInfosType): MockInfosType = + mockInfos.mutate { prevMockInfos -> + prevMockInfos.replaceAll { + with(simplificator) { + simplifyMockInfoEnriched(it) + } + } + } + + + private fun simplifyStaticInstanceStorage(staticInstanceStorage: StaticInstanceStorageType): StaticInstanceStorageType = + staticInstanceStorage.mutate { prevStorage -> + prevStorage.replaceAll { _, v -> with(simplificator) { simplifySymbolicValue(v) as ObjectValue } } + } + + private fun simplifyInitializedStaticFields(initializedStaticFields: InitializedStaticFieldsType): InitializedStaticFieldsType = + initializedStaticFields + + private fun simplifyStaticFieldsUpdates(staticFieldsUpdates: StaticFieldsUpdatesType): StaticFieldsUpdatesType = + staticFieldsUpdates.mutate { prevUpdates -> + prevUpdates.replaceAll { with(simplificator) { staticFieldMemoryUpdateInfo(it) } } + } + + private fun simplifyMeaningfulStaticFields(meaningfulStaticFields: MeaningfulStaticFieldsType): MeaningfulStaticFieldsType = + meaningfulStaticFields + + private fun simplifyFieldValues(fieldValues: FieldValuesType): FieldValuesType = fieldValues + + private fun simplifyAddrToArrayType(addrToArrayType: AddrToArrayTypeType): AddrToArrayTypeType = + addrToArrayType + .mapKeys { (k, _) -> k.accept(simplificator) as UtAddrExpression } + .toPersistentMap() + + private fun simplifyGenericTypeStorageByAddr(genericTypeStorageByAddr: AddrToGenericTypeInfo): AddrToGenericTypeInfo = + genericTypeStorageByAddr + .map { (k, v) -> k.accept(simplificator) as UtAddrExpression to v } + .toPersistentList() + + private fun simplifyAddrToMockInfo(addrToMockInfo: AddrToMockInfoType): AddrToMockInfoType = + addrToMockInfo + .mapKeys { (k, _) -> k.accept(simplificator) as UtAddrExpression } + .toPersistentMap() + + private fun simplifyVisitedValues(visitedValues: VisitedValuesType): VisitedValuesType = + visitedValues.mutate { prevValues -> + prevValues.replaceAll { it.accept(simplificator) as UtAddrExpression } + } + + private fun simplifyTouchedAddresses(touchedAddresses: TouchedAddressesType): TouchedAddressesType = + touchedAddresses.mutate { prevAddresses -> + prevAddresses.replaceAll { it.accept(simplificator) as UtAddrExpression } + } + + private fun simplifyClassIdToClearStatics(classIdToClearStatics: ClassIdToClearStaticsType): ClassIdToClearStaticsType = + classIdToClearStatics + + private fun simplifyInstanceFieldReads(instanceFieldReads: InstanceFieldReadsType): InstanceFieldReadsType = + instanceFieldReads + .map { it.copy(addr = it.addr.accept(simplificator) as UtAddrExpression) } + .toPersistentSet() + + private fun simplifySpeculativelyNotNullAddresses(speculativelyNotNullAddresses: SpeculativelyNotNullAddressesType): SpeculativelyNotNullAddressesType = + speculativelyNotNullAddresses.mutate { prevAddresses -> + prevAddresses.replaceAll { it.accept(simplificator) as UtAddrExpression } + } + + private fun simplifyEnumValues(symbolicEnumValues: SymbolicEnumValuesType): SymbolicEnumValuesType = + symbolicEnumValues.mutate { values -> + values.replaceAll { with(simplificator) { simplifySymbolicValue(it) as ObjectValue } } + } + + private fun simplifyTaintArrayUpdate(taintArrayUpdate: TaintArrayUpdateType): TaintArrayUpdateType = + taintArrayUpdate.mutate { values -> + values.replaceAll { (addr, expr) -> + val simplifiedAddr = addr.accept(simplificator) as UtAddrExpression + val simplifiedExpr = expr.accept(simplificator) + + simplifiedAddr to simplifiedExpr + } + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/SimplificatorAdapter.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/SimplificatorAdapter.kt new file mode 100644 index 0000000000..5faf5b5b32 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/SimplificatorAdapter.kt @@ -0,0 +1,18 @@ +package org.utbot.engine.simplificators + +import java.util.IdentityHashMap + + +interface SimplificatorAdapter { + fun simplify(expression: T): T +} + +abstract class CachingSimplificatorAdapter : SimplificatorAdapter { + private val cache: IdentityHashMap = IdentityHashMap() + + final override fun simplify(expression: T): T = + cache.getOrPut(expression) { simplifyImpl(expression) } + + protected abstract fun simplifyImpl(expression: T): T +} + diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/Util.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/Util.kt new file mode 100644 index 0000000000..8403bc3c8b --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/simplificators/Util.kt @@ -0,0 +1,68 @@ +package org.utbot.engine.simplificators + +import org.utbot.engine.ArrayValue +import org.utbot.engine.MockInfoEnriched +import org.utbot.engine.ObjectValue +import org.utbot.engine.PrimitiveValue +import org.utbot.engine.StaticFieldMemoryUpdateInfo +import org.utbot.engine.SymbolicValue +import org.utbot.engine.UtFieldMockInfo +import org.utbot.engine.UtMockInfo +import org.utbot.engine.UtNewInstanceMockInfo +import org.utbot.engine.UtObjectMockInfo +import org.utbot.engine.UtStaticMethodMockInfo +import org.utbot.engine.UtStaticObjectMockInfo +import org.utbot.engine.pc.Simplificator +import org.utbot.engine.pc.UtAddrExpression +import org.utbot.engine.symbolic.SymbolicStateUpdate + +context(Simplificator) +fun staticFieldMemoryUpdateInfo(staticFieldMemoryUpdateInfo: StaticFieldMemoryUpdateInfo) = + with(staticFieldMemoryUpdateInfo) { + copy(value = simplifySymbolicValue(value)) + } + +context(Simplificator) +fun simplifyMockInfoEnriched(mockInfoEnriched: MockInfoEnriched): MockInfoEnriched { + val mockInfo: UtMockInfo = simplifyUtMockInfo(mockInfoEnriched.mockInfo) + val executables = mockInfoEnriched.executables.mapValues { (_, mockExecutableInstances) -> + mockExecutableInstances.map { mockExecutableInstance -> + with(mockExecutableInstance) { + val symbolicValue = simplifySymbolicValue(value) + copy(value = symbolicValue) + } + } + } + + return MockInfoEnriched(mockInfo, executables) +} + +context(Simplificator) +fun simplifyUtMockInfo(mockInfo: UtMockInfo): UtMockInfo = + with(mockInfo) { + when (this) { + is UtFieldMockInfo -> copy(ownerAddr = ownerAddr?.accept(this@Simplificator) as UtAddrExpression?) + is UtNewInstanceMockInfo -> copy(addr = addr.accept(this@Simplificator) as UtAddrExpression) + is UtObjectMockInfo -> copy(addr = addr.accept(this@Simplificator) as UtAddrExpression) + is UtStaticMethodMockInfo -> copy(addr = addr.accept(this@Simplificator) as UtAddrExpression) + is UtStaticObjectMockInfo -> copy(addr = addr.accept(this@Simplificator) as UtAddrExpression) + } + } + +context(Simplificator) +fun simplifySymbolicValue(value: SymbolicValue): SymbolicValue = + with(value) { + when (this) { + is PrimitiveValue -> copy(expr = expr.accept(this@Simplificator)) + is ArrayValue -> copy(addr = addr.accept(this@Simplificator) as UtAddrExpression) + is ObjectValue -> copy(addr = addr.accept(this@Simplificator) as UtAddrExpression) + } + } + + +context(MemoryUpdateSimplificator) +fun simplifySymbolicStateUpdate(update: SymbolicStateUpdate) = + with(update) { + val memoryUpdates = simplify(memoryUpdates) + copy(memoryUpdates = memoryUpdates) + } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/state/ExecutionStackElement.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/state/ExecutionStackElement.kt new file mode 100644 index 0000000000..378da0f7f0 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/state/ExecutionStackElement.kt @@ -0,0 +1,52 @@ +package org.utbot.engine.state + +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentHashMapOf +import org.utbot.engine.LocalMemoryUpdate +import org.utbot.engine.LocalVariable +import org.utbot.engine.Parameter +import org.utbot.engine.SymbolicValue +import org.utbot.engine.update +import soot.SootMethod +import soot.jimple.Stmt + +/** + * The stack element of the [ExecutionState]. + * Contains properties, that are suitable for specified method in call stack. + * + * @param doesntThrow if true, then engine should drop states with throwing exceptions. + * @param localVariableMemory the local memory associated with the current stack element. + */ +data class ExecutionStackElement( + val caller: Stmt?, + val localVariableMemory: LocalVariableMemory = LocalVariableMemory(), + val parameters: MutableList = mutableListOf(), + val inputArguments: ArrayDeque = ArrayDeque(), + val doesntThrow: Boolean = false, + val method: SootMethod, +) { + fun update(memoryUpdate: LocalMemoryUpdate, doesntThrow: Boolean = this.doesntThrow) = + this.copy(localVariableMemory = localVariableMemory.update(memoryUpdate), doesntThrow = doesntThrow) +} + +/** + * Represents a memory associated with a certain method call. For now consists only of local variables mapping. + * TODO: think on other fields later: [#339](https://github.com/UnitTestBot/UTBotJava/issues/339) [#340](https://github.com/UnitTestBot/UTBotJava/issues/340) + * + * @param [locals] represents a mapping from [LocalVariable]s of a specific method call to [SymbolicValue]s. + */ +data class LocalVariableMemory( + private val locals: PersistentMap = persistentHashMapOf() +) { + fun memoryForNestedMethod(): LocalVariableMemory = this.copy(locals = persistentHashMapOf()) + + fun update(update: LocalMemoryUpdate): LocalVariableMemory = this.copy(locals = locals.update(update.locals)) + + /** + * Returns local variable value. + */ + fun local(variable: LocalVariable): SymbolicValue? = locals[variable] + + val localValues: Set + get() = locals.values.toSet() +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/state/ExecutionState.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/state/ExecutionState.kt new file mode 100644 index 0000000000..6bb6f88f80 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/state/ExecutionState.kt @@ -0,0 +1,280 @@ +package org.utbot.engine.state + +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.PersistentSet +import kotlinx.collections.immutable.persistentHashMapOf +import kotlinx.collections.immutable.persistentHashSetOf +import kotlinx.collections.immutable.persistentListOf +import org.utbot.common.md5 +import org.utbot.engine.Memory +import org.utbot.engine.MethodResult +import org.utbot.engine.SymbolicFailure +import org.utbot.engine.pc.UtSolver +import org.utbot.engine.pc.UtSolverStatusUNDEFINED +import org.utbot.engine.state.StateLabel.CONCRETE +import org.utbot.engine.state.StateLabel.INTERMEDIATE +import org.utbot.engine.state.StateLabel.TERMINAL +import org.utbot.engine.symbolic.Assumption +import org.utbot.engine.symbolic.SymbolicState +import org.utbot.framework.UtSettings +import org.utbot.framework.plugin.api.Step +import soot.SootMethod +import soot.jimple.Stmt +import java.util.Objects + +const val RETURN_DECISION_NUM = -1 +const val CALL_DECISION_NUM = -2 + +data class Edge(val src: Stmt, val dst: Stmt, val decisionNum: Int) { + private val hashCode: Int = Objects.hash(src, dst, decisionNum) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Edge + + if (src != other.src) return false + if (dst != other.dst) return false + if (decisionNum != other.decisionNum) return false + + return true + } + + override fun hashCode(): Int = hashCode +} + +/** + * Possible state types. Engine matches on them and processes differently. + * + * [INTERMEDIATE] is a label for an intermediate state which is suitable for further symbolic analysis. + * + * [TERMINAL] is a label for a terminal state from which we might (or might not) execute concretely and construct + * [UtExecution]. This state represents the final state of the program execution, that is a throw or return from the outer + * method. + * + * [CONCRETE] is a label for a state which is not suitable for further symbolic analysis, and it is also not a terminal + * state. Such states are only suitable for a concrete execution and may result from [Assumption]s. + */ +enum class StateLabel { + INTERMEDIATE, + TERMINAL, + CONCRETE +} + +/** + * Class that store all information about execution state that needed only for analytics module + */ +data class StateAnalyticsProperties( + /** + * Number of forks already performed along state's path, where fork is statement with multiple successors + */ + val depth: Int = 0, + var visitedAfterLastFork: Int = 0, + var visitedBeforeLastFork: Int = 0, + var stmtsSinceLastCovered: Int = 0, + val parent: ExecutionState? = null, +) { + var executingTime: Long = 0 + var reward: Double? = null + val features: MutableList = mutableListOf() + + /** + * Flag that indicates whether this state is fork or not. Fork here means that we have more than one successor + */ + private var isFork: Boolean = false + + fun definitelyFork() { + isFork = true + } + + private var isVisitedNew: Boolean = false + + fun updateIsVisitedNew() { + isVisitedNew = true + stmtsSinceLastCovered = 0 + visitedAfterLastFork++ + } + + private val successorDepth: Int get() = depth + if (isFork) 1 else 0 + + private val successorVisitedAfterLastFork: Int get() = if (!isFork) visitedAfterLastFork else 0 + private val successorVisitedBeforeLastFork: Int get() = visitedBeforeLastFork + if (isFork) visitedAfterLastFork else 0 + private val successorStmtSinceLastCovered: Int get() = 1 + stmtsSinceLastCovered + + fun successorProperties(parent: ExecutionState) = StateAnalyticsProperties( + successorDepth, + successorVisitedAfterLastFork, + successorVisitedBeforeLastFork, + successorStmtSinceLastCovered, + if (UtSettings.enableFeatureProcess) parent else null + ) +} + +/** + * [visitedStatementsHashesToCountInPath] is a map representing how many times each instruction from the [path] + * has occurred. It is required to calculate priority of the branches and decrease the priority for branches leading + * inside a cycle. To improve performance it is a persistent map using state's hashcode to imitate an identity hashmap. + * + * @param symbolicState the current symbolic state. + */ +data class ExecutionState( + val stmt: Stmt, + val symbolicState: SymbolicState, + val executionStack: PersistentList, + val path: PersistentList = persistentListOf(), + val visitedStatementsHashesToCountInPath: PersistentMap = persistentHashMapOf(), + val decisionPath: PersistentList = persistentListOf(0), + val edges: PersistentSet = persistentHashSetOf(), + val stmts: PersistentMap = persistentHashMapOf(), + val pathLength: Int = 0, + val lastEdge: Edge? = null, + val lastMethod: SootMethod? = null, + val methodResult: MethodResult? = null, + val exception: SymbolicFailure? = null, + val label: StateLabel = INTERMEDIATE, + var stateAnalyticsProperties: StateAnalyticsProperties = StateAnalyticsProperties() +) : AutoCloseable { + val solver: UtSolver by symbolicState::solver + + val memory: Memory by symbolicState::memory + + var outgoingEdges = 0 + + fun isInNestedMethod() = executionStack.size > 1 + + val localVariableMemory + get() = executionStack.last().localVariableMemory + + val inputArguments + get() = executionStack.last().inputArguments + + val parameters + get() = executionStack.last().parameters + + /** + * Retrieves MUT parameters. + */ + val methodUnderTestParameters + get() = executionStack.firstOrNull()?.parameters?.map { it.value } + ?: error("Cannot retrieve MUT parameters from empty execution stack") + + val isThrowException: Boolean + get() = (lastEdge?.decisionNum ?: 0) < CALL_DECISION_NUM + + val isInsideStaticInitializer + get() = executionStack.any { it.method.isStaticInitializer } + + /** + * Tell to solver that states with status [UtSolverStatusUNDEFINED] can be created from current state. + * + * Note: Solver optimize cloning respect this flag. + */ + fun expectUndefined() { + solver.expectUndefined = true + } + + override fun close() { + solver.close() + } + + + /** + * Collects full statement path from method entry point to current statement, including current statement. + * + * Each step contains statement, call depth for nested calls (returns belong to called method) and decision. + * Decision for current statement is zero. + * + * Note: calculates depth wrongly for thrown exception, check SAT-811, SAT-812 + * TODO: fix SAT-811, SAT-812 + */ + fun fullPath(): List { + var depth = 0 + val path = path.zip( + decisionPath.subList(1, decisionPath.size) + ).map { (stmt, decision) -> + val stepDepth = when (decision) { + CALL_DECISION_NUM -> depth++ + RETURN_DECISION_NUM -> depth-- + else -> depth + } + Step(stmt, stepDepth, decision) + } + return path + Step(stmt, depth, 0) + } + + /** + * Prettifies full statement path for logging. + * + * Note: marks return statements with *depth-1* to pair with call statement. + */ + fun prettifiedPathLog(): String { + val path = fullPath() + val prettifiedPath = prettifiedPath(path) + return " MD5(path)=${md5(prettifiedPath)}\n$prettifiedPath" + } + + private fun md5(prettifiedPath: String) = prettifiedPath.md5() + + fun md5() = prettifiedPath(fullPath()).md5() + + private fun prettifiedPath(path: List) = + path.joinToString(separator = "\n") { (stmt, depth, decision) -> + val prefix = when (decision) { + CALL_DECISION_NUM -> "call[${depth}] - " + "".padEnd(2 * depth, ' ') + RETURN_DECISION_NUM -> " ret[${depth - 1}] - " + "".padEnd(2 * depth, ' ') + else -> " " + "".padEnd(2 * depth, ' ') + } + "$prefix$stmt" + } + + fun definitelyFork() { + stateAnalyticsProperties.definitelyFork() + } + + fun updateIsVisitedNew() { + stateAnalyticsProperties.updateIsVisitedNew() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ExecutionState + + if (stmt != other.stmt) return false + if (symbolicState != other.symbolicState) return false + if (executionStack != other.executionStack) return false + if (path != other.path) return false + if (visitedStatementsHashesToCountInPath != other.visitedStatementsHashesToCountInPath) return false + if (decisionPath != other.decisionPath) return false + if (edges != other.edges) return false + if (stmts != other.stmts) return false + if (pathLength != other.pathLength) return false + if (lastEdge != other.lastEdge) return false + if (lastMethod != other.lastMethod) return false + if (methodResult != other.methodResult) return false + if (exception != other.exception) return false + + return true + } + + private val hashCode by lazy { + Objects.hash( + stmt, executionStack, path, visitedStatementsHashesToCountInPath, decisionPath, + edges, stmts, pathLength, lastEdge, lastMethod, methodResult, exception + ) + } + + override fun hashCode(): Int = hashCode + + var reward by stateAnalyticsProperties::reward + val features by stateAnalyticsProperties::features + var executingTime by stateAnalyticsProperties::executingTime + val depth by stateAnalyticsProperties::depth + var visitedBeforeLastFork by stateAnalyticsProperties::visitedBeforeLastFork + var visitedAfterLastFork by stateAnalyticsProperties::visitedAfterLastFork + var stmtsSinceLastCovered by stateAnalyticsProperties::stmtsSinceLastCovered + val parent by stateAnalyticsProperties::parent +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/state/ExecutionStateUpdates.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/state/ExecutionStateUpdates.kt new file mode 100644 index 0000000000..c7052a3016 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/state/ExecutionStateUpdates.kt @@ -0,0 +1,152 @@ +package org.utbot.engine.state + +import kotlinx.collections.immutable.plus +import org.utbot.engine.MethodResult +import org.utbot.engine.SymbolicFailure +import org.utbot.engine.SymbolicValue +import org.utbot.engine.putIfAbsent +import org.utbot.engine.symbolic.SymbolicStateUpdate +import soot.SootMethod +import soot.jimple.Stmt + +fun ExecutionState.createExceptionState( + exception: SymbolicFailure, + update: SymbolicStateUpdate +): ExecutionState { + val last = executionStack.last() + // go to negative indexing below CALL_DECISION_NUM for exceptions + val edge = Edge(stmt, stmt, CALL_DECISION_NUM - (++outgoingEdges)) + val localMemory = last.update(update.localMemoryUpdates) + return ExecutionState( + stmt = stmt, + symbolicState = symbolicState + update, + executionStack = executionStack.set(executionStack.lastIndex, localMemory), + path = path, + visitedStatementsHashesToCountInPath = visitedStatementsHashesToCountInPath, + decisionPath = decisionPath + edge.decisionNum, + edges = edges + edge, + stmts = stmts, + pathLength = pathLength + 1, + lastEdge = edge, + lastMethod = executionStack.last().method, + exception = exception, + label = label, + stateAnalyticsProperties = stateAnalyticsProperties.successorProperties(this) + ) +} + +fun ExecutionState.update( + edge: Edge, + symbolicStateUpdate: SymbolicStateUpdate, + doesntThrow: Boolean, +): ExecutionState { + val last = executionStack.last() + val stackElement = last.update( + symbolicStateUpdate.localMemoryUpdates, + last.doesntThrow || doesntThrow + ) + outgoingEdges++ + + val stmtHashCode = stmt.hashCode() + val stmtCountInPath = (visitedStatementsHashesToCountInPath[stmtHashCode] ?: 0) + 1 + + return ExecutionState( + stmt = edge.dst, + symbolicState = symbolicState + symbolicStateUpdate, + executionStack = executionStack.set(executionStack.lastIndex, stackElement), + path = path + stmt, + visitedStatementsHashesToCountInPath = visitedStatementsHashesToCountInPath.put( + stmtHashCode, + stmtCountInPath + ), + decisionPath = decisionPath + edge.decisionNum, + edges = edges + edge, + stmts = stmts.putIfAbsent(stmt, pathLength), + pathLength = pathLength + 1, + lastEdge = edge, + lastMethod = stackElement.method, + label = label, + stateAnalyticsProperties = stateAnalyticsProperties.successorProperties(this) + ) +} + +fun ExecutionState.withLabel(newLabel: StateLabel) = copy(label = newLabel) + + +fun ExecutionState.update( + stateUpdate: SymbolicStateUpdate +): ExecutionState { + val last = executionStack.last() + val stackElement = last.update(stateUpdate.localMemoryUpdates) + return copy( + symbolicState = symbolicState + stateUpdate, + executionStack = executionStack.set(executionStack.lastIndex, stackElement) + ) +} + +fun ExecutionState.push( + stmt: Stmt, + inputArguments: ArrayDeque, + update: SymbolicStateUpdate, + method: SootMethod +): ExecutionState { + val edge = Edge(this.stmt, stmt, CALL_DECISION_NUM) + val stackElement = ExecutionStackElement( + this.stmt, + localVariableMemory.memoryForNestedMethod().update(update.localMemoryUpdates), + inputArguments = inputArguments, + method = method, + doesntThrow = executionStack.last().doesntThrow + ) + outgoingEdges++ + + val stmtHashCode = this.stmt.hashCode() + val stmtCountInPath = (visitedStatementsHashesToCountInPath[stmtHashCode] ?: 0) + 1 + + return ExecutionState( + stmt = stmt, + symbolicState = symbolicState.stateForNestedMethod() + update, + executionStack = executionStack + stackElement, + path = path + this.stmt, + visitedStatementsHashesToCountInPath = visitedStatementsHashesToCountInPath.put( + stmtHashCode, + stmtCountInPath + ), + decisionPath = decisionPath + edge.decisionNum, + edges = edges + edge, + stmts = stmts.putIfAbsent(this.stmt, pathLength), + pathLength = pathLength + 1, + lastEdge = edge, + lastMethod = stackElement.method, + label = label, + stateAnalyticsProperties = stateAnalyticsProperties.successorProperties(this) + ) +} + +fun ExecutionState.pop(methodResult: MethodResult): ExecutionState { + val caller = executionStack.last().caller!! + val edge = Edge(stmt, caller, RETURN_DECISION_NUM) + + val stmtHashcode = stmt.hashCode() + val stmtCountInPath = (visitedStatementsHashesToCountInPath[stmtHashcode] ?: 0) + 1 + + return ExecutionState( + stmt = caller, + symbolicState = symbolicState, + executionStack = executionStack.removeAt(executionStack.lastIndex), + path = path + stmt, + visitedStatementsHashesToCountInPath = visitedStatementsHashesToCountInPath.put( + stmtHashcode, + stmtCountInPath + ), + decisionPath = decisionPath + edge.decisionNum, + edges = edges + edge, + stmts = stmts.putIfAbsent(stmt, pathLength), + pathLength = pathLength + 1, + lastEdge = edge, + lastMethod = executionStack.last().method, + methodResult = methodResult, + label = label, + stateAnalyticsProperties = stateAnalyticsProperties.successorProperties(this) + ) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/symbolic/SymbolicState.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/symbolic/SymbolicState.kt index cbe2d5c7ef..facd1b8f39 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/symbolic/SymbolicState.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/symbolic/SymbolicState.kt @@ -35,5 +35,4 @@ data class SymbolicState( fun stateForNestedMethod() = copy( memory = memory.memoryForNestedMethod() ) - -} \ No newline at end of file +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/symbolic/SymbolicStateUpdate.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/symbolic/SymbolicStateUpdate.kt index 0b883d0a76..63c73e60e6 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/symbolic/SymbolicStateUpdate.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/symbolic/SymbolicStateUpdate.kt @@ -22,13 +22,19 @@ sealed class Constraint>(constraints: Set = /** * Represents hard constraints. */ -class HardConstraint( +open class HardConstraint( constraints: Set = emptySet() ) : Constraint(constraints) { override fun plus(other: HardConstraint): HardConstraint = HardConstraint(addConstraints(other.constraints)) + + companion object { + internal val EMPTY: HardConstraint = HardConstraint() + } } +fun emptyHardConstraint(): HardConstraint = HardConstraint.EMPTY + /** * Represents soft constraints. */ @@ -37,8 +43,14 @@ class SoftConstraint( ) : Constraint(constraints) { override fun plus(other: SoftConstraint): SoftConstraint = SoftConstraint(addConstraints(other.constraints)) + + companion object { + internal val EMPTY: SoftConstraint = SoftConstraint() + } } +fun emptySoftConstraint(): SoftConstraint = SoftConstraint.EMPTY + /** * Represent constraints that must be satisfied for symbolic execution. * At the same time, if they don't, the state they belong to still @@ -52,17 +64,23 @@ class Assumption( override fun plus(other: Assumption): Assumption = Assumption(addConstraints(other.constraints)) override fun toString() = constraints.joinToString(System.lineSeparator()) + + companion object { + internal val EMPTY: Assumption = Assumption() + } } +fun emptyAssumption(): Assumption = Assumption.EMPTY + /** * Represents one or more updates that can be applied to [SymbolicState]. * * TODO: move [localMemoryUpdates] to another place */ data class SymbolicStateUpdate( - val hardConstraints: HardConstraint = HardConstraint(), - val softConstraints: SoftConstraint = SoftConstraint(), - val assumptions: Assumption = Assumption(), + val hardConstraints: HardConstraint = emptyHardConstraint(), + val softConstraints: SoftConstraint = emptySoftConstraint(), + val assumptions: Assumption = emptyAssumption(), val memoryUpdates: MemoryUpdate = MemoryUpdate(), val localMemoryUpdates: LocalMemoryUpdate = LocalMemoryUpdate() ) { @@ -107,7 +125,7 @@ fun Collection.asSoftConstraint() = SoftConstraint(transformTo fun UtBoolExpression.asSoftConstraint() = SoftConstraint(setOf(this)) -fun Collection.asAssumption() = Assumption(toSet()) +fun Collection.asAssumption() = Assumption(transformToSet()) fun UtBoolExpression.asAssumption() = Assumption(setOf(this)) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeRegistry.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeRegistry.kt new file mode 100644 index 0000000000..6c3d1f7865 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeRegistry.kt @@ -0,0 +1,585 @@ +package org.utbot.engine.types + +import com.google.common.collect.HashBiMap +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentSetOf +import org.utbot.common.WorkaroundReason +import org.utbot.common.doNotRun +import org.utbot.common.workaround +import org.utbot.engine.ChunkId +import org.utbot.engine.MAX_NUM_DIMENSIONS +import org.utbot.engine.MemoryUpdate +import org.utbot.engine.MethodResult +import org.utbot.engine.ObjectValue +import org.utbot.engine.TypeConstraint +import org.utbot.engine.TypeStorage +import org.utbot.engine.baseType +import org.utbot.engine.classBytecodeSignatureToClassNameOrNull +import org.utbot.engine.findMockAnnotationOrNull +import org.utbot.engine.getByValue +import org.utbot.engine.isAnonymous +import org.utbot.engine.isInappropriate +import org.utbot.engine.isJavaLangObject +import org.utbot.engine.nullObjectAddr +import org.utbot.engine.numDimensions +import org.utbot.engine.pc.UtAddrExpression +import org.utbot.engine.pc.UtAddrSort +import org.utbot.engine.pc.UtArrayExpressionBase +import org.utbot.engine.pc.UtArraySort +import org.utbot.engine.pc.UtBoolExpression +import org.utbot.engine.pc.UtBoolSort +import org.utbot.engine.pc.UtEqGenericTypeParametersExpression +import org.utbot.engine.pc.UtFalse +import org.utbot.engine.pc.UtGenericExpression +import org.utbot.engine.pc.UtInt32Sort +import org.utbot.engine.pc.UtIsExpression +import org.utbot.engine.pc.UtIsGenericTypeExpression +import org.utbot.engine.pc.UtMkTermArrayExpression +import org.utbot.engine.pc.UtTrue +import org.utbot.engine.pc.mkAnd +import org.utbot.engine.pc.mkArrayConst +import org.utbot.engine.pc.mkArrayWithConst +import org.utbot.engine.pc.mkEq +import org.utbot.engine.pc.mkInt +import org.utbot.engine.pc.mkNot +import org.utbot.engine.pc.mkOr +import org.utbot.engine.pc.select +import org.utbot.engine.pc.store +import org.utbot.engine.namedStore +import org.utbot.engine.symbolic.asHardConstraint +import org.utbot.engine.toIntValue +import org.utbot.engine.toPrimitiveValue +import soot.ArrayType +import soot.BooleanType +import soot.ByteType +import soot.CharType +import soot.DoubleType +import soot.FloatType +import soot.IntType +import soot.LongType +import soot.RefType +import soot.Scene +import soot.ShortType +import soot.SootClass +import soot.SootField +import soot.SootMethod +import soot.Type +import soot.tagkit.AnnotationClassElem +import java.math.BigInteger +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicLong + +/** + * Types/classes registry. + * + * Registers and keeps two mappings: + * - Type <-> unique type id (int) + * - Object address -> to type id + */ +class TypeRegistry { + init { + // initializes type storage for OBJECT_TYPE from current scene + objectTypeStorage = TypeStorage.constructTypeStorageUnsafe( + OBJECT_TYPE, + Scene.v().classes.mapTo(mutableSetOf()) { it.type } + ) + } + + private val typeIdBiMap = HashBiMap.create() + + // A cache for strings representing bit-vectors for some collection of types. + private val typesToBitVecString = mutableMapOf, String>() + private val typeToRating = mutableMapOf() + private val typeToInheritorsTypes = mutableMapOf>() + private val typeToAncestorsTypes = mutableMapOf>() + + // A BiMap containing bijection from every type to an address of the object + // presenting its classRef and vise versa + private val classRefBiMap = HashBiMap.create() + + /** + * Contains mapping from a class to the class containing substitutions for its methods. + */ + private val targetToSubstitution: Map by lazy { + val classesWithTargets = Scene.v().classes.mapNotNull { clazz -> + val annotation = clazz.findMockAnnotationOrNull + ?.elems + ?.singleOrNull { it.name == "target" } as? AnnotationClassElem + + val classNameFromSignature = classBytecodeSignatureToClassNameOrNull(annotation?.desc) + + if (classNameFromSignature == null) { + null + } else { + val target = Scene.v().getSootClass(classNameFromSignature) + target to clazz + } + } + classesWithTargets.toMap() + } + + /** + * Contains mapping from a class with substitutions of the methods of the target class to the target class itself. + */ + private val substitutionToTarget: Map by lazy { + targetToSubstitution.entries.associate { (k, v) -> v to k } + } + + private val typeToFields = mutableMapOf>() + + /** + * An array containing information about whether the object with particular addr could throw a [ClassCastException]. + * + * Note: all objects can throw it by default. + * @see disableCastClassExceptionCheck + */ + private var isClassCastExceptionAllowed: UtArrayExpressionBase = + mkArrayWithConst(UtArraySort(UtAddrSort, UtBoolSort), UtTrue) + + + /** + * Contains information about types for ReferenceValues. + * An element on some position k contains information about type for an object with address == k + * Each element in addrToTypeId is in range [1..numberOfTypes] + */ + private val addrToTypeId: UtArrayExpressionBase by lazy { + mkArrayConst( + "addrToTypeId", + UtAddrSort, + UtInt32Sort + ) + } + + private val genericAddrToTypeArrays = mutableMapOf() + + private fun genericAddrToType(i: Int) = genericAddrToTypeArrays.getOrPut(i) { + mkArrayConst( + "genericAddrToTypeId_$i", + UtAddrSort, + UtInt32Sort + ) + } + + /** + * Contains information about number of dimensions for ReferenceValues. + */ + private val addrToNumDimensions: UtArrayExpressionBase by lazy { + mkArrayConst( + "addrToNumDimensions", + UtAddrSort, + UtInt32Sort + ) + } + + private val genericAddrToNumDimensionsArrays = mutableMapOf() + + private fun genericAddrToNumDimensions(i: Int) = genericAddrToNumDimensionsArrays.getOrPut(i) { + mkArrayConst( + "genericAddrToNumDimensions_$i", + UtAddrSort, + UtInt32Sort + ) + } + + /** + * Contains information about whether the object with some addr is a mock or not. + */ + private val isMockArray: UtArrayExpressionBase by lazy { + mkArrayConst( + "isMock", + UtAddrSort, + UtBoolSort + ) + } + + /** + * Takes information about whether the object with [addr] is mock or not. + * + * @see isMockArray + */ + fun isMock(addr: UtAddrExpression) = isMockArray.select(addr) + + private fun mockCorrectnessConstraint(addr: UtAddrExpression) = + mkOr( + mkEq(isMock(addr), UtFalse), + mkNot(mkEq(addr, nullObjectAddr)) + ) + + fun isMockConstraint(addr: UtAddrExpression) = mkAnd( + mkOr( + mkEq(isMock(addr), UtTrue), + mkEq(addr, nullObjectAddr) + ), + mockCorrectnessConstraint(addr) + ) + + /** + * Makes the numbers of dimensions for every object in the program equal to zero by default + */ + fun softZeroNumDimensions() = UtMkTermArrayExpression(addrToNumDimensions) + + /** + * addrToTypeId is created as const array of the emptyType. If such type occurs anywhere in the program, it means + * we haven't touched the element that this type belongs to + * @see emptyTypeId + */ + fun softEmptyTypes() = UtMkTermArrayExpression(addrToTypeId, mkInt(emptyTypeId)) + + /** + * Calculates type 'rating' for a particular type. Used for ordering ObjectValue's possible types. + * The type with a higher rating is more likely than the one with a lower rating. + */ + fun findRating(type: RefType) = typeToRating.getOrPut(type) { + var finalCost = 0 + + val sootClass = type.sootClass + + // TODO: let's have "preferred types" + if (sootClass.name == "java.util.ArrayList") finalCost += 4096 + if (sootClass.name == "java.util.LinkedList") finalCost += 2048 + if (sootClass.name == "java.util.HashMap") finalCost += 4096 + if (sootClass.name == "java.util.TreeMap") finalCost += 2048 + if (sootClass.name == "java.util.HashSet") finalCost += 2048 + if (sootClass.name == "java.lang.Integer") finalCost += 8192 + if (sootClass.name == "java.lang.Character") finalCost += 8192 + if (sootClass.name == "java.lang.Double") finalCost += 8192 + if (sootClass.name == "java.lang.Long") finalCost += 8192 + + if (sootClass.packageName.startsWith("java.lang")) finalCost += 1024 + + if (sootClass.packageName.startsWith("java.util")) finalCost += 512 + + if (sootClass.packageName.startsWith("java")) finalCost += 128 + + if (sootClass.isPublic) finalCost += 16 + + if (sootClass.isPrivate) finalCost += -16 + + if ("blocking" in sootClass.name.toLowerCase()) finalCost -= 32 + + if (sootClass.type.isJavaLangObject()) finalCost += -32 + + if (sootClass.isAnonymous) finalCost += -128 + + if (sootClass.name.contains("$")) finalCost += -4096 + + if (sootClass.type.sootClass.isInappropriate) finalCost += -8192 + + finalCost + } + + private val classRefCounter = AtomicInteger(classRefAddrsInitialValue) + private fun nextClassRefAddr() = UtAddrExpression(classRefCounter.getAndIncrement()) + + private val symbolicReturnNameCounter = AtomicLong(symbolicReturnNameCounterInitialValue) + fun findNewSymbolicReturnValueName() = + workaround(WorkaroundReason.MAKE_SYMBOLIC) { "symbolicReturnValue\$${symbolicReturnNameCounter.incrementAndGet()}" } + + private val typeCounter = AtomicInteger(typeCounterInitialValue) + private fun nextTypeId() = typeCounter.getAndIncrement() + + /** + * Returns unique typeId for the given type + */ + fun findTypeId(type: Type): Int = typeIdBiMap.getOrPut(type) { nextTypeId() } + + /** + * Returns type for the given typeId + * + * @return If there is such typeId in the program, returns the corresponding type, otherwise returns null + */ + fun typeByIdOrNull(typeId: Int): Type? = typeIdBiMap.getByValue(typeId) + + /** + * Returns symbolic representation for a typeId corresponding to the given address + */ + fun symTypeId(addr: UtAddrExpression) = addrToTypeId.select(addr) + + /** + * Returns a symbolic representation for an [i]th type parameter + * corresponding to the given address + */ + fun genericTypeId(addr: UtAddrExpression, i: Int) = genericAddrToType(i).select(addr) + + /** + * Returns symbolic representation for a number of dimensions corresponding to the given address + */ + fun symNumDimensions(addr: UtAddrExpression) = addrToNumDimensions.select(addr) + + fun genericNumDimensions(addr: UtAddrExpression, i: Int) = genericAddrToNumDimensions(i).select(addr) + + /** + * Returns a constraint stating that number of dimensions for the given address is zero + */ + fun zeroDimensionConstraint(addr: UtAddrExpression) = mkEq(symNumDimensions(addr), mkInt(objectNumDimensions)) + + /** + * Constructs a binary bit-vector by the given types with length 'numberOfTypes'. Each position + * corresponding to one of the typeId. + * + * @param types the collection of possible type + * @return decimal string representing the binary bit-vector + */ + fun constructBitVecString(types: Collection) = typesToBitVecString.getOrPut(types) { + val initialValue = BigInteger(ByteArray(numberOfTypes) { 0 }) + + return types.fold(initialValue) { acc, type -> + val typeId = if (type is ArrayType) findTypeId(type.baseType) else findTypeId(type) + acc.setBit(typeId) + }.toString() + } + + /** + * Creates class reference, i.e. Class<Integer> + * + * Note: Uses type id as an address to have the one and the same class reference for all objects of one class + */ + fun createClassRef(baseType: Type, numDimensions: Int = 0): MethodResult { + val addr = classRefBiMap.getOrPut(baseType) { nextClassRefAddr() } + + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(CLASS_REF_TYPE) + val objectValue = ObjectValue(typeStorage, addr) + + val typeConstraint = typeConstraint(addr, typeStorage).all() + + val typeId = mkInt(findTypeId(baseType)) + val symNumDimensions = mkInt(numDimensions) + + val stores = persistentListOf( + namedStore(CLASS_REF_TYPE_DESCRIPTOR, addr, typeId), + namedStore(CLASS_REF_NUM_DIMENSIONS_DESCRIPTOR, addr, symNumDimensions) + ) + + val touchedDescriptors = persistentSetOf(CLASS_REF_TYPE_DESCRIPTOR, CLASS_REF_NUM_DIMENSIONS_DESCRIPTOR) + + val memoryUpdate = MemoryUpdate( + stores = stores, + touchedChunkDescriptors = touchedDescriptors, + ) + + return MethodResult(objectValue, typeConstraint.asHardConstraint(), memoryUpdates = memoryUpdate) + } + + /** + * Returns a list of inheritors for the given [type], including itself. + */ + fun findInheritorsIncludingTypes(type: RefType, defaultValue: () -> Set) = + typeToInheritorsTypes.getOrPut(type, defaultValue) + + /** + * Returns a list of ancestors for the given [type], including itself. + */ + fun findAncestorsIncludingTypes(type: RefType, defaultValue: () -> Set) = + typeToAncestorsTypes.getOrPut(type, defaultValue) + + fun findFields(type: RefType, defaultValue: () -> List) = + typeToFields.getOrPut(type, defaultValue) + + /** + * Returns a [TypeConstraint] instance for the given [addr] and [typeStorage]. + */ + fun typeConstraint(addr: UtAddrExpression, typeStorage: TypeStorage): TypeConstraint = + TypeConstraint( + constructIsExpression(addr, typeStorage), + mkEq(addr, nullObjectAddr), + constructCorrectnessConstraint(addr, typeStorage) + ) + + private fun constructIsExpression(addr: UtAddrExpression, typeStorage: TypeStorage): UtIsExpression = + UtIsExpression(addr, typeStorage, numberOfTypes) + + /** + * Returns a conjunction of the constraints responsible for the type construction: + * * typeId must be in range [[emptyTypeId]..[numberOfTypes]]; + * * numDimensions must be in range [0..[MAX_NUM_DIMENSIONS]]; + * * if the baseType for [TypeStorage.leastCommonType] is a [java.lang.Object], + * should be added constraints for primitive arrays to prevent + * impossible resolved types: Object[] must be at least primType[][]. + */ + private fun constructCorrectnessConstraint(addr: UtAddrExpression, typeStorage: TypeStorage): UtBoolExpression { + val symType = symTypeId(addr) + val symNumDimensions = symNumDimensions(addr) + val type = typeStorage.leastCommonType + + val constraints = mutableListOf() + + // add constraints for typeId, it must be in 0..numberOfTypes + constraints += org.utbot.engine.Ge(symType.toIntValue(), emptyTypeId.toPrimitiveValue()) + constraints += org.utbot.engine.Le(symType.toIntValue(), numberOfTypes.toPrimitiveValue()) + + // add constraints for number of dimensions, it must be in 0..MAX_NUM_DIMENSIONS + constraints += org.utbot.engine.Ge(symNumDimensions.toIntValue(), 0.toPrimitiveValue()) + constraints += org.utbot.engine.Le(symNumDimensions.toIntValue(), MAX_NUM_DIMENSIONS.toPrimitiveValue()) + + doNotRun { + // add constraints for object and arrays of primitives + if (type.baseType.isJavaLangObject()) { + primTypes.forEach { + val typesAreEqual = mkEq(symType, mkInt(findTypeId(it))) + val numDimensions = + org.utbot.engine.Gt(symNumDimensions.toIntValue(), type.numDimensions.toPrimitiveValue()) + constraints += mkOr(mkNot(typesAreEqual), numDimensions) + } + } + + // there are no arrays of anonymous classes + typeStorage.possibleConcreteTypes + .mapNotNull { (it.baseType as? RefType) } + .filter { it.sootClass.isAnonymous } + .forEach { + val typesAreEqual = mkEq(symType, mkInt(findTypeId(it))) + val numDimensions = mkEq(symNumDimensions.toIntValue(), mkInt(objectNumDimensions).toIntValue()) + constraints += mkOr(mkNot(typesAreEqual), numDimensions) + } + } + + return mkAnd(constraints) + } + + /** + * returns constraint representing, that object with address [addr] is parametrized by [types] type parameters. + * @see UtGenericExpression + */ + fun genericTypeParameterConstraint(addr: UtAddrExpression, types: List) = + UtGenericExpression(addr, types, numberOfTypes) + + /** + * Returns constraint representing that an object with address [addr] has the same type as the type parameter + * with index [i] of an object with address [baseAddr]. + * + * For a SomeCollection the type parameters are [A, B], where A and B are type variables + * with indices zero and one respectively. To connect some element of the collection with its generic type + * add to the constraints `typeConstraintToGenericTypeParameter(elementAddr, collectionAddr, typeParamIndex)`. + * + * @see UtIsGenericTypeExpression + */ + fun typeConstraintToGenericTypeParameter( + addr: UtAddrExpression, + baseAddr: UtAddrExpression, + i: Int + ): UtIsGenericTypeExpression = UtIsGenericTypeExpression(addr, baseAddr, i) + + /** + * Looks for a substitution for the given [method]. + * + * @param method a method to be substituted. + * @return substituted method if the given [method] has substitution, null otherwise. + * + * Note: all the methods in the class with substitutions will be returned instead of methods of the target class + * with the same name and parameters' types without any additional annotations. The only exception is `` + * method, substitutions will be returned only for constructors marked by [org.utbot.api.annotation.UtConstructorMock] + * annotation. + */ + fun findSubstitutionOrNull(method: SootMethod): SootMethod? { + val declaringClass = method.declaringClass + val classWithSubstitutions = targetToSubstitution[declaringClass] + + val substitutedMethod = classWithSubstitutions + ?.methods + ?.singleOrNull { it.name == method.name && it.parameterTypes == method.parameterTypes } + // Note: subSignature is not used in order to support `this` as method's return value. + // Otherwise we'd have to check for wrong `this` type in the subSignature + + if (method.isConstructor) { + // if the constructor doesn't have the mock annotation do not substitute it + substitutedMethod?.findMockAnnotationOrNull ?: return null + } + return substitutedMethod + } + + /** + * Returns a class containing substitutions for the methods belong to the target class, null if there is not such class. + */ + @Suppress("unused") + fun findSubstitutionByTargetOrNull(targetClass: SootClass): SootClass? = targetToSubstitution[targetClass] + + /** + * Returns a target class by given class with methods substitutions. + */ + @Suppress("MemberVisibilityCanBePrivate") + fun findTargetBySubstitutionOrNull(classWithSubstitutions: SootClass): SootClass? = + substitutionToTarget[classWithSubstitutions] + + /** + * Looks for 'real' type. + * + * For example, we have two classes: A and B, B contains substitutions for A's methods. + * `findRealType(a.type)` will return `a.type`, but `findRealType(b.type)` will return `a.type` as well. + * + * Returns: + * * [type] if it is not a RefType; + * * [type] if it is a RefType and it doesn't have a target class to substitute; + * * otherwise a type of the target class, which methods should be substituted. + */ + fun findRealType(type: Type): Type = + if (type !is RefType) type else findTargetBySubstitutionOrNull(type.sootClass)?.type ?: type + + /** + * Returns a select expression containing information about whether [ClassCastException] is allowed or not + * for an object with the given [addr]. + * + * True means that [ClassCastException] might be thrown, false will restrict it. + */ + fun isClassCastExceptionAllowed(addr: UtAddrExpression) = isClassCastExceptionAllowed.select(addr) + + /** + * Modify [isClassCastExceptionAllowed] to make impossible for a [ClassCastException] to be thrown for an object + * with the given [addr]. + */ + fun disableCastClassExceptionCheck(addr: UtAddrExpression) { + isClassCastExceptionAllowed = isClassCastExceptionAllowed.store(addr, UtFalse) + } + + /** + * Returns chunkId for the given [arrayType]. + * + * Examples: + * * Object[] -> RefValues_Arrays + * * int[] -> intArrays + * * int[][] -> MultiArrays + */ + fun arrayChunkId(arrayType: ArrayType) = when (arrayType.numDimensions) { + 1 -> if (arrayType.baseType is RefType) { + ChunkId("RefValues", "Arrays") + } else { + ChunkId("${findRealType(arrayType.baseType)}", "Arrays") + } + else -> ChunkId("Multi", "Arrays") + } + + companion object { + // we use different shifts to distinguish easily types from objects in z3 listings + const val objectCounterInitialValue = 0x00000001 // 0x00000000 is reserved for NULL + + // we want to reserve addresses for every ClassRef in the program starting from this one + // Note: the number had been chosen randomly and can be changes without any consequences + const val classRefAddrsInitialValue = -16777216 // -(2 ^ 24) + + // since we use typeId as addr for ConstRef, we can not use 0x00000000 because of NULL value + const val typeCounterInitialValue = 0x00000001 + const val symbolicReturnNameCounterInitialValue = 0x80000000 + const val objectNumDimensions = 0 + const val emptyTypeId = 0 + private const val primitivesNumber = 8 + + internal val primTypes + get() = listOf( + ByteType.v(), + ShortType.v(), + IntType.v(), + LongType.v(), + FloatType.v(), + DoubleType.v(), + BooleanType.v(), + CharType.v() + ) + + val numberOfTypes get() = Scene.v().classes.size + primitivesNumber + typeCounterInitialValue + + /** + * Stores [TypeStorage] for [OBJECT_TYPE]. As it should be changed when Soot scene changes, + * it is loaded each time when [TypeRegistry] is created in init section. + */ + lateinit var objectTypeStorage: TypeStorage + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt new file mode 100644 index 0000000000..ca88797cb3 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/types/TypeResolver.kt @@ -0,0 +1,609 @@ +package org.utbot.engine.types + +import kotlinx.collections.immutable.persistentListOf +import org.utbot.common.WorkaroundReason +import org.utbot.common.workaround +import org.utbot.engine.ArrayValue +import org.utbot.engine.ChunkId +import org.utbot.engine.Hierarchy +import org.utbot.engine.Memory +import org.utbot.engine.MemoryChunkDescriptor +import org.utbot.engine.MemoryUpdate +import org.utbot.engine.ObjectValue +import org.utbot.engine.ReferenceValue +import org.utbot.engine.TypeStorage +import org.utbot.engine.appropriateClasses +import org.utbot.engine.baseType +import org.utbot.engine.findMockAnnotationOrNull +import org.utbot.engine.isAppropriate +import org.utbot.engine.isArtificialEntity +import org.utbot.engine.isInappropriate +import org.utbot.engine.isJavaLangObject +import org.utbot.engine.isLambda +import org.utbot.engine.isOverridden +import org.utbot.engine.isUtMock +import org.utbot.engine.makeArrayType +import org.utbot.engine.nullObjectAddr +import org.utbot.engine.numDimensions +import org.utbot.engine.pc.UtAddrExpression +import org.utbot.engine.pc.UtBoolExpression +import org.utbot.engine.pc.UtEqGenericTypeParametersExpression +import org.utbot.engine.pc.mkAnd +import org.utbot.engine.pc.mkEq +import org.utbot.engine.rawType +import org.utbot.engine.wrapper +import org.utbot.engine.wrapperToClass +import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.util.UTBOT_FRAMEWORK_API_VISIBLE_PACKAGE +import soot.ArrayType +import soot.IntType +import soot.NullType +import soot.PrimType +import soot.RefType +import soot.Scene +import soot.SootClass +import soot.SootField +import soot.Type +import soot.VoidType +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CountDownLatch +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class TypeResolver(private val typeRegistry: TypeRegistry, private val hierarchy: Hierarchy) { + + fun findOrConstructInheritorsIncludingTypes(type: RefType) = typeRegistry.findInheritorsIncludingTypes(type) { + hierarchy.inheritors(type.sootClass.id).mapTo(mutableSetOf()) { it.type } + } + + fun findOrConstructAncestorsIncludingTypes(type: RefType) = typeRegistry.findAncestorsIncludingTypes(type) { + hierarchy.ancestors(type.sootClass.id).mapTo(mutableSetOf()) { it.type } + } + + /** + * Finds all the inheritors for each type from the [types] and returns their intersection. + * + * Note: every type from the result satisfies [isAppropriate] condition. + */ + fun intersectInheritors(types: Array): Set = intersectTypes(types) { + findOrConstructInheritorsIncludingTypes(it) + } + + /** + * Finds all the ancestors for each type from the [types] and return their intersection. + * + * Note: every type from the result satisfies [isAppropriate] condition. + */ + fun intersectAncestors(types: Array): Set = intersectTypes(types) { + findOrConstructAncestorsIncludingTypes(it) + } + + private fun intersectTypes( + types: Array, + retrieveFunction: (RefType) -> Set + ): Set { + val allObjects = findOrConstructInheritorsIncludingTypes(OBJECT_TYPE) + + // TODO we do not support constructions like List here, be aware of it + // TODO JIRA:1446 + + return types + .map { classOrDefault(it.rawType.typeName) } + .fold(allObjects) { acc, value -> acc.intersect(retrieveFunction(value)) } + .filter { it.sootClass.isAppropriate } + .toSet() + } + + private fun classOrDefault(typeName: String): RefType = + runCatching { Scene.v().getRefType(typeName) }.getOrDefault(OBJECT_TYPE) + + fun findFields(type: RefType) = typeRegistry.findFields(type) { + hierarchy + .ancestors(type.sootClass.id) + .flatMap { it.fields } + } + + /** + * Returns given number of appropriate types that have the highest rating. + * + * @param types Collection of types to sort + * @param take Number of types to take + * + * @see TypeRegistry.findRating + * @see appropriateClasses + */ + fun findTopRatedTypes(types: Collection, take: Int = Int.MAX_VALUE) = + types.appropriateClasses() + .sortedByDescending { type -> + val baseType = if (type is ArrayType) type.baseType else type + // primitive baseType has the highest possible rating + if (baseType is RefType) typeRegistry.findRating(baseType) else Int.MAX_VALUE + } + .take(take) + + /** + * Constructs a [TypeStorage] instance containing [type] as its most common type and + * appropriate types from [possibleTypes] in its [TypeStorage.possibleConcreteTypes]. + * + * @param type the most common type of the constructed type storage. + * @param possibleTypes a list of types to be contained in the constructed type storage. + * + * @return constructed type storage. + * + * Note: [TypeStorage.possibleConcreteTypes] of the type storage returned by this method contains only + * classes we can instantiate: there will be no interfaces, abstract or local classes. + * If there are no such classes, [TypeStorage.possibleConcreteTypes] is an empty set. + * + * @see isAppropriate + */ + fun constructTypeStorage(type: Type, possibleTypes: Collection): TypeStorage { + val concretePossibleTypes = possibleTypes + .map { (if (it is ArrayType) it.baseType else it) to it.numDimensions } + .filterNot { (baseType, numDimensions) -> isInappropriateOrArrayOfMocks(numDimensions, baseType) } + .mapTo(mutableSetOf()) { (baseType, numDimensions) -> + if (numDimensions == 0) baseType else baseType.makeArrayType(numDimensions) + } + + return TypeStorage.constructTypeStorageUnsafe(type, concretePossibleTypes).removeInappropriateTypes() + } + + private fun isInappropriateOrArrayOfMocks(numDimensions: Int, baseType: Type?): Boolean { + if (baseType !is RefType) { + return false + } + + val baseSootClass = baseType.sootClass + + // We don't want to have our wrapper's classes as a part of a regular TypeStorage instance + // Note that we cannot have here 'isOverridden' since iterators of our wrappers are not wrappers + if (wrapperToClass[baseType] != null) { + return true + } + + if (numDimensions == 0 && baseSootClass.isInappropriate) { + // interface, abstract class, or mock could not be constructed + return true + } + + if (numDimensions > 0 && baseSootClass.findMockAnnotationOrNull != null) { + // array of mocks could not be constructed, but array of interfaces or abstract classes could be + return true + } + + return false + } + + /** + * Constructs a [TypeStorage] instance for given [type]. + * Depending on [useConcreteType] it will or will not contain type's inheritors. + * + * @param type a type for which we want to construct type storage. + * @param useConcreteType a boolean parameter to determine whether we want to include inheritors of the type or not. + * + * @return constructed type storage. + * + * Note: [TypeStorage.possibleConcreteTypes] of the type storage returned by this method contains only + * classes we can instantiate: there will be no interfaces, abstract or local classes. + * If there are no such classes, [TypeStorage.possibleConcreteTypes] is an empty set. + * + * @see isAppropriate + */ + fun constructTypeStorage(type: Type, useConcreteType: Boolean): TypeStorage { + // create a typeStorage with concreteType even if the type belongs to an interface or an abstract class + if (useConcreteType) return TypeStorage.constructTypeStorageWithSingleType(type) + + val baseType = type.baseType + + val inheritors = if (baseType !is RefType) { + setOf(baseType) + } else { + // use only 'appropriate' classes in the TypeStorage construction + val allInheritors = findOrConstructInheritorsIncludingTypes(baseType) + + // if the type is ArrayType, we don't have to filter abstract classes and interfaces from the inheritors + // because we still can instantiate, i.e., Number[]. + if (type is ArrayType) { + allInheritors + } else { + allInheritors.filterTo(mutableSetOf()) { it.sootClass.isAppropriate } + } + } + + val extendedInheritors = if (baseType.isJavaLangObject()) inheritors + TypeRegistry.primTypes else inheritors + + val possibleTypes = when (type) { + is RefType, is PrimType -> extendedInheritors + is ArrayType -> when (baseType) { + is RefType -> extendedInheritors.map { it.makeArrayType(type.numDimensions) }.toSet() + else -> setOf(baseType.makeArrayType(type.numDimensions)) + } + else -> error("Unexpected type $type") + } + + return TypeStorage.constructTypeStorageUnsafe(type, possibleTypes).removeInappropriateTypes() + } + + /** + * Remove wrapper types, classes from the visible for Soot package and, if any other type is available, artificial entities. + */ + private fun TypeStorage.removeInappropriateTypes(): TypeStorage { + val leastCommonSootClass = (leastCommonType as? RefType)?.sootClass + val keepArtificialEntities = leastCommonSootClass?.isArtificialEntity == true + + val appropriateTypes = possibleConcreteTypes.filter { + // All not RefType should be included in the concreteTypes, e.g., arrays + val sootClass = (it.baseType as? RefType)?.sootClass ?: return@filter true + + // All artificial entities except anonymous functions should be filtered out if we have another types + if (sootClass.isArtificialEntity) { + if (sootClass.isLambda) { + return@filter true + } + + return@filter keepArtificialEntities + } + + // All wrappers and classes from the visible for Soot package should be filtered out because they could not be instantiated + workaround(WorkaroundReason.HACK) { + if (leastCommonSootClass == OBJECT_TYPE && sootClass.isOverridden) { + return@filter false + } + + if (sootClass.packageName == UTBOT_FRAMEWORK_API_VISIBLE_PACKAGE) { + return@filter false + } + } + + return@filter true + }.toSet() + + return TypeStorage.constructTypeStorageUnsafe(leastCommonType, appropriateTypes) + } + + /** + * Constructs a nullObject with TypeStorage containing all the inheritors for the given type + */ + fun nullObject(type: Type): ReferenceValue { + val typeStorage = TypeStorage.constructTypeStorageWithSingleType(type) + + return when (type) { + is RefType, is NullType, is VoidType -> ObjectValue(typeStorage, nullObjectAddr) + is ArrayType -> ArrayValue(typeStorage, nullObjectAddr) + else -> error("Unsupported nullType $type") + } + } + + fun downCast(arrayValue: ArrayValue, typeToCast: ArrayType): ArrayValue { + val typesBeforeCast = findOrConstructInheritorsIncludingTypes(arrayValue.type.baseType as RefType) + val typesAfterCast = findOrConstructInheritorsIncludingTypes(typeToCast.baseType as RefType) + val possibleTypes = typesBeforeCast.filter { it in typesAfterCast }.map { + it.makeArrayType(arrayValue.type.numDimensions) + } + + return arrayValue.copy(typeStorage = constructTypeStorage(typeToCast, possibleTypes)) + } + + fun downCast(objectValue: ObjectValue, typeToCast: RefType): ObjectValue { + val inheritorsTypes = findOrConstructInheritorsIncludingTypes(typeToCast) + val possibleTypes = objectValue.possibleConcreteTypes.filter { it in inheritorsTypes } + + return wrapper(typeToCast, objectValue.addr) ?: objectValue.copy( + typeStorage = constructTypeStorage( + typeToCast, + possibleTypes + ) + ) + } + + /** + * Connects types and number of dimensions for the two given addresses. Uses for reading from arrays: + * cell should have the same type and number of dimensions as the objects taken/put from/in it. + * It is a simplification, because the object can be subtype of the type of the cell, but it is ignored for now. + */ + fun connectArrayCeilType(ceilAddr: UtAddrExpression, valueAddr: UtAddrExpression): UtBoolExpression { + val ceilSymType = typeRegistry.symTypeId(ceilAddr) + val valueSymType = typeRegistry.symTypeId(valueAddr) + val ceilSymDimension = typeRegistry.symNumDimensions(ceilAddr) + val valueSymDimension = typeRegistry.symNumDimensions(valueAddr) + + return mkAnd(mkEq(ceilSymType, valueSymType), mkEq(ceilSymDimension, valueSymDimension)) + } + + fun findAnyConcreteInheritorIncludingOrDefaultUnsafe(evaluatedType: RefType, defaultType: RefType): RefType = + findAnyConcreteInheritorIncluding(evaluatedType) ?: findAnyConcreteInheritorIncluding(defaultType) + ?: error("No concrete types found neither for $evaluatedType, nor for $defaultType") + + fun findAnyConcreteInheritorIncludingOrDefault(evaluatedType: RefType, defaultType: RefType): RefType? = + findAnyConcreteInheritorIncluding(evaluatedType) ?: findAnyConcreteInheritorIncluding(defaultType) + + private fun findAnyConcreteInheritorIncluding(type: RefType): RefType? = + if (type.sootClass.isAppropriate) { + type + } else { + findOrConstructInheritorsIncludingTypes(type) + .filterNot { !it.hasSootClass() && (it.sootClass.isOverridden || it.sootClass.isUtMock) } + .sortedByDescending { typeRegistry.findRating(it) } + .firstOrNull { it.sootClass.isAppropriate } + } + + companion object { + private fun createUpdateForGenericTypeInfo( + addr: UtAddrExpression, + typeStorages: List + ) = MemoryUpdate(genericTypeStorageByAddr = persistentListOf(addr to typeStorages)) + + /** + * Extracts type information for an object with specified [addr] from the [memory]. + * If it contains more type storages than one, or it contains an empty list of storages, + * an error will be thrown. + * + * [objectClassName] is used for an error message. + */ + fun extractTypeStorageForObjectWithSingleTypeParameter( + addr: UtAddrExpression, + objectClassName: String, + memory: Memory + ): TypeStorage? { + val valueTypeFromGenerics = memory.getTypeStoragesForObjectTypeParameters(addr) + + if (valueTypeFromGenerics != null && valueTypeFromGenerics.size != 1) { + error("$objectClassName must have only one type parameter, but it got ${valueTypeFromGenerics.size}") + } + + return valueTypeFromGenerics?.single() + } + + /** + * Creates a memory update for setting types storages for [firstAddr]'s + * type parameters equal to type storages for [secondAddr]'s type parameters + * according to provided types injection represented by [indexInjection]. + * + * [genericTypeStorageByAddr] is a storage from which this type information is extracted. + */ + private fun setParameterTypeStoragesEquality( + firstAddr: UtAddrExpression, + secondAddr: UtAddrExpression, + indexInjection: Array>, + genericTypeStorageByAddr: Map> + ): MemoryUpdate { + val existingGenericTypes = genericTypeStorageByAddr[secondAddr] ?: return MemoryUpdate() + + val currentGenericTypes = mutableMapOf() + + indexInjection.forEach { (from, to) -> + require(from >= 0 && from < existingGenericTypes.size) { + "Type injection is out of bounds: should be in [0; ${existingGenericTypes.size}) but is $from" + } + + currentGenericTypes[to] = existingGenericTypes[from] + } + + return createUpdateForGenericTypeInfo( + firstAddr, + currentGenericTypes + .entries + .sortedBy { it.key } + .mapTo(mutableListOf()) { it.value } + ) + } + + /** + * Returns a constraint representing that type parameters of an object + * with address [firstAddr] are equal to type parameters of an object + * with address [secondAddr], corresponding to [indexInjection], + * and a memory update for it. + * + * [genericTypeStorageByAddr] is a storage from which this type information is extracted. + * + * @see UtEqGenericTypeParametersExpression + */ + @Suppress("unused") + fun eqGenericTypeParametersConstraint( + firstAddr: UtAddrExpression, + secondAddr: UtAddrExpression, + genericTypeStorageByAddr: Map>, + vararg indexInjection: Pair + ): Pair { + val memoryUpdate = setParameterTypeStoragesEquality( + firstAddr, + secondAddr, + indexInjection, + genericTypeStorageByAddr + ) + + return UtEqGenericTypeParametersExpression(firstAddr, secondAddr, mapOf(*indexInjection)) to memoryUpdate + } + + /** + * Returns a constraint representing that type parameters of an object with address [firstAddr] + * are equal to the corresponding type parameters of an object with address [secondAddr] + * and a corresponding memory update. + * + * [genericTypeStorageByAddr] is a storage from which this type information is extracted. + * + * @see UtEqGenericTypeParametersExpression + */ + fun eqGenericTypeParametersConstraint( + firstAddr: UtAddrExpression, + secondAddr: UtAddrExpression, + parameterSize: Int, + genericTypeStorageByAddr: Map> + ) : Pair { + val injections = Array(parameterSize) { it to it } + + return eqGenericTypeParametersConstraint(firstAddr, secondAddr, genericTypeStorageByAddr, *injections) + } + + /** + * Returns a constraint representing that the first type parameter + * of an object with address [firstAddr] is equal to the first + * type parameter of an object with address [secondAddr]. + * + * [genericTypeStorageByAddr] is a storage from which this type information is extracted. + * + * @see UtEqGenericTypeParametersExpression + */ + fun eqGenericSingleTypeParameterConstraint( + firstAddr: UtAddrExpression, + secondAddr: UtAddrExpression, + genericTypeStorageByAddr: Map> + ): Pair = + eqGenericTypeParametersConstraint(firstAddr, secondAddr, genericTypeStorageByAddr, 0 to 0) + + /** + * Creates a memory update for associating provided [typeStorages] with an object with the provided [addr]. + */ + fun createGenericTypeInfoUpdate( + addr: UtAddrExpression, + typeStorages: List, + genericTypeStorageByAddr: Map> + ): MemoryUpdate { + if (addr !in genericTypeStorageByAddr.keys) { + return createUpdateForGenericTypeInfo(addr, typeStorages) + } + + val alreadyAddedTypeStorages = genericTypeStorageByAddr.getValue(addr) + + // Because of the design decision for genericTypeStorage map, it contains a + // mapping from addresses to associated with them type arguments. + // Therefore, first element of the list is a first type argument for the instance, and so on. + // To update type information, we have to update a corresponding type storage. + // Because of that, update is only possible when we have information about all type arguments. + require(typeStorages.size == alreadyAddedTypeStorages.size) { + "Wrong number of type storages is provided," + + " expected ${alreadyAddedTypeStorages.size} arguments," + + " but only ${typeStorages.size} found" + } + + val modifiedTypeStorages = alreadyAddedTypeStorages.mapIndexed { index, typeStorage -> + val newTypeStorage = typeStorages[index] + + val updatedTypes = typeStorage.possibleConcreteTypes.intersect(newTypeStorage.possibleConcreteTypes) + + // TODO should be really the least common type + // we have two type storages and know that one of them is subset of another one. + // Therefore, when we intersect them, we should chose correct least common type among them, + // but we don't do it here since it is not obvious, what is a correct way to do it. + // There is no access from here to typeResolver or Hierarchy, so it need to be + // reconsidered in the future, how to intersect type storages here or extract this function. + // For now we just take a leastCommonType from a type storage that contains less + // possible concrete types. + val alreadyAddedSize = typeStorage.possibleConcreteTypes.size + val newTypesSize = newTypeStorage.possibleConcreteTypes.size + val leastCommonType = if (alreadyAddedSize < newTypesSize) { + typeStorage.leastCommonType + } else { + newTypeStorage.leastCommonType + } + + TypeStorage.constructTypeStorageUnsafe(leastCommonType, updatedTypes) + } + + return createUpdateForGenericTypeInfo(addr, modifiedTypeStorages) + } + } +} + +internal const val NUMBER_OF_PREFERRED_TYPES = 3 + +internal val SootField.isEnumOrdinal + get() = this.name == "ordinal" && this.declaringClass.name == ENUM_CLASSNAME + +internal val ENUM_CLASSNAME: String = java.lang.Enum::class.java.canonicalName +internal val ENUM_ORDINAL = ChunkId(ENUM_CLASSNAME, "ordinal") +internal val CLASS_REF_CLASSNAME: String = Class::class.java.canonicalName +internal val CLASS_REF_CLASS_ID = Class::class.java.id + +internal val CLASS_REF_TYPE_DESCRIPTOR: MemoryChunkDescriptor + get() = MemoryChunkDescriptor( + ChunkId(CLASS_REF_CLASSNAME, "modeledType"), + CLASS_REF_TYPE, + IntType.v() + ) + +internal val CLASS_REF_NUM_DIMENSIONS_DESCRIPTOR: MemoryChunkDescriptor + get() = MemoryChunkDescriptor( + ChunkId(CLASS_REF_CLASSNAME, "modeledNumDimensions"), + CLASS_REF_TYPE, + IntType.v() + ) + +internal val CLASS_REF_SOOT_CLASS: SootClass + get() = Scene.v().getSootClass(CLASS_REF_CLASSNAME) +internal val ARRAYS_SOOT_CLASS: SootClass + get() = Scene.v().getSootClass(java.util.Arrays::class.java.canonicalName) + +internal val OBJECT_TYPE: RefType + get() = Scene.v().getSootClass(Object::class.java.canonicalName).type +internal val THROWABLE_TYPE: RefType + get() = Scene.v().getSootClass(Throwable::class.java.canonicalName).type +internal val STRING_TYPE: RefType + get() = Scene.v().getSootClass(String::class.java.canonicalName).type +internal val STRING_BUILDER_TYPE: RefType + get() = Scene.v().getSootClass(java.lang.StringBuilder::class.java.canonicalName).type +internal val STRING_BUFFER_TYPE: RefType + get() = Scene.v().getSootClass(java.lang.StringBuffer::class.java.canonicalName).type +internal val OPTIONAL_TYPE: RefType + get() = Scene.v().getSootClass(java.util.Optional::class.java.canonicalName).type +internal val OPTIONAL_INT_TYPE: RefType + get() = Scene.v().getSootClass(java.util.OptionalInt::class.java.canonicalName).type +internal val OPTIONAL_LONG_TYPE: RefType + get() = Scene.v().getSootClass(java.util.OptionalLong::class.java.canonicalName).type +internal val OPTIONAL_DOUBLE_TYPE: RefType + get() = Scene.v().getSootClass(java.util.OptionalDouble::class.java.canonicalName).type +internal val CLASS_REF_TYPE: RefType + get() = CLASS_REF_SOOT_CLASS.type +internal val THREAD_TYPE: RefType + get() = Scene.v().getSootClass(Thread::class.java.canonicalName).type +internal val THREAD_GROUP_TYPE: RefType + get() = Scene.v().getSootClass(ThreadGroup::class.java.canonicalName).type +internal val COMPLETABLE_FUTURE_TYPE: RefType + get() = Scene.v().getSootClass(CompletableFuture::class.java.canonicalName).type +internal val EXECUTORS_TYPE: RefType + get() = Scene.v().getSootClass(Executors::class.java.canonicalName).type +internal val EXECUTOR_SERVICE_TYPE: RefType + get() = Scene.v().getSootClass(ExecutorService::class.java.canonicalName).type +internal val COUNT_DOWN_LATCH_TYPE: RefType + get() = Scene.v().getSootClass(CountDownLatch::class.java.canonicalName).type +internal val SECURITY_MANAGER_TYPE: RefType + get() = Scene.v().getSootClass(SecurityManager::class.java.canonicalName).type + +internal val NEW_INSTANCE_SIGNATURE: String = CLASS_REF_SOOT_CLASS.getMethodByName("newInstance").subSignature + +internal val HASHCODE_SIGNATURE: String = + Scene.v() + .getSootClass(Object::class.java.canonicalName) + .getMethodByName(Object::hashCode.name) + .subSignature + +internal val EQUALS_SIGNATURE: String = + Scene.v() + .getSootClass(Object::class.java.canonicalName) + .getMethodByName(Object::equals.name) + .subSignature + +/** + * Represents [java.lang.System.security] field signature. + * Hardcoded string literal because it is differently processed in Android. + */ +internal const val SECURITY_FIELD_SIGNATURE: String = "" + +/** + * Represents [sun.reflect.Reflection.fieldFilterMap] field signature. + * Hardcoded string literal because [sun.reflect.Reflection] is removed in Java 11. + */ +internal const val FIELD_FILTER_MAP_FIELD_SIGNATURE: String = "" + +/** + * Represents [sun.reflect.Reflection.methodFilterMap] field signature. + * Hardcoded string literal because [sun.reflect.Reflection] is removed in Java 11. + */ +internal const val METHOD_FILTER_MAP_FIELD_SIGNATURE: String = "" + +/** + * Special type represents string literal, which is not String Java object + */ +object SeqType : Type() { + override fun toString() = "SeqType" +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/Exceptions.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/Exceptions.kt new file mode 100644 index 0000000000..335a36929e --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/Exceptions.kt @@ -0,0 +1,13 @@ +package org.utbot.engine.util.mockListeners + +import kotlinx.coroutines.CancellationException + +/** + * Exception used in [org.utbot.engine.util.mockListeners.ForceMockListener]. + */ +class ForceMockCancellationException: CancellationException("Forced mocks without Mockito") + +/** + * Exception used in [org.utbot.engine.util.mockListeners.ForceStaticMockListener]. + */ +class ForceStaticMockCancellationException: CancellationException("Forced static mocks without Mockito-inline") \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/ForceMockListener.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/ForceMockListener.kt index 756cef4059..ac9001dd39 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/ForceMockListener.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/ForceMockListener.kt @@ -1,21 +1,27 @@ package org.utbot.engine.util.mockListeners import org.utbot.engine.EngineController import org.utbot.engine.MockStrategy -import org.utbot.engine.util.mockListeners.exceptions.ForceMockCancellationException +import org.utbot.engine.UtMockInfo +import org.utbot.framework.plugin.api.TestCaseGenerator +import org.utbot.framework.util.Conflict +import org.utbot.framework.util.ConflictTriggers /** * Listener for mocker events in [org.utbot.engine.UtBotSymbolicEngine]. - * If forced mock happened, cancels the engine job. * * Supposed to be created only if Mockito is not installed. */ -class ForceMockListener: MockListener { - var forceMockHappened = false - private set +class ForceMockListener private constructor(triggers: ConflictTriggers): MockListener(triggers) { + override fun onShouldMock(controller: EngineController, strategy: MockStrategy, mockInfo: UtMockInfo) { + triggers[Conflict.ForceMockHappened] = true + } + + companion object { + fun create(testCaseGenerator: TestCaseGenerator, conflictTriggers: ConflictTriggers) : ForceMockListener { + val listener = ForceMockListener(conflictTriggers) + testCaseGenerator.engineActions.add { engine -> engine.attachMockListener(listener) } - override fun onShouldMock(controller: EngineController, strategy: MockStrategy) { - // If force mocking happened -- сancel engine job - controller.job?.cancel(ForceMockCancellationException()) - forceMockHappened = true + return listener + } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/ForceStaticMockListener.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/ForceStaticMockListener.kt new file mode 100644 index 0000000000..4bd3330c46 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/ForceStaticMockListener.kt @@ -0,0 +1,31 @@ +package org.utbot.engine.util.mockListeners + +import org.utbot.engine.EngineController +import org.utbot.engine.MockStrategy +import org.utbot.engine.UtMockInfo +import org.utbot.engine.UtNewInstanceMockInfo +import org.utbot.engine.UtStaticMethodMockInfo +import org.utbot.engine.UtStaticObjectMockInfo +import org.utbot.framework.plugin.api.TestCaseGenerator +import org.utbot.framework.util.Conflict +import org.utbot.framework.util.ConflictTriggers + +/** + * Listener for mocker events in [org.utbot.engine.UtBotSymbolicEngine]. + * + * Supposed to be created only if Mockito inline is not installed. + */ +class ForceStaticMockListener private constructor(triggers: ConflictTriggers): MockListener(triggers) { + override fun onShouldMock(controller: EngineController, strategy: MockStrategy, mockInfo: UtMockInfo) { + triggers[Conflict.ForceStaticMockHappened] = true + } + + companion object { + fun create(testCaseGenerator: TestCaseGenerator, conflictTriggers: ConflictTriggers) : ForceStaticMockListener { + val listener = ForceStaticMockListener(conflictTriggers) + testCaseGenerator.engineActions.add { engine -> engine.attachMockListener(listener) } + + return listener + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/MockListener.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/MockListener.kt index 5d004e3a1c..add21df749 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/MockListener.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/MockListener.kt @@ -2,10 +2,19 @@ package org.utbot.engine.util.mockListeners import org.utbot.engine.EngineController import org.utbot.engine.MockStrategy +import org.utbot.engine.UtMockInfo +import org.utbot.framework.plugin.api.TestCaseGenerator +import org.utbot.framework.util.ConflictTriggers /** * Listener that can be attached using [MockListenerController] to mocker in [org.utbot.engine.UtBotSymbolicEngine]. */ -interface MockListener { - fun onShouldMock(controller: EngineController, strategy: MockStrategy) +abstract class MockListener( + val triggers: ConflictTriggers +) { + abstract fun onShouldMock(controller: EngineController, strategy: MockStrategy, mockInfo: UtMockInfo) + + fun detach(testCaseGenerator: TestCaseGenerator, listener: MockListener) { + testCaseGenerator.engineActions.add { engine -> engine.detachMockListener(listener) } + } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/MockListenerController.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/MockListenerController.kt index 765b0edb04..88e07c722c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/MockListenerController.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/MockListenerController.kt @@ -2,6 +2,7 @@ package org.utbot.engine.util.mockListeners import org.utbot.engine.EngineController import org.utbot.engine.MockStrategy +import org.utbot.engine.UtMockInfo /** * Controller that allows to attach listeners to mocker in [org.utbot.engine.UtBotSymbolicEngine]. @@ -13,7 +14,11 @@ class MockListenerController(private val controller: EngineController) { listeners += listener } - fun onShouldMock(strategy: MockStrategy) { - listeners.map { it.onShouldMock(controller, strategy) } + fun detach(listener: MockListener) { + listeners -= listener + } + + fun onShouldMock(strategy: MockStrategy, mockInfo: UtMockInfo) { + listeners.map { it.onShouldMock(controller, strategy, mockInfo) } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/exceptions/ForceMockCancellationException.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/exceptions/ForceMockCancellationException.kt deleted file mode 100644 index d5b2e3d86c..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/util/mockListeners/exceptions/ForceMockCancellationException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.utbot.engine.util.mockListeners.exceptions - -import kotlinx.coroutines.CancellationException - -/** - * Exception used in [org.utbot.engine.util.mockListeners.ForceMockListener]. - */ -class ForceMockCancellationException: CancellationException("Forced mocks without Mockito") diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt index 84deca8af5..4838dbe940 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/util/statics/concrete/EnumConcreteUtils.kt @@ -9,8 +9,10 @@ import org.utbot.engine.pc.mkNot import org.utbot.engine.pc.select import org.utbot.engine.symbolic.SymbolicStateUpdate import org.utbot.engine.symbolic.asHardConstraint +import org.utbot.engine.types.TypeResolver import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.util.field +import org.utbot.framework.plugin.api.util.fieldId +import org.utbot.framework.plugin.api.util.jField import soot.SootClass import soot.SootField import soot.SootMethod @@ -20,7 +22,7 @@ import soot.jimple.StaticFieldRef import soot.jimple.Stmt import soot.jimple.internal.JAssignStmt -fun UtBotSymbolicEngine.makeSymbolicValuesFromEnumConcreteValues( +fun Traverser.makeSymbolicValuesFromEnumConcreteValues( type: Type, enumConstantRuntimeValues: List> ): Pair, Map> { @@ -43,7 +45,7 @@ fun associateEnumSootFieldsWithConcreteValues( enumConstants: List> ): List>> = enumFields.map { enumSootField -> - val enumField = enumSootField.fieldId.field + val enumField = enumSootField.fieldId.jField val fieldValues = if (enumSootField.isStatic) { val staticFieldValue = enumField.withAccessibility { enumField.get(null) } @@ -62,13 +64,13 @@ fun associateEnumSootFieldsWithConcreteValues( /** * Construct symbolic updates for enum static fields and a symbolic value for a local in the left part of the assignment. */ -fun UtBotSymbolicEngine.makeEnumStaticFieldsUpdates( +fun Traverser.makeEnumStaticFieldsUpdates( staticFields: List>>, declaringClass: SootClass, enumConstantSymbolicResultsByName: Map, enumConstantSymbolicValues: List, enumClassValue: ObjectValue, - fieldId: FieldId + fieldId: FieldId? ): Pair { var staticFieldsUpdates = SymbolicStateUpdate() var symbolicValueForLocal: SymbolicValue? = null @@ -101,7 +103,7 @@ fun UtBotSymbolicEngine.makeEnumStaticFieldsUpdates( } // save value to associate it with local if required - if (sootStaticField.name == fieldId.name) { + if (fieldId != null && sootStaticField.name == fieldId.name) { symbolicValueForLocal = fieldSymbolicValue } } @@ -109,7 +111,7 @@ fun UtBotSymbolicEngine.makeEnumStaticFieldsUpdates( return staticFieldsUpdates to symbolicValueForLocal } -fun UtBotSymbolicEngine.makeEnumNonStaticFieldsUpdates( +fun Traverser.makeEnumNonStaticFieldsUpdates( enumConstantSymbolicValues: List, nonStaticFields: List>> ): SymbolicStateUpdate { diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/z3/Extensions.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/z3/Extensions.kt index c1d120cd9e..179bf82133 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/z3/Extensions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/z3/Extensions.kt @@ -32,15 +32,15 @@ import soot.Type private val Z3Variable.unsigned: Boolean get() = this.type is CharType -fun Expr.value(unsigned: Boolean = false): Any = when (this) { +fun Expr<*>.value(unsigned: Boolean = false): Any = when (this) { is BitVecNum -> this.toIntNum(unsigned) is FPNum -> this.toFloatingPointNum() is BoolExpr -> this.boolValue == Z3_lbool.Z3_L_TRUE - is SeqExpr -> convertSolverString(this.string) + is SeqExpr<*> -> convertSolverString(this.string) else -> error("${this::class}: $this") } -internal fun Expr.intValue() = this.value() as Int +internal fun Expr<*>.intValue() = this.value() as Int /** * Converts a variable to given type. @@ -51,7 +51,7 @@ internal fun Expr.intValue() = this.value() as Int * * @throws IllegalStateException if given expression cannot be convert to given sort */ -internal fun Context.convertVar(variable: Z3Variable, toType: Type): Z3Variable { +fun Context.convertVar(variable: Z3Variable, toType: Type): Z3Variable { if (toType == variable.type) return variable if (variable.type is ByteType && toType is CharType) { return convertVar(convertVar(variable, IntType.v()), toType) @@ -64,7 +64,7 @@ internal fun Context.convertVar(variable: Z3Variable, toType: Type): Z3Variable * * @throws IllegalStateException if given expression cannot be convert to given sort */ -internal fun Context.convertVar(variable: Z3Variable, sort: Sort): Expr { +internal fun Context.convertVar(variable: Z3Variable, sort: Sort): Expr<*> { val expr = variable.expr return when { sort == expr.sort -> expr @@ -94,7 +94,7 @@ internal fun Context.convertVar(variable: Z3Variable, sort: Sort): Expr { * - convert to int for other types, with second step conversion by taking lowest bits * (can lead to significant changes if value outside of target type range). */ -private fun Context.convertFPtoBV(variable: Z3Variable, sortSize: Int): Expr = when (sortSize) { +private fun Context.convertFPtoBV(variable: Z3Variable, sortSize: Int): Expr<*> = when (sortSize) { Long.SIZE_BITS -> convertFPtoBV(variable, sortSize, Long.MIN_VALUE, Long.MAX_VALUE) else -> doNarrowConversion( convertFPtoBV(variable, Int.SIZE_BITS, Int.MIN_VALUE, Int.MAX_VALUE) as BitVecExpr, @@ -105,7 +105,7 @@ private fun Context.convertFPtoBV(variable: Z3Variable, sortSize: Int): Expr = w /** * Converts FP to BV covering special cases for NaN, Infinity and values outside of [minValue, maxValue] range. */ -private fun Context.convertFPtoBV(variable: Z3Variable, sortSize: Int, minValue: Number, maxValue: Number): Expr = +private fun Context.convertFPtoBV(variable: Z3Variable, sortSize: Int, minValue: Number, maxValue: Number): Expr<*> = mkITE( mkFPIsNaN(variable.expr as FPExpr), makeConst(0, mkBitVecSort(sortSize)), mkITE( @@ -128,7 +128,7 @@ internal fun Context.doNarrowConversion(expr: BitVecExpr, sortSize: Int): BitVec /** * Converts a constant value to Z3 constant of given sort - BitVec or FP. */ -fun Context.makeConst(const: Number, sort: Sort): Expr = when (sort) { +fun Context.makeConst(const: Number, sort: Sort): Expr<*> = when (sort) { is BitVecSort -> this.makeBV(const, sort.size) is FPSort -> this.makeFP(const, sort) else -> error("Wrong sort $sort") @@ -150,7 +150,7 @@ fun Context.makeFP(const: Number, sort: FPSort): FPExpr = when (const) { else -> error("Wrong type ${const::class}") } -internal fun Context.toSort(type: Type): Sort = +fun Context.toSort(type: Type): Sort = when (type) { is ByteType -> mkBitVecSort(Byte.SIZE_BITS) is ShortType -> mkBitVecSort(Short.SIZE_BITS) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/z3/Operators.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/z3/Operators.kt index f4d5bb4d3d..38f120c674 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/z3/Operators.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/z3/Operators.kt @@ -15,9 +15,9 @@ import soot.CharType import soot.IntType import soot.ShortType -typealias BinOperator = Operator +typealias BinOperator = Operator> -sealed class Operator( +sealed class Operator>( val onBitVec: (Context, BitVecExpr, BitVecExpr) -> T, val onFP: (Context, FPExpr, FPExpr) -> T = { _, _, _ -> TODO() }, val onBool: (Context, BoolExpr, BoolExpr) -> T = { _, _, _ -> TODO() } @@ -32,7 +32,7 @@ sealed class Operator( doAction(context, aleft, aright) } - protected fun doAction(context: Context, left: Expr, right: Expr): T = when { + protected fun doAction(context: Context, left: Expr<*>, right: Expr<*>): T = when { left is BitVecExpr && right is BitVecExpr -> onBitVec(context, left, right) left is FPExpr && right is FPExpr -> onFP(context, left, right) left is BoolExpr && right is BoolExpr -> onBool(context, left, right) @@ -48,14 +48,14 @@ abstract class BoolOperator( onBool: (Context, BoolExpr, BoolExpr) -> BoolExpr = { _, _, _ -> TODO() } ) : Operator(onBitVec, onFP, onBool) -internal object Le : BoolOperator(Context::mkBVSLE, Context::mkFPLEq) -internal object Lt : BoolOperator(Context::mkBVSLT, Context::mkFPLt) -internal object Ge : BoolOperator(Context::mkBVSGE, Context::mkFPGEq) -internal object Gt : BoolOperator(Context::mkBVSGT, Context::mkFPGt) -internal object Eq : BoolOperator(Context::mkEq, Context::mkFPEq, Context::mkEq) -internal object Ne : BoolOperator(::ne, ::fpNe, ::ne) +object Le : BoolOperator(Context::mkBVSLE, Context::mkFPLEq) +object Lt : BoolOperator(Context::mkBVSLT, Context::mkFPLt) +object Ge : BoolOperator(Context::mkBVSGE, Context::mkFPGEq) +object Gt : BoolOperator(Context::mkBVSGT, Context::mkFPGt) +object Eq : BoolOperator(Context::mkEq, Context::mkFPEq, Context::mkEq) +object Ne : BoolOperator(::ne, ::fpNe, ::ne) -private fun ne(context: Context, left: Expr, right: Expr): BoolExpr = context.mkNot(context.mkEq(left, right)) +private fun ne(context: Context, left: Expr<*>, right: Expr<*>): BoolExpr = context.mkNot(context.mkEq(left, right)) private fun fpNe(context: Context, left: FPExpr, right: FPExpr): BoolExpr = context.mkNot(context.mkFPEq(left, right)) internal object Rem : BinOperator(Context::mkBVSRem, Context::mkFPRem) @@ -129,7 +129,7 @@ internal object Cmp : BinOperator(::bvCmp, ::fpCmp) internal object Cmpl : BinOperator(::bvCmp, ::fpCmpl) internal object Cmpg : BinOperator(::bvCmp, ::fpCmpg) -private fun bvCmp(context: Context, left: BitVecExpr, right: BitVecExpr): Expr = +private fun bvCmp(context: Context, left: BitVecExpr, right: BitVecExpr): Expr<*> = context.mkITE( context.mkBVSLE(left, right), context.mkITE(context.mkEq(left, right), context.mkBV(0, 32), context.mkBV(-1, 32)), @@ -146,7 +146,7 @@ private fun fpCmp(context: Context, left: FPExpr, right: FPExpr) = context.mkBV(1, 32) ) -private fun fpCmpl(context: Context, left: FPExpr, right: FPExpr): Expr = +private fun fpCmpl(context: Context, left: FPExpr, right: FPExpr): Expr<*> = context.mkITE( context.mkOr(context.mkFPIsNaN(left), context.mkFPIsNaN(right)), context.mkBV(-1, 32), context.mkITE( @@ -156,7 +156,7 @@ private fun fpCmpl(context: Context, left: FPExpr, right: FPExpr): Expr = ) ) -private fun fpCmpg(context: Context, left: FPExpr, right: FPExpr): Expr = +private fun fpCmpg(context: Context, left: FPExpr, right: FPExpr): Expr<*> = context.mkITE( context.mkOr(context.mkFPIsNaN(left), context.mkFPIsNaN(right)), context.mkBV(1, 32), context.mkITE( @@ -166,7 +166,7 @@ private fun fpCmpg(context: Context, left: FPExpr, right: FPExpr): Expr = ) ) -fun negate(context: Context, variable: Z3Variable): Expr = when (variable.expr) { +fun negate(context: Context, variable: Z3Variable): Expr<*> = when (variable.expr) { is BitVecExpr -> context.mkBVNeg(context.alignVar(variable).expr as BitVecExpr) is FPExpr -> context.mkFPNeg(variable.expr) is BoolExpr -> context.mkNot(variable.expr) @@ -183,7 +183,7 @@ fun negate(context: Context, variable: Z3Variable): Expr = when (variable.expr) * @see * Java Language Specification: Binary Numeric Promotion */ -internal fun Context.alignVars(left: Z3Variable, right: Z3Variable): Pair { +fun Context.alignVars(left: Z3Variable, right: Z3Variable): Pair, Expr<*>> { val maxSort = maxOf(left.expr.sort, right.expr.sort, mkBitVecSort(Int.SIZE_BITS), compareBy { it.rank() }) return convertVar(left, maxSort) to convertVar(right, maxSort) } @@ -198,7 +198,7 @@ internal fun Context.alignVars(left: Z3Variable, right: Z3Variable): Pair * Java Language Specification: Binary Numeric Promotion */ -internal fun Context.alignVarAndConst(left: Z3Variable, right: Number): Pair { +internal fun Context.alignVarAndConst(left: Z3Variable, right: Number): Pair, Expr<*>> { val maxSort = maxOf(left.expr.sort, toSort(right), mkBitVecSort(Int.SIZE_BITS), compareBy { it.rank() }) return convertVar(left, maxSort) to makeConst(right, maxSort) } @@ -210,7 +210,7 @@ internal fun Context.alignVarAndConst(left: Z3Variable, right: Number): Pair * Java Language Specification: Unary Numeric Promotion */ -internal fun Context.alignVar(variable: Z3Variable): Z3Variable = when (variable.type) { +fun Context.alignVar(variable: Z3Variable): Z3Variable = when (variable.type) { is ByteType, is ShortType, is CharType -> convertVar(variable, IntType.v()) else -> variable } diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/z3/Z3initializer.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/z3/Z3initializer.kt index 97677c78cd..09b88308b1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/z3/Z3initializer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/z3/Z3initializer.kt @@ -4,10 +4,9 @@ import com.microsoft.z3.Context import com.microsoft.z3.Global import org.utbot.common.FileUtil import java.io.File -import java.nio.file.Files.createTempDirectory abstract class Z3Initializer : AutoCloseable { - protected val context: Context by lazy { + private val contextDelegate = lazy { Context().also { // Global.setParameter("smt.core.minimize", "true") Global.setParameter("rewriter.hi_fp_unspecified", "true") @@ -15,39 +14,49 @@ abstract class Z3Initializer : AutoCloseable { Global.setParameter("parallel.threads.max", "4") } } + protected val context: Context by contextDelegate - override fun close() = context.close() + override fun close() { + if (contextDelegate.isInitialized()) { + context.close() + } + } companion object { private val libraries = listOf("libz3", "libz3java") private val vcWinLibrariesToLoadBefore = listOf("vcruntime140", "vcruntime140_1") - private val supportedArchs = setOf("amd64", "x86_64") + private val supportedArchs = setOf("amd64", "x86_64", "aarch64") private val initializeCallback by lazy { System.setProperty("z3.skipLibraryLoad", "true") - val arch = System.getProperty("os.arch") - require(arch in supportedArchs) { "Not supported arch: $arch" } + val archProperty = System.getProperty("os.arch") + require(archProperty in supportedArchs) { "Not supported arch: $archProperty" } - val osProperty = System.getProperty("os.name").toLowerCase() - val (ext, allLibraries) = when { - osProperty.startsWith("windows") -> ".dll" to vcWinLibrariesToLoadBefore + libraries - osProperty.startsWith("linux") -> ".so" to libraries - osProperty.startsWith("mac") -> ".dylib" to libraries + val osProperty = System.getProperty("os.name").lowercase() + val (os, ext, allLibraries) = when { + osProperty.startsWith("windows") -> Triple("windows", ".dll", vcWinLibrariesToLoadBefore + libraries) + osProperty.startsWith("linux") -> Triple("linux", ".so", libraries) + osProperty.startsWith("mac") -> Triple("mac", ".dylib", libraries) else -> error("Unknown OS: $osProperty") } - val libZ3DllUrl = Z3Initializer::class.java + + val arch = if (archProperty == "aarch64") "arm" else "x64" + val z3DistFolder = "lib/$os/$arch/z3" + + val libZ3FilesUrl = Z3Initializer::class.java .classLoader - .getResource("lib/x64/libz3.dll") ?: error("Can't find native library folder") + .getResource("$z3DistFolder/libz3$ext") + ?: error("Can't find native library '$z3DistFolder/libz3$ext' in ${System.getProperty("java.class.path")}") // can't take resource of parent folder right here because in obfuscated jar parent folder // can be missed (e.g., in case if obfuscation was applied) val libFolder: String? - if (libZ3DllUrl.toURI().scheme == "jar") { + if (libZ3FilesUrl.toURI().scheme == "jar") { val tempDir = FileUtil.createTempDirectory("libs-").toFile() allLibraries.forEach { name -> Z3Initializer::class.java .classLoader - .getResourceAsStream("lib/x64/$name$ext") + .getResourceAsStream("$z3DistFolder/$name$ext") ?.use { input -> File(tempDir, "$name$ext") .outputStream() @@ -57,7 +66,7 @@ abstract class Z3Initializer : AutoCloseable { libFolder = "$tempDir" } else { - libFolder = File(libZ3DllUrl.file).parent + libFolder = File(libZ3FilesUrl.file).parent } allLibraries.forEach { System.load("$libFolder/$it$ext") } diff --git a/utbot-framework/src/main/kotlin/org/utbot/external/api/UnitTestBotLight.kt b/utbot-framework/src/main/kotlin/org/utbot/external/api/UnitTestBotLight.kt new file mode 100644 index 0000000000..4faeef7027 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/external/api/UnitTestBotLight.kt @@ -0,0 +1,92 @@ +package org.utbot.external.api + +import org.utbot.engine.EngineController +import org.utbot.engine.ExecutionStateListener +import org.utbot.engine.MockStrategy +import org.utbot.engine.UtBotSymbolicEngine +import org.utbot.framework.UtSettings +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.context.simple.SimpleApplicationContext +import org.utbot.framework.context.simple.SimpleConcreteExecutionContext +import org.utbot.framework.context.simple.SimpleMockerContext +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.framework.plugin.services.JdkInfoDefaultProvider +import org.utbot.framework.util.SootUtils.runSoot +import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation +import java.io.File +import java.nio.file.Paths + + +@Suppress("unused") +object UnitTestBotLight { + + /** + * Creates an instance of symbolic engine with default values + * w/o specific objects like + * - [ApplicationContext] + * - [UtExecutionInstrumentation] + * - [EngineController] + * - etc. + * + * @return symbolic engine instance + */ + @JvmStatic + fun javaUtBotSymbolicEngine( + methodUnderTest: ExecutableId, + classpath: String, + mockStrategy: MockStrategy, + chosenClassesToMockAlways: Set, + ) = UtBotSymbolicEngine( + controller = EngineController(), + methodUnderTest = methodUnderTest, + classpath = classpath, + dependencyPaths = "", + mockStrategy = mockStrategy, + chosenClassesToMockAlways = chosenClassesToMockAlways, + applicationContext = SimpleApplicationContext(SimpleMockerContext( + mockFrameworkInstalled = true, + staticsMockingIsConfigured = true + )), + concreteExecutionContext = SimpleConcreteExecutionContext(classpath), + solverTimeoutInMillis = UtSettings.checkSolverTimeoutMillis + ) + + @JvmStatic + @JvmOverloads + fun run ( + stateListener: ExecutionStateListener, + methodForAutomaticGeneration: TestMethodInfo, + classpath: String, + dependencyClassPath: String, + mockStrategyApi: MockStrategyApi = MockStrategyApi.OTHER_PACKAGES, + ) { + // init Soot if it is not yet + runSoot(classpath.split(File.pathSeparator).map(Paths::get), classpath, false, JdkInfoDefaultProvider().info) + val classLoader = Thread.currentThread().contextClassLoader + val utContext = UtContext(classLoader) + withUtContext(utContext) { + UtBotSymbolicEngine( + EngineController(), + methodForAutomaticGeneration.methodToBeTestedFromUserInput.executableId, + classpath, + dependencyClassPath, + when (mockStrategyApi) { + MockStrategyApi.NO_MOCKS -> MockStrategy.NO_MOCKS + MockStrategyApi.OTHER_PACKAGES -> MockStrategy.OTHER_PACKAGES + MockStrategyApi.OTHER_CLASSES -> MockStrategy.OTHER_CLASSES + }, + HashSet(), + SimpleApplicationContext(SimpleMockerContext( + mockFrameworkInstalled = true, + staticsMockingIsConfigured = true + )), + SimpleConcreteExecutionContext(classpath) + ).addListener(stateListener).traverseAll() + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt index b87f8dcb1e..ecfb1c1634 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt @@ -1,27 +1,18 @@ package org.utbot.external.api import org.utbot.common.FileUtil -import org.utbot.common.packageName +import org.utbot.common.nameOfPackage import org.utbot.framework.UtSettings -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.Junit5 -import org.utbot.framework.codegen.NoStaticMocking -import org.utbot.framework.codegen.StaticsMocking -import org.utbot.framework.codegen.TestFramework -import org.utbot.framework.codegen.model.ModelBasedCodeGeneratorService -import org.utbot.framework.concrete.UtConcreteExecutionData -import org.utbot.framework.concrete.UtConcreteExecutionResult -import org.utbot.framework.concrete.UtExecutionInstrumentation -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.UtBotTestCaseGenerator -import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtTestCase +import org.utbot.framework.codegen.domain.* +import org.utbot.framework.codegen.generator.CodeGeneratorParams +import org.utbot.framework.codegen.services.language.CgLanguageAssistant +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.context.utils.transformValueProvider +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.executableId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.isPrimitive import org.utbot.framework.plugin.api.util.isPrimitiveWrapper @@ -30,136 +21,168 @@ import org.utbot.framework.plugin.api.util.primitiveByWrapper import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.framework.plugin.api.util.withUtContext import org.utbot.framework.plugin.api.util.wrapperByPrimitive +import org.utbot.framework.plugin.services.JdkInfoDefaultProvider import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Seed import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.execute -import java.lang.reflect.Method -import kotlin.reflect.KCallable -import kotlin.reflect.KClass import kotlin.reflect.jvm.kotlinFunction - -fun toUtMethod(method: Method, kClass: KClass<*>) = UtMethod(method.kotlinFunction as KCallable<*>, kClass) +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.plugin.api.* +import java.lang.reflect.Method object UtBotJavaApi { + /** + * For running tests it could be reasonable to reuse the same concrete executor + */ @JvmStatic var stopConcreteExecutorOnExit: Boolean = true + /** + * Generates test code + * @param methodsForGeneration specify methods that are supposed to be executed concretely. + * In order to execute method you are supposed to provide some + * values to pass in it this is why we use [TestMethodInfo] here. + * @param generatedTestCases specify [UtMethodTestSet]s that are used for test code + * generation. By comparison with the first parameter, + * {@code UtMethodTestSet} contains more information about + * test, including result of the executions. Note, that + * you can get the object with any sort of analysis, + * for instance, symbolic or fuzz execution. + * @param destinationClassName the name of containing class for the generated tests + * @param classpath classpath that are used to build the class under test + * @param dependencyClassPath class path including dependencies required for the code generation + * @param classUnderTest for this class test should be generated + * @param projectType JVM, Spring, Python, or other type of project + * @param testFramework test framework that is going to be used for running generated tests + * @param mockFramework framework that will be used in the generated tests + * @param codegenLanguage the target language of the test generation. It can be different from the source language. + * @param staticsMocking the approach to the statics mocking + * @param generateWarningsForStaticMocking enable generation of warning about forced static mocking in comments + * of generated tests. + * @param forceStaticMocking enables static mocking + * @param testClassPackageName package name for the generated class with the tests + * @param applicationContext specify application context here + */ @JvmStatic @JvmOverloads - fun generate( + fun generateTestCode( methodsForGeneration: List, - generatedTestCases: List = mutableListOf(), + generatedTestCases: List = mutableListOf(), destinationClassName: String, classpath: String, dependencyClassPath: String, classUnderTest: Class<*>, + projectType: ProjectType = ProjectType.PureJvm, testFramework: TestFramework = Junit5, mockFramework: MockFramework = MockFramework.MOCKITO, codegenLanguage: CodegenLanguage = CodegenLanguage.JAVA, staticsMocking: StaticsMocking = NoStaticMocking, generateWarningsForStaticMocking: Boolean = false, forceStaticMocking: ForceStaticMocking = ForceStaticMocking.DO_NOT_FORCE, - testClassPackageName: String = classUnderTest.packageName + testClassPackageName: String = classUnderTest.nameOfPackage, + applicationContext: ApplicationContext ): String { - val utContext = UtContext(classUnderTest.classLoader) - - val testCases: MutableList = generatedTestCases.toMutableList() + val testSets: MutableList = generatedTestCases.toMutableList() val concreteExecutor = ConcreteExecutor( - UtExecutionInstrumentation, - classpath, - dependencyClassPath + applicationContext.createConcreteExecutionContext( + fullClasspath = dependencyClassPath, + classpathWithoutDependencies = classpath + ).instrumentationFactory, + classpath ) - testCases.addAll(generateUnitTests(concreteExecutor, methodsForGeneration, classUnderTest)) + testSets.addAll(generateUnitTests(concreteExecutor, methodsForGeneration, classUnderTest)) if (stopConcreteExecutorOnExit) { concreteExecutor.close() } - return withUtContext(utContext) { - val testGenerator = ModelBasedCodeGeneratorService().serviceProvider.apply { - init( - classUnderTest = classUnderTest, - params = mutableMapOf(), + return withUtContext(UtContext(classUnderTest.classLoader)) { + applicationContext.createCodeGenerator( + CodeGeneratorParams( + classUnderTest = classUnderTest.id, + projectType = projectType, testFramework = testFramework, mockFramework = mockFramework, codegenLanguage = codegenLanguage, + cgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage(codegenLanguage), staticsMocking = staticsMocking, forceStaticMocking = forceStaticMocking, generateWarningsForStaticMocking = generateWarningsForStaticMocking, - testClassPackageName = testClassPackageName + testClassPackageName = testClassPackageName, ) - } - - testGenerator.generateAsString( - testCases, - destinationClassName - ) + ).generateAsString(testSets, destinationClassName) } } /** - * Generates test cases using default workflow. + * Generates test sets using default workflow. * - * @see [fuzzingTestCases] + * @see [fuzzingTestSets] */ @JvmStatic @JvmOverloads - fun generateTestCases( - methodsForAutomaticGeneration: List, + fun generateTestSetsForMethods( + methodsToAnalyze: List, classUnderTest: Class<*>, classpath: String, dependencyClassPath: String, mockStrategyApi: MockStrategyApi = MockStrategyApi.OTHER_PACKAGES, - generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis - ): MutableList { + generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis, + applicationContext: ApplicationContext + ): MutableList { + + assert(methodsToAnalyze.all {classUnderTest.declaredMethods.contains(it)}) + { "Some methods are absent in the ${classUnderTest.name} class." } val utContext = UtContext(classUnderTest.classLoader) - val testCases: MutableList = mutableListOf() + val testSets: MutableList = mutableListOf() - testCases.addAll(withUtContext(utContext) { - UtBotTestCaseGenerator - .apply { - init( - FileUtil.isolateClassFiles(classUnderTest.kotlin).toPath(), classpath, dependencyClassPath - ) - } - .generateForSeveralMethods( - methodsForAutomaticGeneration.map { - toUtMethod( - it.methodToBeTestedFromUserInput, - classUnderTest.kotlin - ) - }, - mockStrategyApi, - chosenClassesToMockAlways = emptySet(), - generationTimeoutInMillis - ) + testSets.addAll(withUtContext(utContext) { + val buildPath = FileUtil.isolateClassFiles(classUnderTest).toPath() + TestCaseGenerator( + listOf(buildPath), + classpath, + dependencyClassPath, + jdkInfo = JdkInfoDefaultProvider().info, + applicationContext = applicationContext + ).generate( + methodsToAnalyze.map { it.executableId }, + mockStrategyApi, + chosenClassesToMockAlways = emptySet(), + generationTimeoutInMillis + ) }) - return testCases + return testSets } /** * Generates test cases using only fuzzing workflow. * - * @see [generateTestCases] + * @see [generateTestSetsForMethods] */ @JvmStatic @JvmOverloads - fun fuzzingTestCases( - methodsForAutomaticGeneration: List, + fun fuzzingTestSets( + methodsToAnalyze: List, classUnderTest: Class<*>, classpath: String, dependencyClassPath: String, mockStrategyApi: MockStrategyApi = MockStrategyApi.OTHER_PACKAGES, generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis, - primitiveValuesSupplier: CustomFuzzerValueSupplier = CustomFuzzerValueSupplier { null } - ): MutableList { + primitiveValuesSupplier: CustomFuzzerValueSupplier = CustomFuzzerValueSupplier { null }, + applicationContext: ApplicationContext + ): MutableList { + + assert(methodsToAnalyze.all {classUnderTest.declaredMethods.contains(it)}) + { "Some methods are absent in the ${classUnderTest.name} class." } + fun createPrimitiveModels(supplier: CustomFuzzerValueSupplier, classId: ClassId): Sequence = supplier .takeIf { classId.isPrimitive || classId.isPrimitiveWrapper || classId == stringClassId } @@ -176,37 +199,31 @@ object UtBotJavaApi { } ?.map { UtPrimitiveModel(it) } ?: emptySequence() - val customModelProvider = ModelProvider { description, consumer -> - description.parametersMap.forEach { (classId, indices) -> - createPrimitiveModels(primitiveValuesSupplier, classId).forEach { model -> - indices.forEach { index -> - consumer.accept(index, FuzzedValue(model)) - } + val customModelProvider = JavaValueProvider { _, type -> + sequence { + createPrimitiveModels(primitiveValuesSupplier, type.classId).forEach { model -> + yield(Seed.Simple(FuzzedValue(model))) } } } return withUtContext(UtContext(classUnderTest.classLoader)) { - UtBotTestCaseGenerator - .apply { - init( - FileUtil.isolateClassFiles(classUnderTest.kotlin).toPath(), classpath, dependencyClassPath - ) - }.generateForSeveralMethods( - methodsForAutomaticGeneration.map { - toUtMethod( - it.methodToBeTestedFromUserInput, - classUnderTest.kotlin - ) - }, + val buildPath = FileUtil.isolateClassFiles(classUnderTest).toPath() + TestCaseGenerator( + listOf(buildPath), + classpath, + dependencyClassPath, + jdkInfo = JdkInfoDefaultProvider().info, + applicationContext = applicationContext.transformValueProvider { defaultModelProvider -> + customModelProvider.withFallback(defaultModelProvider) + } + ) + .generate( + methodsToAnalyze.map{ it.executableId }, mockStrategyApi, chosenClassesToMockAlways = emptySet(), generationTimeoutInMillis, - generate = { symbolicEngine -> - symbolicEngine.fuzzing { defaultModelProvider -> - customModelProvider.withFallback(defaultModelProvider) - } - } + generate = { symbolicEngine -> symbolicEngine.fuzzing() } ) }.toMutableList() } @@ -242,26 +259,26 @@ object UtBotJavaApi { arrayOf(), parameters = UtConcreteExecutionData( testInfo.initialState, - instrumentation = emptyList() + instrumentation = emptyList(), + UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis, + isRerun = false, ) ).result } else { testInfo.utResult } - val utExecution = UtExecution( - testInfo.initialState, - testInfo.initialState, // it seems ok for concrete execution - utExecutionResult, - emptyList(), - mutableListOf(), - listOf() + val utExecution = UtSymbolicExecution( + stateBefore = testInfo.initialState, + stateAfter = testInfo.initialState, // it seems ok for concrete execution + result = utExecutionResult, + instrumentation = emptyList(), + path = mutableListOf(), + fullPath = listOf() ) - val utMethod = UtMethod(methodCallable, containingClass.kotlin) - - UtTestCase( - utMethod, + UtMethodTestSet( + methodCallable.executableId, listOf(utExecution) ) }.toList() diff --git a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt index 2f0c10ce54..acb9e55c5f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtModelFactory.kt @@ -1,21 +1,19 @@ package org.utbot.external.api +import org.utbot.common.nameOfPackage import org.utbot.framework.assemble.AssembleModelGenerator import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtClassRefModel import org.utbot.framework.plugin.api.UtCompositeModel -import org.utbot.framework.plugin.api.UtMethod import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.id import java.lang.reflect.Field import java.lang.reflect.Method import java.util.IdentityHashMap import java.util.concurrent.atomic.AtomicInteger -import kotlin.reflect.jvm.kotlinFunction class UtModelFactory( @@ -50,8 +48,9 @@ class UtModelFactory( methodUnderTest: Method, classUnderTest: Class<*>, models: List - ): IdentityHashMap = AssembleModelGenerator(UtMethod(methodUnderTest.kotlinFunction!!, classUnderTest.kotlin)). - createAssembleModels(models) + ): IdentityHashMap = + AssembleModelGenerator(classUnderTest.nameOfPackage) + .createAssembleModels(models) @JvmOverloads fun produceArrayModel( @@ -67,8 +66,10 @@ class UtModelFactory( elements.toMutableMap() ) - fun produceClassRefModel(clazz: Class): UtModel = UtClassRefModel( - classIdForType(clazz), clazz + fun produceClassRefModel(clazz: Class<*>) = UtClassRefModel( + modelIdCounter.incrementAndGet(), + classIdForType(clazz), + clazz.id ) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt index 1842f652c6..39af79204a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssembleModelGenerator.kt @@ -1,15 +1,16 @@ package org.utbot.framework.assemble -import org.utbot.common.packageName +import mu.KotlinLogging +import org.utbot.common.isPrivate +import org.utbot.common.isPublic import org.utbot.engine.ResolvedExecution import org.utbot.engine.ResolvedModels -import org.utbot.engine.isPrivate -import org.utbot.engine.isPublic import org.utbot.framework.UtSettings -import org.utbot.framework.modifications.AnalysisMode.SettersAndDirectAccessors -import org.utbot.framework.modifications.ConstructorAnalyzer -import org.utbot.framework.modifications.ConstructorAssembleInfo -import org.utbot.framework.modifications.UtBotFieldsModificatorsSearcher +import org.utbot.framework.codegen.util.isAccessibleFrom +import org.utbot.modifications.AnalysisMode.SettersAndDirectAccessors +import org.utbot.modifications.ExecutableAnalyzer +import org.utbot.modifications.ExecutableAssembleInfo +import org.utbot.modifications.UtBotFieldsModificatorsSearcher import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.DirectFieldAccessId @@ -20,15 +21,18 @@ import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtClassRefModel import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtCustomModel +import org.utbot.framework.plugin.api.UtDirectGetFieldModel import org.utbot.framework.plugin.api.UtDirectSetFieldModel import org.utbot.framework.plugin.api.UtEnumConstantModel import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.framework.plugin.api.UtMethod +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtStatementCallModel import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation import org.utbot.framework.plugin.api.UtVoidModel @@ -36,21 +40,29 @@ import org.utbot.framework.plugin.api.hasDefaultValue import org.utbot.framework.plugin.api.isMockModel import org.utbot.framework.plugin.api.util.defaultValueModel import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.isFinal +import org.utbot.framework.plugin.api.util.isPrivate +import org.utbot.framework.plugin.api.util.isPublic +import org.utbot.framework.plugin.api.util.isStatic import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.util.nextModelName import java.lang.reflect.Constructor import java.util.IdentityHashMap +import org.utbot.modifications.FieldInvolvementMode /** * Creates [UtAssembleModel] from any [UtModel] or it's inner models if possible - * during generation test for [methodUnderTest]. + * during generation test for [basePackageName]. * * Needs utContext be set and Soot be initialized. * * Note: Caches class related information, can be reused if classes don't change. */ -class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { - private val methodPackageName = methodUnderTest.clazz.java.packageName +class AssembleModelGenerator(private val basePackageName: String) { + + companion object { + private val logger = KotlinLogging.logger {} + } //Instantiated models are stored to avoid cyclic references during reference graph analysis private val instantiatedModels: IdentityHashMap = @@ -61,8 +73,11 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { //Call chain of statements to create assemble model private var callChain = mutableListOf() - private val modificatorsSearcher = UtBotFieldsModificatorsSearcher() - private val constructorAnalyzer = ConstructorAnalyzer() + private val modificatorsSearcher = + UtBotFieldsModificatorsSearcher( + fieldInvolvementMode = FieldInvolvementMode.WriteOnly + ) + private val executableAnalyzer = ExecutableAnalyzer() /** * Clears state before and after block execution. @@ -172,6 +187,16 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { private fun assembleModel(utModel: UtModel): UtModel { val collectedCallChain = callChain.toMutableList() + try { + // We cannot create an assemble model for an anonymous class instance + if (utModel.classId.isAnonymous) { + return utModel + } + } catch (e: ClassNotFoundException) { + // happens, for example, when `utModel.classId.name` is something like "jdk.proxy3.$Proxy144" + return utModel + } + val assembledModel = withCleanState { try { when (utModel) { @@ -179,10 +204,14 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { is UtPrimitiveModel, is UtClassRefModel, is UtVoidModel, - is UtEnumConstantModel -> utModel + is UtEnumConstantModel, + is UtCustomModel -> utModel // for example, UtSpringContextModel + is UtLambdaModel -> assembleLambdaModel(utModel) is UtArrayModel -> assembleArrayModel(utModel) is UtCompositeModel -> assembleCompositeModel(utModel) is UtAssembleModel -> assembleAssembleModel(utModel) + // Python, JavaScript are supposed to be here as well + else -> utModel } } catch (e: AssembleException) { utModel @@ -193,6 +222,18 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { return assembledModel } + private fun assembleLambdaModel(lambdaModel: UtLambdaModel): UtModel { + instantiatedModels[lambdaModel]?.let { return it } + + return UtLambdaModel( + lambdaModel.id, + lambdaModel.samType, + lambdaModel.declaringClass, + lambdaModel.lambdaName, + capturedValues = lambdaModel.capturedValues.map { assembleModel(it) }.toMutableList(), + ) + } + /** * Assembles internal structure of [UtArrayModel]. */ @@ -207,14 +248,19 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { instantiatedModels[this] = assembleModel - assembleModel.constModel = assembleModel(constModel) assembleModel.stores += stores .mapValues { assembleModel(it.value) } .toMutableMap() + if (arrayModel.stores.count() < arrayModel.length) { + assembleModel.constModel = assembleModel(constModel) + } + assembleModel } + private val modelsInAnalysis = mutableListOf() + /** * Assembles internal structure of [UtCompositeModel] if possible and handles assembling exceptions. */ @@ -229,46 +275,59 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { try { val modelName = nextModelName(compositeModel.classId.jClass.simpleName.decapitalize()) - val instantiationChain = mutableListOf() - val modificationsChain = mutableListOf() + val constructorId = findBestConstructorOrNull(compositeModel) + ?: throw AssembleException("No default constructor to instantiate an object of the class ${compositeModel.classId}") + + // we do not analyze a constructor which is currently in the analysis + // thus, we do not encounter an infinite loop in self or cross-reference situations + val shouldAnalyzeConstructor = compositeModel !in modelsInAnalysis + modelsInAnalysis.add(compositeModel) + + val constructorInfo = + if (shouldAnalyzeConstructor) executableAnalyzer.analyze(constructorId) + else ExecutableAssembleInfo(constructorId) + + val instantiationCall = constructorCall(compositeModel, constructorInfo) return UtAssembleModel( compositeModel.id, compositeModel.classId, modelName, - instantiationChain, - modificationsChain, + instantiationCall, compositeModel - ).apply { - - val constructorId = findBestConstructorOrNull(compositeModel) - ?: throw AssembleException("No default constructor to instantiate an object of the class $classId") - - val constructorInfo = constructorAnalyzer.analyze(constructorId) - + ) { instantiatedModels[compositeModel] = this - instantiationChain += constructorCall(compositeModel, this, constructorInfo) compositeModel.fields.forEach { (fieldId, fieldModel) -> + //if field value has been filled by constructor or it is default, we suppose that it is already properly initialized + if ((fieldId in constructorInfo.setFields || fieldModel.hasDefaultValue()) && fieldId !in constructorInfo.affectedFields) + return@forEach + if (fieldId.isStatic) { throw AssembleException("Static field $fieldId can't be set in an object of the class $classId") } + + if (!fieldId.type.isAccessibleFrom(basePackageName)) { + throw AssembleException( + "Field $fieldId can't be set in an object of the class $classId because its type is inaccessible" + ) + } + if (fieldId.isFinal) { throw AssembleException("Final field $fieldId can't be set in an object of the class $classId") } - //fill field value if it hasn't been filled by constructor, and it is not default - if (fieldId in constructorInfo.affectedFields || - (fieldId !in constructorInfo.setFields && !fieldModel.hasDefaultValue()) - ) { - val modifierCall = modifierCall(this, fieldId, assembleModel(fieldModel)) - callChain.add(modifierCall) - } + + val assembledModel = assembleModel(fieldModel) + val modifierCall = modifierCall(this, fieldId, assembledModel) + callChain.add(modifierCall) } - modificationsChain += callChain.toList() + callChain.toList() } } catch (e: AssembleException) { instantiatedModels.remove(compositeModel) throw e + } finally { + modelsInAnalysis.remove(compositeModel) } } @@ -278,17 +337,15 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { private fun assembleAssembleModel(modelBefore: UtAssembleModel): UtModel { instantiatedModels[modelBefore]?.let { return it } - val instantiationChain = mutableListOf() - val modificationChain = mutableListOf() - - return modelBefore.copy( - instantiationChain = instantiationChain, - modificationsChain = modificationChain, - ).apply { + return UtAssembleModel( + modelBefore.id, + modelBefore.classId, + modelBefore.modelName, + assembleStatementCallModel(modelBefore.instantiationCall), + modelBefore.origin + ) { instantiatedModels[modelBefore] = this - - instantiationChain += modelBefore.instantiationChain.map { assembleStatementModel(it) } - modificationChain += modelBefore.modificationsChain.map { assembleStatementModel(it) } + modelBefore.modificationsChain.map { assembleStatementModel(it) } } } @@ -296,33 +353,50 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { * Assembles internal structure of [UtStatementModel]. */ private fun assembleStatementModel(statementModel: UtStatementModel): UtStatementModel = when (statementModel) { - is UtExecutableCallModel -> statementModel.copy(params = statementModel.params.map { assembleModel(it) }) - is UtDirectSetFieldModel -> statementModel.copy(fieldModel = assembleModel(statementModel.fieldModel)) + is UtStatementCallModel -> assembleStatementCallModel(statementModel) + is UtDirectSetFieldModel -> assembleDirectSetFieldModel(statementModel) } + private fun assembleDirectSetFieldModel(statementModel: UtDirectSetFieldModel) = + statementModel.copy( + instance = statementModel.instance.let { assembleModel(it) as UtReferenceModel }, + fieldModel = assembleModel(statementModel.fieldModel) + ) + + private fun assembleStatementCallModel(statementModel: UtStatementCallModel) = + when (statementModel) { + is UtExecutableCallModel-> statementModel.copy( + instance = statementModel.instance?.let { assembleModel(it) as UtReferenceModel }, + params = statementModel.params.map { assembleModel(it) } + ) + is UtDirectGetFieldModel -> statementModel.copy( + instance = assembleModel(statementModel.instance) as UtReferenceModel + ) + } + /** * Assembles internal structure of [UtCompositeModel] if it represents a mock. */ private fun assembleMockCompositeModel(compositeModel: UtCompositeModel): UtCompositeModel { // We have to create a model before the construction of the fields to avoid // infinite recursion when some mock contains itself as a field. - val assembledModel = UtCompositeModel( + val assembledCompositeModel = UtCompositeModel( compositeModel.id, compositeModel.classId, isMock = true, ) - instantiatedModels[compositeModel] = assembledModel + instantiatedModels[compositeModel] = assembledCompositeModel val fields = compositeModel.fields.mapValues { assembleModel(it.value) }.toMutableMap() val mockBehaviour = compositeModel.mocks .mapValues { models -> models.value.map { assembleModel(it) } } .toMutableMap() - assembledModel.fields += fields - assembledModel.mocks += mockBehaviour + assembledCompositeModel.fields += fields + assembledCompositeModel.mocks += mockBehaviour - return assembledModel + return assembledCompositeModel } /** @@ -332,10 +406,9 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { */ private fun constructorCall( compositeModel: UtCompositeModel, - instance: UtAssembleModel, - constructorInfo: ConstructorAssembleInfo, + constructorInfo: ExecutableAssembleInfo, ): UtExecutableCallModel { - val constructorParams = constructorInfo.constructorId.parameters.withIndex() + val constructorParams = constructorInfo.executableId.parameters.withIndex() .map { (index, param) -> val modelOrNull = compositeModel.fields .filter { it.key == constructorInfo.params[index] } @@ -345,13 +418,17 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { assembleModel(fieldModel) } - return UtExecutableCallModel(null, constructorInfo.constructorId, constructorParams, instance) + return UtExecutableCallModel(instance = null, constructorInfo.executableId, constructorParams) } /** * Finds most appropriate constructor in class. * - * We prefer constructor that allows to set more fields than others + * If the [compositeModel].fields is empty, we don't care about affected fields, we would like to take an empty + * constructor if the declaring class is from [java.util] package or an appropriate constructor with the least + * number of arguments. + * + * Otherwise, we prefer constructor that allows to set more fields than others * and use only simple assignments like "this.a = a". * * Returns null if no one appropriate constructor is found. @@ -360,18 +437,27 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { val classId = compositeModel.classId if (!classId.isVisible || classId.isInner) return null - return classId.jClass.declaredConstructors + val constructorIds = classId.jClass.declaredConstructors .filter { it.isVisible } - .sortedByDescending { it.parameterCount } .map { it.executableId } - .firstOrNull { constructorAnalyzer.isAppropriate(it) } + + return if (compositeModel.fields.isEmpty()) { + val fromUtilPackage = classId.packageName.startsWith("java.util") + constructorIds + .sortedBy { it.parameters.size } + .firstOrNull { it.parameters.isEmpty() && fromUtilPackage || executableAnalyzer.isAppropriate(it) } + } else { + constructorIds + .sortedByDescending { it.parameters.size } + .firstOrNull { executableAnalyzer.isAppropriate(it) } + } } private val ClassId.isVisible : Boolean - get() = this.isPublic || !this.isPrivate && this.packageName.startsWith(methodPackageName) + get() = this.isPublic || !this.isPrivate && this.packageName == basePackageName private val Constructor<*>.isVisible : Boolean - get() = this.isPublic || !this.isPrivate && this.declaringClass.packageName.startsWith(methodPackageName) + get() = this.isPublic || !this.isPrivate && this.declaringClass.`package`.name == basePackageName /** * Creates setter or direct setter call to set a field. @@ -385,7 +471,7 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { ): UtStatementModel { val declaringClassId = fieldId.declaringClass - val modifiers = getOrFindSettersAndDirectAccessors(declaringClassId) + val modifiers = getOrFindSettersAndDirectAccessors(instance.classId) val modifier = modifiers[fieldId] ?: throw AssembleException("No setter for field ${fieldId.name} of class ${declaringClassId.name}") @@ -410,13 +496,11 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { * Finds setters and direct accessors for fields of particular class. */ private fun findSettersAndDirectAccessors(classId: ClassId): Map { - val allModificatorsOfClass = modificatorsSearcher - .findModificators(SettersAndDirectAccessors, methodPackageName) - .map { it.key to it.value.filter { st -> st.classId == classId } } + val allModificatorsOfClass = modificatorsSearcher.getFieldToModificators(SettersAndDirectAccessors) return allModificatorsOfClass .mapNotNull { (fieldId, possibleModificators) -> - chooseModificator(fieldId, possibleModificators)?.let { fieldId to it } + chooseModificator(classId, fieldId, possibleModificators)?.let { fieldId to it } } .toMap() } @@ -427,10 +511,14 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { * Note: direct accessor is more preferred than setter. */ private fun chooseModificator( + callerClassId: ClassId, fieldId: FieldId, - settersAndDirectAccessors: List + settersAndDirectAccessors: Set, ): StatementId? { - val directAccessors = settersAndDirectAccessors.filterIsInstance() + val directAccessors = settersAndDirectAccessors + .filterIsInstance() + .filter {it.fieldId.isAccessibleFrom(basePackageName, callerClassId) } + if (directAccessors.any()) { return directAccessors.singleOrNull() ?: throw AssembleException( @@ -439,7 +527,9 @@ class AssembleModelGenerator(private val methodUnderTest: UtMethod<*>) { } if (settersAndDirectAccessors.any()) { - return settersAndDirectAccessors.singleOrNull() + return settersAndDirectAccessors + .filterIsInstance() + .singleOrNull { it.isAccessibleFrom(basePackageName) } ?: throw AssembleException( "Field $fieldId has more than one setter: ${settersAndDirectAccessors.joinToString(" ")}" ) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssemblePrimitiveWrapper.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssemblePrimitiveWrapper.kt new file mode 100644 index 0000000000..4c6a5891fc --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/assemble/AssemblePrimitiveWrapper.kt @@ -0,0 +1,48 @@ +package org.utbot.framework.assemble + +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.byteClassId +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.wrapIfPrimitive + +/** + * Creates [UtAssembleModel] of the wrapper for a given [UtPrimitiveModel]. + */ +fun assemble(model: UtPrimitiveModel): UtAssembleModel { + val modelType = model.classId + val assembledModelType = wrapIfPrimitive(modelType) + + val constructorCall = when (modelType) { + shortClassId -> java.lang.Short::class.java.getConstructor(Short::class.java) + intClassId -> java.lang.Integer::class.java.getConstructor(Int::class.java) + longClassId -> java.lang.Long::class.java.getConstructor(Long::class.java) + charClassId -> java.lang.Character::class.java.getConstructor(Char::class.java) + byteClassId -> java.lang.Byte::class.java.getConstructor(Byte::class.java) + booleanClassId -> java.lang.Boolean::class.java.getConstructor(Boolean::class.java) + floatClassId -> java.lang.Float::class.java.getConstructor(Float::class.java) + doubleClassId -> java.lang.Double::class.java.getConstructor(Double::class.java) + else -> error("Model type $modelType is void or non-primitive") + } + + val constructorCallModel = UtExecutableCallModel( + instance = null, + executable = constructorCall.executableId, + params = listOf(model), + ) + + return UtAssembleModel( + id = null, + classId = assembledModelType, + modelName = modelType.canonicalName, + instantiationCall = constructorCallModel, + ) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/CodeGeneratorService.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/CodeGeneratorService.kt deleted file mode 100644 index 9daffd9e51..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/CodeGeneratorService.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.utbot.framework.codegen - -import org.utbot.framework.plugin.api.UtService - -interface CodeGeneratorService : UtService diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt deleted file mode 100644 index c96347f4c7..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Domain.kt +++ /dev/null @@ -1,569 +0,0 @@ -package org.utbot.framework.codegen - -import org.utbot.framework.DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS -import org.utbot.framework.codegen.model.constructor.builtin.mockitoClassId -import org.utbot.framework.codegen.model.constructor.builtin.ongoingStubbingClassId -import org.utbot.framework.codegen.model.tree.CgClassId -import org.utbot.framework.plugin.api.BuiltinClassId -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.CodeGenerationSettingBox -import org.utbot.framework.plugin.api.CodeGenerationSettingItem -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.isolateCommandLineArgumentsToArgumentFile -import org.utbot.framework.plugin.api.util.booleanArrayClassId -import org.utbot.framework.plugin.api.util.booleanClassId -import org.utbot.framework.plugin.api.util.builtinMethodId -import org.utbot.framework.plugin.api.util.builtinStaticMethodId -import org.utbot.framework.plugin.api.util.byteArrayClassId -import org.utbot.framework.plugin.api.util.charArrayClassId -import org.utbot.framework.plugin.api.util.doubleArrayClassId -import org.utbot.framework.plugin.api.util.doubleClassId -import org.utbot.framework.plugin.api.util.floatArrayClassId -import org.utbot.framework.plugin.api.util.floatClassId -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.intArrayClassId -import org.utbot.framework.plugin.api.util.longArrayClassId -import org.utbot.framework.plugin.api.util.objectClassId -import org.utbot.framework.plugin.api.util.shortArrayClassId -import org.utbot.framework.plugin.api.util.voidClassId -import java.io.File - -data class TestClassFile(val packageName: String, val imports: List, val testClass: String) - -sealed class Import(internal val order: Int) : Comparable { - abstract val qualifiedName: String - - override fun compareTo(other: Import) = importComparator.compare(this, other) -} - -private val importComparator = compareBy { it.order }.thenBy { it.qualifiedName } - -data class StaticImport(val qualifierClass: String, val memberName: String) : Import(1) { - override val qualifiedName: String = "$qualifierClass.$memberName" -} - -data class RegularImport(val packageName: String, val className: String) : Import(2) { - override val qualifiedName: String - get() = if (packageName.isNotEmpty()) "$packageName.$className" else className - - // TODO: check without equals() and hashCode() - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as RegularImport - - if (packageName != other.packageName) return false - if (className != other.className) return false - - return true - } - - override fun hashCode(): Int { - var result = packageName.hashCode() - result = 31 * result + className.hashCode() - return result - } -} - -private const val TEST_NG_PACKAGE = "org.testng" -private const val JUNIT4_PACKAGE = "org.junit" -private const val JUNIT5_PACKAGE = "org.junit.jupiter.api" -const val JUNIT5_PARAMETERIZED_PACKAGE = "org.junit.jupiter.params" - -//JUnit5 imports -private const val TEST_NG_ASSERTIONS = "org.testng.Assert" -private const val TEST_NG_ARRAYS_ASSERTIONS = "org.testng.internal.junit.ArrayAsserts" -private const val JUNIT5_ASSERTIONS = "org.junit.jupiter.api.Assertions" -private const val JUNIT4_ASSERTIONS = "org.junit.Assert" - -fun junitByVersion(version: Int): TestFramework = - when (version) { - 4 -> Junit4 - 5 -> Junit5 - else -> error("Expected JUnit version 4 or 5, but got: $version") - } - -fun testFrameworkByName(testFramework: String): TestFramework = - when (testFramework) { - "junit4" -> Junit4 - "junit5" -> Junit5 - "testng" -> TestNg - else -> error("Unexpected test framework name: $testFramework") - } - -/** - * This feature allows to enable additional mockito-core settings required for static mocking. - * It is implemented via adding special file "MockMaker" into test project resources. - */ -sealed class StaticsMocking( - var isConfigured: Boolean = false, - override val displayName: String, - override val description: String = "Use static methods mocking" -) : CodeGenerationSettingItem { - override fun toString(): String = displayName - - // Get is mandatory because of the initialization order of the inheritors. - // Otherwise, in some cases we could get an incorrect value - companion object : CodeGenerationSettingBox { - override val defaultItem: StaticsMocking - get() = MockitoStaticMocking - override val allItems: List - get() = listOf(NoStaticMocking, MockitoStaticMocking) - } -} - -object NoStaticMocking : StaticsMocking( - displayName = "No static mocking", - description = "Do not use additional settings to mock static fields" -) - -object MockitoStaticMocking : StaticsMocking(displayName = "Mockito static mocking") { - - val mockedStaticClassId = BuiltinClassId( - name = "org.mockito.MockedStatic", - canonicalName = "org.mockito.MockedStatic", - simpleName = "MockedStatic" - ) - - val mockedConstructionClassId = BuiltinClassId( - name = "org.mockito.MockedConstruction", - canonicalName = "org.mockito.MockedConstruction", - simpleName = "MockedConstruction" - ) - - val mockStaticMethodId = builtinStaticMethodId( - classId = mockitoClassId, - name = "mockStatic", - returnType = mockedStaticClassId, - arguments = arrayOf(objectClassId) - ) - - val mockConstructionMethodId = builtinStaticMethodId( - classId = mockitoClassId, - name = "mockConstruction", - returnType = mockedConstructionClassId, - // actually second argument is lambda - arguments = arrayOf(objectClassId, objectClassId) - ) - - val mockedStaticWhen = builtinMethodId( - classId = mockedStaticClassId, - name = "when", - returnType = ongoingStubbingClassId, - // argument type is actually a functional interface - arguments = arrayOf(objectClassId) - ) - - fun mockedStaticWhen(nullable: Boolean): MethodId = builtinMethodId( - classId = mockedStaticClassId, - name = "when", - returnType = CgClassId(ongoingStubbingClassId, isNullable = nullable), - // argument type is actually a functional interface - arguments = arrayOf(objectClassId) - ) -} - -sealed class TestFramework( - override val displayName: String, - override val description: String = "Use $displayName as test framework", -) : CodeGenerationSettingItem { - var isInstalled: Boolean = false - abstract val mainPackage: String - abstract val assertionsClass: ClassId - abstract val arraysAssertionsClass: ClassId - abstract val testAnnotation: String - abstract val testAnnotationId: ClassId - abstract val testAnnotationFqn: String - abstract val parameterizedTestAnnotation: String - abstract val parameterizedTestAnnotationId: ClassId - abstract val parameterizedTestAnnotationFqn: String - abstract val methodSourceAnnotation: String - abstract val methodSourceAnnotationId: ClassId - abstract val methodSourceAnnotationFqn: String - - val assertEquals by lazy { assertionId("assertEquals", objectClassId, objectClassId) } - - val assertFloatEquals by lazy { assertionId("assertEquals", floatClassId, floatClassId, floatClassId) } - - val assertDoubleEquals by lazy { assertionId("assertEquals", doubleClassId, doubleClassId, doubleClassId) } - - val assertArrayEquals by lazy { arrayAssertionId("assertArrayEquals", Array::class.id, Array::class.id) } - - open val assertBooleanArrayEquals by lazy { assertionId("assertArrayEquals", booleanArrayClassId, booleanArrayClassId) } - - val assertByteArrayEquals by lazy { arrayAssertionId("assertArrayEquals", byteArrayClassId, byteArrayClassId) } - - val assertCharArrayEquals by lazy { arrayAssertionId("assertArrayEquals", charArrayClassId, charArrayClassId) } - - val assertShortArrayEquals by lazy { arrayAssertionId("assertArrayEquals", shortArrayClassId, shortArrayClassId) } - - val assertIntArrayEquals by lazy { arrayAssertionId("assertArrayEquals", intArrayClassId, intArrayClassId) } - - val assertLongArrayEquals by lazy { arrayAssertionId("assertArrayEquals", longArrayClassId, longArrayClassId) } - - val assertFloatArrayEquals by lazy { arrayAssertionId("assertArrayEquals", floatArrayClassId, floatArrayClassId, floatClassId) } - - val assertDoubleArrayEquals by lazy { arrayAssertionId("assertArrayEquals", doubleArrayClassId, doubleArrayClassId, doubleClassId) } - - val assertNull by lazy { assertionId("assertNull", objectClassId) } - - val assertFalse by lazy { assertionId("assertFalse", booleanClassId) } - - val assertTrue by lazy { assertionId("assertTrue", booleanClassId) } - - val assertNotEquals by lazy { assertionId("assertNotEquals", objectClassId, objectClassId) } - - protected fun assertionId(name: String, vararg params: ClassId): MethodId = - builtinStaticMethodId(assertionsClass, name, voidClassId, *params) - private fun arrayAssertionId(name: String, vararg params: ClassId): MethodId = - builtinStaticMethodId(arraysAssertionsClass, name, voidClassId, *params) - - abstract fun getRunTestsCommand( - executionInvoke: String, - classPath: String, - classesNames: List, - buildDirectory: String - ): List - - override fun toString() = displayName - - // Get is mandatory because of the initialization order of the inheritors. - // Otherwise, in some cases we could get an incorrect value, i.e. allItems = [null, JUnit5, TestNg] - companion object : CodeGenerationSettingBox { - override val defaultItem: TestFramework get() = Junit5 - override val allItems: List get() = listOf(Junit4, Junit5, TestNg) - val parametrizedDefaultItem: TestFramework get() = Junit5 - } -} - -object TestNg : TestFramework(displayName = "TestNG") { - override val mainPackage: String = TEST_NG_PACKAGE - override val testAnnotation: String = "@$mainPackage.Test" - override val testAnnotationFqn: String = "$mainPackage.Test" - - override val parameterizedTestAnnotation: String = "@$mainPackage.Test" - override val parameterizedTestAnnotationFqn: String = "@$mainPackage.Test" - override val methodSourceAnnotation: String = "@$mainPackage.DataProvider" - override val methodSourceAnnotationFqn: String = "@$mainPackage.DataProvider" - - internal const val testXmlName: String = "testng.xml" - - override val assertionsClass: ClassId = BuiltinClassId( - name = TEST_NG_ASSERTIONS, - canonicalName = TEST_NG_ASSERTIONS, - simpleName = "Assert" - ) - - override val arraysAssertionsClass: ClassId = BuiltinClassId( - name = TEST_NG_ARRAYS_ASSERTIONS, - canonicalName = TEST_NG_ARRAYS_ASSERTIONS, - simpleName = "ArrayAsserts" - ) - - override val assertBooleanArrayEquals by lazy { assertionId("assertEquals", booleanArrayClassId, booleanArrayClassId) } - - val throwingRunnableClassId = BuiltinClassId( - name = "${assertionsClass.name}\$ThrowingRunnable", - canonicalName = "${assertionsClass.canonicalName}.ThrowingRunnable", - simpleName = "ThrowingRunnable" - ) - - val assertThrows = builtinStaticMethodId( - classId = assertionsClass, - name = "assertThrows", - // TODO: actually the return type is 'T extends java.lang.Throwable' - returnType = java.lang.Throwable::class.id, - arguments = arrayOf( - Class::class.id, - throwingRunnableClassId - ) - ) - - override val testAnnotationId: ClassId = BuiltinClassId( - name = "$mainPackage.annotations.Test", - canonicalName = "$mainPackage.annotations.Test", - simpleName = "Test" - ) - - override val parameterizedTestAnnotationId: ClassId = BuiltinClassId( - name = "$mainPackage.annotations.Test", - canonicalName = "$mainPackage.annotations.Test", - simpleName = "Test" - ) - - override val methodSourceAnnotationId: ClassId = BuiltinClassId( - name = "$mainPackage.annotations.DataProvider", - canonicalName = "$mainPackage.annotations.DataProvider", - simpleName = "DataProvider" - ) - - override fun getRunTestsCommand( - executionInvoke: String, - classPath: String, - classesNames: List, - buildDirectory: String - ): List { - // TestNg requires a specific xml to run with - writeXmlFileForTestSuite(buildDirectory, classesNames) - - return listOf(executionInvoke, "$mainPackage.TestNG", "$buildDirectory${File.separator}$testXmlName") - } - - private fun writeXmlFileForTestSuite(buildDirectory: String, testsNames: List) { - val packages = testsNames.map { testName -> - constructPackageLine(testName.extractPackage()) - } - - File(buildDirectory + File.separator + testXmlName).writeText(constructTestNgXml(packages)) - } - - private fun String.extractPackage(): String = split(".").dropLast(1).joinToString(".") - - private fun constructPackageLine(pkg: String): String = "" - - private fun constructTestNgXml(packages: List): String = - """ - - - - - ${packages.joinToString(separator = "\t\t\t\n")} - - - - """.trimIndent() -} - -object Junit4 : TestFramework("JUnit4") { - override val mainPackage: String = JUNIT4_PACKAGE - override val testAnnotation = "@$mainPackage.Test" - override val testAnnotationFqn: String = "$mainPackage.Test" - - override val parameterizedTestAnnotation = "Parameterized tests are not supported for JUnit4" - override val parameterizedTestAnnotationFqn = "Parameterized tests are not supported for JUnit4" - override val methodSourceAnnotation = "Parameterized tests are not supported for JUnit4" - override val methodSourceAnnotationFqn = "Parameterized tests are not supported for JUnit4" - - override val testAnnotationId = BuiltinClassId( - name = "$JUNIT4_PACKAGE.Test", - canonicalName = "$JUNIT4_PACKAGE.Test", - simpleName = "Test" - ) - - override val parameterizedTestAnnotationId = voidClassId - override val methodSourceAnnotationId = voidClassId - - val runWithAnnotationClassId = BuiltinClassId( - name = "$JUNIT4_PACKAGE.runner.RunWith", - canonicalName = "$JUNIT4_PACKAGE.runner.RunWith", - simpleName = "RunWith" - ) - - override val assertionsClass = BuiltinClassId( - name = JUNIT4_ASSERTIONS, - canonicalName = JUNIT4_ASSERTIONS, - simpleName = "Assert" - ) - override val arraysAssertionsClass = assertionsClass - - val ignoreAnnotationClassId = with("$JUNIT4_PACKAGE.Ignore") { - BuiltinClassId( - name = this, - canonicalName = this, - simpleName = "Ignore" - ) - } - - override fun getRunTestsCommand( - executionInvoke: String, - classPath: String, - classesNames: List, - buildDirectory: String - ): List = listOf(executionInvoke, "$mainPackage.runner.JUnitCore") + classesNames -} - -object Junit5 : TestFramework("JUnit5") { - override val mainPackage: String = JUNIT5_PACKAGE - override val testAnnotation = "@$mainPackage.Test" - override val testAnnotationFqn: String = "$mainPackage.Test" - override val parameterizedTestAnnotation = "$JUNIT5_PARAMETERIZED_PACKAGE.ParameterizedTest" - override val parameterizedTestAnnotationFqn: String = "$JUNIT5_PARAMETERIZED_PACKAGE.ParameterizedTest" - override val methodSourceAnnotation: String = "$JUNIT5_PARAMETERIZED_PACKAGE.provider.MethodSource" - override val methodSourceAnnotationFqn: String = "$JUNIT5_PARAMETERIZED_PACKAGE.provider.MethodSource" - - val executableClassId = BuiltinClassId( - name = "$JUNIT5_PACKAGE.function.Executable", - canonicalName = "$JUNIT5_PACKAGE.function.Executable", - simpleName = "Executable" - ) - - val timeoutClassId = BuiltinClassId( - name = "$JUNIT5_PACKAGE.Timeout", - canonicalName = "$JUNIT5_PACKAGE.Timeout", - simpleName = "Timeout" - ) - - val timeunitClassId = BuiltinClassId( - name = "TimeUnit", - canonicalName = "java.util.concurrent.TimeUnit", - simpleName = "TimeUnit" - ) - - override val testAnnotationId = BuiltinClassId( - name = "$JUNIT5_PACKAGE.Test", - canonicalName = "$JUNIT5_PACKAGE.Test", - simpleName = "Test" - ) - - override val parameterizedTestAnnotationId = BuiltinClassId( - name = "$JUNIT5_PARAMETERIZED_PACKAGE.ParameterizedTest", - canonicalName = "$JUNIT5_PARAMETERIZED_PACKAGE.ParameterizedTest", - simpleName = "ParameterizedTest" - ) - - override val methodSourceAnnotationId: ClassId = BuiltinClassId( - name = "$JUNIT5_PARAMETERIZED_PACKAGE.provider.MethodSource", - canonicalName = "$JUNIT5_PARAMETERIZED_PACKAGE.provider.MethodSource", - simpleName = "MethodSource" - ) - - override val assertionsClass = BuiltinClassId( - name = JUNIT5_ASSERTIONS, - canonicalName = JUNIT5_ASSERTIONS, - simpleName = "Assertions" - ) - - override val arraysAssertionsClass = assertionsClass - - val assertThrows = builtinStaticMethodId( - classId = assertionsClass, - name = "assertThrows", - // TODO: actually the return type is 'T extends java.lang.Throwable' - returnType = java.lang.Throwable::class.id, - arguments = arrayOf( - Class::class.id, - executableClassId - ) - ) - - val displayNameClassId = BuiltinClassId( - name = "$JUNIT5_PACKAGE.DisplayName", - canonicalName = "$JUNIT5_PACKAGE.DisplayName", - simpleName = "DisplayName" - ) - - val disabledAnnotationClassId = with("$JUNIT5_PACKAGE.Disabled") { - BuiltinClassId( - name = this, - canonicalName = this, - simpleName = "Disabled" - ) - } - - private const val junitVersion = "1.7.1" // TODO read it from gradle.properties - private const val platformJarName: String = "junit-platform-console-standalone-$junitVersion.jar" - - override fun getRunTestsCommand( - executionInvoke: String, - classPath: String, - classesNames: List, - buildDirectory: String - ): List = - listOf( - executionInvoke, - "-jar", classPath.split(File.pathSeparator).single { platformJarName in it }, - ) + isolateCommandLineArgumentsToArgumentFile(listOf("-cp", classPath).plus(classesNames.map { "-c=$it" })) -} - -enum class RuntimeExceptionTestsBehaviour( - override val displayName: String, - override val description: String -) : CodeGenerationSettingItem { - PASS( - displayName = "Passing", - description = "Tests that produce Runtime exceptions should pass (by inserting throwable assertion)" - ), - FAIL( - displayName = "Failing", - description = "Tests that produce Runtime exceptions should fail" + - "(WARNING!: failing tests may appear in testing class)" - ); - - override fun toString(): String = displayName - - // Get is mandatory because of the initialization order of the inheritors. - // Otherwise, in some cases we could get an incorrect value - companion object : CodeGenerationSettingBox { - override val defaultItem: RuntimeExceptionTestsBehaviour get() = FAIL - override val allItems: List = values().toList() - } -} - -data class HangingTestsTimeout(val timeoutMs: Long) { - constructor() : this(DEFAULT_TIMEOUT_MS) - - companion object { - const val DEFAULT_TIMEOUT_MS = DEFAULT_CONCRETE_EXECUTION_TIMEOUT_IN_CHILD_PROCESS_MS - const val MIN_TIMEOUT_MS = 100L - const val MAX_TIMEOUT_MS = 1_000_000L - } -} - -enum class ForceStaticMocking( - override val displayName: String, - override val description: String, - val warningMessage: List, -) : CodeGenerationSettingItem { - FORCE( - displayName = "Force static mocking", - description = "Use mocks for static methods and constructors invocations even if static mocking is disabled" + - "(WARNING!: can add imports from missing dependencies)", - warningMessage = listOf( - """WARNING!!! Automatically used "${StaticsMocking.defaultItem}" framework for mocking statics""", - "because execution encountered flaky methods", - "To change this behaviour edit [Settings -> UtBot -> Force static mocking]" - ) - ), - DO_NOT_FORCE( - displayName = "Do not force static mocking", - description = "Do not force static mocking if static mocking setting is disabled" + - "(WARNING!: flaky tests can appear)", - warningMessage = listOf( - "Warning!!! This test can be flaky because execution encountered flaky methods,", - """but no "static mocking" was selected""" - ) - ); - - override fun toString(): String = displayName - - // Get is mandatory because of the initialization order of the inheritors. - // Otherwise, in some cases we could get an incorrect value - companion object : CodeGenerationSettingBox { - override val defaultItem: ForceStaticMocking get() = FORCE - override val allItems: List = values().toList() - } -} - -enum class ParametrizedTestSource( - override val displayName: String, - override val description: String = "Use $displayName for parametrized tests" -) : CodeGenerationSettingItem { - DO_NOT_PARAMETRIZE( - displayName = "Not parametrized", - description = "Do not generate parametrized tests" - ), - PARAMETRIZE( - displayName = "Parametrized", - description = "Generate parametrized tests" - ); - - override fun toString(): String = displayName - - // Get is mandatory because of the initialization order of the inheritors. - // Otherwise, in some cases we could get an incorrect value - companion object : CodeGenerationSettingBox { - override val defaultItem: ParametrizedTestSource = DO_NOT_PARAMETRIZE - override val allItems: List = values().toList() - } -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Keywords.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Keywords.kt deleted file mode 100644 index b4f248ddef..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/Keywords.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.utbot.framework.codegen - -import org.utbot.framework.plugin.api.CodegenLanguage - -private val javaKeywords = setOf( - "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", - "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "goto", - "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", - "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", - "throw", "throws", "transient", "try", "void", "volatile", "while", "null", "false", "true" -) - -private val kotlinHardKeywords = setOf( - "as", "as?", "break", "class", "continue", "do", "else", "false", "for", "fun", "if", "in", "!in", "interface", - "is", "!is", "null", "object", "package", "return", "super", "this", "throw", "true", "try", "typealias", "typeof", - "val", "var", "when", "while" -) - -@Suppress("unused") -private val kotlinSoftKeywords = setOf( - "by", "catch", "constructor", "delegate", "dynamic", "field", "file", "finally", "get", "import", "init", - "param", "property", "receiver", "set", "setparam", "value", "where" -) - -@Suppress("unused") -private val kotlinModifierKeywords = setOf( - "actual", "abstract", "annotation", "companion", "const", "crossinline", "data", "enum", "expect", "external", - "final", "infix", "inline", "inner", "internal", "lateinit", "noinline", "open", "operator", "out", "override", - "private", "protected", "public", "reified", "sealed", "suspend", "tailrec", "vararg" -) - -// For now we check only hard keywords because others can be used as methods and variables identifiers -private val kotlinKeywords = kotlinHardKeywords - -private fun getLanguageKeywords(codegenLanguage: CodegenLanguage): Set = when(codegenLanguage) { - CodegenLanguage.JAVA -> javaKeywords - CodegenLanguage.KOTLIN -> kotlinKeywords -} - -fun isLanguageKeyword(word: String, codegenLanguage: CodegenLanguage): Boolean = - word in getLanguageKeywords(codegenLanguage) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/TestCodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/TestCodeGenerator.kt deleted file mode 100644 index ca8b34b8f6..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/TestCodeGenerator.kt +++ /dev/null @@ -1,35 +0,0 @@ -package org.utbot.framework.codegen - -import org.utbot.common.packageName -import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtTestCase - -interface TestCodeGenerator { - fun init( - classUnderTest: Class<*>, - params: MutableMap, List> = mutableMapOf(), - testFramework: TestFramework = Junit5, - mockFramework: MockFramework?, - staticsMocking: StaticsMocking, - forceStaticMocking: ForceStaticMocking, - generateWarningsForStaticMocking: Boolean, - codegenLanguage: CodegenLanguage = CodegenLanguage.JAVA, - parameterizedTestSource: ParametrizedTestSource = ParametrizedTestSource.DO_NOT_PARAMETRIZE, - runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.defaultItem, - hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(), - enableTestsTimeout: Boolean = true, - testClassPackageName: String = classUnderTest.packageName - ) - - fun generateAsString(testCases: Collection, testClassCustomName: String? = null): String - - fun generateAsStringWithTestReport( - testCases: Collection, - testClassCustomName: String? = null - ): TestsCodeWithTestReport -} - -data class TestsCodeWithTestReport(val generatedCode: String, val testsGenerationReport: TestsGenerationReport) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/Domain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/Domain.kt new file mode 100644 index 0000000000..dfe8e2be57 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/Domain.kt @@ -0,0 +1,726 @@ +package org.utbot.framework.codegen.domain + +import org.utbot.framework.DEFAULT_EXECUTION_TIMEOUT_IN_INSTRUMENTED_PROCESS_MS +import org.utbot.framework.codegen.domain.builtin.mockitoClassId +import org.utbot.framework.codegen.domain.builtin.ongoingStubbingClassId +import org.utbot.framework.codegen.tree.argumentsClassId +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.CgClassId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodeGenerationSettingBox +import org.utbot.framework.plugin.api.CodeGenerationSettingItem +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.TypeParameters +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.isolateCommandLineArgumentsToArgumentFile +import org.utbot.framework.plugin.api.util.booleanArrayClassId +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.builtinMethodId +import org.utbot.framework.plugin.api.util.builtinStaticMethodId +import org.utbot.framework.plugin.api.util.byteArrayClassId +import org.utbot.framework.plugin.api.util.charArrayClassId +import org.utbot.framework.plugin.api.util.doubleArrayClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.floatArrayClassId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intArrayClassId +import org.utbot.framework.plugin.api.util.longArrayClassId +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.shortArrayClassId +import org.utbot.framework.plugin.api.util.voidClassId +import java.io.File +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.voidWrapperClassId + +data class TestClassFile(val packageName: String, val imports: List, val testClass: String) + +abstract class Import(val order: Int) : Comparable { + abstract val qualifiedName: String + + override fun compareTo(other: Import) = importComparator.compare(this, other) +} + +private val importComparator = compareBy { it.order }.thenBy { it.qualifiedName } + +data class StaticImport(val qualifierClass: String, val memberName: String) : Import(1) { + override val qualifiedName: String = "$qualifierClass.$memberName" +} + +data class RegularImport(val packageName: String, val className: String) : Import(2) { + override val qualifiedName: String + get() = if (packageName.isNotEmpty()) "$packageName.$className" else className + + // TODO: check without equals() and hashCode() + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RegularImport + + if (packageName != other.packageName) return false + if (className != other.className) return false + + return true + } + + override fun hashCode(): Int { + var result = packageName.hashCode() + result = 31 * result + className.hashCode() + return result + } +} + +private const val TEST_NG_PACKAGE = "org.testng" +private const val JUNIT4_PACKAGE = "org.junit" +private const val JUNIT5_PACKAGE = "org.junit.jupiter.api" +const val JUNIT5_PARAMETERIZED_PACKAGE = "org.junit.jupiter.params" + +//JUnit5 imports +private const val TEST_NG_ASSERTIONS = "org.testng.Assert" +private const val TEST_NG_ARRAYS_ASSERTIONS = "org.testng.internal.junit.ArrayAsserts" +private const val JUNIT5_ASSERTIONS = "org.junit.jupiter.api.Assertions" +private const val JUNIT4_ASSERTIONS = "org.junit.Assert" + +fun junitByVersion(version: Int): TestFramework = + when (version) { + 4 -> Junit4 + 5 -> Junit5 + else -> error("Expected JUnit version 4 or 5, but got: $version") + } + +fun testFrameworkByName(testFramework: String): TestFramework = + when (testFramework) { + "junit4" -> Junit4 + "junit5" -> Junit5 + "testng" -> TestNg + else -> error("Unexpected test framework name: $testFramework") + } + +/** + * This feature allows to enable additional mockito-core settings required for static mocking. + * It is implemented via adding special file "MockMaker" into test project resources. + */ +sealed class StaticsMocking( + var isConfigured: Boolean = false, + override val id: String, + override val displayName: String, + override val description: String = "Use static methods mocking" +) : CodeGenerationSettingItem { + override fun toString(): String = id + + // Get is mandatory because of the initialization order of the inheritors. + // Otherwise, in some cases we could get an incorrect value + companion object : CodeGenerationSettingBox { + override val defaultItem: StaticsMocking + get() = MockitoStaticMocking + override val allItems: List + get() = listOf(NoStaticMocking, MockitoStaticMocking) + } +} + +object NoStaticMocking : StaticsMocking( + id = "No static mocking", + displayName = "No static mocking", + description = "Do not use additional settings to mock static fields" +) + +object MockitoStaticMocking : StaticsMocking(id = "Mockito static mocking", displayName = "Mockito static mocking") { + + val mockedStaticClassId = BuiltinClassId( + canonicalName = "org.mockito.MockedStatic", + simpleName = "MockedStatic" + ) + + val mockedConstructionClassId = BuiltinClassId( + canonicalName = "org.mockito.MockedConstruction", + simpleName = "MockedConstruction" + ) + + val mockStaticMethodId = builtinStaticMethodId( + classId = mockitoClassId, + name = "mockStatic", + returnType = mockedStaticClassId, + arguments = arrayOf(objectClassId) + ) + + val mockConstructionMethodId = builtinStaticMethodId( + classId = mockitoClassId, + name = "mockConstruction", + returnType = mockedConstructionClassId, + // actually second argument is lambda + arguments = arrayOf(objectClassId, objectClassId) + ) + + val mockedStaticWhen = builtinMethodId( + classId = mockedStaticClassId, + name = "when", + returnType = ongoingStubbingClassId, + // argument type is actually a functional interface + arguments = arrayOf(objectClassId) + ) + + fun mockedStaticWhen(nullable: Boolean): MethodId = builtinMethodId( + classId = mockedStaticClassId, + name = "when", + returnType = CgClassId(ongoingStubbingClassId, isNullable = nullable), + // argument type is actually a functional interface + arguments = arrayOf(objectClassId) + ) +} + +abstract class TestFramework( + override val id: String, + override val displayName: String, + override val description: String = "Use $displayName as test framework", +) : CodeGenerationSettingItem { + var isParametrizedTestsConfigured = false + var isInstalled: Boolean = false + abstract val mainPackage: String + abstract val assertionsClass: ClassId + abstract val arraysAssertionsClass: ClassId + abstract val kotlinFailureAssertionsClass: ClassId + abstract val testAnnotationId: ClassId + abstract val beforeMethodId: ClassId + abstract val afterMethodId: ClassId + abstract val parameterizedTestAnnotationId: ClassId + abstract val methodSourceAnnotationId: ClassId + abstract val nestedClassesShouldBeStatic: Boolean + abstract val argListClassId: ClassId + + open val testSuperClass: ClassId? = null + + open val assertSame by lazy { assertionId("assertSame", objectClassId, objectClassId) } + + open val assertEquals by lazy { assertionId("assertEquals", objectClassId, objectClassId) } + + val assertFloatEquals by lazy { assertionId("assertEquals", floatClassId, floatClassId, floatClassId) } + + val assertDoubleEquals by lazy { assertionId("assertEquals", doubleClassId, doubleClassId, doubleClassId) } + + val assertArrayEquals by lazy { arrayAssertionId("assertArrayEquals", Array::class.id, Array::class.id) } + + open val assertBooleanArrayEquals by lazy { assertionId("assertArrayEquals", booleanArrayClassId, booleanArrayClassId) } + + val assertByteArrayEquals by lazy { arrayAssertionId("assertArrayEquals", byteArrayClassId, byteArrayClassId) } + + val assertCharArrayEquals by lazy { arrayAssertionId("assertArrayEquals", charArrayClassId, charArrayClassId) } + + val assertShortArrayEquals by lazy { arrayAssertionId("assertArrayEquals", shortArrayClassId, shortArrayClassId) } + + val assertIntArrayEquals by lazy { arrayAssertionId("assertArrayEquals", intArrayClassId, intArrayClassId) } + + val assertLongArrayEquals by lazy { arrayAssertionId("assertArrayEquals", longArrayClassId, longArrayClassId) } + + val assertFloatArrayEquals by lazy { arrayAssertionId("assertArrayEquals", floatArrayClassId, floatArrayClassId, floatClassId) } + + val assertDoubleArrayEquals by lazy { arrayAssertionId("assertArrayEquals", doubleArrayClassId, doubleArrayClassId, doubleClassId) } + + val assertNull by lazy { assertionId("assertNull", objectClassId) } + + val assertNotNull by lazy { assertionId("assertNotNull", objectClassId) } + + val assertFalse by lazy { assertionId("assertFalse", booleanClassId) } + + val assertTrue by lazy { assertionId("assertTrue", booleanClassId) } + + val assertNotEquals by lazy { assertionId("assertNotEquals", objectClassId, objectClassId) } + + val fail by lazy { assertionId("fail", objectClassId) } + + val kotlinFail by lazy { kotlinFailAssertionId("fail", objectClassId) } + + protected open fun assertionId(name: String, vararg params: ClassId): MethodId = + builtinStaticMethodId(assertionsClass, name, voidClassId, *params) + + private fun arrayAssertionId(name: String, vararg params: ClassId): MethodId = + builtinStaticMethodId(arraysAssertionsClass, name, voidClassId, *params) + + private fun kotlinFailAssertionId(name: String, vararg params: ClassId): MethodId = + builtinStaticMethodId(kotlinFailureAssertionsClass, name, voidClassId, *params) + + abstract fun getRunTestsCommand( + executionInvoke: String, + classPath: String, + classesNames: List, + buildDirectory: String, + additionalArguments: List + ): List + + override fun toString() = id + + // Get is mandatory because of the initialization order of the inheritors. + // Otherwise, in some cases we could get an incorrect value, i.e. allItems = [null, JUnit5, TestNg] + companion object : CodeGenerationSettingBox { + override val defaultItem: TestFramework get() = Junit5 + override val allItems: List get() = listOf(Junit4, Junit5, TestNg) + val parametrizedDefaultItem: TestFramework get() = Junit5 + } +} + +class UnknownTestFramework(id: String) : TestFramework(id = id, displayName = id) { + override val mainPackage: String = id + override val assertionsClass: ClassId = ClassId(id) + override val arraysAssertionsClass: ClassId = ClassId(id) + override val kotlinFailureAssertionsClass: ClassId = ClassId(id) + override val testAnnotationId: ClassId = ClassId(id) + override val beforeMethodId: ClassId = ClassId(id) + override val afterMethodId: ClassId = ClassId(id) + override val parameterizedTestAnnotationId: ClassId = ClassId(id) + override val methodSourceAnnotationId: ClassId = ClassId(id) + override val nestedClassesShouldBeStatic: Boolean = false + override val argListClassId: ClassId = ClassId(id) + + override fun getRunTestsCommand( + executionInvoke: String, + classPath: String, + classesNames: List, + buildDirectory: String, + additionalArguments: List + ): List = emptyList() + +} + +object TestNg : TestFramework(id = "TestNG",displayName = "TestNG") { + override val mainPackage: String = TEST_NG_PACKAGE + + override val testAnnotationId: ClassId = BuiltinClassId( + canonicalName = "$mainPackage.annotations.Test", + simpleName = "Test" + ) + + override val beforeMethodId = BuiltinClassId( + canonicalName = "$mainPackage.annotations.BeforeMethod", + simpleName = "BeforeMethod" + ) + + override val afterMethodId = BuiltinClassId( + canonicalName = "$mainPackage.annotations.AfterMethod", + simpleName = "AfterMethod" + ) + + internal const val testXmlName: String = "testng.xml" + + override val assertionsClass: ClassId = BuiltinClassId( + canonicalName = TEST_NG_ASSERTIONS, + simpleName = "Assert" + ) + + override val arraysAssertionsClass: ClassId = BuiltinClassId( + canonicalName = TEST_NG_ARRAYS_ASSERTIONS, + simpleName = "ArrayAsserts" + ) + + override val kotlinFailureAssertionsClass = assertionsClass + + override val assertBooleanArrayEquals by lazy { assertionId("assertEquals", booleanArrayClassId, booleanArrayClassId) } + + val throwingRunnableClassId = BuiltinClassId( + canonicalName = "${assertionsClass.canonicalName}.ThrowingRunnable", + simpleName = "ThrowingRunnable" + ) + + val assertThrows = builtinStaticMethodId( + classId = assertionsClass, + name = "assertThrows", + // TODO: actually the return type is 'T extends java.lang.Throwable' + returnType = java.lang.Throwable::class.id, + arguments = arrayOf( + Class::class.id, + throwingRunnableClassId + ) + ) + + override val parameterizedTestAnnotationId: ClassId = BuiltinClassId( + canonicalName = "$mainPackage.annotations.Test", + simpleName = "Test", + ) + + override val methodSourceAnnotationId: ClassId = BuiltinClassId( + canonicalName = "$mainPackage.annotations.DataProvider", + simpleName = "DataProvider" + ) + + override val nestedClassesShouldBeStatic = true + + override val argListClassId: ClassId + get() = Array?>::class.id + + @OptIn(ExperimentalStdlibApi::class) + override fun getRunTestsCommand( + executionInvoke: String, + classPath: String, + classesNames: List, + buildDirectory: String, + additionalArguments: List + ): List { + // TestNg requires a specific xml to run with + writeXmlFileForTestSuite(buildDirectory, classesNames) + + return buildList { + add(executionInvoke) + addAll(additionalArguments) + add("$mainPackage.TestNG") + add("$buildDirectory${File.separator}$testXmlName") + } + } + + private fun writeXmlFileForTestSuite(buildDirectory: String, testsNames: List) { + val packages = testsNames.map { testName -> + constructPackageLine(testName.extractPackage()) + } + + File(buildDirectory + File.separator + testXmlName).writeText(constructTestNgXml(packages)) + } + + private fun String.extractPackage(): String = split(".").dropLast(1).joinToString(".") + + private fun constructPackageLine(pkg: String): String = "" + + private fun constructTestNgXml(packages: List): String = + """ + + + + + ${packages.joinToString(separator = "\t\t\t\n")} + + + + """.trimIndent() +} + +object Junit4 : TestFramework(id = "JUnit4",displayName = "JUnit 4") { + private val parametrizedTestsNotSupportedError: Nothing + get() = error("Parametrized tests are not supported for JUnit 4") + + override val mainPackage: String = JUNIT4_PACKAGE + + override val testAnnotationId = BuiltinClassId( + canonicalName = "$mainPackage.Test", + simpleName = "Test" + ) + + override val beforeMethodId = BuiltinClassId( + canonicalName = "$mainPackage.Before", + simpleName = "Before" + ) + + override val afterMethodId = BuiltinClassId( + canonicalName = "$mainPackage.After", + simpleName = "After" + ) + + override val parameterizedTestAnnotationId = voidClassId + override val methodSourceAnnotationId = voidClassId + + val runWithAnnotationClassId = BuiltinClassId( + canonicalName = "$JUNIT4_PACKAGE.runner.RunWith", + simpleName = "RunWith" + ) + + override val assertionsClass = BuiltinClassId( + canonicalName = JUNIT4_ASSERTIONS, + simpleName = "Assert" + ) + override val arraysAssertionsClass = assertionsClass + override val kotlinFailureAssertionsClass = assertionsClass + + val ignoreAnnotationClassId = with("$JUNIT4_PACKAGE.Ignore") { + BuiltinClassId( + canonicalName = this, + simpleName = "Ignore" + ) + } + + override val nestedClassesShouldBeStatic = true + + override val argListClassId: ClassId + get() = parametrizedTestsNotSupportedError + + @OptIn(ExperimentalStdlibApi::class) + override fun getRunTestsCommand( + executionInvoke: String, + classPath: String, + classesNames: List, + buildDirectory: String, + additionalArguments: List + ): List = buildList { + add(executionInvoke) + addAll(additionalArguments) + add("$mainPackage.runner.JUnitCore") + addAll(classesNames) + } +} + +object Junit5 : TestFramework(id = "JUnit5", displayName = "JUnit 5") { + override val mainPackage: String = JUNIT5_PACKAGE + + override val testAnnotationId = BuiltinClassId( + canonicalName = "$JUNIT5_PACKAGE.Test", + simpleName = "Test" + ) + + override val beforeMethodId = BuiltinClassId( + canonicalName = "${mainPackage}.BeforeEach", + simpleName = "BeforeEach" + ) + + override val afterMethodId = BuiltinClassId( + canonicalName = "${mainPackage}.AfterEach", + simpleName = "AfterEach" + ) + + val executableClassId = BuiltinClassId( + canonicalName = "$JUNIT5_PACKAGE.function.Executable", + simpleName = "Executable" + ) + + val timeoutClassId = BuiltinClassId( + canonicalName = "$JUNIT5_PACKAGE.Timeout", + simpleName = "Timeout" + ) + + val timeunitClassId = BuiltinClassId( + canonicalName = "java.util.concurrent.TimeUnit", + simpleName = "TimeUnit" + ) + + val durationClassId = BuiltinClassId( + canonicalName = "java.time.Duration", + simpleName = "Duration" + ) + + val ofMillis = builtinStaticMethodId( + classId = durationClassId, + name = "ofMillis", + returnType = durationClassId, + arguments = arrayOf(longClassId) + ) + + val nestedTestClassAnnotationId = BuiltinClassId( + canonicalName = "$JUNIT5_PACKAGE.Nested", + simpleName = "Nested" + ) + + override val parameterizedTestAnnotationId = BuiltinClassId( + canonicalName = "$JUNIT5_PARAMETERIZED_PACKAGE.ParameterizedTest", + simpleName = "ParameterizedTest" + ) + + override val methodSourceAnnotationId: ClassId = BuiltinClassId( + canonicalName = "$JUNIT5_PARAMETERIZED_PACKAGE.provider.MethodSource", + simpleName = "MethodSource" + ) + + override val assertionsClass = BuiltinClassId( + canonicalName = JUNIT5_ASSERTIONS, + simpleName = "Assertions" + ) + + override val arraysAssertionsClass = assertionsClass + + override val kotlinFailureAssertionsClass = BuiltinClassId( + canonicalName = "org.junit.jupiter.api", + simpleName = "Assertions" + ) + + val assertThrows = builtinStaticMethodId( + classId = assertionsClass, + name = "assertThrows", + // TODO: actually the return type is 'T extends java.lang.Throwable' + returnType = java.lang.Throwable::class.id, + arguments = arrayOf( + Class::class.id, + executableClassId + ) + ) + + val assertTimeoutPreemptively = builtinStaticMethodId( + classId = assertionsClass, + name = "assertTimeoutPreemptively", + returnType = voidWrapperClassId, + arguments = arrayOf( + durationClassId, + executableClassId + ) + ) + + val displayNameClassId = BuiltinClassId( + canonicalName = "$JUNIT5_PACKAGE.DisplayName", + simpleName = "DisplayName" + ) + + val disabledAnnotationClassId = with("$JUNIT5_PACKAGE.Disabled") { + BuiltinClassId( + canonicalName = this, + simpleName = "Disabled" + ) + } + + override val nestedClassesShouldBeStatic = false + + override val argListClassId: ClassId + get() { + val arrayListId = java.util.ArrayList::class.id + return BuiltinClassId( + simpleName = arrayListId.simpleName, + canonicalName = arrayListId.canonicalName, + packageName = arrayListId.packageName, + typeParameters = TypeParameters(listOf(argumentsClassId)) + ) + } + + private const val junitVersion = "1.9.0" // TODO read it from gradle.properties + private const val platformJarName: String = "junit-platform-console-standalone-$junitVersion.jar" + + @OptIn(ExperimentalStdlibApi::class) + override fun getRunTestsCommand( + executionInvoke: String, + classPath: String, + classesNames: List, + buildDirectory: String, + additionalArguments: List + ): List = buildList { + add(executionInvoke) + addAll(additionalArguments) + add("-jar") + add(classPath.split(File.pathSeparator).single { platformJarName in it }) + add(isolateCommandLineArgumentsToArgumentFile(listOf("-cp", classPath).plus(classesNames.map { "-c=$it" }))) + } +} + +enum class RuntimeExceptionTestsBehaviour( + override val id: String, + override val displayName: String, + override val description: String +) : CodeGenerationSettingItem { + PASS( + id = "Passing", + displayName = "Pass", + description = "Tests that produce Runtime exceptions should pass (by inserting throwable assertion)" + ), + FAIL( + id = "Failing", + displayName = "Fail", + description = "Tests that produce Runtime exceptions should fail" + + "(WARNING!: failing tests may appear in testing class)" + ); + + override fun toString(): String = id + + // Get is mandatory because of the initialization order of the inheritors. + // Otherwise, in some cases we could get an incorrect value + companion object : CodeGenerationSettingBox { + override val defaultItem: RuntimeExceptionTestsBehaviour get() = FAIL + override val allItems: List = values().toList() + } +} + +data class HangingTestsTimeout(val timeoutMs: Long) { + constructor() : this(DEFAULT_TIMEOUT_MS) + + companion object { + const val DEFAULT_TIMEOUT_MS = DEFAULT_EXECUTION_TIMEOUT_IN_INSTRUMENTED_PROCESS_MS + const val MIN_TIMEOUT_MS = 100L + const val MAX_TIMEOUT_MS = 1_000_000L + } +} + +enum class ForceStaticMocking( + override val id: String, + override val displayName: String, + override val description: String, + val warningMessage: List, +) : CodeGenerationSettingItem { + FORCE( + id = "Force static mocking", + displayName = "Force static mocking", + description = "Use mocks for static methods and constructors invocations even if static mocking is disabled" + + "(WARNING!: can add imports from missing dependencies)", + warningMessage = listOf( + """WARNING!!! Automatically used "${StaticsMocking.defaultItem}" framework for mocking statics""", + "because execution encountered flaky methods", + "To change this behaviour edit [Settings -> UtBot -> Force static mocking]" + ) + ), + DO_NOT_FORCE( + id = "Do not force static mocking", + displayName = "Do not force static mocking", + description = "Do not force static mocking if static mocking setting is disabled" + + "(WARNING!: flaky tests can appear)", + warningMessage = listOf( + "Warning!!! This test can be flaky because execution encountered flaky methods,", + """but no "static mocking" was selected""" + ) + ); + + override fun toString(): String = id + + // Get is mandatory because of the initialization order of the inheritors. + // Otherwise, in some cases we could get an incorrect value + companion object : CodeGenerationSettingBox { + override val defaultItem: ForceStaticMocking get() = FORCE + override val allItems: List = values().toList() + } +} + +enum class ParametrizedTestSource( + override val id: String, + override val displayName: String, + override val description: String = "Use $displayName for parametrized tests" +) : CodeGenerationSettingItem { + DO_NOT_PARAMETRIZE( + id = "Not parametrized", + displayName = "Not parametrized", + description = "Do not generate parametrized tests" + ), + PARAMETRIZE( + id = "Parametrized", + displayName = "Parametrized", + description = "Generate parametrized tests" + ); + + override fun toString(): String = id + + // Get is mandatory because of the initialization order of the inheritors. + // Otherwise, in some cases we could get an incorrect value + companion object : CodeGenerationSettingBox { + override val defaultItem: ParametrizedTestSource = DO_NOT_PARAMETRIZE + override val allItems: List = values().toList() + } +} + +enum class ProjectType { + /** + * Standard JVM project without DI frameworks + */ + PureJvm, + + /** + * Spring or Spring Boot project + */ + Spring, + + /** + * Python project + */ + Python, + + /** + * JavaScript project + */ + JavaScript, +} + +/** + * Extended [UtModel] model with testSet id and execution id. + * + * Used as a key in [valueByUtModelWrapper]. + * Was introduced primarily for shared among all test methods global variables. + */ +data class UtModelWrapper( + val testSetId: Int, + val executionId: Int, + val model: UtModel, +) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/MockitoBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/MockitoBuiltins.kt similarity index 82% rename from utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/MockitoBuiltins.kt rename to utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/MockitoBuiltins.kt index 1a2805b615..d78bd10b3d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/MockitoBuiltins.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/MockitoBuiltins.kt @@ -1,7 +1,6 @@ -package org.utbot.framework.codegen.model.constructor.builtin +package org.utbot.framework.codegen.domain.builtin import org.utbot.framework.plugin.api.BuiltinClassId -import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.util.booleanClassId import org.utbot.framework.plugin.api.util.builtinMethodId import org.utbot.framework.plugin.api.util.builtinStaticMethodId @@ -16,42 +15,44 @@ import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.framework.plugin.api.util.shortClassId import org.utbot.framework.plugin.api.util.stringClassId -internal val mockitoBuiltins: Set - get() = setOf( - mockMethodId, whenMethodId, thenMethodId, thenReturnMethodId, - any, anyOfClass, anyByte, anyChar, anyShort, anyInt, anyLong, - anyFloat, anyDouble, anyBoolean, anyString - ) - internal val mockitoClassId = BuiltinClassId( - name = "org.mockito.Mockito", canonicalName = "org.mockito.Mockito", simpleName = "Mockito", ) internal val ongoingStubbingClassId = BuiltinClassId( - name = "org.mockito.stubbing.OngoingStubbing", canonicalName = "org.mockito.stubbing.OngoingStubbing", simpleName = "OngoingStubbing", ) +internal val stubberClassId = BuiltinClassId( + canonicalName = "org.mockito.stubbing.Stubber", + simpleName = "Stubber" +) + +// We wrap an Object classId in BuiltinClassId +// in order to deal with [CgExpression.canBeReceiverOf] +// when we want to call ANY method on an object. +internal val genericObjectClassId = BuiltinClassId( + canonicalName = "java.lang.Object", + simpleName = "Object" +) + internal val answerClassId = BuiltinClassId( - name = "org.mockito.stubbing.Answer", canonicalName = "org.mockito.stubbing.Answer", simpleName = "Answer", ) internal val argumentMatchersClassId = BuiltinClassId( - name = "org.mockito.ArgumentMatchers", canonicalName = "org.mockito.ArgumentMatchers", simpleName = "ArgumentMatchers", ) internal val mockedConstructionContextClassId = BuiltinClassId( - name = "org.mockito.MockedConstruction.Context", - canonicalName = "org.mockito.MockedConstruction.Context", // TODO use $ as a delimiter of outer and nested classes? + canonicalName = "org.mockito.MockedConstruction.Context", simpleName = "Context", - isNested = true + name = "org.mockito.MockedConstruction\$Context", + isNested = true, ) internal val mockMethodId = builtinStaticMethodId( @@ -85,6 +86,19 @@ internal val thenReturnMethodId = builtinMethodId( arguments = arrayOf(objectClassId) ) +internal val doNothingMethodId = builtinStaticMethodId( + classId = mockitoClassId, + name = "doNothing", + returnType = stubberClassId, +) + +internal val whenStubberMethodId = builtinMethodId( + classId = stubberClassId, + name = "when", + returnType = genericObjectClassId, + arguments = arrayOf(objectClassId) +) + // TODO: for this method and other static methods implement some utils that allow calling // TODO: these methods without explicit declaring class id specification, because right now such calls are too verbose internal val any = builtinStaticMethodId( diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/ReflectionBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/ReflectionBuiltins.kt similarity index 89% rename from utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/ReflectionBuiltins.kt rename to utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/ReflectionBuiltins.kt index dd5acb019f..522049321e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/ReflectionBuiltins.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/ReflectionBuiltins.kt @@ -1,4 +1,4 @@ -package org.utbot.framework.codegen.model.constructor.builtin +package org.utbot.framework.codegen.domain.builtin import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.MethodId @@ -19,15 +19,6 @@ import java.lang.reflect.InvocationTargetException //TODO: these methods are called builtins, but actually are just [MethodId] //may be fixed in https://github.com/UnitTestBot/UTBotJava/issues/138 -internal val reflectionBuiltins: Set - get() = setOf( - setAccessible, invoke, newInstance, get, forName, - getDeclaredMethod, getDeclaredConstructor, allocateInstance, - getClass, getDeclaredField, isEnumConstant, getFieldName, - equals, getSuperclass, set, newArrayInstance, - setArrayElement, getArrayElement, getTargetException, - ) - internal val setAccessible = methodId( classId = AccessibleObject::class.id, name = "setAccessible", @@ -49,7 +40,7 @@ internal val newInstance = methodId( arguments = arrayOf(Array::class.id) ) -internal val get = methodId( +internal val getMethodId = methodId( classId = Field::class.id, name = "get", returnType = objectClassId, @@ -132,7 +123,7 @@ internal val getSuperclass = methodId( returnType = Class::class.id ) -internal val set = methodId( +internal val setMethodId = methodId( classId = Field::class.id, name = "set", returnType = voidClassId, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/UtilMethodBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/UtilMethodBuiltins.kt new file mode 100644 index 0000000000..e87b580b18 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/UtilMethodBuiltins.kt @@ -0,0 +1,128 @@ +package org.utbot.framework.codegen.domain.builtin + +import org.mockito.MockitoAnnotations +import org.utbot.framework.codegen.domain.MockitoStaticMocking +import org.utbot.framework.codegen.renderer.utilMethodTextById +import org.utbot.framework.codegen.tree.ututils.UtilClassKind.Companion.PACKAGE_DELIMITER +import org.utbot.framework.codegen.tree.ututils.UtilClassKind.Companion.UT_UTILS_BASE_PACKAGE_NAME +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.BuiltinMethodId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.framework.utils.UtilMethodProvider + + +/** + * This provider represents an util class file that is generated and put into the user's test module. + * The generated class is UtUtils (its id is defined at [utJavaUtilsClassId] or [utKotlinUtilsClassId]). + * + * Content of this util class may be different (due to mocks in deepEquals), but the methods (and their ids) are the same. + */ +internal class UtilClassFileMethodProvider(language: CodegenLanguage) + : UtilMethodProvider(selectUtilClassId(language)) { + /** + * This property contains the current version of util class. + * This version will be written to the util class file inside a comment. + * + * Whenever we want to create an util class, we first check if there is an already existing one. + * If there is, then we decide whether we need to overwrite it or not. One of the factors here + * is the version of this existing class. If the version of existing class is older than the one + * that is currently stored in [UtilClassFileMethodProvider.UTIL_CLASS_VERSION], then we need to + * overwrite an util class, because it might have been changed in the new version. + * + * **IMPORTANT** if you make any changes to util methods (see [utilMethodTextById]), do not forget to update this version. + */ + val UTIL_CLASS_VERSION = "2.1" +} + +class TestClassUtilMethodProvider(testClassId: ClassId) : UtilMethodProvider(testClassId) + +internal fun selectUtilClassId(codegenLanguage: CodegenLanguage): ClassId = + when (codegenLanguage) { + CodegenLanguage.JAVA -> utJavaUtilsClassId + CodegenLanguage.KOTLIN -> utKotlinUtilsClassId + } + +internal val utJavaUtilsClassId: ClassId + get() = BuiltinClassId( + canonicalName = UT_UTILS_BASE_PACKAGE_NAME + PACKAGE_DELIMITER + "java" + PACKAGE_DELIMITER + "UtUtils", + simpleName = "UtUtils", + isFinal = true, + ) + +internal val utKotlinUtilsClassId: ClassId + get() = BuiltinClassId( + canonicalName = UT_UTILS_BASE_PACKAGE_NAME + PACKAGE_DELIMITER + "kotlin" + PACKAGE_DELIMITER + "UtUtils", + simpleName = "UtUtils", + isFinal = true, + isKotlinObject = true + ) + +val openMocksMethodId = BuiltinMethodId( + classId = MockitoAnnotations::class.id, + name = "openMocks", + returnType = AutoCloseable::class.java.id, + parameters = listOf(objectClassId), + isStatic = true, +) + +/** + * [MethodId] for [AutoCloseable.close]. + */ +val closeMethodId = MethodId( + classId = AutoCloseable::class.java.id, + name = "close", + returnType = voidClassId, + parameters = emptyList(), +) + +private val clearCollectionMethodId = MethodId( + classId = Collection::class.java.id, + name = "clear", + returnType = voidClassId, + parameters = emptyList() +) + +private val clearMapMethodId = MethodId( + classId = Map::class.java.id, + name = "clear", + returnType = voidClassId, + parameters = emptyList() +) + +fun clearMethodId(javaClass: Class<*>): MethodId = when { + Collection::class.java.isAssignableFrom(javaClass) -> clearCollectionMethodId + Map::class.java.isAssignableFrom(javaClass) -> clearMapMethodId + else -> error("Clear method is not implemented for $javaClass") +} + +val mocksAutoCloseable: Set = setOf( + MockitoStaticMocking.mockedStaticClassId, + MockitoStaticMocking.mockedConstructionClassId +) + +val predefinedAutoCloseable: Set = mocksAutoCloseable + +/** + * Checks if this class is marked as auto closeable + * (useful for classes that could not be loaded by class loader like mocks for mocking statics from Mockito Inline). + */ +internal val ClassId.isPredefinedAutoCloseable: Boolean + get() = this in predefinedAutoCloseable + +/** + * Returns [AutoCloseable.close] method id for all auto closeable. + * and predefined as auto closeable via [isPredefinedAutoCloseable], and null otherwise. + * Null always for [BuiltinClassId]. + */ +internal val ClassId.closeMethodIdOrNull: MethodId? + get() = when { + isPredefinedAutoCloseable -> closeMethodId + this is BuiltinClassId -> null + else -> (jClass as? AutoCloseable)?.let { closeMethodId } + } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/context/CgContext.kt new file mode 100644 index 0000000000..7051b84ad4 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/context/CgContext.kt @@ -0,0 +1,699 @@ +package org.utbot.framework.codegen.domain.context + +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.HangingTestsTimeout +import org.utbot.framework.codegen.domain.Import +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.TestFramework +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.PersistentSet +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.persistentSetOf +import org.utbot.common.DynamicProperty +import org.utbot.common.MutableDynamicProperties +import org.utbot.common.mutableDynamicPropertiesOf +import org.utbot.framework.codegen.domain.UtModelWrapper +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.codegen.domain.builtin.TestClassUtilMethodProvider +import org.utbot.framework.codegen.domain.builtin.UtilClassFileMethodProvider +import org.utbot.framework.codegen.domain.models.* +import org.utbot.framework.codegen.services.access.Block +import org.utbot.framework.codegen.tree.EnvironmentFieldStateCache +import org.utbot.framework.codegen.tree.importIfNeeded +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.codegen.services.language.CgLanguageAssistant +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.MockFramework +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isCheckedException +import org.utbot.framework.plugin.api.util.isSubtypeOf +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.utils.UtilMethodProvider + +typealias CgContextProperties = MutableDynamicProperties +typealias CgContextProperty = DynamicProperty + +/** + * Interface for all code generation context aware entities + * + * Most of the properties are 'val'. + * Although, some of the properties are declared as 'var' so that + * they can be reassigned as well as modified + * + * For example, [outerMostTestClass] and [currentExecution] can be reassigned + * when we start generating another method or test class + * + * [existingVariableNames] is a 'var' property + * that can be reverted to its previous value on exit from a name scope + * + * @see [CgContextOwner.withNameScope] + */ +interface CgContextOwner { + // current class under test + val classUnderTest: ClassId + + // If project under test is configured with Spring, we should do some extra analysis + val projectType: ProjectType + + // test class currently being generated (if series of nested classes is generated, it is the outermost one) + val outerMostTestClass: ClassId + + // test class currently being generated (if series of nested classes is generated, it is the innermost one) + var currentTestClass: ClassId + + // provider of util methods used for the test class currently being generated + val utilMethodProvider: UtilMethodProvider + + // current executable under test + // NOTE: may differ from `executableToCall` + var currentExecutableUnderTest: ExecutableId? + + // executable that is called in the current test method body and whose result is used in asserts as `actual` + // NOTE: may differ from `executableUnderTest` + val currentExecutableToCall: ExecutableId? get() = + currentExecution?.stateBefore?.executableToCall ?: currentExecutableUnderTest + + // ClassInfo for the outermost class currently being generated + val outerMostTestClassContext: TestClassContext + + // If generating series of nested classes, it is ClassInfo for the innermost one, + // otherwise it should be equal to outerMostTestClassInfo + val currentTestClassContext: TestClassContext + + // exceptions that can be thrown inside of current method being built + val collectedExceptions: MutableSet + + // annotations required by the current method being built + val collectedMethodAnnotations: MutableSet + + // imports required by the test class being built + val collectedImports: MutableSet + + val importedStaticMethods: MutableSet + val importedClasses: MutableSet + + // util methods required by the test class being built + val requiredUtilMethods: MutableSet + + val utilMethodsUsed: Boolean + + // test methods being generated + val testMethods: MutableList + + // names of methods that already exist in the test class + val existingMethodNames: MutableSet + + // At the start of a test method we save the initial state of some static fields in variables. + // This map is used to restore the initial values of these variables at the end of the test method. + val prevStaticFieldValues: MutableMap + + // names of parameters of methods under test + val paramNames: Map> + + // UtExecution we currently generate a test method for. + // It is null when no test method is being generated at the moment. + var currentExecution: UtExecution? + + val testFramework: TestFramework + + val mockFramework: MockFramework + + val staticsMocking: StaticsMocking + + val forceStaticMocking: ForceStaticMocking + + val generateWarningsForStaticMocking: Boolean + + val codegenLanguage: CodegenLanguage + + val cgLanguageAssistant: CgLanguageAssistant + + val parametrizedTestSource: ParametrizedTestSource + + /** + * Flag indicating whether a mock framework is used in the generated code + * NOTE! This flag is not about whether a mock framework is present + * in the user's project dependencies or not. + * This flag is true if the generated test class contains at least one mock object, + * and false otherwise. See method [withMockFramework]. + */ + var mockFrameworkUsed: Boolean + + // object that represents a set of information about JUnit of selected version + + // Persistent collections are used to conveniently restore their previous state. + // For example, when we exit a block of code we return to the previous name scope. + // At that moment we revert some collections (e.g. variable names) to the previous state. + + // current block of code being built + var currentBlock: PersistentList + + // variable names being used in the current name scope + var existingVariableNames: PersistentSet + + // variables of java.lang.Class type declared in the current name scope + var declaredClassRefs: PersistentMap + + // Variables of either java.lang.reflect.Constructor or java.lang.reflect.Method types + // declared in the current name scope. + // java.lang.reflect.Executable is a superclass of both of these types. + var declaredExecutableRefs: PersistentMap + + // Variables of java.lang.reflect.Field type declared in the current name scope + var declaredFieldRefs: PersistentMap + + // generated this instance for method under test + var thisInstance: CgValue? + + // generated arguments for method under test + val methodArguments: MutableList + + // a variable representing an actual result of the method under test call + var actual: CgVariable + + // a variable representing if test method contains reflective call or not + // and should we catch exceptions like InvocationTargetException or not so on + var containsReflectiveCall: Boolean + + // map from a set of tests for a method to another map + // which connects code generation error message + // with the number of times it occurred + val codeGenerationErrors: MutableMap> + + // package for generated test class + val testClassPackageName: String + + val shouldOptimizeImports: Boolean + + /** + * Used for differentiating models in codegen + * because comparisons by [UtModel] are not enough, + * especially for Spring related variable processing. + * + * Default value is -1, meaning that we are not processing any test set. + * + * @see [CgContext.withTestSetIdScope] + */ + var currentTestSetId: Int + + /** + * Used for differentiating models in codegen + * because comparisons by [UtModel] are not enough, + * especially for Spring related variable processing. + * + * Default value is -1, meaning that we are not processing any execution. + * + * @see [CgContext.withExecutionIdScope] + */ + var currentExecutionId: Int + + // used for comparing stateBefore and result variables -- in case of equality do not create new variable + var valueByUtModelWrapper: MutableMap + + // parameters of the method currently being generated + val currentMethodParameters: MutableMap + + val testClassCustomName: String? + + /** + * Determines whether tests that throw Runtime exceptions should fail or pass. + */ + val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour + + /** + * Timeout for possibly hanging tests (uses info from concrete executor). + */ + val hangingTestsTimeout: HangingTestsTimeout + + /** + * Determines whether tests with timeout should fail (with added Timeout annotation) or be disabled (for our pipeline). + */ + val enableTestsTimeout: Boolean + + var statesCache: EnvironmentFieldStateCache + + /** + * Result models required to create generic execution in parametrized tests. + */ + var successfulExecutionsModels: List + + /** + * Many of [CgContext] properties are only needed in some specific scenarios + * (e.g. Spring-related properties and parametrized test specific properties). + * + * To avoid overly inflating [CgContext] interface such properties should + * be added as dynamic properties. + * + * @see DynamicProperty + */ + // TODO make parameterized test specific properties dynamic + val properties: CgContextProperties + + fun block(init: () -> Unit): Block { + val prevBlock = currentBlock + return try { + val block = persistentListOf() + currentBlock = block + withNameScope { + init() + currentBlock + } + } finally { + currentBlock = prevBlock + } + } + + operator fun CgStatement.unaryPlus() { + currentBlock = currentBlock.add(this) + } + + operator fun CgExecutableCall.unaryPlus(): CgStatementExecutableCall = + CgStatementExecutableCall(this).also { + currentBlock = currentBlock.add(it) + } + + fun updateExecutableUnderTest(executableId: ExecutableId) { + currentExecutableUnderTest = executableId + } + + fun withTestSetIdScope(testSetId: Int, block: () -> R): R { + currentTestSetId = testSetId + return block() + } + + fun withExecutionIdScope(executionId: Int, block: () -> R): R { + currentExecutionId = executionId + return block() + } + + fun addExceptionIfNeeded(exception: ClassId) + fun runWithoutCollectingExceptions(block: () -> T): T + + fun createGetClassExpression(id: ClassId, codegenLanguage: CodegenLanguage): CgGetClass = + when (codegenLanguage) { + CodegenLanguage.JAVA -> CgGetJavaClass(id) + CodegenLanguage.KOTLIN -> CgGetKotlinClass(id) + }.also { + importIfNeeded(id) + } + + /** + * This method sets up context for a new test class file generation and executes the given [block]. + * Afterwards, context is set back to the initial state. + */ + fun withTestClassFileScope(block: () -> R): R + + /** + * This method sets up context for a new test class generation and executes the given [block]. + * Afterwards, context is set back to the initial state. + */ + fun withTestClassScope(block: () -> R): R + + /** + * This method does almost all the same as [withTestClassScope], but for nested test classes. + * The difference is that instead of working with [outerMostTestClassContext] it works with [currentTestClassContext]. + */ + fun withNestedClassScope(testClassModel: SimpleTestClassModel, block: () -> R): R + + /** + * Set [mockFrameworkUsed] flag to true if the block is successfully executed + */ + fun withMockFramework(block: () -> R): R { + val result = block() + mockFrameworkUsed = true + return result + } + + fun rememberVariableForModel(variable: CgVariable, model: UtModel? = null) { + model?.let { + valueByUtModelWrapper[it.wrap()] = variable + } + } + + fun withNameScope(block: () -> R): R { + val prevVariableNames = existingVariableNames + val prevDeclaredClassRefs = declaredClassRefs + val prevDeclaredExecutableRefs = declaredExecutableRefs + val prevDeclaredFieldRefs = declaredFieldRefs + val prevValueByCgModel = valueByUtModelWrapper.toMutableMap() + return try { + block() + } finally { + existingVariableNames = prevVariableNames + declaredClassRefs = prevDeclaredClassRefs + declaredExecutableRefs = prevDeclaredExecutableRefs + declaredFieldRefs = prevDeclaredFieldRefs + valueByUtModelWrapper = prevValueByCgModel + } + } + + /** + * [ClassId] of a class that contains util methods. + * For example, it can be the current test class, or it can be a generated separate `UtUtils` class. + */ + val utilsClassId: ClassId + get() = utilMethodProvider.utilClassId + + /** + * Checks is it our util reflection field getter method. + * When this method is used with type cast in Kotlin, this type cast have to be safety + */ + val MethodId.isGetFieldUtilMethod: Boolean + get() = this == utilMethodProvider.getFieldValueMethodId + || this == utilMethodProvider.getStaticFieldValueMethodId + + val testClassThisInstance: CgThisInstance + + // util methods and auxiliary classes of current test class + + val capturedArgumentClass: ClassId + get() = utilMethodProvider.capturedArgumentClassId + + val getUnsafeInstance: MethodId + get() = utilMethodProvider.getUnsafeInstanceMethodId + + val createInstance: MethodId + get() = utilMethodProvider.createInstanceMethodId + + val createArray: MethodId + get() = utilMethodProvider.createArrayMethodId + + val setField: MethodId + get() = utilMethodProvider.setFieldMethodId + + val setStaticField: MethodId + get() = utilMethodProvider.setStaticFieldMethodId + + val getFieldValue: MethodId + get() = utilMethodProvider.getFieldValueMethodId + + val getStaticFieldValue: MethodId + get() = utilMethodProvider.getStaticFieldValueMethodId + + val getEnumConstantByName: MethodId + get() = utilMethodProvider.getEnumConstantByNameMethodId + + val deepEquals: MethodId + get() = utilMethodProvider.deepEqualsMethodId + + val arraysDeepEquals: MethodId + get() = utilMethodProvider.arraysDeepEqualsMethodId + + val iterablesDeepEquals: MethodId + get() = utilMethodProvider.iterablesDeepEqualsMethodId + + val streamsDeepEquals: MethodId + get() = utilMethodProvider.streamsDeepEqualsMethodId + + val mapsDeepEquals: MethodId + get() = utilMethodProvider.mapsDeepEqualsMethodId + + val hasCustomEquals: MethodId + get() = utilMethodProvider.hasCustomEqualsMethodId + + val getArrayLength: MethodId + get() = utilMethodProvider.getArrayLengthMethodId + + val consumeBaseStream: MethodId + get() = utilMethodProvider.consumeBaseStreamMethodId + + val buildStaticLambda: MethodId + get() = utilMethodProvider.buildStaticLambdaMethodId + + val buildLambda: MethodId + get() = utilMethodProvider.buildLambdaMethodId + + val getLookupIn: MethodId + get() = utilMethodProvider.getLookupInMethodId + + val getSingleAbstractMethod: MethodId + get() = utilMethodProvider.getSingleAbstractMethodMethodId + + val getLambdaCapturedArgumentTypes: MethodId + get() = utilMethodProvider.getLambdaCapturedArgumentTypesMethodId + + val getLambdaCapturedArgumentValues: MethodId + get() = utilMethodProvider.getLambdaCapturedArgumentValuesMethodId + + val getInstantiatedMethodType: MethodId + get() = utilMethodProvider.getInstantiatedMethodTypeMethodId + + val getLambdaMethod: MethodId + get() = utilMethodProvider.getLambdaMethodMethodId + + fun UtModel.wrap(): UtModelWrapper = + UtModelWrapper( + testSetId = currentTestSetId, + executionId = currentExecutionId, + model = this, + ) +} + +/** + * Context with current code generation info + */ +class CgContext( + override val classUnderTest: ClassId, + override val projectType: ProjectType, + val generateUtilClassFile: Boolean = false, + override var currentExecutableUnderTest: ExecutableId? = null, + override val collectedExceptions: MutableSet = mutableSetOf(), + override val collectedMethodAnnotations: MutableSet = mutableSetOf(), + override val collectedImports: MutableSet = mutableSetOf(), + override val importedStaticMethods: MutableSet = mutableSetOf(), + override val importedClasses: MutableSet = mutableSetOf(), + override val requiredUtilMethods: MutableSet = mutableSetOf(), + override val testMethods: MutableList = mutableListOf(), + override val existingMethodNames: MutableSet = mutableSetOf(), + override val prevStaticFieldValues: MutableMap = mutableMapOf(), + override val paramNames: Map>, + override var currentExecution: UtExecution? = null, + override val testFramework: TestFramework, + override val mockFramework: MockFramework, + override val staticsMocking: StaticsMocking, + override val forceStaticMocking: ForceStaticMocking, + override val generateWarningsForStaticMocking: Boolean, + override val codegenLanguage: CodegenLanguage = CodegenLanguage.defaultItem, + override val cgLanguageAssistant: CgLanguageAssistant, + override val parametrizedTestSource: ParametrizedTestSource = ParametrizedTestSource.DO_NOT_PARAMETRIZE, + override var mockFrameworkUsed: Boolean = false, + override var currentBlock: PersistentList = persistentListOf(), + override var existingVariableNames: PersistentSet = persistentSetOf(), + override var declaredClassRefs: PersistentMap = persistentMapOf(), + override var declaredExecutableRefs: PersistentMap = persistentMapOf(), + override var declaredFieldRefs: PersistentMap = persistentMapOf(), + override var thisInstance: CgValue? = null, + override val methodArguments: MutableList = mutableListOf(), + override val codeGenerationErrors: MutableMap> = mutableMapOf(), + override val testClassPackageName: String = classUnderTest.packageName, + override var shouldOptimizeImports: Boolean = false, + override var testClassCustomName: String? = null, + override val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = + RuntimeExceptionTestsBehaviour.defaultItem, + override val hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(), + override val enableTestsTimeout: Boolean = true, + override var containsReflectiveCall: Boolean = false, + override val properties: CgContextProperties = mutableDynamicPropertiesOf(), +) : CgContextOwner { + override lateinit var statesCache: EnvironmentFieldStateCache + override lateinit var actual: CgVariable + override lateinit var successfulExecutionsModels: List + + /** + * This property cannot be accessed outside of test class file scope + * (i.e. outside of [CgContextOwner.withTestClassFileScope]). + */ + override val outerMostTestClassContext: TestClassContext + get() = _outerMostTestClassContext ?: error("Accessing outerMostTestClassInfo out of class file scope") + + private var _outerMostTestClassContext: TestClassContext? = cgLanguageAssistant.outerMostTestClassContent + + /** + * This property cannot be accessed outside of test class scope + * (i.e. outside of [CgContextOwner.withTestClassScope]). + */ + override val currentTestClassContext: TestClassContext + get() = _currentTestClassContext ?: error("Accessing currentTestClassInfo out of class scope") + + private var _currentTestClassContext: TestClassContext? = null + + override val outerMostTestClass: ClassId by lazy { + val (name, simpleName) = cgLanguageAssistant.testClassName( + testClassCustomName, testClassPackageName, classUnderTest + ) + BuiltinClassId( + canonicalName = name, + simpleName = simpleName, + isFinal = true, + ) + } + + /** + * Determine where the util methods will come from. + * If we don't want to use a separately generated util class, + * util methods will be generated directly in the test class (see [TestClassUtilMethodProvider]). + * Otherwise, an util class will be generated separately and we will use util methods from it (see [UtilClassFileMethodProvider]). + */ + override val utilMethodProvider: UtilMethodProvider + get() = if (generateUtilClassFile) { + UtilClassFileMethodProvider(codegenLanguage) + } else { + TestClassUtilMethodProvider(outerMostTestClass) + } + + override lateinit var currentTestClass: ClassId + + override fun withTestClassFileScope(block: () -> R): R { + clearClassScope() + _outerMostTestClassContext = TestClassContext() + return try { + block() + } finally { + clearClassScope() + } + } + + override fun withTestClassScope(block: () -> R): R { + _currentTestClassContext = outerMostTestClassContext + currentTestClass = outerMostTestClass + return try { + block() + } finally { + _currentTestClassContext = null + } + } + + override fun withNestedClassScope(testClassModel: SimpleTestClassModel, block: () -> R): R { + val previousCurrentTestClassInfo = currentTestClassContext + val previousCurrentTestClass = currentTestClass + currentTestClass = createClassIdForNestedClass(testClassModel) + _currentTestClassContext = TestClassContext() + return try { + block() + } finally { + _currentTestClassContext = previousCurrentTestClassInfo + currentTestClass = previousCurrentTestClass + } + } + + private fun createClassIdForNestedClass(testClassModel: SimpleTestClassModel): ClassId { + val simpleName = "${testClassModel.classUnderTest.simpleName}Test" + return BuiltinClassId( + canonicalName = currentTestClass.canonicalName + "." + simpleName, + simpleName = simpleName, + isFinal = true, + ) + } + + private fun clearClassScope() { + _outerMostTestClassContext = null + collectedImports.clear() + importedStaticMethods.clear() + importedClasses.clear() + testMethods.clear() + requiredUtilMethods.clear() + valueByUtModelWrapper.clear() + mockFrameworkUsed = false + } + + // number of times collection of exceptions was suspended + private var exceptionCollectionSuspensionDepth = 0 + + override fun runWithoutCollectingExceptions(block: () -> T): T { + exceptionCollectionSuspensionDepth++ + return try { + block() + } finally { + exceptionCollectionSuspensionDepth-- + } + } + + override fun addExceptionIfNeeded(exception: ClassId) { + if (exceptionCollectionSuspensionDepth > 0) return + if (exception !is BuiltinClassId) { + require(exception isSubtypeOf Throwable::class.id) { + "Class $exception which is not a Throwable was passed" + } + + val isUnchecked = !exception.jClass.isCheckedException + val alreadyAdded = + collectedExceptions.any { existingException -> exception isSubtypeOf existingException } + + if (isUnchecked || alreadyAdded) return + + collectedExceptions + .removeIf { existingException -> existingException isSubtypeOf exception } + } + + if (collectedExceptions.add(exception)) { + importIfNeeded(exception) + } + } + + override var currentTestSetId: Int = -1 + + override var currentExecutionId: Int = -1 + + override var valueByUtModelWrapper: MutableMap = mutableMapOf() + + override val currentMethodParameters: MutableMap = mutableMapOf() + + override val testClassThisInstance: CgThisInstance = CgThisInstance(outerMostTestClass) + + override val utilMethodsUsed: Boolean + get() = requiredUtilMethods.isNotEmpty() + + fun customCopy(shouldOptimizeImports: Boolean, testClassCustomName: String?) = CgContext( + shouldOptimizeImports = shouldOptimizeImports, + testClassCustomName = testClassCustomName, + + classUnderTest = this.classUnderTest, + projectType = this.projectType, + generateUtilClassFile = this.generateUtilClassFile, + currentExecutableUnderTest = this.currentExecutableUnderTest, + collectedExceptions =this.collectedExceptions, + collectedMethodAnnotations = this.collectedMethodAnnotations, + collectedImports = this.collectedImports, + importedStaticMethods = this.importedStaticMethods, + importedClasses = this.importedClasses, + requiredUtilMethods = this.requiredUtilMethods, + testMethods = this.testMethods, + existingMethodNames = this.existingMethodNames, + prevStaticFieldValues = this.prevStaticFieldValues, + paramNames = this.paramNames, + currentExecution = this.currentExecution, + testFramework = this.testFramework, + mockFramework = this.mockFramework, + staticsMocking = this.staticsMocking, + forceStaticMocking = this.forceStaticMocking, + generateWarningsForStaticMocking = this.generateWarningsForStaticMocking, + codegenLanguage = this.codegenLanguage, + cgLanguageAssistant = this.cgLanguageAssistant, + parametrizedTestSource = this.parametrizedTestSource, + mockFrameworkUsed = this.mockFrameworkUsed, + currentBlock = this.currentBlock, + existingVariableNames = this.existingVariableNames, + declaredClassRefs = this.declaredClassRefs, + declaredExecutableRefs = this.declaredExecutableRefs, + declaredFieldRefs = this.declaredFieldRefs, + thisInstance = this.thisInstance, + methodArguments = this.methodArguments, + codeGenerationErrors = this.codeGenerationErrors, + testClassPackageName = this.testClassPackageName, + runtimeExceptionTestsBehaviour = this.runtimeExceptionTestsBehaviour, + hangingTestsTimeout = this.hangingTestsTimeout, + enableTestsTimeout = this.enableTestsTimeout, + containsReflectiveCall = this.containsReflectiveCall, + properties = this.properties, + ) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/context/TestClassContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/context/TestClassContext.kt new file mode 100644 index 0000000000..00f223d102 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/context/TestClassContext.kt @@ -0,0 +1,39 @@ +package org.utbot.framework.codegen.domain.context + +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgAnnotation +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.codegen.domain.models.CgClass + +/** + * This class stores context information needed to build [CgClass]. + * Should only be used in [CgContextOwner]. + */ +data class TestClassContext( + // set of interfaces that the test class must inherit + val collectedTestClassInterfaces: MutableSet = mutableSetOf(), + + // set of annotations of the test class + val collectedTestClassAnnotations: MutableSet = mutableSetOf(), + + // list of data provider methods that test class must implement + val cgDataProviderMethods: MutableList = mutableListOf(), +) { + // test class superclass (if needed) + var testClassSuperclass: ClassId? = null + set(value) { + // Assigning a value to the testClassSuperclass when it is already non-null + // means that we need the test class to have more than one superclass + // which is impossible in Java and Kotlin. + require(value == null || field == null) { "It is impossible for the test class to have more than one superclass" } + field = value + } + + fun clear() { + collectedTestClassAnnotations.clear() + collectedTestClassInterfaces.clear() + cgDataProviderMethods.clear() + testClassSuperclass = null + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgElement.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgElement.kt new file mode 100644 index 0000000000..4d3ee7c675 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgElement.kt @@ -0,0 +1,1038 @@ +package org.utbot.framework.codegen.domain.models + +import org.utbot.common.WorkaroundReason +import org.utbot.common.workaround +import org.utbot.framework.codegen.domain.Import +import org.utbot.framework.codegen.renderer.CgRendererContext +import org.utbot.framework.codegen.renderer.CgVisitor +import org.utbot.framework.codegen.renderer.auxiliaryClassTextById +import org.utbot.framework.codegen.renderer.utilMethodTextById +import org.utbot.framework.codegen.tree.VisibilityModifier +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.CgClassId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.DocClassLinkStmt +import org.utbot.framework.plugin.api.DocCodeStmt +import org.utbot.framework.plugin.api.DocCustomTagStatement +import org.utbot.framework.plugin.api.DocMethodLinkStmt +import org.utbot.framework.plugin.api.DocPreTagStatement +import org.utbot.framework.plugin.api.DocRegularLineStmt +import org.utbot.framework.plugin.api.DocRegularStmt +import org.utbot.framework.plugin.api.DocStatement +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.TypeParameters +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.objectArrayClassId +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.voidClassId + +interface CgElement { + // TODO: order of cases is important here due to inheritance between some of the element types + fun accept(visitor: CgVisitor): R = visitor.run { + when (val element = this@CgElement) { + is CgClassFile -> visit(element) + is CgClass -> visit(element) + is CgClassBody -> visit(element) + is CgStaticsRegion -> visit(element) + is CgNestedClassesRegion<*> -> visit(element) + is CgSimpleRegion<*> -> visit(element) + is CgTestMethodCluster -> visit(element) + is CgMethodsCluster -> visit(element) + is CgAuxiliaryClass -> visit(element) + is CgUtilMethod -> visit(element) + is CgFrameworkUtilMethod -> visit(element) + is CgTestMethod -> visit(element) + is CgErrorTestMethod -> visit(element) + is CgParameterizedTestDataProviderMethod -> visit(element) + is CgCommentedAnnotation -> visit(element) + is CgSingleArgAnnotation -> visit(element) + is CgMultipleArgsAnnotation -> visit(element) + is CgArrayAnnotationArgument -> visit(element) + is CgNamedAnnotationArgument -> visit(element) + is CgSingleLineComment -> visit(element) + is CgTripleSlashMultilineComment -> visit(element) + is CgMultilineComment -> visit(element) + is CgDocumentationComment -> visit(element) + is CgDocPreTagStatement -> visit(element) + is CgCustomTagStatement -> visit(element) + is CgDocCodeStmt -> visit(element) + is CgDocRegularStmt -> visit(element) + is CgDocRegularLineStmt -> visit(element) + is CgDocClassLinkStmt -> visit(element) + is CgDocMethodLinkStmt -> visit(element) + is CgAnonymousFunction -> visit(element) + is CgReturnStatement -> visit(element) + is CgArrayElementAccess -> visit(element) + is CgSpread -> visit(element) + is CgLessThan -> visit(element) + is CgGreaterThan -> visit(element) + is CgEqualTo -> visit(element) + is CgIncrement -> visit(element) + is CgDecrement -> visit(element) + is CgTryCatch -> visit(element) + is CgInnerBlock -> visit(element) + is CgForLoop -> visit(element) + is CgForEachLoop -> visit(element) + is CgWhileLoop -> visit(element) + is CgDoWhileLoop -> visit(element) + is CgBreakStatement -> visit(element) + is CgContinueStatement -> visit(element) + is CgDeclaration -> visit(element) + is CgFieldDeclaration -> visit(element) + is CgAssignment -> visit(element) + is CgTypeCast -> visit(element) + is CgIsInstance -> visit(element) + is CgThisInstance -> visit(element) + is CgNotNullAssertion -> visit(element) + is CgVariable -> visit(element) + is CgParameterDeclaration -> visit(element) + is CgFormattedString -> visit(element) + is CgLiteral -> visit(element) + is CgNonStaticRunnable -> visit(element) + is CgStaticRunnable -> visit(element) + is CgAllocateInitializedArray -> visit(element) + is CgArrayInitializer -> visit(element) + is CgAllocateArray -> visit(element) + is CgEnumConstantAccess -> visit(element) + is CgFieldAccess -> visit(element) + is CgStaticFieldAccess -> visit(element) + is CgIfStatement -> visit(element) + is CgSwitchCaseLabel -> visit(element) + is CgSwitchCase -> visit(element) + is CgLogicalAnd -> visit(element) + is CgLogicalOr -> visit(element) + is CgGetLength -> visit(element) + is CgGetJavaClass -> visit(element) + is CgGetKotlinClass -> visit(element) + is CgStatementExecutableCall -> visit(element) + is CgConstructorCall -> visit(element) + is CgMethodCall -> visit(element) + is CgThrowStatement -> visit(element) + is CgErrorWrapper -> visit(element) + is CgEmptyLine -> visit(element) + else -> throw IllegalArgumentException("Can not visit element of type ${element::class}") + } + } +} + +// Code entities + +open class CgClassFile( + open val imports: List, + open val declaredClass: CgClass, +): CgElement + +class CgClass( + val id: ClassId, + val documentation: CgDocumentationComment?, + val annotations: List, + val superclass: ClassId?, + val interfaces: List, + val body: CgClassBody, + val isStatic: Boolean, + val isNested: Boolean, + val visibility: VisibilityModifier = VisibilityModifier.PUBLIC, +): CgElement { + val packageName + get() = id.packageName + + val simpleName + get() = id.simpleName +} + +/** + * Body of a class. + * @property methodRegions regions containing methods + * @property staticDeclarationRegions regions containing static declarations. + * This is usually util methods and data providers. + * In Kotlin all static declarations must be grouped together in a companion object. + * In Java there is no such restriction, but for uniformity we are grouping + * Java static declarations together as well. It can also improve code readability. + */ +class CgClassBody( + val classId: ClassId, + val methodRegions: List, + val staticDeclarationRegions: List, + val nestedClassRegions: List>, + val fields: Set = emptySet(), +) : CgElement + +/** + * Field of a class. + * @property ownerClassId [ClassId] of the field owner class. + * @property declaration declaration itself. + * @property annotation optional annotation. + * @property visibility field visibility. + */ +class CgFieldDeclaration( + val ownerClassId: ClassId, + val declaration: CgDeclaration, + val annotation: CgAnnotation? = null, + val visibility: VisibilityModifier = VisibilityModifier.PRIVATE, +) : CgElement + +/** + * A class representing the IntelliJ IDEA's regions. + * A region is a part of code between the special starting and ending comments. + * + * @property header The header of the region, + * no ///region and ///endregion comments are generated if [header] is `null` + */ +sealed class CgRegion : CgElement { + abstract val header: String? + abstract val content: List +} + +open class CgSimpleRegion( + override val header: String?, + override val content: List +) : CgRegion() + +/** + * A region that stores some static declarations, e.g. data providers or util methods. + * There may be more than one static region in a class and they all are stored + * in a [CgClassBody.staticDeclarationRegions]. + * In case of Kotlin, they all will be rendered inside of a companion object. + */ +class CgStaticsRegion( + override val header: String?, + override val content: List +) : CgSimpleRegion(header, content) + +/** + * A region that stores all nested classes + */ +abstract class CgNestedClassesRegion( + override val header: String?, + override val content: List, +): CgRegion() + +class CgRealNestedClassesRegion(header: String? = null, nestedClasses: List): + CgNestedClassesRegion(header, nestedClasses) + +/** + * Regions with nested classes that are represented with [CgAuxiliaryClass], not [CgClass]. + */ +class CgAuxiliaryNestedClassesRegion(header: String? = null, nestedClasses: List): + CgNestedClassesRegion(header, nestedClasses) + +data class CgTestMethodCluster( + override val header: String?, + val description: CgTripleSlashMultilineComment?, + override val content: List +) : CgRegion() + +/** + * Stores all methods as one cluster + * (for e.g. in test class we can store all tests for one executable in such cluster) + */ +data class CgMethodsCluster( + override val header: String?, + override val content: List> +) : CgRegion>() { + companion object { + fun withoutDocs(methodsList: List) = CgMethodsCluster( + header = null, + content = listOf( + CgSimpleRegion( + header = null, + content = methodsList + ) + ) + ) + } +} + +/** + * Util entity is either an instance of [CgAuxiliaryClass] or [CgUtilMethod]. + * Util methods are the helper methods that we use in our generated tests, + * and auxiliary classes are the classes that util methods use. + */ +sealed class CgUtilEntity : CgElement { + internal abstract fun getText(rendererContext: CgRendererContext): String +} + +/** + * This class represents classes that are required by our util methods. + * For example, class `CapturedArgument` that is used by util methods that construct lambda values. + * + * **Note** that we call such classes **auxiliary** instead of **util** in order to avoid confusion + * with class `org.utbot.runtime.utils.UtUtils`, which is generally called an **util class**. + * `UtUtils` is a class that contains all our util methods and **auxiliary classes**. + */ +data class CgAuxiliaryClass(val id: ClassId) : CgUtilEntity() { + override fun getText(rendererContext: CgRendererContext): String { + return rendererContext.utilMethodProvider + .auxiliaryClassTextById(id, rendererContext.codegenLanguage) + } +} + +/** + * This class does not inherit from [CgMethod], because it only needs an [id], + * and it does not need to have info about all the other properties of [CgMethod]. + * This is because util methods are hardcoded. On the rendering stage their text + * is retrieved by their [MethodId]. + * + * @property id identifier of the util method. + */ +data class CgUtilMethod(val id: MethodId) : CgUtilEntity() { + override fun getText(rendererContext: CgRendererContext): String { + return with(rendererContext) { + rendererContext.utilMethodProvider + .utilMethodTextById(id, mockFrameworkUsed, mockFramework, codegenLanguage) + } + } +} + +// Methods + +sealed class CgMethod(open val isStatic: Boolean) : CgElement { + abstract val name: String + abstract val returnType: ClassId + abstract val parameters: List + abstract val statements: List + abstract val exceptions: Set + abstract val annotations: List + abstract val documentation: CgDocumentationComment + abstract val requiredFields: List + abstract val visibility: VisibilityModifier +} + +class CgTestMethod( + override val name: String, + override val returnType: ClassId = voidClassId, + override val parameters: List = emptyList(), + override val statements: List, + override val exceptions: Set = emptySet(), + override val annotations: List, + override val visibility: VisibilityModifier = VisibilityModifier.PUBLIC, + val type: CgTestMethodType, + override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList()), + override val requiredFields: List = emptyList(), +) : CgMethod(isStatic = false) + +class CgFrameworkUtilMethod( + override val name: String, + override val statements: List, + override val exceptions: Set, + override val annotations: List, + override val visibility: VisibilityModifier = VisibilityModifier.PUBLIC, +) : CgMethod(isStatic = false) { + override val returnType: ClassId = voidClassId + override val parameters: List = emptyList() + override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList()) + override val requiredFields: List = emptyList() +} + +class CgErrorTestMethod( + override val name: String, + override val statements: List, + override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList()), + override val visibility: VisibilityModifier = VisibilityModifier.PUBLIC, +) : CgMethod(isStatic = false) { + override val exceptions: Set = emptySet() + override val returnType: ClassId = voidClassId + override val parameters: List = emptyList() + override val annotations: List = emptyList() + override val requiredFields: List = emptyList() +} + +class CgParameterizedTestDataProviderMethod( + override val name: String, + override val statements: List, + override val returnType: ClassId, + override val annotations: List, + override val exceptions: Set, + override val visibility: VisibilityModifier = VisibilityModifier.PUBLIC, +) : CgMethod(isStatic = true) { + override val parameters: List = emptyList() + override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList()) + override val requiredFields: List = emptyList() +} + +enum class CgTestMethodType(val displayName: String, val isThrowing: Boolean) { + SUCCESSFUL(displayName = "Successful tests without exceptions", isThrowing = false), + PASSED_EXCEPTION(displayName = "Thrown exceptions marked as passed", isThrowing = true), + FAILING(displayName = "Failing tests (with exceptions)", isThrowing = true), + TIMEOUT(displayName = "Failing tests (with timeout)", isThrowing = true), + ARTIFICIAL(displayName = "Failing tests (with custom exception)", isThrowing = true), + CRASH(displayName = "Possibly crashing tests", isThrowing = true), + PARAMETRIZED(displayName = "Parametrized tests", isThrowing = false); + + override fun toString(): String = displayName +} + +// Annotations + +enum class AnnotationTarget { + Class, + + Method, + + Field, +} + +abstract class CgAnnotation : CgElement { + abstract val classId: ClassId + abstract val target: AnnotationTarget +} + +/** + * NOTE: use `StatementConstructor.addAnnotation` + * instead of explicit constructor call. + */ +class CgCommentedAnnotation(val annotation: CgAnnotation) : CgAnnotation() { + override val classId: ClassId = annotation.classId + override val target: AnnotationTarget = annotation.target +} + +/** + * NOTE: use `StatementConstructor.addAnnotation` + * instead of explicit constructor call. + */ +class CgSingleArgAnnotation( + override val classId: ClassId, + val argument: CgExpression, + override val target: AnnotationTarget, +) : CgAnnotation() + +/** + * NOTE: use `StatementConstructor.addAnnotation` + * instead of explicit constructor call. + */ +data class CgMultipleArgsAnnotation( + override val classId: ClassId, + val arguments: MutableList, + override val target: AnnotationTarget, +) : CgAnnotation() + +data class CgArrayAnnotationArgument( + val values: List +) : CgExpression { + override val type: ClassId = objectArrayClassId // TODO: is this type correct? +} + +class CgNamedAnnotationArgument( + val name: String, + val value: CgExpression +) : CgElement + +// Statements + +interface CgStatement : CgElement + +// Comments + +sealed class CgComment : CgStatement + +data class CgSingleLineComment(val comment: String) : CgComment() + +/** + * A comment that consists of multiple lines. + * The appearance of such comment may vary depending + * on the [CgAbstractMultilineComment] inheritor being used. + * Each inheritor is rendered differently. + */ +sealed class CgAbstractMultilineComment : CgComment() { + abstract val lines: List +} + +/** + * Multiline comment where each line starts with /// + */ +data class CgTripleSlashMultilineComment(override val lines: List) : CgAbstractMultilineComment() + +/** + * Classic Java multiline comment starting with /* and ending with */ + */ +data class CgMultilineComment(override val lines: List) : CgAbstractMultilineComment() { + constructor(line: String) : this(listOf(line)) +} + +//class CgDocumentationComment(val lines: List) : CgComment { +// constructor(text: String?) : this(text?.split("\n") ?: listOf()) +//} +data class CgDocumentationComment(val lines: List) : CgComment() { + constructor(text: String?) : this(text?.split("\n")?.map { CgDocRegularStmt(it) }?.toList() ?: listOf()) + + override fun equals(other: Any?): Boolean = + if (other is CgDocumentationComment) this.hashCode() == other.hashCode() else false + + override fun hashCode(): Int = lines.hashCode() +} + +sealed class CgDocStatement : CgStatement { //todo is it really CgStatement or maybe something else? + abstract fun isEmpty(): Boolean +} + +sealed class CgDocTagStatement : CgDocStatement() { + abstract val content: List + + override fun isEmpty(): Boolean = content.all { it.isEmpty() } +} + +data class CgDocPreTagStatement(override val content: List) : CgDocTagStatement() + +/** + * Represents a type for statements containing custom JavaDoc tags. + */ +data class CgCustomTagStatement(override val content: List) : CgDocTagStatement() + +data class CgDocCodeStmt(val stmt: String) : CgDocStatement() { + override fun isEmpty(): Boolean = stmt.isEmpty() +} + +data class CgDocRegularStmt(val stmt: String) : CgDocStatement() { + override fun isEmpty(): Boolean = stmt.isEmpty() +} + +/** + * Represents an element of a whole line of a multiline comment. + */ +data class CgDocRegularLineStmt(val stmt: String) : CgDocStatement() { + override fun isEmpty(): Boolean = stmt.isEmpty() +} + +open class CgDocClassLinkStmt(val className: String) : CgDocStatement() { + override fun isEmpty(): Boolean = className.isEmpty() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CgDocClassLinkStmt + + if (className != other.className) return false + + return true + } + + override fun hashCode(): Int { + return className.hashCode() + } +} + +data class CgDocMethodLinkStmt(val methodName: String, val stmt: String) : CgDocClassLinkStmt(stmt) + +fun convertDocToCg(stmt: DocStatement): CgDocStatement { + return when (stmt) { + is DocPreTagStatement -> { + val stmts = stmt.content.map { convertDocToCg(it) } + CgDocPreTagStatement(content = stmts) + } + is DocCustomTagStatement -> { + val stmts = stmt.content.map { convertDocToCg(it) } + CgCustomTagStatement(content = stmts) + } + is DocRegularStmt -> CgDocRegularStmt(stmt = stmt.stmt) + is DocRegularLineStmt -> CgDocRegularLineStmt(stmt = stmt.stmt) + is DocClassLinkStmt -> CgDocClassLinkStmt(className = stmt.className) + is DocMethodLinkStmt -> CgDocMethodLinkStmt(methodName = stmt.methodName, stmt = stmt.className) + is DocCodeStmt -> CgDocCodeStmt(stmt = stmt.stmt) + } +} + +// Anonymous function (lambda) + +data class CgAnonymousFunction( + override val type: ClassId, + val parameters: List, + val body: List +) : CgExpression + +// Return statement + +data class CgReturnStatement(val expression: CgExpression) : CgStatement + +// Array element access + +// TODO: check nested array element access expressions e.g. a[0][1][2] +// TODO in general it is not CgReferenceExpression because array element can have a primitive type +data class CgArrayElementAccess(val array: CgExpression, val index: CgExpression) : CgReferenceExpression { + override val type: ClassId = array.type.elementClassId ?: objectClassId +} + +// Loop conditions +sealed class CgComparison : CgExpression { + abstract val left: CgExpression + abstract val right: CgExpression + + override val type: ClassId = booleanClassId +} + +data class CgLessThan( + override val left: CgExpression, + override val right: CgExpression +) : CgComparison() + +data class CgGreaterThan( + override val left: CgExpression, + override val right: CgExpression +) : CgComparison() + +data class CgEqualTo( + override val left: CgExpression, + override val right: CgExpression +) : CgComparison() + +// Increment and decrement + +data class CgIncrement(val variable: CgVariable) : CgStatement + +data class CgDecrement(val variable: CgVariable) : CgStatement + +// Inner block in method (keeps parent method fields visible) + +data class CgInnerBlock(val statements: List) : CgStatement + +// Try-catch + +// for now finally clause is not supported +data class CgTryCatch( + val statements: List, + val handlers: List, + val finally: List?, + val resources: List? = null +) : CgStatement + +// ?: error("") + +data class CgErrorWrapper( + val message: String, + val expression: CgExpression, +) : CgExpression { + override val type: ClassId + get() = expression.type +} + +// Loops + +sealed class CgLoop : CgStatement { + abstract val condition: CgExpression // TODO: how to represent conditions + abstract val statements: List +} + +data class CgForLoop( + val initialization: CgDeclaration, + override val condition: CgExpression, + val update: CgStatement, + override val statements: List +) : CgLoop() + +data class CgWhileLoop( + override val condition: CgExpression, + override val statements: List +) : CgLoop() + +data class CgDoWhileLoop( + override val condition: CgExpression, + override val statements: List +) : CgLoop() + +/** + * @property condition represents variable in foreach loop + * @property iterable represents iterable in foreach loop + * @property statements represents statements in foreach loop + */ +data class CgForEachLoop( + override val condition: CgExpression, + override val statements: List, + val iterable: CgReferenceExpression, +) : CgLoop() + +// Control statements + +object CgBreakStatement : CgStatement +object CgContinueStatement : CgStatement + +// Variable declaration + +class CgDeclaration( + val variableType: ClassId, + val variableName: String, + val initializer: CgExpression?, + val isMutable: Boolean = false, +) : CgStatement { + val variable: CgVariable + get() = CgVariable(variableName, variableType) +} + +// Variable assignment + +data class CgAssignment( + val lValue: CgExpression, + val rValue: CgExpression +) : CgStatement + +// Expressions + +interface CgExpression : CgStatement { + val type: ClassId +} + +// marker interface representing expressions returning reference +// TODO: it seems that not all [CgValue] implementations are reference expressions +interface CgReferenceExpression : CgExpression + +/** + * Type cast model + * + * @property isSafetyCast shows if we should use "as?" instead of "as" in Kotlin code + */ +data class CgTypeCast( + val targetType: ClassId, + val expression: CgExpression, + val isSafetyCast: Boolean = false, +) : CgExpression { + override val type: ClassId = targetType +} + +/** + * Represents [java.lang.Class.isInstance] method. + */ +data class CgIsInstance( + val classExpression: CgExpression, + val value: CgExpression, +): CgExpression { + override val type: ClassId = booleanClassId +} + +// Value + +// TODO in general CgLiteral is not CgReferenceExpression because it can hold primitive values +interface CgValue : CgReferenceExpression + +// This instance + +data class CgThisInstance(override val type: ClassId) : CgValue + +// Variables + +open class CgVariable( + val name: String, + override val type: ClassId, +) : CgValue { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CgVariable + + if (name != other.name) return false + if (type != other.type) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + type.hashCode() + return result + } + + override fun toString(): String { + return "${type.simpleName} $name" + } +} + +/** + * If expression is a variable, then this is a variable + * with explicit not null annotation if this is required in language. + * + * In Kotlin the difference is in addition of "!!" to the expression + */ +data class CgNotNullAssertion(val expression: CgExpression) : CgValue { + override val type: ClassId + get() = when (val expressionType = expression.type) { + is BuiltinClassId -> BuiltinClassId( + canonicalName = expressionType.canonicalName, + simpleName = expressionType.simpleName, + isNullable = false, + ) + else -> ClassId( + expressionType.name, + expressionType.elementClassId, + isNullable = false, + ) + } +} + +/** + * Method parameters declaration + * + * @property isReferenceType is used for rendering nullable types in Kotlin codegen. + */ +data class CgParameterDeclaration( + val parameter: CgVariable, + val isVararg: Boolean = false, + val isReferenceType: Boolean = false +) : CgElement { + constructor(name: String, type: ClassId, isReferenceType: Boolean = false) : this( + CgVariable(name, type), + isReferenceType = isReferenceType + ) + + val name: String + get() = parameter.name + + val type: ClassId + get() = parameter.type +} + +/** + * Test method parameter can be one of the following types: + * - argument of MUT with a certain index + * - result expected from MUT with the given arguments + * - exception expected from MUT with the given arguments + */ +sealed class CgParameterKind { + object ThisInstance : CgParameterKind() + data class Argument(val index: Int) : CgParameterKind() + data class Statics(val model: UtModel) : CgParameterKind() + object ExpectedResult : CgParameterKind() + object ExpectedException : CgParameterKind() +} + + +// Primitive and String literals + +data class CgLiteral(override val type: ClassId, val value: Any?) : CgValue { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CgLiteral + + if (type != other.type) return false + if (value != other.value) return false + + return true + } + + override fun hashCode(): Int { + var result = type.hashCode() + result = 31 * result + (value?.hashCode() ?: 0) + return result + } +} + +// Runnable like this::toString or (new Object())::toString (non-static) or Random::nextRandomInt (static) etc +abstract class CgRunnable : CgValue { + abstract val methodId: MethodId +} + +/** + * [referenceExpression] is "this" in this::toString or (new Object()) in (new Object())::toString (non-static) + */ +data class CgNonStaticRunnable( + override val type: ClassId, + val referenceExpression: CgReferenceExpression, + override val methodId: MethodId +) : CgRunnable() + +/** + * [classId] is Random is Random::nextRandomInt (static) etc + */ +data class CgStaticRunnable( + override val type: ClassId, + val classId: ClassId, + override val methodId: MethodId, +) : CgRunnable() + +// Array allocation + +open class CgAllocateArray(type: ClassId, elementType: ClassId, val size: Int) : CgReferenceExpression { + override val type: ClassId by lazy { + CgClassId( + type.name, + updateElementType(elementType), + isNullable = type.isNullable + ) + } + val elementType: ClassId by lazy { + workaround(WorkaroundReason.ARRAY_ELEMENT_TYPES_ALWAYS_NULLABLE) { + // for now all array element types are nullable + updateElementType(elementType) + } + } + + private fun updateElementType(type: ClassId): ClassId = + if (type.elementClassId != null) { + CgClassId(type.name, updateElementType(type.elementClassId!!), isNullable = true) + } else { + CgClassId(type, isNullable = true) + } +} + +/** + * Allocation of an array with initialization. For example: `new String[] {"a", "b", null}`. + */ +data class CgAllocateInitializedArray(val initializer: CgArrayInitializer) : + CgAllocateArray(initializer.arrayType, initializer.elementType, initializer.size) + +data class CgArrayInitializer(val arrayType: ClassId, val elementType: ClassId, val values: List) : + CgExpression { + val size: Int + get() = values.size + + override val type: ClassId + get() = arrayType +} + + +// Spread operator (for Kotlin, empty for Java) + +data class CgSpread(override val type: ClassId, val array: CgExpression) : CgExpression + +// Interpolated string +// e.g. String.format() for Java, "${}" for Kotlin + +class CgFormattedString(val array: List) : CgElement + +// Enum constant + +data class CgEnumConstantAccess( + val enumClass: ClassId, + val name: String +) : CgReferenceExpression { + override val type: ClassId = enumClass +} + +// Property access + +// TODO in general it is not CgReferenceExpression because field can have a primitive type +abstract class CgAbstractFieldAccess : CgReferenceExpression { + abstract val fieldId: FieldId + + override val type: ClassId + get() = fieldId.type +} + +data class CgFieldAccess( + val caller: CgExpression, + override val fieldId: FieldId +) : CgAbstractFieldAccess() + +data class CgStaticFieldAccess( + override val fieldId: FieldId +) : CgAbstractFieldAccess() { + val declaringClass: ClassId = fieldId.declaringClass + val fieldName: String = fieldId.name +} + +// Conditional statements + +data class CgIfStatement( + val condition: CgExpression, + val trueBranch: List, + val falseBranch: List? = null // false branch may be absent +) : CgStatement + +data class CgSwitchCaseLabel( + val label: CgLiteral? = null, // have to be compile time constant (null for default label) + val statements: List, + val addBreakStatementToEnd: Boolean = true // do not set this field to "true" value if you manually added "break" to statements +) : CgStatement + +data class CgSwitchCase( + val value: CgExpression, // TODO required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum' + val labels: List, + val defaultLabel: CgSwitchCaseLabel? = null +) : CgStatement + +// Binary logical operators + +data class CgLogicalAnd( + val left: CgExpression, + val right: CgExpression +) : CgExpression { + override val type: ClassId = booleanClassId +} + +data class CgLogicalOr( + val left: CgExpression, + val right: CgExpression +) : CgExpression { + override val type: ClassId = booleanClassId +} + +// Acquisition of array length, e.g. args.length + +/** + * @param variable represents an array variable + */ +data class CgGetLength(val variable: CgVariable) : CgExpression { + override val type: ClassId = intClassId +} + +// Acquisition of java or kotlin class, e.g. MyClass.class in Java, MyClass::class.java in Kotlin or MyClass::class for Kotlin classes + +sealed class CgGetClass : CgReferenceExpression { + abstract val classId: ClassId + + override val type: ClassId = Class::class.id +} + +/** + * NOTE: use `CgContext.createGetClassExpression` + * instead of the explicit constructor call. + */ +data class CgGetJavaClass(override val classId: ClassId) : CgGetClass() + +/** + * NOTE: use `CgContext.createGetClassExpression` + * instead of the explicit constructor call. + */ +data class CgGetKotlinClass(override val classId: ClassId) : CgGetClass() + +// Executable calls + +data class CgStatementExecutableCall(val call: CgExecutableCall) : CgStatement + +// TODO in general it is not CgReferenceExpression because returned value can have a primitive type +// (or no value can be returned) +abstract class CgExecutableCall : CgReferenceExpression { + abstract val executableId: ExecutableId + abstract val arguments: List + abstract val typeParameters: TypeParameters +} + +data class CgConstructorCall( + override val executableId: ConstructorId, + override val arguments: List, + override val typeParameters: TypeParameters = TypeParameters() +) : CgExecutableCall() { + override val type: ClassId = executableId.classId +} + +data class CgMethodCall( + val caller: CgExpression?, + override val executableId: MethodId, + override val arguments: List, + override val typeParameters: TypeParameters = TypeParameters() +) : CgExecutableCall() { + override val type: ClassId = executableId.returnType +} + +fun CgExecutableCall.toStatement(): CgStatementExecutableCall = CgStatementExecutableCall(this) + +// Throw statement + +data class CgThrowStatement( + val exception: CgExpression +) : CgStatement + +// Empty line + +object CgEmptyLine : CgStatement + +data class CgExceptionHandler( + val exception: CgVariable, + val statements: List +) \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgMethodTestSet.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgMethodTestSet.kt new file mode 100644 index 0000000000..05bbbf1b04 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/CgMethodTestSet.kt @@ -0,0 +1,140 @@ +package org.utbot.framework.codegen.domain.models + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtClusterInfo +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtExecutionFailure +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtMethodTestSet +import org.utbot.framework.plugin.api.UtSymbolicExecution +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.fuzzer.UtFuzzedExecution + +data class CgMethodTestSet( + val executableUnderTest: ExecutableId, + val errors: Map = emptyMap(), + val clustersInfo: List>, +) { + val executablesToCall get() = + executions.map { it.executableToCall ?: executableUnderTest } + .distinctBy { it.classId to it.signature } + + var executions: List = emptyList() + private set + + constructor(from: UtMethodTestSet) : this(from.method, from.errors, from.clustersInfo) { + executions = from.executions + } + + /** + * Constructor for JavaScript and Python purposes. + */ + constructor( + executableId: ExecutableId, + errors: Map = emptyMap(), + executions: List = emptyList(), + clustersInfo: List> = listOf(null to executions.indices), + ) : this(executableId, errors, clustersInfo) { + this.executions = executions + } + + fun prepareTestSetsForParameterizedTestGeneration(): List { + val testSetList = mutableListOf() + + // Mocks are not supported in parametrized tests, so we exclude them + val testSetWithoutMocking = this.excludeExecutionsWithMocking() + for (splitByExecutionTestSet in testSetWithoutMocking.splitExecutionsByResult()) { + for (splitByChangedStaticsTestSet in splitByExecutionTestSet.splitExecutionsByChangedStatics()) { + testSetList += splitByChangedStaticsTestSet + } + } + + return testSetList + } + + /** + * Finds a [ClassId] of all result models in executions. + * + * Tries to find a unique result type in testSets or + * gets executable return type. + * + * Returns `null` if there are multiple distinct [executablesToCall]. + */ + fun getCommonResultTypeOrNull(): ClassId? { + val executableId = executablesToCall.singleOrNull() ?: return null + return when (executableId.returnType) { + voidClassId -> executableId.returnType + else -> { + val successfulExecutions = executions.filter { it.result is UtExecutionSuccess } + if (successfulExecutions.isNotEmpty()) { + successfulExecutions + .map { (it.result as UtExecutionSuccess).model.classId } + .distinct() + .singleOrNull() + ?: executableId.returnType + } else { + executableId.returnType + } + } + } + } + + /** + * Splits [CgMethodTestSet] into separate test sets having + * unique result model [ClassId] in each subset. + */ + private fun splitExecutionsByResult() : List { + val successfulExecutions = executions.filter { it.result is UtExecutionSuccess } + val failureExecutions = executions.filter { it.result is UtExecutionFailure } + + val executionsByResult: MutableMap> = + successfulExecutions + .groupBy { (it.result as UtExecutionSuccess).model.classId }.toMutableMap() + + // if we have failure executions, we add them to the first successful executions group + val groupClassId = executionsByResult.keys.firstOrNull() + if (groupClassId != null) { + executionsByResult[groupClassId] = executionsByResult[groupClassId]!! + failureExecutions + } else { + executionsByResult[objectClassId] = failureExecutions + } + + return executionsByResult.map{ (_, executions) -> substituteExecutions(executions) } + } + + /** + * Splits [CgMethodTestSet] test sets by affected static fields statics. + * + * A separate test set is created for each combination of modified statics. + */ + private fun splitExecutionsByChangedStatics(): List { + val executionsByStaticsUsage = executions.groupBy { it.stateBefore.statics.keys } + + val executionsByStaticsUsageAndTheirTypes = executionsByStaticsUsage + .flatMap { (_, executions) -> + executions.groupBy { it.stateBefore.statics.values.map { model -> model.classId } }.values + } + + return executionsByStaticsUsageAndTheirTypes.map { executions -> substituteExecutions(executions) } + } + + /** + * Excludes [UtFuzzedExecution] and [UtSymbolicExecution] with mocking from [CgMethodTestSet]. + * + * It is used in parameterized test generation. + * We exclude them because we cannot track force mocking occurrences in fuzzing process + * and cannot deal with mocking in parameterized mode properly. + */ + private fun excludeExecutionsWithMocking(): CgMethodTestSet { + val symbolicExecutionsWithoutMocking = executions + .filterIsInstance() + .filter { !it.containsMocking } + + return substituteExecutions(symbolicExecutionsWithoutMocking) + } + + fun substituteExecutions(newExecutions: List): CgMethodTestSet = + copy().apply { executions = newExecutions } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt new file mode 100644 index 0000000000..d218e87451 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt @@ -0,0 +1,33 @@ +package org.utbot.framework.codegen.domain.models + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper +import org.utbot.framework.plugin.api.mapper.mapStateBeforeModels + +/** + * Stores method test sets in a structure that replicates structure of their methods in [classUnderTest]. + * I.e., if some method is declared in nested class of [classUnderTest], its testset will be put + * in [TestClassModel] in one of [nestedClasses] + */ +abstract class TestClassModel( + val classUnderTest: ClassId, + val methodTestSets: List, + val nestedClasses: List, +) + +class SimpleTestClassModel( + classUnderTest: ClassId, + methodTestSets: List, + nestedClasses: List = listOf(), +): TestClassModel(classUnderTest, methodTestSets, nestedClasses) + +fun SimpleTestClassModel.mapStateBeforeModels(mapperProvider: () -> UtModelDeepMapper) = + SimpleTestClassModel( + classUnderTest = classUnderTest, + nestedClasses = nestedClasses, + methodTestSets = methodTestSets.map { testSet -> + testSet.substituteExecutions( + testSet.executions.map { execution -> execution.mapStateBeforeModels(mapperProvider()) } + ) + } + ) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/builders/SimpleTestClassModelBuilder.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/builders/SimpleTestClassModelBuilder.kt new file mode 100644 index 0000000000..41f1b6824b --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/builders/SimpleTestClassModelBuilder.kt @@ -0,0 +1,55 @@ +package org.utbot.framework.codegen.domain.models.builders + +import org.utbot.framework.codegen.domain.UtModelWrapper +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.SimpleTestClassModel +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.enclosingClass + +typealias TypedModelWrappers = Map> + +open class SimpleTestClassModelBuilder: TestClassModelBuilder() { + override fun createTestClassModel( + classUnderTest: ClassId, + testSets: List, + ): SimpleTestClassModel { + // For each class stores list of methods declared in this class (methods from nested classes are excluded) + val class2methodTestSets = testSets.groupBy { it.executableUnderTest.classId } + + val classesWithMethodsUnderTest = testSets + .map { it.executableUnderTest.classId } + .distinct() + + // For each class stores list of its "direct" nested classes + val class2nestedClasses = mutableMapOf>() + + for (classId in classesWithMethodsUnderTest) { + var currentClass = classId + var enclosingClass = currentClass.enclosingClass + // while we haven't reached the top of nested class hierarchy or the main class under test + while (enclosingClass != null && currentClass != classUnderTest) { + class2nestedClasses.getOrPut(enclosingClass) { mutableSetOf() } += currentClass + currentClass = enclosingClass + enclosingClass = enclosingClass.enclosingClass + } + } + + return constructRecursively(classUnderTest, class2methodTestSets, class2nestedClasses) + } + + private fun constructRecursively( + clazz: ClassId, + class2methodTestSets: Map>, + class2nestedClasses: Map> + ): SimpleTestClassModel { + val currentNestedClasses = class2nestedClasses.getOrDefault(clazz, listOf()) + val currentMethodTestSets = class2methodTestSets.getOrDefault(clazz, listOf()) + return SimpleTestClassModel( + clazz, + currentMethodTestSets, + currentNestedClasses.map { + constructRecursively(it, class2methodTestSets, class2nestedClasses) + } + ) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/builders/TestClassModelBuilder.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/builders/TestClassModelBuilder.kt new file mode 100644 index 0000000000..a1f47e873e --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/builders/TestClassModelBuilder.kt @@ -0,0 +1,9 @@ +package org.utbot.framework.codegen.domain.models.builders + +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.TestClassModel +import org.utbot.framework.plugin.api.ClassId + +abstract class TestClassModelBuilder { + abstract fun createTestClassModel(classUnderTest: ClassId, testSets: List): TestClassModel +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/AbstractCodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/AbstractCodeGenerator.kt new file mode 100644 index 0000000000..a7a05831f5 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/AbstractCodeGenerator.kt @@ -0,0 +1,80 @@ +package org.utbot.framework.codegen.generator + +import mu.KotlinLogging +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgClassFile +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.renderer.CgAbstractRenderer +import org.utbot.framework.plugin.api.UtMethodTestSet +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +abstract class AbstractCodeGenerator(params: CodeGeneratorParams) { + protected val logger = KotlinLogging.logger {} + + open var context: CgContext = with(params) { + CgContext( + classUnderTest = classUnderTest, + projectType = projectType, + generateUtilClassFile = generateUtilClassFile, + paramNames = paramNames, + testFramework = testFramework, + mockFramework = mockFramework, + codegenLanguage = codegenLanguage, + cgLanguageAssistant = cgLanguageAssistant, + parametrizedTestSource = parameterizedTestSource, + staticsMocking = staticsMocking, + forceStaticMocking = forceStaticMocking, + generateWarningsForStaticMocking = generateWarningsForStaticMocking, + runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour, + hangingTestsTimeout = hangingTestsTimeout, + enableTestsTimeout = enableTestsTimeout, + testClassPackageName = testClassPackageName + ) + } + + //TODO: we support custom test class name only in utbot-online, probably support them in plugin as well + fun generateAsString(testSets: Collection, testClassCustomName: String? = null): String = + generateAsStringWithTestReport(testSets, testClassCustomName).generatedCode + + //TODO: we support custom test class name only in utbot-online, probably support them in plugin as well + fun generateAsStringWithTestReport( + testSets: Collection, + testClassCustomName: String? = null, + ): CodeGeneratorResult { + val cgTestSets = testSets.map { CgMethodTestSet(it) }.toList() + return withCustomContext(testClassCustomName) { + context.withTestClassFileScope { + generate(cgTestSets) + } + } + } + + protected abstract fun generate(testSets: List): CodeGeneratorResult + + protected fun renderToString(testClassFile: CgClassFile): String { + logger.info { "Rendering phase started at ${now()}" } + val renderer = CgAbstractRenderer.makeRenderer(context) + testClassFile.accept(renderer) + logger.info { "Rendering phase finished at ${now()}" } + + return renderer.toString() + } + + protected fun now(): String = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS")) + + /** + * Wrapper function that configures context as needed for utbot-online: + * - turns on imports optimization in code generator + * - passes a custom test class name if there is one + */ + fun withCustomContext(testClassCustomName: String? = null, block: () -> R): R { + val prevContext = context + return try { + context = prevContext.customCopy(shouldOptimizeImports = true, testClassCustomName = testClassCustomName) + block() + } finally { + context = prevContext + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/CodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/CodeGenerator.kt new file mode 100644 index 0000000000..d43eb7079b --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/CodeGenerator.kt @@ -0,0 +1,29 @@ +package org.utbot.framework.codegen.generator + +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.builders.SimpleTestClassModelBuilder +import org.utbot.framework.codegen.tree.CgSimpleTestClassConstructor +import org.utbot.framework.codegen.tree.ututils.UtilClassKind +import org.utbot.framework.plugin.api.ClassId + +open class CodeGenerator(params: CodeGeneratorParams): AbstractCodeGenerator(params) { + protected val classUnderTest: ClassId = params.classUnderTest + + override fun generate(testSets: List): CodeGeneratorResult { + val testClassModel = SimpleTestClassModelBuilder().createTestClassModel(classUnderTest, testSets) + + logger.info { "Code generation phase started at ${now()}" } + val astConstructor = CgSimpleTestClassConstructor(context) + val testClassFile = astConstructor.construct(testClassModel) + logger.info { "Code generation phase finished at ${now()}" } + + val generatedCode = renderToString(testClassFile) + + return CodeGeneratorResult( + generatedCode = generatedCode, + utilClassKind = UtilClassKind.fromCgContextOrNull(context), + testsGenerationReport = astConstructor.testsGenerationReport, + ) + } +} + diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/CodeGeneratorParams.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/CodeGeneratorParams.kt new file mode 100644 index 0000000000..5bbc205307 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/CodeGeneratorParams.kt @@ -0,0 +1,33 @@ +package org.utbot.framework.codegen.generator + +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.HangingTestsTimeout +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.codegen.services.language.CgLanguageAssistant +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MockFramework + +data class CodeGeneratorParams( + val classUnderTest: ClassId, + val projectType: ProjectType, + val paramNames: MutableMap> = mutableMapOf(), + val generateUtilClassFile: Boolean = false, + val testFramework: TestFramework = TestFramework.defaultItem, + val mockFramework: MockFramework = MockFramework.defaultItem, + val staticsMocking: StaticsMocking = StaticsMocking.defaultItem, + val forceStaticMocking: ForceStaticMocking = ForceStaticMocking.defaultItem, + val generateWarningsForStaticMocking: Boolean = true, + val codegenLanguage: CodegenLanguage = CodegenLanguage.defaultItem, + val cgLanguageAssistant: CgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage(codegenLanguage), + val parameterizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem, + val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.defaultItem, + val hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(), + val enableTestsTimeout: Boolean = true, + val testClassPackageName: String = classUnderTest.packageName, +) \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/CodeGeneratorResult.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/CodeGeneratorResult.kt new file mode 100644 index 0000000000..67db5a0bee --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/CodeGeneratorResult.kt @@ -0,0 +1,16 @@ +package org.utbot.framework.codegen.generator + +import org.utbot.framework.codegen.reports.TestsGenerationReport +import org.utbot.framework.codegen.tree.ututils.UtilClassKind + +/** + * @property generatedCode the source code of the test class + * @property testsGenerationReport some info about test generation process + * @property utilClassKind the kind of util class if it is required, otherwise - null + */ +data class CodeGeneratorResult( + val generatedCode: String, + val testsGenerationReport: TestsGenerationReport, + // null if no util class needed, e.g. when we are generating utils directly into test class + val utilClassKind: UtilClassKind? = null, +) \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/ModelBasedCodeGeneratorService.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/ModelBasedCodeGeneratorService.kt deleted file mode 100644 index 4feeddb15f..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/ModelBasedCodeGeneratorService.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.utbot.framework.codegen.model - -import org.utbot.framework.codegen.CodeGeneratorService -import org.utbot.framework.codegen.TestCodeGenerator - -class ModelBasedCodeGeneratorService : CodeGeneratorService { - override val displayName: String = "Model based code generator" - override val serviceProvider: TestCodeGenerator = ModelBasedTestCodeGenerator() -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/ModelBasedTestCodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/ModelBasedTestCodeGenerator.kt deleted file mode 100644 index c96b88603e..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/ModelBasedTestCodeGenerator.kt +++ /dev/null @@ -1,97 +0,0 @@ -package org.utbot.framework.codegen.model - -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.HangingTestsTimeout -import org.utbot.framework.codegen.ParametrizedTestSource -import org.utbot.framework.codegen.RuntimeExceptionTestsBehaviour -import org.utbot.framework.codegen.StaticsMocking -import org.utbot.framework.codegen.TestCodeGenerator -import org.utbot.framework.codegen.TestFramework -import org.utbot.framework.codegen.TestsCodeWithTestReport -import org.utbot.framework.codegen.model.constructor.context.CgContext -import org.utbot.framework.codegen.model.constructor.tree.CgTestClassConstructor -import org.utbot.framework.codegen.model.tree.CgTestClassFile -import org.utbot.framework.codegen.model.visitor.CgAbstractRenderer -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.framework.plugin.api.util.id - -class ModelBasedTestCodeGenerator : TestCodeGenerator { - private lateinit var context: CgContext - - override fun init( - classUnderTest: Class<*>, - params: MutableMap, List>, - testFramework: TestFramework, - mockFramework: MockFramework?, - staticsMocking: StaticsMocking, - forceStaticMocking: ForceStaticMocking, - generateWarningsForStaticMocking: Boolean, - codegenLanguage: CodegenLanguage, - parameterizedTestSource: ParametrizedTestSource, - runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour, - hangingTestsTimeout: HangingTestsTimeout, - enableTestsTimeout: Boolean, - testClassPackageName: String - ) { - context = CgContext( - classUnderTest = classUnderTest.id, - // TODO: remove existingNames parameter completely - existingMethodNames = mutableSetOf(), - paramNames = params, - testFramework = testFramework, - mockFramework = mockFramework ?: MockFramework.MOCKITO, - codegenLanguage = codegenLanguage, - parameterizedTestSource = parameterizedTestSource, - staticsMocking = staticsMocking, - forceStaticMocking = forceStaticMocking, - generateWarningsForStaticMocking = generateWarningsForStaticMocking, - runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour, - hangingTestsTimeout = hangingTestsTimeout, - enableTestsTimeout = enableTestsTimeout, - testClassPackageName = testClassPackageName - ) - } - - //TODO: we support custom test class name only in utbot-online, probably support them in plugin as well - override fun generateAsString(testCases: Collection, testClassCustomName: String?): String = - generateAsStringWithTestReport(testCases, testClassCustomName).generatedCode - - //TODO: we support custom test class name only in utbot-online, probably support them in plugin as well - override fun generateAsStringWithTestReport( - testCases: Collection, - testClassCustomName: String? - ): TestsCodeWithTestReport = - withCustomContext(testClassCustomName) { - context.withClassScope { - val testClassFile = CgTestClassConstructor(context).construct(testCases) - TestsCodeWithTestReport(renderClassFile(testClassFile), testClassFile.testsGenerationReport) - } - } - - /** - * Wrapper function that configures context as needed for utbot-online: - * - turns on imports optimization in code generator - * - passes a custom test class name if there is one - */ - private fun withCustomContext(testClassCustomName: String? = null, block: () -> R): R { - val prevContext = context - return try { - context = prevContext.copy( - shouldOptimizeImports = true, - testClassCustomName = testClassCustomName - ) - block() - } finally { - context = prevContext - } - } - - private fun renderClassFile(file: CgTestClassFile): String { - val renderer = CgAbstractRenderer.makeRenderer(context) - file.accept(renderer) - return renderer.toString() - } -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt deleted file mode 100644 index 9169e42c44..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/builtin/UtilMethodBuiltins.kt +++ /dev/null @@ -1,183 +0,0 @@ -package org.utbot.framework.codegen.model.constructor.builtin - -import org.utbot.framework.codegen.MockitoStaticMocking -import org.utbot.framework.codegen.model.constructor.util.utilMethodId -import org.utbot.framework.codegen.model.tree.CgClassId -import org.utbot.framework.plugin.api.BuiltinClassId -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.util.booleanClassId -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.intClassId -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.objectClassId -import org.utbot.framework.plugin.api.util.stringClassId -import org.utbot.framework.plugin.api.util.voidClassId -import sun.misc.Unsafe - -/** - * Set of ids of all possible util methods for a given class - * The class may actually not have some of these methods if they - * are not required in the process of code generation - */ -internal val ClassId.possibleUtilMethodIds: Set - get() = setOf( - getUnsafeInstanceMethodId, - createInstanceMethodId, - createArrayMethodId, - setFieldMethodId, - setStaticFieldMethodId, - getFieldValueMethodId, - getStaticFieldValueMethodId, - getEnumConstantByNameMethodId, - deepEqualsMethodId, - arraysDeepEqualsMethodId, - iterablesDeepEqualsMethodId, - streamsDeepEqualsMethodId, - mapsDeepEqualsMethodId, - hasCustomEqualsMethodId, - getArrayLengthMethodId - ) - -internal val ClassId.getUnsafeInstanceMethodId: MethodId - get() = utilMethodId( - name = "getUnsafeInstance", - returnType = Unsafe::class.id, - ) - -/** - * Method that creates instance using Unsafe - */ -internal val ClassId.createInstanceMethodId: MethodId - get() = utilMethodId( - name = "createInstance", - returnType = CgClassId(objectClassId, isNullable = true), - arguments = arrayOf(stringClassId) - ) - -internal val ClassId.createArrayMethodId: MethodId - get() = utilMethodId( - name = "createArray", - returnType = Array::class.id, - arguments = arrayOf(stringClassId, intClassId, Array::class.id) - ) - -internal val ClassId.setFieldMethodId: MethodId - get() = utilMethodId( - name = "setField", - returnType = voidClassId, - arguments = arrayOf(objectClassId, stringClassId, objectClassId) - ) - -internal val ClassId.setStaticFieldMethodId: MethodId - get() = utilMethodId( - name = "setStaticField", - returnType = voidClassId, - arguments = arrayOf(Class::class.id, stringClassId, objectClassId) - ) - -internal val ClassId.getFieldValueMethodId: MethodId - get() = utilMethodId( - name = "getFieldValue", - returnType = objectClassId, - arguments = arrayOf(objectClassId, stringClassId) - ) - -internal val ClassId.getStaticFieldValueMethodId: MethodId - get() = utilMethodId( - name = "getStaticFieldValue", - returnType = objectClassId, - arguments = arrayOf(Class::class.id, stringClassId) - ) - -internal val ClassId.getEnumConstantByNameMethodId: MethodId - get() = utilMethodId( - name = "getEnumConstantByName", - returnType = objectClassId, - arguments = arrayOf(Class::class.id, stringClassId) - ) - -internal val ClassId.deepEqualsMethodId: MethodId - get() = utilMethodId( - name = "deepEquals", - returnType = booleanClassId, - arguments = arrayOf(objectClassId, objectClassId) - ) - -internal val ClassId.arraysDeepEqualsMethodId: MethodId - get() = utilMethodId( - name = "arraysDeepEquals", - returnType = booleanClassId, - arguments = arrayOf(objectClassId, objectClassId) - ) - -internal val ClassId.iterablesDeepEqualsMethodId: MethodId - get() = utilMethodId( - name = "iterablesDeepEquals", - returnType = booleanClassId, - arguments = arrayOf(java.lang.Iterable::class.id, java.lang.Iterable::class.id) - ) - -internal val ClassId.streamsDeepEqualsMethodId: MethodId - get() = utilMethodId( - name = "streamsDeepEquals", - returnType = booleanClassId, - arguments = arrayOf(java.util.stream.Stream::class.id, java.util.stream.Stream::class.id) - ) - -internal val ClassId.mapsDeepEqualsMethodId: MethodId - get() = utilMethodId( - name = "mapsDeepEquals", - returnType = booleanClassId, - arguments = arrayOf(java.util.Map::class.id, java.util.Map::class.id) - ) - -internal val ClassId.hasCustomEqualsMethodId: MethodId - get() = utilMethodId( - name = "hasCustomEquals", - returnType = booleanClassId, - arguments = arrayOf(Class::class.id) - ) - -internal val ClassId.getArrayLengthMethodId: MethodId - get() = utilMethodId( - name = "getArrayLength", - returnType = intClassId, - arguments = arrayOf(objectClassId) - ) - -/** - * [MethodId] for [AutoCloseable.close]. - */ -val closeMethodId = MethodId( - classId = AutoCloseable::class.java.id, - name = "close", - returnType = voidClassId, - parameters = emptyList() -) - -val mocksAutoCloseable: Set = setOf( - MockitoStaticMocking.mockedStaticClassId, - MockitoStaticMocking.mockedConstructionClassId -) - -val predefinedAutoCloseable: Set = mocksAutoCloseable - -/** - * Checks if this class is marked as auto closeable - * (useful for classes that could not be loaded by class loader like mocks for mocking statics from Mockito Inline). - */ -internal val ClassId.isPredefinedAutoCloseable: Boolean - get() = this in predefinedAutoCloseable - -/** - * Returns [AutoCloseable.close] method id for all auto closeable. - * and predefined as auto closeable via [isPredefinedAutoCloseable], and null otherwise. - * Null always for [BuiltinClassId]. - */ -internal val ClassId.closeMethodIdOrNull: MethodId? - get() = when { - isPredefinedAutoCloseable -> closeMethodId - this is BuiltinClassId -> null - else -> (jClass as? AutoCloseable)?.let { closeMethodId } - } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt deleted file mode 100644 index a7c3dc4216..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/context/CgContext.kt +++ /dev/null @@ -1,439 +0,0 @@ -package org.utbot.framework.codegen.model.constructor.context - -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.HangingTestsTimeout -import org.utbot.framework.codegen.Import -import org.utbot.framework.codegen.ParametrizedTestSource -import org.utbot.framework.codegen.RuntimeExceptionTestsBehaviour -import org.utbot.framework.codegen.StaticsMocking -import org.utbot.framework.codegen.TestFramework -import org.utbot.framework.codegen.model.constructor.builtin.arraysDeepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.createArrayMethodId -import org.utbot.framework.codegen.model.constructor.builtin.createInstanceMethodId -import org.utbot.framework.codegen.model.constructor.builtin.deepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getArrayLengthMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getEnumConstantByNameMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getFieldValueMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getStaticFieldValueMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getUnsafeInstanceMethodId -import org.utbot.framework.codegen.model.constructor.builtin.hasCustomEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.iterablesDeepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.mapsDeepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.possibleUtilMethodIds -import org.utbot.framework.codegen.model.constructor.builtin.setFieldMethodId -import org.utbot.framework.codegen.model.constructor.builtin.setStaticFieldMethodId -import org.utbot.framework.codegen.model.constructor.tree.Block -import org.utbot.framework.codegen.model.constructor.util.EnvironmentFieldStateCache -import org.utbot.framework.codegen.model.constructor.util.importIfNeeded -import org.utbot.framework.codegen.model.tree.CgAnnotation -import org.utbot.framework.codegen.model.tree.CgExecutableCall -import org.utbot.framework.codegen.model.tree.CgStatement -import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall -import org.utbot.framework.codegen.model.tree.CgTestMethod -import org.utbot.framework.codegen.model.tree.CgThisInstance -import org.utbot.framework.codegen.model.tree.CgValue -import org.utbot.framework.codegen.model.tree.CgVariable -import org.utbot.framework.codegen.model.util.createTestClassName -import org.utbot.framework.plugin.api.BuiltinClassId -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.ExecutableId -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtReferenceModel -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.framework.plugin.api.util.executableId -import java.util.IdentityHashMap -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.PersistentMap -import kotlinx.collections.immutable.PersistentSet -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.persistentMapOf -import kotlinx.collections.immutable.persistentSetOf -import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMethodId - -/** - * Interface for all code generation context aware entities - * - * Most of the properties are 'val'. - * Although, some of the properties are declared as 'var' so that - * they can be reassigned as well as modified - * - * For example, [currentTestClass] and [currentExecutable] can be reassigned - * when we start generating another method or test class - * - * [variables] and [existingVariableNames] are 'var' properties - * that can be reverted to their previous values on exit from a name scope - * - * @see [CgContextOwner.withNameScope] - */ -internal interface CgContextOwner { - // current class under test - val classUnderTest: ClassId - - // test class currently being generated - val currentTestClass: ClassId - - // current executable under test - var currentExecutable: ExecutableId? - - // test class superclass (if needed) - var testClassSuperclass: ClassId? - - // list of interfaces that the test class must inherit - val collectedTestClassInterfaces: MutableSet - - // list of annotations of the test class - val collectedTestClassAnnotations: MutableSet - - // exceptions that can be thrown inside of current method being built - val collectedExceptions: MutableSet - - // annotations required by the current method being built - val collectedTestMethodAnnotations: MutableSet - - // imports required by the test class being built - val collectedImports: MutableSet - - val importedStaticMethods: MutableSet - val importedClasses: MutableSet - - // util methods required by the test class being built - val requiredUtilMethods: MutableSet - - // test methods being generated - val testMethods: MutableList - - // names of methods that already exist in the test class - val existingMethodNames: MutableSet - - // At the start of a test method we save the initial state of some static fields in variables. - // This map is used to restore the initial values of these variables at the end of the test method. - val prevStaticFieldValues: MutableMap - - // names of parameters of methods under test - val paramNames: Map, List> - - // UtExecution we currently generate a test method for. - // It is null when no test method is being generated at the moment. - var currentExecution: UtExecution? - - val testFramework: TestFramework - - val mockFramework: MockFramework - - val staticsMocking: StaticsMocking - - val forceStaticMocking: ForceStaticMocking - - val generateWarningsForStaticMocking: Boolean - - val codegenLanguage: CodegenLanguage - - val parameterizedTestSource: ParametrizedTestSource - - // flag indicating whether a mock framework is used in the generated code - var mockFrameworkUsed: Boolean - - // object that represents a set of information about JUnit of selected version - - // Persistent collections are used to conveniently restore their previous state. - // For example, when we exit a block of code we return to the previous name scope. - // At that moment we revert some collections (e.g. variable names) to the previous state. - - // current block of code being built - var currentBlock: PersistentList - - // variable names being used in the current name scope - var existingVariableNames: PersistentSet - - // all declared variables in the current name scope - var variables: PersistentSet - - // variables of java.lang.Class type declared in the current name scope - var declaredClassRefs: PersistentMap - - // Variables of either java.lang.reflect.Constructor or java.lang.reflect.Method types - // declared in the current name scope. - // java.lang.reflect.Executable is a superclass of both of these types. - var declaredExecutableRefs: PersistentMap - - // generated this instance for method under test - var thisInstance: CgValue? - - // generated arguments for method under test - val methodArguments: MutableList - - // a variable representing an actual result of the method under test call - var actual: CgVariable - - // map from a set of tests for a method to another map - // which connects code generation error message - // with the number of times it occurred - val codeGenerationErrors: MutableMap> - - // package for generated test class - val testClassPackageName: String - - val shouldOptimizeImports: Boolean - - var valueByModel: IdentityHashMap - - // use it to compare stateBefore and result variables - in case of equality do not create new variable - var valueByModelId: MutableMap - - val testClassCustomName: String? - - /** - * Determines whether tests that throw Runtime exceptions should fail or pass. - */ - val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour - - /** - * Timeout for possibly hanging tests (uses info from concrete executor). - */ - val hangingTestsTimeout: HangingTestsTimeout - - /** - * Determines whether tests with timeout should fail (with added Timeout annotation) or be disabled (for our pipeline). - */ - val enableTestsTimeout: Boolean - - var statesCache: EnvironmentFieldStateCache - - fun block(init: () -> Unit): Block { - val prevBlock = currentBlock - return try { - val block = persistentListOf() - currentBlock = block - withNameScope { - init() - currentBlock - } - } finally { - currentBlock = prevBlock - } - } - - operator fun CgStatement.unaryPlus() { - currentBlock = currentBlock.add(this) - } - - operator fun CgExecutableCall.unaryPlus(): CgStatementExecutableCall = - CgStatementExecutableCall(this).also { - currentBlock = currentBlock.add(it) - } - - fun updateCurrentExecutable(method: UtMethod<*>) { - currentExecutable = method.callable.executableId - } - - fun addException(exception: ClassId) { - if (collectedExceptions.add(exception)) { - importIfNeeded(exception) - } - } - - fun addAnnotation(annotation: CgAnnotation) { - if (collectedTestMethodAnnotations.add(annotation)) { - importIfNeeded(annotation.classId) // TODO: check how JUnit annotations are loaded - } - } - - fun withClassScope(block: () -> R): R { - clearClassScope() - return try { - block() - } finally { - clearClassScope() - } - } - - /** - * Set [mockFrameworkUsed] flag to true if the block is successfully executed - */ - fun withMockFramework(block: () -> R): R { - val result = block() - mockFrameworkUsed = true - return result - } - - fun updateVariableScope(variable: CgVariable, model: UtModel? = null) { - variables = variables.add(variable) - model?.let { - valueByModel[it] = variable - (model as UtReferenceModel).let { refModel -> - refModel.id.let { id -> valueByModelId[id] = variable } - } - } - } - - fun withNameScope(block: () -> R): R { - val prevVariableNames = existingVariableNames - val prevVariables = variables - val prevDeclaredClassRefs = declaredClassRefs - val prevDeclaredExecutableRefs = declaredExecutableRefs - val prevValueByModel = IdentityHashMap(valueByModel) - val prevValueByModelId = valueByModelId.toMutableMap() - return try { - block() - } finally { - existingVariableNames = prevVariableNames - variables = prevVariables - declaredClassRefs = prevDeclaredClassRefs - declaredExecutableRefs = prevDeclaredExecutableRefs - valueByModel = prevValueByModel - valueByModelId = prevValueByModelId - } - } - - private fun clearClassScope() { - collectedImports.clear() - importedStaticMethods.clear() - importedClasses.clear() - testMethods.clear() - requiredUtilMethods.clear() - valueByModel.clear() - valueByModelId.clear() - mockFrameworkUsed = false - } - - /** - * Check whether a method is an util method of the current class - */ - val MethodId.isUtil: Boolean - get() = this in currentTestClass.possibleUtilMethodIds - - /** - * Checks is it our util reflection field getter method. - * When this method is used with type cast in Kotlin, this type cast have to be safety - */ - val MethodId.isGetFieldUtilMethod: Boolean - get() = isUtil && (name == getFieldValue.name || name == getStaticFieldValue.name) - - val testClassThisInstance: CgThisInstance - - // util methods of current test class - - val getUnsafeInstance: MethodId - get() = currentTestClass.getUnsafeInstanceMethodId - - val createInstance: MethodId - get() = currentTestClass.createInstanceMethodId - - val createArray: MethodId - get() = currentTestClass.createArrayMethodId - - val setField: MethodId - get() = currentTestClass.setFieldMethodId - - val setStaticField: MethodId - get() = currentTestClass.setStaticFieldMethodId - - val getFieldValue: MethodId - get() = currentTestClass.getFieldValueMethodId - - val getStaticFieldValue: MethodId - get() = currentTestClass.getStaticFieldValueMethodId - - val getEnumConstantByName: MethodId - get() = currentTestClass.getEnumConstantByNameMethodId - - val deepEquals: MethodId - get() = currentTestClass.deepEqualsMethodId - - val arraysDeepEquals: MethodId - get() = currentTestClass.arraysDeepEqualsMethodId - - val iterablesDeepEquals: MethodId - get() = currentTestClass.iterablesDeepEqualsMethodId - - val streamsDeepEquals: MethodId - get() = currentTestClass.streamsDeepEqualsMethodId - - val mapsDeepEquals: MethodId - get() = currentTestClass.mapsDeepEqualsMethodId - - val hasCustomEquals: MethodId - get() = currentTestClass.hasCustomEqualsMethodId - - val getArrayLength: MethodId - get() = currentTestClass.getArrayLengthMethodId -} - -/** - * Context with current code generation info - */ -internal data class CgContext( - override val classUnderTest: ClassId, - override var currentExecutable: ExecutableId? = null, - override val collectedTestClassInterfaces: MutableSet = mutableSetOf(), - override val collectedTestClassAnnotations: MutableSet = mutableSetOf(), - override val collectedExceptions: MutableSet = mutableSetOf(), - override val collectedTestMethodAnnotations: MutableSet = mutableSetOf(), - override val collectedImports: MutableSet = mutableSetOf(), - override val importedStaticMethods: MutableSet = mutableSetOf(), - override val importedClasses: MutableSet = mutableSetOf(), - override val requiredUtilMethods: MutableSet = mutableSetOf(), - override val testMethods: MutableList = mutableListOf(), - override val existingMethodNames: MutableSet = mutableSetOf(), - override val prevStaticFieldValues: MutableMap = mutableMapOf(), - override val paramNames: Map, List>, - override var currentExecution: UtExecution? = null, - override val testFramework: TestFramework, - override val mockFramework: MockFramework, - override val staticsMocking: StaticsMocking, - override val forceStaticMocking: ForceStaticMocking, - override val generateWarningsForStaticMocking: Boolean, - override val codegenLanguage: CodegenLanguage = CodegenLanguage.defaultItem, - override val parameterizedTestSource: ParametrizedTestSource = ParametrizedTestSource.DO_NOT_PARAMETRIZE, - override var mockFrameworkUsed: Boolean = false, - override var currentBlock: PersistentList = persistentListOf(), - override var existingVariableNames: PersistentSet = persistentSetOf(), - override var variables: PersistentSet = persistentSetOf(), - override var declaredClassRefs: PersistentMap = persistentMapOf(), - override var declaredExecutableRefs: PersistentMap = persistentMapOf(), - override var thisInstance: CgValue? = null, - override val methodArguments: MutableList = mutableListOf(), - override val codeGenerationErrors: MutableMap> = mutableMapOf(), - override val testClassPackageName: String = classUnderTest.packageName, - override var shouldOptimizeImports: Boolean = false, - override var testClassCustomName: String? = null, - override val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = - RuntimeExceptionTestsBehaviour.defaultItem, - override val hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(), - override val enableTestsTimeout: Boolean = true -) : CgContextOwner { - override lateinit var statesCache: EnvironmentFieldStateCache - override lateinit var actual: CgVariable - - override val currentTestClass: ClassId by lazy { - val packagePrefix = if (testClassPackageName.isNotEmpty()) "$testClassPackageName." else "" - val simpleName = testClassCustomName ?: "${createTestClassName(classUnderTest.name)}Test" - val name = "$packagePrefix$simpleName" - BuiltinClassId( - name = name, - canonicalName = name, - simpleName = simpleName - ) - } - - override var testClassSuperclass: ClassId? = null - set(value) { - // Assigning a value to the testClassSuperclass when it is already non-null - // means that we need the test class to have more than one superclass - // which is impossible in Java and Kotlin. - require(field == null) { "It is impossible for the test class to have more than one superclass" } - field = value - } - - override var valueByModel: IdentityHashMap = IdentityHashMap() - - override var valueByModelId: MutableMap = mutableMapOf() - - override val testClassThisInstance: CgThisInstance = CgThisInstance(currentTestClass) -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/name/CgNameGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/name/CgNameGenerator.kt deleted file mode 100644 index aabaae88ff..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/name/CgNameGenerator.kt +++ /dev/null @@ -1,183 +0,0 @@ -package org.utbot.framework.codegen.model.constructor.name - -import org.utbot.framework.codegen.isLanguageKeyword -import org.utbot.framework.codegen.model.constructor.context.CgContext -import org.utbot.framework.codegen.model.constructor.context.CgContextOwner -import org.utbot.framework.codegen.model.constructor.util.infiniteInts -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.ConstructorId -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.util.executableId -import org.utbot.framework.plugin.api.util.isArray - -/** - * Interface for method and variable name generators - */ -internal interface CgNameGenerator { - /** - * Generate a variable name given a [base] name. - * @param isMock denotes whether a variable represents a mock object or not - */ - fun variableName(base: String, isMock: Boolean = false): String - - /** - * Convert a given class id to a string that can serve - * as a part of a variable name - */ - fun nameFrom(id: ClassId): String = - when { - id.isAnonymous -> id.prettifiedName - id.isArray -> id.prettifiedName - id.simpleName.isScreamingSnakeCase() -> id.simpleName.fromScreamingSnakeCaseToCamelCase() // special case for enum instances - else -> id.simpleName.decapitalize() - } - - /** - * Generate a variable name given a [type] of variable and a [base] name - * If [base] is not null, then use it to generate name - * Otherwise, fall back to generating a name by [type] - * @param isMock denotes whether a variable represents a mock object or not - */ - fun variableName(type: ClassId, base: String? = null, isMock: Boolean = false): String - - /** - * Generate a new test method name. - */ - fun testMethodNameFor(method: UtMethod<*>, customName: String? = null): String - - /** - * Generates a new parameterized test method name by data provider method name. - */ - fun parameterizedTestMethodName(dataProviderMethodName: String): String - - /** - * Generates a new data for parameterized test provider method name - */ - fun dataProviderMethodNameFor(method: UtMethod<*>): String - - /** - * Generate a new error method name - */ - fun errorMethodNameFor(method: UtMethod<*>): String -} - -/** - * Class that generates names for methods and variables - * To avoid name collisions it uses existing names information from CgContext - */ -internal class CgNameGeneratorImpl(private val context: CgContext) - : CgNameGenerator, CgContextOwner by context { - - override fun variableName(base: String, isMock: Boolean): String { - val baseName = if (isMock) base + "Mock" else base - return when { - baseName in existingVariableNames -> nextIndexedVarName(baseName) - isLanguageKeyword(baseName, codegenLanguage) -> createNameFromKeyword(baseName) - else -> baseName - }.also { - existingVariableNames = existingVariableNames.add(it) - } - } - - override fun variableName(type: ClassId, base: String?, isMock: Boolean): String { - val baseName = base?.fromScreamingSnakeCaseToCamelCase() ?: nameFrom(type) - return variableName(baseName.decapitalize(), isMock) - } - - override fun testMethodNameFor(method: UtMethod<*>, customName: String?): String { - val executableName = when (val id = method.callable.executableId) { - is ConstructorId -> id.classId.prettifiedName // TODO: maybe we need some suffix e.g. "Ctor"? - is MethodId -> id.name - } - // no index suffix allowed only when there's a vacant custom name - val name = if (customName != null && customName !in existingMethodNames) { - customName - } else { - val base = customName ?: "test${executableName.capitalize()}" - nextIndexedMethodName(base) - } - existingMethodNames += name - return name - } - - private val dataProviderMethodPrefix = "provideDataFor" - - override fun parameterizedTestMethodName(dataProviderMethodName: String) = - dataProviderMethodName.replace(dataProviderMethodPrefix, "parameterizedTestsFor") - - override fun dataProviderMethodNameFor(method: UtMethod<*>): String { - val indexedName = nextIndexedMethodName(method.callable.name.capitalize(), skipOne = true) - - existingMethodNames += indexedName - return "$dataProviderMethodPrefix$indexedName" - } - - override fun errorMethodNameFor(method: UtMethod<*>): String { - val executableName = when (val id = method.callable.executableId) { - is ConstructorId -> id.classId.prettifiedName - is MethodId -> id.name - } - val newName = when (val base = "test${executableName.capitalize()}_errors") { - !in existingMethodNames -> base - else -> nextIndexedMethodName(base) - } - existingMethodNames += newName - return newName - } - - /** - * Creates a new indexed variable name by [base] name. - */ - private fun nextIndexedVarName(base: String): String = - infiniteInts() - .map { "$base$it" } - .first { it !in existingVariableNames } - - /** - * Creates a new indexed methodName by [base] name. - * - * @param skipOne shows if we add "1" to first method name or not - */ - private fun nextIndexedMethodName(base: String, skipOne: Boolean = false): String = - infiniteInts() - .map { if (skipOne && it == 1) base else "$base$it" } - .first { it !in existingMethodNames } - - private fun createNameFromKeyword(baseName: String): String = when(codegenLanguage) { - CodegenLanguage.JAVA -> nextIndexedVarName(baseName) - CodegenLanguage.KOTLIN -> { - // use backticks for first variable with keyword name and use indexed names for all next such variables - if (baseName !in existingVariableNames) "`$baseName`" else nextIndexedVarName(baseName) - } - } -} - -/** - * Checks names like JUST_COLOR. - * @see SCREAMING_SNAKE_CASE - */ -private fun String.isScreamingSnakeCase(): Boolean { - // the code below is a bit complicated, but we want to support non-latin letters too, - // so we cannot just use such regex: [A-Z0-9]+(_[A-Z0-9]+)* - return split("_").all { - word -> word.isNotEmpty() && word.all { c -> c.isUpperCase() || c.isDigit() } - } -} - -/** - * Transforms string in SCREAMING_SNAKE_CASE to camelCase. If string is not in SCREAMING_SNAKE_CASE, returns it as it is. - * @see [isScreamingSnakeCase] - */ -private fun String.fromScreamingSnakeCaseToCamelCase(): String { - if (!isScreamingSnakeCase()) return this - - val lowerCaseWords = split("_").map { it.toLowerCase() } - val firstWord = lowerCaseWords.first() - val otherWords = lowerCaseWords.drop(1).map { it.capitalize() } - - val transformedWords = listOf(firstWord) + otherWords - - return transformedWords.joinToString("") -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt deleted file mode 100644 index 19acd0ba98..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgCallableAccessManager.kt +++ /dev/null @@ -1,406 +0,0 @@ -package org.utbot.framework.codegen.model.constructor.tree - -import kotlinx.collections.immutable.PersistentList -import org.utbot.framework.codegen.Junit5 -import org.utbot.framework.codegen.TestNg -import org.utbot.framework.codegen.model.constructor.builtin.any -import org.utbot.framework.codegen.model.constructor.builtin.anyOfClass -import org.utbot.framework.codegen.model.constructor.builtin.arraysDeepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.createArrayMethodId -import org.utbot.framework.codegen.model.constructor.builtin.createInstanceMethodId -import org.utbot.framework.codegen.model.constructor.builtin.deepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.forName -import org.utbot.framework.codegen.model.constructor.builtin.getArrayLengthMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getDeclaredConstructor -import org.utbot.framework.codegen.model.constructor.builtin.getDeclaredMethod -import org.utbot.framework.codegen.model.constructor.builtin.getEnumConstantByNameMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getFieldValueMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getStaticFieldValueMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getTargetException -import org.utbot.framework.codegen.model.constructor.builtin.getUnsafeInstanceMethodId -import org.utbot.framework.codegen.model.constructor.builtin.hasCustomEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.invoke -import org.utbot.framework.codegen.model.constructor.builtin.iterablesDeepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.mapsDeepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.newInstance -import org.utbot.framework.codegen.model.constructor.builtin.setAccessible -import org.utbot.framework.codegen.model.constructor.builtin.setFieldMethodId -import org.utbot.framework.codegen.model.constructor.builtin.setStaticFieldMethodId -import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.context.CgContext -import org.utbot.framework.codegen.model.constructor.context.CgContextOwner -import org.utbot.framework.codegen.model.constructor.util.CgComponents -import org.utbot.framework.codegen.model.constructor.util.classCgClassId -import org.utbot.framework.codegen.model.constructor.util.getAmbiguousOverloadsOf -import org.utbot.framework.codegen.model.constructor.util.importIfNeeded -import org.utbot.framework.codegen.model.constructor.util.typeCast -import org.utbot.framework.codegen.model.tree.CgAllocateArray -import org.utbot.framework.codegen.model.tree.CgAssignment -import org.utbot.framework.codegen.model.tree.CgConstructorCall -import org.utbot.framework.codegen.model.tree.CgExecutableCall -import org.utbot.framework.codegen.model.tree.CgExpression -import org.utbot.framework.codegen.model.tree.CgGetJavaClass -import org.utbot.framework.codegen.model.tree.CgMethodCall -import org.utbot.framework.codegen.model.tree.CgSpread -import org.utbot.framework.codegen.model.tree.CgStatement -import org.utbot.framework.codegen.model.tree.CgThisInstance -import org.utbot.framework.codegen.model.tree.CgValue -import org.utbot.framework.codegen.model.tree.CgVariable -import org.utbot.framework.codegen.model.util.at -import org.utbot.framework.codegen.model.util.isAccessibleFrom -import org.utbot.framework.codegen.model.util.nullLiteral -import org.utbot.framework.codegen.model.util.resolve -import org.utbot.framework.plugin.api.BuiltinMethodId -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.ConstructorId -import org.utbot.framework.plugin.api.ExecutableId -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.UtExplicitlyThrownException -import org.utbot.framework.plugin.api.util.exceptions -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.isArray -import org.utbot.framework.plugin.api.util.isPrimitive -import org.utbot.framework.plugin.api.util.isSubtypeOf -import org.utbot.framework.plugin.api.util.method -import org.utbot.framework.plugin.api.util.objectArrayClassId -import org.utbot.framework.plugin.api.util.objectClassId - -typealias Block = PersistentList - -class CgIncompleteMethodCall(val method: MethodId, val caller: CgExpression?) - -/** - * Provides DSL methods for method and field access elements creation - * - * Checks the accessibility of methods and fields and replaces - * direct access with reflective access when needed - */ -interface CgCallableAccessManager { - operator fun CgExpression?.get(methodId: MethodId): CgIncompleteMethodCall - - operator fun ClassId.get(staticMethodId: MethodId): CgIncompleteMethodCall - - operator fun ConstructorId.invoke(vararg args: Any?): CgExecutableCall - - operator fun CgIncompleteMethodCall.invoke(vararg args: Any?): CgMethodCall -} - -internal class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableAccessManager, - CgContextOwner by context { - - private val statementConstructor by lazy { CgComponents.getStatementConstructorBy(context) } - - private val variableConstructor by lazy { CgComponents.getVariableConstructorBy(context) } - - override operator fun CgExpression?.get(methodId: MethodId): CgIncompleteMethodCall = - CgIncompleteMethodCall(methodId, this) - - override operator fun ClassId.get(staticMethodId: MethodId): CgIncompleteMethodCall = - CgIncompleteMethodCall(staticMethodId, null) - - override operator fun ConstructorId.invoke(vararg args: Any?): CgExecutableCall { - val resolvedArgs = args.resolve() - val constructorCall = if (this canBeCalledWith resolvedArgs) { - CgConstructorCall(this, resolvedArgs.guardedForDirectCallOf(this)) - } else { - callWithReflection(resolvedArgs) - } - newConstructorCall(this) - return constructorCall - } - - override operator fun CgIncompleteMethodCall.invoke(vararg args: Any?): CgMethodCall { - val resolvedArgs = args.resolve() - val methodCall = if (method.canBeCalledWith(caller, resolvedArgs)) { - CgMethodCall(caller, method, resolvedArgs.guardedForDirectCallOf(method)) - } else { - method.callWithReflection(caller, resolvedArgs) - } - newMethodCall(method) - return methodCall - } - - private fun newMethodCall(methodId: MethodId) { - if (methodId.isUtil) requiredUtilMethods += methodId - importIfNeeded(methodId) - - //Builtin methods does not have jClass, so [methodId.method] will crash on it, - //so we need to collect required exceptions manually from source codes - if (methodId is BuiltinMethodId) { - methodId.findExceptionTypes().forEach { addException(it) } - return - } - //If [InvocationTargetException] is thrown manually in test, we need - // to add "throws Throwable" and other exceptions are not required so on. - if (methodId == getTargetException) { - collectedExceptions.clear() - addException(Throwable::class.id) - return - } - - val methodIsUnderTestAndThrowsExplicitly = methodId == currentExecutable - && currentExecution?.result is UtExplicitlyThrownException - val frameworkSupportsAssertThrows = testFramework == Junit5 || testFramework == TestNg - - //If explicit exception is wrapped with assertThrows, - // no "throws" in test method signature is required. - if (methodIsUnderTestAndThrowsExplicitly && frameworkSupportsAssertThrows) { - return - } - - methodId.method.exceptionTypes.forEach { addException(it.id) } - } - - private fun newConstructorCall(constructorId: ConstructorId) { - importIfNeeded(constructorId.classId) - for (exception in constructorId.exceptions) { - addException(exception) - } - } - - private fun BuiltinMethodId.findExceptionTypes(): Set { - if (!this.isUtil) return emptySet() - - with(currentTestClass) { - return when (this@findExceptionTypes) { - getEnumConstantByNameMethodId -> setOf(IllegalAccessException::class.id) - getStaticFieldValueMethodId, - getFieldValueMethodId, - setStaticFieldMethodId, - setFieldMethodId, - createInstanceMethodId, - getUnsafeInstanceMethodId -> setOf(Exception::class.id) - createArrayMethodId -> setOf(ClassNotFoundException::class.id) - deepEqualsMethodId, - arraysDeepEqualsMethodId, - iterablesDeepEqualsMethodId, - streamsDeepEqualsMethodId, - mapsDeepEqualsMethodId, - hasCustomEqualsMethodId, - getArrayLengthMethodId -> emptySet() - else -> error("Unknown util method $this") - } - } - } - - private infix fun CgExpression?.canBeReceiverOf(executable: MethodId): Boolean = - when { - // TODO: rewrite by using CgMethodId, etc. - currentTestClass == executable.classId && this isThisInstanceOf currentTestClass -> true - executable.isStatic -> true - else -> this?.type?.isSubtypeOf(executable.classId) ?: false - } - - private infix fun CgExpression.canBeArgOf(type: ClassId): Boolean { - // TODO: SAT-1210 support generics so that we wouldn't need to check specific cases such as this one - if (this is CgExecutableCall && (executableId == any || executableId == anyOfClass)) { - return true - } - return this == nullLiteral() && type.isAccessibleFrom(testClassPackageName) - || this.type isSubtypeOf type - } - - private infix fun CgExpression?.isThisInstanceOf(classId: ClassId): Boolean = - this is CgThisInstance && this.type == classId - - /** - * Check whether @receiver (list of expressions) is a valid list of arguments for [executableId] - * - * First, we check all arguments except for the last one. - * It is done to consider the last argument separately since it can be a vararg, - * which requires some additional checks. - * - * For the last argument there can be several cases: - * - Last argument is not of array type - then we simply check this argument as all the others - * - Last argument is of array type: - * - Given arguments and parameters have the same size - * - Last argument is an array and it matches last parameter array type - * - Last argument is a single element of a vararg parameter - then we check - * if argument's type matches the vararg element's type - * - Given arguments and parameters have different size (last parameter is vararg) - then we - * check if all of the given arguments match the vararg element's type - * - */ - private infix fun List.canBeArgsOf(executableId: ExecutableId): Boolean { - val paramTypes = executableId.parameters - - // no arguments case - if (paramTypes.isEmpty()) { - return this.isEmpty() - } - - val paramTypesExceptLast = paramTypes.dropLast(1) - val lastParamType = paramTypes.last() - - // considering all arguments except the last one - for ((arg, paramType) in (this zip paramTypesExceptLast)) { - if (!(arg canBeArgOf paramType)) return false - } - - // when the last parameter is not of array type - if (!lastParamType.isArray) { - val lastArg = this.last() - return lastArg canBeArgOf lastParamType - } - - // when arguments and parameters have equal size - if (size == paramTypes.size) { - val lastArg = this.last() - return when { - // last argument matches last param type - lastArg canBeArgOf lastParamType -> true - // last argument is a single element of a vararg parameter - lastArg canBeArgOf lastParamType.elementClassId!! -> true - else -> false - } - } - - // when arguments size is greater than the parameters size - // meaning that the last parameter is vararg - return subList(paramTypes.size - 1, size).all { - it canBeArgOf lastParamType.elementClassId!! - } - } - - /** - * @return true if a method can be called with the given arguments without reflection - */ - private fun MethodId.canBeCalledWith(caller: CgExpression?, args: List): Boolean = - (isUtil || isAccessibleFrom(testClassPackageName)) - && caller canBeReceiverOf this - && args canBeArgsOf this - - /** - * @return true if a constructor can be called with the given arguments without reflection - */ - private infix fun ConstructorId.canBeCalledWith(args: List): Boolean = - isAccessibleFrom(testClassPackageName) && !classId.isAbstract && args canBeArgsOf this - - private fun List.guardedForDirectCallOf(executable: ExecutableId): List { - val ambiguousOverloads = executable.classId - .getAmbiguousOverloadsOf(executable) - .filterNot { it == executable } - .toList() - - val isEmptyAmbiguousOverloads = ambiguousOverloads.isEmpty() - - return if (isEmptyAmbiguousOverloads) this else castAmbiguousArguments(executable, this, ambiguousOverloads) - } - - private fun castAmbiguousArguments( - executable: ExecutableId, - args: List, - ambiguousOverloads: List - ): List = - args.withIndex().map { (i ,arg) -> - val targetType = executable.parameters[i] - - // always cast nulls - if (arg == nullLiteral()) return@map typeCast(targetType, arg) - - // in case arg type exactly equals target type, do nothing - if (arg.type == targetType) return@map arg - - // arg type is subtype of target type - // check other overloads for ambiguous types - val typesInOverloadings = ambiguousOverloads.map { it.parameters[i] } - val ancestors = typesInOverloadings.filter { arg.type.isSubtypeOf(it) } - - if (ancestors.isNotEmpty()) typeCast(targetType, arg) else arg - } - - private fun ExecutableId.toExecutableVariable(args: List): CgVariable { - val declaringClass = statementConstructor.newVar(Class::class.id) { classId[forName](classId.name) } - val argTypes = (args zip parameters).map { (arg, paramType) -> - val baseName = when (arg) { - is CgVariable -> "${arg.name}Type" - else -> "${paramType.prettifiedName.decapitalize()}Type" - } - statementConstructor.newVar(classCgClassId, baseName) { - if (paramType.isPrimitive) { - CgGetJavaClass(paramType) - } else { - Class::class.id[forName](paramType.name) - } - } - } - - return when (this) { - is MethodId -> { - val name = this.name + "Method" - statementConstructor.newVar(java.lang.reflect.Method::class.id, name) { - declaringClass[getDeclaredMethod](this.name, *argTypes.toTypedArray()) - } - } - is ConstructorId -> { - val name = this.classId.prettifiedName.decapitalize() + "Constructor" - statementConstructor.newVar(java.lang.reflect.Constructor::class.id, name) { - declaringClass[getDeclaredConstructor](*argTypes.toTypedArray()) - } - } - } - } - - /** - * Receives a list of [CgExpression]. - * Transforms it into a list of [CgExpression] where: - * - array and literal values are cast to [java.lang.Object] - * - other values remain as they were - * - * @return a list of [CgExpression] where each expression can be - * used as an argument of reflective call to a method or constructor - */ - private fun List.guardedForReflectiveCall(): List = - map { - when { - it is CgValue && it.type.isArray -> typeCast(objectClassId, it) - it == nullLiteral() -> typeCast(objectClassId, it) - else -> it - } - } - - private fun MethodId.callWithReflection(caller: CgExpression?, args: List): CgMethodCall { - val method = declaredExecutableRefs[this] - ?: toExecutableVariable(args).also { - declaredExecutableRefs = declaredExecutableRefs.put(this, it) - +it[setAccessible](true) - } - - val arguments = args.guardedForReflectiveCall().toTypedArray() - val argumentsArrayVariable = convertVarargToArray(method, arguments) - - return method[invoke](caller, CgSpread(argumentsArrayVariable.type, argumentsArrayVariable)) - } - - private fun ConstructorId.callWithReflection(args: List): CgExecutableCall { - val constructor = declaredExecutableRefs[this] - ?: this.toExecutableVariable(args).also { - declaredExecutableRefs = declaredExecutableRefs.put(this, it) - +it[setAccessible](true) - } - - val arguments = args.guardedForReflectiveCall().toTypedArray() - val argumentsArrayVariable = convertVarargToArray(constructor, arguments) - - return constructor[newInstance](argumentsArrayVariable) - } - - private fun convertVarargToArray(reflectionCallVariable: CgVariable, arguments: Array): CgVariable { - val argumentsArrayVariable = variableConstructor.newVar( - baseType = objectArrayClassId, - baseName = "${reflectionCallVariable.name}Arguments" - ) { - CgAllocateArray( - type = objectArrayClassId, - elementType = objectClassId, - size = arguments.size - ) - } - - for ((i, argument) in arguments.withIndex()) { - +CgAssignment(argumentsArrayVariable.at(i), argument) - } - - return argumentsArrayVariable - } -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt deleted file mode 100644 index 570dbe3a6f..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgFieldStateManager.kt +++ /dev/null @@ -1,253 +0,0 @@ -package org.utbot.framework.codegen.model.constructor.tree - -import org.utbot.framework.codegen.model.constructor.builtin.forName -import org.utbot.framework.codegen.model.constructor.builtin.getArrayElement -import org.utbot.framework.codegen.model.constructor.context.CgContext -import org.utbot.framework.codegen.model.constructor.context.CgContextOwner -import org.utbot.framework.codegen.model.constructor.util.CgComponents -import org.utbot.framework.codegen.model.constructor.util.CgFieldState -import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructor -import org.utbot.framework.codegen.model.constructor.util.FieldStateCache -import org.utbot.framework.codegen.model.constructor.util.classCgClassId -import org.utbot.framework.codegen.model.constructor.util.getFieldVariableName -import org.utbot.framework.codegen.model.constructor.util.getStaticFieldVariableName -import org.utbot.framework.codegen.model.constructor.util.needExpectedDeclaration -import org.utbot.framework.codegen.model.tree.CgExpression -import org.utbot.framework.codegen.model.tree.CgGetJavaClass -import org.utbot.framework.codegen.model.tree.CgValue -import org.utbot.framework.codegen.model.tree.CgVariable -import org.utbot.framework.codegen.model.util.at -import org.utbot.framework.codegen.model.util.get -import org.utbot.framework.codegen.model.util.isAccessibleFrom -import org.utbot.framework.codegen.model.util.stringLiteral -import org.utbot.framework.fields.ArrayElementAccess -import org.utbot.framework.fields.FieldAccess -import org.utbot.framework.fields.FieldPath -import org.utbot.framework.fields.ModifiedFields -import org.utbot.framework.fields.StateModificationInfo -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.util.hasField -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.isArray -import org.utbot.framework.plugin.api.util.isRefType -import org.utbot.framework.plugin.api.util.objectClassId -import org.utbot.framework.util.hasThisInstance -import java.lang.reflect.Array - -internal interface CgFieldStateManager { - fun rememberInitialEnvironmentState(info: StateModificationInfo) - fun rememberFinalEnvironmentState(info: StateModificationInfo) -} - -internal class CgFieldStateManagerImpl(val context: CgContext) - : CgContextOwner by context, - CgFieldStateManager, - CgCallableAccessManager by CgComponents.getCallableAccessManagerBy(context), - CgStatementConstructor by CgComponents.getStatementConstructorBy(context) { - - override fun rememberInitialEnvironmentState(info: StateModificationInfo) { - rememberThisInstanceState(info, FieldState.INITIAL) - rememberArgumentsState(info, FieldState.INITIAL) - rememberStaticFieldsState(info, FieldState.INITIAL) - } - - override fun rememberFinalEnvironmentState(info: StateModificationInfo) { - rememberThisInstanceState(info, FieldState.FINAL) - rememberArgumentsState(info, FieldState.FINAL) - rememberStaticFieldsState(info, FieldState.FINAL) - } - - /** - * [variablePrefix] is used as a name prefix for variables - * that contain the initial or final states of some fields. - */ - private enum class FieldState(val variablePrefix: String) { - INITIAL("initial"), - FINAL("final") - } - - private fun rememberThisInstanceState(info: StateModificationInfo, state: FieldState) { - if (!currentExecution!!.hasThisInstance()) { - return - } - val thisInstance = context.thisInstance!! - val modifiedFields = info.thisInstance - // by now this instance variable must have already been created - saveFieldsState(thisInstance, modifiedFields, statesCache.thisInstance, state) - } - - private fun rememberArgumentsState(info: StateModificationInfo, state: FieldState) { - // by now variables of all arguments must have already been created - for ((i, argument) in context.methodArguments.withIndex()) { - if (i > info.parameters.lastIndex) break - - // TODO no one understands what is going on here; need to rewrite it one day or add docs - val modifiedFields = info.parameters[i] - saveFieldsState(argument, modifiedFields, statesCache.arguments[i], state) - } - } - - private fun rememberStaticFieldsState(info: StateModificationInfo, state: FieldState) { - for ((classId, modifiedStaticFields) in info.staticFields) { - saveStaticFieldsState(classId, modifiedStaticFields, state) - statesCache.classesWithStaticFields[classId]!!.before - } - } - - private fun getStaticFieldStateVariableName(owner: ClassId, path: FieldPath, state: FieldState): String = - state.variablePrefix + getStaticFieldVariableName(owner, path).capitalize() - - private fun getFieldStateVariableName(owner: CgValue, path: FieldPath, state: FieldState): String = - state.variablePrefix + getFieldVariableName(owner, path).capitalize() - - /** - * For initial state we only need to create variables for ref type field values, - * because in order to assert inequality of ref type fields we need both initial - * and final state variables. - * - * On the contrary, when the field is not of ref type, - * then we will only need its final state and the assertion will make sure - * that this final state is equal to its expected value. - * - * Assertion examples: - * - ref type fields: - * - * `assertFalse(initialState == finalState);` - * - * - non-ref type fields: - * - * `assertEquals(5, finalState);` - */ - private fun saveFieldsState( - owner: CgValue, - modifiedFields: ModifiedFields, - cache: FieldStateCache, - state: FieldState - ) { - if (modifiedFields.isEmpty()) return - emptyLineIfNeeded() - val fields = when (state) { - FieldState.INITIAL -> modifiedFields - .filter { it.path.elements.isNotEmpty() && it.path.fieldType.isRefType } - .filter { needExpectedDeclaration(it.after) } - FieldState.FINAL -> modifiedFields - } - for ((path, before, after) in fields) { - val customName = getFieldStateVariableName(owner, path, state) - val variable = variableForFieldState(owner, path, customName) - when (state) { - FieldState.INITIAL -> cache.before[path] = CgFieldState(variable, before) - FieldState.FINAL -> cache.after[path] = CgFieldState(variable, after) - } - } - } - - private fun saveStaticFieldsState( - owner: ClassId, - modifiedFields: ModifiedFields, - state: FieldState - ) { - if (modifiedFields.isNotEmpty()) { - emptyLineIfNeeded() - } - for (modifiedField in modifiedFields) { - val (path, before, after) = modifiedField - val customName = getStaticFieldStateVariableName(owner, path, state) - val variable = variableForStaticFieldState(owner, path, customName) - val cache = statesCache.classesWithStaticFields[owner]!! - when (state) { - FieldState.INITIAL -> cache.before[path] = CgFieldState(variable, before) - FieldState.FINAL -> cache.after[path] = CgFieldState(variable, after) - } - } - } - - private fun CgExpression.getFieldBy(fieldPath: FieldPath, customName: String? = null): CgVariable { - val path = fieldPath.elements - var lastAccessibleIndex = path.lastIndex - - // type of current accessed element, starting from current expression and followed by elements from path - var curType = type - for ((index, fieldPathElement) in path.withIndex()) { - when (fieldPathElement) { - is FieldAccess -> { - if (!fieldPathElement.field.isAccessibleFrom(testClassPackageName)) { - lastAccessibleIndex = index - 1 - break - } - - // if previous field has type that does not have current field, this field is inaccessible - if (index > 0 && !path[index - 1].type.hasField(fieldPathElement.field.name)) { - lastAccessibleIndex = index - 1 - break - } - } - is ArrayElementAccess -> { - // cannot use direct array access from not array type - if (!curType.isArray) { - lastAccessibleIndex = index - 1 - break - } - } - } - - curType = fieldPathElement.type - } - - var index = 0 - var currentFieldType = this.type - - val lastPublicAccessor = generateSequence(this) { prev -> - if (index > lastAccessibleIndex) return@generateSequence null - val newElement = path[index++] - currentFieldType = newElement.type - when (newElement) { - is FieldAccess -> prev[newElement.field] - is ArrayElementAccess -> prev.at(newElement.index) - } - }.last() - - if (index == path.size) { - return newVar(currentFieldType, customName) { lastPublicAccessor } - } - - val lastPublicFieldVariable = lastPublicAccessor as? CgVariable ?: newVar(currentFieldType) { lastPublicAccessor } - return generateSequence(lastPublicFieldVariable) { prev -> - if (index > path.lastIndex) return@generateSequence null - val passedPath = FieldPath(path.subList(0, index + 1)) - val name = if (index == path.lastIndex) customName else getFieldVariableName(prev, passedPath) - val expression = when (val newElement = path[index++]) { - is FieldAccess -> { - val field = newElement.field - testClassThisInstance[getFieldValue](prev, stringLiteral(field.name)) - } - is ArrayElementAccess -> { - Array::class.id[getArrayElement](prev, newElement.index) - } - } - newVar(objectClassId, name) { expression } - }.last() - } - - private fun variableForFieldState(owner: CgValue, fieldPath: FieldPath, customName: String? = null): CgVariable { - return owner.getFieldBy(fieldPath, customName) - } - - private fun variableForStaticFieldState(owner: ClassId, fieldPath: FieldPath, customName: String?): CgVariable { - val firstField = (fieldPath.elements.first() as FieldAccess).field - val firstAccessor = if (owner.isAccessibleFrom(testClassPackageName) && firstField.isAccessibleFrom(testClassPackageName)) { - owner[firstField] - } else { - // TODO: there is a function getClassOf() for these purposes, but it is not accessible from here for now - val ownerClass = if (owner isAccessibleFrom testClassPackageName) { - CgGetJavaClass(owner) - } else { - newVar(classCgClassId) { Class::class.id[forName](owner.name) } - } - newVar(objectClassId) { testClassThisInstance[getStaticFieldValue](ownerClass, stringLiteral(firstField.name)) } - } - val path = fieldPath.elements - val remainingPath = fieldPath.copy(elements = path.drop(1)) - return firstAccessor.getFieldBy(remainingPath, customName) - } -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt deleted file mode 100644 index 57ea4d2ffb..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgMethodConstructor.kt +++ /dev/null @@ -1,1694 +0,0 @@ -package org.utbot.framework.codegen.model.constructor.tree - -import org.utbot.common.PathUtil -import org.utbot.common.packageName -import org.utbot.engine.isStatic -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.JUNIT5_PARAMETERIZED_PACKAGE -import org.utbot.framework.codegen.Junit4 -import org.utbot.framework.codegen.Junit5 -import org.utbot.framework.codegen.ParametrizedTestSource -import org.utbot.framework.codegen.RuntimeExceptionTestsBehaviour.PASS -import org.utbot.framework.codegen.TestNg -import org.utbot.framework.codegen.model.constructor.builtin.closeMethodIdOrNull -import org.utbot.framework.codegen.model.constructor.builtin.forName -import org.utbot.framework.codegen.model.constructor.builtin.getClass -import org.utbot.framework.codegen.model.constructor.builtin.getTargetException -import org.utbot.framework.codegen.model.constructor.builtin.invoke -import org.utbot.framework.codegen.model.constructor.builtin.newInstance -import org.utbot.framework.codegen.model.constructor.builtin.setArrayElement -import org.utbot.framework.codegen.model.constructor.context.CgContext -import org.utbot.framework.codegen.model.constructor.context.CgContextOwner -import org.utbot.framework.codegen.model.constructor.util.CgComponents -import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructor -import org.utbot.framework.codegen.model.constructor.util.EnvironmentFieldStateCache -import org.utbot.framework.codegen.model.constructor.util.FieldStateCache -import org.utbot.framework.codegen.model.constructor.util.classCgClassId -import org.utbot.framework.codegen.model.constructor.util.needExpectedDeclaration -import org.utbot.framework.codegen.model.constructor.util.overridesEquals -import org.utbot.framework.codegen.model.constructor.util.typeCast -import org.utbot.framework.codegen.model.tree.CgAllocateArray -import org.utbot.framework.codegen.model.tree.CgAnnotation -import org.utbot.framework.codegen.model.tree.CgArrayElementAccess -import org.utbot.framework.codegen.model.tree.CgAssignment -import org.utbot.framework.codegen.model.tree.CgClassId -import org.utbot.framework.codegen.model.tree.CgConstructorCall -import org.utbot.framework.codegen.model.tree.CgDeclaration -import org.utbot.framework.codegen.model.tree.CgDocPreTagStatement -import org.utbot.framework.codegen.model.tree.CgDocRegularStmt -import org.utbot.framework.codegen.model.tree.CgDocumentationComment -import org.utbot.framework.codegen.model.tree.CgEmptyLine -import org.utbot.framework.codegen.model.tree.CgEqualTo -import org.utbot.framework.codegen.model.tree.CgErrorTestMethod -import org.utbot.framework.codegen.model.tree.CgExecutableCall -import org.utbot.framework.codegen.model.tree.CgExpression -import org.utbot.framework.codegen.model.tree.CgFieldAccess -import org.utbot.framework.codegen.model.tree.CgGetJavaClass -import org.utbot.framework.codegen.model.tree.CgIfStatement -import org.utbot.framework.codegen.model.tree.CgLiteral -import org.utbot.framework.codegen.model.tree.CgMethod -import org.utbot.framework.codegen.model.tree.CgMethodCall -import org.utbot.framework.codegen.model.tree.CgMultilineComment -import org.utbot.framework.codegen.model.tree.CgParameterDeclaration -import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod -import org.utbot.framework.codegen.model.tree.CgRegion -import org.utbot.framework.codegen.model.tree.CgReturnStatement -import org.utbot.framework.codegen.model.tree.CgSimpleRegion -import org.utbot.framework.codegen.model.tree.CgSingleLineComment -import org.utbot.framework.codegen.model.tree.CgStatement -import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall -import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess -import org.utbot.framework.codegen.model.tree.CgTestMethod -import org.utbot.framework.codegen.model.tree.CgTestMethodType -import org.utbot.framework.codegen.model.tree.CgTestMethodType.CRASH -import org.utbot.framework.codegen.model.tree.CgTestMethodType.FAILING -import org.utbot.framework.codegen.model.tree.CgTestMethodType.PARAMETRIZED -import org.utbot.framework.codegen.model.tree.CgTestMethodType.SUCCESSFUL -import org.utbot.framework.codegen.model.tree.CgTestMethodType.TIMEOUT -import org.utbot.framework.codegen.model.tree.CgTryCatch -import org.utbot.framework.codegen.model.tree.CgTypeCast -import org.utbot.framework.codegen.model.tree.CgValue -import org.utbot.framework.codegen.model.tree.CgVariable -import org.utbot.framework.codegen.model.tree.buildForLoop -import org.utbot.framework.codegen.model.tree.buildParameterizedTestDataProviderMethod -import org.utbot.framework.codegen.model.tree.buildTestMethod -import org.utbot.framework.codegen.model.tree.convertDocToCg -import org.utbot.framework.codegen.model.tree.toStatement -import org.utbot.framework.codegen.model.util.at -import org.utbot.framework.codegen.model.util.canBeSetIn -import org.utbot.framework.codegen.model.util.equalTo -import org.utbot.framework.codegen.model.util.get -import org.utbot.framework.codegen.model.util.inc -import org.utbot.framework.codegen.model.util.isAccessibleFrom -import org.utbot.framework.codegen.model.util.isInaccessible -import org.utbot.framework.codegen.model.util.length -import org.utbot.framework.codegen.model.util.lessThan -import org.utbot.framework.codegen.model.util.nullLiteral -import org.utbot.framework.codegen.model.util.resolve -import org.utbot.framework.codegen.model.util.stringLiteral -import org.utbot.framework.fields.ExecutionStateAnalyzer -import org.utbot.framework.fields.FieldPath -import org.utbot.framework.plugin.api.BuiltinClassId -import org.utbot.framework.plugin.api.BuiltinMethodId -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.ConcreteExecutionFailureException -import org.utbot.framework.plugin.api.ConstructorId -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.TimeoutException -import org.utbot.framework.plugin.api.TypeParameters -import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtClassRefModel -import org.utbot.framework.plugin.api.UtCompositeModel -import org.utbot.framework.plugin.api.UtConcreteExecutionFailure -import org.utbot.framework.plugin.api.UtDirectSetFieldModel -import org.utbot.framework.plugin.api.UtEnumConstantModel -import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtExecutionFailure -import org.utbot.framework.plugin.api.UtExecutionSuccess -import org.utbot.framework.plugin.api.UtExplicitlyThrownException -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation -import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtReferenceModel -import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.framework.plugin.api.UtTimeoutException -import org.utbot.framework.plugin.api.UtVoidModel -import org.utbot.framework.plugin.api.onFailure -import org.utbot.framework.plugin.api.onSuccess -import org.utbot.framework.plugin.api.util.booleanClassId -import org.utbot.framework.plugin.api.util.booleanWrapperClassId -import org.utbot.framework.plugin.api.util.byteClassId -import org.utbot.framework.plugin.api.util.byteWrapperClassId -import org.utbot.framework.plugin.api.util.charClassId -import org.utbot.framework.plugin.api.util.charWrapperClassId -import org.utbot.framework.plugin.api.util.doubleArrayClassId -import org.utbot.framework.plugin.api.util.doubleClassId -import org.utbot.framework.plugin.api.util.doubleWrapperClassId -import org.utbot.framework.plugin.api.util.field -import org.utbot.framework.plugin.api.util.floatArrayClassId -import org.utbot.framework.plugin.api.util.floatClassId -import org.utbot.framework.plugin.api.util.floatWrapperClassId -import org.utbot.framework.plugin.api.util.hasField -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.intClassId -import org.utbot.framework.plugin.api.util.intWrapperClassId -import org.utbot.framework.plugin.api.util.isArray -import org.utbot.framework.plugin.api.util.isInnerClassEnclosingClassReference -import org.utbot.framework.plugin.api.util.isIterableOrMap -import org.utbot.framework.plugin.api.util.isPrimitive -import org.utbot.framework.plugin.api.util.isPrimitiveArray -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.kClass -import org.utbot.framework.plugin.api.util.longClassId -import org.utbot.framework.plugin.api.util.longWrapperClassId -import org.utbot.framework.plugin.api.util.methodId -import org.utbot.framework.plugin.api.util.objectArrayClassId -import org.utbot.framework.plugin.api.util.objectClassId -import org.utbot.framework.plugin.api.util.shortClassId -import org.utbot.framework.plugin.api.util.shortWrapperClassId -import org.utbot.framework.plugin.api.util.stringClassId -import org.utbot.framework.plugin.api.util.voidClassId -import org.utbot.framework.util.isUnit -import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl -import java.lang.reflect.InvocationTargetException -import kotlin.reflect.jvm.javaType - -private const val DEEP_EQUALS_MAX_DEPTH = 5 // TODO move it to plugin settings? - -internal class CgMethodConstructor(val context: CgContext) : CgContextOwner by context, - CgFieldStateManager by CgComponents.getFieldStateManagerBy(context), - CgCallableAccessManager by CgComponents.getCallableAccessManagerBy(context), - CgStatementConstructor by CgComponents.getStatementConstructorBy(context) { - - private val nameGenerator = CgComponents.getNameGeneratorBy(context) - private val testFrameworkManager = CgComponents.getTestFrameworkManagerBy(context) - - private val variableConstructor = CgComponents.getVariableConstructorBy(context) - private val mockFrameworkManager = CgComponents.getMockFrameworkManagerBy(context) - - private val floatDelta: Float = 1e-6f - private val doubleDelta = 1e-6 - - // a model for execution result (it is lateinit because execution can fail, - // and we need it only on assertions generation stage - private lateinit var resultModel: UtModel - - private lateinit var methodType: CgTestMethodType - - private fun setupInstrumentation() { - val instrumentation = currentExecution!!.instrumentation - if (instrumentation.isEmpty()) return - - if (generateWarningsForStaticMocking && forceStaticMocking == ForceStaticMocking.DO_NOT_FORCE) { - // warn user about possible flaky tests - multilineComment(forceStaticMocking.warningMessage) - return - } - - instrumentation - .filterIsInstance() - .forEach { mockFrameworkManager.mockNewInstance(it) } - instrumentation - .filterIsInstance() - .groupBy { it.methodId.classId } - .forEach { (classId, methodMocks) -> mockFrameworkManager.mockStaticMethodsOfClass(classId, methodMocks) } - - if (generateWarningsForStaticMocking && forceStaticMocking == ForceStaticMocking.FORCE) { - // warn user about forced using static mocks - multilineComment(forceStaticMocking.warningMessage) - } - } - - /** - * Create variables for initial values of the static fields and store them in [prevStaticFieldValues] - * in order to use these variables at the end of the test to restore the initial static fields state. - * - * Note: - * Later in the test method we also cache the 'before' and 'after' states of fields (including static fields). - * This cache is stored in [statesCache]. - * - * However, it is _not_ the same cache as [prevStaticFieldValues]. - * The [statesCache] should not be confused with [prevStaticFieldValues] cache. - * - * The difference is that [prevStaticFieldValues] contains the static field states before we made _any_ changes. - * On the other hand, [statesCache] contains 'before' and 'after' states where the 'before' state is - * the state that we _specifically_ set up in order to cover a certain branch. - * - * Thus, this method only caches an actual initial static fields state in order to recover it - * at the end of the test, and it has nothing to do with the 'before' and 'after' caches. - */ - private fun rememberInitialStaticFields() { - for ((field, _) in currentExecution!!.stateBefore.statics.accessibleFields()) { - val declaringClass = field.declaringClass - val fieldAccessible = field.isAccessibleFrom(testClassPackageName) - - // prevValue is nullable if not accessible because of getStaticFieldValue(..) : Any? - val prevValue = newVar(CgClassId(field.type, isNullable = !fieldAccessible), - "prev${field.name.capitalize()}") { - if (fieldAccessible) { - declaringClass[field] - } else { - val declaringClassVar = newVar(classCgClassId) { - Class::class.id[forName](declaringClass.name) - } - testClassThisInstance[getStaticFieldValue](declaringClassVar, field.name) - } - } - // remember the previous value of a static field to recover it at the end of the test - prevStaticFieldValues[field] = prevValue - } - } - - private fun mockStaticFields() { - for ((field, model) in currentExecution!!.stateBefore.statics.accessibleFields()) { - val declaringClass = field.declaringClass - val fieldAccessible = field.canBeSetIn(testClassPackageName) - val fieldValue = variableConstructor.getOrCreateVariable(model, field.name) - if (fieldAccessible) { - declaringClass[field] `=` fieldValue - } else { - val declaringClassVar = newVar(classCgClassId) { - Class::class.id[forName](declaringClass.name) - } - +testClassThisInstance[setStaticField](declaringClassVar, field.name, fieldValue) - } - } - } - - private fun recoverStaticFields() { - for ((field, prevValue) in prevStaticFieldValues.accessibleFields()) { - if (field.canBeSetIn(testClassPackageName)) { - field.declaringClass[field] `=` prevValue - } else { - val declaringClass = getClassOf(field.declaringClass) - +testClassThisInstance[setStaticField](declaringClass, field.name, prevValue) - } - } - } - - private fun Map.accessibleFields(): Map = filterKeys { !it.isInaccessible } - - /** - * @return expression for [java.lang.Class] of the given [classId] - */ - // TODO: move this method somewhere, because now it duplicates the identical method from MockFrameworkManager - private fun getClassOf(classId: ClassId): CgExpression = - if (classId isAccessibleFrom testClassPackageName) { - CgGetJavaClass(classId) - } else { - newVar(classCgClassId) { Class::class.id[forName](classId.name) } - } - - /** - * Generates result assertions for unit tests. - */ - private fun generateResultAssertions() { - when (currentExecutable) { - is ConstructorId -> { - // we cannot generate any assertions for constructor testing - // but we need to generate a constructor call - val constructorCall = currentExecutable as ConstructorId - val currentExecution = currentExecution!! - currentExecution.result - .onSuccess { - methodType = SUCCESSFUL - - // TODO engine returns UtCompositeModel sometimes (concrete execution?) - - // TODO support inner classes constructors testing JIRA:1461 - require(!constructorCall.classId.isInner) { - "Inner class ${constructorCall.classId} constructor testing is not supported yet" - } - - actual = newVar(constructorCall.classId, "actual") { - constructorCall(*methodArguments.toTypedArray()) - } - } - .onFailure { exception -> - processExecutionFailure(currentExecution, exception) - } - } - is BuiltinMethodId -> error("Unexpected BuiltinMethodId $currentExecutable while generating result assertions") - is MethodId -> { - emptyLineIfNeeded() - val method = currentExecutable as MethodId - val currentExecution = currentExecution!! - // build assertions - currentExecution.result - .onSuccess { result -> - methodType = SUCCESSFUL - - // TODO possible engine bug - void method return type and result not UtVoidModel - if (result.isUnit() || method.returnType == voidClassId) { - +thisInstance[method](*methodArguments.toTypedArray()) - } else { - resultModel = result - val expected = variableConstructor.getOrCreateVariable(result, "expected") - assertEquality(expected, actual) - } - } - .onFailure { exception -> - processExecutionFailure(currentExecution, exception) - } - } - } - } - - private fun processExecutionFailure(execution: UtExecution, exception: Throwable) { - val methodInvocationBlock = { - with(currentExecutable) { - when (this) { - is MethodId -> thisInstance[this](*methodArguments.toTypedArray()).intercepted() - is ConstructorId -> this(*methodArguments.toTypedArray()).intercepted() - } - } - } - - if (shouldTestPassWithException(execution, exception)) { - testFrameworkManager.expectException(exception::class.id) { - methodInvocationBlock() - } - methodType = SUCCESSFUL - - return - } - - when (exception) { - is TimeoutException -> { - methodType = TIMEOUT - writeWarningAboutTimeoutExceeding() - } - is ConcreteExecutionFailureException -> { - methodType = CRASH - writeWarningAboutCrash() - } - else -> { - methodType = FAILING - writeWarningAboutFailureTest(exception) - } - } - - methodInvocationBlock() - } - - private fun shouldTestPassWithException(execution: UtExecution, exception: Throwable): Boolean { - // tests with timeout or crash should be processed differently - if (exception is TimeoutException || exception is ConcreteExecutionFailureException) return false - - val exceptionRequiresAssert = exception !is RuntimeException || runtimeExceptionTestsBehaviour == PASS - val exceptionIsExplicit = execution.result is UtExplicitlyThrownException - return exceptionRequiresAssert || exceptionIsExplicit - } - - private fun writeWarningAboutTimeoutExceeding() { - +CgMultilineComment( - listOf( - "This execution may take longer than the ${hangingTestsTimeout.timeoutMs} ms timeout", - " and therefore fail due to exceeding the timeout." - ) - ) - } - - private fun writeWarningAboutFailureTest(exception: Throwable) { - +CgMultilineComment( - listOf( - "This test fails because executable under testing $currentExecutable", - "produces Runtime exception $exception", - ) - // TODO add stacktrace JIRA:1644 - ) - } - - private fun writeWarningAboutCrash() { - +CgSingleLineComment("This invocation possibly crashes JVM") - } - - /** - * Generates result assertions in parameterized tests for successful executions - * and just runs the method if all executions are unsuccessful. - */ - private fun generateAssertionsForParameterizedTest() { - emptyLineIfNeeded() - val method = currentExecutable as MethodId - currentExecution!!.result - .onSuccess { result -> - if (result.isUnit()) { - +thisInstance[method](*methodArguments.toTypedArray()) - } else { - resultModel = result - - val expected = variableConstructor.parameterizedVariable( - result.classId, - expectedResultVarName, - isNotNull = true - ) - - val actualVariableName = when (codegenLanguage) { - CodegenLanguage.JAVA -> when (method.returnType) { - intClassId -> "${intWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - shortClassId -> "${shortWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - longClassId -> "${longWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - byteClassId -> "${byteWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - booleanClassId -> "${booleanWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - charClassId -> "${intWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - floatClassId -> "${floatWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - doubleWrapperClassId -> "${doubleWrapperClassId.simpleName.capitalize()}.valueOf(actual)" - else -> "actual" - } - CodegenLanguage.KOTLIN -> "actual" - } - - assertEquality(expected, CgVariable(actualVariableName, method.returnType)) - } - } - .onFailure { thisInstance[method](*methodArguments.toTypedArray()).intercepted() } - } - - /** - * Generates assertions for field states. - * - * Note: not supported in parameterized tests. - */ - private fun generateFieldStateAssertions() { - val thisInstanceCache = statesCache.thisInstance - for (path in thisInstanceCache.paths) { - assertStatesByPath(thisInstanceCache, path) - } - for (argumentCache in statesCache.arguments) { - for (path in argumentCache.paths) { - assertStatesByPath(argumentCache, path) - } - } - for ((_, staticFieldCache) in statesCache.classesWithStaticFields) { - for (path in staticFieldCache.paths) { - assertStatesByPath(staticFieldCache, path) - } - } - } - - /** - * If the given field is _not_ of reference type, then the variable for its 'before' state - * is not created, because we only need its final state to make an assertion. - * For reference type fields, in turn, we make an assertion assertFalse(before == after). - */ - private fun assertStatesByPath(cache: FieldStateCache, path: FieldPath) { - emptyLineIfNeeded() - val beforeVariable = cache.before[path]?.variable - val (afterVariable, afterModel) = cache.after[path]!! - - if (afterModel !is UtReferenceModel) { - val expectedAfter = - variableConstructor.getOrCreateVariable(afterModel, "expected" + afterVariable.name.capitalize()) - assertEquality(expectedAfter, afterVariable) - } else { - if (beforeVariable != null) - testFrameworkManager.assertBoolean(false, beforeVariable equalTo afterVariable) - // TODO: fail here - } - } - - private fun assertDeepEquals( - expectedModel: UtModel, - expected: CgVariable?, - actual: CgVariable, - statements: MutableList, - depth: Int, - visitedModels: MutableSet, - ) { - if (expectedModel in visitedModels) return - - var expected = expected - if (expected == null) { - require(!needExpectedDeclaration(expectedModel)) - expected = actual - } - - visitedModels += expectedModel - - with(testFrameworkManager) { - if (depth >= DEEP_EQUALS_MAX_DEPTH) { - statements += CgSingleLineComment("Current deep equals depth exceeds max depth $DEEP_EQUALS_MAX_DEPTH") - statements += getDeepEqualsAssertion(expected, actual).toStatement() - return - } - - when (expectedModel) { - is UtPrimitiveModel -> { - statements += when { - (expected.type == floatClassId || expected.type == floatWrapperClassId) -> - assertions[assertFloatEquals]( // cast have to be not safe here because of signature - typeCast(floatClassId, expected, isSafetyCast = false), - typeCast(floatClassId, actual, isSafetyCast = false), - floatDelta - ) - (expected.type == doubleClassId || expected.type == doubleWrapperClassId) -> - assertions[assertDoubleEquals]( // cast have to be not safe here because of signature - typeCast(doubleClassId, expected, isSafetyCast = false), - typeCast(doubleClassId, actual, isSafetyCast = false), - doubleDelta - ) - expectedModel.value is Boolean -> { - when (parameterizedTestSource) { - ParametrizedTestSource.DO_NOT_PARAMETRIZE -> - if (expectedModel.value as Boolean) { - assertions[assertTrue](actual) - } else { - assertions[assertFalse](actual) - } - ParametrizedTestSource.PARAMETRIZE -> - assertions[assertEquals](expected, actual) - } - } - // other primitives and string - else -> { - require(expected.type.isPrimitive || expected.type == String::class.java) { - "Expected primitive or String but got ${expected.type}" - } - assertions[assertEquals](expected, actual) - } - }.toStatement() - } - is UtEnumConstantModel -> { - statements += assertions[assertEquals]( - expected, - actual - ).toStatement() - } - is UtClassRefModel -> { - // TODO this stuff is needed because Kotlin has javaclass property instead of Java getClass method - // probably it is better to change getClass method behaviour in the future - val actualObject: CgVariable - if (codegenLanguage == CodegenLanguage.KOTLIN) { - val actualCastedToObject = CgDeclaration( - objectClassId, - variableConstructor.constructVarName("actualObject"), - CgTypeCast(objectClassId, actual) - ) - statements += actualCastedToObject - actualObject = actualCastedToObject.variable - } else { - actualObject = actual - } - - statements += assertions[assertEquals]( - CgGetJavaClass(expected.type), - actualObject[getClass]() - ).toStatement() - } - is UtNullModel -> { - statements += assertions[assertNull](actual).toStatement() - } - is UtArrayModel -> { - val arrayInfo = expectedModel.collectArrayInfo() - val nestedElementClassId = arrayInfo.nestedElementClassId - ?: error("Expected element class id from array ${arrayInfo.classId} but null found") - - if (!arrayInfo.isPrimitiveArray) { - // array of objects, have to use deep equals - - // We can't use for loop here because array model can contain different models - // and there is no a general approach to process it in loop - // For example, actual can be Object[3] and - // actual[0] instance of Point[][] - // actual[1] instance of int[][][] - // actual[2] instance of Object[] - - addArraysLengthAssertion(expected, actual, statements) - statements += getDeepEqualsAssertion(expected, actual).toStatement() - return - } - - // It does not work for Double and Float because JUnit does not have equals overloading with wrappers - if (nestedElementClassId == floatClassId || nestedElementClassId == doubleClassId) { - floatingPointArraysDeepEquals(arrayInfo, expected, actual, statements) - return - } - - // common primitive array, can use default array equals - addArraysLengthAssertion(expected, actual, statements) - statements += getArrayEqualsAssertion( - expectedModel.classId, - typeCast(expectedModel.classId, expected, isSafetyCast = true), - typeCast(expectedModel.classId, actual, isSafetyCast = true) - ).toStatement() - } - is UtAssembleModel -> { - // UtCompositeModel deep equals is much more easier and human friendly - expectedModel.origin?.let { - assertDeepEquals(it, expected, actual, statements, depth, visitedModels) - return - } - - // special case for strings as they are constructed from UtAssembleModel but can be compared with equals - if (expectedModel.classId == stringClassId) { - statements += assertions[assertEquals]( - expected, - actual - ).toStatement() - return - } - - // We cannot implement deep equals for not field set model - // because if modification was made by direct field access, we can compare modifications by field access too - // (like in modification expected.value = 5 we can assert equality expected.value and actual.value), - // but in other cases we don't know what fields do we need to compare - // (like if modification was List add() method invocation) - - // We can add some heuristics to process standard assemble models like List, Set and Map. - // So, there is a space for improvements - if (expectedModel.modificationsChain.isEmpty() || expectedModel.modificationsChain.any { it !is UtDirectSetFieldModel }) { - statements += getDeepEqualsAssertion(expected, actual).toStatement() - return - } - - for (modificationStep in expectedModel.modificationsChain) { - modificationStep as UtDirectSetFieldModel - val fieldId = modificationStep.fieldId - val fieldModel = modificationStep.fieldModel - - // we should not process enclosing class - // (actually, we do not do it properly anyway) - if (fieldId.isInnerClassEnclosingClassReference) continue - - traverseFieldRecursively( - fieldId, - fieldModel, - expected, - actual, - statements, - depth, - visitedModels - ) - } - } - is UtCompositeModel -> { - // Basically, to compare two iterables or maps, we need to iterate over them and compare each entry. - // But it leads to a lot of trash code in each test method, and it is more clear to use - // outer deep equals here - if (expected.isIterableOrMap()) { - statements += CgSingleLineComment( - "${expected.type.canonicalName} is iterable or Map, use outer deep equals to iterate over" - ) - statements += getDeepEqualsAssertion(expected, actual).toStatement() - - return - } - - if (expected.hasNotParametrizedCustomEquals()) { - // We rely on already existing equals - statements += CgSingleLineComment("${expected.type.canonicalName} has overridden equals method") - statements += assertions[assertEquals](expected, actual).toStatement() - - return - } - - for ((fieldId, fieldModel) in expectedModel.fields) { - // we should not process enclosing class - // (actually, we do not do it properly anyway) - if (fieldId.isInnerClassEnclosingClassReference) continue - - traverseFieldRecursively( - fieldId, - fieldModel, - expected, - actual, - statements, - depth, - visitedModels - ) - } - } - is UtVoidModel -> { - // Unit result is considered in generateResultAssertions method - error("Unexpected UtVoidModel in deep equals") - } - } - } - } - - private fun TestFrameworkManager.addArraysLengthAssertion( - expected: CgVariable, - actual: CgVariable, - statements: MutableList - ): CgDeclaration { - val cgGetLengthDeclaration = CgDeclaration( - intClassId, - variableConstructor.constructVarName("${expected.name}Size"), - expected.length(this, testClassThisInstance, getArrayLength) - ) - statements += cgGetLengthDeclaration - statements += assertions[assertEquals]( - cgGetLengthDeclaration.variable, - actual.length(this, testClassThisInstance, getArrayLength) - ).toStatement() - - return cgGetLengthDeclaration - } - - /** - * Generate deep equals for float and double any-dimensional arrays (DOES NOT includes wrappers) - */ - private fun TestFrameworkManager.floatingPointArraysDeepEquals( - expectedArrayInfo: ClassIdArrayInfo, - expected: CgVariable, - actual: CgVariable, - statements: MutableList, - ) { - val cgGetLengthDeclaration = addArraysLengthAssertion(expected, actual, statements) - - val nestedElementClassId = expectedArrayInfo.nestedElementClassId - ?: error("Expected from floating point array ${expectedArrayInfo.classId} to contain elements but null found") - require(nestedElementClassId == floatClassId || nestedElementClassId == doubleClassId) { - "Expected float or double ClassId but `$nestedElementClassId` found" - } - - if (expectedArrayInfo.isSingleDimensionalArray) { - // we can use array equals for all single dimensional arrays - statements += when (nestedElementClassId) { - floatClassId -> getFloatArrayEqualsAssertion( - typeCast(floatArrayClassId, expected, isSafetyCast = true), - typeCast(floatArrayClassId, actual, isSafetyCast = true), - floatDelta - ) - else -> getDoubleArrayEqualsAssertion( - typeCast(doubleArrayClassId, expected, isSafetyCast = true), - typeCast(doubleArrayClassId, actual, isSafetyCast = true), - doubleDelta - ) - }.toStatement() - } else { - // we can't use array equals for multidimensional double and float arrays - // so we need to go deeper to single-dimensional array - val loop = buildForLoop { - val (i, init) = variableConstructor.loopInitialization(intClassId, "i", initializer = 0) - initialization = init - condition = i lessThan cgGetLengthDeclaration.variable.resolve() - update = i.inc() - - val loopStatements = mutableListOf() - val expectedNestedElement = CgDeclaration( - expected.type.elementClassId!!, - variableConstructor.constructVarName("${expected.name}NestedElement"), - CgArrayElementAccess(expected, i) - ) - val actualNestedElement = CgDeclaration( - actual.type.elementClassId!!, - variableConstructor.constructVarName("${actual.name}NestedElement"), - CgArrayElementAccess(actual, i) - ) - - loopStatements += expectedNestedElement - loopStatements += actualNestedElement - loopStatements += CgEmptyLine() - - val nullBranchStatements = listOf( - assertions[assertNull](actualNestedElement.variable).toStatement() - ) - - val notNullBranchStatements = mutableListOf() - floatingPointArraysDeepEquals( - expectedArrayInfo.getNested(), - expectedNestedElement.variable, - actualNestedElement.variable, - notNullBranchStatements - ) - - loopStatements += CgIfStatement( - CgEqualTo(expectedNestedElement.variable, nullLiteral()), - nullBranchStatements, - notNullBranchStatements - ) - - - this@buildForLoop.statements = loopStatements - } - - statements += loop - } - } - - private fun CgVariable.isIterableOrMap(): Boolean = type.isIterableOrMap - - /** - * Some classes have overridden equals method, but it doesn't work properly. - * For example, List has overridden equals method but it relies on T equals. - * So, if T doesn't override equals, assertEquals with List fails. - * Therefore, all standard collections and map can fail. - * We overapproximate this assumption for all parametrized classes because we can't be sure that - * overridden equals doesn't rely on type parameters equals. - */ - private fun CgVariable.hasNotParametrizedCustomEquals(): Boolean { - if (type.jClass.overridesEquals()) { - // type parameters is list of class type parameters - empty if class is not generic - val typeParameters = type.kClass.typeParameters - - return typeParameters.isEmpty() - } - - return false - } - - /** - * We can't use [emptyLineIfNeeded] from here because it changes field [currentBlock]. - * Also, we can't extract generic part because [currentBlock] is PersistentList (not mutable). - */ - private fun MutableList.addEmptyLineIfNeeded() { - val lastStatement = lastOrNull() ?: return - if (lastStatement is CgEmptyLine) return - - this += CgEmptyLine() - } - - private fun traverseFieldRecursively( - fieldId: FieldId, - fieldModel: UtModel, - expected: CgVariable, - actual: CgVariable, - statements: MutableList, - depth: Int, - visitedModels: MutableSet - ) { - // if field is static, it is represents itself in "before" and - // "after" state: no need to assert its equality to itself. - if (fieldId.isStatic) { - return - } - - // if model is already processed, so we don't want to add new statements - if (fieldModel in visitedModels) { - return - } - - // fieldModel is not visited and will be marked in assertDeepEquals call - val fieldName = fieldId.name - var expectedVariable: CgVariable? = null - - if (needExpectedDeclaration(fieldModel)) { - val expectedFieldDeclaration = createDeclarationForFieldFromVariable(fieldId, expected, fieldName) - - statements += expectedFieldDeclaration - expectedVariable = expectedFieldDeclaration.variable - } - - val actualFieldDeclaration = createDeclarationForFieldFromVariable(fieldId, actual, fieldName) - statements += actualFieldDeclaration - - assertDeepEquals( - fieldModel, - expectedVariable, - actualFieldDeclaration.variable, - statements, - depth + 1, - visitedModels, - ) - statements.addEmptyLineIfNeeded() - } - - @Suppress("UNUSED_ANONYMOUS_PARAMETER") - private fun createDeclarationForFieldFromVariable( - fieldId: FieldId, - variable: CgVariable, - fieldName: String - ): CgDeclaration { - val expectedFieldDeclaration = createDeclarationForNewVarAndUpdateVariableScopeOrGetExistingVariable( - baseType = fieldId.type, - baseName = "${variable.name}${fieldName.capitalize()}", - init = { fieldId.getAccessExpression(variable) } - ).either( - { declaration -> declaration }, - { unexpectedExistingVariable -> - error( - "Unexpected existing variable for field $fieldName with type ${fieldId.type} " + - "from expected variable ${variable.name} with type ${variable.type}" - ) - } - ) - return expectedFieldDeclaration - } - - private fun FieldId.getAccessExpression(variable: CgVariable): CgExpression = - // Can directly access field only if it is declared in variable class (or in its ancestors) - // and is accessible from current package - if (variable.type.hasField(name) && isAccessibleFrom(testClassPackageName)) { - if (field.isStatic) CgStaticFieldAccess(this) else CgFieldAccess(variable, this) - } else { - testClassThisInstance[getFieldValue](variable, stringLiteral(name)) - } - - /** - * Stores array information about ClassId. - * @property classId ClassId itself. - * @property nestedElementClassId the most nested element ClassId if it is array and null otherwise. - * @property dimensions 0 for non-arrays and number of dimensions in case of arrays. - */ - private data class ClassIdArrayInfo(val classId: ClassId, val nestedElementClassId: ClassId?, val dimensions: Int) { - val isArray get(): Boolean = dimensions > 0 - - val isPrimitiveArray get(): Boolean = isArray && nestedElementClassId!!.isPrimitive - - val isSingleDimensionalArray get(): Boolean = dimensions == 1 - - fun getNested(): ClassIdArrayInfo { - require(dimensions > 0) { "Trying to get nested array from not array type $classId" } - - return copy(dimensions = dimensions - 1) - } - } - - private val UtArrayModel.isArray: Boolean - get() = this.classId.isArray - - private fun UtArrayModel.collectArrayInfo(): ClassIdArrayInfo { - if (!isArray) return ClassIdArrayInfo( - classId = classId, - nestedElementClassId = null, - dimensions = 0 - ) - - val nestedElementClassIdList = generateSequence(classId.elementClassId) { it.elementClassId }.toList() - val dimensions = nestedElementClassIdList.size - val nestedElementClassId = nestedElementClassIdList.last() - - return ClassIdArrayInfo(classId, nestedElementClassId, dimensions) - } - - private fun assertEquality(expected: CgValue, actual: CgVariable) { - when { - expected.type.isArray -> { - // TODO: How to compare arrays of Float and Double wrappers? - // TODO: For example, JUnit5 does not have an assertEquals() overload for these wrappers. - // TODO: So for now we compare arrays of these wrappers as arrays of Objects, but that is probably wrong. - when (expected.type.elementClassId!!) { - floatClassId -> testFrameworkManager.assertFloatArrayEquals( - typeCast(floatArrayClassId, expected, isSafetyCast = true), - typeCast(floatArrayClassId, actual, isSafetyCast = true), - floatDelta - ) - doubleClassId -> testFrameworkManager.assertDoubleArrayEquals( - typeCast(doubleArrayClassId, expected, isSafetyCast = true), - typeCast(doubleArrayClassId, actual, isSafetyCast = true), - doubleDelta - ) - else -> { - val targetType = when { - expected.type.isPrimitiveArray -> expected.type - actual.type.isPrimitiveArray -> actual.type - else -> objectArrayClassId - } - if (targetType.isPrimitiveArray) { - // we can use simple arrayEquals for primitive arrays - testFrameworkManager.assertArrayEquals( - targetType, - typeCast(targetType, expected, isSafetyCast = true), - typeCast(targetType, actual, isSafetyCast = true) - ) - } else { - // array of objects, have to use deep equals - - if (expected is CgLiteral) { - // Literal can only be Primitive or String, can use equals here - testFrameworkManager.assertEquals(expected, actual) - return - } - - require(resultModel is UtArrayModel) { - "Result model have to be UtArrayModel to generate arrays assertion " + - "but `${resultModel::class}` found" - } - - generateDeepEqualsOrNullAssertion(expected, actual) - } - } - } - } - else -> when { - (expected.type == floatClassId || expected.type == floatWrapperClassId) -> { - testFrameworkManager.assertFloatEquals( - typeCast(floatClassId, expected, isSafetyCast = true), - typeCast(floatClassId, actual, isSafetyCast = true), - floatDelta - ) - } - (expected.type == doubleClassId || expected.type == doubleWrapperClassId) -> { - testFrameworkManager.assertDoubleEquals( - typeCast(doubleClassId, expected, isSafetyCast = true), - typeCast(doubleClassId, actual, isSafetyCast = true), - doubleDelta - ) - } - expected == nullLiteral() -> testFrameworkManager.assertNull(actual) - expected is CgLiteral && expected.value is Boolean -> { - when (parameterizedTestSource) { - ParametrizedTestSource.DO_NOT_PARAMETRIZE -> - testFrameworkManager.assertBoolean(expected.value, actual) - ParametrizedTestSource.PARAMETRIZE -> - testFrameworkManager.assertEquals(expected, actual) - } - } - else -> { - if (expected is CgLiteral) { - // Literal can only be Primitive or String, can use equals here - testFrameworkManager.assertEquals(expected, actual) - return - } - - generateDeepEqualsOrNullAssertion(expected, actual) - } - } - } - } - - /** - * We can't use standard deepEquals method in parametrized tests - * because nullable objects require different asserts. - * See https://github.com/UnitTestBot/UTBotJava/issues/252 for more details. - */ - private fun generateDeepEqualsOrNullAssertion( - expected: CgValue, - actual: CgVariable, - ) { - when (parameterizedTestSource) { - ParametrizedTestSource.DO_NOT_PARAMETRIZE -> - currentBlock = currentBlock.addAll(generateDeepEqualsAssertion(expected, actual)) - ParametrizedTestSource.PARAMETRIZE -> { - val assertNullStmt = listOf(testFrameworkManager.assertions[testFramework.assertNull](actual).toStatement()) - currentBlock = currentBlock.add( - CgIfStatement( - CgEqualTo(expected, nullLiteral()), - assertNullStmt, - generateDeepEqualsAssertion(expected, actual) - ) - ) - } - } - } - - private fun generateDeepEqualsAssertion( - expected: CgValue, - actual: CgVariable, - ): List { - require(expected is CgVariable) { - "Expected value have to be Literal or Variable but `${expected::class}` found" - } - - val statements = mutableListOf(CgEmptyLine()) - assertDeepEquals( - resultModel, - expected, - actual, - statements, - depth = 0, - visitedModels = hashSetOf() - ) - - return statements.dropLastWhile { it is CgEmptyLine } - } - - private fun recordActualResult() { - currentExecution!!.result.onSuccess { result -> - when (val executable = currentExecutable) { - is ConstructorId -> { - // there is nothing to generate for constructors - return - } - is BuiltinMethodId -> error("Unexpected BuiltinMethodId $currentExecutable while generating actual result") - is MethodId -> { - // TODO possible engine bug - void method return type and result not UtVoidModel - if (result.isUnit() || executable.returnType == voidClassId) return - - emptyLineIfNeeded() - - actual = newVar( - CgClassId(executable.returnType, isNullable = result is UtNullModel), - "actual" - ) { - thisInstance[executable](*methodArguments.toTypedArray()) - } - } - } - } - } - - fun createTestMethod(utMethod: UtMethod<*>, execution: UtExecution): CgTestMethod = - withTestMethodScope(execution) { - val testMethodName = nameGenerator.testMethodNameFor(utMethod, execution.testMethodName) - // TODO: remove this line when SAT-1273 is completed - execution.displayName = execution.displayName?.let { "${utMethod.callable.name}: $it" } - testMethod(testMethodName, execution.displayName) { - rememberInitialStaticFields() - val stateAnalyzer = ExecutionStateAnalyzer(execution) - val modificationInfo = stateAnalyzer.findModifiedFields() - // TODO: move such methods to another class and leave only 2 public methods: remember initial and final states - val mainBody = { - mockStaticFields() - setupInstrumentation() - // build this instance - thisInstance = execution.stateBefore.thisInstance?.let { - variableConstructor.getOrCreateVariable(it) - } - // build arguments - for ((index, param) in execution.stateBefore.parameters.withIndex()) { - val name = paramNames[utMethod]?.get(index) - methodArguments += variableConstructor.getOrCreateVariable(param, name) - } - rememberInitialEnvironmentState(modificationInfo) - recordActualResult() - generateResultAssertions() - rememberFinalEnvironmentState(modificationInfo) - generateFieldStateAssertions() - } - - val statics = currentExecution!!.stateBefore.statics - if (statics.isNotEmpty()) { - +tryBlock { - mainBody() - }.finally { - recoverStaticFields() - } - } else { - mainBody() - } - - mockFrameworkManager.getAndClearMethodResources()?.let { resources -> - val closeFinallyBlock = resources.map { - val variable = it.variable - variable.type.closeMethodIdOrNull?.let { closeMethod -> - CgMethodCall(variable, closeMethod, arguments = emptyList()).toStatement() - } ?: error("Resource $variable was expected to be auto closeable but it is not") - } - - val tryWithMocksFinallyClosing = CgTryCatch(currentBlock, handlers = emptyList(), closeFinallyBlock) - currentBlock = currentBlock.clear() - resources.forEach { - // First argument for mocked resource declaration initializer is a target type. - // Pass this argument as a type parameter for the mocked resource - val typeParameter = when (val firstArg = (it.initializer as CgMethodCall).arguments.first()) { - is CgGetJavaClass -> firstArg.classId - is CgVariable -> firstArg.type - else -> error("Unexpected mocked resource declaration argument $firstArg") - } - val varType = CgClassId( - it.variableType, - TypeParameters(listOf(typeParameter)), - isNullable = true, - ) - +CgDeclaration( - varType, - it.variableName, - // guard initializer to reuse typecast creation logic - initializer = guardExpression(varType, nullLiteral()).expression, - isMutable = true) - } - +tryWithMocksFinallyClosing - } - } - } - - private val expectedResultVarName = "expectedResult" - private val expectedErrorVarName = "expectedError" - - fun createParameterizedTestMethod(utTestCase: UtTestCase, dataProviderMethodName: String): CgTestMethod? { - val methodUnderTest = utTestCase.method - val methodUnderTestParameters = utTestCase.method.callable.parameters - - if (utTestCase.executions.isEmpty()) { - return null - } - - //TODO: orientation on arbitrary execution may be misleading, but what is the alternative? - //may be a heuristic to select a model with minimal number of internal nulls should be used - val arbitraryExecution = utTestCase.executions - .firstOrNull { it.result is UtExecutionSuccess && (it.result as UtExecutionSuccess).model !is UtNullModel } - ?: utTestCase.executions.first() - - return withTestMethodScope(arbitraryExecution) { - val testName = nameGenerator.parameterizedTestMethodName(dataProviderMethodName) - val testArguments = mutableListOf() - val mainBody = { - // build this instance - thisInstance = arbitraryExecution.stateBefore.thisInstance?.let { - val thisInstanceVariable = constructInstanceVariable(it) - testArguments += CgParameterDeclaration(thisInstanceVariable) - thisInstanceVariable - } - - // build arguments for method under test and parameterized test - for (index in arbitraryExecution.stateBefore.parameters.indices) { - val argumentName = paramNames[methodUnderTest]?.get(index) - val paramIndex = if (methodUnderTest.isStatic) index else index + 1 - val paramType = methodUnderTestParameters[paramIndex].type.javaType - - val argumentClassId = when { - paramType is Class<*> && paramType.isArray -> paramType.id - paramType is ParameterizedTypeImpl -> paramType.rawType.id - else -> ClassId(paramType.typeName) - } - - val argument = variableConstructor.parameterizedVariable(argumentClassId, argumentName) - methodArguments += argument - testArguments += CgParameterDeclaration( - argument.name, argument.type, isReferenceType = !argument.type.isPrimitive - ) - } - val method = currentExecutable as MethodId - val containsFailureExecution = containsFailureExecution(utTestCase) - if (method.returnType != voidClassId) { - testArguments += CgParameterDeclaration( - expectedResultVarName, resultClassId(method.returnType), - isReferenceType = containsFailureExecution || !method.returnType.isPrimitive - ) - } - if (containsFailureExecution) { - testArguments += CgParameterDeclaration( - expectedErrorVarName, - throwableClassId(), - isReferenceType = true - ) - } - - //record result and generate result assertions - recordActualResult() - generateAssertionsForParameterizedTest() - } - - methodType = PARAMETRIZED - testMethod( - testName, - displayName = null, - testArguments, - parameterized = true, - dataProviderMethodName - ) { - if (containsFailureExecution(utTestCase)) { - +tryBlock(mainBody) - .catch(Throwable::class.java.id) { e -> - val pseudoExceptionVarName = when (codegenLanguage) { - CodegenLanguage.JAVA -> "${expectedErrorVarName}.isInstance(${e.name.decapitalize()})" - CodegenLanguage.KOTLIN -> "${expectedErrorVarName}!!.isInstance(${e.name.decapitalize()})" - } - - testFrameworkManager.assertBoolean(CgVariable(pseudoExceptionVarName, booleanClassId)) - } - } else { - mainBody() - } - } - } - } - - /** - * Constructs a variable for the instance parameter of parametrized test. - */ - private fun constructInstanceVariable(instanceModel: UtModel): CgVariable { - val className = instanceModel.classId.simpleName.decapitalize() - return variableConstructor.parameterizedVariable(instanceModel.classId, className) - } - - /** - * Constructs data provider method for parameterized tests. - * - * The body of this method is constructed manually, statement by statement. - * Standard logic for generating each test case parameter code is used. - */ - fun createParameterizedTestDataProvider( - utTestCase: UtTestCase, - dataProviderMethodName: String - ): CgParameterizedTestDataProviderMethod { - val parametersStatements = mutableListOf() - - val argListLength = utTestCase.executions.size - val argListDeclaration = createArgList(argListLength) - val argListVariable = argListDeclaration.variable - - parametersStatements += argListDeclaration - parametersStatements += CgEmptyLine() - - for ((execIndex, execution) in utTestCase.executions.withIndex()) { - withTestMethodScope(execution) { - //collect arguments - val arguments = mutableListOf() - val executionArgumentsBody = { - execution.stateBefore.thisInstance?.let { - arguments += variableConstructor.getOrCreateVariable(it) - } - - for ((paramIndex, paramModel) in execution.stateBefore.parameters.withIndex()) { - val argumentName = paramNames[utTestCase.method]?.get(paramIndex) - arguments += variableConstructor.getOrCreateVariable(paramModel, argumentName) - } - - val method = currentExecutable as MethodId - val needsReturnValue = method.returnType != voidClassId - val containsFailureExecution = containsFailureExecution(utTestCase) - execution.result - .onSuccess { - if (needsReturnValue) { - arguments += variableConstructor.getOrCreateVariable(it) - } - if (containsFailureExecution) { - arguments += nullLiteral() - } - } - .onFailure { - if (needsReturnValue) { - arguments += nullLiteral() - } - if (containsFailureExecution) { - arguments += CgGetJavaClass(it::class.id) - } - } - emptyLineIfNeeded() - } - - //create a block for current test case - parametersStatements += innerBlock( - {}, - block(executionArgumentsBody) - + createArgumentsCallRepresentation(execIndex, argListVariable, arguments) - ) - } - } - - parametersStatements.addEmptyLineIfNeeded() - parametersStatements += CgReturnStatement(argListVariable) - - return buildParameterizedTestDataProviderMethod { - name = dataProviderMethodName - returnType = argListClassId() - statements = parametersStatements - annotations = createDataProviderAnnotations(dataProviderMethodName) - } - } - - private fun withTestMethodScope(execution: UtExecution, block: () -> R): R { - clearMethodScope() - currentExecution = execution - statesCache = EnvironmentFieldStateCache.emptyCacheFor(execution) - return try { - block() - } finally { - clearMethodScope() - } - } - - private fun clearMethodScope() { - collectedExceptions.clear() - collectedTestMethodAnnotations.clear() - prevStaticFieldValues.clear() - thisInstance = null - methodArguments.clear() - currentExecution = null - mockFrameworkManager.clearExecutionResources() - } - - /** - * Generates a collection of [CgStatement] to prepare arguments - * for current execution in parameterized tests. - */ - private fun createArgumentsCallRepresentation( - executionIndex: Int, - argsVariable: CgVariable, - arguments: List, - ): List = when (testFramework) { - Junit5 -> { - val argumentsMethodCall = CgMethodCall(caller = null, argumentsMethodId(), arguments) - listOf( - CgStatementExecutableCall(CgMethodCall(argsVariable, addToListMethodId(), listOf(argumentsMethodCall))) - ) - } - TestNg -> { - val statements = mutableListOf() - val argsArrayAllocation = CgAllocateArray(Array::class.java.id, objectClassId, arguments.size) - val argsArrayDeclaration = CgDeclaration(objectArrayClassId, "testCaseObjects", argsArrayAllocation) - statements += argsArrayDeclaration - for ((i, argument) in arguments.withIndex()) { - statements += setArgumentsArrayElement(argsArrayDeclaration.variable, i, argument) - } - statements += setArgumentsArrayElement(argsVariable, executionIndex, argsArrayDeclaration.variable) - - statements - } - Junit4 -> error("Parameterized tests are not supported for JUnit4") - } - - /** - * Sets an element of arguments array in parameterized test, - * if test framework represents arguments as array. - */ - private fun setArgumentsArrayElement(array: CgVariable, index: Int, value: CgExpression): CgStatement = - if (array.type == objectClassId) { - java.lang.reflect.Array::class.id[setArrayElement](array, index, value) - } else { - CgAssignment(array.at(index), value) - } - - /** - * Creates annotations for data provider method in parameterized tests - * depending on test framework. - */ - private fun createDataProviderAnnotations(dataProviderMethodName: String?): MutableList = - when (testFramework) { - Junit5 -> mutableListOf() - TestNg -> mutableListOf( - annotation( - testFramework.methodSourceAnnotationId, - listOf("name" to CgLiteral(stringClassId, dataProviderMethodName)) - ), - ) - Junit4 -> error("Parameterized tests are not supported for JUnit4") - } - - /** - * Creates declaration of argList collection in parameterized tests. - */ - private fun createArgList(length: Int): CgDeclaration = when (testFramework) { - Junit5 -> { - val constructorCall = CgConstructorCall(ConstructorId(argListClassId(), emptyList()), emptyList()) - CgDeclaration(argListClassId(), "argList", constructorCall) - } - TestNg -> { - val allocateArrayCall = CgAllocateArray(argListClassId(), Array::class.java.id, length) - CgDeclaration(argListClassId(), "argList", allocateArrayCall) - } - Junit4 -> error("Parameterized tests are not supported for JUnit4") - } - - /** - * Creates a [ClassId] for arguments collection. - */ - private fun argListClassId(): ClassId = when (testFramework) { - Junit5 -> BuiltinClassId( - name = "java.util.ArrayList<${JUNIT5_PARAMETERIZED_PACKAGE}.provider.Arguments>", - simpleName = "ArrayList<${JUNIT5_PARAMETERIZED_PACKAGE}.provider.Arguments>", - canonicalName = "java.util.ArrayList<${JUNIT5_PARAMETERIZED_PACKAGE}.provider.Arguments>", - packageName = "java.util", - ) - TestNg -> BuiltinClassId( - name = Array?>::class.java.name, - simpleName = when (codegenLanguage) { - CodegenLanguage.JAVA -> "Object[][]" - CodegenLanguage.KOTLIN -> "Array?>" - }, - canonicalName = Array?>::class.java.canonicalName, - packageName = Array?>::class.java.packageName, - ) - Junit4 -> error("Parameterized tests are not supported for JUnit4") - } - - - /** - * A [MethodId] to add an item into [ArrayList]. - */ - private fun addToListMethodId(): MethodId = methodId( - classId = ArrayList::class.id, - name = "add", - returnType = booleanClassId, - arguments = arrayOf(Object::class.id), - ) - - /** - * A [MethodId] to call JUnit Arguments method. - */ - private fun argumentsMethodId(): MethodId { - val argumentsClassId = BuiltinClassId( - name = "org.junit.jupiter.params.provider.Arguments", - simpleName = "Arguments", - canonicalName = "org.junit.jupiter.params.provider.Arguments", - packageName = "org.junit.jupiter.params.provider", - ) - - return methodId( - classId = argumentsClassId, - name = "arguments", - returnType = argumentsClassId, - arguments = arrayOf(Object::class.id), - ) - } - - private fun containsFailureExecution(testCase: UtTestCase) = - testCase.executions.any { it.result is UtExecutionFailure } - - private fun resultClassId(returnType: ClassId): ClassId = when (returnType) { - booleanClassId -> booleanWrapperClassId - byteClassId -> byteWrapperClassId - charClassId -> charWrapperClassId - shortClassId -> shortWrapperClassId - intClassId -> intWrapperClassId - longClassId -> longWrapperClassId - floatClassId -> floatWrapperClassId - doubleClassId -> doubleWrapperClassId - else -> returnType - } - - /** - * A [ClassId] for Class. - */ - private fun throwableClassId(): ClassId = BuiltinClassId( - name = "java.lang.Class", - simpleName = "Class", - canonicalName = "java.lang.Class", - packageName = "java.lang", - ) - - - private fun collectParameterizedTestAnnotations(dataProviderMethodName: String?): Set = - when (testFramework) { - Junit5 -> setOf( - annotation(testFramework.parameterizedTestAnnotationId), - annotation(testFramework.methodSourceAnnotationId, dataProviderMethodName), - ) - TestNg -> setOf( - annotation( - testFramework.parameterizedTestAnnotationId, - listOf("dataProvider" to CgLiteral(stringClassId, dataProviderMethodName)) - ), - ) - Junit4 -> error("Parameterized tests are not supported for JUnit4") - } - - private fun testMethod( - methodName: String, - displayName: String?, - params: List = emptyList(), - parameterized: Boolean = false, - dataProviderMethodName: String? = null, - body: () -> Unit, - ): CgTestMethod { - collectedTestMethodAnnotations += if (parameterized) { - collectParameterizedTestAnnotations(dataProviderMethodName) - } else { - setOf(annotation(testFramework.testAnnotationId)) - } - displayName?.let { testFrameworkManager.addDisplayName(it) } - - val result = currentExecution!!.result - if (result is UtTimeoutException) { - testFrameworkManager.setTestExecutionTimeout(hangingTestsTimeout.timeoutMs) - } - - if (result is UtTimeoutException && !enableTestsTimeout) { - testFrameworkManager.disableTestMethod( - "Disabled due to failing by exceeding the timeout" - ) - } - - if (result is UtConcreteExecutionFailure) { - testFrameworkManager.disableTestMethod( - "Disabled due to possible JVM crash" - ) - } - - val testMethod = buildTestMethod { - name = methodName - parameters = params - statements = block(body) - // Exceptions and annotations assignment must run after everything else is set up - exceptions += collectedExceptions - annotations += collectedTestMethodAnnotations - methodType = this@CgMethodConstructor.methodType - val docComment = currentExecution?.summary?.map { convertDocToCg(it) }?.toMutableList() ?: mutableListOf() - - // add JVM crash report path if exists - if (result is UtConcreteExecutionFailure) { - result.extractJvmReportPathOrNull()?.let { - val jvmReportDocumentation = CgDocRegularStmt(getJvmReportDocumentation(it)) - val lastTag = docComment.lastOrNull() - // if the last statement is a

     tag, put the path inside it
    -                    if (lastTag == null || lastTag !is CgDocPreTagStatement) {
    -                        docComment += jvmReportDocumentation
    -                    } else {
    -                        val tagContent = lastTag.content
    -                        docComment.removeLast()
    -                        docComment += CgDocPreTagStatement(tagContent + jvmReportDocumentation)
    -                    }
    -                }
    -            }
    -
    -            documentation = CgDocumentationComment(docComment)
    -            documentation = if (parameterized) {
    -                CgDocumentationComment(text = null)
    -            } else {
    -                CgDocumentationComment(docComment)
    -            }
    -        }
    -        testMethods += testMethod
    -        return testMethod
    -    }
    -
    -    fun errorMethod(method: UtMethod<*>, errors: Map): CgRegion {
    -        val name = nameGenerator.errorMethodNameFor(method)
    -        val body = block {
    -            comment("Couldn't generate some tests. List of errors:")
    -            comment()
    -            errors.entries.sortedByDescending { it.value }.forEach { (message, repeated) ->
    -                val multilineMessage = message
    -                    .split("\r") // split stacktrace from concrete if present
    -                    .flatMap { line ->
    -                        line
    -                            .split(" ")
    -                            .windowed(size = 10, step = 10, partialWindows = true) {
    -                                it.joinToString(separator = " ")
    -                            }
    -                    }
    -                comment("$repeated occurrences of:")
    -
    -                if (multilineMessage.size <= 1) {
    -                    // wrap one liner with line comment
    -                    multilineMessage.singleOrNull()?.let { comment(it) }
    -                } else {
    -                    // wrap all lines with multiline comment
    -                    multilineComment(multilineMessage)
    -                }
    -
    -                emptyLine()
    -            }
    -        }
    -        val errorTestMethod = CgErrorTestMethod(name, body)
    -        return CgSimpleRegion("Errors report for ${method.callable.name}", listOf(errorTestMethod))
    -    }
    -
    -    private fun getJvmReportDocumentation(jvmReportPath: String): String {
    -        val pureJvmReportPath = jvmReportPath.substringAfter("# ")
    -
    -        // \n is here because IntellijIdea cannot process other separators
    -        return PathUtil.toHtmlLinkTag(PathUtil.replaceSeparator(pureJvmReportPath), fileName = "JVM crash report") + "\n"
    -    }
    -
    -    private fun UtConcreteExecutionFailure.extractJvmReportPathOrNull(): String? =
    -        exception.processStdout.singleOrNull {
    -            "hs_err_pid" in it
    -        }
    -
    -    private fun CgExecutableCall.wrapReflectiveCall() {
    -        +tryBlock {
    -            +this@wrapReflectiveCall
    -        }.catch(InvocationTargetException::class.id) { e ->
    -            throwStatement {
    -                e[getTargetException]()
    -            }
    -        }
    -    }
    -
    -    /**
    -     * Intercept calls to [java.lang.reflect.Method.invoke] and to [java.lang.reflect.Constructor.newInstance]
    -     * in order to wrap these calls in a try-catch block that will handle [InvocationTargetException]
    -     * that may be thrown by these calls.
    -     */
    -    private fun CgExecutableCall.intercepted() {
    -        val executableToWrap = when (executableId) {
    -            is MethodId -> invoke
    -            is ConstructorId -> newInstance
    -        }
    -        if (executableId == executableToWrap) {
    -            this.wrapReflectiveCall()
    -        } else {
    -            +this
    -        }
    -    }
    -}
    \ No newline at end of file
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt
    deleted file mode 100644
    index 83c77d7d6e..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgTestClassConstructor.kt
    +++ /dev/null
    @@ -1,299 +0,0 @@
    -package org.utbot.framework.codegen.model.constructor.tree
    -
    -import org.utbot.common.appendHtmlLine
    -import org.utbot.engine.displayName
    -import org.utbot.framework.codegen.ParametrizedTestSource
    -import org.utbot.framework.codegen.model.constructor.context.CgContext
    -import org.utbot.framework.codegen.model.constructor.context.CgContextOwner
    -import org.utbot.framework.codegen.model.constructor.util.CgComponents
    -import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructor
    -import org.utbot.framework.codegen.model.tree.CgMethod
    -import org.utbot.framework.codegen.model.tree.CgExecutableUnderTestCluster
    -import org.utbot.framework.codegen.model.tree.CgParameterDeclaration
    -import org.utbot.framework.codegen.model.tree.CgRegion
    -import org.utbot.framework.codegen.model.tree.CgSimpleRegion
    -import org.utbot.framework.codegen.model.tree.CgStaticsRegion
    -import org.utbot.framework.codegen.model.tree.CgTestClassFile
    -import org.utbot.framework.codegen.model.tree.CgTestMethod
    -import org.utbot.framework.codegen.model.tree.CgTestMethodCluster
    -import org.utbot.framework.codegen.model.tree.CgTestMethodType.*
    -import org.utbot.framework.codegen.model.tree.CgTripleSlashMultilineComment
    -import org.utbot.framework.codegen.model.tree.CgUtilMethod
    -import org.utbot.framework.codegen.model.tree.buildTestClass
    -import org.utbot.framework.codegen.model.tree.buildTestClassBody
    -import org.utbot.framework.codegen.model.tree.buildTestClassFile
    -import org.utbot.framework.codegen.model.visitor.importUtilMethodDependencies
    -import org.utbot.framework.plugin.api.MethodId
    -import org.utbot.framework.plugin.api.UtMethod
    -import org.utbot.framework.plugin.api.UtTestCase
    -import org.utbot.framework.util.description
    -import kotlin.reflect.KClass
    -
    -internal class CgTestClassConstructor(val context: CgContext) :
    -    CgContextOwner by context,
    -    CgStatementConstructor by CgComponents.getStatementConstructorBy(context) {
    -
    -    private val methodConstructor = CgComponents.getMethodConstructorBy(context)
    -    private val nameGenerator = CgComponents.getNameGeneratorBy(context)
    -
    -    private val cgDataProviderMethods = mutableListOf()
    -
    -    private val testsGenerationReport: TestsGenerationReport = TestsGenerationReport()
    -
    -    /**
    -     * Given a list of test cases constructs CgTestClass
    -     */
    -    fun construct(testCases: Collection): CgTestClassFile {
    -        return buildTestClassFile {
    -            testClass = buildTestClass {
    -                // TODO: obtain test class from plugin
    -                id = currentTestClass
    -                body = buildTestClassBody {
    -                    cgDataProviderMethods.clear()
    -                    for (testCase in testCases) {
    -                        updateCurrentExecutable(testCase.method)
    -                        val currentMethodUnderTestRegions = construct(testCase)
    -                        val executableUnderTestCluster = CgExecutableUnderTestCluster(
    -                            "Test suites for executable $currentExecutable",
    -                            currentMethodUnderTestRegions
    -                        )
    -                        testMethodRegions += executableUnderTestCluster
    -                    }
    -
    -                    dataProvidersAndUtilMethodsRegion += CgStaticsRegion(
    -                        "Data providers and utils methods",
    -                        cgDataProviderMethods + createUtilMethods()
    -                    )
    -                }
    -                // It is important that annotations, superclass and interfaces assignment is run after
    -                // all methods are generated so that all necessary info is already present in the context
    -                annotations += context.collectedTestClassAnnotations
    -                superclass = context.testClassSuperclass
    -                interfaces += context.collectedTestClassInterfaces
    -            }
    -            imports += context.collectedImports
    -            testsGenerationReport = this@CgTestClassConstructor.testsGenerationReport
    -        }
    -    }
    -
    -    private fun construct(testCase: UtTestCase): List> {
    -        val (methodUnderTest, executions, _, _, clustersInfo) = testCase
    -        val regions = mutableListOf>()
    -        val requiredFields = mutableListOf()
    -
    -        when (context.parameterizedTestSource) {
    -            ParametrizedTestSource.DO_NOT_PARAMETRIZE -> {
    -                for ((clusterSummary, executionIndices) in clustersInfo) {
    -                    val currentTestCaseTestMethods = mutableListOf()
    -                    emptyLineIfNeeded()
    -                    for (i in executionIndices) {
    -                        runCatching {
    -                            currentTestCaseTestMethods += methodConstructor.createTestMethod(methodUnderTest, executions[i])
    -                        }.onFailure { e -> processFailure(testCase, e) }
    -                    }
    -                    val clusterHeader = clusterSummary?.header
    -                    val clusterContent = clusterSummary?.content
    -                        ?.split('\n')
    -                        ?.let { CgTripleSlashMultilineComment(it) }
    -                    regions += CgTestMethodCluster(clusterHeader, clusterContent, currentTestCaseTestMethods)
    -
    -                    testsGenerationReport.addTestsByType(testCase, currentTestCaseTestMethods)
    -                }
    -            }
    -            ParametrizedTestSource.PARAMETRIZE -> {
    -                runCatching {
    -                    val dataProviderMethodName = nameGenerator.dataProviderMethodNameFor(testCase.method)
    -
    -                    val parameterizedTestMethod =
    -                        methodConstructor.createParameterizedTestMethod(testCase, dataProviderMethodName)
    -
    -                    if (parameterizedTestMethod != null) {
    -                        requiredFields += parameterizedTestMethod.requiredFields
    -
    -                        cgDataProviderMethods +=
    -                            methodConstructor.createParameterizedTestDataProvider(testCase, dataProviderMethodName)
    -
    -                        regions += CgSimpleRegion(
    -                            "Parameterized test for method ${methodUnderTest.displayName}",
    -                            listOf(parameterizedTestMethod),
    -                        )
    -                    }
    -                }.onFailure { error -> processFailure(testCase, error) }
    -            }
    -        }
    -
    -        val errors = testCase.allErrors
    -        if (errors.isNotEmpty()) {
    -            regions += methodConstructor.errorMethod(testCase.method, errors)
    -            testsGenerationReport.addMethodErrors(testCase, errors)
    -        }
    -
    -        return regions
    -    }
    -
    -    private fun processFailure(testCase: UtTestCase, failure: Throwable) {
    -        codeGenerationErrors
    -            .getOrPut(testCase) { mutableMapOf() }
    -            .merge(failure.description, 1, Int::plus)
    -    }
    -
    -    // TODO: collect imports of util methods
    -    private fun createUtilMethods(): List {
    -        val utilMethods = mutableListOf()
    -        // some util methods depend on the others
    -        // using this loop we make sure that all the
    -        // util methods dependencies are taken into account
    -        while (requiredUtilMethods.isNotEmpty()) {
    -            val method = requiredUtilMethods.first()
    -            requiredUtilMethods.remove(method)
    -            if (method.name !in existingMethodNames) {
    -                utilMethods += CgUtilMethod(method)
    -                importUtilMethodDependencies(method)
    -                existingMethodNames += method.name
    -                requiredUtilMethods += method.dependencies()
    -            }
    -        }
    -        return utilMethods
    -    }
    -
    -    /**
    -     * If @receiver is an util method, then returns a list of util method ids that @receiver depends on
    -     * Otherwise, an empty list is returned
    -     */
    -    private fun MethodId.dependencies(): List = when (this) {
    -        createInstance -> listOf(getUnsafeInstance)
    -        deepEquals -> listOf(arraysDeepEquals, iterablesDeepEquals, streamsDeepEquals, mapsDeepEquals, hasCustomEquals)
    -        arraysDeepEquals, iterablesDeepEquals, streamsDeepEquals, mapsDeepEquals -> listOf(deepEquals)
    -        else -> emptyList()
    -    }
    -
    -    /**
    -     * Engine errors + codegen errors for a given UtTestCase
    -     */
    -    private val UtTestCase.allErrors: Map
    -        get() = errors + codeGenerationErrors.getOrDefault(this, mapOf())
    -}
    -
    -typealias MethodGeneratedTests = MutableMap, MutableSet>
    -typealias ErrorsCount = Map
    -
    -data class TestsGenerationReport(
    -    val executables: MutableSet> = mutableSetOf(),
    -    var successfulExecutions: MethodGeneratedTests = mutableMapOf(),
    -    var timeoutExecutions: MethodGeneratedTests = mutableMapOf(),
    -    var failedExecutions: MethodGeneratedTests = mutableMapOf(),
    -    var crashExecutions: MethodGeneratedTests = mutableMapOf(),
    -    var errors: MutableMap, ErrorsCount> = mutableMapOf()
    -) {
    -    val classUnderTest: KClass<*>
    -        get() = executables.firstOrNull()?.clazz
    -            ?: error("No executables found in test report")
    -
    -    // Summary message is generated lazily to avoid evaluation of classUnderTest
    -    var summaryMessage: () -> String = { "Unit tests for $classUnderTest were generated successfully." }
    -    val initialWarnings: MutableList<() -> String> = mutableListOf()
    -
    -    fun addMethodErrors(testCase: UtTestCase, errors: Map) {
    -        this.errors[testCase.method] = errors
    -    }
    -
    -    fun addTestsByType(testCase: UtTestCase, testMethods: List) {
    -        with(testCase.method) {
    -            executables += this
    -
    -            testMethods.forEach {
    -                when (it.type) {
    -                    SUCCESSFUL -> updateExecutions(it, successfulExecutions)
    -                    FAILING -> updateExecutions(it, failedExecutions)
    -                    TIMEOUT -> updateExecutions(it, timeoutExecutions)
    -                    CRASH -> updateExecutions(it, crashExecutions)
    -                    PARAMETRIZED -> {
    -                        // Parametrized tests are not supported in the tests report yet
    -                        // TODO JIRA:1507
    -                    }
    -                }
    -            }
    -        }
    -    }
    -
    -    override fun toString(): String = buildString {
    -        appendHtmlLine(summaryMessage())
    -        appendHtmlLine()
    -        initialWarnings.forEach { appendHtmlLine(it()) }
    -        appendHtmlLine()
    -
    -        val testMethodsStatistic = executables.map { it.countTestMethods() }
    -        val errors = executables.map { it.countErrors() }
    -        val overallTestMethods = testMethodsStatistic.sumBy { it.count }
    -        val overallErrors = errors.sum()
    -        appendHtmlLine("Overall test methods: $overallTestMethods")
    -        appendHtmlLine("Successful test methods: ${testMethodsStatistic.sumBy { it.successful }}")
    -        appendHtmlLine(
    -            "Failing because of unexpected exception test methods: ${testMethodsStatistic.sumBy { it.failing }}"
    -        )
    -        appendHtmlLine(
    -            "Failing because of exceeding timeout test methods: ${testMethodsStatistic.sumBy { it.timeout }}"
    -        )
    -        appendHtmlLine(
    -            "Failing because of possible JVM crash test methods: ${testMethodsStatistic.sumBy { it.crashes }}"
    -        )
    -        appendHtmlLine("Not generated because of internal errors test methods: $overallErrors")
    -    }
    -
    -    // TODO: should we use TsvWriter from univocity instead of this manual implementation?
    -    fun getFileContent(): String =
    -        (listOf(getHeader()) + getLines()).joinToString(System.lineSeparator())
    -
    -    private fun getHeader(): String {
    -        val columnNames = listOf(
    -            "Executable/Number of test methods",
    -            SUCCESSFUL,
    -            FAILING,
    -            TIMEOUT,
    -            CRASH,
    -            "Errors tests"
    -        )
    -
    -        return columnNames.joinToString(TAB_SEPARATOR)
    -    }
    -
    -    private fun getLines(): List =
    -        executables.map { executable ->
    -            val testMethodStatistic = executable.countTestMethods()
    -            with(testMethodStatistic) {
    -                listOf(
    -                    executable,
    -                    successful,
    -                    failing,
    -                    timeout,
    -                    crashes,
    -                    executable.countErrors()
    -                ).joinToString(TAB_SEPARATOR)
    -            }
    -        }
    -
    -    private fun UtMethod<*>.countTestMethods(): TestMethodStatistic = TestMethodStatistic(
    -        testMethodsNumber(successfulExecutions),
    -        testMethodsNumber(failedExecutions),
    -        testMethodsNumber(timeoutExecutions),
    -        testMethodsNumber(crashExecutions)
    -    )
    -
    -    private fun UtMethod<*>.countErrors(): Int = errors.getOrDefault(this, emptyMap()).values.sum()
    -
    -    private fun UtMethod<*>.testMethodsNumber(executables: MethodGeneratedTests): Int =
    -        executables.getOrDefault(this, emptySet()).size
    -
    -    private fun UtMethod<*>.updateExecutions(it: CgTestMethod, executions: MethodGeneratedTests) {
    -        executions.getOrPut(this) { mutableSetOf() } += it
    -    }
    -
    -    private data class TestMethodStatistic(val successful: Int, val failing: Int, val timeout: Int, val crashes: Int) {
    -        val count: Int = successful + failing + timeout + crashes
    -    }
    -
    -    companion object {
    -        private const val TAB_SEPARATOR: String = "\t"
    -        const val EXTENSION: String = ".tsv"
    -    }
    -}
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt
    deleted file mode 100644
    index 534505dec5..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/CgVariableConstructor.kt
    +++ /dev/null
    @@ -1,443 +0,0 @@
    -package org.utbot.framework.codegen.model.constructor.tree
    -
    -import org.utbot.framework.codegen.model.constructor.builtin.forName
    -import org.utbot.framework.codegen.model.constructor.builtin.setArrayElement
    -import org.utbot.framework.codegen.model.constructor.context.CgContext
    -import org.utbot.framework.codegen.model.constructor.context.CgContextOwner
    -import org.utbot.framework.codegen.model.constructor.util.CgComponents
    -import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructor
    -import org.utbot.framework.codegen.model.constructor.util.get
    -import org.utbot.framework.codegen.model.constructor.util.isDefaultValueOf
    -import org.utbot.framework.codegen.model.constructor.util.isNotDefaultValueOf
    -import org.utbot.framework.codegen.model.constructor.util.typeCast
    -import org.utbot.framework.codegen.model.tree.CgAllocateArray
    -import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray
    -import org.utbot.framework.codegen.model.tree.CgDeclaration
    -import org.utbot.framework.codegen.model.tree.CgEnumConstantAccess
    -import org.utbot.framework.codegen.model.tree.CgExpression
    -import org.utbot.framework.codegen.model.tree.CgFieldAccess
    -import org.utbot.framework.codegen.model.tree.CgGetJavaClass
    -import org.utbot.framework.codegen.model.tree.CgLiteral
    -import org.utbot.framework.codegen.model.tree.CgNotNullVariable
    -import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess
    -import org.utbot.framework.codegen.model.tree.CgValue
    -import org.utbot.framework.codegen.model.tree.CgVariable
    -import org.utbot.framework.codegen.model.util.at
    -import org.utbot.framework.codegen.model.util.canBeSetIn
    -import org.utbot.framework.codegen.model.util.get
    -import org.utbot.framework.codegen.model.util.inc
    -import org.utbot.framework.codegen.model.util.isAccessibleFrom
    -import org.utbot.framework.codegen.model.util.lessThan
    -import org.utbot.framework.codegen.model.util.nullLiteral
    -import org.utbot.framework.codegen.model.util.resolve
    -import org.utbot.framework.plugin.api.BuiltinClassId
    -import org.utbot.framework.plugin.api.ClassId
    -import org.utbot.framework.plugin.api.ConstructorId
    -import org.utbot.framework.plugin.api.MethodId
    -import org.utbot.framework.plugin.api.UtArrayModel
    -import org.utbot.framework.plugin.api.UtAssembleModel
    -import org.utbot.framework.plugin.api.UtClassRefModel
    -import org.utbot.framework.plugin.api.UtCompositeModel
    -import org.utbot.framework.plugin.api.UtDirectSetFieldModel
    -import org.utbot.framework.plugin.api.UtEnumConstantModel
    -import org.utbot.framework.plugin.api.UtExecutableCallModel
    -import org.utbot.framework.plugin.api.UtModel
    -import org.utbot.framework.plugin.api.UtNullModel
    -import org.utbot.framework.plugin.api.UtPrimitiveModel
    -import org.utbot.framework.plugin.api.UtReferenceModel
    -import org.utbot.framework.plugin.api.UtVoidModel
    -import org.utbot.framework.plugin.api.util.defaultValueModel
    -import org.utbot.framework.plugin.api.util.field
    -import org.utbot.framework.plugin.api.util.findFieldOrNull
    -import org.utbot.framework.plugin.api.util.id
    -import org.utbot.framework.plugin.api.util.intClassId
    -import org.utbot.framework.plugin.api.util.isArray
    -import org.utbot.framework.plugin.api.util.isPrimitiveWrapperOrString
    -import org.utbot.framework.plugin.api.util.stringClassId
    -import org.utbot.framework.plugin.api.util.wrapperByPrimitive
    -import java.lang.reflect.Field
    -import java.lang.reflect.Modifier
    -
    -/**
    - * Constructs CgValue or CgVariable given a UtModel
    - */
    -@Suppress("unused")
    -internal class CgVariableConstructor(val context: CgContext) :
    -    CgContextOwner by context,
    -    CgCallableAccessManager by CgComponents.getCallableAccessManagerBy(context),
    -    CgStatementConstructor by CgComponents.getStatementConstructorBy(context) {
    -
    -    private val nameGenerator = CgComponents.getNameGeneratorBy(context)
    -    private val mockFrameworkManager = CgComponents.getMockFrameworkManagerBy(context)
    -
    -    /**
    -     * Take already created CgValue or construct either a new [CgVariable] or new [CgLiteral] for the given model.
    -     *
    -     * Here we consider reference models and other models separately.
    -     *
    -     * It is important, because variables for reference models are constructed differently from the others.
    -     * The difference is that the reference model variables are put into [valueByModel] cache
    -     * not after the whole object is set up, but right after the variable has been initialized.
    -     * For example, when we do `A a = new A()` we already have the variable, and it is stored in [valueByModel].
    -     * After that, we set all the necessary fields and call all the necessary methods.
    -     *
    -     * On the other hand, non-reference model variables are put into [valueByModel] after they have been fully set up.
    -     *
    -     * The point of this early caching of reference model variables is that classes may be recursive.
    -     * For example, we may want to create a looped object: `a.value = a`.
    -     * If we did not cache the variable `a` on its instantiation, then we would try to create `a` from its model
    -     * from scratch. Since this object is recursively pointing to itself, this process would lead
    -     * to a stack overflow. Specifically to avoid this, we cache reference model variables right after
    -     * their instantiation.
    -     *
    -     * We use [valueByModelId] for [UtReferenceModel] by id to not create new variable in case state before
    -     * was not transformed.
    -     */
    -    fun getOrCreateVariable(model: UtModel, name: String? = null): CgValue {
    -        // name could be taken from existing names, or be specified manually, or be created from generator
    -        val baseName = name ?: nameGenerator.nameFrom(model.classId)
    -        return if (model is UtReferenceModel) valueByModelId.getOrPut(model.id) {
    -            when (model) {
    -                is UtCompositeModel -> constructComposite(model, baseName)
    -                is UtAssembleModel -> constructAssemble(model, baseName)
    -                is UtArrayModel -> constructArray(model, baseName)
    -            }
    -        } else valueByModel.getOrPut(model) {
    -            when (model) {
    -                is UtNullModel -> nullLiteral()
    -                is UtPrimitiveModel -> CgLiteral(model.classId, model.value)
    -                is UtEnumConstantModel -> constructEnumConstant(model, baseName)
    -                is UtClassRefModel -> constructClassRef(model, baseName)
    -                is UtReferenceModel -> error("Unexpected UtReferenceModel: ${model::class}")
    -                is UtVoidModel -> error("Unexpected UtVoidModel: ${model::class}")
    -            }
    -        }
    -    }
    -
    -    /**
    -     * Creates general variable of type (to replace with concrete value in each test case).
    -     */
    -    fun parameterizedVariable(classId: ClassId, baseName: String? = null, isNotNull: Boolean = false): CgVariable {
    -        val name = nameGenerator.variableName(type = classId, base = baseName, isMock = false)
    -        return if (isNotNull) CgNotNullVariable(name, classId) else CgVariable(name, classId)
    -    }
    -
    -    private fun constructComposite(model: UtCompositeModel, baseName: String): CgVariable {
    -        val obj = if (model.isMock) {
    -            mockFrameworkManager.createMockFor(model, baseName)
    -        } else {
    -            newVar(model.classId, baseName) { testClassThisInstance[createInstance](model.classId.name) }
    -        }
    -
    -        valueByModelId[model.id] = obj
    -
    -        require(obj.type !is BuiltinClassId) {
    -            "Unexpected BuiltinClassId ${obj.type} found while constructing from composite model"
    -        }
    -
    -        for ((fieldId, fieldModel) in model.fields) {
    -            val field = fieldId.field
    -            val variableForField = getOrCreateVariable(fieldModel)
    -            val fieldFromVariableSpecifiedType = obj.type.findFieldOrNull(field.name)
    -
    -            // we cannot set field directly if variable declared type does not have such field
    -            // or we cannot directly create variable for field with the specified type (it is private, for example)
    -            // Example:
    -            // Object heapByteBuffer = createInstance("java.nio.HeapByteBuffer");
    -            // branchRegisterRequest.byteBuffer = heapByteBuffer;
    -            // byteBuffer is field of type ByteBuffer and upper line is incorrect
    -            val canFieldBeDirectlySetByVariableAndFieldTypeRestrictions =
    -                fieldFromVariableSpecifiedType != null && fieldFromVariableSpecifiedType.type.id == variableForField.type
    -            if (canFieldBeDirectlySetByVariableAndFieldTypeRestrictions && fieldId.canBeSetIn(testClassPackageName)) {
    -                // TODO: check if it is correct to use declaringClass of a field here
    -                val fieldAccess = if (field.isStatic) CgStaticFieldAccess(fieldId) else CgFieldAccess(obj, fieldId)
    -                fieldAccess `=` variableForField
    -            } else {
    -                // composite models must not have info about static fields, hence only non-static fields are set here
    -                +testClassThisInstance[setField](obj, fieldId.name, variableForField)
    -            }
    -        }
    -        return obj
    -    }
    -
    -    private fun constructAssemble(model: UtAssembleModel, baseName: String?): CgValue {
    -        for (statementModel in model.allStatementsChain) {
    -            when (statementModel) {
    -                is UtDirectSetFieldModel -> {
    -                    val instance = declareOrGet(statementModel.instance)
    -                    // fields here are supposed to be accessible, so we assign them directly without any checks
    -                    instance[statementModel.fieldId] `=` declareOrGet(statementModel.fieldModel)
    -                }
    -                is UtExecutableCallModel -> {
    -                    val executable = statementModel.executable
    -                    val params = statementModel.params
    -                    val cgCall = when (executable) {
    -                        is MethodId -> {
    -                            val caller = statementModel.instance?.let { declareOrGet(it) }
    -                            val args = params.map { declareOrGet(it) }
    -                            caller[executable](*args.toTypedArray())
    -                        }
    -                        is ConstructorId -> {
    -                            val args = params.map { declareOrGet(it) }
    -                            executable(*args.toTypedArray())
    -                        }
    -                    }
    -
    -                    // if call result is stored in a variable
    -                    if (statementModel.returnValue == null) {
    -                        +cgCall
    -                    } else {
    -                        val type = when (executable) {
    -                            is MethodId -> executable.returnType
    -                            is ConstructorId -> executable.classId
    -                        }
    -
    -                        // Don't use redundant constructors for primitives and String
    -                        val initExpr = if (isPrimitiveWrapperOrString(type)) cgLiteralForWrapper(params) else cgCall
    -                        newVar(type, statementModel.returnValue, baseName) { initExpr }
    -                            .takeIf { statementModel == model.finalInstantiationModel }
    -                            ?.also { valueByModelId[model.id] = it }
    -                    }
    -                }
    -            }
    -        }
    -
    -        return valueByModelId.getValue(model.id)
    -    }
    -
    -    /**
    -     * Makes a replacement of constructor call to instantiate a primitive wrapper
    -     * with direct setting of the value. The reason is that in Kotlin constructors
    -     * of primitive wrappers are private.
    -     */
    -    private fun cgLiteralForWrapper(params: List): CgLiteral {
    -        val paramModel = params.singleOrNull()
    -        require(paramModel is UtPrimitiveModel) { "Incorrect param models for primitive wrapper" }
    -
    -        val classId = wrapperByPrimitive[paramModel.classId]
    -            ?: if (paramModel.classId == stringClassId) {
    -                stringClassId
    -            } else {
    -                error("${paramModel.classId} is not a primitive wrapper or a string")
    -            }
    -
    -        return CgLiteral(classId, paramModel.value)
    -    }
    -
    -    private fun constructArray(arrayModel: UtArrayModel, baseName: String?): CgVariable {
    -        val elementType = arrayModel.classId.elementClassId!!
    -        val elementModels = (0 until arrayModel.length).map {
    -            arrayModel.stores.getOrDefault(it, arrayModel.constModel)
    -        }
    -
    -        val canInitWithValues = elementModels.all { it is UtPrimitiveModel } || elementModels.all { it is UtNullModel }
    -
    -        val initializer = if (canInitWithValues) {
    -            CgAllocateInitializedArray(arrayModel)
    -        } else {
    -            CgAllocateArray(arrayModel.classId, elementType, arrayModel.length)
    -        }
    -
    -        val array = newVar(arrayModel.classId, baseName) { initializer }
    -        valueByModelId[arrayModel.id] = array
    -
    -        if (canInitWithValues) {
    -            return array
    -        }
    -
    -        if (arrayModel.length <= 0) return array
    -        if (arrayModel.length == 1) {
    -            // take first element value if it is present, otherwise use default value from model
    -            val elementModel = arrayModel[0]
    -            if (elementModel isNotDefaultValueOf elementType) {
    -                array.setArrayElement(0, getOrCreateVariable(elementModel))
    -            }
    -        } else {
    -            val indexedValuesFromStores =
    -                if (arrayModel.stores.size == arrayModel.length) {
    -                    // do not use constModel because stores fully cover array
    -                    arrayModel.stores.entries.filter { (_, element) -> element isNotDefaultValueOf elementType }
    -                } else {
    -                    // fill array if constModel is not default type value
    -                    if (arrayModel.constModel isNotDefaultValueOf elementType) {
    -                        val defaultVariable = getOrCreateVariable(arrayModel.constModel, "defaultValue")
    -                        basicForLoop(arrayModel.length) { i ->
    -                            array.setArrayElement(i, defaultVariable)
    -                        }
    -                    }
    -
    -                    // choose all not default values
    -                    val defaultValue = if (arrayModel.constModel isDefaultValueOf elementType) {
    -                        arrayModel.constModel
    -                    } else {
    -                        elementType.defaultValueModel()
    -                    }
    -                    arrayModel.stores.entries.filter { (_, element) -> element != defaultValue }
    -                }
    -
    -            // set all values from stores manually
    -            indexedValuesFromStores
    -                .sortedBy { it.key }
    -                .forEach { (index, element) -> array.setArrayElement(index, getOrCreateVariable(element)) }
    -        }
    -
    -        return array
    -    }
    -
    -    // TODO: cannot be used now but will be useful in case of storing stores in generated code
    -    /**
    -     * Splits sorted by indices pairs of index and value from stores to continuous by index chunks
    -     * [indexedValuesFromStores] have to be sorted by key
    -     */
    -    private fun splitSettingFromStoresToForLoops(
    -        array: CgVariable,
    -        indexedValuesFromStores: List>
    -    ) {
    -        val ranges = mutableListOf()
    -
    -        var start = 0
    -        for (i in 0 until indexedValuesFromStores.lastIndex) {
    -            if (indexedValuesFromStores[i + 1].key - indexedValuesFromStores[i].key > 1) {
    -                ranges += start..i
    -                start = i + 1
    -            }
    -            if (i == indexedValuesFromStores.lastIndex - 1) {
    -                ranges += start..indexedValuesFromStores.lastIndex
    -            }
    -        }
    -
    -        for (range in ranges) {
    -            // IntRange stores end inclusively but sublist takes it exclusively
    -            setStoresRange(array, indexedValuesFromStores.subList(range.first, range.last + 1))
    -        }
    -    }
    -
    -    /**
    -     * [indexedValuesFromStores] have to be continuous sorted range
    -     */
    -    private fun setStoresRange(
    -        array: CgVariable,
    -        indexedValuesFromStores: List>
    -    ) {
    -        if (indexedValuesFromStores.size < 3) {
    -            // range is too small, better set manually
    -            indexedValuesFromStores.forEach { (index, element) ->
    -                array.setArrayElement(index, getOrCreateVariable(element))
    -            }
    -        } else {
    -            val minIndex = indexedValuesFromStores.first().key
    -            val maxIndex = indexedValuesFromStores.last().key
    -
    -            var indicesIndex = 0
    -            // we use until form of for loop so need to shift upper border
    -            basicForLoop(start = minIndex, until = maxIndex + 1) { i ->
    -                // use already sorted indices
    -                val (_, value) = indexedValuesFromStores[indicesIndex++]
    -                array.setArrayElement(i, getOrCreateVariable(value))
    -            }
    -        }
    -    }
    -
    -    private fun constructEnumConstant(model: UtEnumConstantModel, baseName: String?): CgVariable {
    -        return newVar(model.classId, baseName) {
    -            CgEnumConstantAccess(model.classId, model.value.name)
    -        }
    -    }
    -
    -    private fun constructClassRef(model: UtClassRefModel, baseName: String?): CgVariable {
    -        val classId = model.value.id
    -        val init = if (classId.isAccessibleFrom(testClassPackageName)) {
    -            CgGetJavaClass(classId)
    -        } else {
    -            classId[forName](classId.name)
    -        }
    -
    -        return newVar(Class::class.id, baseName) { init }
    -    }
    -
    -    /**
    -     * Either declares a new variable or gets it from context's cache
    -     * Returns the obtained variable
    -     */
    -    private fun declareOrGet(model: UtModel): CgValue = valueByModel[model] ?: getOrCreateVariable(model)
    -
    -    private fun basicForLoop(start: Any, until: Any, body: (i: CgExpression) -> Unit) {
    -        forLoop {
    -            val (i, init) = loopInitialization(intClassId, "i", start.resolve())
    -            initialization = init
    -            condition = i lessThan until.resolve()
    -            update = i.inc()
    -            statements = block { body(i) }
    -        }
    -    }
    -
    -    /**
    -     * A for-loop performing 'n' iterations starting with 0
    -     *
    -     * for (int i = 0; i < n; i++) {
    -     *     ...
    -     * }
    -     */
    -    private fun basicForLoop(until: Any, body: (i: CgExpression) -> Unit) {
    -        basicForLoop(start = 0, until, body)
    -    }
    -
    -    /**
    -     * Create loop initializer expression
    -     */
    -    @Suppress("SameParameterValue")
    -    internal fun loopInitialization(
    -        variableType: ClassId,
    -        baseVariableName: String,
    -        initializer: Any?
    -    ): Pair {
    -        val declaration = CgDeclaration(variableType, baseVariableName.toVarName(), initializer.resolve())
    -        val variable = declaration.variable
    -        updateVariableScope(variable)
    -        return variable to declaration
    -    }
    -
    -    /**
    -     * @receiver must represent a variable containing an array value.
    -     * If an array was created with reflection, then the variable is of [Object] type.
    -     * Otherwise, the variable is of the actual array type.
    -     *
    -     * Both cases are considered here.
    -     * If the variable is [Object], we use reflection method to set an element.
    -     * Otherwise, we set an element directly.
    -     */
    -    private fun CgVariable.setArrayElement(index: Any, value: CgValue) {
    -        val i = index.resolve()
    -        // we have to use reflection if we cannot easily cast array element to array type
    -        // (in case array does not have array type (maybe just object) or element is private class)
    -        if (!type.isArray || (type != value.type && !value.type.isAccessibleFrom(testClassPackageName))) {
    -            +java.lang.reflect.Array::class.id[setArrayElement](this, i, value)
    -        } else {
    -            val arrayElement = if (type == value.type) {
    -                value
    -            } else {
    -                typeCast(type.elementClassId!!, value, isSafetyCast = true)
    -            }
    -
    -            this.at(i) `=` arrayElement
    -        }
    -    }
    -
    -    internal fun constructVarName(baseName: String, isMock: Boolean = false): String =
    -        nameGenerator.variableName(baseName, isMock)
    -
    -    private fun String.toVarName(): String = nameGenerator.variableName(this)
    -
    -}
    -
    -private val Field.isPublic: Boolean
    -    get() = Modifier.isPublic(modifiers)
    -
    -private val Field.isPrivate: Boolean
    -    get() = Modifier.isPrivate(modifiers)
    -
    -val Field.isStatic: Boolean
    -    get() = Modifier.isStatic(modifiers)
    -
    -private val Field.isFinal: Boolean
    -    get() = Modifier.isFinal(modifiers)
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/MockFrameworkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/MockFrameworkManager.kt
    deleted file mode 100644
    index d22b2f07a1..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/MockFrameworkManager.kt
    +++ /dev/null
    @@ -1,400 +0,0 @@
    -package org.utbot.framework.codegen.model.constructor.tree
    -
    -import org.utbot.framework.codegen.MockitoStaticMocking
    -import org.utbot.framework.codegen.NoStaticMocking
    -import org.utbot.framework.codegen.model.constructor.builtin.any
    -import org.utbot.framework.codegen.model.constructor.builtin.anyBoolean
    -import org.utbot.framework.codegen.model.constructor.builtin.anyByte
    -import org.utbot.framework.codegen.model.constructor.builtin.anyChar
    -import org.utbot.framework.codegen.model.constructor.builtin.anyDouble
    -import org.utbot.framework.codegen.model.constructor.builtin.anyFloat
    -import org.utbot.framework.codegen.model.constructor.builtin.anyInt
    -import org.utbot.framework.codegen.model.constructor.builtin.anyLong
    -import org.utbot.framework.codegen.model.constructor.builtin.anyOfClass
    -import org.utbot.framework.codegen.model.constructor.builtin.anyShort
    -import org.utbot.framework.codegen.model.constructor.builtin.argumentMatchersClassId
    -import org.utbot.framework.codegen.model.constructor.builtin.forName
    -import org.utbot.framework.codegen.model.constructor.builtin.mockMethodId
    -import org.utbot.framework.codegen.model.constructor.builtin.mockedConstructionContextClassId
    -import org.utbot.framework.codegen.model.constructor.builtin.mockitoClassId
    -import org.utbot.framework.codegen.model.constructor.builtin.thenReturnMethodId
    -import org.utbot.framework.codegen.model.constructor.builtin.whenMethodId
    -import org.utbot.framework.codegen.model.constructor.context.CgContext
    -import org.utbot.framework.codegen.model.constructor.context.CgContextOwner
    -import org.utbot.framework.codegen.model.constructor.util.CgComponents
    -import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructor
    -import org.utbot.framework.codegen.model.constructor.util.CgStatementConstructorImpl
    -import org.utbot.framework.codegen.model.constructor.util.classCgClassId
    -import org.utbot.framework.codegen.model.constructor.util.hasAmbiguousOverloadsOf
    -import org.utbot.framework.codegen.model.tree.CgAnonymousFunction
    -import org.utbot.framework.codegen.model.tree.CgAssignment
    -import org.utbot.framework.codegen.model.tree.CgClassId
    -import org.utbot.framework.codegen.model.tree.CgConstructorCall
    -import org.utbot.framework.codegen.model.tree.CgDeclaration
    -import org.utbot.framework.codegen.model.tree.CgExecutableCall
    -import org.utbot.framework.codegen.model.tree.CgExpression
    -import org.utbot.framework.codegen.model.tree.CgGetJavaClass
    -import org.utbot.framework.codegen.model.tree.CgLiteral
    -import org.utbot.framework.codegen.model.tree.CgMethodCall
    -import org.utbot.framework.codegen.model.tree.CgParameterDeclaration
    -import org.utbot.framework.codegen.model.tree.CgRunnable
    -import org.utbot.framework.codegen.model.tree.CgStatement
    -import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall
    -import org.utbot.framework.codegen.model.tree.CgStaticRunnable
    -import org.utbot.framework.codegen.model.tree.CgSwitchCase
    -import org.utbot.framework.codegen.model.tree.CgSwitchCaseLabel
    -import org.utbot.framework.codegen.model.tree.CgValue
    -import org.utbot.framework.codegen.model.tree.CgVariable
    -import org.utbot.framework.codegen.model.util.isAccessibleFrom
    -import org.utbot.framework.plugin.api.ClassId
    -import org.utbot.framework.plugin.api.ConstructorId
    -import org.utbot.framework.plugin.api.ExecutableId
    -import org.utbot.framework.plugin.api.MethodId
    -import org.utbot.framework.plugin.api.TypeParameters
    -import org.utbot.framework.plugin.api.UtCompositeModel
    -import org.utbot.framework.plugin.api.UtModel
    -import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation
    -import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation
    -import org.utbot.framework.plugin.api.util.atomicIntegerClassId
    -import org.utbot.framework.plugin.api.util.atomicIntegerGet
    -import org.utbot.framework.plugin.api.util.atomicIntegerGetAndIncrement
    -import org.utbot.framework.plugin.api.util.booleanClassId
    -import org.utbot.framework.plugin.api.util.byteClassId
    -import org.utbot.framework.plugin.api.util.charClassId
    -import org.utbot.framework.plugin.api.util.doubleClassId
    -import org.utbot.framework.plugin.api.util.floatClassId
    -import org.utbot.framework.plugin.api.util.id
    -import org.utbot.framework.plugin.api.util.intClassId
    -import org.utbot.framework.plugin.api.util.longClassId
    -import org.utbot.framework.plugin.api.util.shortClassId
    -import org.utbot.framework.plugin.api.util.voidClassId
    -
    -internal abstract class CgVariableConstructorComponent(val context: CgContext) :
    -        CgContextOwner by context,
    -        CgCallableAccessManager by CgCallableAccessManagerImpl(context),
    -        CgStatementConstructor by CgStatementConstructorImpl(context) {
    -
    -    val variableConstructor: CgVariableConstructor by lazy { CgComponents.getVariableConstructorBy(context) }
    -
    -    fun mockitoArgumentMatchersFor(executable: ExecutableId): Array =
    -            executable.parameters.map {
    -                val matcher = it.mockitoAnyMatcher(executable.classId.hasAmbiguousOverloadsOf(executable))
    -                if (matcher != anyOfClass) argumentMatchersClassId[matcher]() else matchByClass(it)
    -            }.toTypedArray()
    -
    -    /**
    -     * Clears all resources required for [currentExecution].
    -     */
    -    open fun clearExecutionResources() {
    -        // do nothing by default
    -    }
    -
    -    // TODO: implement other similar methods like thenThrow, etc.
    -    fun CgMethodCall.thenReturn(returnType: ClassId, vararg args: CgValue) {
    -        val castedArgs = args
    -            // guard args to reuse typecast creation logic
    -            .map { if (it.type == returnType) it else guardExpression(returnType, it).expression }
    -            .toTypedArray()
    -
    -        +this[thenReturnMethodId](*castedArgs)
    -    }
    -
    -    fun ClassId.mockitoAnyMatcher(withExplicitClass: Boolean): MethodId =
    -            when (this) {
    -                byteClassId -> anyByte
    -                charClassId -> anyChar
    -                shortClassId -> anyShort
    -                intClassId -> anyInt
    -                longClassId -> anyLong
    -                floatClassId -> anyFloat
    -                doubleClassId -> anyDouble
    -                booleanClassId -> anyBoolean
    -                // we cannot match by string here
    -                // because anyString accepts only non-nullable strings but argument could be null
    -                else -> if (withExplicitClass) anyOfClass else any
    -            }
    -
    -    /**
    -     * @return expression for [java.lang.Class] of the given [classId]
    -     */
    -    protected fun getClassOf(classId: ClassId): CgExpression =
    -            if (classId isAccessibleFrom testClassPackageName) {
    -                CgGetJavaClass(classId)
    -            } else {
    -                newVar(classCgClassId) { Class::class.id[forName](classId.name) }
    -            }
    -
    -    private fun matchByClass(id: ClassId): CgMethodCall =
    -            argumentMatchersClassId[anyOfClass](getClassOf(id))
    -}
    -
    -internal class MockFrameworkManager(context: CgContext) : CgVariableConstructorComponent(context) {
    -
    -    private val objectMocker = MockitoMocker(context)
    -    private val staticMocker = when (context.staticsMocking) {
    -        is NoStaticMocking -> null
    -        is MockitoStaticMocking -> MockitoStaticMocker(context, objectMocker)
    -    }
    -
    -    /**
    -     * Precondition: in the given [model] flag [UtCompositeModel.isMock] must be true.
    -     * @return a variable representing a created mock object.
    -     */
    -    fun createMockFor(model: UtCompositeModel, baseName: String): CgVariable = withMockFramework {
    -        require(model.isMock) { "Mock model is expected in MockObjectConstructor" }
    -
    -        objectMocker.createMock(model, baseName)
    -    }
    -
    -    fun mockNewInstance(mock: UtNewInstanceInstrumentation) {
    -        staticMocker?.mockNewInstance(mock)
    -    }
    -
    -    fun mockStaticMethodsOfClass(classId: ClassId, methodMocks: List) {
    -        staticMocker?.mockStaticMethodsOfClass(classId, methodMocks)
    -    }
    -
    -    override fun clearExecutionResources() {
    -        staticMocker?.clearExecutionResources()
    -    }
    -
    -    internal fun getAndClearMethodResources(): List? =
    -        if (staticMocker is MockitoStaticMocker) staticMocker.copyAndClearMockResources() else null
    -}
    -
    -private abstract class ObjectMocker(
    -        context: CgContext
    -) : CgVariableConstructorComponent(context) {
    -    abstract fun createMock(model: UtCompositeModel, baseName: String): CgVariable
    -
    -    abstract fun mock(clazz: CgExpression): CgMethodCall
    -
    -    abstract fun `when`(call: CgExecutableCall): CgMethodCall
    -}
    -
    -private abstract class StaticMocker(
    -        context: CgContext
    -) : CgVariableConstructorComponent(context) {
    -    abstract fun mockNewInstance(mock: UtNewInstanceInstrumentation)
    -    abstract fun mockStaticMethodsOfClass(classId: ClassId, methodMocks: List)
    -}
    -
    -private class MockitoMocker(context: CgContext) : ObjectMocker(context) {
    -    override fun createMock(model: UtCompositeModel, baseName: String): CgVariable {
    -        // create mock object
    -        val modelClass = getClassOf(model.classId)
    -        val mockObject = newVar(model.classId, baseName = baseName, isMock = true) { mock(modelClass) }
    -
    -        for ((executable, values) in model.mocks) {
    -            // void method
    -            if (executable.returnType == voidClassId) {
    -                // void methods on mocks do nothing by default
    -                continue
    -            }
    -
    -            when (executable) {
    -                is MethodId -> {
    -                    if (executable.parameters.any { !it.isAccessibleFrom(testClassPackageName) }) {
    -                        error("Cannot mock method $executable with not accessible parameters" )
    -                    }
    -
    -                    val matchers = mockitoArgumentMatchersFor(executable)
    -                    if (!executable.isAccessibleFrom(testClassPackageName)) {
    -                        error("Cannot mock method $executable as it is not accessible from package $testClassPackageName")
    -                    }
    -
    -                    val results = values.map { variableConstructor.getOrCreateVariable(it) }.toTypedArray()
    -                    `when`(mockObject[executable](*matchers)).thenReturn(executable.returnType, *results)
    -                }
    -                else -> error("ConstructorId was not expected to appear in simple mocker but got $executable")
    -            }
    -        }
    -
    -        return mockObject
    -    }
    -
    -    override fun mock(clazz: CgExpression): CgMethodCall =
    -            mockitoClassId[mockMethodId](clazz)
    -
    -    override fun `when`(call: CgExecutableCall): CgMethodCall =
    -            mockitoClassId[whenMethodId](call)
    -}
    -
    -private class MockitoStaticMocker(context: CgContext, private val mocker: ObjectMocker) : StaticMocker(context) {
    -    private val resources = mutableListOf()
    -    private val mockedStaticForMethods = mutableMapOf()
    -    private val mockedStaticConstructions = mutableSetOf()
    -
    -    override fun mockNewInstance(mock: UtNewInstanceInstrumentation) {
    -        val classId = mock.classId
    -        if (classId in mockedStaticConstructions) return
    -
    -        val mockClassCounter = CgDeclaration(
    -            atomicIntegerClassId,
    -            variableConstructor.constructVarName(MOCK_CLASS_COUNTER_NAME),
    -            CgConstructorCall(ConstructorId(atomicIntegerClassId, emptyList()), emptyList())
    -        )
    -        +mockClassCounter
    -
    -        val mocksExecutablesAnswers = mock
    -            .instances
    -            .filterIsInstance()
    -            .filter { it.isMock }
    -            .map { it.mocks }
    -
    -        val modelClass = getClassOf(classId)
    -
    -        val mockConstructionInitializer = mockConstruction(
    -            modelClass,
    -            classId,
    -            mocksExecutablesAnswers,
    -            mockClassCounter.variable
    -        )
    -        val mockedConstructionDeclaration = CgDeclaration(
    -            CgClassId(MockitoStaticMocking.mockedConstructionClassId),
    -            variableConstructor.constructVarName(MOCKED_CONSTRUCTION_NAME),
    -            mockConstructionInitializer
    -        )
    -        resources += mockedConstructionDeclaration
    -        +CgAssignment(mockedConstructionDeclaration.variable, mockConstructionInitializer)
    -        mockedStaticConstructions += classId
    -    }
    -
    -    override fun mockStaticMethodsOfClass(classId: ClassId, methodMocks: List) {
    -        for ((methodId, values) in methodMocks) {
    -            if (methodId.parameters.any { !it.isAccessibleFrom(testClassPackageName) }) {
    -                error("Cannot mock static method $methodId with not accessible parameters" )
    -            }
    -
    -            val matchers = mockitoArgumentMatchersFor(methodId)
    -            val mockedStaticDeclaration = getOrCreateMockStatic(classId)
    -            val mockedStaticVariable = mockedStaticDeclaration.variable
    -            val methodRunnable = if (matchers.isEmpty()) {
    -                CgStaticRunnable(type = methodId.returnType, classId, methodId)
    -            } else {
    -                CgAnonymousFunction(
    -                    type = methodId.returnType,
    -                    parameters = emptyList(),
    -                    listOf(CgStatementExecutableCall(CgMethodCall(
    -                        caller = null,
    -                        methodId,
    -                        matchers.toList()
    -                    )))
    -                )
    -            }
    -            // void method
    -            if (methodId.returnType == voidClassId) {
    -                // we do not generate additional code for void methods because they do nothing by default
    -                continue
    -            }
    -
    -            if (!methodId.isAccessibleFrom(testClassPackageName)) {
    -                error("Cannot mock static method $methodId as it is not accessible from package $testClassPackageName")
    -            }
    -
    -            val results = values.map { variableConstructor.getOrCreateVariable(it) }.toTypedArray()
    -            `when`(mockedStaticVariable, methodRunnable).thenReturn(methodId.returnType, *results)
    -        }
    -    }
    -
    -    override fun clearExecutionResources() {
    -        resources.clear()
    -        mockedStaticForMethods.clear()
    -        mockedStaticConstructions.clear()
    -    }
    -
    -    private fun getOrCreateMockStatic(classId: ClassId): CgDeclaration =
    -        mockedStaticForMethods.getOrPut(classId) {
    -            val modelClass = getClassOf(classId)
    -            val classMockStaticCall = mockStatic(modelClass)
    -            val mockedStaticVariableName = variableConstructor.constructVarName(MOCKED_STATIC_NAME)
    -            CgDeclaration(
    -                CgClassId(MockitoStaticMocking.mockedStaticClassId),
    -                mockedStaticVariableName,
    -                classMockStaticCall
    -            ).also {
    -                resources += it
    -                +CgAssignment(it.variable, classMockStaticCall)
    -            }
    -        }
    -
    -    private fun mockConstruction(
    -        clazz: CgExpression,
    -        classId: ClassId,
    -        mocksWhenAnswers: List>>,
    -        mockClassCounter: CgVariable
    -    ): CgMethodCall {
    -        val mockParameter = variableConstructor.declareParameter(
    -            classId,
    -            variableConstructor.constructVarName(classId.simpleName, isMock = true)
    -        )
    -        val contextParameter = variableConstructor.declareParameter(
    -            mockedConstructionContextClassId,
    -            variableConstructor.constructVarName("context")
    -        )
    -
    -        val caseLabels = mutableListOf()
    -        for ((index, mockWhenAnswers) in mocksWhenAnswers.withIndex()) {
    -            val statements = mutableListOf()
    -            for ((executable, values) in mockWhenAnswers) {
    -                if (executable.returnType == voidClassId) continue
    -
    -                when (executable) {
    -                    is MethodId -> {
    -                        val matchers = mockitoArgumentMatchersFor(executable)
    -                        val results = values.map { variableConstructor.getOrCreateVariable(it) }.toTypedArray()
    -                        statements += CgStatementExecutableCall(
    -                            mocker.`when`(mockParameter[executable](*matchers))[thenReturnMethodId](*results)
    -                        )
    -                    }
    -                    else -> error("Expected MethodId but got ConstructorId $executable")
    -                }
    -            }
    -
    -            caseLabels += CgSwitchCaseLabel(CgLiteral(intClassId, index), statements)
    -        }
    -
    -        val switchCase = CgSwitchCase(mockClassCounter[atomicIntegerGet](), caseLabels)
    -
    -        val answersBlock = CgAnonymousFunction(
    -            voidClassId,
    -            listOf(mockParameter, contextParameter).map { CgParameterDeclaration(it, isVararg = false) },
    -            listOf(switchCase, CgStatementExecutableCall(mockClassCounter[atomicIntegerGetAndIncrement]()))
    -        )
    -
    -        return mockitoClassId[MockitoStaticMocking.mockConstructionMethodId](clazz, answersBlock)
    -    }
    -
    -    private fun mockStatic(clazz: CgExpression): CgMethodCall =
    -        mockitoClassId[MockitoStaticMocking.mockStaticMethodId](clazz)
    -
    -    private fun `when`(
    -        mockedStatic: CgVariable,
    -        runnable: CgExpression,
    -    ): CgMethodCall {
    -        val typeParams = when (runnable) {
    -            is CgRunnable, is CgAnonymousFunction -> listOf(runnable.type)
    -            else -> error("Unsupported runnable type: $runnable")
    -        }
    -        return CgMethodCall(
    -            mockedStatic,
    -            MockitoStaticMocking.mockedStaticWhen(nullable = mockedStatic.type.isNullable),
    -            listOf(runnable),
    -            TypeParameters(typeParams),
    -        )
    -    }
    -
    -
    -    fun copyAndClearMockResources(): List? {
    -        val copiedResources = resources.toList()
    -        clearExecutionResources()
    -
    -        return copiedResources.ifEmpty { null }
    -    }
    -
    -    companion object {
    -        private const val MOCKED_CONSTRUCTION_NAME = "mockedConstruction"
    -        private const val MOCKED_STATIC_NAME = "mockedStatic"
    -        private const val MOCK_CLASS_COUNTER_NAME = "mockClassCounter"
    -    }
    -}
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/TestFrameworkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/TestFrameworkManager.kt
    deleted file mode 100644
    index 7b8ed610b9..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/tree/TestFrameworkManager.kt
    +++ /dev/null
    @@ -1,370 +0,0 @@
    -package org.utbot.framework.codegen.model.constructor.tree
    -
    -import org.utbot.framework.codegen.Junit4
    -import org.utbot.framework.codegen.Junit5
    -import org.utbot.framework.codegen.TestNg
    -import org.utbot.framework.codegen.model.constructor.builtin.arraysDeepEqualsMethodId
    -import org.utbot.framework.codegen.model.constructor.builtin.deepEqualsMethodId
    -import org.utbot.framework.codegen.model.constructor.builtin.forName
    -import org.utbot.framework.codegen.model.constructor.builtin.hasCustomEqualsMethodId
    -import org.utbot.framework.codegen.model.constructor.builtin.iterablesDeepEqualsMethodId
    -import org.utbot.framework.codegen.model.constructor.builtin.mapsDeepEqualsMethodId
    -import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMethodId
    -import org.utbot.framework.codegen.model.constructor.context.CgContext
    -import org.utbot.framework.codegen.model.constructor.context.CgContextOwner
    -import org.utbot.framework.codegen.model.constructor.util.CgComponents
    -import org.utbot.framework.codegen.model.constructor.util.classCgClassId
    -import org.utbot.framework.codegen.model.constructor.util.importIfNeeded
    -import org.utbot.framework.codegen.model.tree.CgCommentedAnnotation
    -import org.utbot.framework.codegen.model.tree.CgEnumConstantAccess
    -import org.utbot.framework.codegen.model.tree.CgExpression
    -import org.utbot.framework.codegen.model.tree.CgGetJavaClass
    -import org.utbot.framework.codegen.model.tree.CgLiteral
    -import org.utbot.framework.codegen.model.tree.CgMethodCall
    -import org.utbot.framework.codegen.model.tree.CgMultipleArgsAnnotation
    -import org.utbot.framework.codegen.model.tree.CgNamedAnnotationArgument
    -import org.utbot.framework.codegen.model.tree.CgSingleArgAnnotation
    -import org.utbot.framework.codegen.model.tree.CgValue
    -import org.utbot.framework.codegen.model.util.classLiteralAnnotationArgument
    -import org.utbot.framework.codegen.model.util.isAccessibleFrom
    -import org.utbot.framework.codegen.model.util.resolve
    -import org.utbot.framework.codegen.model.util.stringLiteral
    -import org.utbot.framework.plugin.api.BuiltinMethodId
    -import org.utbot.framework.plugin.api.ClassId
    -import org.utbot.framework.plugin.api.util.booleanArrayClassId
    -import org.utbot.framework.plugin.api.util.byteArrayClassId
    -import org.utbot.framework.plugin.api.util.charArrayClassId
    -import org.utbot.framework.plugin.api.util.doubleArrayClassId
    -import org.utbot.framework.plugin.api.util.floatArrayClassId
    -import org.utbot.framework.plugin.api.util.id
    -import org.utbot.framework.plugin.api.util.intArrayClassId
    -import org.utbot.framework.plugin.api.util.longArrayClassId
    -import org.utbot.framework.plugin.api.util.shortArrayClassId
    -import java.util.concurrent.TimeUnit
    -
    -@Suppress("MemberVisibilityCanBePrivate")
    -internal abstract class TestFrameworkManager(val context: CgContext)
    -    : CgContextOwner by context,
    -        CgCallableAccessManager by CgComponents.getCallableAccessManagerBy(context) {
    -
    -    val assertions = context.testFramework.assertionsClass
    -
    -    val assertEquals = context.testFramework.assertEquals
    -    val assertFloatEquals = context.testFramework.assertFloatEquals
    -    val assertDoubleEquals = context.testFramework.assertDoubleEquals
    -
    -    val assertNull = context.testFramework.assertNull
    -    val assertTrue = context.testFramework.assertTrue
    -    val assertFalse = context.testFramework.assertFalse
    -
    -    val assertArrayEquals = context.testFramework.assertArrayEquals
    -    val assertBooleanArrayEquals = context.testFramework.assertBooleanArrayEquals
    -    val assertByteArrayEquals = context.testFramework.assertByteArrayEquals
    -    val assertCharArrayEquals = context.testFramework.assertCharArrayEquals
    -    val assertShortArrayEquals = context.testFramework.assertShortArrayEquals
    -    val assertIntArrayEquals = context.testFramework.assertIntArrayEquals
    -    val assertLongArrayEquals = context.testFramework.assertLongArrayEquals
    -    val assertFloatArrayEquals = context.testFramework.assertFloatArrayEquals
    -    val assertDoubleArrayEquals = context.testFramework.assertDoubleArrayEquals
    -
    -    protected val statementConstructor = CgComponents.getStatementConstructorBy(context)
    -
    -    protected open val timeoutArgumentName: String = "timeout"
    -
    -    open fun assertEquals(expected: CgValue, actual: CgValue) {
    -        +assertions[assertEquals](expected, actual)
    -    }
    -
    -    open fun assertFloatEquals(expected: CgExpression, actual: CgExpression, delta: Any) {
    -        +assertions[assertFloatEquals](expected, actual, delta)
    -    }
    -
    -    open fun assertDoubleEquals(expected: CgExpression, actual: CgExpression, delta: Any) {
    -        +assertions[assertDoubleEquals](expected, actual, delta)
    -    }
    -
    -    open fun getArrayEqualsAssertion(arrayType: ClassId, expected: CgExpression, actual: CgExpression): CgMethodCall =
    -        when (arrayType) {
    -            booleanArrayClassId -> assertions[assertBooleanArrayEquals](expected, actual)
    -            byteArrayClassId -> assertions[assertByteArrayEquals](expected, actual)
    -            charArrayClassId -> assertions[assertCharArrayEquals](expected, actual)
    -            shortArrayClassId -> assertions[assertShortArrayEquals](expected, actual)
    -            intArrayClassId -> assertions[assertIntArrayEquals](expected, actual)
    -            longArrayClassId -> assertions[assertLongArrayEquals](expected, actual)
    -            floatArrayClassId -> assertions[assertFloatArrayEquals](expected, actual)
    -            doubleArrayClassId -> assertions[assertDoubleArrayEquals](expected, actual)
    -            else -> assertions[assertArrayEquals](expected, actual)
    -        }
    -
    -    fun assertArrayEquals(arrayType: ClassId, expected: CgExpression, actual: CgExpression) {
    -        +getArrayEqualsAssertion(arrayType, expected, actual)
    -    }
    -
    -    open fun getDeepEqualsAssertion(expected: CgExpression, actual: CgExpression): CgMethodCall {
    -        requiredUtilMethods += currentTestClass.deepEqualsMethodId
    -        requiredUtilMethods += currentTestClass.arraysDeepEqualsMethodId
    -        requiredUtilMethods += currentTestClass.iterablesDeepEqualsMethodId
    -        requiredUtilMethods += currentTestClass.streamsDeepEqualsMethodId
    -        requiredUtilMethods += currentTestClass.mapsDeepEqualsMethodId
    -        requiredUtilMethods += currentTestClass.hasCustomEqualsMethodId
    -
    -        // TODO we cannot use common assertEquals because of using custom deepEquals
    -        //  For this reason we have to use assertTrue here
    -        //  Unfortunately, if test with assertTrue fails, it gives non informative message false != true
    -        //  Thus, we need to provide custom message to assertTrue showing compared objects correctly
    -        //  SAT-1345
    -        return assertions[assertTrue](testClassThisInstance[deepEquals](expected, actual))
    -    }
    -
    -    @Suppress("unused")
    -    fun assertDeepEquals(expected: CgExpression, actual: CgExpression) {
    -        +getDeepEqualsAssertion(expected, actual)
    -    }
    -
    -    open fun getFloatArrayEqualsAssertion(expected: CgExpression, actual: CgExpression, delta: Any): CgMethodCall =
    -        assertions[assertFloatArrayEquals](expected, actual, delta)
    -
    -    fun assertFloatArrayEquals(expected: CgExpression, actual: CgExpression, delta: Any) {
    -        +getFloatArrayEqualsAssertion(expected, actual, delta)
    -    }
    -
    -    open fun getDoubleArrayEqualsAssertion(expected: CgExpression, actual: CgExpression, delta: Any): CgMethodCall =
    -        assertions[assertDoubleArrayEquals](expected, actual, delta)
    -
    -    fun assertDoubleArrayEquals(expected: CgExpression, actual: CgExpression, delta: Any) {
    -        +getDoubleArrayEqualsAssertion(expected, actual, delta)
    -    }
    -
    -    fun assertNull(actual: CgExpression) {
    -        +assertions[assertNull](actual)
    -    }
    -
    -    fun assertBoolean(expected: Boolean, actual: CgExpression) {
    -        if (expected) {
    -            +assertions[assertTrue](actual)
    -        } else {
    -            +assertions[assertFalse](actual)
    -        }
    -    }
    -
    -    fun assertBoolean(actual: CgExpression) = assertBoolean(expected = true, actual)
    -
    -    // Exception expectation differs between test frameworks
    -    // JUnit4 requires to add a specific argument to the test method annotation
    -    // JUnit5 requires using method assertThrows()
    -    // TestNg allows both approaches, we use similar to JUnit5
    -    abstract fun expectException(exception: ClassId, block: () -> Unit)
    -
    -    open fun setTestExecutionTimeout(timeoutMs: Long) {
    -        val timeout = CgNamedAnnotationArgument(
    -            name = timeoutArgumentName,
    -            value = timeoutMs.resolve()
    -        )
    -        val testAnnotation = collectedTestMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId }
    -
    -        if (testAnnotation is CgMultipleArgsAnnotation) {
    -            testAnnotation.arguments += timeout
    -        } else {
    -            collectedTestMethodAnnotations += CgMultipleArgsAnnotation(
    -                testFramework.testAnnotationId,
    -                mutableListOf(timeout)
    -            )
    -        }
    -    }
    -
    -    abstract fun disableTestMethod(reason: String)
    -
    -    // We add a commented JUnit5 DisplayName annotation here by default,
    -    // because other test frameworks do not support such feature.
    -    open fun addDisplayName(name: String) {
    -        val displayName = CgSingleArgAnnotation(Junit5.displayNameClassId, stringLiteral(name))
    -        collectedTestMethodAnnotations += CgCommentedAnnotation(displayName)
    -    }
    -
    -    protected fun ClassId.toExceptionClass(): CgExpression =
    -            if (isAccessibleFrom(testClassPackageName)) {
    -                CgGetJavaClass(this)
    -            } else {
    -                statementConstructor.newVar(classCgClassId) { Class::class.id[forName](name) }
    -            }
    -}
    -
    -internal class TestNgManager(context: CgContext) : TestFrameworkManager(context) {
    -    override val timeoutArgumentName: String = "timeOut"
    -
    -    private val assertThrows: BuiltinMethodId
    -        get() {
    -            require(testFramework is TestNg) { "According to settings, TestNg was expected, but got: $testFramework" }
    -
    -            return testFramework.assertThrows
    -        }
    -
    -    override fun assertEquals(expected: CgValue, actual: CgValue) = super.assertEquals(actual, expected)
    -
    -    override fun assertFloatEquals(expected: CgExpression, actual: CgExpression, delta: Any) =
    -            super.assertFloatEquals(actual, expected, delta)
    -
    -    override fun assertDoubleEquals(expected: CgExpression, actual: CgExpression, delta: Any) =
    -            super.assertDoubleEquals(actual, expected, delta)
    -
    -    override fun getArrayEqualsAssertion(arrayType: ClassId, expected: CgExpression, actual: CgExpression): CgMethodCall =
    -            super.getArrayEqualsAssertion(arrayType, actual, expected)
    -
    -    override fun getDeepEqualsAssertion(expected: CgExpression, actual: CgExpression): CgMethodCall =
    -            super.getDeepEqualsAssertion(actual, expected)
    -
    -    override fun getFloatArrayEqualsAssertion(expected: CgExpression, actual: CgExpression, delta: Any): CgMethodCall =
    -            super.getFloatArrayEqualsAssertion(actual, expected, delta)
    -
    -    override fun getDoubleArrayEqualsAssertion(expected: CgExpression, actual: CgExpression, delta: Any): CgMethodCall =
    -            super.getDoubleArrayEqualsAssertion(actual, expected, delta)
    -
    -    override fun expectException(exception: ClassId, block: () -> Unit) {
    -        require(testFramework is TestNg) { "According to settings, TestNg was expected, but got: $testFramework" }
    -        val lambda = statementConstructor.lambda(testFramework.throwingRunnableClassId) { block() }
    -        +assertions[assertThrows](exception.toExceptionClass(), lambda)
    -    }
    -
    -    override fun disableTestMethod(reason: String) {
    -        require(testFramework is TestNg) { "According to settings, TestNg was expected, but got: $testFramework" }
    -
    -        val disabledAnnotationArgument = CgNamedAnnotationArgument(
    -            name = "enabled",
    -            value = false.resolve()
    -        )
    -
    -        val descriptionArgumentName = "description"
    -        val descriptionTestAnnotationArgument = CgNamedAnnotationArgument(
    -            name = descriptionArgumentName,
    -            value = reason.resolve()
    -        )
    -
    -        val testAnnotation = collectedTestMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId }
    -        if (testAnnotation is CgMultipleArgsAnnotation) {
    -            testAnnotation.arguments += disabledAnnotationArgument
    -
    -            val alreadyExistingDescriptionAnnotationArgument = testAnnotation.arguments.singleOrNull {
    -                it.name == descriptionArgumentName
    -            }
    -
    -            // append new description to existing one
    -            if (alreadyExistingDescriptionAnnotationArgument != null) {
    -                val gluedDescription = with(alreadyExistingDescriptionAnnotationArgument) {
    -                    require(value is CgLiteral && value.value is String) {
    -                        "Expected description to be String literal but got ${value.type}"
    -                    }
    -
    -                    listOf(value.value, reason).joinToString("; ").resolve()
    -                }
    -
    -                testAnnotation.arguments.map {
    -                    if (it.name != descriptionArgumentName) return@map it
    -
    -                    CgNamedAnnotationArgument(
    -                        name = descriptionArgumentName,
    -                        value = gluedDescription
    -                    )
    -                }
    -            } else {
    -                testAnnotation.arguments += descriptionTestAnnotationArgument
    -            }
    -        } else {
    -            collectedTestMethodAnnotations += CgMultipleArgsAnnotation(
    -                testFramework.testAnnotationId,
    -                mutableListOf(disabledAnnotationArgument, descriptionTestAnnotationArgument)
    -            )
    -        }
    -    }
    -}
    -
    -internal class Junit4Manager(context: CgContext) : TestFrameworkManager(context) {
    -    override fun expectException(exception: ClassId, block: () -> Unit) {
    -        require(testFramework is Junit4) { "According to settings, JUnit4 was expected, but got: $testFramework" }
    -
    -        require(exception.isAccessibleFrom(testClassPackageName)) {
    -            "Exception $exception is not accessible from package $testClassPackageName"
    -        }
    -
    -        val expected = CgNamedAnnotationArgument(
    -            name = "expected",
    -            value = classLiteralAnnotationArgument(exception, codegenLanguage)
    -        )
    -        val testAnnotation = collectedTestMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId }
    -        if (testAnnotation is CgMultipleArgsAnnotation) {
    -            testAnnotation.arguments += expected
    -        } else {
    -            collectedTestMethodAnnotations += CgMultipleArgsAnnotation(testFramework.testAnnotationId, mutableListOf(expected))
    -        }
    -        block()
    -    }
    -
    -    override fun disableTestMethod(reason: String) {
    -        require(testFramework is Junit4) { "According to settings, JUnit4 was expected, but got: $testFramework" }
    -
    -        collectedTestMethodAnnotations += CgMultipleArgsAnnotation(
    -            testFramework.ignoreAnnotationClassId,
    -            mutableListOf(
    -                CgNamedAnnotationArgument(
    -                    name = "value",
    -                    value = reason.resolve()
    -                )
    -            )
    -        )
    -    }
    -}
    -
    -internal class Junit5Manager(context: CgContext) : TestFrameworkManager(context) {
    -    private val assertThrows: BuiltinMethodId
    -        get() {
    -            require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" }
    -
    -            return testFramework.assertThrows
    -        }
    -
    -    override fun expectException(exception: ClassId, block: () -> Unit) {
    -        require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" }
    -        val lambda = statementConstructor.lambda(testFramework.executableClassId) { block() }
    -        +assertions[assertThrows](exception.toExceptionClass(), lambda)
    -    }
    -
    -    override fun addDisplayName(name: String) {
    -        require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" }
    -        collectedTestMethodAnnotations += statementConstructor.annotation(testFramework.displayNameClassId, name)
    -    }
    -
    -    override fun setTestExecutionTimeout(timeoutMs: Long) {
    -        require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" }
    -
    -        val timeoutAnnotationArguments = mutableListOf()
    -        timeoutAnnotationArguments += CgNamedAnnotationArgument(
    -            name = "value",
    -            value = timeoutMs.resolve()
    -        )
    -
    -        val milliseconds = TimeUnit.MILLISECONDS
    -        timeoutAnnotationArguments += CgNamedAnnotationArgument(
    -            name = "unit",
    -            value = CgEnumConstantAccess(testFramework.timeunitClassId, milliseconds.name)
    -        )
    -        importIfNeeded(testFramework.timeunitClassId)
    -
    -        collectedTestMethodAnnotations += CgMultipleArgsAnnotation(
    -            Junit5.timeoutClassId,
    -            timeoutAnnotationArguments
    -        )
    -    }
    -
    -    override fun disableTestMethod(reason: String) {
    -        require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" }
    -
    -        collectedTestMethodAnnotations += CgMultipleArgsAnnotation(
    -            testFramework.disabledAnnotationClassId,
    -            mutableListOf(
    -                CgNamedAnnotationArgument(
    -                    name = "value",
    -                    value = reason.resolve()
    -                )
    -            )
    -        )
    -    }
    -}
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgComponents.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgComponents.kt
    deleted file mode 100644
    index f31c528fb6..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgComponents.kt
    +++ /dev/null
    @@ -1,59 +0,0 @@
    -package org.utbot.framework.codegen.model.constructor.util
    -
    -import org.utbot.framework.codegen.Junit4
    -import org.utbot.framework.codegen.Junit5
    -import org.utbot.framework.codegen.TestNg
    -import org.utbot.framework.codegen.model.constructor.context.CgContext
    -import org.utbot.framework.codegen.model.constructor.name.CgNameGenerator
    -import org.utbot.framework.codegen.model.constructor.name.CgNameGeneratorImpl
    -import org.utbot.framework.codegen.model.constructor.tree.CgCallableAccessManager
    -import org.utbot.framework.codegen.model.constructor.tree.CgCallableAccessManagerImpl
    -import org.utbot.framework.codegen.model.constructor.tree.CgMethodConstructor
    -import org.utbot.framework.codegen.model.constructor.tree.CgTestClassConstructor
    -import org.utbot.framework.codegen.model.constructor.tree.CgVariableConstructor
    -import org.utbot.framework.codegen.model.constructor.tree.CgFieldStateManager
    -import org.utbot.framework.codegen.model.constructor.tree.CgFieldStateManagerImpl
    -import org.utbot.framework.codegen.model.constructor.tree.Junit4Manager
    -import org.utbot.framework.codegen.model.constructor.tree.Junit5Manager
    -import org.utbot.framework.codegen.model.constructor.tree.MockFrameworkManager
    -import org.utbot.framework.codegen.model.constructor.tree.TestFrameworkManager
    -import org.utbot.framework.codegen.model.constructor.tree.TestNgManager
    -
    -// TODO: probably rewrite it to delegates so that we could write 'val testFrameworkManager by CgComponents' etc.
    -internal object CgComponents {
    -    fun getNameGeneratorBy(context: CgContext) = nameGenerators.getOrPut(context) { CgNameGeneratorImpl(context) }
    -
    -    fun getCallableAccessManagerBy(context: CgContext) =
    -            callableAccessManagers.getOrPut(context) { CgCallableAccessManagerImpl(context) }
    -
    -    fun getStatementConstructorBy(context: CgContext) =
    -            statementConstructors.getOrPut(context) { CgStatementConstructorImpl(context) }
    -
    -    fun getTestFrameworkManagerBy(context: CgContext) = when (context.testFramework) {
    -        is Junit4 -> testFrameworkManagers.getOrPut(context) { Junit4Manager(context) }
    -        is Junit5 -> testFrameworkManagers.getOrPut(context) { Junit5Manager(context) }
    -        is TestNg -> testFrameworkManagers.getOrPut(context) { TestNgManager(context) }
    -    }
    -
    -    fun getMockFrameworkManagerBy(context: CgContext) =
    -            mockFrameworkManagers.getOrPut(context) { MockFrameworkManager(context) }
    -
    -    fun getFieldStateManagerBy(context: CgContext) =
    -            fieldStateManagers.getOrPut(context) { CgFieldStateManagerImpl(context) }
    -
    -    fun getVariableConstructorBy(context: CgContext) = variableConstructors.getOrPut(context) { CgVariableConstructor(context) }
    -
    -    fun getMethodConstructorBy(context: CgContext) = methodConstructors.getOrPut(context) { CgMethodConstructor(context) }
    -    fun getTestClassConstructorBy(context: CgContext) = testClassConstructors.getOrPut(context) { CgTestClassConstructor(context) }
    -
    -    private val nameGenerators: MutableMap = mutableMapOf()
    -    private val statementConstructors: MutableMap = mutableMapOf()
    -    private val callableAccessManagers: MutableMap = mutableMapOf()
    -    private val testFrameworkManagers: MutableMap = mutableMapOf()
    -    private val mockFrameworkManagers: MutableMap = mutableMapOf()
    -    private val fieldStateManagers: MutableMap = mutableMapOf()
    -
    -    private val variableConstructors: MutableMap = mutableMapOf()
    -    private val methodConstructors: MutableMap = mutableMapOf()
    -    private val testClassConstructors: MutableMap = mutableMapOf()
    -}
    \ No newline at end of file
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt
    deleted file mode 100644
    index 97a94e313b..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/CgStatementConstructor.kt
    +++ /dev/null
    @@ -1,491 +0,0 @@
    -package org.utbot.framework.codegen.model.constructor.util
    -
    -import org.utbot.framework.codegen.model.constructor.builtin.forName
    -import org.utbot.framework.codegen.model.constructor.builtin.mockMethodId
    -import org.utbot.framework.codegen.model.constructor.context.CgContext
    -import org.utbot.framework.codegen.model.constructor.context.CgContextOwner
    -import org.utbot.framework.codegen.model.constructor.tree.CgCallableAccessManager
    -import org.utbot.framework.codegen.model.tree.CgAllocateArray
    -import org.utbot.framework.codegen.model.tree.CgAnnotation
    -import org.utbot.framework.codegen.model.tree.CgAnonymousFunction
    -import org.utbot.framework.codegen.model.tree.CgComment
    -import org.utbot.framework.codegen.model.tree.CgDeclaration
    -import org.utbot.framework.codegen.model.tree.CgEmptyLine
    -import org.utbot.framework.codegen.model.tree.CgEnumConstantAccess
    -import org.utbot.framework.codegen.model.tree.CgErrorWrapper
    -import org.utbot.framework.codegen.model.tree.CgExecutableCall
    -import org.utbot.framework.codegen.model.tree.CgExpression
    -import org.utbot.framework.codegen.model.tree.CgForEachLoopBuilder
    -import org.utbot.framework.codegen.model.tree.CgForLoopBuilder
    -import org.utbot.framework.codegen.model.tree.CgGetClass
    -import org.utbot.framework.codegen.model.tree.CgIfStatement
    -import org.utbot.framework.codegen.model.tree.CgInnerBlock
    -import org.utbot.framework.codegen.model.tree.CgLiteral
    -import org.utbot.framework.codegen.model.tree.CgLogicalAnd
    -import org.utbot.framework.codegen.model.tree.CgLogicalOr
    -import org.utbot.framework.codegen.model.tree.CgMethodCall
    -import org.utbot.framework.codegen.model.tree.CgMultilineComment
    -import org.utbot.framework.codegen.model.tree.CgMultipleArgsAnnotation
    -import org.utbot.framework.codegen.model.tree.CgNamedAnnotationArgument
    -import org.utbot.framework.codegen.model.tree.CgParameterDeclaration
    -import org.utbot.framework.codegen.model.tree.CgReturnStatement
    -import org.utbot.framework.codegen.model.tree.CgSingleArgAnnotation
    -import org.utbot.framework.codegen.model.tree.CgSingleLineComment
    -import org.utbot.framework.codegen.model.tree.CgStatement
    -import org.utbot.framework.codegen.model.tree.CgThrowStatement
    -import org.utbot.framework.codegen.model.tree.CgTryCatch
    -import org.utbot.framework.codegen.model.tree.CgVariable
    -import org.utbot.framework.codegen.model.tree.buildAssignment
    -import org.utbot.framework.codegen.model.tree.buildCgForEachLoop
    -import org.utbot.framework.codegen.model.tree.buildDeclaration
    -import org.utbot.framework.codegen.model.tree.buildDoWhileLoop
    -import org.utbot.framework.codegen.model.tree.buildForLoop
    -import org.utbot.framework.codegen.model.tree.buildSimpleBlock
    -import org.utbot.framework.codegen.model.tree.buildTryCatch
    -import org.utbot.framework.codegen.model.tree.buildWhileLoop
    -import org.utbot.framework.codegen.model.util.buildExceptionHandler
    -import org.utbot.framework.codegen.model.util.isAccessibleFrom
    -import org.utbot.framework.codegen.model.util.nullLiteral
    -import org.utbot.framework.codegen.model.util.resolve
    -import org.utbot.framework.plugin.api.BuiltinClassId
    -import org.utbot.framework.plugin.api.ClassId
    -import org.utbot.framework.plugin.api.ExecutableId
    -import org.utbot.framework.plugin.api.UtModel
    -import org.utbot.framework.plugin.api.util.executable
    -import org.utbot.framework.plugin.api.util.id
    -import org.utbot.framework.plugin.api.util.isArray
    -import org.utbot.framework.plugin.api.util.isNotSubtypeOf
    -import org.utbot.framework.plugin.api.util.isSubtypeOf
    -import org.utbot.framework.plugin.api.util.objectArrayClassId
    -import org.utbot.framework.plugin.api.util.objectClassId
    -import fj.data.Either
    -import java.lang.reflect.Constructor
    -import java.lang.reflect.Method
    -import kotlin.reflect.KFunction
    -import kotlin.reflect.jvm.kotlinFunction
    -
    -interface CgStatementConstructor {
    -    fun newVar(baseType: ClassId, baseName: String? = null, init: () -> CgExpression): CgVariable =
    -        newVar(baseType, model = null, baseName, isMutable = false, init)
    -
    -    fun newVar(
    -        baseType: ClassId,
    -        baseName: String? = null,
    -        isMutable: Boolean = false,
    -        init: () -> CgExpression
    -    ): CgVariable =
    -        newVar(baseType, model = null, baseName, isMutable = isMutable, init)
    -
    -    fun newVar(
    -        baseType: ClassId,
    -        model: UtModel? = null,
    -        baseName: String? = null,
    -        isMutable: Boolean = false,
    -        init: () -> CgExpression
    -    ): CgVariable = newVar(baseType, model, baseName, isMock = false, isMutable = isMutable, init)
    -
    -    fun newVar(
    -        baseType: ClassId,
    -        model: UtModel? = null,
    -        baseName: String? = null,
    -        isMock: Boolean = false,
    -        isMutable: Boolean = false,
    -        init: () -> CgExpression
    -    ): CgVariable
    -
    -    fun createDeclarationForNewVarAndUpdateVariableScopeOrGetExistingVariable(
    -        baseType: ClassId,
    -        model: UtModel? = null,
    -        baseName: String? = null,
    -        isMock: Boolean = false,
    -        isMutableVar: Boolean = false,
    -        init: () -> CgExpression
    -    ): Either
    -
    -    // assignment
    -    infix fun CgExpression.`=`(value: Any?)
    -    infix fun CgExpression.and(other: CgExpression): CgLogicalAnd
    -    infix fun CgExpression.or(other: CgExpression): CgLogicalOr
    -    fun ifStatement(condition: CgExpression, trueBranch: () -> Unit): CgIfStatement
    -    fun forLoop(init: CgForLoopBuilder.() -> Unit)
    -    fun whileLoop(condition: CgExpression, statements: () -> Unit)
    -    fun doWhileLoop(condition: CgExpression, statements: () -> Unit)
    -    fun forEachLoop(init: CgForEachLoopBuilder.() -> Unit)
    -
    -    fun tryBlock(init: () -> Unit): CgTryCatch
    -    fun tryBlock(init: () -> Unit, resources: List?): CgTryCatch
    -    fun CgTryCatch.catch(exception: ClassId, init: (CgVariable) -> Unit): CgTryCatch
    -    fun CgTryCatch.finally(init: () -> Unit): CgTryCatch
    -
    -    fun innerBlock(init: () -> Unit, additionalStatements: List): CgInnerBlock
    -
    -//    fun CgTryCatchBuilder.statements(init: () -> Unit)
    -//    fun CgTryCatchBuilder.handler(exception: ClassId, init: (CgVariable) -> Unit)
    -
    -    fun comment(text: String): CgComment
    -    fun comment(): CgComment
    -
    -    fun multilineComment(lines: List): CgComment
    -
    -    fun lambda(type: ClassId, vararg parameters: CgVariable, body: () -> Unit): CgAnonymousFunction
    -
    -    fun annotation(classId: ClassId, argument: Any?): CgAnnotation
    -    fun annotation(classId: ClassId, namedArguments: List>): CgAnnotation
    -    fun annotation(
    -        classId: ClassId,
    -        buildArguments: MutableList>.() -> Unit = {}
    -    ): CgAnnotation
    -
    -    fun returnStatement(expression: () -> CgExpression)
    -
    -    // Throw statement
    -    fun throwStatement(exception: () -> CgExpression): CgThrowStatement
    -
    -    fun emptyLine()
    -    fun emptyLineIfNeeded()
    -
    -    // utils
    -
    -    fun declareParameter(type: ClassId, name: String): CgVariable = declareVariable(type, name)
    -
    -    fun declareVariable(type: ClassId, name: String): CgVariable
    -
    -    fun guardExpression(baseType: ClassId, expression: CgExpression): ExpressionWithType
    -}
    -
    -internal class CgStatementConstructorImpl(context: CgContext) :
    -    CgStatementConstructor,
    -    CgContextOwner by context,
    -    CgCallableAccessManager by CgComponents.getCallableAccessManagerBy(context) {
    -
    -    private val nameGenerator = CgComponents.getNameGeneratorBy(context)
    -
    -    override fun createDeclarationForNewVarAndUpdateVariableScopeOrGetExistingVariable(
    -        baseType: ClassId,
    -        model: UtModel?,
    -        baseName: String?,
    -        isMock: Boolean,
    -        isMutableVar: Boolean,
    -        init: () -> CgExpression
    -    ): Either {
    -        // check if we can use base type and initializer directly
    -        // if not, fall back to using reflection
    -        val baseExpr = init()
    -        val (type, expr) = when (baseExpr) {
    -            is CgEnumConstantAccess -> guardEnumConstantAccess(baseExpr)
    -            is CgAllocateArray -> guardArrayAllocation(baseExpr)
    -            is CgExecutableCall -> guardExecutableCall(baseType, baseExpr)
    -            else -> guardExpression(baseType, baseExpr)
    -        }
    -
    -        val classRef = classRefOrNull(type, expr)
    -        if (classRef in declaredClassRefs) {
    -            return Either.right(declaredClassRefs[classRef]!!)
    -        }
    -
    -        val name = when {
    -            classRef != null && baseName == null -> {
    -                val base = classRef.prettifiedName.decapitalize()
    -                nameGenerator.variableName(base + "Clazz")
    -            }
    -            // we use baseType here intentionally
    -            else -> nameGenerator.variableName(baseType, baseName, isMock)
    -        }
    -
    -        importIfNeeded(type)
    -
    -        val declaration = buildDeclaration {
    -            variableType = type
    -            variableName = name
    -            initializer = expr
    -            isMutable = isMutableVar
    -        }
    -
    -        classRef?.let { declaredClassRefs = declaredClassRefs.put(it, declaration.variable) }
    -        updateVariableScope(declaration.variable, model)
    -
    -        return Either.left(declaration)
    -    }
    -
    -    override fun newVar(
    -        baseType: ClassId,
    -        model: UtModel?,
    -        baseName: String?,
    -        isMock: Boolean,
    -        isMutable: Boolean,
    -        init: () -> CgExpression
    -    ): CgVariable {
    -        val declarationOrVar: Either =
    -            createDeclarationForNewVarAndUpdateVariableScopeOrGetExistingVariable(
    -                baseType,
    -                model,
    -                baseName,
    -                isMock,
    -                isMutable,
    -                init
    -            )
    -
    -        return declarationOrVar.either(
    -            { declaration ->
    -                currentBlock += declaration
    -
    -                declaration.variable
    -            },
    -            { variable -> variable }
    -        )
    -    }
    -
    -    override fun CgExpression.`=`(value: Any?) {
    -        currentBlock += buildAssignment {
    -            lValue = this@`=`
    -            rValue = value.resolve()
    -        }
    -    }
    -
    -    override fun CgExpression.and(other: CgExpression): CgLogicalAnd =
    -        CgLogicalAnd(this, other)
    -
    -    override fun CgExpression.or(other: CgExpression): CgLogicalOr =
    -        CgLogicalOr(this, other)
    -
    -    override fun ifStatement(condition: CgExpression, trueBranch: () -> Unit): CgIfStatement {
    -        return CgIfStatement(condition, block(trueBranch)).also {
    -            currentBlock += it
    -        }
    -    }
    -
    -    override fun forLoop(init: CgForLoopBuilder.() -> Unit) = withNameScope {
    -        currentBlock += buildForLoop(init)
    -    }
    -
    -    override fun whileLoop(condition: CgExpression, statements: () -> Unit) {
    -        currentBlock += buildWhileLoop {
    -            this.condition = condition
    -            this.statements += block(statements)
    -        }
    -    }
    -
    -    override fun doWhileLoop(condition: CgExpression, statements: () -> Unit) {
    -        currentBlock += buildDoWhileLoop {
    -            this.condition = condition
    -            this.statements += block(statements)
    -        }
    -    }
    -
    -    override fun forEachLoop(init: CgForEachLoopBuilder.() -> Unit) = withNameScope {
    -        currentBlock += buildCgForEachLoop(init)
    -    }
    -
    -    override fun tryBlock(init: () -> Unit): CgTryCatch = tryBlock(init, null)
    -
    -    override fun tryBlock(init: () -> Unit, resources: List?): CgTryCatch =
    -        buildTryCatch {
    -            statements = block(init)
    -            this.resources = resources
    -        }
    -
    -    override fun CgTryCatch.catch(exception: ClassId, init: (CgVariable) -> Unit): CgTryCatch {
    -        val newHandler = buildExceptionHandler {
    -            val e = declareVariable(exception, nameGenerator.variableName(exception.simpleName.decapitalize()))
    -            this.exception = e
    -            this.statements = block { init(e) }
    -        }
    -        return this.copy(handlers = handlers + newHandler)
    -    }
    -
    -    override fun CgTryCatch.finally(init: () -> Unit): CgTryCatch {
    -        val finallyBlock = block(init)
    -        return this.copy(finally = finallyBlock)
    -    }
    -
    -    override fun innerBlock(
    -        init: () -> Unit,
    -        additionalStatements: List,
    -    ): CgInnerBlock = buildSimpleBlock {
    -        statements = mutableListOf() + block(init) + additionalStatements
    -    }
    -
    -    override fun comment(text: String): CgComment =
    -        CgSingleLineComment(text).also {
    -            currentBlock += it
    -        }
    -
    -    override fun comment(): CgComment =
    -        CgSingleLineComment("").also {
    -            currentBlock += it
    -        }
    -
    -    override fun multilineComment(lines: List): CgComment =
    -        CgMultilineComment(lines).also {
    -            currentBlock += it
    -        }
    -
    -    override fun lambda(type: ClassId, vararg parameters: CgVariable, body: () -> Unit): CgAnonymousFunction {
    -        return withNameScope {
    -            for (parameter in parameters) {
    -                declareParameter(parameter.type, parameter.name)
    -            }
    -            val paramDeclarations = parameters.map { CgParameterDeclaration(it) }
    -            CgAnonymousFunction(type, paramDeclarations, block(body))
    -        }
    -    }
    -
    -    override fun annotation(classId: ClassId, argument: Any?): CgAnnotation {
    -        val annotation = CgSingleArgAnnotation(classId, argument.resolve())
    -        addAnnotation(annotation)
    -        return annotation
    -    }
    -
    -    override fun annotation(classId: ClassId, namedArguments: List>): CgAnnotation {
    -        val annotation = CgMultipleArgsAnnotation(
    -            classId,
    -            namedArguments.mapTo(mutableListOf()) { (name, value) -> CgNamedAnnotationArgument(name, value) }
    -        )
    -        addAnnotation(annotation)
    -        return annotation
    -    }
    -
    -    override fun annotation(
    -        classId: ClassId,
    -        buildArguments: MutableList>.() -> Unit
    -    ): CgAnnotation {
    -        val arguments = mutableListOf>()
    -            .apply(buildArguments)
    -            .map { (name, value) -> CgNamedAnnotationArgument(name, value) }
    -        val annotation = CgMultipleArgsAnnotation(classId, arguments.toMutableList())
    -        addAnnotation(annotation)
    -        return annotation
    -    }
    -
    -    override fun returnStatement(expression: () -> CgExpression) {
    -        currentBlock += CgReturnStatement(expression())
    -    }
    -
    -    // Throw statement
    -
    -    override fun throwStatement(exception: () -> CgExpression): CgThrowStatement =
    -        CgThrowStatement(exception()).also { currentBlock += it }
    -
    -    override fun emptyLine() {
    -        currentBlock += CgEmptyLine()
    -    }
    -
    -    /**
    -     * Add an empty line if the current block has at least one statement
    -     * and the current last statement is not an empty line.
    -     */
    -    override fun emptyLineIfNeeded() {
    -        val lastStatement = currentBlock.lastOrNull() ?: return
    -        if (lastStatement is CgEmptyLine) return
    -        emptyLine()
    -    }
    -
    -    override fun declareVariable(type: ClassId, name: String): CgVariable =
    -        CgVariable(name, type).also {
    -            updateVariableScope(it)
    -        }
    -
    -    // utils
    -
    -    private fun classRefOrNull(type: ClassId, expr: CgExpression): ClassId? {
    -        if (type == Class::class.id && expr is CgGetClass) return expr.classId
    -
    -        if (type == Class::class.id && expr is CgExecutableCall && expr.executableId == forName) {
    -            val name = (expr.arguments.getOrNull(0) as? CgLiteral)?.value as? String
    -
    -            if (name != null) {
    -                return BuiltinClassId.getBuiltinClassByNameOrNull(name) ?: ClassId(name)
    -            }
    -        }
    -
    -        return null
    -    }
    -
    -    private fun guardEnumConstantAccess(access: CgEnumConstantAccess): ExpressionWithType {
    -        val (enumClass, constant) = access
    -
    -        return if (enumClass.isAccessibleFrom(testClassPackageName)) {
    -            ExpressionWithType(enumClass, access)
    -        } else {
    -            val enumClassVariable = newVar(classCgClassId) {
    -                Class::class.id[forName](enumClass.name)
    -            }
    -
    -            ExpressionWithType(objectClassId, testClassThisInstance[getEnumConstantByName](enumClassVariable, constant))
    -        }
    -    }
    -
    -    private fun guardArrayAllocation(allocation: CgAllocateArray): ExpressionWithType {
    -        // TODO: check if this is the right way to check array type accessibility
    -        return if (allocation.type.isAccessibleFrom(testClassPackageName)) {
    -            ExpressionWithType(allocation.type, allocation)
    -        } else {
    -            ExpressionWithType(
    -                objectArrayClassId,
    -                testClassThisInstance[createArray](allocation.elementType.name, allocation.size)
    -            )
    -        }
    -    }
    -
    -    private val ExecutableId.kotlinFunction: KFunction<*>?
    -        get() {
    -            return when (val executable = this.executable) {
    -                is Method -> executable.kotlinFunction
    -                is Constructor<*> -> executable.kotlinFunction
    -                else -> error("Unknown Executable type: ${this::class}")
    -            }
    -        }
    -
    -    private fun guardExecutableCall(baseType: ClassId, call: CgExecutableCall): ExpressionWithType {
    -        // TODO: SAT-1210 support generics so that we wouldn't need to obtain kotlinFunction
    -        // TODO: in order to check whether we are working with a TypeVariable or not
    -//        val returnType = runCatching { call.executableId.kotlinFunction }.getOrNull()?.returnType?.javaType
    -
    -        if (call.executableId != mockMethodId) return guardExpression(baseType, call)
    -
    -        // call represents a call to mock() method
    -        return if (baseType.isAccessibleFrom(testClassPackageName)) {
    -            ExpressionWithType(baseType, call)
    -        } else {
    -            ExpressionWithType(objectClassId, call)
    -        }
    -    }
    -
    -    override fun guardExpression(baseType: ClassId, expression: CgExpression): ExpressionWithType {
    -        val type: ClassId
    -        val expr: CgExpression
    -
    -        val typeAccessible = baseType.isAccessibleFrom(testClassPackageName)
    -
    -        when {
    -            expression.type isSubtypeOf baseType && typeAccessible -> {
    -                type = baseType
    -                expr = expression
    -            }
    -            expression.type isSubtypeOf baseType && !typeAccessible -> {
    -                type = if (expression.type.isArray) objectArrayClassId else objectClassId
    -                expr = expression
    -            }
    -            expression.type isNotSubtypeOf baseType && typeAccessible -> {
    -                // consider util methods getField and getStaticField
    -                // TODO should we consider another cases?
    -                val isGetFieldUtilMethod = (expression is CgMethodCall && expression.executableId.isGetFieldUtilMethod)
    -                val shouldCastBeSafety = expression == nullLiteral() || isGetFieldUtilMethod
    -
    -                type = baseType
    -                expr = typeCast(baseType, expression, shouldCastBeSafety)
    -            }
    -            expression.type isNotSubtypeOf baseType && !typeAccessible -> {
    -                type = if (expression.type.isArray) objectArrayClassId else objectClassId
    -                expr = if (expression is CgMethodCall && expression.executableId.isUtil) {
    -                    CgErrorWrapper("${expression.executableId.name} failed", expression)
    -                } else {
    -                    expression
    -                }
    -            }
    -            else -> error("Impossible case")
    -        }
    -
    -        return ExpressionWithType(type, expr)
    -    }
    -}
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt
    deleted file mode 100644
    index c6fa56305c..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/ConstructorUtils.kt
    +++ /dev/null
    @@ -1,313 +0,0 @@
    -package org.utbot.framework.codegen.model.constructor.util
    -
    -import org.utbot.framework.codegen.RegularImport
    -import org.utbot.framework.codegen.StaticImport
    -import org.utbot.framework.codegen.model.constructor.context.CgContextOwner
    -import org.utbot.framework.codegen.model.tree.CgClassId
    -import org.utbot.framework.codegen.model.tree.CgExpression
    -import org.utbot.framework.codegen.model.tree.CgGetClass
    -import org.utbot.framework.codegen.model.tree.CgGetJavaClass
    -import org.utbot.framework.codegen.model.tree.CgTypeCast
    -import org.utbot.framework.codegen.model.tree.CgValue
    -import org.utbot.framework.codegen.model.tree.CgVariable
    -import org.utbot.framework.codegen.model.util.isAccessibleFrom
    -import org.utbot.framework.fields.ArrayElementAccess
    -import org.utbot.framework.fields.FieldAccess
    -import org.utbot.framework.fields.FieldPath
    -import org.utbot.framework.plugin.api.BuiltinClassId
    -import org.utbot.framework.plugin.api.BuiltinMethodId
    -import org.utbot.framework.plugin.api.ClassId
    -import org.utbot.framework.plugin.api.ConstructorId
    -import org.utbot.framework.plugin.api.ExecutableId
    -import org.utbot.framework.plugin.api.MethodId
    -import org.utbot.framework.plugin.api.UtArrayModel
    -import org.utbot.framework.plugin.api.UtExecution
    -import org.utbot.framework.plugin.api.UtModel
    -import org.utbot.framework.plugin.api.UtNullModel
    -import org.utbot.framework.plugin.api.UtPrimitiveModel
    -import org.utbot.framework.plugin.api.WildcardTypeParameter
    -import org.utbot.framework.plugin.api.util.booleanClassId
    -import org.utbot.framework.plugin.api.util.byteClassId
    -import org.utbot.framework.plugin.api.util.charClassId
    -import org.utbot.framework.plugin.api.util.doubleClassId
    -import org.utbot.framework.plugin.api.util.enclosingClass
    -import org.utbot.framework.plugin.api.util.executable
    -import org.utbot.framework.plugin.api.util.floatClassId
    -import org.utbot.framework.plugin.api.util.id
    -import org.utbot.framework.plugin.api.util.intClassId
    -import org.utbot.framework.plugin.api.util.isRefType
    -import org.utbot.framework.plugin.api.util.isSubtypeOf
    -import org.utbot.framework.plugin.api.util.longClassId
    -import org.utbot.framework.plugin.api.util.shortClassId
    -import org.utbot.framework.plugin.api.util.underlyingType
    -import kotlinx.collections.immutable.PersistentList
    -import kotlinx.collections.immutable.PersistentSet
    -
    -internal data class EnvironmentFieldStateCache(
    -    val thisInstance: FieldStateCache,
    -    val arguments: Array,
    -    val classesWithStaticFields: MutableMap
    -) {
    -    companion object {
    -        fun emptyCacheFor(execution: UtExecution): EnvironmentFieldStateCache {
    -            val argumentsCache = Array(execution.stateBefore.parameters.size) { FieldStateCache() }
    -
    -            val staticFields = execution.stateBefore.statics.keys
    -            val classesWithStaticFields = staticFields.groupBy { it.declaringClass }.keys
    -            val staticFieldsCache = mutableMapOf().apply {
    -                for (classId in classesWithStaticFields) {
    -                    put(classId, FieldStateCache())
    -                }
    -            }
    -
    -            return EnvironmentFieldStateCache(
    -                thisInstance = FieldStateCache(),
    -                arguments = argumentsCache,
    -                classesWithStaticFields = staticFieldsCache
    -            )
    -        }
    -    }
    -
    -    override fun equals(other: Any?): Boolean {
    -        if (this === other) return true
    -        if (javaClass != other?.javaClass) return false
    -
    -        other as EnvironmentFieldStateCache
    -
    -        if (thisInstance != other.thisInstance) return false
    -        if (!arguments.contentEquals(other.arguments)) return false
    -        if (classesWithStaticFields != other.classesWithStaticFields) return false
    -
    -        return true
    -    }
    -
    -    override fun hashCode(): Int {
    -        var result = thisInstance.hashCode()
    -        result = 31 * result + arguments.contentHashCode()
    -        result = 31 * result + classesWithStaticFields.hashCode()
    -        return result
    -    }
    -}
    -
    -internal class FieldStateCache {
    -    val before: MutableMap = mutableMapOf()
    -    val after: MutableMap = mutableMapOf()
    -
    -    val paths: List
    -        get() = (before.keys union after.keys).toList()
    -
    -    override fun equals(other: Any?): Boolean {
    -        if (this === other) return true
    -        if (javaClass != other?.javaClass) return false
    -
    -        other as FieldStateCache
    -
    -        if (before != other.before) return false
    -        if (after != other.after) return false
    -
    -        return true
    -    }
    -
    -    override fun hashCode(): Int {
    -        var result = before.hashCode()
    -        result = 31 * result + after.hashCode()
    -        return result
    -    }
    -}
    -
    -internal data class CgFieldState(val variable: CgVariable, val model: UtModel)
    -
    -data class ExpressionWithType(val type: ClassId, val expression: CgExpression)
    -
    -val classCgClassId = CgClassId(Class::class.id, typeParameters = WildcardTypeParameter(), isNullable = false)
    -
    -internal fun getStaticFieldVariableName(owner: ClassId, path: FieldPath): String {
    -    val elements = mutableListOf()
    -    elements += owner.simpleName.decapitalize()
    -    // TODO: check how capitalize() works with numeric strings e.g. "0"
    -    elements += path.toStringList().map { it.capitalize() }
    -    return elements.joinToString("")
    -}
    -
    -internal fun getFieldVariableName(owner: CgValue, path: FieldPath): String {
    -    val elements = mutableListOf()
    -    if (owner is CgVariable) {
    -        elements += owner.name
    -    }
    -    // TODO: check how capitalize() works with numeric strings e.g. "0"
    -    elements += path.toStringList().map { it.capitalize() }
    -    if (elements.size > 0) {
    -        elements[0] = elements[0].decapitalize()
    -    }
    -    return elements.joinToString("")
    -}
    -
    -private fun FieldPath.toStringList(): List =
    -    elements.map {
    -        when (it) {
    -            is FieldAccess -> it.field.name
    -            is ArrayElementAccess -> it.index.toString()
    -        }
    -    }
    -
    -internal fun infiniteInts(): Sequence =
    -    generateSequence(1) { it + 1 }
    -
    -/**
    - * Checks if we have already imported a class with such simple name.
    - * If so, we cannot import [type] (because it will be used with simple name and will be clashed with already imported)
    - * and should use its fully qualified name instead.
    - */
    -private fun CgContextOwner.doesNotHaveSimpleNameClash(type: ClassId): Boolean =
    -    importedClasses.none { it.simpleName == type.simpleName }
    -
    -internal fun CgContextOwner.importIfNeeded(type: ClassId) {
    -    // TODO: for now we consider that tests are generated in the same package as CUT, but this may change
    -    val underlyingType = type.underlyingType
    -
    -    underlyingType
    -        .takeIf { (it.isRefType && it.packageName != testClassPackageName && it.packageName != "java.lang") || it.isNested }
    -        // we cannot import inaccessible classes (builtin classes like JUnit are accessible here because they are public)
    -        ?.takeIf { it.isAccessibleFrom(testClassPackageName) }
    -        // don't import classes from default package
    -        ?.takeIf { !it.isInDefaultPackage }
    -        // cannot import anonymous classes
    -        ?.takeIf { !it.isAnonymous }
    -        // do not import if there is a simple name clash
    -        ?.takeIf { doesNotHaveSimpleNameClash(it) }
    -        ?.let {
    -            importedClasses += it
    -            collectedImports += it.toImport()
    -        }
    -
    -    // for nested classes we need to import enclosing class
    -    if (underlyingType.isNested) {
    -        importIfNeeded(underlyingType.enclosingClass!!)
    -    }
    -}
    -
    -internal fun CgContextOwner.importIfNeeded(method: MethodId) {
    -    val name = method.name
    -    val packageName = method.classId.packageName
    -    method.takeIf { it.isStatic && packageName != testClassPackageName && packageName != "java.lang" }
    -        .takeIf { importedStaticMethods.none { it.name == name } }
    -        // do not import method under test in order to specify the declaring class directly for its calls
    -        .takeIf { currentExecutable != method }
    -        ?.let {
    -            importedStaticMethods += method
    -            collectedImports += StaticImport(method.classId.canonicalName, method.name)
    -        }
    -}
    -
    -/**
    - * Casts [expression] to [targetType].
    - *
    - * @param isSafetyCast shows if we should render "as?" instead of "as" in Kotlin
    - */
    -internal fun CgContextOwner.typeCast(
    -    targetType: ClassId,
    -    expression: CgExpression,
    -    isSafetyCast: Boolean = false
    -): CgTypeCast {
    -    if (targetType.simpleName.isEmpty()) {
    -        error("Cannot cast an expression to the anonymous type $targetType")
    -    }
    -    importIfNeeded(targetType)
    -    return CgTypeCast(targetType, expression, isSafetyCast)
    -}
    -
    -@Suppress("unused")
    -internal fun CgContextOwner.getJavaClass(classId: ClassId): CgGetClass {
    -    importIfNeeded(classId)
    -    return CgGetJavaClass(classId)
    -}
    -
    -internal fun Class<*>.overridesEquals(): Boolean =
    -    when {
    -        // Object does not override equals
    -        this == Any::class.java -> false
    -        id isSubtypeOf Map::class.id -> true
    -        id isSubtypeOf Collection::class.id -> true
    -        else -> declaredMethods.any { it.name == "equals" && it.parameterTypes.contentEquals(arrayOf(Any::class.java)) }
    -    }
    -
    -// NOTE: this function does not consider executable return type because it is not important in our case
    -internal fun ClassId.getAmbiguousOverloadsOf(executableId: ExecutableId): Sequence {
    -    val allExecutables = when (executableId) {
    -        is MethodId -> allMethods
    -        is ConstructorId -> allConstructors
    -    }
    -
    -    return allExecutables.filter {
    -        it.name == executableId.name && it.parameters.size == executableId.executable.parameters.size
    -    }
    -}
    -
    -
    -internal infix fun ClassId.hasAmbiguousOverloadsOf(executableId: ExecutableId): Boolean {
    -    // TODO: for now we assume that builtin classes do not have ambiguous overloads
    -    if (this is BuiltinClassId) return false
    -
    -    return getAmbiguousOverloadsOf(executableId).toList().size > 1
    -}
    -
    -private val defaultByPrimitiveType: Map = mapOf(
    -    booleanClassId to false,
    -    byteClassId to 0.toByte(),
    -    charClassId to '\u0000',
    -    shortClassId to 0.toShort(),
    -    intClassId to 0,
    -    longClassId to 0L,
    -    floatClassId to 0.0f,
    -    doubleClassId to 0.0
    -)
    -
    -/**
    - * By 'default' here we mean a value that is used for a type in one of the two cases:
    - * - When we allocate an array of some type in the following manner: `new int[10]`,
    - * the array is filled with some value. In case of `int` this value is `0`, for `boolean`
    - * it is `false`, etc.
    - * - When we allocate an instance of some reference type e.g. `new A()` and the class `A` has field `int a`.
    - * If the constructor we use does not initialize `a` directly and `a` is not assigned to some value
    - * at the declaration site, then `a` will be assigned to some `default` value, e.g. `0` for `int`.
    - *
    - * Here we do not consider default values of nested arrays of multidimensional arrays,
    - * because they depend on the way the outer array is allocated:
    - * - An array allocated using `new int[10][]` will contain 10 `null` elements.
    - * - An array allocated using `new int[10][5]` will contain 10 arrays of 5 elements,
    - *   where each element is `0`.
    - */
    -internal infix fun UtModel.isDefaultValueOf(type: ClassId): Boolean =
    -    when (this) {
    -        is UtNullModel -> type.isRefType // null is default for ref types
    -        is UtPrimitiveModel -> value == defaultByPrimitiveType[type]
    -        else -> false
    -    }
    -
    -internal infix fun UtModel.isNotDefaultValueOf(type: ClassId): Boolean = !this.isDefaultValueOf(type)
    -
    -/**
    - * If the model contains a store for the given [index], return the model of this store.
    - * Otherwise, return a [UtArrayModel.constModel] of this array model.
    - */
    -internal operator fun UtArrayModel.get(index: Int): UtModel = stores[index] ?: constModel
    -
    -
    -internal fun ClassId.utilMethodId(name: String, returnType: ClassId, vararg arguments: ClassId): MethodId =
    -    BuiltinMethodId(this, name, returnType, arguments.toList())
    -
    -fun ClassId.toImport(): RegularImport = RegularImport(packageName, simpleNameWithEnclosings)
    -
    -// Immutable collections utils
    -
    -internal operator fun  PersistentList.plus(element: T): PersistentList =
    -    this.add(element)
    -
    -internal operator fun  PersistentList.plus(other: PersistentList): PersistentList =
    -    this.addAll(other)
    -
    -internal operator fun  PersistentSet.plus(element: T): PersistentSet =
    -    this.add(element)
    -
    -internal operator fun  PersistentSet.plus(other: PersistentSet): PersistentSet =
    -    this.addAll(other)
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/DeclarationUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/DeclarationUtils.kt
    deleted file mode 100644
    index 8100f48d40..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/constructor/util/DeclarationUtils.kt
    +++ /dev/null
    @@ -1,15 +0,0 @@
    -package org.utbot.framework.codegen.model.constructor.util
    -
    -import org.utbot.framework.plugin.api.UtModel
    -import org.utbot.framework.plugin.api.UtNullModel
    -import org.utbot.framework.plugin.api.UtPrimitiveModel
    -
    -/**
    - * Checks if an expected variable is needed,
    - * or it will be further asserted with assertNull or assertTrue/False.
    - */
    -fun needExpectedDeclaration(model: UtModel): Boolean {
    -    val representsNull = model is UtNullModel
    -    val representsBoolean = model is UtPrimitiveModel && model.value is Boolean
    -    return !(representsNull || representsBoolean)
    -}
    \ No newline at end of file
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt
    deleted file mode 100644
    index 55963cbb48..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/Builders.kt
    +++ /dev/null
    @@ -1,200 +0,0 @@
    -package org.utbot.framework.codegen.model.tree
    -
    -import org.utbot.framework.codegen.Import
    -import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport
    -import org.utbot.framework.codegen.model.util.CgExceptionHandler
    -import org.utbot.framework.plugin.api.ClassId
    -import org.utbot.framework.plugin.api.util.voidClassId
    -
    -interface CgBuilder {
    -    fun build(): T
    -}
    -
    -// Code entities
    -
    -class CgTestClassFileBuilder : CgBuilder {
    -    val imports: MutableList = mutableListOf()
    -    lateinit var testClass: CgTestClass
    -    lateinit var testsGenerationReport: TestsGenerationReport
    -
    -    override fun build() = CgTestClassFile(imports, testClass, testsGenerationReport)
    -}
    -
    -fun buildTestClassFile(init: CgTestClassFileBuilder.() -> Unit) = CgTestClassFileBuilder().apply(init).build()
    -
    -class CgTestClassBuilder : CgBuilder {
    -    lateinit var id: ClassId
    -    val annotations: MutableList = mutableListOf()
    -    var superclass: ClassId? = null
    -    val interfaces: MutableList = mutableListOf()
    -    lateinit var body: CgTestClassBody
    -
    -    override fun build() = CgTestClass(id, annotations, superclass, interfaces, body)
    -}
    -
    -fun buildTestClass(init: CgTestClassBuilder.() -> Unit) = CgTestClassBuilder().apply(init).build()
    -
    -class CgTestClassBodyBuilder : CgBuilder {
    -    val testMethodRegions: MutableList = mutableListOf()
    -
    -    val dataProvidersAndUtilMethodsRegion: MutableList> = mutableListOf()
    -
    -    override fun build() = CgTestClassBody(testMethodRegions, dataProvidersAndUtilMethodsRegion)
    -}
    -
    -fun buildTestClassBody(init: CgTestClassBodyBuilder.() -> Unit) = CgTestClassBodyBuilder().apply(init).build()
    -
    -// Methods
    -
    -interface CgMethodBuilder : CgBuilder {
    -    val name: String
    -    val returnType: ClassId
    -    val parameters: List
    -    val statements: List
    -    val exceptions: Set
    -    val annotations: List
    -    val documentation: CgDocumentationComment
    -}
    -
    -class CgTestMethodBuilder : CgMethodBuilder {
    -    override lateinit var name: String
    -    override val returnType: ClassId = voidClassId
    -    override lateinit var parameters: List
    -    override lateinit var statements: List
    -    override val exceptions: MutableSet = mutableSetOf()
    -    override val annotations: MutableList = mutableListOf()
    -    lateinit var methodType: CgTestMethodType
    -    override var documentation: CgDocumentationComment = CgDocumentationComment(emptyList())
    -
    -    override fun build() = CgTestMethod(
    -        name,
    -        returnType,
    -        parameters,
    -        statements,
    -        exceptions,
    -        annotations,
    -        methodType,
    -        documentation,
    -    )
    -}
    -
    -fun buildTestMethod(init: CgTestMethodBuilder.() -> Unit) = CgTestMethodBuilder().apply(init).build()
    -
    -class CgErrorTestMethodBuilder : CgMethodBuilder {
    -    override lateinit var name: String
    -    override val returnType: ClassId = voidClassId
    -    override val parameters: List = emptyList()
    -    override lateinit var statements: List
    -    override val exceptions: Set = emptySet()
    -    override val annotations: List = emptyList()
    -    override var documentation: CgDocumentationComment = CgDocumentationComment(emptyList())
    -
    -    override fun build() = CgErrorTestMethod(name, statements, documentation)
    -}
    -
    -fun buildErrorTestMethod(init: CgErrorTestMethodBuilder.() -> Unit) = CgErrorTestMethodBuilder().apply(init).build()
    -
    -class CgParameterizedTestDataProviderBuilder : CgMethodBuilder {
    -    override lateinit var name: String
    -
    -    override lateinit var returnType: ClassId
    -    override val parameters: List = mutableListOf()
    -    override lateinit var statements: List
    -    override lateinit var annotations: MutableList
    -    override val exceptions: MutableSet = mutableSetOf()
    -    override var documentation: CgDocumentationComment = CgDocumentationComment(emptyList())
    -
    -    override fun build() = CgParameterizedTestDataProviderMethod(name, statements, returnType, annotations)
    -}
    -
    -fun buildParameterizedTestDataProviderMethod(
    -    init: CgParameterizedTestDataProviderBuilder.() -> Unit
    -) = CgParameterizedTestDataProviderBuilder().apply(init).build()
    -
    -// Variable declaration
    -
    -class CgDeclarationBuilder : CgBuilder {
    -    lateinit var variableType: ClassId
    -    lateinit var variableName: String
    -    var initializer: CgExpression? = null
    -    var isMutable: Boolean = false
    -
    -    override fun build() = CgDeclaration(variableType, variableName, initializer, isMutable)
    -}
    -
    -fun buildDeclaration(init: CgDeclarationBuilder.() -> Unit) = CgDeclarationBuilder().apply(init).build()
    -
    -// Variable assignment
    -class CgAssignmentBuilder : CgBuilder {
    -    lateinit var lValue: CgExpression
    -    lateinit var rValue: CgExpression
    -
    -    override fun build() = CgAssignment(lValue, rValue)
    -}
    -
    -fun buildAssignment(init: CgAssignmentBuilder.() -> Unit) = CgAssignmentBuilder().apply(init).build()
    -
    -class CgTryCatchBuilder : CgBuilder {
    -    lateinit var statements: List
    -    private val handlers: MutableList = mutableListOf()
    -    var finally: List? = null
    -    var resources: List? = null
    -
    -    override fun build(): CgTryCatch = CgTryCatch(statements, handlers, finally, resources)
    -}
    -
    -fun buildTryCatch(init: CgTryCatchBuilder.() -> Unit): CgTryCatch = CgTryCatchBuilder().apply(init).build()
    -
    -class CgBlockBuilder : CgBuilder {
    -    lateinit var statements: List
    -
    -    override fun build() = CgInnerBlock(statements)
    -}
    -
    -fun buildSimpleBlock(init: CgBlockBuilder.() -> Unit) = CgBlockBuilder().apply(init).build()
    -
    -// Loops
    -interface CgLoopBuilder : CgBuilder {
    -    val condition: CgExpression
    -    val statements: List
    -}
    -
    -class CgForLoopBuilder : CgLoopBuilder {
    -    lateinit var initialization: CgDeclaration
    -    override lateinit var condition: CgExpression
    -    lateinit var update: CgStatement
    -    override lateinit var statements: List
    -
    -    override fun build() = CgForLoop(initialization, condition, update, statements)
    -}
    -
    -fun buildForLoop(init: CgForLoopBuilder.() -> Unit) = CgForLoopBuilder().apply(init).build()
    -
    -class CgWhileLoopBuilder : CgLoopBuilder {
    -    override lateinit var condition: CgExpression
    -    override val statements: MutableList = mutableListOf()
    -
    -    override fun build() = CgWhileLoop(condition, statements)
    -}
    -
    -fun buildWhileLoop(init: CgWhileLoopBuilder.() -> Unit) = CgWhileLoopBuilder().apply(init).build()
    -
    -class CgDoWhileLoopBuilder : CgLoopBuilder {
    -    override lateinit var condition: CgExpression
    -    override val statements: MutableList = mutableListOf()
    -
    -    override fun build() = CgDoWhileLoop(condition, statements)
    -}
    -
    -fun buildDoWhileLoop(init: CgDoWhileLoopBuilder.() -> Unit) = CgDoWhileLoopBuilder().apply(init).build()
    -
    -class CgForEachLoopBuilder : CgLoopBuilder {
    -    override lateinit var condition: CgExpression
    -    override lateinit var statements: List
    -    lateinit var iterable: CgReferenceExpression
    -
    -    override fun build(): CgForEachLoop = CgForEachLoop(condition, statements, iterable)
    -}
    -
    -fun buildCgForEachLoop(init: CgForEachLoopBuilder.() -> Unit): CgForEachLoop =
    -    CgForEachLoopBuilder().apply(init).build()
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt
    deleted file mode 100644
    index c3630a7192..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/tree/CgElement.kt
    +++ /dev/null
    @@ -1,812 +0,0 @@
    -package org.utbot.framework.codegen.model.tree
    -
    -import org.utbot.common.WorkaroundReason
    -import org.utbot.common.workaround
    -import org.utbot.framework.codegen.Import
    -import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport
    -import org.utbot.framework.codegen.model.util.CgExceptionHandler
    -import org.utbot.framework.codegen.model.visitor.CgVisitor
    -import org.utbot.framework.plugin.api.ClassId
    -import org.utbot.framework.plugin.api.ConstructorId
    -import org.utbot.framework.plugin.api.DocClassLinkStmt
    -import org.utbot.framework.plugin.api.DocCodeStmt
    -import org.utbot.framework.plugin.api.DocMethodLinkStmt
    -import org.utbot.framework.plugin.api.DocPreTagStatement
    -import org.utbot.framework.plugin.api.DocRegularStmt
    -import org.utbot.framework.plugin.api.DocStatement
    -import org.utbot.framework.plugin.api.ExecutableId
    -import org.utbot.framework.plugin.api.FieldId
    -import org.utbot.framework.plugin.api.MethodId
    -import org.utbot.framework.plugin.api.TypeParameters
    -import org.utbot.framework.plugin.api.UtArrayModel
    -import org.utbot.framework.plugin.api.util.booleanClassId
    -import org.utbot.framework.plugin.api.util.id
    -import org.utbot.framework.plugin.api.util.intClassId
    -import org.utbot.framework.plugin.api.util.objectArrayClassId
    -import org.utbot.framework.plugin.api.util.objectClassId
    -import org.utbot.framework.plugin.api.util.voidClassId
    -
    -interface CgElement {
    -    // TODO: order of cases is important here due to inheritance between some of the element types
    -    fun  accept(visitor: CgVisitor): R = visitor.run {
    -        when (val element = this@CgElement) {
    -            is CgTestClassFile -> visit(element)
    -            is CgTestClass -> visit(element)
    -            is CgTestClassBody -> visit(element)
    -            is CgStaticsRegion -> visit(element)
    -            is CgSimpleRegion<*> -> visit(element)
    -            is CgTestMethodCluster -> visit(element)
    -            is CgExecutableUnderTestCluster -> visit(element)
    -            is CgUtilMethod -> visit(element)
    -            is CgTestMethod -> visit(element)
    -            is CgErrorTestMethod -> visit(element)
    -            is CgParameterizedTestDataProviderMethod -> visit(element)
    -            is CgCommentedAnnotation -> visit(element)
    -            is CgSingleArgAnnotation -> visit(element)
    -            is CgMultipleArgsAnnotation -> visit(element)
    -            is CgArrayAnnotationArgument -> visit(element)
    -            is CgNamedAnnotationArgument -> visit(element)
    -            is CgSingleLineComment -> visit(element)
    -            is CgTripleSlashMultilineComment -> visit(element)
    -            is CgMultilineComment -> visit(element)
    -            is CgDocumentationComment -> visit(element)
    -            is CgDocPreTagStatement -> visit(element)
    -            is CgDocCodeStmt -> visit(element)
    -            is CgDocRegularStmt -> visit(element)
    -            is CgDocClassLinkStmt -> visit(element)
    -            is CgDocMethodLinkStmt -> visit(element)
    -            is CgAnonymousFunction -> visit(element)
    -            is CgReturnStatement -> visit(element)
    -            is CgArrayElementAccess -> visit(element)
    -            is CgSpread -> visit(element)
    -            is CgLessThan -> visit(element)
    -            is CgGreaterThan -> visit(element)
    -            is CgEqualTo -> visit(element)
    -            is CgIncrement -> visit(element)
    -            is CgDecrement -> visit(element)
    -            is CgTryCatch -> visit(element)
    -            is CgInnerBlock -> visit(element)
    -            is CgForLoop -> visit(element)
    -            is CgWhileLoop -> visit(element)
    -            is CgDoWhileLoop -> visit(element)
    -            is CgBreakStatement -> visit(element)
    -            is CgContinueStatement -> visit(element)
    -            is CgDeclaration -> visit(element)
    -            is CgAssignment -> visit(element)
    -            is CgTypeCast -> visit(element)
    -            is CgThisInstance -> visit(element)
    -            //Not that order of variables is important
    -            is CgNotNullVariable -> visit(element)
    -            is CgVariable -> visit(element)
    -            is CgParameterDeclaration -> visit(element)
    -            is CgLiteral -> visit(element)
    -            is CgNonStaticRunnable -> visit(element)
    -            is CgStaticRunnable -> visit(element)
    -            is CgAllocateInitializedArray -> visit(element)
    -            is CgAllocateArray -> visit(element)
    -            is CgEnumConstantAccess -> visit(element)
    -            is CgFieldAccess -> visit(element)
    -            is CgStaticFieldAccess -> visit(element)
    -            is CgIfStatement -> visit(element)
    -            is CgSwitchCaseLabel -> visit(element)
    -            is CgSwitchCase -> visit(element)
    -            is CgLogicalAnd -> visit(element)
    -            is CgLogicalOr -> visit(element)
    -            is CgGetLength -> visit(element)
    -            is CgGetJavaClass -> visit(element)
    -            is CgGetKotlinClass -> visit(element)
    -            is CgStatementExecutableCall -> visit(element)
    -            is CgConstructorCall -> visit(element)
    -            is CgMethodCall -> visit(element)
    -            is CgThrowStatement -> visit(element)
    -            is CgErrorWrapper -> visit(element)
    -            is CgEmptyLine -> visit(element)
    -            else -> throw IllegalArgumentException("Can not visit element of type ${element::class}")
    -        }
    -    }
    -}
    -
    -// Code entities
    -
    -data class CgTestClassFile(
    -    val imports: List,
    -    val testClass: CgTestClass,
    -    val testsGenerationReport: TestsGenerationReport
    -) : CgElement
    -
    -data class CgTestClass(
    -    val id: ClassId,
    -    val annotations: List,
    -    val superclass: ClassId?,
    -    val interfaces: List,
    -    val body: CgTestClassBody,
    -) : CgElement {
    -    val packageName = id.packageName
    -    val simpleName = id.simpleName
    -}
    -
    -data class CgTestClassBody(
    -    val testMethodRegions: List,
    -    val utilsRegion: List>
    -) : CgElement {
    -    val regions: List>
    -        get() = testMethodRegions
    -}
    -
    -/**
    - * A class representing the IntelliJ IDEA's regions.
    - * A region is a part of code between the special starting and ending comments.
    - *
    - * [header] The header of the region.
    - */
    -sealed class CgRegion : CgElement {
    -    abstract val header: String?
    -    abstract val content: List
    -}
    -
    -open class CgSimpleRegion(
    -    override val header: String?,
    -    override val content: List
    -) : CgRegion()
    -
    -/**
    - * Stores data providers for parametrized tests and util methods
    - */
    -class CgStaticsRegion(
    -    override val header: String?,
    -    override val content: List
    -) : CgSimpleRegion(header, content)
    -
    -data class CgTestMethodCluster(
    -    override val header: String?,
    -    val description: CgTripleSlashMultilineComment?,
    -    override val content: List
    -) : CgRegion()
    -
    -/**
    - * Stores all clusters (ERROR, successful, timeouts, etc.) for executable under test.
    - */
    -data class CgExecutableUnderTestCluster(
    -    override val header: String?,
    -    override val content: List>
    -) : CgRegion>()
    -
    -/**
    - * This class does not inherit from [CgMethod], because it only needs an [id],
    - * and it does not need to have info about all the other properties of [CgMethod].
    - * This is because util methods are hardcoded. On the rendering stage their text
    - * is retrieved by their [MethodId].
    - *
    - * [id] identifier of the util method.
    - */
    -data class CgUtilMethod(val id: MethodId) : CgElement
    -
    -// Methods
    -
    -sealed class CgMethod(open val isStatic: Boolean) : CgElement {
    -    abstract val name: String
    -    abstract val returnType: ClassId
    -    abstract val parameters: List
    -    abstract val statements: List
    -    abstract val exceptions: Set
    -    abstract val annotations: List
    -    abstract val documentation: CgDocumentationComment
    -    abstract val requiredFields: List
    -}
    -
    -class CgTestMethod(
    -    override val name: String,
    -    override val returnType: ClassId,
    -    override val parameters: List,
    -    override val statements: List,
    -    override val exceptions: Set,
    -    override val annotations: List,
    -    val type: CgTestMethodType,
    -    override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList()),
    -    override val requiredFields: List = emptyList(),
    -) : CgMethod(false)
    -
    -class CgErrorTestMethod(
    -    override val name: String,
    -    override val statements: List,
    -    override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList())
    -) : CgMethod(false) {
    -    override val exceptions: Set = emptySet()
    -    override val returnType: ClassId = voidClassId
    -    override val parameters: List = emptyList()
    -    override val annotations: List = emptyList()
    -    override val requiredFields: List = emptyList()
    -}
    -
    -class CgParameterizedTestDataProviderMethod(
    -    override val name: String,
    -    override val statements: List,
    -    override val returnType: ClassId,
    -    override val annotations: List,
    -) : CgMethod(isStatic = true) {
    -    override val exceptions: Set = emptySet()
    -
    -    override val parameters: List = emptyList()
    -    override val documentation: CgDocumentationComment = CgDocumentationComment(emptyList())
    -    override val requiredFields: List = emptyList()
    -}
    -
    -enum class CgTestMethodType(val displayName: String) {
    -    SUCCESSFUL("Successful tests"),
    -    FAILING("Failing tests (with exceptions)"),
    -    TIMEOUT("Failing tests (with timeout)"),
    -    CRASH("Possibly crashing tests"),
    -    PARAMETRIZED("Parametrized tests");
    -
    -    override fun toString(): String = displayName
    -}
    -
    -// Annotations
    -
    -abstract class CgAnnotation : CgElement {
    -    abstract val classId: ClassId
    -}
    -
    -class CgCommentedAnnotation(val annotation: CgAnnotation) : CgAnnotation() {
    -    override val classId: ClassId = annotation.classId
    -}
    -
    -class CgSingleArgAnnotation(
    -    override val classId: ClassId,
    -    val argument: CgExpression
    -) : CgAnnotation()
    -
    -class CgMultipleArgsAnnotation(
    -    override val classId: ClassId,
    -    val arguments: MutableList
    -) : CgAnnotation()
    -
    -class CgArrayAnnotationArgument(
    -    val values: List
    -) : CgExpression {
    -    override val type: ClassId = objectArrayClassId // TODO: is this type correct?
    -}
    -
    -class CgNamedAnnotationArgument(
    -    val name: String,
    -    val value: CgExpression
    -) : CgElement
    -
    -// Statements
    -
    -interface CgStatement : CgElement
    -
    -// Comments
    -
    -sealed class CgComment : CgStatement
    -
    -class CgSingleLineComment(val comment: String) : CgComment()
    -
    -/**
    - * A comment that consists of multiple lines.
    - * The appearance of such comment may vary depending
    - * on the [CgAbstractMultilineComment] inheritor being used.
    - * Each inheritor is rendered differently.
    - */
    -sealed class CgAbstractMultilineComment : CgComment() {
    -    abstract val lines: List
    -}
    -
    -/**
    - * Multiline comment where each line starts with ///
    - */
    -class CgTripleSlashMultilineComment(override val lines: List) : CgAbstractMultilineComment()
    -
    -/**
    - * Classic Java multiline comment starting with /* and ending with */
    - */
    -class CgMultilineComment(override val lines: List) : CgAbstractMultilineComment() {
    -    constructor(line: String) : this(listOf(line))
    -}
    -
    -//class CgDocumentationComment(val lines: List) : CgComment {
    -//    constructor(text: String?) : this(text?.split("\n") ?: listOf())
    -//}
    -class CgDocumentationComment(val lines: List) : CgComment() {
    -    constructor(text: String?) : this(text?.split("\n")?.map { CgDocRegularStmt(it) }?.toList() ?: listOf())
    -
    -    override fun equals(other: Any?): Boolean =
    -        if (other is CgDocumentationComment) this.hashCode() == other.hashCode() else false
    -
    -    override fun hashCode(): Int = lines.hashCode()
    -}
    -
    -sealed class CgDocStatement : CgStatement { //todo is it really CgStatement or maybe something else?
    -    abstract fun isEmpty(): Boolean
    -}
    -
    -sealed class CgDocTagStatement(val content: List) : CgDocStatement() {
    -    override fun isEmpty(): Boolean = content.all { it.isEmpty() }
    -}
    -
    -class CgDocPreTagStatement(content: List) : CgDocTagStatement(content) {
    -    override fun equals(other: Any?): Boolean =
    -        if (other is CgDocPreTagStatement) this.hashCode() == other.hashCode() else false
    -
    -    override fun hashCode(): Int = content.hashCode()
    -}
    -
    -class CgDocCodeStmt(val stmt: String) : CgDocStatement() {
    -    override fun isEmpty(): Boolean = stmt.isEmpty()
    -
    -    override fun equals(other: Any?): Boolean =
    -        if (other is CgDocCodeStmt) this.hashCode() == other.hashCode() else false
    -
    -    override fun hashCode(): Int = stmt.hashCode()
    -}
    -
    -class CgDocRegularStmt(val stmt: String) : CgDocStatement() {
    -    override fun isEmpty(): Boolean = stmt.isEmpty()
    -
    -    override fun equals(other: Any?): Boolean =
    -        if (other is CgDocCodeStmt) this.hashCode() == other.hashCode() else false
    -
    -    override fun hashCode(): Int = stmt.hashCode()
    -}
    -
    -open class CgDocClassLinkStmt(val className: String) : CgDocStatement() {
    -    override fun isEmpty(): Boolean = className.isEmpty()
    -
    -    override fun equals(other: Any?): Boolean =
    -        if (other is CgDocClassLinkStmt) this.hashCode() == other.hashCode() else false
    -
    -    override fun hashCode(): Int = className.hashCode()
    -}
    -
    -class CgDocMethodLinkStmt(val methodName: String, stmt: String) : CgDocClassLinkStmt(stmt) {
    -    override fun equals(other: Any?): Boolean =
    -        if (other is CgDocMethodLinkStmt) this.hashCode() == other.hashCode() else false
    -
    -    override fun hashCode(): Int {
    -        var result = super.hashCode()
    -        result = 31 * result + methodName.hashCode()
    -        return result
    -    }
    -}
    -
    -fun convertDocToCg(stmt: DocStatement): CgDocStatement {
    -    return when (stmt) {
    -        is DocPreTagStatement -> {
    -            val stmts = stmt.content.map { convertDocToCg(it) }
    -            CgDocPreTagStatement(content = stmts)
    -        }
    -        is DocRegularStmt -> CgDocRegularStmt(stmt = stmt.stmt)
    -        is DocClassLinkStmt -> CgDocClassLinkStmt(className = stmt.className)
    -        is DocMethodLinkStmt -> CgDocMethodLinkStmt(methodName = stmt.methodName, stmt = stmt.className)
    -        is DocCodeStmt -> CgDocCodeStmt(stmt = stmt.stmt)
    -    }
    -}
    -
    -// Anonymous function (lambda)
    -
    -class CgAnonymousFunction(
    -    override val type: ClassId,
    -    val parameters: List,
    -    val body: List
    -) : CgExpression
    -
    -// Return statement
    -
    -class CgReturnStatement(val expression: CgExpression) : CgStatement
    -
    -// Array element access
    -
    -// TODO: check nested array element access expressions e.g. a[0][1][2]
    -// TODO in general it is not CgReferenceExpression because array element can have a primitive type
    -class CgArrayElementAccess(val array: CgExpression, val index: CgExpression) : CgReferenceExpression {
    -    override val type: ClassId = array.type.elementClassId ?: objectClassId
    -}
    -
    -// Loop conditions
    -sealed class CgComparison : CgExpression {
    -    abstract val left: CgExpression
    -    abstract val right: CgExpression
    -
    -    override val type: ClassId = booleanClassId
    -}
    -
    -class CgLessThan(
    -    override val left: CgExpression,
    -    override val right: CgExpression
    -) : CgComparison()
    -
    -class CgGreaterThan(
    -    override val left: CgExpression,
    -    override val right: CgExpression
    -) : CgComparison()
    -
    -class CgEqualTo(
    -    override val left: CgExpression,
    -    override val right: CgExpression
    -) : CgComparison()
    -
    -// Increment and decrement
    -
    -class CgIncrement(val variable: CgVariable) : CgStatement
    -
    -class CgDecrement(val variable: CgVariable) : CgStatement
    -
    -// Inner block in method (keeps parent method fields visible)
    -
    -class CgInnerBlock(val statements: List) : CgStatement
    -
    -// Try-catch
    -
    -// for now finally clause is not supported
    -data class CgTryCatch(
    -    val statements: List,
    -    val handlers: List,
    -    val finally: List?,
    -    val resources: List? = null
    -) : CgStatement
    -
    -// ?: error("")
    -
    -data class CgErrorWrapper(
    -    val message: String,
    -    val expression: CgExpression,
    -): CgExpression {
    -    override val type: ClassId
    -        get() = expression.type
    -}
    -
    -// Loops
    -
    -sealed class CgLoop : CgStatement {
    -    abstract val condition: CgExpression // TODO: how to represent conditions
    -    abstract val statements: List
    -}
    -
    -class CgForLoop(
    -    val initialization: CgDeclaration,
    -    override val condition: CgExpression,
    -    val update: CgStatement,
    -    override val statements: List
    -) : CgLoop()
    -
    -class CgWhileLoop(
    -    override val condition: CgExpression,
    -    override val statements: List
    -) : CgLoop()
    -
    -class CgDoWhileLoop(
    -    override val condition: CgExpression,
    -    override val statements: List
    -) : CgLoop()
    -
    -/**
    - * @property condition represents variable in foreach loop
    - * @property iterable represents iterable in foreach loop
    - * @property statements represents statements in foreach loop
    - */
    -class CgForEachLoop(
    -    override val condition: CgExpression,
    -    override val statements: List,
    -    val iterable: CgReferenceExpression,
    -) : CgLoop()
    -
    -// Control statements
    -
    -object CgBreakStatement : CgStatement
    -object CgContinueStatement : CgStatement
    -
    -// Variable declaration
    -
    -class CgDeclaration(
    -    val variableType: ClassId,
    -    val variableName: String,
    -    val initializer: CgExpression?,
    -    val isMutable: Boolean = false,
    -) : CgStatement {
    -    val variable: CgVariable
    -        get() = CgVariable(variableName, variableType)
    -}
    -
    -// Variable assignment
    -
    -class CgAssignment(
    -    val lValue: CgExpression,
    -    val rValue: CgExpression
    -) : CgStatement
    -
    -// Expressions
    -
    -interface CgExpression : CgStatement {
    -    val type: ClassId
    -}
    -
    -// marker interface representing expressions returning reference
    -interface CgReferenceExpression : CgExpression
    -
    -/**
    - * Type cast model
    - *
    - * @property isSafetyCast shows if we should use "as?" instead of "as" in Kotlin code
    - */
    -class CgTypeCast(
    -    val targetType: ClassId,
    -    val expression: CgExpression,
    -    val isSafetyCast: Boolean = false,
    -) : CgExpression {
    -    override val type: ClassId = targetType
    -}
    -
    -// Value
    -
    -// TODO in general CgLiteral is not CgReferenceExpression because it can hold primitive values
    -interface CgValue : CgReferenceExpression
    -
    -// This instance
    -
    -class CgThisInstance(override val type: ClassId) : CgValue
    -
    -// Variables
    -
    -open class CgVariable(
    -    val name: String,
    -    override val type: ClassId,
    -) : CgValue {
    -    override fun equals(other: Any?): Boolean {
    -        if (this === other) return true
    -        if (javaClass != other?.javaClass) return false
    -
    -        other as CgVariable
    -
    -        if (name != other.name) return false
    -        if (type != other.type) return false
    -
    -        return true
    -    }
    -
    -    override fun hashCode(): Int {
    -        var result = name.hashCode()
    -        result = 31 * result + type.hashCode()
    -        return result
    -    }
    -
    -    override fun toString(): String {
    -        return "${type.simpleName} $name"
    -    }
    -}
    -
    -/**
    - * A variable with explicit not null annotation if this is required in language.
    - *
    - * Note:
    - * - in Java it is an equivalent of [CgVariable]
    - * - in Kotlin the difference is in addition of "!!" to the name
    - */
    -class CgNotNullVariable(name: String, type: ClassId) : CgVariable(name, type)
    -
    -/**
    - * Method parameters declaration
    - *
    - * @property isReferenceType is used for rendering nullable types in Kotlin codegen.
    - */
    -data class CgParameterDeclaration(
    -    val parameter: CgVariable,
    -    val isVararg: Boolean = false,
    -    val isReferenceType: Boolean = false
    -) : CgElement {
    -    constructor(name: String, type: ClassId, isReferenceType: Boolean = false) : this(
    -        CgVariable(name, type),
    -        isReferenceType = isReferenceType
    -    )
    -
    -    val name: String
    -        get() = parameter.name
    -
    -    val type: ClassId
    -        get() = parameter.type
    -}
    -
    -// Primitive and String literals
    -
    -class CgLiteral(override val type: ClassId, val value: Any?) : CgValue {
    -    override fun equals(other: Any?): Boolean {
    -        if (this === other) return true
    -        if (javaClass != other?.javaClass) return false
    -
    -        other as CgLiteral
    -
    -        if (type != other.type) return false
    -        if (value != other.value) return false
    -
    -        return true
    -    }
    -
    -    override fun hashCode(): Int {
    -        var result = type.hashCode()
    -        result = 31 * result + (value?.hashCode() ?: 0)
    -        return result
    -    }
    -}
    -
    -// Runnable like this::toString or (new Object())::toString (non-static) or Random::nextRandomInt (static) etc
    -abstract class CgRunnable(override val type: ClassId, val methodId: MethodId): CgValue
    -
    -/**
    - * [referenceExpression] is "this" in this::toString or (new Object()) in (new Object())::toString (non-static)
    - */
    -class CgNonStaticRunnable(
    -    type: ClassId,
    -    val referenceExpression: CgReferenceExpression,
    -    methodId: MethodId
    -): CgRunnable(type, methodId)
    -
    -/**
    - * [classId] is Random is Random::nextRandomInt (static) etc
    - */
    -class CgStaticRunnable(type: ClassId, val classId: ClassId, methodId: MethodId): CgRunnable(type, methodId)
    -
    -// Array allocation
    -
    -open class CgAllocateArray(type: ClassId, elementType: ClassId, val size: Int)
    -    : CgReferenceExpression {
    -    override val type: ClassId by lazy { CgClassId(type.name, updateElementType(elementType), isNullable = type.isNullable) }
    -    val elementType: ClassId by lazy {
    -        workaround(WorkaroundReason.ARRAY_ELEMENT_TYPES_ALWAYS_NULLABLE) {
    -            // for now all array element types are nullable
    -            updateElementType(elementType)
    -        }
    -    }
    -
    -    private fun updateElementType(type: ClassId): ClassId =
    -        if (type.elementClassId != null) {
    -            CgClassId(type.name, updateElementType(type.elementClassId!!), isNullable = true)
    -        } else {
    -            CgClassId(type, isNullable = true)
    -        }
    -}
    -
    -class CgAllocateInitializedArray(val model: UtArrayModel)
    -    : CgAllocateArray(model.classId, model.classId.elementClassId!!, model.length)
    -
    -
    -// Spread operator (for Kotlin, empty for Java)
    -
    -class CgSpread(override val type: ClassId, val array: CgExpression): CgExpression
    -
    -// Enum constant
    -
    -data class CgEnumConstantAccess(
    -    val enumClass: ClassId,
    -    val name: String
    -) : CgReferenceExpression {
    -    override val type: ClassId = enumClass
    -}
    -
    -// Property access
    -
    -// TODO in general it is not CgReferenceExpression because field can have a primitive type
    -abstract class CgAbstractFieldAccess : CgReferenceExpression {
    -    abstract val fieldId: FieldId
    -
    -    override val type: ClassId
    -        get() = fieldId.type
    -}
    -
    -class CgFieldAccess(
    -    val caller: CgExpression,
    -    override val fieldId: FieldId
    -) : CgAbstractFieldAccess()
    -
    -class CgStaticFieldAccess(
    -    override val fieldId: FieldId
    -) : CgAbstractFieldAccess() {
    -    val declaringClass: ClassId = fieldId.declaringClass
    -    val fieldName: String = fieldId.name
    -}
    -
    -// Conditional statements
    -
    -class CgIfStatement(
    -    val condition: CgExpression,
    -    val trueBranch: List,
    -    val falseBranch: List? = null // false branch may be absent
    -) : CgStatement
    -
    -data class CgSwitchCaseLabel(
    -    val label: CgLiteral? = null, // have to be compile time constant (null for default label)
    -    val statements: MutableList
    -) : CgStatement
    -
    -data class CgSwitchCase(
    -    val value: CgExpression, // TODO required: 'char, byte, short, int, Character, Byte, Short, Integer, String, or an enum'
    -    val labels: List,
    -    val defaultLabel: CgSwitchCaseLabel? = null
    -) : CgStatement
    -
    -// Binary logical operators
    -
    -class CgLogicalAnd(
    -    val left: CgExpression,
    -    val right: CgExpression
    -) : CgExpression {
    -    override val type: ClassId = booleanClassId
    -}
    -
    -class CgLogicalOr(
    -    val left: CgExpression,
    -    val right: CgExpression
    -) : CgExpression {
    -    override val type: ClassId = booleanClassId
    -}
    -
    -// Acquisition of array length, e.g. args.length
    -
    -/**
    - * @param variable represents an array variable
    - */
    -class CgGetLength(val variable: CgVariable) : CgExpression {
    -    override val type: ClassId = intClassId
    -}
    -
    -// Acquisition of java or kotlin class, e.g. MyClass.class in Java, MyClass::class.java in Kotlin or MyClass::class for Kotlin classes
    -
    -sealed class CgGetClass(val classId: ClassId) : CgReferenceExpression {
    -    override val type: ClassId = Class::class.id
    -}
    -
    -class CgGetJavaClass(classId: ClassId) : CgGetClass(classId)
    -
    -class CgGetKotlinClass(classId: ClassId) : CgGetClass(classId)
    -
    -// Executable calls
    -
    -class CgStatementExecutableCall(val call: CgExecutableCall) : CgStatement
    -
    -// TODO in general it is not CgReferenceExpression because returned value can have a primitive type
    -//  (or no value can be returned)
    -abstract class CgExecutableCall : CgReferenceExpression {
    -    abstract val executableId: ExecutableId
    -    abstract val arguments: List
    -    abstract val typeParameters: TypeParameters
    -}
    -
    -class CgConstructorCall(
    -    override val executableId: ConstructorId,
    -    override val arguments: List,
    -    override val typeParameters: TypeParameters = TypeParameters()
    -) : CgExecutableCall() {
    -    override val type: ClassId = executableId.classId
    -}
    -
    -class CgMethodCall(
    -    val caller: CgExpression?,
    -    override val executableId: MethodId,
    -    override val arguments: List,
    -    override val typeParameters: TypeParameters = TypeParameters()
    -) : CgExecutableCall() {
    -    override val type: ClassId = executableId.returnType
    -}
    -
    -fun CgExecutableCall.toStatement(): CgStatementExecutableCall = CgStatementExecutableCall(this)
    -
    -// Throw statement
    -
    -class CgThrowStatement(
    -    val exception: CgExpression
    -) : CgStatement
    -
    -// Empty line
    -
    -class CgEmptyLine : CgStatement
    -
    -class CgClassId(
    -    name: String,
    -    elementClassId: ClassId? = null,
    -    override val typeParameters: TypeParameters = TypeParameters(),
    -    override val isNullable: Boolean = true,
    -) : ClassId(name, elementClassId) {
    -    constructor(
    -        classId: ClassId,
    -        typeParameters: TypeParameters = TypeParameters(),
    -        isNullable: Boolean = true,
    -    ) : this(classId.name, classId.elementClassId, typeParameters, isNullable)
    -}
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt
    deleted file mode 100644
    index 6203ed01c7..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassIdUtil.kt
    +++ /dev/null
    @@ -1,25 +0,0 @@
    -package org.utbot.framework.codegen.model.util
    -
    -import org.utbot.framework.plugin.api.ClassId
    -import org.utbot.framework.plugin.api.util.id
    -
    -/**
    - * For now we will count class accessible if it is:
    - * - Public or package-private within package [packageName].
    - * - It's outer class (if exists) is accessible too.
    - * NOTE: local and synthetic classes are considered as inaccessible.
    - * NOTE: protected classes cannot be accessed because test class does not extend any classes.
    - *
    - * @param packageName name of the package we check accessibility from
    - */
    -infix fun ClassId.isAccessibleFrom(packageName: String): Boolean {
    -    val isOuterClassAccessible = if (isNested) {
    -        outerClass!!.id.isAccessibleFrom(packageName)
    -    } else {
    -        true
    -    }
    -
    -    val isAccessibleFromPackageByModifiers = isPublic || (this.packageName == packageName && (isPackagePrivate || isProtected))
    -
    -    return isOuterClassAccessible && isAccessibleFromPackageByModifiers && !isLocal && !isSynthetic
    -}
    \ No newline at end of file
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassNameUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassNameUtil.kt
    deleted file mode 100644
    index 35815952e7..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ClassNameUtil.kt
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -package org.utbot.framework.codegen.model.util
    -
    -/**
    - * Creates a name of test class.
    - * We need the name in code and the name of test class file be similar.
    - * On this way we need to avoid symbols like '$'.
    - */
    -fun createTestClassName(name: String): String = name
    -    .substringAfterLast('.')
    -    .replace('\$', '_')
    \ No newline at end of file
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyPatterns.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyPatterns.kt
    deleted file mode 100644
    index 3314d1deed..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyPatterns.kt
    +++ /dev/null
    @@ -1,73 +0,0 @@
    -package org.utbot.framework.codegen.model.util
    -
    -import org.utbot.framework.codegen.Junit4
    -import org.utbot.framework.codegen.Junit5
    -import org.utbot.framework.codegen.TestFramework
    -import org.utbot.framework.codegen.TestNg
    -import org.utbot.framework.plugin.api.MockFramework
    -
    -data class Patterns(
    -    val moduleLibraryPatterns: List,
    -    val libraryPatterns: List,
    -)
    -
    -fun TestFramework.patterns(): Patterns {
    -    val moduleLibraryPatterns = when (this) {
    -        Junit4 -> junit4ModulePatterns
    -        Junit5 -> junit5ModulePatterns
    -        TestNg -> testNgModulePatterns
    -    }
    -    val libraryPatterns = when (this) {
    -        Junit4 -> junit4Patterns
    -        Junit5 -> junit5Patterns
    -        TestNg -> testNgPatterns
    -    }
    -
    -    return Patterns(moduleLibraryPatterns, libraryPatterns)
    -}
    -
    -fun MockFramework.patterns(): Patterns {
    -    val moduleLibraryPatterns = when (this) {
    -        MockFramework.MOCKITO -> mockitoModulePatterns
    -    }
    -    val libraryPatterns = when (this) {
    -        MockFramework.MOCKITO -> mockitoPatterns
    -    }
    -
    -    return Patterns(moduleLibraryPatterns, libraryPatterns)
    -}
    -
    -val JUNIT_4_JAR_PATTERN = Regex("junit-4(\\.[0-9]+){1,2}")
    -val JUNIT_4_MVN_PATTERN = Regex("junit:junit:4(\\.[0-9]+){1,2}")
    -val JUNIT_4_BASIC_PATTERN = Regex("JUnit4")
    -val junit4Patterns = listOf(JUNIT_4_JAR_PATTERN, JUNIT_4_MVN_PATTERN, JUNIT_4_BASIC_PATTERN)
    -
    -val JUNIT4_BASIC_MODULE_PATTERN = Regex("junit$")
    -val junit4ModulePatterns = listOf(JUNIT4_BASIC_MODULE_PATTERN)
    -
    -val JUNIT_5_JAR_PATTERN = Regex("junit-jupiter-5(\\.[0-9]+){1,2}")
    -val JUNIT_5_MVN_PATTERN = Regex("org\\.junit\\.jupiter:junit-jupiter-api:5(\\.[0-9]+){1,2}")
    -val JUNIT_5_BASIC_PATTERN = Regex("JUnit5\\.4")
    -val junit5Patterns = listOf(JUNIT_5_JAR_PATTERN, JUNIT_5_MVN_PATTERN, JUNIT_5_BASIC_PATTERN)
    -
    -val JUNIT5_BASIC_MODULE_PATTERN = Regex("junit-jupiter")
    -val junit5ModulePatterns = listOf(JUNIT5_BASIC_MODULE_PATTERN)
    -
    -val TEST_NG_JAR_PATTERN = Regex("testng-[0-9](\\.[0-9]+){2}")
    -val TEST_NG_MVN_PATTERN = Regex("org\\.testng:testng:(\\.[0-9]+){3}")
    -val TEST_NG_BASIC_PATTERN = Regex("testng")
    -val testNgPatterns = listOf(TEST_NG_JAR_PATTERN, TEST_NG_MVN_PATTERN, TEST_NG_BASIC_PATTERN)
    -
    -val TEST_NG_BASIC_MODULE_PATTERN = Regex("testng")
    -val testNgModulePatterns = listOf(TEST_NG_BASIC_MODULE_PATTERN)
    -
    -val MOCKITO_JAR_PATTERN = Regex("mockito-core-[3-9](\\.[0-9]+){2}")
    -val MOCKITO_MVN_PATTERN = Regex("org\\.mockito:mockito-core:[3-9](\\.[0-9]+){2}")
    -val mockitoPatterns = listOf(MOCKITO_JAR_PATTERN, MOCKITO_MVN_PATTERN)
    -
    -val MOCKITO_BASIC_MODULE_PATTERN = Regex("mockito-core")
    -val mockitoModulePatterns = listOf(MOCKITO_BASIC_MODULE_PATTERN)
    -
    -const val MOCKITO_EXTENSIONS_STORAGE = "mockito-extensions"
    -const val MOCKITO_MOCKMAKER_FILE_NAME = "org.mockito.plugins.MockMaker"
    -val MOCKITO_EXTENSIONS_FILE_CONTENT = listOf("mock-maker-inline")
    \ No newline at end of file
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyUtils.kt
    deleted file mode 100644
    index fcaff8be3e..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DependencyUtils.kt
    +++ /dev/null
    @@ -1,97 +0,0 @@
    -package org.utbot.framework.codegen.model.util
    -
    -import org.utbot.framework.codegen.TestFramework
    -import org.utbot.framework.concrete.UtExecutionInstrumentation
    -import org.utbot.framework.plugin.api.MockFramework
    -import java.io.File
    -import java.util.jar.JarFile
    -import mu.KotlinLogging
    -
    -private val logger = KotlinLogging.logger {}
    -
    -/**
    - * Checks that dependency paths contains some frameworks
    - * and their versions correspond to our requirements.
    - *
    - * Note: [UtExecutionInstrumentation] must be in dependency path too
    - * as it is used by Engine in the child process in Concrete Executor.
    - */
    -fun checkFrameworkDependencies(dependencyPaths: String?) {
    -    if (dependencyPaths.isNullOrEmpty()) {
    -        error("Dependency paths is empty, no test framework and mock framework to generate tests")
    -    }
    -
    -    //TODO: JIRA:1659
    -    // check (somehow) that [UtExecutionInstrumentation] is in dependency path: in one of jars or folders
    -
    -    val dependencyPathsSequence = dependencyPaths.splitToSequence(File.pathSeparatorChar)
    -
    -    val dependencyNames = dependencyPathsSequence
    -        .mapNotNull { findDependencyName(it) }
    -        .map { it.toLowerCase() }
    -        .toSet()
    -
    -    val testFrameworkPatterns = TestFramework.allItems.map { it.patterns() }
    -    val testFrameworkFound = dependencyNames.matchesAnyOf(testFrameworkPatterns) ||
    -            dependencyPathsSequence.any { checkDependencyIsFatJar(it) }
    -
    -    if (!testFrameworkFound) {
    -        error("""
    -          Test frameworks are not found in dependency path $dependencyPaths, dependency names are:
    -          ${dependencyNames.joinToString(System.lineSeparator())}
    -          """
    -        )
    -    }
    -
    -    val mockFrameworkPatterns = MockFramework.allItems.map { it.patterns() }
    -    val mockFrameworkFound = dependencyNames.matchesAnyOf(mockFrameworkPatterns) ||
    -            dependencyPathsSequence.any { checkDependencyIsFatJar(it) }
    -
    -    if (!mockFrameworkFound) {
    -        error("""
    -          Mock frameworks are not found in dependency path $dependencyPaths, dependency names are:
    -          ${dependencyNames.joinToString(System.lineSeparator())}
    -          """
    -        )
    -    }
    -}
    -
    -private fun Set.matchesAnyOf(patterns: List): Boolean {
    -    val expressions = patterns.flatMap { it.moduleLibraryPatterns + it.libraryPatterns }
    -    return any { libraryName ->
    -        expressions.any { expr -> libraryName.let { expr.containsMatchIn(it) } }
    -    }
    -}
    -
    -private fun findDependencyName(jarPath: String): String? {
    -    try {
    -        val attributes = JarFile(jarPath).manifest.mainAttributes
    -
    -        val bundleName = attributes.getValue("Bundle-SymbolicName")
    -        val bundleVersion = attributes.getValue("Bundle-Version")
    -        val moduleName = attributes.getValue("Automatic-Module-Name")
    -        val implementationTitle = attributes.getValue("Implementation-Title")
    -        val implementationVersion = attributes.getValue("Implementation-Version")
    -
    -        if (bundleName != null) return "$bundleName:$bundleVersion"
    -        if (moduleName != null) return "$moduleName:$implementationTitle:$implementationVersion"
    -    } catch (e: Exception) {
    -        logger.warn { "Unexpected error during parsing $jarPath manifest file $e" }
    -    }
    -
    -    return null
    -}
    -
    -// We consider Fat JARs contain test frameworks and mock frameworks in the dependencies.
    -private fun checkDependencyIsFatJar(jarPath: String): Boolean {
    -    try {
    -        val attributes = JarFile(jarPath).manifest.mainAttributes
    -        val jarType = attributes.getValue("JAR-Type")
    -
    -        return jarType == "Fat JAR"
    -    } catch (e: Exception) {
    -        logger.warn { "Unexpected error during parsing $jarPath manifest file $e" }
    -    }
    -
    -    return false
    -}
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DslUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DslUtil.kt
    deleted file mode 100644
    index 5edbf60ec6..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/DslUtil.kt
    +++ /dev/null
    @@ -1,127 +0,0 @@
    -package org.utbot.framework.codegen.model.util
    -
    -import org.utbot.framework.codegen.model.constructor.tree.CgCallableAccessManager
    -import org.utbot.framework.codegen.model.tree.CgArrayElementAccess
    -import org.utbot.framework.codegen.model.tree.CgDecrement
    -import org.utbot.framework.codegen.model.tree.CgEqualTo
    -import org.utbot.framework.codegen.model.tree.CgExpression
    -import org.utbot.framework.codegen.model.tree.CgFieldAccess
    -import org.utbot.framework.codegen.model.tree.CgGetClass
    -import org.utbot.framework.codegen.model.tree.CgGetJavaClass
    -import org.utbot.framework.codegen.model.tree.CgGetKotlinClass
    -import org.utbot.framework.codegen.model.tree.CgGetLength
    -import org.utbot.framework.codegen.model.tree.CgGreaterThan
    -import org.utbot.framework.codegen.model.tree.CgIncrement
    -import org.utbot.framework.codegen.model.tree.CgLessThan
    -import org.utbot.framework.codegen.model.tree.CgLiteral
    -import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess
    -import org.utbot.framework.codegen.model.tree.CgThisInstance
    -import org.utbot.framework.codegen.model.tree.CgVariable
    -import org.utbot.framework.plugin.api.ClassId
    -import org.utbot.framework.plugin.api.CodegenLanguage
    -import org.utbot.framework.plugin.api.FieldId
    -import org.utbot.framework.plugin.api.MethodId
    -import org.utbot.framework.plugin.api.util.booleanClassId
    -import org.utbot.framework.plugin.api.util.byteClassId
    -import org.utbot.framework.plugin.api.util.charClassId
    -import org.utbot.framework.plugin.api.util.doubleClassId
    -import org.utbot.framework.plugin.api.util.floatClassId
    -import org.utbot.framework.plugin.api.util.intClassId
    -import org.utbot.framework.plugin.api.util.isArray
    -import org.utbot.framework.plugin.api.util.longClassId
    -import org.utbot.framework.plugin.api.util.objectClassId
    -import org.utbot.framework.plugin.api.util.shortClassId
    -import org.utbot.framework.plugin.api.util.stringClassId
    -
    -fun CgExpression.at(index: Any?): CgArrayElementAccess =
    -    CgArrayElementAccess(this, index.resolve())
    -
    -infix fun CgExpression.equalTo(other: Any?): CgEqualTo =
    -    CgEqualTo(this, other.resolve())
    -
    -infix fun CgExpression.lessThan(other: Any?): CgLessThan =
    -    CgLessThan(this, other.resolve())
    -
    -infix fun CgExpression.greaterThan(other: Any?): CgGreaterThan =
    -    CgGreaterThan(this, other.resolve())
    -
    -// Literals
    -
    -// TODO: is it OK to use Object as a type of null literal?
    -fun nullLiteral() = CgLiteral(objectClassId, null)
    -
    -fun intLiteral(num: Int) = CgLiteral(intClassId, num)
    -
    -fun longLiteral(num: Long) = CgLiteral(longClassId, num)
    -
    -fun byteLiteral(num: Byte) = CgLiteral(byteClassId, num)
    -
    -fun shortLiteral(num: Short) = CgLiteral(shortClassId, num)
    -
    -fun floatLiteral(num: Float) = CgLiteral(floatClassId, num)
    -
    -fun doubleLiteral(num: Double) = CgLiteral(doubleClassId, num)
    -
    -fun booleanLiteral(b: Boolean) = CgLiteral(booleanClassId, b)
    -
    -fun charLiteral(c: Char) = CgLiteral(charClassId, c)
    -
    -fun stringLiteral(string: String) = CgLiteral(stringClassId, string)
    -
    -// Field access
    -
    -// non-static fields
    -operator fun CgExpression.get(fieldId: FieldId): CgFieldAccess =
    -    CgFieldAccess(this, fieldId)
    -
    -// static fields
    -// TODO: unused receiver
    -operator fun ClassId.get(fieldId: FieldId): CgStaticFieldAccess =
    -    CgStaticFieldAccess(fieldId)
    -
    -// Get array length
    -
    -/**
    - * Returns length field access for array type variable and [getArrayLengthMethodId] call otherwise.
    - */
    -fun CgVariable.length(
    -    cgCallableAccessManager: CgCallableAccessManager,
    -    thisInstance: CgThisInstance,
    -    getArrayLengthMethodId: MethodId
    -): CgExpression {
    -    val thisVariable = this
    -
    -    return if (type.isArray) {
    -        CgGetLength(thisVariable)
    -    } else {
    -        with(cgCallableAccessManager) { thisInstance[getArrayLengthMethodId](thisVariable) }
    -    }
    -}
    -
    -// Increment and decrement
    -
    -fun CgVariable.inc(): CgIncrement = CgIncrement(this)
    -
    -fun CgVariable.dec(): CgDecrement = CgDecrement(this)
    -
    -fun Any?.resolve(): CgExpression = when (this) {
    -    null -> nullLiteral()
    -    is Int -> intLiteral(this)
    -    is Long -> longLiteral(this)
    -    is Byte -> byteLiteral(this)
    -    is Short -> shortLiteral(this)
    -    is Float -> floatLiteral(this)
    -    is Double -> doubleLiteral(this)
    -    is Boolean -> booleanLiteral(this)
    -    is Char -> charLiteral(this)
    -    is String -> stringLiteral(this)
    -    is CgExpression -> this
    -    else -> error("Expected primitive, string, null or CgExpression, but got: ${this::class}")
    -}
    -
    -fun Array<*>.resolve(): List = map { it.resolve() }
    -
    -fun classLiteralAnnotationArgument(id: ClassId, codegenLanguage: CodegenLanguage): CgGetClass = when (codegenLanguage) {
    -    CodegenLanguage.JAVA -> CgGetJavaClass(id)
    -    CodegenLanguage.KOTLIN -> CgGetKotlinClass(id)
    -}
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ExecutableIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ExecutableIdUtil.kt
    deleted file mode 100644
    index e5150759ab..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/ExecutableIdUtil.kt
    +++ /dev/null
    @@ -1,15 +0,0 @@
    -package org.utbot.framework.codegen.model.util
    -
    -import org.utbot.framework.plugin.api.ExecutableId
    -
    -/**
    - * For now we will count executable accessible if it is whether public
    - * or package-private inside target package [packageName].
    - *
    - * @param packageName name of the package we check accessibility from
    - */
    -fun ExecutableId.isAccessibleFrom(packageName: String): Boolean {
    -    val isAccessibleFromPackageByModifiers = isPublic || (classId.packageName == packageName && (isPackagePrivate || isProtected))
    -
    -    return classId.isAccessibleFrom(packageName) && isAccessibleFromPackageByModifiers
    -}
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt
    deleted file mode 100644
    index aa3bb3f763..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/FieldIdUtil.kt
    +++ /dev/null
    @@ -1,32 +0,0 @@
    -package org.utbot.framework.codegen.model.util
    -
    -import org.utbot.framework.plugin.api.FieldId
    -import org.utbot.framework.plugin.api.util.id
    -
    -/**
    - * For now we will count field accessible if it is not private and its class is also accessible,
    - * because we generate tests in the same package with the class under test,
    - * which means we can access public, protected and package-private fields
    - *
    - * @param packageName name of the package we check accessibility from
    - */
    -fun FieldId.isAccessibleFrom(packageName: String): Boolean {
    -    val isClassAccessible = declaringClass.isAccessibleFrom(packageName)
    -    val isAccessibleByVisibility = isPublic || (declaringClass.packageName == packageName && (isPackagePrivate || isProtected))
    -    val isAccessibleFromPackageByModifiers = isAccessibleByVisibility && !isSynthetic
    -
    -    return isClassAccessible && isAccessibleFromPackageByModifiers
    -}
    -
    -/**
    - * Whether or not a field can be set without reflection
    - */
    -fun FieldId.canBeSetIn(packageName: String): Boolean = isAccessibleFrom(packageName) && !isFinal
    -
    -private val systemClassId = System::class.id
    -
    -/**
    - * Security field is inaccessible in Runtime even via reflection.
    - */
    -val FieldId.isInaccessible: Boolean
    -    get() = name == "security" && declaringClass == systemClassId
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/TreeUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/TreeUtil.kt
    deleted file mode 100644
    index c117ed9768..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/TreeUtil.kt
    +++ /dev/null
    @@ -1,22 +0,0 @@
    -package org.utbot.framework.codegen.model.util
    -
    -import org.utbot.framework.codegen.model.tree.CgStatement
    -import org.utbot.framework.codegen.model.tree.CgVariable
    -
    -data class CgExceptionHandler(
    -        val exception: CgVariable,
    -        val statements: List
    -)
    -
    -class CgExceptionHandlerBuilder {
    -    lateinit var exception: CgVariable
    -    lateinit var statements: List
    -
    -    fun build(): CgExceptionHandler {
    -        return CgExceptionHandler(exception, statements)
    -    }
    -}
    -
    -internal fun buildExceptionHandler(init: CgExceptionHandlerBuilder.() -> Unit): CgExceptionHandler {
    -    return CgExceptionHandlerBuilder().apply(init).build()
    -}
    diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt
    deleted file mode 100644
    index 866787fb17..0000000000
    --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgAbstractRenderer.kt
    +++ /dev/null
    @@ -1,902 +0,0 @@
    -package org.utbot.framework.codegen.model.visitor
    -
    -import org.apache.commons.text.StringEscapeUtils
    -import org.utbot.common.WorkaroundReason.LONG_CODE_FRAGMENTS
    -import org.utbot.common.workaround
    -import org.utbot.framework.codegen.Import
    -import org.utbot.framework.codegen.RegularImport
    -import org.utbot.framework.codegen.StaticImport
    -import org.utbot.framework.codegen.model.constructor.context.CgContext
    -import org.utbot.framework.codegen.model.tree.CgAbstractFieldAccess
    -import org.utbot.framework.codegen.model.tree.CgAbstractMultilineComment
    -import org.utbot.framework.codegen.model.tree.CgArrayElementAccess
    -import org.utbot.framework.codegen.model.tree.CgAssignment
    -import org.utbot.framework.codegen.model.tree.CgBreakStatement
    -import org.utbot.framework.codegen.model.tree.CgComment
    -import org.utbot.framework.codegen.model.tree.CgCommentedAnnotation
    -import org.utbot.framework.codegen.model.tree.CgComparison
    -import org.utbot.framework.codegen.model.tree.CgContinueStatement
    -import org.utbot.framework.codegen.model.tree.CgDeclaration
    -import org.utbot.framework.codegen.model.tree.CgDecrement
    -import org.utbot.framework.codegen.model.tree.CgDoWhileLoop
    -import org.utbot.framework.codegen.model.tree.CgDocClassLinkStmt
    -import org.utbot.framework.codegen.model.tree.CgDocCodeStmt
    -import org.utbot.framework.codegen.model.tree.CgDocMethodLinkStmt
    -import org.utbot.framework.codegen.model.tree.CgDocPreTagStatement
    -import org.utbot.framework.codegen.model.tree.CgDocRegularStmt
    -import org.utbot.framework.codegen.model.tree.CgDocumentationComment
    -import org.utbot.framework.codegen.model.tree.CgElement
    -import org.utbot.framework.codegen.model.tree.CgEmptyLine
    -import org.utbot.framework.codegen.model.tree.CgEnumConstantAccess
    -import org.utbot.framework.codegen.model.tree.CgErrorTestMethod
    -import org.utbot.framework.codegen.model.tree.CgErrorWrapper
    -import org.utbot.framework.codegen.model.tree.CgExecutableCall
    -import org.utbot.framework.codegen.model.tree.CgExecutableUnderTestCluster
    -import org.utbot.framework.codegen.model.tree.CgExpression
    -import org.utbot.framework.codegen.model.tree.CgFieldAccess
    -import org.utbot.framework.codegen.model.tree.CgForLoop
    -import org.utbot.framework.codegen.model.tree.CgGreaterThan
    -import org.utbot.framework.codegen.model.tree.CgIfStatement
    -import org.utbot.framework.codegen.model.tree.CgIncrement
    -import org.utbot.framework.codegen.model.tree.CgInnerBlock
    -import org.utbot.framework.codegen.model.tree.CgLessThan
    -import org.utbot.framework.codegen.model.tree.CgLiteral
    -import org.utbot.framework.codegen.model.tree.CgLogicalAnd
    -import org.utbot.framework.codegen.model.tree.CgLogicalOr
    -import org.utbot.framework.codegen.model.tree.CgLoop
    -import org.utbot.framework.codegen.model.tree.CgMethod
    -import org.utbot.framework.codegen.model.tree.CgMethodCall
    -import org.utbot.framework.codegen.model.tree.CgMultilineComment
    -import org.utbot.framework.codegen.model.tree.CgMultipleArgsAnnotation
    -import org.utbot.framework.codegen.model.tree.CgNamedAnnotationArgument
    -import org.utbot.framework.codegen.model.tree.CgNonStaticRunnable
    -import org.utbot.framework.codegen.model.tree.CgNotNullVariable
    -import org.utbot.framework.codegen.model.tree.CgParameterDeclaration
    -import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod
    -import org.utbot.framework.codegen.model.tree.CgRegion
    -import org.utbot.framework.codegen.model.tree.CgReturnStatement
    -import org.utbot.framework.codegen.model.tree.CgSimpleRegion
    -import org.utbot.framework.codegen.model.tree.CgSingleArgAnnotation
    -import org.utbot.framework.codegen.model.tree.CgSingleLineComment
    -import org.utbot.framework.codegen.model.tree.CgSpread
    -import org.utbot.framework.codegen.model.tree.CgStatement
    -import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall
    -import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess
    -import org.utbot.framework.codegen.model.tree.CgStaticRunnable
    -import org.utbot.framework.codegen.model.tree.CgStaticsRegion
    -import org.utbot.framework.codegen.model.tree.CgTestClass
    -import org.utbot.framework.codegen.model.tree.CgTestClassBody
    -import org.utbot.framework.codegen.model.tree.CgTestClassFile
    -import org.utbot.framework.codegen.model.tree.CgTestMethod
    -import org.utbot.framework.codegen.model.tree.CgTestMethodCluster
    -import org.utbot.framework.codegen.model.tree.CgThisInstance
    -import org.utbot.framework.codegen.model.tree.CgThrowStatement
    -import org.utbot.framework.codegen.model.tree.CgTripleSlashMultilineComment
    -import org.utbot.framework.codegen.model.tree.CgTryCatch
    -import org.utbot.framework.codegen.model.tree.CgUtilMethod
    -import org.utbot.framework.codegen.model.tree.CgVariable
    -import org.utbot.framework.codegen.model.tree.CgWhileLoop
    -import org.utbot.framework.codegen.model.util.CgPrinter
    -import org.utbot.framework.codegen.model.util.CgPrinterImpl
    -import org.utbot.framework.codegen.model.util.resolve
    -import org.utbot.framework.plugin.api.ClassId
    -import org.utbot.framework.plugin.api.CodegenLanguage
    -import org.utbot.framework.plugin.api.MethodId
    -import org.utbot.framework.plugin.api.TypeParameters
    -import org.utbot.framework.plugin.api.UtArrayModel
    -import org.utbot.framework.plugin.api.UtModel
    -import org.utbot.framework.plugin.api.UtNullModel
    -import org.utbot.framework.plugin.api.UtPrimitiveModel
    -import org.utbot.framework.plugin.api.util.booleanClassId
    -import org.utbot.framework.plugin.api.util.byteClassId
    -import org.utbot.framework.plugin.api.util.charClassId
    -import org.utbot.framework.plugin.api.util.doubleClassId
    -import org.utbot.framework.plugin.api.util.floatClassId
    -import org.utbot.framework.plugin.api.util.intClassId
    -import org.utbot.framework.plugin.api.util.longClassId
    -import org.utbot.framework.plugin.api.util.shortClassId
    -
    -internal abstract class CgAbstractRenderer(val context: CgContext, val printer: CgPrinter = CgPrinterImpl()) : CgVisitor,
    -    CgPrinter by printer {
    -
    -    protected abstract val statementEnding: String
    -
    -    protected abstract val logicalAnd: String
    -    protected abstract val logicalOr: String
    -
    -    protected val regionStart: String = "///region"
    -    protected val regionEnd: String = "///endregion"
    -
    -    protected abstract val language: CodegenLanguage
    -
    -    protected abstract val langPackage: String
    -
    -    //We may render array elements in initializer in one line or in separate lines:
    -    //items count in one line depends on the value type.
    -    protected fun arrayElementsInLine(constModel: UtModel): Int {
    -        if (constModel is UtNullModel) return 10
    -        return when (constModel.classId) {
    -            intClassId, byteClassId, longClassId, charClassId -> 8
    -            booleanClassId, shortClassId, doubleClassId, floatClassId -> 6
    -            else -> error("Non primitive value of type ${constModel.classId} is unexpected in array initializer")
    -        }
    -    }
    -
    -    private val MethodId.accessibleByName: Boolean
    -        get() = (context.shouldOptimizeImports && this in context.importedStaticMethods) || classId == context.currentTestClass
    -
    -    override fun visit(element: CgElement) {
    -        val error =
    -            "CgRenderer has reached the top of Cg elements hierarchy and did not find a method for ${element::class}"
    -        throw IllegalArgumentException(error)
    -    }
    -
    -    override fun visit(element: CgTestClassFile) {
    -        renderClassPackage(element.testClass)
    -        renderClassFileImports(element)
    -        element.testClass.accept(this)
    -    }
    -
    -    override fun visit(element: CgTestClassBody) {
    -        // render regions for test methods and utils
    -        for ((i, region) in (element.regions + element.utilsRegion).withIndex()) {
    -            if (i != 0) println()
    -
    -            region.accept(this)
    -        }
    -    }
    -
    -    /**
    -     * Render the region only if it is not empty.
    -     */
    -    override fun visit(element: CgStaticsRegion) {
    -        element.render()
    -    }
    -
    -    /**
    -     * Render the region only if it is not empty.
    -     */
    -    override fun visit(element: CgSimpleRegion<*>) {
    -        element.render()
    -    }
    -
    -    /**
    -     * Render the cluster only if it is not empty.
    -     */
    -    override fun visit(element: CgTestMethodCluster) {
    -        element.render()
    -    }
    -
    -    /**
    -     * Render the cluster only if it is not empty.
    -     */
    -    override fun visit(element: CgExecutableUnderTestCluster) {
    -        // We print the next line after all contained regions to prevent gluing of region ends
    -        element.render(printLineAfterContentEnd = true)
    -    }
    -
    -    /**
    -     * Renders the region with a specific rendering for [CgTestMethodCluster.description]
    -     */
    -    private fun CgRegion<*>.render(printLineAfterContentEnd: Boolean = false) {
    -        if (content.isEmpty()) return
    -
    -        print(regionStart)
    -        header?.let { print(" $it") }
    -        println()
    -
    -        if (this is CgTestMethodCluster) description?.accept(this@CgAbstractRenderer)
    -
    -        for (method in content) {
    -            println()
    -            method.accept(this@CgAbstractRenderer)
    -        }
    -
    -        if (printLineAfterContentEnd) println()
    -
    -        println(regionEnd)
    -    }
    -
    -    override fun visit(element: CgUtilMethod) {
    -        context.currentTestClass
    -                .utilMethodById(element.id, context)
    -                .split("\n")
    -                .forEach { line -> println(line) }
    -    }
    -
    -    // Methods
    -
    -    override fun visit(element: CgMethod) {
    -        // TODO introduce CgBlock
    -        print(" ")
    -        visit(element.statements, printNextLine = true)
    -    }
    -
    -    override fun visit(element: CgTestMethod) {
    -        renderMethodDocumentation(element)
    -        for (annotation in element.annotations) {
    -            annotation.accept(this)
    -        }
    -        renderMethodSignature(element)
    -        visit(element as CgMethod)
    -    }
    -
    -    override fun visit(element: CgErrorTestMethod) {
    -        renderMethodDocumentation(element)
    -        renderMethodSignature(element)
    -        visit(element as CgMethod)
    -    }
    -
    -    override fun visit(element: CgParameterizedTestDataProviderMethod) {
    -        for (annotation in element.annotations) {
    -            annotation.accept(this)
    -        }
    -        renderMethodSignature(element)
    -        visit(element as CgMethod)
    -    }
    -
    -    // Annotations
    -
    -    override fun visit(element: CgCommentedAnnotation) {
    -        print("//")
    -        element.annotation.accept(this)
    -    }
    -
    -    override fun visit(element: CgSingleArgAnnotation) {
    -        print("@${element.classId.asString()}")
    -        print("(")
    -        element.argument.accept(this)
    -        println(")")
    -    }
    -
    -    override fun visit(element: CgMultipleArgsAnnotation) {
    -        print("@${element.classId.asString()}")
    -        if (element.arguments.isNotEmpty()) {
    -            print("(")
    -            element.arguments.renderSeparated()
    -            print(")")
    -        }
    -        println()
    -    }
    -
    -    override fun visit(element: CgNamedAnnotationArgument) {
    -        print(element.name)
    -        print(" = ")
    -        element.value.accept(this)
    -    }
    -
    -    // Comments
    -
    -    override fun visit(element: CgComment) {
    -        visit(element as CgElement)
    -    }
    -
    -    override fun visit(element: CgSingleLineComment) {
    -        println("// ${element.comment}")
    -    }
    -
    -    override fun visit(element: CgAbstractMultilineComment) {
    -        visit(element as CgElement)
    -    }
    -
    -    override fun visit(element: CgTripleSlashMultilineComment) {
    -        for (line in element.lines) {
    -            println("/// $line")
    -        }
    -    }
    -
    -    override fun visit(element: CgMultilineComment) {
    -        val lines = element.lines
    -        if (lines.isEmpty()) return
    -
    -        if (lines.size == 1) {
    -            print("/* ${lines.first()} */")
    -            return
    -        }
    -
    -        // print lines saving indentation
    -        print("/* ")
    -        println(lines.first())
    -        lines.subList(1, lines.lastIndex).forEach { println(it) }
    -        print(lines.last())
    -        println(" */")
    -    }
    -
    -    override fun visit(element: CgDocumentationComment) {
    -        if (element.lines.all { it.isEmpty() }) return
    -
    -        println("/**")
    -        for (line in element.lines) line.accept(this)
    -        println(" */")
    -    }
    -    override fun visit(element: CgDocPreTagStatement) {
    -        if (element.content.all { it.isEmpty() }) return
    -
    -        println("
    ")
    -        for (stmt in element.content) stmt.accept(this)
    -        println("
    ") - } - override fun visit(element: CgDocCodeStmt) { - if (element.isEmpty()) return - - val text = element.stmt - .replace("\n", "\n * ") - //remove multiline comment symbols to avoid comment in comment effect - .replace("/*", "") - .replace("*/", "") - print("{@code $text }") - } - override fun visit(element: CgDocRegularStmt){ - if (element.isEmpty()) return - - print(element.stmt.replace("\n", "\n * ")) - } - override fun visit(element: CgDocClassLinkStmt) { - if (element.isEmpty()) return - - print(element.className) - } - override fun visit(element: CgDocMethodLinkStmt){ - if (element.isEmpty()) return - - print("${element.className}::${element.methodName}") //todo make it as link {@link org.utbot.examples.online.Loops#whileLoop(int) } - } - - /** - * Renders any block of code with curly braces. - * - * NOTE: [printNextLine] has default false value in [CgVisitor] - * - * NOTE: due to JVM restrictions for methods size - * [in 65536 bytes](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3) - * we comment long blocks - */ - override fun visit(block: List, printNextLine: Boolean) { - println("{") - - val isBlockTooLarge = workaround(LONG_CODE_FRAGMENTS) { block.size > LARGE_CODE_BLOCK_SIZE } - - if (isBlockTooLarge) { - print("/*") - println(" This block of code is ${block.size} lines long and could lead to compilation error") - } - - withIndent { - for (statement in block) { - statement.accept(this) - } - } - - if (isBlockTooLarge) println("*/") - - print("}") - - if (printNextLine) println() - } - - // Return statement - - override fun visit(element: CgReturnStatement) { - print("return ") - element.expression.accept(this) - println(statementEnding) - } - - // Array element access - - override fun visit(element: CgArrayElementAccess) { - element.array.accept(this) - print("[") - element.index.accept(this) - print("]") - } - - // Spread operator - - override fun visit(element: CgSpread) { - element.array.accept(this) - } - - // Loop conditions - - override fun visit(element: CgComparison) { - return visit(element as CgElement) - } - - override fun visit(element: CgLessThan) { - element.left.accept(this) - print(" < ") - element.right.accept(this) - } - - override fun visit(element: CgGreaterThan) { - element.left.accept(this) - print(" > ") - element.right.accept(this) - } - - // Increment and decrement - - override fun visit(element: CgIncrement) { - print("${element.variable.name}++") - } - - override fun visit(element: CgDecrement) { - print("${element.variable.name}--") - } - - // Try-catch - - override fun visit(element: CgTryCatch) { - print("try ") - // TODO: SAT-1329 actually Kotlin does not support try-with-resources so we have to use "use" method here - element.resources?.let { - println("(") - withIndent { - for (resource in element.resources) { - resource.accept(this) - } - } - print(") ") - } - // TODO introduce CgBlock - visit(element.statements) - for ((exception, statements) in element.handlers) { - print(" catch (") - renderExceptionCatchVariable(exception) - print(") ") - // TODO introduce CgBlock - visit(statements, printNextLine = element.finally == null) - } - element.finally?.let { - print(" finally ") - // TODO introduce CgBlock - visit(element.finally, printNextLine = true) - } - } - - abstract override fun visit(element: CgErrorWrapper) - - //Simple block - - abstract override fun visit(element: CgInnerBlock) - - // Loops - - override fun visit(element: CgLoop) { - return visit(element as CgElement) - } - - override fun visit(element: CgForLoop) { - renderForLoopVarControl(element) - print(") ") - // TODO introduce CgBlock - visit(element.statements) - println() - } - - override fun visit(element: CgWhileLoop) { - print("while (") - element.condition.accept(this) - print(") ") - // TODO introduce CgBlock - visit(element.statements) - println() - } - - override fun visit(element: CgDoWhileLoop) { - print("do ") - // TODO introduce CgBlock - visit(element.statements) - print(" while (") - element.condition.accept(this) - println(");") - } - - // Control statements - override fun visit(element: CgBreakStatement) { - println("break$statementEnding") - } - - override fun visit(element: CgContinueStatement) { - println("continue$statementEnding") - } - - // Variable declaration - - override fun visit(element: CgDeclaration) { - renderDeclarationLeftPart(element) - element.initializer?.let { - print(" = ") - it.accept(this) - } - println(statementEnding) - } - - // Variable assignment - - override fun visit(element: CgAssignment) { - element.lValue.accept(this) - print(" = ") - element.rValue.accept(this) - println(statementEnding) - } - - // Expressions - - override fun visit(element: CgExpression) { - visit(element as CgElement) - } - - // This instance - - override fun visit(element: CgThisInstance) { - print("this") - } - - // Variables - - override fun visit(element: CgVariable) { - print(element.name.escapeNamePossibleKeyword()) - } - - abstract override fun visit(element: CgNotNullVariable) - - // Method parameters - - abstract override fun visit(element: CgParameterDeclaration) - - // Primitive and String literals - - override fun visit(element: CgLiteral) { - val value = with(element.value) { - when (this) { - is Byte -> toStringConstant() - is Char -> toStringConstant() - is Short -> toStringConstant() - is Int -> toStringConstant() - is Long -> toStringConstant() - is Float -> toStringConstant() - is Double -> toStringConstant() - is Boolean -> toStringConstant() - is String -> toStringConstant() - else -> "$this" - } - } - print(value) - } - - // Non-static runnable like this::toString or (new Object())::toString etc - override fun visit(element: CgNonStaticRunnable) { - // TODO we need braces for expressions like (new Object())::toString but not for this::toString - print("(") - element.referenceExpression.accept(this) - print(")::") - print(element.methodId.name) - } - - // Static runnable like Random::nextRandomInt etc - override fun visit(element: CgStaticRunnable) { - print(element.classId.asString()) - print("::") - print(element.methodId.name) - } - - // Enum constant - - override fun visit(element: CgEnumConstantAccess) { - print(element.enumClass.asString()) - print(".") - print(element.name) - } - - // Property access - - override fun visit(element: CgAbstractFieldAccess) { - visit(element as CgElement) - } - - override fun visit(element: CgFieldAccess) { - element.caller.accept(this) - print(".") - print(element.fieldId.name) - } - - override fun visit(element: CgStaticFieldAccess) { - print(element.declaringClass.asString()) - print(".") - print(element.fieldName) - } - - // Conditional statement - - override fun visit(element: CgIfStatement) { - print("if (") - element.condition.accept(this) - print(") ") - // TODO introduce CgBlock - visit(element.trueBranch) - element.falseBranch?.let { - print(" else ") - // TODO introduce CgBlock - visit(element.falseBranch) - } - println() - } - - // Binary logical operators - - override fun visit(element: CgLogicalAnd) { - element.left.accept(this) - print(" $logicalAnd ") - element.right.accept(this) - } - - override fun visit(element: CgLogicalOr) { - element.left.accept(this) - print(" $logicalOr ") - element.right.accept(this) - } - - // Executable calls - - override fun visit(element: CgStatementExecutableCall) { - element.call.accept(this) - println(statementEnding) - } - - override fun visit(element: CgExecutableCall) { - visit(element as CgElement) - } - - // TODO: consider the case of generic functions - // TODO: write tests for all cases of method call rendering (with or without caller, etc.) - override fun visit(element: CgMethodCall) { - val caller = element.caller - if (caller != null) { - // 'this' can be omitted, otherwise render caller - if (caller !is CgThisInstance) { - caller.accept(this) - renderAccess(caller) - } - } else { - // for static methods render declaring class only if required - if (!element.executableId.accessibleByName) { - val method = element.executableId - print(method.classId.asString()) - print(".") - } - } - print(element.executableId.name.escapeNamePossibleKeyword()) - - renderTypeParameters(element.typeParameters) - renderExecutableCallArguments(element) - } - - // Throw statement - - override fun visit(element: CgThrowStatement) { - print("throw ") - element.exception.accept(this) - println(statementEnding) - } - - override fun visit(element: CgEmptyLine) { - println() - } - - override fun toString(): String = printer.toString() - - protected abstract fun renderRegularImport(regularImport: RegularImport) - protected abstract fun renderStaticImport(staticImport: StaticImport) - - //we render parameters in method signature on one line or on separate lines depending their amount - protected val maxParametersAmountInOneLine = 3 - - protected abstract fun renderMethodSignature(element: CgTestMethod) - protected abstract fun renderMethodSignature(element: CgErrorTestMethod) - protected abstract fun renderMethodSignature(element: CgParameterizedTestDataProviderMethod) - - protected abstract fun renderForLoopVarControl(element: CgForLoop) - - protected abstract fun renderDeclarationLeftPart(element: CgDeclaration) - - protected abstract fun toStringConstantImpl(byte: Byte): String - protected abstract fun toStringConstantImpl(short: Short): String - protected abstract fun toStringConstantImpl(int: Int): String - protected abstract fun toStringConstantImpl(long: Long): String - protected abstract fun toStringConstantImpl(float: Float): String - - protected abstract fun renderAccess(caller: CgExpression) - protected abstract fun renderTypeParameters(typeParameters: TypeParameters) - protected abstract fun renderExecutableCallArguments(executableCall: CgExecutableCall) - - protected abstract fun renderExceptionCatchVariable(exception: CgVariable) - - protected fun UtArrayModel.getElementExpr(index: Int): CgExpression { - val itemModel = stores.getOrDefault(index, constModel) - val cgValue: CgExpression = when (itemModel) { - is UtPrimitiveModel -> itemModel.value.resolve() - is UtNullModel -> null.resolve() - else -> error("Non primitive or null model $itemModel is unexpected in array initializer") - } - - return cgValue - } - - protected fun getEscapedImportRendering(import: Import): String = - import.qualifiedName - .split(".") - .joinToString(".") { it.escapeNamePossibleKeyword() } - - protected fun List.printSeparated(newLines: Boolean = false) { - for ((index, element) in this.withIndex()) { - print(element) - when { - index < lastIndex -> { - print(",") - if (newLines) println() else print(" ") - } - index == lastIndex -> if (newLines) println() - } - } - } - - protected fun List.renderSeparated(newLines: Boolean = false) { - for ((index, element) in this.withIndex()) { - element.accept(this@CgAbstractRenderer) - when { - index < lastIndex -> { - print(",") - if (newLines) println() else print(" ") - } - index == lastIndex -> if (newLines) println() - } - } - } - - protected fun UtArrayModel.renderElements(length: Int, elementsInLine: Int) { - if (length <= elementsInLine) { // one-line array - for (i in 0 until length) { - val expr = this.getElementExpr(i) - expr.accept(this@CgAbstractRenderer) - if (i != length - 1) { - print(", ") - } - } - } else { // multiline array - println() // line break after `int[] x = {` - withIndent { - for (i in 0 until length) { - val expr = this.getElementExpr(i) - expr.accept(this@CgAbstractRenderer) - - if (i == length - 1) { - println() - } else if (i % elementsInLine == elementsInLine - 1) { - println(",") - } else { - print(", ") - } - } - } - } - } - - protected inline fun withIndent(block: () -> Unit) { - try { - pushIndent() - block() - } finally { - popIndent() - } - } - - protected open fun isAccessibleBySimpleNameImpl(classId: ClassId): Boolean = - classId in context.importedClasses || classId.packageName == context.testClassPackageName - - protected abstract fun escapeNamePossibleKeywordImpl(s: String): String - - protected fun String.escapeNamePossibleKeyword(): String = escapeNamePossibleKeywordImpl(this) - - protected fun ClassId.asString(): String { - if (!context.shouldOptimizeImports) return canonicalName - - // use simpleNameWithEnclosings instead of simpleName to consider nested classes case - return if (this.isAccessibleBySimpleName()) simpleNameWithEnclosings else canonicalName - } - - private fun renderClassPackage(element: CgTestClass) { - if (element.packageName.isNotEmpty()) { - println("package ${element.packageName}${statementEnding}") - println() - } - } - - private fun renderClassFileImports(element: CgTestClassFile) { - val regularImports = element.imports.filterIsInstance() - val staticImports = element.imports.filterIsInstance() - - for (import in regularImports) { - renderRegularImport(import) - } - if (regularImports.isNotEmpty()) { - println() - } - - for (import in staticImports) { - renderStaticImport(import) - } - if (staticImports.isNotEmpty()) { - println() - } - } - - private fun renderMethodDocumentation(element: CgMethod) { - element.documentation.accept(this) - } - - private fun Byte.toStringConstant() = when { - this == Byte.MAX_VALUE -> "${langPackage}.Byte.MAX_VALUE" - this == Byte.MIN_VALUE -> "${langPackage}.Byte.MIN_VALUE" - else -> toStringConstantImpl(this) - } - - private fun Char.toStringConstant() = when (this) { - '\'' -> "'\\''" - else -> "'" + StringEscapeUtils.escapeJava("$this") + "'" - } - - private fun Short.toStringConstant() = when (this) { - Short.MAX_VALUE -> "${langPackage}.Short.MAX_VALUE" - Short.MIN_VALUE -> "${langPackage}.Short.MIN_VALUE" - else -> toStringConstantImpl(this) - } - - private fun Int.toStringConstant(): String = toStringConstantImpl(this) - - private fun Long.toStringConstant() = when { - this == Long.MAX_VALUE -> "${langPackage}.Long.MAX_VALUE" - this == Long.MIN_VALUE -> "${langPackage}.Long.MIN_VALUE" - else -> toStringConstantImpl(this) - } - - private fun Float.toStringConstant() = when { - isNaN() -> "${langPackage}.Float.NaN" - this == Float.POSITIVE_INFINITY -> "${langPackage}.Float.POSITIVE_INFINITY" - this == Float.NEGATIVE_INFINITY -> "${langPackage}.Float.NEGATIVE_INFINITY" - else -> toStringConstantImpl(this) - } - - private fun Double.toStringConstant() = when { - isNaN() -> "${langPackage}.Double.NaN" - this == Double.POSITIVE_INFINITY -> "${langPackage}.Double.POSITIVE_INFINITY" - this == Double.NEGATIVE_INFINITY -> "${langPackage}.Double.NEGATIVE_INFINITY" - else -> "$this" - } - - private fun Boolean.toStringConstant() = - if (this) "true" else "false" - - private fun String.toStringConstant(): String = "\"" + escapeCharacters() + "\"" - - protected abstract fun String.escapeCharacters(): String - - private fun ClassId.isAccessibleBySimpleName(): Boolean = isAccessibleBySimpleNameImpl(this) - - companion object { - fun makeRenderer( - context: CgContext, - printer: CgPrinter = CgPrinterImpl() - ): CgAbstractRenderer = - when (context.codegenLanguage) { - CodegenLanguage.JAVA -> CgJavaRenderer(context, printer) - CodegenLanguage.KOTLIN -> CgKotlinRenderer(context, printer) - } - - /** - * @see [LONG_CODE_FRAGMENTS] - */ - private const val LARGE_CODE_BLOCK_SIZE: Int = 1000 - } -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt deleted file mode 100644 index 751057a1ab..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgJavaRenderer.kt +++ /dev/null @@ -1,338 +0,0 @@ -package org.utbot.framework.codegen.model.visitor - -import org.apache.commons.text.StringEscapeUtils -import org.utbot.framework.codegen.RegularImport -import org.utbot.framework.codegen.StaticImport -import org.utbot.framework.codegen.model.constructor.context.CgContext -import org.utbot.framework.codegen.model.tree.CgAllocateArray -import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray -import org.utbot.framework.codegen.model.tree.CgAnonymousFunction -import org.utbot.framework.codegen.model.tree.CgArrayAnnotationArgument -import org.utbot.framework.codegen.model.tree.CgBreakStatement -import org.utbot.framework.codegen.model.tree.CgConstructorCall -import org.utbot.framework.codegen.model.tree.CgDeclaration -import org.utbot.framework.codegen.model.tree.CgEqualTo -import org.utbot.framework.codegen.model.tree.CgErrorTestMethod -import org.utbot.framework.codegen.model.tree.CgErrorWrapper -import org.utbot.framework.codegen.model.tree.CgExecutableCall -import org.utbot.framework.codegen.model.tree.CgExpression -import org.utbot.framework.codegen.model.tree.CgForLoop -import org.utbot.framework.codegen.model.tree.CgGetJavaClass -import org.utbot.framework.codegen.model.tree.CgGetKotlinClass -import org.utbot.framework.codegen.model.tree.CgGetLength -import org.utbot.framework.codegen.model.tree.CgInnerBlock -import org.utbot.framework.codegen.model.tree.CgMethod -import org.utbot.framework.codegen.model.tree.CgNotNullVariable -import org.utbot.framework.codegen.model.tree.CgParameterDeclaration -import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod -import org.utbot.framework.codegen.model.tree.CgReturnStatement -import org.utbot.framework.codegen.model.tree.CgStatement -import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall -import org.utbot.framework.codegen.model.tree.CgSwitchCase -import org.utbot.framework.codegen.model.tree.CgSwitchCaseLabel -import org.utbot.framework.codegen.model.tree.CgTestClass -import org.utbot.framework.codegen.model.tree.CgTestMethod -import org.utbot.framework.codegen.model.tree.CgTypeCast -import org.utbot.framework.codegen.model.tree.CgVariable -import org.utbot.framework.codegen.model.util.CgPrinter -import org.utbot.framework.codegen.model.util.CgPrinterImpl -import org.utbot.framework.codegen.model.util.nullLiteral -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.TypeParameters -import org.utbot.framework.plugin.api.util.wrapperByPrimitive - -internal class CgJavaRenderer(context: CgContext, printer: CgPrinter = CgPrinterImpl()) : - CgAbstractRenderer(context, printer) { - - override val statementEnding: String = ";" - - override val logicalAnd: String - get() = "&&" - - override val logicalOr: String - get() = "||" - - override val language: CodegenLanguage = CodegenLanguage.JAVA - - override val langPackage: String = "java.lang" - - override fun visit(element: CgTestClass) { - for (annotation in element.annotations) { - annotation.accept(this) - } - print("public class ") - print(element.simpleName) - if (element.superclass != null) { - print(" extends ${element.superclass.asString()}") - } - if (element.interfaces.isNotEmpty()) { - print(" implements ") - element.interfaces.map { it.asString() }.printSeparated() - } - println(" {") - withIndent { element.body.accept(this) } - println("}") - } - - override fun visit(element: CgArrayAnnotationArgument) { - print("{") - element.values.renderSeparated() - print("}") - } - - override fun visit(element: CgAnonymousFunction) { - print("(") - element.parameters.renderSeparated() - print(") -> ") - // TODO introduce CgBlock - - val expression = element.body.singleExpressionOrNull() - // expression lambda can be rendered without curly braces - if (expression != null) { - expression.accept(this) - - return - } - - visit(element.body) - } - - override fun visit(element: CgEqualTo) { - element.left.accept(this) - print(" == ") - element.right.accept(this) - } - - override fun visit(element: CgTypeCast) { - val expr = element.expression - val wrappedTargetType = wrapperByPrimitive.getOrDefault(element.targetType, element.targetType) - val exprTypeIsSimilar = expr.type == element.targetType || expr.type == wrappedTargetType - - // cast for null is mandatory in case of ambiguity - for example, readObject(Object) and readObject(Map) - if (exprTypeIsSimilar && expr != nullLiteral()) { - element.expression.accept(this) - return - } - - print("(") - print("(") - print(wrappedTargetType.asString()) - print(") ") - element.expression.accept(this) - print(")") - } - - override fun visit(element: CgErrorWrapper) { - element.expression.accept(this) - } - - override fun visit(element: CgParameterDeclaration) { - if (element.isVararg) { - print(element.type.elementClassId!!.asString()) - print("...") - } else { - print(element.type.asString()) - } - print(" ") - print(element.name.escapeNamePossibleKeyword()) - } - - override fun visit(element: CgGetJavaClass) { - // TODO: check how it works on ref types, primitives, ref arrays, primitive arrays, etc. - print(element.classId.asString()) - print(".class") - } - - override fun visit(element: CgGetKotlinClass) { - // For now we assume that we never need KClass in the generated Java test classes. - // If it changes, this error may be removed. - error("KClass attempted to be used in the Java test class") - } - - override fun visit(element: CgNotNullVariable) { - print(element.name.escapeNamePossibleKeyword()) - } - - override fun visit(element: CgAllocateArray) { - // TODO: Arsen strongly required to rewrite later - val typeName = element.type.canonicalName.substringBefore("[") - val otherDimensions = element.type.canonicalName.substringAfter("]") - print("new $typeName[${element.size}]$otherDimensions") - } - - override fun visit(element: CgAllocateInitializedArray) { - val arrayModel = element.model - val elementsInLine = arrayElementsInLine(arrayModel.constModel) - - print("{") - arrayModel.renderElements(element.size, elementsInLine) - print("}") - } - - override fun visit(element: CgGetLength) { - element.variable.accept(this) - print(".length") - } - - override fun visit(element: CgConstructorCall) { - print("new ") - print(element.executableId.classId.asString()) - renderExecutableCallArguments(element) - } - - override fun renderRegularImport(regularImport: RegularImport) { - val escapedImport = getEscapedImportRendering(regularImport) - println("import $escapedImport$statementEnding") - } - - override fun renderStaticImport(staticImport: StaticImport) { - val escapedImport = getEscapedImportRendering(staticImport) - println("import static $escapedImport$statementEnding") - } - - override fun renderMethodSignature(element: CgTestMethod) { - // test methods always have void return type - print("public void ") - print(element.name) - - print("(") - val newLinesNeeded = element.parameters.size > maxParametersAmountInOneLine - element.parameters.renderSeparated(newLinesNeeded) - print(")") - - renderExceptions(element) - } - - override fun renderMethodSignature(element: CgErrorTestMethod) { - // error test methods always have void return type - println("public void ${element.name}()") - } - - override fun renderMethodSignature(element: CgParameterizedTestDataProviderMethod) { - //we do not have a good string representation for two-dimensional array, so this strange if-else is required - val returnType = - if (element.returnType.simpleName == "Object[][]") "java.lang.Object[][]" else "${element.returnType}" - print("public static $returnType ${element.name}() throws Exception") - } - - override fun visit(element: CgInnerBlock) { - println("{") - withIndent { - for (statement in element.statements) { - statement.accept(this) - } - } - println("}") - } - - override fun renderForLoopVarControl(element: CgForLoop) { - print("for (") - // TODO: rewrite in the future - with(element.initialization) { - print(variableType.asString()) - print(" ") - visit(variable) - initializer?.let { - print(" = ") - it.accept(this@CgJavaRenderer) - } - print("$statementEnding ") - } - element.condition.accept(this) - print("$statementEnding ") - element.update.accept(this) - } - - override fun renderDeclarationLeftPart(element: CgDeclaration) { - print(element.variableType.asString()) - print(" ") - visit(element.variable) - } - - override fun renderAccess(caller: CgExpression) { - print(".") - } - - override fun visit(element: CgSwitchCaseLabel) { - if (element.label != null) { - print("case ") - element.label.accept(this) - } else { - print("default") - } - println(":") - withIndent { - for (statement in element.statements) { - statement.accept(this) - } - // break statement in the end - CgBreakStatement.accept(this) - } - } - - override fun visit(element: CgSwitchCase) { - print("switch (") - element.value.accept(this) - println(") {") - withIndent { - for (caseLabel in element.labels) { - caseLabel.accept(this) - } - element.defaultLabel?.accept(this) - } - println("}") - } - - override fun toStringConstantImpl(byte: Byte): String = "(byte) $byte" - - override fun toStringConstantImpl(short: Short): String = "(short) $short" - - override fun toStringConstantImpl(long: Long): String = "${long}L" - - override fun toStringConstantImpl(float: Float): String = "${float}f" - - override fun toStringConstantImpl(int: Int): String = when (int) { - Int.MAX_VALUE -> "Integer.MAX_VALUE" - Int.MIN_VALUE -> "Integer.MIN_VALUE" - else -> "$int" - } - - override fun String.escapeCharacters(): String = StringEscapeUtils.escapeJava(this) - - override fun renderExecutableCallArguments(executableCall: CgExecutableCall) { - print("(") - executableCall.arguments.renderSeparated() - print(")") - } - - override fun renderTypeParameters(typeParameters: TypeParameters) {} - - override fun renderExceptionCatchVariable(exception: CgVariable) { - print("${exception.type} ${exception.name.escapeNamePossibleKeyword()}") - } - - override fun isAccessibleBySimpleNameImpl(classId: ClassId): Boolean = - super.isAccessibleBySimpleNameImpl(classId) || classId.packageName == "java.lang" - - override fun escapeNamePossibleKeywordImpl(s: String): String = s - - private fun renderExceptions(method: CgMethod) { - method.exceptions.takeIf { it.isNotEmpty() }?.let { exceptions -> - print(" throws ") - print(exceptions.joinToString(separator = ", ", postfix = " ") { it.asString() }) - } - } - - /** - * Returns containing [CgExpression] if [this] represents return statement or one executable call, and null otherwise. - */ - private fun List.singleExpressionOrNull(): CgExpression? = - singleOrNull().let { - when (it) { - is CgReturnStatement -> it.expression - is CgStatementExecutableCall -> it.call - else -> null - } - } -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt deleted file mode 100644 index c5fe8c3bd0..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgKotlinRenderer.kt +++ /dev/null @@ -1,503 +0,0 @@ -package org.utbot.framework.codegen.model.visitor - -import org.apache.commons.text.StringEscapeUtils -import org.utbot.common.WorkaroundReason -import org.utbot.common.workaround -import org.utbot.framework.codegen.RegularImport -import org.utbot.framework.codegen.StaticImport -import org.utbot.framework.codegen.isLanguageKeyword -import org.utbot.framework.codegen.model.constructor.context.CgContext -import org.utbot.framework.codegen.model.tree.CgAllocateArray -import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray -import org.utbot.framework.codegen.model.tree.CgAnonymousFunction -import org.utbot.framework.codegen.model.tree.CgArrayAnnotationArgument -import org.utbot.framework.codegen.model.tree.CgArrayElementAccess -import org.utbot.framework.codegen.model.tree.CgComparison -import org.utbot.framework.codegen.model.tree.CgConstructorCall -import org.utbot.framework.codegen.model.tree.CgDeclaration -import org.utbot.framework.codegen.model.tree.CgEqualTo -import org.utbot.framework.codegen.model.tree.CgErrorTestMethod -import org.utbot.framework.codegen.model.tree.CgErrorWrapper -import org.utbot.framework.codegen.model.tree.CgExecutableCall -import org.utbot.framework.codegen.model.tree.CgExpression -import org.utbot.framework.codegen.model.tree.CgFieldAccess -import org.utbot.framework.codegen.model.tree.CgForLoop -import org.utbot.framework.codegen.model.tree.CgGetJavaClass -import org.utbot.framework.codegen.model.tree.CgGetKotlinClass -import org.utbot.framework.codegen.model.tree.CgGetLength -import org.utbot.framework.codegen.model.tree.CgInnerBlock -import org.utbot.framework.codegen.model.tree.CgMethod -import org.utbot.framework.codegen.model.tree.CgNotNullVariable -import org.utbot.framework.codegen.model.tree.CgParameterDeclaration -import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod -import org.utbot.framework.codegen.model.tree.CgSpread -import org.utbot.framework.codegen.model.tree.CgStaticsRegion -import org.utbot.framework.codegen.model.tree.CgSwitchCase -import org.utbot.framework.codegen.model.tree.CgSwitchCaseLabel -import org.utbot.framework.codegen.model.tree.CgTestClass -import org.utbot.framework.codegen.model.tree.CgTestMethod -import org.utbot.framework.codegen.model.tree.CgTypeCast -import org.utbot.framework.codegen.model.tree.CgVariable -import org.utbot.framework.codegen.model.util.CgPrinter -import org.utbot.framework.codegen.model.util.CgPrinterImpl -import org.utbot.framework.codegen.model.util.nullLiteral -import org.utbot.framework.plugin.api.BuiltinClassId -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.TypeParameters -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.WildcardTypeParameter -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.isArray -import org.utbot.framework.plugin.api.util.isPrimitive -import org.utbot.framework.plugin.api.util.isPrimitiveWrapper -import org.utbot.framework.plugin.api.util.kClass -import org.utbot.framework.plugin.api.util.voidClassId - -//TODO rewrite using KtPsiFactory? -internal class CgKotlinRenderer(context: CgContext, printer: CgPrinter = CgPrinterImpl()) : CgAbstractRenderer(context, printer) { - override val statementEnding: String = "" - - override val logicalAnd: String - get() = "and" - - override val logicalOr: String - get() = "or" - - override val language: CodegenLanguage = CodegenLanguage.KOTLIN - - override val langPackage: String = "kotlin" - - override fun visit(element: CgTestClass) { - for (annotation in element.annotations) { - annotation.accept(this) - } - print("class ") - print(element.simpleName) - if (element.superclass != null || element.interfaces.isNotEmpty()) { - print(" :") - } - val supertypes = mutableListOf() - .apply { - // Here we do not consider constructors with arguments, but for now they are not needed. - // Also, we do not yet support type parameters in code generation, so generic - // superclasses or interfaces are not supported. Although, they are not needed for now. - if (element.superclass != null) { - add("${element.superclass.asString()}()") - } - element.interfaces.forEach { - add(it.asString()) - } - }.joinToString() - if (supertypes.isNotEmpty()) { - print(" $supertypes") - } - println(" {") - withIndent { element.body.accept(this) } - println("}") - } - - override fun visit(element: CgStaticsRegion) { - if (element.content.isEmpty()) return - - print(regionStart) - element.header?.let { print(" $it") } - println() - - println("companion object {") - withIndent { - for (item in element.content) { - println() - println("@JvmStatic") - item.accept(this) - } - } - println("}") - - println(regionEnd) - } - - - // Property access - - override fun visit(element: CgFieldAccess) { - element.caller.accept(this) - renderAccess(element.caller) - print(element.fieldId.name) - } - - override fun visit(element: CgArrayElementAccess) { - if (element.array.type.isNullable) { - element.array.accept(this) - print("?.get(") - element.index.accept(this) - print(")") - } else { - super.visit(element) - } - } - - override fun renderAccess(caller: CgExpression) { - if (caller.type.isNullable) print("?") - print(".") - } - - override fun visit(element: CgParameterDeclaration) { - if (element.isVararg) { - print("vararg ") - } - print("${element.name.escapeNamePossibleKeyword()}: ") - print(getKotlinClassString(element.type)) - if (element.isReferenceType) { - print("?") - } - } - - // TODO: probably rewrite to use better syntax if there is one - override fun visit(element: CgArrayAnnotationArgument) { - print("value = [") - element.values.renderSeparated() - print("]") - } - - override fun visit(element: CgSpread) { - print("*") - element.array.accept(this) - } - - override fun visit(element: CgAnonymousFunction) { - print("{ ") - element.parameters.renderSeparated(true) - if (element.parameters.isNotEmpty()) { - print(" -> ") - } else { - println() - } - // cannot use visit(element.body) here because { was already printed - withIndent { - for (statement in element.body) { - statement.accept(this) - } - } - print("}") - } - - override fun visit(element: CgEqualTo) { - element.left.accept(this) - print(" === ") - element.right.accept(this) - } - - override fun visit(element: CgTypeCast) { - element.expression.accept(this) - // perform type cast only if target type is not equal to expression type - // but cast from nullable to not nullable should be performed - // TODO SAT-1445 actually this safetyCast check looks like hack workaround and possibly does not work - // so it should be carefully tested one day - if (!element.isSafetyCast || element.expression.type != element.targetType) { - // except for the case when a wrapper is cast to its primitive or vice versa - - val isCastFromPrimitiveToWrapper = element.targetType.isPrimitiveWrapper && - element.expression.type.isPrimitive - val isCastFromWrapperToPrimitive = element.targetType.isPrimitive && - element.expression.type.isPrimitiveWrapper - val isNullLiteral = element.expression == nullLiteral() - if (!isNullLiteral && element.isSafetyCast && (isCastFromPrimitiveToWrapper || isCastFromWrapperToPrimitive)) { - return - } - - if (element.isSafetyCast) print(" as? ") else print(" as ") - print(getKotlinClassString(element.targetType)) - renderTypeParameters(element.targetType.typeParameters) - val initNullable = element.type.isNullable - if (element.targetType.isNullable || initNullable) print("?") - } - } - - override fun visit(element: CgErrorWrapper) { - element.expression.accept(this) - print(" ?: error(\"${element.message}\")") - } - - override fun visit(element: CgGetJavaClass) { - // TODO: check how it works on ref types, primitives, ref arrays, primitive arrays, etc. - print(getKotlinClassString(element.classId)) - print("::class.java") - } - - override fun visit(element: CgGetKotlinClass) { - // TODO: check how it works on ref types, primitives, ref arrays, primitive arrays, etc. - print(getKotlinClassString(element.classId)) - print("::class") - } - - override fun visit(element: CgNotNullVariable) { - print("${element.name.escapeNamePossibleKeyword()}!!") - } - - override fun visit(element: CgAllocateArray) { - // TODO think about void as primitive - print(getKotlinClassString(element.type)) - print("(${element.size})") - if (!element.elementType.isPrimitive) { - print(" { null }") - } - } - - override fun visit(element: CgAllocateInitializedArray) { - val arrayModel = element.model - val elementsInLine = arrayElementsInLine(arrayModel.constModel) - - if (arrayModel.constModel is UtPrimitiveModel) { - val prefix = arrayModel.constModel.classId.name.toLowerCase() - print("${prefix}ArrayOf(") - arrayModel.renderElements(element.size, elementsInLine) - print(")") - } else { - print(getKotlinClassString(element.type)) - print("(${element.size})") - if (!element.elementType.isPrimitive) print(" { null }") - } - } - - override fun visit(element: CgGetLength) { - element.variable.accept(this) - print(".size") - } - - override fun visit(element: CgConstructorCall) { - print(getKotlinClassString(element.executableId.classId)) - print("(") - element.arguments.renderSeparated() - print(")") - } - - override fun renderRegularImport(regularImport: RegularImport) { - val escapedImport = getEscapedImportRendering(regularImport) - println("import $escapedImport$statementEnding") - } - - override fun renderStaticImport(staticImport: StaticImport) { - val escapedImport = getEscapedImportRendering(staticImport) - println("import $escapedImport$statementEnding") - } - - override fun renderMethodSignature(element: CgTestMethod) { - print("fun ") - // TODO resolve $ in name - print(element.name) - print("(") - val newLines = element.parameters.size > maxParametersAmountInOneLine - element.parameters.renderSeparated(newLines) - print(")") - renderMethodReturnType(element) - } - - override fun renderMethodSignature(element: CgErrorTestMethod) { - // error test methods always have void return type - print("fun ") - print(element.name) - println("()") - } - - override fun renderMethodSignature(element: CgParameterizedTestDataProviderMethod) { - val returnType = - if (element.returnType.simpleName == "Array?>") "Array?>" else "${element.returnType}" - println("fun ${element.name}(): $returnType") - } - - private fun renderMethodReturnType(method: CgMethod) { - if (method.returnType != voidClassId) { - print(": ") - print(getKotlinClassString(method.returnType)) - } - } - - override fun visit(element: CgInnerBlock) { - println("run {") - withIndent { - for (statement in element.statements) { - statement.accept(this) - } - } - println("}") - } - - override fun renderForLoopVarControl(element: CgForLoop) { - print("for (") - with(element.initialization) { - visit(variable) - print(" in ") - initializer?.accept(this@CgKotlinRenderer) - } - print(" until ") - (element.condition as CgComparison).right.accept(this) // TODO it is comparison just now - // TODO is step always increment? - } - - private fun renderKotlinTypeInDeclaration(element: CgDeclaration) { - // TODO consider moving to getKotlinClassString - print(": ") - print(getKotlinClassString(element.variableType)) - renderTypeParameters(element.variableType.typeParameters) - val initNullable = element.initializer?.run { type.isNullable } ?: false - if (element.variableType.isNullable || initNullable) print("?") - } - - override fun renderDeclarationLeftPart(element: CgDeclaration) { - if (element.isMutable) print("var ") else print("val ") - visit(element.variable) - // TODO SAT-1683 enable explicit types - // renderKotlinTypeInDeclaration(element) - } - - override fun visit(element: CgSwitchCaseLabel) { - if (element.label != null) { - element.label.accept(this) - } else { - print("else") - } - print(" -> ") - visit(element.statements, printNextLine = true) - } - - override fun visit(element: CgSwitchCase) { - print("when (") - element.value.accept(this) - println(") {") - withIndent { - for (caseLabel in element.labels) { - caseLabel.accept(this) - } - element.defaultLabel?.accept(this) - } - println("}") - } - - override fun toStringConstantImpl(byte: Byte): String = buildString { - if (byte < 0) { - append("(") - append("$byte") - append(")") - } else { - append("$byte") - } - append(".toByte()") - } - - override fun toStringConstantImpl(short: Short): String = buildString { - if (short < 0) { - append("(") - append("$short") - append(")") - } else { - append("$short") - } - append(".toShort()") - } - - override fun toStringConstantImpl(long: Long): String = "${long}L" - - override fun toStringConstantImpl(float: Float): String = "${float}f" - - override fun toStringConstantImpl(int: Int): String = when (int) { - Int.MAX_VALUE -> "Int.MAX_VALUE" - Int.MIN_VALUE -> "Int.MIN_VALUE" - else -> "$int" - } - - /** - * See [Escaping in Kotlin](https://stackoverflow.com/questions/44170959/kotlin-form-feed-character-illegal-escape-f/) - */ - override fun String.escapeCharacters(): String = - StringEscapeUtils.escapeJava(this) - .replace("$", "\\$") - .replace("\\f", "\\u000C") - .replace("\\xxx", "\\\u0058\u0058\u0058") - - override fun renderExecutableCallArguments(executableCall: CgExecutableCall) { - print("(") - val lastArgument = executableCall.arguments.lastOrNull() - if (lastArgument != null && lastArgument is CgAnonymousFunction) { - executableCall.arguments.dropLast(1).renderSeparated() - print(") ") - executableCall.arguments.last().accept(this) - } else { - executableCall.arguments.renderSeparated() - print(")") - } - } - - override fun renderExceptionCatchVariable(exception: CgVariable) { - print("${exception.name.escapeNamePossibleKeyword()}: ${exception.type.kClass.simpleName}") - } - - override fun isAccessibleBySimpleNameImpl(classId: ClassId): Boolean { - return super.isAccessibleBySimpleNameImpl(classId) || classId.packageName == "kotlin" - } - - override fun escapeNamePossibleKeywordImpl(s: String): String = - if (isLanguageKeyword(s, context.codegenLanguage)) "`$s`" else s - - private fun getKotlinClassString(id: ClassId): String = - if (id.isArray) { - getKotlinArrayClassOfString(id) - } else { - when (id.jvmName) { - "Ljava/lang/Object;" -> Any::class.simpleName!! - "B", "Ljava/lang/Byte;" -> Byte::class.simpleName!! - "S", "Ljava/lang/Short;" -> Short::class.simpleName!! - "C", "Ljava/lang/Character;" -> Char::class.simpleName!! - "I", "Ljava/lang/Integer;" -> Int::class.simpleName!! - "J", "Ljava/lang/Long;" -> Long::class.simpleName!! - "F", "Ljava/lang/Float;" -> Float::class.simpleName!! - "D", "Ljava/lang/Double;" -> Double::class.simpleName!! - "Z", "Ljava/lang/Boolean;" -> Boolean::class.simpleName!! - "Ljava/lang/CharSequence;" -> CharSequence::class.simpleName!! - "Ljava/lang/String;" -> String::class.simpleName!! - else -> { - // we cannot access kClass for BuiltinClassId - // we cannot use simple name here because this class can be not imported - if (id is BuiltinClassId) id.name else id.kClass.id.asString() - } - } - } - - private fun getKotlinArrayClassOfString(classId: ClassId): String = - if (!classId.elementClassId!!.isPrimitive) { - if (classId.elementClassId != java.lang.Object::class.id) { - workaround(WorkaroundReason.ARRAY_ELEMENT_TYPES_ALWAYS_NULLABLE) { - // for now all element types are nullable - // Use kotlin.Array, because Array becomes java.lang.reflect.Array - // when passed as a type parameter - "kotlin.Array<${getKotlinClassString(classId.elementClassId!!)}?>" - } - } else { - "kotlin.Array" - } - } else { - when (classId.jvmName) { - "[B" -> ByteArray::class.simpleName!! - "[S" -> ShortArray::class.simpleName!! - "[C" -> CharArray::class.simpleName!! - "[I" -> IntArray::class.simpleName!! - "[J" -> LongArray::class.simpleName!! - "[F" -> FloatArray::class.simpleName!! - "[D" -> DoubleArray::class.simpleName!! - "[Z" -> BooleanArray::class.simpleName!! - else -> classId.kClass.id.asString() - } - } - - override fun renderTypeParameters(typeParameters: TypeParameters) { - if (typeParameters.parameters.isNotEmpty()) { - print("<") - if (typeParameters is WildcardTypeParameter) { - print("*") - } else { - print(typeParameters.parameters.joinToString { getKotlinClassString(it)}) - } - print(">") - } - } -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt deleted file mode 100644 index 23cf060706..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/CgVisitor.kt +++ /dev/null @@ -1,244 +0,0 @@ -package org.utbot.framework.codegen.model.visitor - -import org.utbot.framework.codegen.model.tree.CgAbstractFieldAccess -import org.utbot.framework.codegen.model.tree.CgAbstractMultilineComment -import org.utbot.framework.codegen.model.tree.CgAllocateArray -import org.utbot.framework.codegen.model.tree.CgAllocateInitializedArray -import org.utbot.framework.codegen.model.tree.CgAnonymousFunction -import org.utbot.framework.codegen.model.tree.CgArrayAnnotationArgument -import org.utbot.framework.codegen.model.tree.CgArrayElementAccess -import org.utbot.framework.codegen.model.tree.CgAssignment -import org.utbot.framework.codegen.model.tree.CgBreakStatement -import org.utbot.framework.codegen.model.tree.CgComment -import org.utbot.framework.codegen.model.tree.CgCommentedAnnotation -import org.utbot.framework.codegen.model.tree.CgComparison -import org.utbot.framework.codegen.model.tree.CgConstructorCall -import org.utbot.framework.codegen.model.tree.CgContinueStatement -import org.utbot.framework.codegen.model.tree.CgDeclaration -import org.utbot.framework.codegen.model.tree.CgDecrement -import org.utbot.framework.codegen.model.tree.CgDoWhileLoop -import org.utbot.framework.codegen.model.tree.CgDocClassLinkStmt -import org.utbot.framework.codegen.model.tree.CgDocCodeStmt -import org.utbot.framework.codegen.model.tree.CgDocMethodLinkStmt -import org.utbot.framework.codegen.model.tree.CgDocPreTagStatement -import org.utbot.framework.codegen.model.tree.CgDocRegularStmt -import org.utbot.framework.codegen.model.tree.CgDocumentationComment -import org.utbot.framework.codegen.model.tree.CgElement -import org.utbot.framework.codegen.model.tree.CgEmptyLine -import org.utbot.framework.codegen.model.tree.CgEnumConstantAccess -import org.utbot.framework.codegen.model.tree.CgEqualTo -import org.utbot.framework.codegen.model.tree.CgErrorTestMethod -import org.utbot.framework.codegen.model.tree.CgErrorWrapper -import org.utbot.framework.codegen.model.tree.CgExecutableCall -import org.utbot.framework.codegen.model.tree.CgExecutableUnderTestCluster -import org.utbot.framework.codegen.model.tree.CgExpression -import org.utbot.framework.codegen.model.tree.CgFieldAccess -import org.utbot.framework.codegen.model.tree.CgForLoop -import org.utbot.framework.codegen.model.tree.CgGetJavaClass -import org.utbot.framework.codegen.model.tree.CgGetKotlinClass -import org.utbot.framework.codegen.model.tree.CgGetLength -import org.utbot.framework.codegen.model.tree.CgGreaterThan -import org.utbot.framework.codegen.model.tree.CgIfStatement -import org.utbot.framework.codegen.model.tree.CgIncrement -import org.utbot.framework.codegen.model.tree.CgInnerBlock -import org.utbot.framework.codegen.model.tree.CgLessThan -import org.utbot.framework.codegen.model.tree.CgLiteral -import org.utbot.framework.codegen.model.tree.CgLogicalAnd -import org.utbot.framework.codegen.model.tree.CgLogicalOr -import org.utbot.framework.codegen.model.tree.CgLoop -import org.utbot.framework.codegen.model.tree.CgMethod -import org.utbot.framework.codegen.model.tree.CgMethodCall -import org.utbot.framework.codegen.model.tree.CgMultilineComment -import org.utbot.framework.codegen.model.tree.CgMultipleArgsAnnotation -import org.utbot.framework.codegen.model.tree.CgNamedAnnotationArgument -import org.utbot.framework.codegen.model.tree.CgNonStaticRunnable -import org.utbot.framework.codegen.model.tree.CgNotNullVariable -import org.utbot.framework.codegen.model.tree.CgParameterDeclaration -import org.utbot.framework.codegen.model.tree.CgParameterizedTestDataProviderMethod -import org.utbot.framework.codegen.model.tree.CgReturnStatement -import org.utbot.framework.codegen.model.tree.CgSimpleRegion -import org.utbot.framework.codegen.model.tree.CgSingleArgAnnotation -import org.utbot.framework.codegen.model.tree.CgSingleLineComment -import org.utbot.framework.codegen.model.tree.CgSpread -import org.utbot.framework.codegen.model.tree.CgStatement -import org.utbot.framework.codegen.model.tree.CgStatementExecutableCall -import org.utbot.framework.codegen.model.tree.CgStaticFieldAccess -import org.utbot.framework.codegen.model.tree.CgStaticRunnable -import org.utbot.framework.codegen.model.tree.CgStaticsRegion -import org.utbot.framework.codegen.model.tree.CgSwitchCase -import org.utbot.framework.codegen.model.tree.CgSwitchCaseLabel -import org.utbot.framework.codegen.model.tree.CgTestClass -import org.utbot.framework.codegen.model.tree.CgTestClassBody -import org.utbot.framework.codegen.model.tree.CgTestClassFile -import org.utbot.framework.codegen.model.tree.CgTestMethod -import org.utbot.framework.codegen.model.tree.CgTestMethodCluster -import org.utbot.framework.codegen.model.tree.CgThisInstance -import org.utbot.framework.codegen.model.tree.CgThrowStatement -import org.utbot.framework.codegen.model.tree.CgTripleSlashMultilineComment -import org.utbot.framework.codegen.model.tree.CgTryCatch -import org.utbot.framework.codegen.model.tree.CgTypeCast -import org.utbot.framework.codegen.model.tree.CgUtilMethod -import org.utbot.framework.codegen.model.tree.CgVariable -import org.utbot.framework.codegen.model.tree.CgWhileLoop - -interface CgVisitor { - fun visit(element: CgElement): R - - fun visit(element: CgTestClassFile): R - - fun visit(element: CgTestClass): R - - fun visit(element: CgTestClassBody): R - - fun visit(element: CgStaticsRegion): R - fun visit(element: CgSimpleRegion<*>): R - fun visit(element: CgTestMethodCluster): R - fun visit(element: CgExecutableUnderTestCluster): R - - fun visit(element: CgUtilMethod): R - - // Methods - fun visit(element: CgMethod): R - fun visit(element: CgTestMethod): R - fun visit(element: CgErrorTestMethod): R - fun visit(element: CgParameterizedTestDataProviderMethod): R - - // Annotations - fun visit(element: CgCommentedAnnotation): R - fun visit(element: CgSingleArgAnnotation): R - fun visit(element: CgMultipleArgsAnnotation): R - fun visit(element: CgNamedAnnotationArgument): R - fun visit(element: CgArrayAnnotationArgument): R - - // Comments - fun visit(element: CgComment): R - fun visit(element: CgSingleLineComment): R - fun visit(element: CgAbstractMultilineComment): R - fun visit(element: CgTripleSlashMultilineComment): R - fun visit(element: CgMultilineComment): R - fun visit(element: CgDocumentationComment): R - - // Comment statements - fun visit(element: CgDocPreTagStatement): R - fun visit(element: CgDocCodeStmt): R - fun visit(element: CgDocRegularStmt): R - fun visit(element: CgDocClassLinkStmt): R - fun visit(element: CgDocMethodLinkStmt): R - - // Any block - // IMPORTANT: there is no line separator in the end by default - // because some blocks have it (like loops) but some do not (like try .. catch, if .. else etc) - fun visit(block: List, printNextLine: Boolean = false): R - - // Anonymous function (lambda) - fun visit(element: CgAnonymousFunction): R - - // Return statement - fun visit(element: CgReturnStatement): R - - // Array element access - fun visit(element: CgArrayElementAccess): R - - // Loop conditions - fun visit(element: CgComparison): R - fun visit(element: CgLessThan): R - fun visit(element: CgGreaterThan): R - fun visit(element: CgEqualTo): R - - // Increment and decrement - fun visit(element: CgIncrement): R - fun visit(element: CgDecrement): R - - fun visit(element: CgErrorWrapper): R - - // Try-catch - fun visit(element: CgTryCatch): R - - //Simple block - fun visit(element: CgInnerBlock): R - - // Loops - fun visit(element: CgLoop): R - fun visit(element: CgForLoop): R - fun visit(element: CgWhileLoop): R - fun visit(element: CgDoWhileLoop): R - - // Control statements - fun visit(element: CgBreakStatement): R - fun visit(element: CgContinueStatement): R - - // Variable declaration - fun visit(element: CgDeclaration): R - - // Variable assignment - fun visit(element: CgAssignment): R - - // Expressions - fun visit(element: CgExpression): R - - // Type cast - fun visit(element: CgTypeCast): R - - // This instance - fun visit(element: CgThisInstance): R - - // Variables - fun visit(element: CgVariable): R - fun visit(element: CgNotNullVariable): R - - // Method parameters - fun visit(element: CgParameterDeclaration): R - - // Primitive and String literals - fun visit(element: CgLiteral): R - - // Non-static runnable like this::toString or (new Object())::toString etc - fun visit(element: CgNonStaticRunnable): R - // Static runnable like Random::nextRandomInt etc - fun visit(element: CgStaticRunnable): R - - // Array allocation - fun visit(element: CgAllocateArray): R - fun visit(element: CgAllocateInitializedArray): R - - // Spread operator - fun visit(element: CgSpread): R - - // Enum constant - fun visit(element: CgEnumConstantAccess): R - - // Property access - fun visit(element: CgAbstractFieldAccess): R - fun visit(element: CgFieldAccess): R - fun visit(element: CgStaticFieldAccess): R - - // Conditional statement - - fun visit(element: CgIfStatement): R - fun visit(element: CgSwitchCaseLabel): R - fun visit(element: CgSwitchCase): R - - // Binary logical operators - - fun visit(element: CgLogicalAnd): R - fun visit(element: CgLogicalOr): R - - // Acquisition of array length, e.g. args.length - fun visit(element: CgGetLength): R - - // Acquisition of java or kotlin class, e.g. MyClass.class in Java, MyClass::class.java in Kotlin or MyClass::class for Kotlin classes - fun visit(element: CgGetJavaClass): R - fun visit(element: CgGetKotlinClass): R - - // Executable calls - fun visit(element: CgStatementExecutableCall): R - fun visit(element: CgExecutableCall): R - fun visit(element: CgConstructorCall): R - fun visit(element: CgMethodCall): R - - // Throw statement - fun visit(element: CgThrowStatement): R - - // Empty line - fun visit(element: CgEmptyLine): R -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt deleted file mode 100644 index c45e10e18b..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/visitor/UtilMethods.kt +++ /dev/null @@ -1,880 +0,0 @@ -package org.utbot.framework.codegen.model.visitor - -import org.utbot.framework.codegen.StaticImport -import org.utbot.framework.codegen.model.constructor.builtin.arraysDeepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.createArrayMethodId -import org.utbot.framework.codegen.model.constructor.builtin.createInstanceMethodId -import org.utbot.framework.codegen.model.constructor.builtin.deepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getArrayLengthMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getEnumConstantByNameMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getFieldValueMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getStaticFieldValueMethodId -import org.utbot.framework.codegen.model.constructor.builtin.getUnsafeInstanceMethodId -import org.utbot.framework.codegen.model.constructor.builtin.hasCustomEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.iterablesDeepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.mapsDeepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.builtin.setFieldMethodId -import org.utbot.framework.codegen.model.constructor.builtin.setStaticFieldMethodId -import org.utbot.framework.codegen.model.constructor.builtin.streamsDeepEqualsMethodId -import org.utbot.framework.codegen.model.constructor.context.CgContext -import org.utbot.framework.codegen.model.constructor.context.CgContextOwner -import org.utbot.framework.codegen.model.constructor.util.importIfNeeded -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.util.id -import java.lang.reflect.Field -import java.lang.reflect.Modifier -import java.util.Arrays -import java.util.Objects - -internal fun ClassId.utilMethodById(id: MethodId, context: CgContext): String = - with(context) { - when (id) { - getUnsafeInstanceMethodId -> getUnsafeInstance(codegenLanguage) - createInstanceMethodId -> createInstance(codegenLanguage) - createArrayMethodId -> createArray(codegenLanguage) - setFieldMethodId -> setField(codegenLanguage) - setStaticFieldMethodId -> setStaticField(codegenLanguage) - getFieldValueMethodId -> getFieldValue(codegenLanguage) - getStaticFieldValueMethodId -> getStaticFieldValue(codegenLanguage) - getEnumConstantByNameMethodId -> getEnumConstantByName(codegenLanguage) - deepEqualsMethodId -> deepEquals(codegenLanguage, mockFrameworkUsed, mockFramework) - arraysDeepEqualsMethodId -> arraysDeepEquals(codegenLanguage) - iterablesDeepEqualsMethodId -> iterablesDeepEquals(codegenLanguage) - streamsDeepEqualsMethodId -> streamsDeepEquals(codegenLanguage) - mapsDeepEqualsMethodId -> mapsDeepEquals(codegenLanguage) - hasCustomEqualsMethodId -> hasCustomEquals(codegenLanguage) - getArrayLengthMethodId -> getArrayLength(codegenLanguage) - else -> error("Unknown util method for class $this: $id") - } - } - -fun getEnumConstantByName(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private static Object getEnumConstantByName(Class enumClass, String name) throws IllegalAccessException { - java.lang.reflect.Field[] fields = enumClass.getDeclaredFields(); - for (java.lang.reflect.Field field : fields) { - String fieldName = field.getName(); - if (field.isEnumConstant() && fieldName.equals(name)) { - field.setAccessible(true); - - return field.get(null); - } - } - - return null; - } - """ - } - CodegenLanguage.KOTLIN -> { - """ - private fun getEnumConstantByName(enumClass: Class<*>, name: String): kotlin.Any? { - val fields: kotlin.Array = enumClass.declaredFields - for (field in fields) { - val fieldName = field.name - if (field.isEnumConstant && fieldName == name) { - field.isAccessible = true - - return field.get(null) - } - } - - return null - } - """ - } - }.trimIndent() - -fun getStaticFieldValue(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private static Object getStaticFieldValue(Class clazz, String fieldName) throws IllegalAccessException, NoSuchFieldException { - java.lang.reflect.Field field; - Class originClass = clazz; - do { - try { - field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); - - return field.get(null); - } catch (NoSuchFieldException e) { - clazz = clazz.getSuperclass(); - } - } while (clazz != null); - - throw new NoSuchFieldException("Field '" + fieldName + "' not found on class " + originClass); - } - """ - } - CodegenLanguage.KOTLIN -> { - """ - private fun getStaticFieldValue(clazz: Class<*>, fieldName: String): kotlin.Any? { - var currentClass: Class<*>? = clazz - var field: java.lang.reflect.Field - do { - try { - field = currentClass!!.getDeclaredField(fieldName) - field.isAccessible = true - val modifiersField: java.lang.reflect.Field = java.lang.reflect.Field::class.java.getDeclaredField("modifiers") - modifiersField.isAccessible = true - modifiersField.setInt(field, field.modifiers and java.lang.reflect.Modifier.FINAL.inv()) - - return field.get(null) - } catch (e: NoSuchFieldException) { - currentClass = currentClass!!.superclass - } - } while (currentClass != null) - - throw NoSuchFieldException("Field '" + fieldName + "' not found on class " + clazz) - } - """ - } - }.trimIndent() - -fun getFieldValue(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private static Object getFieldValue(Object obj, String fieldName) throws IllegalAccessException, NoSuchFieldException { - Class clazz = obj.getClass(); - java.lang.reflect.Field field; - do { - try { - field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); - - return field.get(obj); - } catch (NoSuchFieldException e) { - clazz = clazz.getSuperclass(); - } - } while (clazz != null); - - throw new NoSuchFieldException("Field '" + fieldName + "' not found on class " + obj.getClass()); - } - """ - } - CodegenLanguage.KOTLIN -> { - """ - private fun getFieldValue(any: kotlin.Any, fieldName: String): kotlin.Any? { - var clazz: Class<*>? = any.javaClass - var field: java.lang.reflect.Field - do { - try { - field = clazz!!.getDeclaredField(fieldName) - field.isAccessible = true - val modifiersField: java.lang.reflect.Field = java.lang.reflect.Field::class.java.getDeclaredField("modifiers") - modifiersField.isAccessible = true - modifiersField.setInt(field, field.modifiers and java.lang.reflect.Modifier.FINAL.inv()) - - return field.get(any) - } catch (e: NoSuchFieldException) { - clazz = clazz!!.superclass - } - } while (clazz != null) - - throw NoSuchFieldException("Field '" + fieldName + "' not found on class " + any.javaClass) - } - """ - } - }.trimIndent() - -fun setStaticField(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private static void setStaticField(Class clazz, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { - java.lang.reflect.Field field; - - do { - try { - field = clazz.getDeclaredField(fieldName); - } catch (Exception e) { - clazz = clazz.getSuperclass(); - field = null; - } - } while (field == null); - - java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); - - field.setAccessible(true); - field.set(null, fieldValue); - } - """ - } - CodegenLanguage.KOTLIN -> { - """ - private fun setStaticField(defaultClass: Class<*>, fieldName: String, fieldValue: kotlin.Any?) { - var field: java.lang.reflect.Field? - var clazz = defaultClass - - do { - try { - field = clazz.getDeclaredField(fieldName) - } catch (e: Exception) { - clazz = clazz.superclass - field = null - } - } while (field == null) - - val modifiersField: java.lang.reflect.Field = java.lang.reflect.Field::class.java.getDeclaredField("modifiers") - modifiersField.isAccessible = true - modifiersField.setInt(field, field.modifiers and java.lang.reflect.Modifier.FINAL.inv()) - - field.isAccessible = true - field.set(null, fieldValue) - } - """ - } - }.trimIndent() - -fun setField(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private static void setField(Object object, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { - Class clazz = object.getClass(); - java.lang.reflect.Field field; - - do { - try { - field = clazz.getDeclaredField(fieldName); - } catch (Exception e) { - clazz = clazz.getSuperclass(); - field = null; - } - } while (field == null); - - java.lang.reflect.Field modifiersField = java.lang.reflect.Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); - - field.setAccessible(true); - field.set(object, fieldValue); - } - """ - } - CodegenLanguage.KOTLIN -> { - """ - private fun setField(any: kotlin.Any, fieldName: String, fieldValue: kotlin.Any?) { - var clazz: Class<*> = any.javaClass - var field: java.lang.reflect.Field? - do { - try { - field = clazz.getDeclaredField(fieldName) - } catch (e: Exception) { - clazz = clazz.superclass - field = null - } - } while (field == null) - - val modifiersField: java.lang.reflect.Field = java.lang.reflect.Field::class.java.getDeclaredField("modifiers") - modifiersField.isAccessible = true - modifiersField.setInt(field, field.modifiers and java.lang.reflect.Modifier.FINAL.inv()) - - field.isAccessible = true - field.set(any, fieldValue) - } - """ - } - }.trimIndent() - -fun createArray(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private static Object[] createArray(String className, int length, Object... values) throws ClassNotFoundException { - Object array = java.lang.reflect.Array.newInstance(Class.forName(className), length); - - for (int i = 0; i < values.length; i++) { - java.lang.reflect.Array.set(array, i, values[i]); - } - - return (Object[]) array; - } - """ - } - CodegenLanguage.KOTLIN -> { - """ - private fun createArray( - className: String, - length: Int, - vararg values: kotlin.Any - ): kotlin.Array { - val array: kotlin.Any = java.lang.reflect.Array.newInstance(Class.forName(className), length) - - for (i in values.indices) { - java.lang.reflect.Array.set(array, i, values[i]) - } - - return array as kotlin.Array - } - """ - } - }.trimIndent() - -fun createInstance(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private static Object createInstance(String className) - throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InvocationTargetException { - Class clazz = Class.forName(className); - return Class.forName("sun.misc.Unsafe").getDeclaredMethod("allocateInstance", Class.class) - .invoke(getUnsafeInstance(), clazz); - } - """ - } - CodegenLanguage.KOTLIN -> { - """ - private fun createInstance(className: String): kotlin.Any? { - val clazz: Class<*> = Class.forName(className) - return Class.forName("sun.misc.Unsafe").getDeclaredMethod("allocateInstance", Class::class.java) - .invoke(getUnsafeInstance(), clazz) - } - """ - } - }.trimIndent() - -fun getUnsafeInstance(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private static Object getUnsafeInstance() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { - java.lang.reflect.Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe"); - f.setAccessible(true); - return f.get(null); - } - """ - } - CodegenLanguage.KOTLIN -> { - """ - private fun getUnsafeInstance(): kotlin.Any? { - val f: java.lang.reflect.Field = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe") - f.isAccessible = true - return f[null] - } - """ - } - }.trimIndent() - -/** - * Mockito mock uses its own equals which we cannot rely on - */ -private fun isMockCondition(mockFrameworkUsed: Boolean, mockFramework: MockFramework): String { - if (!mockFrameworkUsed) return "" - - // TODO for now we have only Mockito but in can be changed in the future - if (mockFramework != MockFramework.MOCKITO) return "" - - return " && !org.mockito.Mockito.mockingDetails(o1).isMock()" -} - -fun deepEquals(language: CodegenLanguage, mockFrameworkUsed: Boolean, mockFramework: MockFramework): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - static class FieldsPair { - final Object o1; - final Object o2; - - public FieldsPair(Object o1, Object o2) { - this.o1 = o1; - this.o2 = o2; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - FieldsPair that = (FieldsPair) o; - return Objects.equals(o1, that.o1) && Objects.equals(o2, that.o2); - } - - @Override - public int hashCode() { - return Objects.hash(o1, o2); - } - } - - private boolean deepEquals(Object o1, Object o2) { - return deepEquals(o1, o2, new java.util.HashSet<>()); - } - - private boolean deepEquals(Object o1, Object o2, java.util.Set visited) { - visited.add(new FieldsPair(o1, o2)); - - if (o1 == o2) { - return true; - } - - if (o1 == null || o2 == null) { - return false; - } - - if (o1 instanceof Iterable) { - if (!(o2 instanceof Iterable)) { - return false; - } - - return iterablesDeepEquals((Iterable) o1, (Iterable) o2, visited); - } - - if (o2 instanceof Iterable) { - return false; - } - - if (o1 instanceof java.util.stream.Stream) { - if (!(o2 instanceof java.util.stream.Stream)) { - return false; - } - - return streamsDeepEquals((java.util.stream.Stream) o1, (java.util.stream.Stream) o2, visited); - } - - if (o2 instanceof java.util.stream.Stream) { - return false; - } - - if (o1 instanceof java.util.Map) { - if (!(o2 instanceof java.util.Map)) { - return false; - } - - return mapsDeepEquals((java.util.Map) o1, (java.util.Map) o2, visited); - } - - if (o2 instanceof java.util.Map) { - return false; - } - - Class firstClass = o1.getClass(); - if (firstClass.isArray()) { - if (!o2.getClass().isArray()) { - return false; - } - - // Primitive arrays should not appear here - return arraysDeepEquals(o1, o2, visited); - } - - // common classes - - // check if class has custom equals method (including wrappers and strings) - // It is very important to check it here but not earlier because iterables and maps also have custom equals - // based on elements equals - if (hasCustomEquals(firstClass)${isMockCondition(mockFrameworkUsed, mockFramework)}) { - return o1.equals(o2); - } - - // common classes without custom equals, use comparison by fields - final java.util.List fields = new java.util.ArrayList<>(); - while (firstClass != Object.class) { - fields.addAll(java.util.Arrays.asList(firstClass.getDeclaredFields())); - // Interface should not appear here - firstClass = firstClass.getSuperclass(); - } - - for (java.lang.reflect.Field field : fields) { - field.setAccessible(true); - try { - final Object field1 = field.get(o1); - final Object field2 = field.get(o2); - if (!visited.contains(new FieldsPair(field1, field2)) && !deepEquals(field1, field2, visited)) { - return false; - } - } catch (IllegalArgumentException e) { - return false; - } catch (IllegalAccessException e) { - // should never occur because field was set accessible - return false; - } - } - - return true; - } - """.trimIndent() - } - CodegenLanguage.KOTLIN -> { - """ - private fun deepEquals(o1: kotlin.Any?, o2: kotlin.Any?): Boolean = deepEquals(o1, o2, hashSetOf()) - - private fun deepEquals( - o1: kotlin.Any?, - o2: kotlin.Any?, - visited: kotlin.collections.MutableSet> - ): Boolean { - visited += o1 to o2 - - if (o1 === o2) return true - - if (o1 == null || o2 == null) return false - - if (o1 is kotlin.collections.Iterable<*>) { - return if (o2 !is kotlin.collections.Iterable<*>) false else iterablesDeepEquals(o1, o2, visited) - } - - if (o2 is kotlin.collections.Iterable<*>) return false - - if (o1 is java.util.stream.Stream<*>) { - return if (o2 !is java.util.stream.Stream<*>) false else streamsDeepEquals(o1, o2, visited) - } - - if (o2 is java.util.stream.Stream<*>) return false - - if (o1 is kotlin.collections.Map<*, *>) { - return if (o2 !is kotlin.collections.Map<*, *>) false else mapsDeepEquals(o1, o2, visited) - } - - if (o2 is kotlin.collections.Map<*, *>) return false - - var firstClass: Class<*> = o1.javaClass - if (firstClass.isArray) { - return if (!o2.javaClass.isArray) { - false - } else { - arraysDeepEquals(o1, o2, visited) - } - } - - // check if class has custom equals method (including wrappers and strings) - // It is very important to check it here but not earlier because iterables and maps also have custom equals - // based on elements equals - if (hasCustomEquals(firstClass)${isMockCondition(mockFrameworkUsed, mockFramework)}) { - return o1 == o2 - } - - // common classes without custom equals, use comparison by fields - val fields: kotlin.collections.MutableList = mutableListOf() - while (firstClass != kotlin.Any::class.java) { - fields += listOf(*firstClass.declaredFields) - // Interface should not appear here - firstClass = firstClass.superclass - } - - for (field in fields) { - field.isAccessible = true - try { - val field1 = field[o1] - val field2 = field[o2] - if ((field1 to field2) !in visited && !deepEquals(field1, field2, visited)) return false - } catch (e: IllegalArgumentException) { - return false - } catch (e: IllegalAccessException) { - // should never occur - return false - } - } - - return true - } - """.trimIndent() - } - } - -fun arraysDeepEquals(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private boolean arraysDeepEquals(Object arr1, Object arr2, java.util.Set visited) { - final int length = java.lang.reflect.Array.getLength(arr1); - if (length != java.lang.reflect.Array.getLength(arr2)) { - return false; - } - - for (int i = 0; i < length; i++) { - if (!deepEquals(java.lang.reflect.Array.get(arr1, i), java.lang.reflect.Array.get(arr2, i), visited)) { - return false; - } - } - - return true; - } - """.trimIndent() - } - CodegenLanguage.KOTLIN -> { - """ - private fun arraysDeepEquals( - arr1: kotlin.Any?, - arr2: kotlin.Any?, - visited: kotlin.collections.MutableSet> - ): Boolean { - val size = java.lang.reflect.Array.getLength(arr1) - if (size != java.lang.reflect.Array.getLength(arr2)) return false - - for (i in 0 until size) { - if (!deepEquals(java.lang.reflect.Array.get(arr1, i), java.lang.reflect.Array.get(arr2, i), visited)) { - return false - } - } - - return true - } - """.trimIndent() - } - } - -fun iterablesDeepEquals(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private boolean iterablesDeepEquals(Iterable i1, Iterable i2, java.util.Set visited) { - final java.util.Iterator firstIterator = i1.iterator(); - final java.util.Iterator secondIterator = i2.iterator(); - while (firstIterator.hasNext() && secondIterator.hasNext()) { - if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) { - return false; - } - } - - if (firstIterator.hasNext()) { - return false; - } - - return !secondIterator.hasNext(); - } - """.trimIndent() - } - CodegenLanguage.KOTLIN -> { - """ - private fun iterablesDeepEquals( - i1: Iterable<*>, - i2: Iterable<*>, - visited: kotlin.collections.MutableSet> - ): Boolean { - val firstIterator = i1.iterator() - val secondIterator = i2.iterator() - while (firstIterator.hasNext() && secondIterator.hasNext()) { - if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) return false - } - - return if (firstIterator.hasNext()) false else !secondIterator.hasNext() - } - """.trimIndent() - } - } - -fun streamsDeepEquals(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private boolean streamsDeepEquals( - java.util.stream.Stream s1, - java.util.stream.Stream s2, - java.util.Set visited - ) { - final java.util.Iterator firstIterator = s1.iterator(); - final java.util.Iterator secondIterator = s2.iterator(); - while (firstIterator.hasNext() && secondIterator.hasNext()) { - if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) { - return false; - } - } - - if (firstIterator.hasNext()) { - return false; - } - - return !secondIterator.hasNext(); - } - """.trimIndent() - } - CodegenLanguage.KOTLIN -> { - """ - private fun streamsDeepEquals( - s1: java.util.stream.Stream<*>, - s2: java.util.stream.Stream<*>, - visited: kotlin.collections.MutableSet> - ): Boolean { - val firstIterator = s1.iterator() - val secondIterator = s2.iterator() - while (firstIterator.hasNext() && secondIterator.hasNext()) { - if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) return false - } - - return if (firstIterator.hasNext()) false else !secondIterator.hasNext() - } - """.trimIndent() - } - } - -fun mapsDeepEquals(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private boolean mapsDeepEquals( - java.util.Map m1, - java.util.Map m2, - java.util.Set visited - ) { - final java.util.Iterator> firstIterator = m1.entrySet().iterator(); - final java.util.Iterator> secondIterator = m2.entrySet().iterator(); - while (firstIterator.hasNext() && secondIterator.hasNext()) { - final java.util.Map.Entry firstEntry = firstIterator.next(); - final java.util.Map.Entry secondEntry = secondIterator.next(); - - if (!deepEquals(firstEntry.getKey(), secondEntry.getKey(), visited)) { - return false; - } - - if (!deepEquals(firstEntry.getValue(), secondEntry.getValue(), visited)) { - return false; - } - } - - if (firstIterator.hasNext()) { - return false; - } - - return !secondIterator.hasNext(); - } - """.trimIndent() - } - CodegenLanguage.KOTLIN -> { - """ - private fun mapsDeepEquals( - m1: kotlin.collections.Map<*, *>, - m2: kotlin.collections.Map<*, *>, - visited: kotlin.collections.MutableSet> - ): Boolean { - val firstIterator = m1.entries.iterator() - val secondIterator = m2.entries.iterator() - while (firstIterator.hasNext() && secondIterator.hasNext()) { - val firstEntry = firstIterator.next() - val secondEntry = secondIterator.next() - - if (!deepEquals(firstEntry.key, secondEntry.key, visited)) return false - - if (!deepEquals(firstEntry.value, secondEntry.value, visited)) return false - } - - return if (firstIterator.hasNext()) false else !secondIterator.hasNext() - } - """.trimIndent() - } - } - -fun hasCustomEquals(language: CodegenLanguage): String = - when (language) { - CodegenLanguage.JAVA -> { - """ - private boolean hasCustomEquals(Class clazz) { - while (!Object.class.equals(clazz)) { - try { - clazz.getDeclaredMethod("equals", Object.class); - return true; - } catch (Exception e) { - // Interface should not appear here - clazz = clazz.getSuperclass(); - } - } - - return false; - } - """.trimIndent() - } - CodegenLanguage.KOTLIN -> { - """ - private fun hasCustomEquals(clazz: Class<*>): Boolean { - var c = clazz - while (kotlin.Any::class.java != c) { - try { - c.getDeclaredMethod("equals", kotlin.Any::class.java) - return true - } catch (e: Exception) { - // Interface should not appear here - c = c.superclass - } - } - return false - } - """.trimIndent() - } - } - -fun getArrayLength(codegenLanguage: CodegenLanguage) = - when (codegenLanguage) { - CodegenLanguage.JAVA -> - """ - private static int getArrayLength(Object arr) { - return java.lang.reflect.Array.getLength(arr); - } - """.trimIndent() - CodegenLanguage.KOTLIN -> - """ - private fun getArrayLength(arr: kotlin.Any?): Int = java.lang.reflect.Array.getLength(arr) - """.trimIndent() - } - -internal fun CgContextOwner.importUtilMethodDependencies(id: MethodId) { - for (classId in currentTestClass.regularImportsByUtilMethod(id, codegenLanguage)) { - importIfNeeded(classId) - } - for (methodId in currentTestClass.staticImportsByUtilMethod(id)) { - collectedImports += StaticImport(methodId.classId.canonicalName, methodId.name) - } -} - -private fun ClassId.regularImportsByUtilMethod(id: MethodId, codegenLanguage: CodegenLanguage): List { - val fieldClassId = Field::class.id - return when (id) { - getUnsafeInstanceMethodId -> listOf(fieldClassId) - createInstanceMethodId -> listOf(java.lang.reflect.InvocationTargetException::class.id) - createArrayMethodId -> listOf(java.lang.reflect.Array::class.id) - setFieldMethodId -> listOf(fieldClassId, Modifier::class.id) - setStaticFieldMethodId -> listOf(fieldClassId, Modifier::class.id) - getFieldValueMethodId -> listOf(fieldClassId, Modifier::class.id) - getStaticFieldValueMethodId -> listOf(fieldClassId, Modifier::class.id) - getEnumConstantByNameMethodId -> listOf(fieldClassId) - deepEqualsMethodId -> when (codegenLanguage) { - CodegenLanguage.JAVA -> listOf( - Objects::class.id, - Iterable::class.id, - Map::class.id, - List::class.id, - ArrayList::class.id, - Set::class.id, - HashSet::class.id, - fieldClassId, - Arrays::class.id - ) - CodegenLanguage.KOTLIN -> listOf(fieldClassId, Arrays::class.id) - } - arraysDeepEqualsMethodId -> when (codegenLanguage) { - CodegenLanguage.JAVA -> listOf(java.lang.reflect.Array::class.id, Set::class.id) - CodegenLanguage.KOTLIN -> listOf(java.lang.reflect.Array::class.id) - } - iterablesDeepEqualsMethodId -> when (codegenLanguage) { - CodegenLanguage.JAVA -> listOf(Iterable::class.id, Iterator::class.id, Set::class.id) - CodegenLanguage.KOTLIN -> emptyList() - } - streamsDeepEqualsMethodId -> when (codegenLanguage) { - CodegenLanguage.JAVA -> listOf(java.util.stream.Stream::class.id, Set::class.id) - CodegenLanguage.KOTLIN -> emptyList() - } - mapsDeepEqualsMethodId -> when (codegenLanguage) { - CodegenLanguage.JAVA -> listOf(Map::class.id, Iterator::class.id, Set::class.id) - CodegenLanguage.KOTLIN -> emptyList() - } - hasCustomEqualsMethodId -> emptyList() - getArrayLengthMethodId -> listOf(java.lang.reflect.Array::class.id) - else -> error("Unknown util method for class $this: $id") - } -} - -// Note: for now always returns an empty list, because no util method -// requires static imports, but this may change in the future -@Suppress("unused", "unused_parameter") -private fun ClassId.staticImportsByUtilMethod(id: MethodId): List = emptyList() \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgAbstractRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgAbstractRenderer.kt new file mode 100644 index 0000000000..2d3e357403 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgAbstractRenderer.kt @@ -0,0 +1,996 @@ +package org.utbot.framework.codegen.renderer + +import org.apache.commons.text.StringEscapeUtils +import org.utbot.common.FileUtil +import org.utbot.common.WorkaroundReason.LONG_CODE_FRAGMENTS +import org.utbot.common.workaround +import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.domain.Import +import org.utbot.framework.codegen.domain.RegularImport +import org.utbot.framework.codegen.domain.StaticImport +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgClassFile +import org.utbot.framework.codegen.domain.models.CgAbstractFieldAccess +import org.utbot.framework.codegen.domain.models.CgAbstractMultilineComment +import org.utbot.framework.codegen.domain.models.CgArrayElementAccess +import org.utbot.framework.codegen.domain.models.CgAssignment +import org.utbot.framework.codegen.domain.models.CgAuxiliaryClass +import org.utbot.framework.codegen.domain.models.CgBreakStatement +import org.utbot.framework.codegen.domain.models.CgClass +import org.utbot.framework.codegen.domain.models.CgComment +import org.utbot.framework.codegen.domain.models.CgCommentedAnnotation +import org.utbot.framework.codegen.domain.models.CgComparison +import org.utbot.framework.codegen.domain.models.CgContinueStatement +import org.utbot.framework.codegen.domain.models.CgCustomTagStatement +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgDecrement +import org.utbot.framework.codegen.domain.models.CgDoWhileLoop +import org.utbot.framework.codegen.domain.models.CgDocClassLinkStmt +import org.utbot.framework.codegen.domain.models.CgDocCodeStmt +import org.utbot.framework.codegen.domain.models.CgDocMethodLinkStmt +import org.utbot.framework.codegen.domain.models.CgDocPreTagStatement +import org.utbot.framework.codegen.domain.models.CgDocRegularLineStmt +import org.utbot.framework.codegen.domain.models.CgDocRegularStmt +import org.utbot.framework.codegen.domain.models.CgDocumentationComment +import org.utbot.framework.codegen.domain.models.CgElement +import org.utbot.framework.codegen.domain.models.CgEmptyLine +import org.utbot.framework.codegen.domain.models.CgEnumConstantAccess +import org.utbot.framework.codegen.domain.models.CgErrorTestMethod +import org.utbot.framework.codegen.domain.models.CgErrorWrapper +import org.utbot.framework.codegen.domain.models.CgExecutableCall +import org.utbot.framework.codegen.domain.models.CgMethodsCluster +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgFieldAccess +import org.utbot.framework.codegen.domain.models.CgFieldDeclaration +import org.utbot.framework.codegen.domain.models.CgForEachLoop +import org.utbot.framework.codegen.domain.models.CgForLoop +import org.utbot.framework.codegen.domain.models.CgFrameworkUtilMethod +import org.utbot.framework.codegen.domain.models.CgGreaterThan +import org.utbot.framework.codegen.domain.models.CgIfStatement +import org.utbot.framework.codegen.domain.models.CgIncrement +import org.utbot.framework.codegen.domain.models.CgInnerBlock +import org.utbot.framework.codegen.domain.models.CgIsInstance +import org.utbot.framework.codegen.domain.models.CgLessThan +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgLogicalAnd +import org.utbot.framework.codegen.domain.models.CgLogicalOr +import org.utbot.framework.codegen.domain.models.CgLoop +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgMultilineComment +import org.utbot.framework.codegen.domain.models.CgMultipleArgsAnnotation +import org.utbot.framework.codegen.domain.models.CgNamedAnnotationArgument +import org.utbot.framework.codegen.domain.models.CgNestedClassesRegion +import org.utbot.framework.codegen.domain.models.CgNonStaticRunnable +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration +import org.utbot.framework.codegen.domain.models.CgParameterizedTestDataProviderMethod +import org.utbot.framework.codegen.domain.models.CgRegion +import org.utbot.framework.codegen.domain.models.CgReturnStatement +import org.utbot.framework.codegen.domain.models.CgSimpleRegion +import org.utbot.framework.codegen.domain.models.CgSingleArgAnnotation +import org.utbot.framework.codegen.domain.models.CgSingleLineComment +import org.utbot.framework.codegen.domain.models.CgSpread +import org.utbot.framework.codegen.domain.models.CgStatement +import org.utbot.framework.codegen.domain.models.CgStatementExecutableCall +import org.utbot.framework.codegen.domain.models.CgStaticFieldAccess +import org.utbot.framework.codegen.domain.models.CgStaticRunnable +import org.utbot.framework.codegen.domain.models.CgStaticsRegion +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgTestMethodCluster +import org.utbot.framework.codegen.domain.models.CgThisInstance +import org.utbot.framework.codegen.domain.models.CgThrowStatement +import org.utbot.framework.codegen.domain.models.CgTripleSlashMultilineComment +import org.utbot.framework.codegen.domain.models.CgTryCatch +import org.utbot.framework.codegen.domain.models.CgUtilMethod +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.domain.models.CgWhileLoop +import org.utbot.framework.codegen.tree.VisibilityModifier +import org.utbot.framework.codegen.tree.ututils.UtilClassKind +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.TypeParameters +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.byteClassId +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.isRefType +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.shortClassId + +abstract class CgAbstractRenderer( + val context: CgRendererContext, + val printer: CgPrinter = CgPrinterImpl() +) : CgVisitor, + CgPrinter by printer { + + protected abstract val statementEnding: String + + protected abstract val logicalAnd: String + protected abstract val logicalOr: String + + protected open val regionStart: String = "///region" + protected open val regionEnd: String = "///endregion" + protected var isInterrupted = false + + protected abstract val langPackage: String + + // We may render array elements in initializer in one line or in separate lines: + // items count in one line depends on the element type. + protected fun arrayElementsInLine(elementType: ClassId): Int { + if (elementType.isRefType) return 10 + if (elementType.isArray) return 1 + return when (elementType) { + intClassId, byteClassId, longClassId, charClassId -> 8 + booleanClassId, shortClassId, doubleClassId, floatClassId -> 6 + else -> error("Non primitive value of type $elementType is unexpected in array initializer") + } + } + + /** + * Returns true if one can call methods of this class without specifying a caller (for example if ClassId represents this instance) + */ + protected abstract val ClassId.methodsAreAccessibleAsTopLevel: Boolean + + private val MethodId.accessibleByName: Boolean + get() = (context.shouldOptimizeImports && this in context.importedStaticMethods) || classId.methodsAreAccessibleAsTopLevel + + override fun visit(element: CgElement) { + val error = + "CgRenderer has reached the top of Cg elements hierarchy and did not find a method for ${element::class}" + throw IllegalArgumentException(error) + } + + override fun visit(element: CgClassFile) { + renderClassPackage(element.declaredClass) + renderClassFileImports(element) + element.declaredClass.accept(this) + } + + /** + * Render the region only if it is not empty. + */ + override fun visit(element: CgStaticsRegion) { + element.render() + } + + /** + * Render the region only if it is not empty. + */ + override fun visit(element: CgNestedClassesRegion<*>) { + element.render() + } + + /** + * Render the region only if it is not empty. + */ + override fun visit(element: CgSimpleRegion<*>) { + element.render() + } + + /** + * Render the cluster only if it is not empty. + */ + override fun visit(element: CgTestMethodCluster) { + element.render() + } + + /** + * Render the cluster only if it is not empty. + */ + override fun visit(element: CgMethodsCluster) { + // We print the next line after all contained regions to prevent gluing of region ends + element.render(printLineAfterContentEnd = true) + } + + /** + * Renders the region with a specific rendering for [CgTestMethodCluster.description] + */ + private fun CgRegion<*>.render(printLineAfterContentEnd: Boolean = false) { + if (content.isEmpty() || isInterrupted) return + + header?.let { + print(regionStart) + println(" $it") + } + + if (this is CgTestMethodCluster) description?.accept(this@CgAbstractRenderer) + + var isLimitExceeded = false + for (method in content) { + if (printer.printedLength > UtSettings.maxTestFileSize) { + isLimitExceeded = true + break + } + println() + method.accept(this@CgAbstractRenderer) + } + + if (printLineAfterContentEnd) println() + + header?.let { + println(regionEnd) + } + + if (isLimitExceeded && !isInterrupted) { + visit(CgSingleLineComment("Abrupt generation termination: file size exceeds configured limit (${FileUtil.byteCountToDisplaySize(UtSettings.maxTestFileSize.toLong())})")) + visit(CgSingleLineComment("The limit can be configured in '{HOME_DIR}/.utbot/settings.properties' with 'maxTestsFileSize' property")) + isInterrupted = true + } + } + + override fun visit(element: CgAuxiliaryClass) { + val auxiliaryClassText = element.getText(context) + auxiliaryClassText.split("\n") + .forEach { line -> println(line) } + } + + override fun visit(element: CgUtilMethod) { + val utilMethodText = element.getText(context) + utilMethodText.split("\n") + .forEach { line -> println(line) } + } + + // Methods + + override fun visit(element: CgMethod) { + // TODO introduce CgBlock + print(" ") + visit(element.statements, printNextLine = true) + } + + override fun visit(element: CgFrameworkUtilMethod) { + for (annotation in element.annotations) { + annotation.accept(this) + } + renderMethodSignature(element) + visit(element as CgMethod) + } + + override fun visit(element: CgTestMethod) { + if (UtSettings.addTestMethodMarkers) { + visit(CgSingleLineComment(TEST_METHOD_START_MARKER)) + } + renderMethodDocumentation(element) + for (annotation in element.annotations) { + annotation.accept(this) + } + renderMethodSignature(element) + visit(element as CgMethod) + if (UtSettings.addTestMethodMarkers) { + visit(CgSingleLineComment(TEST_METHOD_END_MARKER)) + } + } + + override fun visit(element: CgErrorTestMethod) { + renderMethodDocumentation(element) + renderMethodSignature(element) + visit(element as CgMethod) + } + + override fun visit(element: CgParameterizedTestDataProviderMethod) { + for (annotation in element.annotations) { + annotation.accept(this) + } + renderMethodSignature(element) + visit(element as CgMethod) + } + + // Annotations + + override fun visit(element: CgCommentedAnnotation) { + print("//") + element.annotation.accept(this) + } + + override fun visit(element: CgSingleArgAnnotation) { + print("@${element.classId.asString()}") + print("(") + element.argument.accept(this) + println(")") + } + + override fun visit(element: CgMultipleArgsAnnotation) { + print("@${element.classId.asString()}") + if (element.arguments.isNotEmpty()) { + print("(") + element.arguments.renderSeparated() + print(")") + } + println() + } + + override fun visit(element: CgNamedAnnotationArgument) { + print(element.name) + print(" = ") + element.value.accept(this) + } + + // Comments + + override fun visit(element: CgComment) { + visit(element as CgElement) + } + + override fun visit(element: CgSingleLineComment) { + println("// ${element.comment}") + } + + override fun visit(element: CgAbstractMultilineComment) { + visit(element as CgElement) + } + + override fun visit(element: CgTripleSlashMultilineComment) { + for (line in element.lines) { + println("/// $line") + } + } + + override fun visit(element: CgMultilineComment) { + val lines = element.lines + if (lines.isEmpty()) return + + if (lines.size == 1) { + println("/* ${lines.first()} */") + return + } + + // print lines saving indentation + print("/* ") + println(lines.first()) + lines.subList(1, lines.lastIndex).forEach { println(it) } + print(lines.last()) + println(" */") + } + + override fun visit(element: CgDocumentationComment) { + if (element.lines.all { it.isEmpty() }) return + + println("/**") + element.lines.forEach { it.accept(this) } + println(" */") + } + override fun visit(element: CgDocPreTagStatement) { + if (element.content.all { it.isEmpty() }) return + println("
    ")
    +        for (stmt in element.content) stmt.accept(this)
    +        println("
    ") + } + + override fun visit(element: CgCustomTagStatement) { + if (element.content.all { it.isEmpty() }) return + + element.content.forEach { it.accept(this) } + } + + override fun visit(element: CgDocCodeStmt) { + if (element.isEmpty()) return + + val text = element.stmt + .replace("\n", "\n * ") + //remove multiline comment symbols to avoid comment in comment effect + .replace("/*", "") + .replace("*/", "") + print("{@code $text }") + } + override fun visit(element: CgDocRegularStmt){ + if (element.isEmpty()) return + + print(element.stmt.replace("\n", "\n * ")) + } + override fun visit(element: CgDocRegularLineStmt){ + if (element.isEmpty()) return + + // It is better to avoid using \n in print, using println is preferred. + // Mixing println's and print's with '\n' BREAKS indention. + // See [https://stackoverflow.com/questions/6685665/system-out-println-vs-n-in-java]. + println(" * " + element.stmt) + } + override fun visit(element: CgDocClassLinkStmt) { + if (element.isEmpty()) return + + print(element.className) + } + override fun visit(element: CgDocMethodLinkStmt){ + if (element.isEmpty()) return + + print("${element.className}::${element.methodName}") //todo make it as link {@link org.utbot.examples.online.Loops#whileLoop(int) } + } + + /** + * Renders any block of code with curly braces. + * + * NOTE: [printNextLine] has default false value in [CgVisitor] + * + * NOTE: due to JVM restrictions for methods size + * [in 65536 bytes](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3) + * we comment long blocks + */ + override fun visit(block: List, printNextLine: Boolean) { + println("{") + + val isBlockTooLarge = workaround(LONG_CODE_FRAGMENTS) { block.size > LARGE_CODE_BLOCK_SIZE } + + if (isBlockTooLarge) { + print("/*") + println(" This block of code is ${block.size} lines long and could lead to compilation error") + } + + withIndent { + for (statement in block) { + statement.accept(this) + } + } + + if (isBlockTooLarge) println("*/") + + print("}") + + if (printNextLine) println() + } + + // Return statement + + override fun visit(element: CgReturnStatement) { + print("return ") + element.expression.accept(this) + println(statementEnding) + } + + // Array element access + + override fun visit(element: CgArrayElementAccess) { + element.array.accept(this) + print("[") + element.index.accept(this) + print("]") + } + + // Spread operator + + override fun visit(element: CgSpread) { + element.array.accept(this) + } + + // Loop conditions + + override fun visit(element: CgComparison) { + return visit(element as CgElement) + } + + override fun visit(element: CgLessThan) { + element.left.accept(this) + print(" < ") + element.right.accept(this) + } + + override fun visit(element: CgGreaterThan) { + element.left.accept(this) + print(" > ") + element.right.accept(this) + } + + // Increment and decrement + + override fun visit(element: CgIncrement) { + print("${element.variable.name}++") + } + + override fun visit(element: CgDecrement) { + print("${element.variable.name}--") + } + + // isInstance check + + override fun visit(element: CgIsInstance) { + element.classExpression.accept(this) + print(".isInstance(") + element.value.accept(this) + print(")") + } + + // Try-catch + + override fun visit(element: CgTryCatch) { + print("try ") + // TODO: SAT-1329 actually Kotlin does not support try-with-resources so we have to use "use" method here + element.resources?.let { + println("(") + withIndent { + for (resource in element.resources) { + resource.accept(this) + } + } + print(") ") + } + // TODO introduce CgBlock + visit(element.statements) + for ((exception, statements) in element.handlers) { + print(" catch (") + renderExceptionCatchVariable(exception) + print(") ") + // TODO introduce CgBlock + visit(statements, printNextLine = element.finally == null) + } + element.finally?.let { + print(" finally ") + // TODO introduce CgBlock + visit(element.finally, printNextLine = true) + } + } + + abstract override fun visit(element: CgErrorWrapper) + + //Simple block + + abstract override fun visit(element: CgInnerBlock) + + // Loops + + override fun visit(element: CgLoop) { + return visit(element as CgElement) + } + + override fun visit(element: CgForLoop) { + renderForLoopVarControl(element) + print(") ") + // TODO introduce CgBlock + visit(element.statements) + println() + } + + override fun visit(element: CgForEachLoop) {} + + override fun visit(element: CgWhileLoop) { + print("while (") + element.condition.accept(this) + print(") ") + // TODO introduce CgBlock + visit(element.statements) + println() + } + + override fun visit(element: CgDoWhileLoop) { + print("do ") + // TODO introduce CgBlock + visit(element.statements) + print(" while (") + element.condition.accept(this) + println(");") + } + + // Control statements + override fun visit(element: CgBreakStatement) { + println("break$statementEnding") + } + + override fun visit(element: CgContinueStatement) { + println("continue$statementEnding") + } + + // Variable declaration + + override fun visit(element: CgDeclaration) { + renderDeclarationLeftPart(element) + element.initializer?.let { + print(" = ") + it.accept(this) + } + println(statementEnding) + } + + // Class field declaration + + override fun visit(element: CgFieldDeclaration) { + element.annotation?.accept(this) + renderVisibility(element.visibility) + element.declaration.accept(this) + } + + // Variable assignment + + override fun visit(element: CgAssignment) { + element.lValue.accept(this) + print(" = ") + element.rValue.accept(this) + println(statementEnding) + } + + // Expressions + + override fun visit(element: CgExpression) { + visit(element as CgElement) + } + + // This instance + + override fun visit(element: CgThisInstance) { + print("this") + } + + // Variables + + override fun visit(element: CgVariable) { + print(element.name.escapeNamePossibleKeyword()) + } + + // Method parameters + + abstract override fun visit(element: CgParameterDeclaration) + + // Primitive and String literals + + override fun visit(element: CgLiteral) { + print(element.toStringConstant()) + } + + protected fun CgLiteral.toStringConstant(asRawString: Boolean = false) = + with(this.value) { + when (this) { + is Byte -> toStringConstant() + is Char -> toStringConstant() + is Short -> toStringConstant() + is Int -> toStringConstant() + is Long -> toStringConstant() + is Float -> toStringConstant() + is Double -> toStringConstant() + is Boolean -> toStringConstant() + // String is "\"" + "str" + "\"", RawString is "str" + is String -> if (asRawString) "$this".escapeCharacters() else toStringConstant() + else -> "$this" + } + } + + // Non-static runnable like this::toString or (new Object())::toString etc + override fun visit(element: CgNonStaticRunnable) { + // TODO we need braces for expressions like (new Object())::toString but not for this::toString + print("(") + element.referenceExpression.accept(this) + print(")::") + print(element.methodId.name) + } + + // Static runnable like Random::nextRandomInt etc + override fun visit(element: CgStaticRunnable) { + print(element.classId.asString()) + print("::") + print(element.methodId.name) + } + + // Enum constant + + override fun visit(element: CgEnumConstantAccess) { + print(element.enumClass.asString()) + print(".") + print(element.name) + } + + // Property access + + override fun visit(element: CgAbstractFieldAccess) { + visit(element as CgElement) + } + + override fun visit(element: CgFieldAccess) { + element.caller.accept(this) + print(".") + print(element.fieldId.name) + } + + override fun visit(element: CgStaticFieldAccess) { + if (!element.declaringClass.methodsAreAccessibleAsTopLevel) { + print(element.declaringClass.asString()) + print(".") + } + print(element.fieldName) + } + + // Conditional statement + + override fun visit(element: CgIfStatement) { + print("if (") + element.condition.accept(this) + print(") ") + // TODO introduce CgBlock + visit(element.trueBranch) + element.falseBranch?.let { + print(" else ") + // TODO introduce CgBlock + visit(element.falseBranch) + } + println() + } + + // Binary logical operators + + override fun visit(element: CgLogicalAnd) { + element.left.accept(this) + print(" $logicalAnd ") + element.right.accept(this) + } + + override fun visit(element: CgLogicalOr) { + element.left.accept(this) + print(" $logicalOr ") + element.right.accept(this) + } + + // Executable calls + + override fun visit(element: CgStatementExecutableCall) { + element.call.accept(this) + println(statementEnding) + } + + override fun visit(element: CgExecutableCall) { + visit(element as CgElement) + } + + // TODO: consider the case of generic functions + // TODO: write tests for all cases of method call rendering (with or without caller, etc.) + override fun visit(element: CgMethodCall) { + val caller = element.caller + if (caller != null) { + // 'this' can be omitted, otherwise render caller + if (caller !is CgThisInstance) { + // TODO: we need parentheses for calls like (-1).inv(), do something smarter here + if (caller !is CgVariable) print("(") + caller.accept(this) + if (caller !is CgVariable) print(")") + renderAccess(caller) + } + } else { + // for static methods render declaring class only if required + if (!element.executableId.accessibleByName) { + val method = element.executableId + print(method.classId.asString()) + print(".") + } + } + print(element.executableId.name.escapeNamePossibleKeyword()) + + renderTypeParameters(element.typeParameters) + renderExecutableCallArguments(element) + } + + // Throw statement + + override fun visit(element: CgThrowStatement) { + print("throw ") + element.exception.accept(this) + println(statementEnding) + } + + override fun visit(element: CgEmptyLine) { + println() + } + + override fun toString(): String = printer.toString() + + protected abstract fun renderRegularImport(regularImport: RegularImport) + protected abstract fun renderStaticImport(staticImport: StaticImport) + + //we render parameters in method signature on one line or on separate lines depending their amount + protected val maxParametersAmountInOneLine = 3 + + protected abstract fun renderMethodSignature(element: CgTestMethod) + protected abstract fun renderMethodSignature(element: CgErrorTestMethod) + protected abstract fun renderMethodSignature(element: CgParameterizedTestDataProviderMethod) + protected abstract fun renderMethodSignature(element: CgFrameworkUtilMethod) + + protected abstract fun renderForLoopVarControl(element: CgForLoop) + + protected abstract fun renderDeclarationLeftPart(element: CgDeclaration) + + protected abstract fun toStringConstantImpl(byte: Byte): String + protected abstract fun toStringConstantImpl(short: Short): String + protected abstract fun toStringConstantImpl(int: Int): String + protected abstract fun toStringConstantImpl(long: Long): String + protected abstract fun toStringConstantImpl(float: Float): String + + protected abstract fun renderAccess(caller: CgExpression) + protected abstract fun renderTypeParameters(typeParameters: TypeParameters) + protected abstract fun renderExecutableCallArguments(executableCall: CgExecutableCall) + + protected abstract fun renderExceptionCatchVariable(exception: CgVariable) + + protected fun getEscapedImportRendering(import: Import): String = + import.qualifiedName + .split(".") + .joinToString(".") { it.escapeNamePossibleKeyword() } + + protected fun List.printSeparated(newLines: Boolean = false) { + for ((index, element) in this.withIndex()) { + print(element) + when { + index < lastIndex -> { + print(",") + if (newLines) println() else print(" ") + } + index == lastIndex -> if (newLines) println() + } + } + } + + protected fun List.renderSeparated(newLines: Boolean = false) { + for ((index, element) in this.withIndex()) { + element.accept(this@CgAbstractRenderer) + when { + index < lastIndex -> { + print(",") + if (newLines) println() else print(" ") + } + index == lastIndex -> if (newLines) println() + } + } + } + + protected fun List.renderElements(elementsInLine: Int) { + val length = this.size + if (length <= elementsInLine) { // one-line array + for (i in 0 until length) { + val expr = this[i] + expr.accept(this@CgAbstractRenderer) + if (i != length - 1) { + print(", ") + } + } + } else { // multiline array + println() // line break after `int[] x = {` + withIndent { + for (i in 0 until length) { + val expr = this[i] + expr.accept(this@CgAbstractRenderer) + + if (i == length - 1) { + println() + } else if (i % elementsInLine == elementsInLine - 1) { + println(",") + } else { + print(", ") + } + } + } + } + } + + protected inline fun withIndent(block: () -> Unit) { + try { + pushIndent() + block() + } finally { + popIndent() + } + } + + protected open fun isAccessibleBySimpleNameImpl(classId: ClassId): Boolean = + classId in context.importedClasses || + classId.simpleName !in context.importedClasses.map { it.simpleName } && classId.packageName == context.classPackageName + + protected abstract fun escapeNamePossibleKeywordImpl(s: String): String + + protected fun String.escapeNamePossibleKeyword(): String = escapeNamePossibleKeywordImpl(this) + + protected fun ClassId.asString(): String { + if (!context.shouldOptimizeImports) return canonicalName + + // use simpleNameWithEnclosings instead of simpleName to consider nested classes case + return if (this.isAccessibleBySimpleName()) simpleNameWithEnclosingClasses else canonicalName + } + + private fun renderClassPackage(element: CgClass) { + if (element.packageName.isNotEmpty()) { + println("package ${element.packageName}${statementEnding}") + println() + } + } + + protected open fun renderClassFileImports(element: CgClassFile) { + val regularImports = element.imports.filterIsInstance() + val staticImports = element.imports.filterIsInstance() + + for (import in regularImports) { + renderRegularImport(import) + } + if (regularImports.isNotEmpty()) { + println() + } + + for (import in staticImports) { + renderStaticImport(import) + } + if (staticImports.isNotEmpty()) { + println() + } + } + + protected abstract fun renderVisibility(modifier: VisibilityModifier) + + protected abstract fun renderClassModality(aClass: CgClass) + + protected fun renderMethodDocumentation(element: CgMethod) { + element.documentation.accept(this) + } + + private fun Byte.toStringConstant() = when { + this == Byte.MAX_VALUE -> "${langPackage}.Byte.MAX_VALUE" + this == Byte.MIN_VALUE -> "${langPackage}.Byte.MIN_VALUE" + else -> toStringConstantImpl(this) + } + + private fun Char.toStringConstant() = when (this) { + '\'' -> "'\\''" + else -> "'" + StringEscapeUtils.escapeJava("$this") + "'" + } + + private fun Short.toStringConstant() = when (this) { + Short.MAX_VALUE -> "${langPackage}.Short.MAX_VALUE" + Short.MIN_VALUE -> "${langPackage}.Short.MIN_VALUE" + else -> toStringConstantImpl(this) + } + + private fun Int.toStringConstant(): String = toStringConstantImpl(this) + + private fun Long.toStringConstant() = when { + this == Long.MAX_VALUE -> "${langPackage}.Long.MAX_VALUE" + this == Long.MIN_VALUE -> "${langPackage}.Long.MIN_VALUE" + else -> toStringConstantImpl(this) + } + + private fun Float.toStringConstant() = when { + isNaN() -> "${langPackage}.Float.NaN" + this == Float.POSITIVE_INFINITY -> "${langPackage}.Float.POSITIVE_INFINITY" + this == Float.NEGATIVE_INFINITY -> "${langPackage}.Float.NEGATIVE_INFINITY" + else -> toStringConstantImpl(this) + } + + private fun Double.toStringConstant() = when { + isNaN() -> "${langPackage}.Double.NaN" + this == Double.POSITIVE_INFINITY -> "${langPackage}.Double.POSITIVE_INFINITY" + this == Double.NEGATIVE_INFINITY -> "${langPackage}.Double.NEGATIVE_INFINITY" + else -> "$this" + } + + private fun Boolean.toStringConstant() = + if (this) "true" else "false" + + protected fun String.toStringConstant(): String = "\"" + escapeCharacters() + "\"" + + protected abstract fun String.escapeCharacters(): String + + private fun ClassId.isAccessibleBySimpleName(): Boolean = isAccessibleBySimpleNameImpl(this) + + companion object { + const val TEST_METHOD_START_MARKER = "test method start marker" + const val TEST_METHOD_END_MARKER = "test method end marker" + + fun makeRenderer( + context: CgContext, + printer: CgPrinter = CgPrinterImpl() + ): CgAbstractRenderer { + val rendererContext = CgRendererContext.fromCgContext(context) + return makeRenderer(rendererContext, printer) + } + + fun makeRenderer( + utilClassKind: UtilClassKind, + codegenLanguage: CodegenLanguage, + printer: CgPrinter = CgPrinterImpl() + ): CgAbstractRenderer { + val rendererContext = CgRendererContext.fromUtilClassKind(utilClassKind, codegenLanguage) + return makeRenderer(rendererContext, printer) + } + + private fun makeRenderer(context: CgRendererContext, printer: CgPrinter): CgAbstractRenderer { + return context.cgLanguageAssistant.cgRenderer(context, printer) + } + + /** + * @see [LONG_CODE_FRAGMENTS] + */ + private const val LARGE_CODE_BLOCK_SIZE: Int = 1000 + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgJavaRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgJavaRenderer.kt new file mode 100644 index 0000000000..e607ccd0cc --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgJavaRenderer.kt @@ -0,0 +1,454 @@ +package org.utbot.framework.codegen.renderer + +import org.apache.commons.text.StringEscapeUtils +import org.utbot.framework.codegen.domain.RegularImport +import org.utbot.framework.codegen.domain.StaticImport +import org.utbot.framework.codegen.domain.models.CgAllocateArray +import org.utbot.framework.codegen.domain.models.CgAllocateInitializedArray +import org.utbot.framework.codegen.domain.models.CgAnonymousFunction +import org.utbot.framework.codegen.domain.models.CgArrayAnnotationArgument +import org.utbot.framework.codegen.domain.models.CgArrayInitializer +import org.utbot.framework.codegen.domain.models.CgBreakStatement +import org.utbot.framework.codegen.domain.models.CgConstructorCall +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgEqualTo +import org.utbot.framework.codegen.domain.models.CgErrorTestMethod +import org.utbot.framework.codegen.domain.models.CgErrorWrapper +import org.utbot.framework.codegen.domain.models.CgExecutableCall +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgForLoop +import org.utbot.framework.codegen.domain.models.CgGetJavaClass +import org.utbot.framework.codegen.domain.models.CgGetKotlinClass +import org.utbot.framework.codegen.domain.models.CgGetLength +import org.utbot.framework.codegen.domain.models.CgInnerBlock +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.codegen.domain.models.CgNotNullAssertion +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration +import org.utbot.framework.codegen.domain.models.CgParameterizedTestDataProviderMethod +import org.utbot.framework.codegen.domain.models.CgReturnStatement +import org.utbot.framework.codegen.domain.models.CgStatement +import org.utbot.framework.codegen.domain.models.CgStatementExecutableCall +import org.utbot.framework.codegen.domain.models.CgSwitchCase +import org.utbot.framework.codegen.domain.models.CgSwitchCaseLabel +import org.utbot.framework.codegen.domain.models.CgClass +import org.utbot.framework.codegen.domain.models.CgClassBody +import org.utbot.framework.codegen.domain.models.CgFormattedString +import org.utbot.framework.codegen.domain.models.CgFrameworkUtilMethod +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgTypeCast +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.tree.VisibilityModifier +import org.utbot.framework.codegen.util.nullLiteral +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.TypeParameters +import org.utbot.framework.plugin.api.util.isFinal +import org.utbot.framework.plugin.api.util.wrapperByPrimitive + +internal class CgJavaRenderer(context: CgRendererContext, printer: CgPrinter = CgPrinterImpl()) : + CgAbstractRenderer(context, printer) { + + override val statementEnding: String = ";" + + override val logicalAnd: String + get() = "&&" + + override val logicalOr: String + get() = "||" + + override val langPackage: String = "java.lang" + + override val ClassId.methodsAreAccessibleAsTopLevel: Boolean + get() = this == context.generatedClass + + override fun visit(element: CgClass) { + element.documentation?.accept(this) + + for (annotation in element.annotations) { + annotation.accept(this) + } + + renderVisibility(element.visibility) + renderClassModality(element) + if (element.isStatic) { + print("static ") + } + print("class ") + print(element.simpleName) + + val superclass = element.superclass + if (superclass != null) { + print(" extends ${superclass.asString()}") + } + if (element.interfaces.isNotEmpty()) { + print(" implements ") + element.interfaces.map { it.asString() }.printSeparated() + } + println(" {") + withIndent { element.body.accept(this) } + println("}") + } + + override fun visit(element: CgClassBody) { + // render class fields + for (field in element.fields) { + field.accept(this) + println() + } + + // render regions for test methods and utils + val allRegions = element.methodRegions + element.nestedClassRegions + element.staticDeclarationRegions + for ((i, region) in allRegions.withIndex()) { + if (i != 0) println() + + region.accept(this) + } + } + + override fun visit(element: CgArrayAnnotationArgument) { + print("{") + element.values.renderSeparated() + print("}") + } + + override fun visit(element: CgAnonymousFunction) { + print("(") + element.parameters.renderSeparated() + print(") -> ") + // TODO introduce CgBlock + + val expression = element.body.singleExpressionOrNull() + // expression lambda can be rendered without curly braces + if (expression != null) { + expression.accept(this) + + return + } + + visit(element.body) + } + + override fun visit(element: CgEqualTo) { + element.left.accept(this) + print(" == ") + element.right.accept(this) + } + + override fun visit(element: CgTypeCast) { + val expr = element.expression + val wrappedTargetType = wrapperByPrimitive.getOrDefault(element.targetType, element.targetType) + val exprTypeIsSimilar = expr.type == element.targetType || expr.type == wrappedTargetType + + // cast for null is mandatory in case of ambiguity - for example, readObject(Object) and readObject(Map) + if (exprTypeIsSimilar && expr != nullLiteral()) { + element.expression.accept(this) + return + } + + val isNegativeNumber = element.expression is CgLiteral + && element.expression.value is Number && element.expression.value.toDouble() < 0 + + print("(") + + print("(") + print(wrappedTargetType.asString()) + print(") ") + + if (isNegativeNumber) print("(") + element.expression.accept(this) + if (isNegativeNumber) print(")") + + print(")") + } + + override fun visit(element: CgErrorWrapper) { + element.expression.accept(this) + } + + // Not-null assertion + + override fun visit(element: CgNotNullAssertion) { + element.expression.accept(this) + } + + override fun visit(element: CgParameterDeclaration) { + if (element.isVararg) { + print(element.type.elementClassId!!.asString()) + print("...") + } else { + print(element.type.asString()) + } + print(" ") + print(element.name.escapeNamePossibleKeyword()) + } + + override fun visit(element: CgGetJavaClass) { + // TODO: check how it works on ref types, primitives, ref arrays, primitive arrays, etc. + print(element.classId.asString()) + print(".class") + } + + override fun visit(element: CgGetKotlinClass) { + // For now we assume that we never need KClass in the generated Java test classes. + // If it changes, this error may be removed. + error("KClass attempted to be used in the Java test class") + } + + override fun visit(element: CgAllocateArray) { + // TODO: Arsen strongly required to rewrite later + val typeName = element.type.canonicalName.substringBefore("[") + val otherDimensions = element.type.canonicalName.substringAfter("]") + print("new $typeName[${element.size}]$otherDimensions") + } + + override fun visit(element: CgAllocateInitializedArray) { + // TODO: same as in visit(CgAllocateArray): we should rewrite the typeName and otherDimensions variables declaration + // to avoid using substringBefore() and substringAfter() directly + val typeName = element.type.canonicalName.substringBefore("[") + val otherDimensions = element.type.canonicalName.substringAfter("]") + // we can't specify the size of the first dimension when using initializer, + // as opposed to CgAllocateArray where there is no initializer + print("new $typeName[]$otherDimensions") + element.initializer.accept(this) + } + + override fun visit(element: CgArrayInitializer) { + val elementsInLine = arrayElementsInLine(element.elementType) + + print("{") + element.values.renderElements(elementsInLine) + print("}") + } + + override fun visit(element: CgGetLength) { + element.variable.accept(this) + print(".length") + } + + override fun visit(element: CgConstructorCall) { + when(element.type.isInner){ + true -> { + // the first argument of inner classes is outer class + print(element.arguments.first().accept(this)) + print(".new ") + print(element.executableId.classId.simpleName) + + print("(") + element.arguments.drop(1).renderSeparated() + print(")") + } + false -> { + print("new ") + print(element.executableId.classId.asString()) + renderExecutableCallArguments(element) + } + } + } + + override fun renderRegularImport(regularImport: RegularImport) { + val escapedImport = getEscapedImportRendering(regularImport) + println("import $escapedImport$statementEnding") + } + + override fun renderStaticImport(staticImport: StaticImport) { + val escapedImport = getEscapedImportRendering(staticImport) + println("import static $escapedImport$statementEnding") + } + + override fun renderMethodSignature(element: CgTestMethod) { + renderVisibility(element.visibility) + // test methods always have void return type + print("void ") + print(element.name) + + print("(") + val newLinesNeeded = element.parameters.size > maxParametersAmountInOneLine + element.parameters.renderSeparated(newLinesNeeded) + print(")") + + renderExceptions(element) + } + + override fun renderMethodSignature(element: CgErrorTestMethod) { + renderVisibility(element.visibility) + // error test methods always have void return type + println("void ${element.name}()") + } + + override fun renderMethodSignature(element: CgParameterizedTestDataProviderMethod) { + //we do not have a good string representation for two-dimensional array, so this strange if-else is required + val returnType = + if (element.returnType.simpleName == "Object[][]") "java.lang.Object[][]" else "${element.returnType}" + + renderVisibility(element.visibility) + print("static $returnType ${element.name}()") + renderExceptions(element) + } + + override fun renderMethodSignature(element: CgFrameworkUtilMethod) { + renderVisibility(element.visibility) + // framework util methods always have void return type + print("void ") + print(element.name) + print("()") + + renderExceptions(element) + } + + override fun visit(element: CgInnerBlock) { + println("{") + withIndent { + for (statement in element.statements) { + statement.accept(this) + } + } + println("}") + } + + override fun renderForLoopVarControl(element: CgForLoop) { + print("for (") + // TODO: rewrite in the future + with(element.initialization) { + print(variableType.asString()) + print(" ") + visit(variable) + initializer?.let { + print(" = ") + it.accept(this@CgJavaRenderer) + } + print("$statementEnding ") + } + element.condition.accept(this) + print("$statementEnding ") + element.update.accept(this) + } + + override fun renderDeclarationLeftPart(element: CgDeclaration) { + print(element.variableType.asString()) + print(" ") + visit(element.variable) + } + + override fun renderAccess(caller: CgExpression) { + print(".") + } + + override fun visit(element: CgSwitchCaseLabel) { + if (element.label != null) { + print("case ") + element.label.accept(this) + } else { + print("default") + } + println(":") + withIndent { + for (statement in element.statements) { + statement.accept(this) + } + + if (element.addBreakStatementToEnd) { + CgBreakStatement.accept(this) + } + } + } + + override fun visit(element: CgSwitchCase) { + print("switch (") + element.value.accept(this) + println(") {") + withIndent { + for (caseLabel in element.labels) { + caseLabel.accept(this) + } + element.defaultLabel?.accept(this) + } + println("}") + } + + override fun visit(element: CgFormattedString) { + val nonLiteralElements = element.array.filterNot { it is CgLiteral } + + print("String.format(") + val constructedMsg = buildString { + element.array.forEachIndexed { index, cgElement -> + if (cgElement is CgLiteral) append( + cgElement.toStringConstant(asRawString = true) + ) else append("%s") + if (index < element.array.lastIndex) append(" ") + } + } + + print(constructedMsg.toStringConstant()) + + // Comma to separate msg from variables + if (nonLiteralElements.isNotEmpty()) print(", ") + nonLiteralElements.renderSeparated(newLines = false) + print(")") + } + + override fun toStringConstantImpl(byte: Byte): String = "(byte) $byte" + + override fun toStringConstantImpl(short: Short): String = "(short) $short" + + override fun toStringConstantImpl(long: Long): String = "${long}L" + + override fun toStringConstantImpl(float: Float): String = "${float}f" + + override fun toStringConstantImpl(int: Int): String = when (int) { + Int.MAX_VALUE -> "Integer.MAX_VALUE" + Int.MIN_VALUE -> "Integer.MIN_VALUE" + else -> "$int" + } + + override fun String.escapeCharacters(): String = StringEscapeUtils.escapeJava(this) + + override fun renderExecutableCallArguments(executableCall: CgExecutableCall) { + print("(") + executableCall.arguments.renderSeparated() + print(")") + } + + override fun renderTypeParameters(typeParameters: TypeParameters) {} + + override fun renderExceptionCatchVariable(exception: CgVariable) { + print("${exception.type} ${exception.name.escapeNamePossibleKeyword()}") + } + + override fun isAccessibleBySimpleNameImpl(classId: ClassId): Boolean = + super.isAccessibleBySimpleNameImpl(classId) || classId.packageName == "java.lang" + + override fun escapeNamePossibleKeywordImpl(s: String): String = s + + override fun renderVisibility(modifier: VisibilityModifier) { + when (modifier) { + VisibilityModifier.PUBLIC -> print("public ") + VisibilityModifier.PRIVATE -> print("private ") + VisibilityModifier.PROTECTED -> print("protected ") + VisibilityModifier.PACKAGE_PRIVATE -> Unit + VisibilityModifier.INTERNAL -> error("Java: unexpected visibility modifier -- $modifier") + } + } + + override fun renderClassModality(aClass: CgClass) { + if (aClass.id.isFinal) print("final ") + } + + private fun renderExceptions(method: CgMethod) { + method.exceptions.takeIf { it.isNotEmpty() }?.let { exceptions -> + print(" throws ") + print(exceptions.joinToString(separator = ", ", postfix = " ") { it.asString() }) + } + } + + /** + * Returns containing [CgExpression] if [this] represents return statement or one executable call, and null otherwise. + */ + private fun List.singleExpressionOrNull(): CgExpression? = + singleOrNull().let { + when (it) { + is CgReturnStatement -> it.expression + is CgStatementExecutableCall -> it.call + else -> null + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgKotlinRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgKotlinRenderer.kt new file mode 100644 index 0000000000..8a92d09617 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgKotlinRenderer.kt @@ -0,0 +1,644 @@ +package org.utbot.framework.codegen.renderer + +import org.apache.commons.text.StringEscapeUtils +import org.utbot.common.WorkaroundReason +import org.utbot.common.workaround +import org.utbot.framework.codegen.domain.RegularImport +import org.utbot.framework.codegen.domain.StaticImport +import org.utbot.framework.codegen.services.language.isLanguageKeyword +import org.utbot.framework.codegen.domain.models.CgAllocateArray +import org.utbot.framework.codegen.domain.models.CgAllocateInitializedArray +import org.utbot.framework.codegen.domain.models.CgAnonymousFunction +import org.utbot.framework.codegen.domain.models.CgArrayAnnotationArgument +import org.utbot.framework.codegen.domain.models.CgArrayElementAccess +import org.utbot.framework.codegen.domain.models.CgArrayInitializer +import org.utbot.framework.codegen.domain.models.CgAuxiliaryClass +import org.utbot.framework.codegen.domain.models.CgComparison +import org.utbot.framework.codegen.domain.models.CgConstructorCall +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgEqualTo +import org.utbot.framework.codegen.domain.models.CgErrorTestMethod +import org.utbot.framework.codegen.domain.models.CgErrorWrapper +import org.utbot.framework.codegen.domain.models.CgExecutableCall +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgFieldAccess +import org.utbot.framework.codegen.domain.models.CgForLoop +import org.utbot.framework.codegen.domain.models.CgGetJavaClass +import org.utbot.framework.codegen.domain.models.CgGetKotlinClass +import org.utbot.framework.codegen.domain.models.CgGetLength +import org.utbot.framework.codegen.domain.models.CgInnerBlock +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.codegen.domain.models.CgNotNullAssertion +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration +import org.utbot.framework.codegen.domain.models.CgParameterizedTestDataProviderMethod +import org.utbot.framework.codegen.domain.models.CgSimpleRegion +import org.utbot.framework.codegen.domain.models.CgSpread +import org.utbot.framework.codegen.domain.models.CgStaticsRegion +import org.utbot.framework.codegen.domain.models.CgSwitchCase +import org.utbot.framework.codegen.domain.models.CgSwitchCaseLabel +import org.utbot.framework.codegen.domain.models.CgClass +import org.utbot.framework.codegen.domain.models.CgClassBody +import org.utbot.framework.codegen.domain.models.CgFormattedString +import org.utbot.framework.codegen.domain.models.CgFrameworkUtilMethod +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgTypeCast +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.tree.VisibilityModifier +import org.utbot.framework.codegen.util.nullLiteral +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.TypeParameters +import org.utbot.framework.plugin.api.WildcardTypeParameter +import org.utbot.framework.plugin.api.util.isFinal +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.isKotlinFile +import org.utbot.framework.plugin.api.util.isPrimitive +import org.utbot.framework.plugin.api.util.isPrimitiveWrapper +import org.utbot.framework.plugin.api.util.kClass +import org.utbot.framework.plugin.api.util.voidClassId + +//TODO rewrite using KtPsiFactory? +internal class CgKotlinRenderer(context: CgRendererContext, printer: CgPrinter = CgPrinterImpl()) : CgAbstractRenderer(context, printer) { + override val statementEnding: String = "" + + override val logicalAnd: String + get() = "and" + + override val logicalOr: String + get() = "or" + + override val langPackage: String = "kotlin" + + override val ClassId.methodsAreAccessibleAsTopLevel: Boolean + // NB: the order of operands is important as `isKotlinFile` uses reflection and thus can't be called on context.generatedClass + get() = (this == context.generatedClass) || isKotlinFile + + override fun visit(element: CgClass) { + element.documentation?.accept(this) + + for (annotation in element.annotations) { + annotation.accept(this) + } + + renderVisibility(element.visibility) + renderClassModality(element) + if (!element.isStatic && element.isNested) { + print("inner ") + } + if (element.id.isKotlinObject) + print("object ") + else + print("class ") + print(element.simpleName) + + if (element.superclass != null || element.interfaces.isNotEmpty()) { + print(" :") + } + val supertypes = mutableListOf() + .apply { + // Here we do not consider constructors with arguments, but for now they are not needed. + // Also, we do not yet support type parameters in code generation, so generic + // superclasses or interfaces are not supported. Although, they are not needed for now. + val superclass = element.superclass + if (superclass != null) { + add("${superclass.asString()}()") + } + element.interfaces.forEach { + add(it.asString()) + } + }.joinToString() + if (supertypes.isNotEmpty()) { + print(" $supertypes") + } + println(" {") + withIndent { element.body.accept(this) } + println("}") + } + + override fun visit(element: CgClassBody) { + // render regions for test methods + for ((i, region) in (element.methodRegions + element.nestedClassRegions).withIndex()) { + if (i != 0) println() + + region.accept(this) + } + + if (element.staticDeclarationRegions.isEmpty()) { + return + } + // render static declaration regions inside a companion object + println() + + // In Kotlin, we put static declarations in a companion object of the class, + // but that **does not** apply to nested classes. + // They must be located in the class itself, not its companion object. + // That is why here we extract all the auxiliary classes from static regions + // to form a separate region specifically for them. + // See the docs on CgAuxiliaryClass for details on what they represent. + val auxiliaryClassesRegion = element.staticDeclarationRegions + .flatMap { it.content } + .filterIsInstance() + .let { classes -> CgSimpleRegion("Util classes", classes) } + + if (auxiliaryClassesRegion.content.isNotEmpty()) { + auxiliaryClassesRegion.accept(this) + println() + } + + // Here we update the static regions by removing all the auxiliary classes from them. + // The remaining content of regions will be rendered inside a companion object. + val updatedStaticRegions = element.staticDeclarationRegions.map { region -> + val updatedContent = region.content.filterNot { it is CgAuxiliaryClass } + CgStaticsRegion(region.header, updatedContent) + } + + fun renderAllStaticRegions() { + for ((i, staticsRegion) in updatedStaticRegions.withIndex()) { + if (i != 0) println() + + staticsRegion.accept(this) + } + } + + // We should generate static methods in companion object iff generated class is not an object + if (element.classId.isKotlinObject) { + renderAllStaticRegions() + } else { + renderCompanionObject(::renderAllStaticRegions) + } + } + + /** + * Build a companion object. + * @param body a lambda that contains the logic of construction of a companion object's body + */ + private fun renderCompanionObject(body: () -> Unit) { + println("companion object {") + withIndent(body) + println("}") + } + + // Property access + + override fun visit(element: CgFieldAccess) { + element.caller.accept(this) + renderAccess(element.caller) + print(element.fieldId.name) + } + + override fun visit(element: CgArrayElementAccess) { + if (element.array.type.isNullable) { + element.array.accept(this) + print("?.get(") + element.index.accept(this) + print(")") + } else { + super.visit(element) + } + } + + override fun renderAccess(caller: CgExpression) { + if (caller.type.isNullable) print("?") + print(".") + } + + override fun visit(element: CgParameterDeclaration) { + if (element.isVararg) { + print("vararg ") + } + print("${element.name.escapeNamePossibleKeyword()}: ") + print(getKotlinClassString(element.type)) + if (element.isReferenceType) { + print("?") + } + } + + // TODO: probably rewrite to use better syntax if there is one + override fun visit(element: CgArrayAnnotationArgument) { + print("value = [") + element.values.renderSeparated() + print("]") + } + + override fun visit(element: CgSpread) { + print("*") + element.array.accept(this) + } + + override fun visit(element: CgAnonymousFunction) { + print("{ ") + element.parameters.renderSeparated(true) + if (element.parameters.isNotEmpty()) { + print(" -> ") + } else { + println() + } + // cannot use visit(element.body) here because { was already printed + withIndent { + for (statement in element.body) { + statement.accept(this) + } + } + print("}") + } + + override fun visit(element: CgEqualTo) { + element.left.accept(this) + print(" === ") + element.right.accept(this) + } + + /** + * Sometimes we can omit rendering type cast and simply render its [CgTypeCast.expression] instead. + * This method checks if the type cast can be omitted. + * + * For example, type cast can be omitted when a primitive wrapper is cast to its corresponding primitive (or vice versa), + * because in Kotlin there are no primitive types as opposed to Java. + * + * Also, sometimes we can omit type cast when the [CgTypeCast.targetType] is the same as the type of [CgTypeCast.expression]. + */ + private fun isCastNeeded(element: CgTypeCast): Boolean { + val targetType = element.targetType + val expressionType = element.expression.type + + val isPrimitiveToWrapperCast = targetType.isPrimitiveWrapper && expressionType.isPrimitive + val isWrapperToPrimitiveCast = targetType.isPrimitive && expressionType.isPrimitiveWrapper + val isNullLiteral = element.expression == nullLiteral() + + if (!isNullLiteral && element.isSafetyCast && (isPrimitiveToWrapperCast || isWrapperToPrimitiveCast)) { + return false + } + + // perform type cast only if target type is not equal to expression type + // but cast from nullable to not nullable should be performed + // TODO SAT-1445 actually this safetyCast check looks like hack workaround and possibly does not work + // so it should be carefully tested one day + return !element.isSafetyCast || expressionType != targetType + } + + override fun visit(element: CgTypeCast) { + if (!isCastNeeded(element)) { + element.expression.accept(this) + } else { + print("(") + + element.expression.accept(this) + + if (element.isSafetyCast) print(" as? ") else print(" as ") + print(getKotlinClassString(element.targetType)) + renderTypeParameters(element.targetType.typeParameters) + val initNullable = element.type.isNullable + if (element.targetType.isNullable || initNullable) print("?") + + print(")") + } + } + + override fun visit(element: CgErrorWrapper) { + element.expression.accept(this) + print(" ?: error(\"${element.message}\")") + } + + override fun visit(element: CgGetJavaClass) { + // TODO: check how it works on ref types, primitives, ref arrays, primitive arrays, etc. + print(getKotlinClassString(element.classId)) + print("::class.java") + } + + override fun visit(element: CgGetKotlinClass) { + // TODO: check how it works on ref types, primitives, ref arrays, primitive arrays, etc. + print(getKotlinClassString(element.classId)) + print("::class") + } + + override fun visit(element: CgNotNullAssertion) { + element.expression.accept(this) + print("!!") + } + + override fun visit(element: CgAllocateArray) { + // TODO think about void as primitive + print(getKotlinClassString(element.type)) + print("(${element.size})") + if (!element.elementType.isPrimitive) { + print(" { null }") + } + } + + override fun visit(element: CgAllocateInitializedArray) { + print(getKotlinClassString(element.type)) + print("(${element.size})") + print(" {") + element.initializer.accept(this) + print(" }") + } + + override fun visit(element: CgArrayInitializer) { + val elementType = element.elementType + val elementsInLine = arrayElementsInLine(elementType) + + if (elementType.isPrimitive) { + val prefix = elementType.name.toLowerCase() + print("${prefix}ArrayOf(") + element.values.renderElements(elementsInLine) + print(")") + } else { + print(getKotlinClassString(element.arrayType)) + print("(${element.size})") + print(" { null }") + } + } + + override fun visit(element: CgGetLength) { + element.variable.accept(this) + print(".size") + } + + override fun visit(element: CgConstructorCall) { + print(getKotlinClassString(element.executableId.classId)) + print("(") + element.arguments.renderSeparated() + print(")") + } + + override fun visit(element: CgParameterizedTestDataProviderMethod) { + println() + println("@JvmStatic") + super.visit(element) + } + + override fun renderRegularImport(regularImport: RegularImport) { + val escapedImport = getEscapedImportRendering(regularImport) + println("import $escapedImport$statementEnding") + } + + override fun renderStaticImport(staticImport: StaticImport) { + val escapedImport = getEscapedImportRendering(staticImport) + println("import $escapedImport$statementEnding") + } + + override fun renderMethodSignature(element: CgTestMethod) { + print("fun ") + // TODO resolve $ in name + print(element.name) + print("(") + val newLines = element.parameters.size > maxParametersAmountInOneLine + element.parameters.renderSeparated(newLines) + print(")") + renderMethodReturnType(element) + } + + override fun renderMethodSignature(element: CgErrorTestMethod) { + // error test methods always have void return type + print("fun ") + print(element.name) + println("()") + } + + override fun renderMethodSignature(element: CgParameterizedTestDataProviderMethod) { + val returnType = getKotlinClassString(element.returnType) + println("fun ${element.name}(): $returnType") + } + + override fun renderMethodSignature(element: CgFrameworkUtilMethod) { + print("fun ") + // TODO: resolve $ in name as in [CgTestMethod] + print(element.name) + print("()") + renderMethodReturnType(element) + } + + private fun renderMethodReturnType(method: CgMethod) { + if (method.returnType != voidClassId) { + print(": ") + print(getKotlinClassString(method.returnType)) + } + } + + override fun visit(element: CgInnerBlock) { + println("run {") + withIndent { + for (statement in element.statements) { + statement.accept(this) + } + } + println("}") + } + + override fun renderForLoopVarControl(element: CgForLoop) { + print("for (") + with(element.initialization) { + visit(variable) + print(" in ") + initializer?.accept(this@CgKotlinRenderer) + } + print(" until ") + (element.condition as CgComparison).right.accept(this) // TODO it is comparison just now + // TODO is step always increment? + } + + private fun renderKotlinTypeInDeclaration(element: CgDeclaration) { + // TODO consider moving to getKotlinClassString + print(": ") + print(getKotlinClassString(element.variableType)) + renderTypeParameters(element.variableType.typeParameters) + val initNullable = element.initializer?.run { type.isNullable } ?: false + if (element.variableType.isNullable || initNullable) print("?") + } + + override fun renderDeclarationLeftPart(element: CgDeclaration) { + if (element.isMutable) print("var ") else print("val ") + visit(element.variable) + // TODO SAT-1683 enable explicit types + // renderKotlinTypeInDeclaration(element) + } + + override fun visit(element: CgSwitchCaseLabel) { + if (element.label != null) { + element.label.accept(this) + } else { + print("else") + } + print(" -> ") + visit(element.statements, printNextLine = true) + } + + override fun visit(element: CgSwitchCase) { + print("when (") + element.value.accept(this) + println(") {") + withIndent { + for (caseLabel in element.labels) { + caseLabel.accept(this) + } + element.defaultLabel?.accept(this) + } + println("}") + } + + override fun visit(element: CgFormattedString) { + print("\"") + element.array.forEachIndexed { index, cgElement -> + if (cgElement is CgLiteral) + print(cgElement.toStringConstant(asRawString = true)) + else { + print("$") + when (cgElement) { + // It is not necessary to wrap variables with curly brackets + is CgVariable -> cgElement.accept(this) + else -> { + print("{") + cgElement.accept(this) + print("}") + } + } + } + + if (index < element.array.lastIndex) print(" ") + } + print("\"") + } + + override fun toStringConstantImpl(byte: Byte): String = buildString { + if (byte < 0) { + append("(") + append("$byte") + append(")") + } else { + append("$byte") + } + append(".toByte()") + } + + override fun toStringConstantImpl(short: Short): String = buildString { + if (short < 0) { + append("(") + append("$short") + append(")") + } else { + append("$short") + } + append(".toShort()") + } + + override fun toStringConstantImpl(long: Long): String = "${long}L" + + override fun toStringConstantImpl(float: Float): String = "${float}f" + + override fun toStringConstantImpl(int: Int): String = when (int) { + Int.MAX_VALUE -> "Int.MAX_VALUE" + Int.MIN_VALUE -> "Int.MIN_VALUE" + else -> "$int" + } + + /** + * See [Escaping in Kotlin](https://stackoverflow.com/questions/44170959/kotlin-form-feed-character-illegal-escape-f/) + */ + override fun String.escapeCharacters(): String = + StringEscapeUtils.escapeJava(this) + .replace("$", "\\$") + .replace("\\f", "\\u000C") + .replace("\\xxx", "\\\u0058\u0058\u0058") + + override fun renderExecutableCallArguments(executableCall: CgExecutableCall) { + print("(") + val lastArgument = executableCall.arguments.lastOrNull() + if (lastArgument != null && lastArgument is CgAnonymousFunction) { + executableCall.arguments.dropLast(1).renderSeparated() + print(") ") + executableCall.arguments.last().accept(this) + } else { + executableCall.arguments.renderSeparated() + print(")") + } + } + + override fun renderExceptionCatchVariable(exception: CgVariable) { + print("${exception.name.escapeNamePossibleKeyword()}: ${exception.type.kClass.simpleName}") + } + + override fun isAccessibleBySimpleNameImpl(classId: ClassId): Boolean { + return super.isAccessibleBySimpleNameImpl(classId) || classId.packageName == "kotlin" + } + + override fun escapeNamePossibleKeywordImpl(s: String): String = + if (isLanguageKeyword(s, context.cgLanguageAssistant)) "`$s`" else s + + override fun renderVisibility(modifier: VisibilityModifier) { + when (modifier) { + VisibilityModifier.PUBLIC -> Unit + VisibilityModifier.PRIVATE -> print("private ") + VisibilityModifier.PROTECTED -> print("protected ") + VisibilityModifier.INTERNAL -> print("internal ") + VisibilityModifier.PACKAGE_PRIVATE -> error("Kotlin: unexpected visibility modifier -- $modifier") + } + } + + override fun renderClassModality(aClass: CgClass) { + if (!aClass.id.isFinal) print("open ") + } + + private fun getKotlinClassString(id: ClassId): String = + if (id.isArray) { + getKotlinArrayClassOfString(id) + } else { + when (id.jvmName) { + "Ljava/lang/Object;" -> Any::class.simpleName!! + "B", "Ljava/lang/Byte;" -> Byte::class.simpleName!! + "S", "Ljava/lang/Short;" -> Short::class.simpleName!! + "C", "Ljava/lang/Character;" -> Char::class.simpleName!! + "I", "Ljava/lang/Integer;" -> Int::class.simpleName!! + "J", "Ljava/lang/Long;" -> Long::class.simpleName!! + "F", "Ljava/lang/Float;" -> Float::class.simpleName!! + "D", "Ljava/lang/Double;" -> Double::class.simpleName!! + "Z", "Ljava/lang/Boolean;" -> Boolean::class.simpleName!! + "Ljava/lang/CharSequence;" -> CharSequence::class.simpleName!! + "Ljava/lang/String;" -> String::class.simpleName!! + else -> { + // we cannot access kClass for BuiltinClassId + // we cannot use simple name here because this class can be not imported + if (id is BuiltinClassId) id.canonicalName else id.kClass.id.asString() + } + } + } + + private fun getKotlinArrayClassOfString(classId: ClassId): String = + if (!classId.elementClassId!!.isPrimitive) { + if (classId.elementClassId != java.lang.Object::class.id) { + workaround(WorkaroundReason.ARRAY_ELEMENT_TYPES_ALWAYS_NULLABLE) { + // for now all element types are nullable + // Use kotlin.Array, because Array becomes java.lang.reflect.Array + // when passed as a type parameter + "kotlin.Array<${getKotlinClassString(classId.elementClassId!!)}?>" + } + } else { + "kotlin.Array" + } + } else { + when (classId.jvmName) { + "[B" -> ByteArray::class.simpleName!! + "[S" -> ShortArray::class.simpleName!! + "[C" -> CharArray::class.simpleName!! + "[I" -> IntArray::class.simpleName!! + "[J" -> LongArray::class.simpleName!! + "[F" -> FloatArray::class.simpleName!! + "[D" -> DoubleArray::class.simpleName!! + "[Z" -> BooleanArray::class.simpleName!! + else -> classId.kClass.id.asString() + } + } + + override fun renderTypeParameters(typeParameters: TypeParameters) { + if (typeParameters.parameters.isNotEmpty()) { + print("<") + if (typeParameters is WildcardTypeParameter) { + print("*") + } else { + print(typeParameters.parameters.joinToString { getKotlinClassString(it)}) + } + print(">") + } + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/CgPrinter.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgPrinter.kt similarity index 84% rename from utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/CgPrinter.kt rename to utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgPrinter.kt index 0e4713f9cc..62daa3b6b4 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/model/util/CgPrinter.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgPrinter.kt @@ -1,4 +1,6 @@ -package org.utbot.framework.codegen.model.util +package org.utbot.framework.codegen.renderer + +import org.utbot.framework.plugin.api.util.IndentUtil interface CgPrinter { fun print(text: String) @@ -8,6 +10,7 @@ interface CgPrinter { fun popIndent() override fun toString(): String + var printedLength: Int } class CgPrinterImpl( @@ -30,6 +33,8 @@ class CgPrinterImpl( override fun toString(): String = builder.toString() + override var printedLength: Int = builder.length + override fun print(text: String) { if (atLineStart) { printIndent() @@ -48,10 +53,6 @@ class CgPrinterImpl( atLineStart = true } - fun printLine() { - appendLine() - } - private fun printIndent() { append(indent) } @@ -59,6 +60,6 @@ class CgPrinterImpl( private operator fun String.times(n: Int): String = repeat(n) companion object { - private const val TAB = " " + private const val TAB = IndentUtil.TAB } } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgRendererContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgRendererContext.kt new file mode 100644 index 0000000000..c3276b9b22 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgRendererContext.kt @@ -0,0 +1,62 @@ +package org.utbot.framework.codegen.renderer + +import org.utbot.framework.codegen.domain.builtin.selectUtilClassId +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.tree.ututils.UtilClassKind +import org.utbot.framework.codegen.tree.ututils.UtilClassKind.Companion.utilsPackageFullName +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.codegen.services.language.CgLanguageAssistant +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.MockFramework +import org.utbot.framework.utils.UtilMethodProvider + +/** + * Information from [CgContext] that is relevant for the renderer. + * Not all the information from [CgContext] is required to render a class, + * so this more lightweight context is created for this purpose. + */ +class CgRendererContext( + val shouldOptimizeImports: Boolean, + val importedClasses: Set, + val importedStaticMethods: Set, + val classPackageName: String, + val generatedClass: ClassId, + val utilMethodProvider: UtilMethodProvider, + val codegenLanguage: CodegenLanguage, + val mockFrameworkUsed: Boolean, + val mockFramework: MockFramework, + val cgLanguageAssistant: CgLanguageAssistant, +) { + companion object { + fun fromCgContext(context: CgContext): CgRendererContext { + return CgRendererContext( + shouldOptimizeImports = context.shouldOptimizeImports, + importedClasses = context.importedClasses, + importedStaticMethods = context.importedStaticMethods, + classPackageName = context.testClassPackageName, + generatedClass = context.outerMostTestClass, + utilMethodProvider = context.utilMethodProvider, + codegenLanguage = context.codegenLanguage, + cgLanguageAssistant = context.cgLanguageAssistant, + mockFrameworkUsed = context.mockFrameworkUsed, + mockFramework = context.mockFramework + ) + } + + fun fromUtilClassKind(utilClassKind: UtilClassKind, language: CodegenLanguage): CgRendererContext { + return CgRendererContext( + shouldOptimizeImports = false, + importedClasses = emptySet(), + importedStaticMethods = emptySet(), + classPackageName = utilsPackageFullName(language), + generatedClass = selectUtilClassId(language), + utilMethodProvider = utilClassKind.utilMethodProvider, + codegenLanguage = language, + mockFrameworkUsed = utilClassKind.mockFrameworkUsed, + mockFramework = utilClassKind.mockFramework, + cgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage(language), + ) + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgVisitor.kt new file mode 100644 index 0000000000..fe75905f06 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/CgVisitor.kt @@ -0,0 +1,275 @@ +package org.utbot.framework.codegen.renderer + +import org.utbot.framework.codegen.domain.models.CgClassFile +import org.utbot.framework.codegen.domain.models.CgAbstractFieldAccess +import org.utbot.framework.codegen.domain.models.CgAbstractMultilineComment +import org.utbot.framework.codegen.domain.models.CgAllocateArray +import org.utbot.framework.codegen.domain.models.CgAllocateInitializedArray +import org.utbot.framework.codegen.domain.models.CgAnonymousFunction +import org.utbot.framework.codegen.domain.models.CgArrayAnnotationArgument +import org.utbot.framework.codegen.domain.models.CgArrayElementAccess +import org.utbot.framework.codegen.domain.models.CgArrayInitializer +import org.utbot.framework.codegen.domain.models.CgAssignment +import org.utbot.framework.codegen.domain.models.CgAuxiliaryClass +import org.utbot.framework.codegen.domain.models.CgBreakStatement +import org.utbot.framework.codegen.domain.models.CgComment +import org.utbot.framework.codegen.domain.models.CgCommentedAnnotation +import org.utbot.framework.codegen.domain.models.CgComparison +import org.utbot.framework.codegen.domain.models.CgConstructorCall +import org.utbot.framework.codegen.domain.models.CgContinueStatement +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgDecrement +import org.utbot.framework.codegen.domain.models.CgDoWhileLoop +import org.utbot.framework.codegen.domain.models.CgDocClassLinkStmt +import org.utbot.framework.codegen.domain.models.CgDocCodeStmt +import org.utbot.framework.codegen.domain.models.CgDocMethodLinkStmt +import org.utbot.framework.codegen.domain.models.CgDocPreTagStatement +import org.utbot.framework.codegen.domain.models.CgCustomTagStatement +import org.utbot.framework.codegen.domain.models.CgDocRegularStmt +import org.utbot.framework.codegen.domain.models.CgDocumentationComment +import org.utbot.framework.codegen.domain.models.CgElement +import org.utbot.framework.codegen.domain.models.CgEmptyLine +import org.utbot.framework.codegen.domain.models.CgEnumConstantAccess +import org.utbot.framework.codegen.domain.models.CgEqualTo +import org.utbot.framework.codegen.domain.models.CgErrorTestMethod +import org.utbot.framework.codegen.domain.models.CgErrorWrapper +import org.utbot.framework.codegen.domain.models.CgExecutableCall +import org.utbot.framework.codegen.domain.models.CgMethodsCluster +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgFieldAccess +import org.utbot.framework.codegen.domain.models.CgForEachLoop +import org.utbot.framework.codegen.domain.models.CgForLoop +import org.utbot.framework.codegen.domain.models.CgGetJavaClass +import org.utbot.framework.codegen.domain.models.CgGetKotlinClass +import org.utbot.framework.codegen.domain.models.CgGetLength +import org.utbot.framework.codegen.domain.models.CgGreaterThan +import org.utbot.framework.codegen.domain.models.CgIfStatement +import org.utbot.framework.codegen.domain.models.CgIncrement +import org.utbot.framework.codegen.domain.models.CgInnerBlock +import org.utbot.framework.codegen.domain.models.CgIsInstance +import org.utbot.framework.codegen.domain.models.CgLessThan +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgLogicalAnd +import org.utbot.framework.codegen.domain.models.CgLogicalOr +import org.utbot.framework.codegen.domain.models.CgLoop +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgMultilineComment +import org.utbot.framework.codegen.domain.models.CgMultipleArgsAnnotation +import org.utbot.framework.codegen.domain.models.CgNamedAnnotationArgument +import org.utbot.framework.codegen.domain.models.CgNonStaticRunnable +import org.utbot.framework.codegen.domain.models.CgNotNullAssertion +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration +import org.utbot.framework.codegen.domain.models.CgParameterizedTestDataProviderMethod +import org.utbot.framework.codegen.domain.models.CgReturnStatement +import org.utbot.framework.codegen.domain.models.CgSimpleRegion +import org.utbot.framework.codegen.domain.models.CgSingleArgAnnotation +import org.utbot.framework.codegen.domain.models.CgSingleLineComment +import org.utbot.framework.codegen.domain.models.CgSpread +import org.utbot.framework.codegen.domain.models.CgStatement +import org.utbot.framework.codegen.domain.models.CgStatementExecutableCall +import org.utbot.framework.codegen.domain.models.CgStaticFieldAccess +import org.utbot.framework.codegen.domain.models.CgStaticRunnable +import org.utbot.framework.codegen.domain.models.CgStaticsRegion +import org.utbot.framework.codegen.domain.models.CgSwitchCase +import org.utbot.framework.codegen.domain.models.CgSwitchCaseLabel +import org.utbot.framework.codegen.domain.models.CgClass +import org.utbot.framework.codegen.domain.models.CgClassBody +import org.utbot.framework.codegen.domain.models.CgDocRegularLineStmt +import org.utbot.framework.codegen.domain.models.CgFieldDeclaration +import org.utbot.framework.codegen.domain.models.CgFormattedString +import org.utbot.framework.codegen.domain.models.CgFrameworkUtilMethod +import org.utbot.framework.codegen.domain.models.CgNestedClassesRegion +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgTestMethodCluster +import org.utbot.framework.codegen.domain.models.CgThisInstance +import org.utbot.framework.codegen.domain.models.CgThrowStatement +import org.utbot.framework.codegen.domain.models.CgTripleSlashMultilineComment +import org.utbot.framework.codegen.domain.models.CgTryCatch +import org.utbot.framework.codegen.domain.models.CgTypeCast +import org.utbot.framework.codegen.domain.models.CgUtilMethod +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.domain.models.CgWhileLoop + +interface CgVisitor { + fun visit(element: CgElement): R + + fun visit(element: CgClassFile): R + + fun visit(element: CgClass): R + + fun visit(element: CgClassBody): R + + fun visit(element: CgStaticsRegion): R + + fun visit(element: CgNestedClassesRegion<*>): R + fun visit(element: CgSimpleRegion<*>): R + fun visit(element: CgTestMethodCluster): R + fun visit(element: CgMethodsCluster): R + + fun visit(element: CgAuxiliaryClass): R + fun visit(element: CgUtilMethod): R + + // Methods + fun visit(element: CgMethod): R + fun visit(element: CgTestMethod): R + fun visit(element: CgErrorTestMethod): R + fun visit(element: CgParameterizedTestDataProviderMethod): R + fun visit(element: CgFrameworkUtilMethod): R + + // Annotations + fun visit(element: CgCommentedAnnotation): R + fun visit(element: CgSingleArgAnnotation): R + fun visit(element: CgMultipleArgsAnnotation): R + fun visit(element: CgNamedAnnotationArgument): R + fun visit(element: CgArrayAnnotationArgument): R + + // Comments + fun visit(element: CgComment): R + fun visit(element: CgSingleLineComment): R + fun visit(element: CgAbstractMultilineComment): R + fun visit(element: CgTripleSlashMultilineComment): R + fun visit(element: CgMultilineComment): R + fun visit(element: CgDocumentationComment): R + + // Comment statements + fun visit(element: CgDocPreTagStatement): R + fun visit(element: CgCustomTagStatement): R + fun visit(element: CgDocCodeStmt): R + fun visit(element: CgDocRegularStmt): R + fun visit(element: CgDocRegularLineStmt): R + fun visit(element: CgDocClassLinkStmt): R + fun visit(element: CgDocMethodLinkStmt): R + + // Any block + // IMPORTANT: there is no line separator in the end by default + // because some blocks have it (like loops) but some do not (like try .. catch, if .. else etc) + fun visit(block: List, printNextLine: Boolean = false): R + + // Anonymous function (lambda) + fun visit(element: CgAnonymousFunction): R + + // Return statement + fun visit(element: CgReturnStatement): R + + // Array element access + fun visit(element: CgArrayElementAccess): R + + // Loop conditions + fun visit(element: CgComparison): R + fun visit(element: CgLessThan): R + fun visit(element: CgGreaterThan): R + fun visit(element: CgEqualTo): R + + // Increment and decrement + fun visit(element: CgIncrement): R + fun visit(element: CgDecrement): R + + fun visit(element: CgErrorWrapper): R + + // Try-catch + fun visit(element: CgTryCatch): R + + //Simple block + fun visit(element: CgInnerBlock): R + + // Loops + fun visit(element: CgLoop): R + fun visit(element: CgForLoop): R + fun visit(element: CgWhileLoop): R + fun visit(element: CgDoWhileLoop): R + + // Control statements + fun visit(element: CgBreakStatement): R + fun visit(element: CgContinueStatement): R + + // Variable declaration + fun visit(element: CgDeclaration): R + + // Field declaration + fun visit(element: CgFieldDeclaration): R + + // Variable assignment + fun visit(element: CgAssignment): R + + // Expressions + fun visit(element: CgExpression): R + + // Type cast + fun visit(element: CgTypeCast): R + + // isInstance check + fun visit(element: CgIsInstance): R + + // This instance + fun visit(element: CgThisInstance): R + + // Variables + fun visit(element: CgVariable): R + + // Not-null assertion + + fun visit(element: CgNotNullAssertion): R + + // Method parameters + fun visit(element: CgParameterDeclaration): R + + // Primitive and String literals + fun visit(element: CgLiteral): R + + // Non-static runnable like this::toString or (new Object())::toString etc + fun visit(element: CgNonStaticRunnable): R + // Static runnable like Random::nextRandomInt etc + fun visit(element: CgStaticRunnable): R + + // Array allocation + fun visit(element: CgAllocateArray): R + fun visit(element: CgAllocateInitializedArray): R + fun visit(element: CgArrayInitializer): R + + // Spread operator + fun visit(element: CgSpread): R + + // Formatted string + fun visit(element: CgFormattedString): R + + // Enum constant + fun visit(element: CgEnumConstantAccess): R + + // Property access + fun visit(element: CgAbstractFieldAccess): R + fun visit(element: CgFieldAccess): R + fun visit(element: CgStaticFieldAccess): R + + // Conditional statement + + fun visit(element: CgIfStatement): R + fun visit(element: CgSwitchCaseLabel): R + fun visit(element: CgSwitchCase): R + + // Binary logical operators + + fun visit(element: CgLogicalAnd): R + fun visit(element: CgLogicalOr): R + + // Acquisition of array length, e.g. args.length + fun visit(element: CgGetLength): R + + // Acquisition of java or kotlin class, e.g. MyClass.class in Java, MyClass::class.java in Kotlin or MyClass::class for Kotlin classes + fun visit(element: CgGetJavaClass): R + fun visit(element: CgGetKotlinClass): R + + // Executable calls + fun visit(element: CgStatementExecutableCall): R + fun visit(element: CgExecutableCall): R + fun visit(element: CgConstructorCall): R + fun visit(element: CgMethodCall): R + + // Throw statement + fun visit(element: CgThrowStatement): R + + // Empty line + fun visit(element: CgEmptyLine): R + + fun visit(element: CgForEachLoop): R +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/UtilMethodRenderer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/UtilMethodRenderer.kt new file mode 100644 index 0000000000..8052b907f1 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/renderer/UtilMethodRenderer.kt @@ -0,0 +1,1506 @@ +package org.utbot.framework.codegen.renderer + +import org.utbot.framework.codegen.domain.StaticImport +import org.utbot.framework.codegen.domain.builtin.TestClassUtilMethodProvider +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.tree.importIfNeeded +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.MockFramework +import org.utbot.framework.plugin.api.util.fieldClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.utils.UtilMethodProvider +import java.lang.invoke.CallSite +import java.lang.invoke.LambdaMetafactory +import java.lang.invoke.MethodHandle +import java.lang.invoke.MethodHandles +import java.lang.invoke.MethodType +import java.lang.reflect.Field +import java.lang.reflect.Method +import java.lang.reflect.Modifier +import java.util.Arrays +import java.util.Objects +import java.util.stream.Collectors + +private enum class Visibility(val text: String) { + PRIVATE("private"), + @Suppress("unused") + PROTECTED("protected"), + PUBLIC("public"); + + infix fun by(language: CodegenLanguage): String { + if (this == PUBLIC && language == CodegenLanguage.KOTLIN) { + // public is default in Kotlin + return "" + } + return "$text " + } +} + +// TODO: This method may throw an exception that will crash rendering. +// TODO: Add isolation on rendering: https://github.com/UnitTestBot/UTBotJava/issues/853 +internal fun UtilMethodProvider.utilMethodTextById( + id: MethodId, + mockFrameworkUsed: Boolean, + mockFramework: MockFramework, + codegenLanguage: CodegenLanguage +): String { + // If util methods are declared in the test class, then they are private. Otherwise, they are public. + val visibility = if (this is TestClassUtilMethodProvider) Visibility.PRIVATE else Visibility.PUBLIC + return with(this) { + when (id) { + getUnsafeInstanceMethodId -> getUnsafeInstance(visibility, codegenLanguage) + createInstanceMethodId -> createInstance(visibility, codegenLanguage) + createArrayMethodId -> createArray(visibility, codegenLanguage) + setFieldMethodId -> setField(visibility, codegenLanguage) + setStaticFieldMethodId -> setStaticField(visibility, codegenLanguage) + getFieldValueMethodId -> getFieldValue(visibility, codegenLanguage) + getStaticFieldValueMethodId -> getStaticFieldValue(visibility, codegenLanguage) + getEnumConstantByNameMethodId -> getEnumConstantByName(visibility, codegenLanguage) + deepEqualsMethodId -> deepEquals(visibility, codegenLanguage, mockFrameworkUsed, mockFramework) + arraysDeepEqualsMethodId -> arraysDeepEquals(visibility, codegenLanguage) + iterablesDeepEqualsMethodId -> iterablesDeepEquals(visibility, codegenLanguage) + streamsDeepEqualsMethodId -> streamsDeepEquals(visibility, codegenLanguage) + mapsDeepEqualsMethodId -> mapsDeepEquals(visibility, codegenLanguage) + hasCustomEqualsMethodId -> hasCustomEquals(visibility, codegenLanguage) + getArrayLengthMethodId -> getArrayLength(visibility, codegenLanguage) + consumeBaseStreamMethodId -> consumeBaseStream(visibility, codegenLanguage) + buildStaticLambdaMethodId -> buildStaticLambda(visibility, codegenLanguage) + buildLambdaMethodId -> buildLambda(visibility, codegenLanguage) + // the following methods are used only by other util methods, so they can always be private + getLookupInMethodId -> getLookupIn(codegenLanguage) + getLambdaCapturedArgumentTypesMethodId -> getLambdaCapturedArgumentTypes(codegenLanguage) + getLambdaCapturedArgumentValuesMethodId -> getLambdaCapturedArgumentValues(codegenLanguage) + getInstantiatedMethodTypeMethodId -> getInstantiatedMethodType(codegenLanguage) + getLambdaMethodMethodId -> getLambdaMethod(codegenLanguage) + getSingleAbstractMethodMethodId -> getSingleAbstractMethod(codegenLanguage) + else -> error("Unknown util method for class $this: $id") + } + } +} + +// TODO: This method may throw an exception that will crash rendering. +// TODO: Add isolation on rendering: https://github.com/UnitTestBot/UTBotJava/issues/853 +internal fun UtilMethodProvider.auxiliaryClassTextById(id: ClassId, codegenLanguage: CodegenLanguage): String = + with(this) { + when (id) { + capturedArgumentClassId -> capturedArgumentClass(codegenLanguage) + else -> error("Unknown auxiliary class: $id") + } + } + +private fun getEnumConstantByName(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static Object getEnumConstantByName(Class enumClass, String name) throws IllegalAccessException { + java.lang.reflect.Field[] fields = enumClass.getDeclaredFields(); + for (java.lang.reflect.Field field : fields) { + String fieldName = field.getName(); + if (field.isEnumConstant() && fieldName.equals(name)) { + field.setAccessible(true); + + return field.get(null); + } + } + + return null; + } + """ + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun getEnumConstantByName(enumClass: Class<*>, name: String): kotlin.Any? { + val fields: kotlin.Array = enumClass.declaredFields + for (field in fields) { + val fieldName = field.name + if (field.isEnumConstant && fieldName == name) { + field.isAccessible = true + + return field.get(null) + } + } + + return null + } + """ + } + }.trimIndent() + +private fun getFieldRetrievingBlock(language : CodegenLanguage, fullClassName : String, fieldName : String, resultName : String): String { + val methodName = "methodForGetDeclaredFields${System.nanoTime()}" + val fieldsName = "allFieldsFromFieldClass${System.nanoTime()}" + return when (language) { + CodegenLanguage.JAVA -> + """ + java.lang.reflect.Method $methodName = java.lang.Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + $methodName.setAccessible(true); + java.lang.reflect.Field[] $fieldsName = (java.lang.reflect.Field[]) $methodName.invoke($fullClassName.class, false); + $resultName = java.util.Arrays.stream($fieldsName).filter(field1 -> field1.getName().equals("$fieldName")).findFirst().get(); + """ + + CodegenLanguage.KOTLIN -> + """ + val $methodName = Class::class.java.getDeclaredMethod("getDeclaredFields0", Boolean::class.java) + $methodName.isAccessible = true + val $fieldsName = $methodName.invoke($fullClassName::class.java, false) as kotlin.Array + $resultName = $fieldsName.filter { field1: java.lang.reflect.Field -> field1.name == "$fieldName" }.first() + """ + } +} +private fun getStaticFieldValue(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static Object getStaticFieldValue(Class clazz, String fieldName) throws IllegalAccessException, NoSuchFieldException { + java.lang.reflect.Field field; + Class originClass = clazz; + do { + try { + field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + + java.lang.reflect.Field modifiersField; + ${getFieldRetrievingBlock(language, "java.lang.reflect.Field", "modifiers", "modifiersField")} + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); + + return field.get(null); + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } catch (NoSuchMethodException e2) { + e2.printStackTrace(); + } catch (java.lang.reflect.InvocationTargetException e3) { + e3.printStackTrace(); + } + } while (clazz != null); + + throw new NoSuchFieldException("Field '" + fieldName + "' not found on class " + originClass); + } + """ + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun getStaticFieldValue(clazz: Class<*>, fieldName: String): kotlin.Any? { + var currentClass: Class<*>? = clazz + var field: java.lang.reflect.Field + do { + try { + field = currentClass!!.getDeclaredField(fieldName) + field.isAccessible = true + + val modifiersField: java.lang.reflect.Field + ${getFieldRetrievingBlock(language, "java.lang.reflect.Field", "modifiers", "modifiersField")} + + modifiersField.isAccessible = true + modifiersField.setInt(field, field.modifiers and java.lang.reflect.Modifier.FINAL.inv()) + + return field.get(null) + } catch (e: NoSuchFieldException) { + currentClass = currentClass!!.superclass + } + } while (currentClass != null) + + throw NoSuchFieldException("Field '" + fieldName + "' not found on class " + clazz) + } + """ + } + }.trimIndent() + +private fun getFieldValue(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static Object getFieldValue(Object obj, String fieldClassName, String fieldName) throws ClassNotFoundException, NoSuchMethodException, java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchFieldException { + Class clazz = Class.forName(fieldClassName); + java.lang.reflect.Field field = clazz.getDeclaredField(fieldName); + + field.setAccessible(true); + + java.lang.reflect.Field modifiersField; + ${getFieldRetrievingBlock(language, "java.lang.reflect.Field", "modifiers", "modifiersField")} + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); + + return field.get(obj); + } + """ + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun getFieldValue(any: kotlin.Any, fieldClassName: String, fieldName: String): kotlin.Any? { + val clazz: Class<*> = Class.forName(fieldClassName) + val field: java.lang.reflect.Field = clazz.getDeclaredField(fieldName) + + field.isAccessible = true + + val modifiersField: java.lang.reflect.Field + ${getFieldRetrievingBlock(language, "java.lang.reflect.Field", "modifiers", "modifiersField")} + modifiersField.isAccessible = true + modifiersField.setInt(field, field.modifiers and java.lang.reflect.Modifier.FINAL.inv()) + + return field.get(any) + } + """ + } + }.trimIndent() + +private fun setStaticField(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static void setStaticField(Class clazz, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { + java.lang.reflect.Field field; + + try { + do { + try { + field = clazz.getDeclaredField(fieldName); + } catch (Exception e) { + clazz = clazz.getSuperclass(); + field = null; + } + } while (field == null); + + java.lang.reflect.Field modifiersField; + ${getFieldRetrievingBlock(language, "java.lang.reflect.Field", "modifiers", "modifiersField")} + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); + + field.setAccessible(true); + field.set(null, fieldValue); + } + catch(java.lang.reflect.InvocationTargetException e){ + e.printStackTrace(); + } + catch(NoSuchMethodException e2) { + e2.printStackTrace(); + } + } + """ + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun setStaticField(defaultClass: Class<*>, fieldName: String, fieldValue: kotlin.Any?) { + var field: java.lang.reflect.Field? + var clazz = defaultClass + + do { + try { + field = clazz.getDeclaredField(fieldName) + } catch (e: Exception) { + clazz = clazz.superclass + field = null + } + } while (field == null) + + val modifiersField: java.lang.reflect.Field + ${getFieldRetrievingBlock(language, "java.lang.reflect.Field", "modifiers", "modifiersField")} + modifiersField.isAccessible = true + modifiersField.setInt(field, field.modifiers and java.lang.reflect.Modifier.FINAL.inv()) + + field.isAccessible = true + field.set(null, fieldValue) + } + """ + } + }.trimIndent() + +private fun setField(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static void setField(Object object, String fieldClassName, String fieldName, Object fieldValue) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, java.lang.reflect.InvocationTargetException { + Class clazz = Class.forName(fieldClassName); + java.lang.reflect.Field field = clazz.getDeclaredField(fieldName); + + java.lang.reflect.Field modifiersField; + ${getFieldRetrievingBlock(language, "java.lang.reflect.Field", "modifiers", "modifiersField")} + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); + + field.setAccessible(true); + field.set(object, fieldValue); + } + """ + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun setField(any: kotlin.Any, fieldClassName: String, fieldName: String, fieldValue: kotlin.Any?) { + val clazz: Class<*> = Class.forName(fieldClassName) + val field: java.lang.reflect.Field = clazz.getDeclaredField(fieldName) + + val modifiersField: java.lang.reflect.Field + ${getFieldRetrievingBlock(language, "java.lang.reflect.Field", "modifiers", "modifiersField")} + modifiersField.isAccessible = true + modifiersField.setInt(field, field.modifiers and java.lang.reflect.Modifier.FINAL.inv()) + + field.isAccessible = true + field.set(any, fieldValue) + } + """ + } + }.trimIndent() + +private fun createArray(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static Object[] createArray(String className, int length, Object... values) throws ClassNotFoundException { + Object array = java.lang.reflect.Array.newInstance(Class.forName(className), length); + + for (int i = 0; i < values.length; i++) { + java.lang.reflect.Array.set(array, i, values[i]); + } + + return (Object[]) array; + } + """ + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun createArray( + className: String, + length: Int, + vararg values: kotlin.Any + ): kotlin.Array { + val array: kotlin.Any = java.lang.reflect.Array.newInstance(Class.forName(className), length) + + for (i in values.indices) { + java.lang.reflect.Array.set(array, i, values[i]) + } + + return array as kotlin.Array + } + """ + } + }.trimIndent() + +private fun createInstance(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static Object createInstance(String className) throws Exception { + Class clazz = Class.forName(className); + return Class.forName("sun.misc.Unsafe").getDeclaredMethod("allocateInstance", Class.class) + .invoke(getUnsafeInstance(), clazz); + } + """ + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun createInstance(className: String): kotlin.Any { + val clazz: Class<*> = Class.forName(className) + return Class.forName("sun.misc.Unsafe").getDeclaredMethod("allocateInstance", Class::class.java) + .invoke(getUnsafeInstance(), clazz) + } + """ + } + }.trimIndent() + +private fun getUnsafeInstance(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static Object getUnsafeInstance() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { + java.lang.reflect.Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe"); + f.setAccessible(true); + return f.get(null); + } + """ + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun getUnsafeInstance(): kotlin.Any? { + val f: java.lang.reflect.Field = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe") + f.isAccessible = true + return f[null] + } + """ + } + }.trimIndent() + +/** + * Mockito mock uses its own equals which we cannot rely on + */ +private fun isMockCondition(mockFrameworkUsed: Boolean, mockFramework: MockFramework): String { + if (!mockFrameworkUsed) return "" + + return when (mockFramework) { + MockFramework.MOCKITO -> " && !org.mockito.Mockito.mockingDetails(o1).isMock()" + // in case we will add any other mock frameworks, newer Kotlin compiler versions + // will report a non-exhaustive 'when', so we will not forget to support them here as well + } +} + +private fun deepEquals( + visibility: Visibility, + language: CodegenLanguage, + mockFrameworkUsed: Boolean, + mockFramework: MockFramework +): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + static class FieldsPair { + final Object o1; + final Object o2; + + public FieldsPair(Object o1, Object o2) { + this.o1 = o1; + this.o2 = o2; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FieldsPair that = (FieldsPair) o; + return java.util.Objects.equals(o1, that.o1) && java.util.Objects.equals(o2, that.o2); + } + + @Override + public int hashCode() { + return java.util.Objects.hash(o1, o2); + } + } + + ${visibility by language}static boolean deepEquals(Object o1, Object o2) { + return deepEquals(o1, o2, new java.util.HashSet<>()); + } + + private static boolean deepEquals(Object o1, Object o2, java.util.Set visited) { + visited.add(new FieldsPair(o1, o2)); + + if (o1 == o2) { + return true; + } + + if (o1 == null || o2 == null) { + return false; + } + + if (o1 instanceof Iterable) { + if (!(o2 instanceof Iterable)) { + return false; + } + + return iterablesDeepEquals((Iterable) o1, (Iterable) o2, visited); + } + + if (o2 instanceof Iterable) { + return false; + } + + if (o1 instanceof java.util.stream.BaseStream) { + if (!(o2 instanceof java.util.stream.BaseStream)) { + return false; + } + + return streamsDeepEquals((java.util.stream.BaseStream) o1, (java.util.stream.BaseStream) o2, visited); + } + + if (o2 instanceof java.util.stream.BaseStream) { + return false; + } + + if (o1 instanceof java.util.Map) { + if (!(o2 instanceof java.util.Map)) { + return false; + } + + return mapsDeepEquals((java.util.Map) o1, (java.util.Map) o2, visited); + } + + if (o2 instanceof java.util.Map) { + return false; + } + + Class firstClass = o1.getClass(); + if (firstClass.isArray()) { + if (!o2.getClass().isArray()) { + return false; + } + + // Primitive arrays should not appear here + return arraysDeepEquals(o1, o2, visited); + } + + // common classes + + // check if class has custom equals method (including wrappers and strings) + // It is very important to check it here but not earlier because iterables and maps also have custom equals + // based on elements equals + if (hasCustomEquals(firstClass)${isMockCondition(mockFrameworkUsed, mockFramework)}) { + return o1.equals(o2); + } + + // common classes without custom equals, use comparison by fields + final java.util.List fields = new java.util.ArrayList<>(); + while (firstClass != Object.class) { + fields.addAll(java.util.Arrays.asList(firstClass.getDeclaredFields())); + // Interface should not appear here + firstClass = firstClass.getSuperclass(); + } + + for (java.lang.reflect.Field field : fields) { + field.setAccessible(true); + try { + final Object field1 = field.get(o1); + final Object field2 = field.get(o2); + if (!visited.contains(new FieldsPair(field1, field2)) && !deepEquals(field1, field2, visited)) { + return false; + } + } catch (IllegalArgumentException e) { + return false; + } catch (IllegalAccessException e) { + // should never occur because field was set accessible + return false; + } + } + + return true; + } + """.trimIndent() + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun deepEquals(o1: kotlin.Any?, o2: kotlin.Any?): Boolean = deepEquals(o1, o2, hashSetOf()) + + private fun deepEquals( + o1: kotlin.Any?, + o2: kotlin.Any?, + visited: kotlin.collections.MutableSet> + ): Boolean { + visited += o1 to o2 + + if (o1 === o2) return true + + if (o1 == null || o2 == null) return false + + if (o1 is kotlin.collections.Iterable<*>) { + return if (o2 !is kotlin.collections.Iterable<*>) false else iterablesDeepEquals(o1, o2, visited) + } + + if (o2 is kotlin.collections.Iterable<*>) return false + + if (o1 is java.util.stream.BaseStream<*, *>) { + return if (o2 !is java.util.stream.BaseStream<*, *>) false else streamsDeepEquals(o1, o2, visited) + } + + if (o2 is java.util.stream.BaseStream<*, *>) return false + + if (o1 is kotlin.collections.Map<*, *>) { + return if (o2 !is kotlin.collections.Map<*, *>) false else mapsDeepEquals(o1, o2, visited) + } + + if (o2 is kotlin.collections.Map<*, *>) return false + + var firstClass: Class<*> = o1.javaClass + if (firstClass.isArray) { + return if (!o2.javaClass.isArray) { + false + } else { + arraysDeepEquals(o1, o2, visited) + } + } + + // check if class has custom equals method (including wrappers and strings) + // It is very important to check it here but not earlier because iterables and maps also have custom equals + // based on elements equals + if (hasCustomEquals(firstClass)${isMockCondition(mockFrameworkUsed, mockFramework)}) { + return o1 == o2 + } + + // common classes without custom equals, use comparison by fields + val fields: kotlin.collections.MutableList = mutableListOf() + while (firstClass != kotlin.Any::class.java) { + fields += listOf(*firstClass.declaredFields) + // Interface should not appear here + firstClass = firstClass.superclass + } + + for (field in fields) { + field.isAccessible = true + try { + val field1 = field[o1] + val field2 = field[o2] + if ((field1 to field2) !in visited && !deepEquals(field1, field2, visited)) return false + } catch (e: IllegalArgumentException) { + return false + } catch (e: IllegalAccessException) { + // should never occur + return false + } + } + + return true + } + """.trimIndent() + } + } + +private fun arraysDeepEquals(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static boolean arraysDeepEquals(Object arr1, Object arr2, java.util.Set visited) { + final int length = java.lang.reflect.Array.getLength(arr1); + if (length != java.lang.reflect.Array.getLength(arr2)) { + return false; + } + + for (int i = 0; i < length; i++) { + if (!deepEquals(java.lang.reflect.Array.get(arr1, i), java.lang.reflect.Array.get(arr2, i), visited)) { + return false; + } + } + + return true; + } + """.trimIndent() + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun arraysDeepEquals( + arr1: kotlin.Any?, + arr2: kotlin.Any?, + visited: kotlin.collections.MutableSet> + ): Boolean { + val size = java.lang.reflect.Array.getLength(arr1) + if (size != java.lang.reflect.Array.getLength(arr2)) return false + + for (i in 0 until size) { + if (!deepEquals(java.lang.reflect.Array.get(arr1, i), java.lang.reflect.Array.get(arr2, i), visited)) { + return false + } + } + + return true + } + """.trimIndent() + } + } + +private fun iterablesDeepEquals(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static boolean iterablesDeepEquals(Iterable i1, Iterable i2, java.util.Set visited) { + final java.util.Iterator firstIterator = i1.iterator(); + final java.util.Iterator secondIterator = i2.iterator(); + while (firstIterator.hasNext() && secondIterator.hasNext()) { + if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) { + return false; + } + } + + if (firstIterator.hasNext()) { + return false; + } + + return !secondIterator.hasNext(); + } + """.trimIndent() + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun iterablesDeepEquals( + i1: Iterable<*>, + i2: Iterable<*>, + visited: kotlin.collections.MutableSet> + ): Boolean { + val firstIterator = i1.iterator() + val secondIterator = i2.iterator() + while (firstIterator.hasNext() && secondIterator.hasNext()) { + if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) return false + } + + return if (firstIterator.hasNext()) false else !secondIterator.hasNext() + } + """.trimIndent() + } + } + +private fun streamsDeepEquals(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static boolean streamsDeepEquals( + java.util.stream.BaseStream s1, + java.util.stream.BaseStream s2, + java.util.Set visited + ) { + final java.util.Iterator firstIterator = s1.iterator(); + final java.util.Iterator secondIterator = s2.iterator(); + while (firstIterator.hasNext() && secondIterator.hasNext()) { + if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) { + return false; + } + } + + if (firstIterator.hasNext()) { + return false; + } + + return !secondIterator.hasNext(); + } + """.trimIndent() + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun streamsDeepEquals( + s1: java.util.stream.BaseStream<*, *>, + s2: java.util.stream.BaseStream<*, *>, + visited: kotlin.collections.MutableSet> + ): Boolean { + val firstIterator = s1.iterator() + val secondIterator = s2.iterator() + while (firstIterator.hasNext() && secondIterator.hasNext()) { + if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) return false + } + + return if (firstIterator.hasNext()) false else !secondIterator.hasNext() + } + """.trimIndent() + } + } + +private fun mapsDeepEquals(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static boolean mapsDeepEquals( + java.util.Map m1, + java.util.Map m2, + java.util.Set visited + ) { + final java.util.Iterator> firstIterator = m1.entrySet().iterator(); + final java.util.Iterator> secondIterator = m2.entrySet().iterator(); + while (firstIterator.hasNext() && secondIterator.hasNext()) { + final java.util.Map.Entry firstEntry = firstIterator.next(); + final java.util.Map.Entry secondEntry = secondIterator.next(); + + if (!deepEquals(firstEntry.getKey(), secondEntry.getKey(), visited)) { + return false; + } + + if (!deepEquals(firstEntry.getValue(), secondEntry.getValue(), visited)) { + return false; + } + } + + if (firstIterator.hasNext()) { + return false; + } + + return !secondIterator.hasNext(); + } + """.trimIndent() + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun mapsDeepEquals( + m1: kotlin.collections.Map<*, *>, + m2: kotlin.collections.Map<*, *>, + visited: kotlin.collections.MutableSet> + ): Boolean { + val firstIterator = m1.entries.iterator() + val secondIterator = m2.entries.iterator() + while (firstIterator.hasNext() && secondIterator.hasNext()) { + val firstEntry = firstIterator.next() + val secondEntry = secondIterator.next() + + if (!deepEquals(firstEntry.key, secondEntry.key, visited)) return false + + if (!deepEquals(firstEntry.value, secondEntry.value, visited)) return false + } + + return if (firstIterator.hasNext()) false else !secondIterator.hasNext() + } + """.trimIndent() + } + } + +private fun hasCustomEquals(visibility: Visibility, language: CodegenLanguage): String = + when (language) { + CodegenLanguage.JAVA -> { + """ + ${visibility by language}static boolean hasCustomEquals(Class clazz) { + while (!Object.class.equals(clazz)) { + try { + clazz.getDeclaredMethod("equals", Object.class); + return true; + } catch (Exception e) { + // Interface should not appear here + clazz = clazz.getSuperclass(); + } + } + + return false; + } + """.trimIndent() + } + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun hasCustomEquals(clazz: Class<*>): Boolean { + var c = clazz + while (kotlin.Any::class.java != c) { + try { + c.getDeclaredMethod("equals", kotlin.Any::class.java) + return true + } catch (e: Exception) { + // Interface should not appear here + c = c.superclass + } + } + return false + } + """.trimIndent() + } + } + +private fun getArrayLength(visibility: Visibility, language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + ${visibility by language}static int getArrayLength(Object arr) { + return java.lang.reflect.Array.getLength(arr); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + ${visibility by language}fun getArrayLength(arr: kotlin.Any?): Int = java.lang.reflect.Array.getLength(arr) + """.trimIndent() + } + +private fun consumeBaseStream(visibility: Visibility, language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + ${visibility by language}static void consumeBaseStream(java.util.stream.BaseStream stream) { + stream.iterator().forEachRemaining(value -> {}); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> { + """ + ${visibility by language}fun consumeBaseStream(stream: java.util.stream.BaseStream<*, *>) { + stream.iterator().forEachRemaining {} + } + """.trimIndent() + } + } + +private fun buildStaticLambda(visibility: Visibility, language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * @param samType a class representing a functional interface. + * @param declaringClass a class where the lambda is declared. + * @param lambdaName a name of the synthetic method that represents a lambda. + * @param capturedArguments a vararg containing {@link CapturedArgument} instances representing + * values that the given lambda has captured. + * @return an {@link Object} that represents an instance of the given functional interface {@code samType} + * and implements its single abstract method with the behavior of the given lambda. + */ + ${visibility by language}static Object buildStaticLambda( + Class samType, + Class declaringClass, + String lambdaName, + CapturedArgument... capturedArguments + ) throws Throwable { + // Create lookup for class where the lambda is declared in. + java.lang.invoke.MethodHandles.Lookup caller = getLookupIn(declaringClass); + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + java.lang.reflect.Method singleAbstractMethod = getSingleAbstractMethod(samType); + String invokedName = singleAbstractMethod.getName(); + // Method type of single abstract method of the target functional interface. + java.lang.invoke.MethodType samMethodType = java.lang.invoke.MethodType.methodType(singleAbstractMethod.getReturnType(), singleAbstractMethod.getParameterTypes()); + + java.lang.reflect.Method lambdaMethod = getLambdaMethod(declaringClass, lambdaName); + lambdaMethod.setAccessible(true); + java.lang.invoke.MethodType lambdaMethodType = java.lang.invoke.MethodType.methodType(lambdaMethod.getReturnType(), lambdaMethod.getParameterTypes()); + java.lang.invoke.MethodHandle lambdaMethodHandle = caller.findStatic(declaringClass, lambdaName, lambdaMethodType); + + Class[] capturedArgumentTypes = getLambdaCapturedArgumentTypes(capturedArguments); + java.lang.invoke.MethodType invokedType = java.lang.invoke.MethodType.methodType(samType, capturedArgumentTypes); + java.lang.invoke.MethodType instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes); + + // Create a CallSite for the given lambda. + java.lang.invoke.CallSite site = java.lang.invoke.LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType); + + Object[] capturedValues = getLambdaCapturedArgumentValues(capturedArguments); + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + java.lang.invoke.MethodHandle handle = site.getTarget(); + return handle.invokeWithArguments(capturedValues); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param samType a class representing a functional interface. + * @param declaringClass a class where the lambda is declared. + * @param lambdaName a name of the synthetic method that represents a lambda. + * @param capturedArguments a vararg containing [CapturedArgument] instances representing + * values that the given lambda has captured. + * @return an [Any] that represents an instance of the given functional interface `samType` + * and implements its single abstract method with the behavior of the given lambda. + */ + ${visibility by language}fun buildStaticLambda( + samType: Class<*>, + declaringClass: Class<*>, + lambdaName: String, + vararg capturedArguments: CapturedArgument + ): Any { + // Create lookup for class where the lambda is declared in. + val caller = getLookupIn(declaringClass) + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + val singleAbstractMethod = getSingleAbstractMethod(samType) + val invokedName = singleAbstractMethod.name + // Method type of single abstract method of the target functional interface. + val samMethodType = java.lang.invoke.MethodType.methodType(singleAbstractMethod.returnType, singleAbstractMethod.parameterTypes) + + val lambdaMethod = getLambdaMethod(declaringClass, lambdaName) + lambdaMethod.isAccessible = true + val lambdaMethodType = java.lang.invoke.MethodType.methodType(lambdaMethod.returnType, lambdaMethod.parameterTypes) + val lambdaMethodHandle = caller.findStatic(declaringClass, lambdaName, lambdaMethodType) + + val capturedArgumentTypes = getLambdaCapturedArgumentTypes(*capturedArguments) + val invokedType = java.lang.invoke.MethodType.methodType(samType, capturedArgumentTypes) + val instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes) + + // Create a CallSite for the given lambda. + val site = java.lang.invoke.LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType + ) + val capturedValues = getLambdaCapturedArgumentValues(*capturedArguments) + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + val handle = site.target + return handle.invokeWithArguments(*capturedValues) + } + """.trimIndent() + } + +private fun buildLambda(visibility: Visibility, language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * @param samType a class representing a functional interface. + * @param declaringClass a class where the lambda is declared. + * @param lambdaName a name of the synthetic method that represents a lambda. + * @param capturedReceiver an object of {@code declaringClass} that is captured by the lambda. + * When the synthetic lambda method is not static, it means that the lambda captures an instance + * of the class it is declared in. + * @param capturedArguments a vararg containing {@link CapturedArgument} instances representing + * values that the given lambda has captured. + * @return an {@link Object} that represents an instance of the given functional interface {@code samType} + * and implements its single abstract method with the behavior of the given lambda. + */ + ${visibility by language}static Object buildLambda( + Class samType, + Class declaringClass, + String lambdaName, + Object capturedReceiver, + CapturedArgument... capturedArguments + ) throws Throwable { + // Create lookup for class where the lambda is declared in. + java.lang.invoke.MethodHandles.Lookup caller = getLookupIn(declaringClass); + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + java.lang.reflect.Method singleAbstractMethod = getSingleAbstractMethod(samType); + String invokedName = singleAbstractMethod.getName(); + // Method type of single abstract method of the target functional interface. + java.lang.invoke.MethodType samMethodType = java.lang.invoke.MethodType.methodType(singleAbstractMethod.getReturnType(), singleAbstractMethod.getParameterTypes()); + + java.lang.reflect.Method lambdaMethod = getLambdaMethod(declaringClass, lambdaName); + lambdaMethod.setAccessible(true); + java.lang.invoke.MethodType lambdaMethodType = java.lang.invoke.MethodType.methodType(lambdaMethod.getReturnType(), lambdaMethod.getParameterTypes()); + java.lang.invoke.MethodHandle lambdaMethodHandle = caller.findVirtual(declaringClass, lambdaName, lambdaMethodType); + + Class[] capturedArgumentTypes = getLambdaCapturedArgumentTypes(capturedArguments); + java.lang.invoke.MethodType invokedType = java.lang.invoke.MethodType.methodType(samType, capturedReceiver.getClass(), capturedArgumentTypes); + java.lang.invoke.MethodType instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes); + + // Create a CallSite for the given lambda. + java.lang.invoke.CallSite site = java.lang.invoke.LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType); + + Object[] capturedArgumentValues = getLambdaCapturedArgumentValues(capturedArguments); + + // This array will contain the value of captured receiver + // (`this` instance of class where the lambda is declared) + // and the values of captured arguments. + Object[] capturedValues = new Object[capturedArguments.length + 1]; + + // Setting the captured receiver value. + capturedValues[0] = capturedReceiver; + + // Setting the captured argument values. + System.arraycopy(capturedArgumentValues, 0, capturedValues, 1, capturedArgumentValues.length); + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + java.lang.invoke.MethodHandle handle = site.getTarget(); + return handle.invokeWithArguments(capturedValues); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param samType a class representing a functional interface. + * @param declaringClass a class where the lambda is declared. + * @param lambdaName a name of the synthetic method that represents a lambda. + * @param capturedReceiver an object of `declaringClass` that is captured by the lambda. + * When the synthetic lambda method is not static, it means that the lambda captures an instance + * of the class it is declared in. + * @param capturedArguments a vararg containing [CapturedArgument] instances representing + * values that the given lambda has captured. + * @return an [Any] that represents an instance of the given functional interface `samType` + * and implements its single abstract method with the behavior of the given lambda. + */ + ${visibility by language}fun buildLambda( + samType: Class<*>, + declaringClass: Class<*>, + lambdaName: String, + capturedReceiver: Any, + vararg capturedArguments: CapturedArgument + ): Any { + // Create lookup for class where the lambda is declared in. + val caller = getLookupIn(declaringClass) + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + val singleAbstractMethod = getSingleAbstractMethod(samType) + val invokedName = singleAbstractMethod.name + // Method type of single abstract method of the target functional interface. + val samMethodType = java.lang.invoke.MethodType.methodType(singleAbstractMethod.returnType, singleAbstractMethod.parameterTypes) + + val lambdaMethod = getLambdaMethod(declaringClass, lambdaName) + lambdaMethod.isAccessible = true + val lambdaMethodType = java.lang.invoke.MethodType.methodType(lambdaMethod.returnType, lambdaMethod.parameterTypes) + val lambdaMethodHandle = caller.findVirtual(declaringClass, lambdaName, lambdaMethodType) + + val capturedArgumentTypes = getLambdaCapturedArgumentTypes(*capturedArguments) + val invokedType = java.lang.invoke.MethodType.methodType(samType, capturedReceiver.javaClass, *capturedArgumentTypes) + val instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes) + + // Create a CallSite for the given lambda. + val site = java.lang.invoke.LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType + ) + val capturedValues = mutableListOf() + .apply { + add(capturedReceiver) + addAll(getLambdaCapturedArgumentValues(*capturedArguments)) + }.toTypedArray() + + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + val handle = site.target + return handle.invokeWithArguments(*capturedValues) + } + """.trimIndent() + } + +private fun getLookupIn(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * @param clazz a class to create lookup instance for. + * @return {@link java.lang.invoke.MethodHandles.Lookup} instance for the given {@code clazz}. + * It can be used, for example, to search methods of this {@code clazz}, even the {@code private} ones. + */ + private static java.lang.invoke.MethodHandles.Lookup getLookupIn(Class clazz) throws IllegalAccessException, NoSuchFieldException, java.lang.NoSuchMethodException, java.lang.reflect.InvocationTargetException { + java.lang.invoke.MethodHandles.Lookup lookup = java.lang.invoke.MethodHandles.lookup().in(clazz); + + // Allow lookup to access all members of declaringClass, including the private ones. + // For example, it is useful to access private synthetic methods representing lambdas. + + java.lang.reflect.Field allowedModes; + java.lang.reflect.Field allModesField; + ${getFieldRetrievingBlock(language, "java.lang.invoke.MethodHandles.Lookup", "allowedModes", "allowedModes")} + ${getFieldRetrievingBlock(language, "java.lang.invoke.MethodHandles.Lookup", "ALL_MODES", "allModesField")} + allModesField.setAccessible(true); + allowedModes.setAccessible(true); + allowedModes.setInt(lookup, (Integer) allModesField.get(null)); + + return lookup; + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param clazz a class to create lookup instance for. + * @return [java.lang.invoke.MethodHandles.Lookup] instance for the given [clazz]. + * It can be used, for example, to search methods of this [clazz], even the `private` ones. + */ + private fun getLookupIn(clazz: Class<*>): java.lang.invoke.MethodHandles.Lookup { + val lookup = java.lang.invoke.MethodHandles.lookup().`in`(clazz) + + // Allow lookup to access all members of declaringClass, including the private ones. + // For example, it is useful to access private synthetic methods representing lambdas. + val allowedModes: java.lang.reflect.Field + val allModesField: java.lang.reflect.Field + ${getFieldRetrievingBlock(language, "java.lang.invoke.MethodHandles.Lookup", "allowedModes", "allowedModes")} + ${getFieldRetrievingBlock(language, "java.lang.invoke.MethodHandles.Lookup", "ALL_MODES", "allModesField")} + allowedModes.isAccessible = true + allModesField.isAccessible = true + allowedModes.setInt(lookup, allModesField.get(null) as Int) + + return lookup + } + """.trimIndent() + } + +private fun getLambdaCapturedArgumentTypes(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * @param capturedArguments values captured by some lambda. Note that this argument does not contain + * the possibly captured instance of the class where the lambda is declared. + * It contains all the other captured values. They are represented as arguments of the synthetic method + * that the lambda is compiled into. Hence, the name of the argument. + * @return types of the given {@code capturedArguments}. + * These types are required to build {@code invokedType}, which represents + * the target functional interface with info about captured values' types. + * See {@link java.lang.invoke.LambdaMetafactory#metafactory} method documentation for more details on what {@code invokedType} is. + */ + private static Class[] getLambdaCapturedArgumentTypes(CapturedArgument... capturedArguments) { + Class[] capturedArgumentTypes = new Class[capturedArguments.length]; + for (int i = 0; i < capturedArguments.length; i++) { + capturedArgumentTypes[i] = capturedArguments[i].type; + } + return capturedArgumentTypes; + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param capturedArguments values captured by some lambda. Note that this argument does not contain + * the possibly captured instance of the class where the lambda is declared. + * It contains all the other captured values. They are represented as arguments of the synthetic method + * that the lambda is compiled into. Hence, the name of the argument. + * @return types of the given `capturedArguments`. + * These types are required to build `invokedType`, which represents + * the target functional interface with info about captured values' types. + * See [java.lang.invoke.LambdaMetafactory.metafactory] method documentation for more details on what `invokedType` is. + */ + private fun getLambdaCapturedArgumentTypes(vararg capturedArguments: CapturedArgument): Array> { + return capturedArguments + .map { it.type } + .toTypedArray() + } + """.trimIndent() + } + +private fun getLambdaCapturedArgumentValues(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * Obtain captured values to be used as captured arguments in the lambda call. + */ + private static Object[] getLambdaCapturedArgumentValues(CapturedArgument... capturedArguments) { + return java.util.Arrays.stream(capturedArguments) + .map(argument -> argument.value) + .toArray(); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * Obtain captured values to be used as captured arguments in the lambda call. + */ + private fun getLambdaCapturedArgumentValues(vararg capturedArguments: CapturedArgument): Array { + return capturedArguments + .map { it.value } + .toTypedArray() + } + """.trimIndent() + } + +private fun getInstantiatedMethodType(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * @param lambdaMethod {@link java.lang.reflect.Method} that represents a synthetic method for lambda. + * @param capturedArgumentTypes types of values captured by lambda. + * @return {@link java.lang.invoke.MethodType} that represents the value of argument {@code instantiatedMethodType} + * of method {@link java.lang.invoke.LambdaMetafactory#metafactory}. + */ + private static java.lang.invoke.MethodType getInstantiatedMethodType(java.lang.reflect.Method lambdaMethod, Class[] capturedArgumentTypes) { + // Types of arguments of synthetic method (representing lambda) without the types of captured values. + java.util.List> instantiatedMethodParamTypeList = java.util.Arrays.stream(lambdaMethod.getParameterTypes()) + .skip(capturedArgumentTypes.length) + .collect(java.util.stream.Collectors.toList()); + + // The same types, but stored in an array. + Class[] instantiatedMethodParamTypes = new Class[instantiatedMethodParamTypeList.size()]; + for (int i = 0; i < instantiatedMethodParamTypeList.size(); i++) { + instantiatedMethodParamTypes[i] = instantiatedMethodParamTypeList.get(i); + } + + return java.lang.invoke.MethodType.methodType(lambdaMethod.getReturnType(), instantiatedMethodParamTypes); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param lambdaMethod [java.lang.reflect.Method] that represents a synthetic method for lambda. + * @param capturedArgumentTypes types of values captured by lambda. + * @return [java.lang.invoke.MethodType] that represents the value of argument `instantiatedMethodType` + * of method [java.lang.invoke.LambdaMetafactory.metafactory]. + */ + private fun getInstantiatedMethodType( + lambdaMethod: java.lang.reflect.Method, + capturedArgumentTypes: Array> + ): java.lang.invoke.MethodType { + // Types of arguments of synthetic method (representing lambda) without the types of captured values. + val instantiatedMethodParamTypes = lambdaMethod.parameterTypes + .drop(capturedArgumentTypes.size) + .toTypedArray() + + return java.lang.invoke.MethodType.methodType(lambdaMethod.returnType, instantiatedMethodParamTypes) + } + """.trimIndent() + } + +private fun getLambdaMethod(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * @param declaringClass class where a lambda is declared. + * @param lambdaName name of synthetic method that represents a lambda. + * @return {@link java.lang.reflect.Method} instance for the synthetic method that represent a lambda. + */ + private static java.lang.reflect.Method getLambdaMethod(Class declaringClass, String lambdaName) { + return java.util.Arrays.stream(declaringClass.getDeclaredMethods()) + .filter(method -> method.getName().equals(lambdaName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No lambda method named " + lambdaName + " was found in class: " + declaringClass.getCanonicalName())); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param declaringClass class where a lambda is declared. + * @param lambdaName name of synthetic method that represents a lambda. + * @return [java.lang.reflect.Method] instance for the synthetic method that represent a lambda. + */ + private fun getLambdaMethod(declaringClass: Class<*>, lambdaName: String): java.lang.reflect.Method { + return declaringClass.declaredMethods.firstOrNull { it.name == lambdaName } + ?: throw IllegalArgumentException("No lambda method named ${'$'}lambdaName was found in class: ${'$'}{declaringClass.canonicalName}") + } + """.trimIndent() + } + +private fun getSingleAbstractMethod(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + private static java.lang.reflect.Method getSingleAbstractMethod(Class clazz) { + java.util.List abstractMethods = java.util.Arrays.stream(clazz.getMethods()) + .filter(method -> java.lang.reflect.Modifier.isAbstract(method.getModifiers())) + .collect(java.util.stream.Collectors.toList()); + + if (abstractMethods.isEmpty()) { + throw new IllegalArgumentException("No abstract methods found in class: " + clazz.getCanonicalName()); + } + if (abstractMethods.size() > 1) { + throw new IllegalArgumentException("More than one abstract method found in class: " + clazz.getCanonicalName()); + } + + return abstractMethods.get(0); + } + """.trimIndent() + CodegenLanguage.KOTLIN -> + """ + /** + * @param clazz functional interface + * @return a [java.lang.reflect.Method] for the single abstract method of the given functional interface `clazz`. + */ + private fun getSingleAbstractMethod(clazz: Class<*>): java.lang.reflect.Method { + val abstractMethods = clazz.methods.filter { java.lang.reflect.Modifier.isAbstract(it.modifiers) } + require(abstractMethods.isNotEmpty()) { "No abstract methods found in class: " + clazz.canonicalName } + require(abstractMethods.size <= 1) { "More than one abstract method found in class: " + clazz.canonicalName } + return abstractMethods[0] + } + """.trimIndent() + } + +private fun capturedArgumentClass(language: CodegenLanguage) = + when (language) { + CodegenLanguage.JAVA -> + """ + /** + * This class represents the {@code type} and {@code value} of a value captured by lambda. + * Captured values are represented as arguments of a synthetic method that lambda is compiled into, + * hence the name of the class. + */ + public static class CapturedArgument { + private Class type; + private Object value; + + public CapturedArgument(Class type, Object value) { + this.type = type; + this.value = value; + } + } + """.trimIndent() + CodegenLanguage.KOTLIN -> { + """ + /** + * This class represents the `type` and `value` of a value captured by lambda. + * Captured values are represented as arguments of a synthetic method that lambda is compiled into, + * hence the name of the class. + */ + data class CapturedArgument(val type: Class<*>, val value: Any?) + """.trimIndent() + } + } + +internal fun CgContextOwner.importUtilMethodDependencies(id: MethodId) { + // if util methods come from a separate UtUtils class and not from the test class, + // then we don't need to import any other methods, hence we return from method + val utilMethodProvider = utilMethodProvider as? TestClassUtilMethodProvider ?: return + for (classId in utilMethodProvider.regularImportsByUtilMethod(id, codegenLanguage)) { + importIfNeeded(classId) + } + for (methodId in utilMethodProvider.staticImportsByUtilMethod(id)) { + collectedImports += StaticImport(methodId.classId.canonicalName, methodId.name) + } +} + +private fun TestClassUtilMethodProvider.regularImportsByUtilMethod( + id: MethodId, + codegenLanguage: CodegenLanguage +): List { + return when (id) { + getUnsafeInstanceMethodId -> listOf(fieldClassId) + createInstanceMethodId -> listOf(java.lang.reflect.InvocationTargetException::class.id) + createArrayMethodId -> listOf(java.lang.reflect.Array::class.id) + setFieldMethodId -> listOf(fieldClassId, Modifier::class.id) + setStaticFieldMethodId -> listOf(fieldClassId, Modifier::class.id) + getFieldValueMethodId -> listOf(fieldClassId, Modifier::class.id) + getStaticFieldValueMethodId -> listOf(fieldClassId, Modifier::class.id) + getEnumConstantByNameMethodId -> listOf(fieldClassId) + deepEqualsMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf( + Objects::class.id, + Iterable::class.id, + Map::class.id, + List::class.id, + ArrayList::class.id, + Set::class.id, + HashSet::class.id, + fieldClassId, + Arrays::class.id + ) + CodegenLanguage.KOTLIN -> listOf(fieldClassId, Arrays::class.id) + } + arraysDeepEqualsMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(java.lang.reflect.Array::class.id, Set::class.id) + CodegenLanguage.KOTLIN -> listOf(java.lang.reflect.Array::class.id) + } + iterablesDeepEqualsMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(Iterable::class.id, Iterator::class.id, Set::class.id) + CodegenLanguage.KOTLIN -> emptyList() + } + streamsDeepEqualsMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(java.util.stream.BaseStream::class.id, Set::class.id) + CodegenLanguage.KOTLIN -> emptyList() + } + mapsDeepEqualsMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(Map::class.id, Iterator::class.id, Set::class.id) + CodegenLanguage.KOTLIN -> emptyList() + } + hasCustomEqualsMethodId -> emptyList() + getArrayLengthMethodId -> listOf(java.lang.reflect.Array::class.id) + consumeBaseStreamMethodId -> listOf(java.util.stream.BaseStream::class.id) + buildStaticLambdaMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf( + MethodHandles::class.id, Method::class.id, MethodType::class.id, + MethodHandle::class.id, CallSite::class.id, LambdaMetafactory::class.id + ) + CodegenLanguage.KOTLIN -> listOf(MethodType::class.id, LambdaMetafactory::class.id) + } + buildLambdaMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf( + MethodHandles::class.id, Method::class.id, MethodType::class.id, + MethodHandle::class.id, CallSite::class.id, LambdaMetafactory::class.id + ) + CodegenLanguage.KOTLIN -> listOf(MethodType::class.id, LambdaMetafactory::class.id) + } + getLookupInMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(MethodHandles::class.id, Field::class.id, Modifier::class.id) + CodegenLanguage.KOTLIN -> listOf(MethodHandles::class.id, Modifier::class.id) + } + getLambdaCapturedArgumentTypesMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(LambdaMetafactory::class.id) + CodegenLanguage.KOTLIN -> listOf(LambdaMetafactory::class.id) + } + getLambdaCapturedArgumentValuesMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(Arrays::class.id) + CodegenLanguage.KOTLIN -> emptyList() + } + getInstantiatedMethodTypeMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf( + Method::class.id, MethodType::class.id, LambdaMetafactory::class.id, + java.util.List::class.id, Arrays::class.id, Collectors::class.id + ) + CodegenLanguage.KOTLIN -> listOf(Method::class.id, MethodType::class.id, LambdaMetafactory::class.id) + } + getLambdaMethodMethodId -> when (codegenLanguage) { + CodegenLanguage.JAVA -> listOf(Method::class.id, Arrays::class.id) + CodegenLanguage.KOTLIN -> listOf(Method::class.id) + } + getSingleAbstractMethodMethodId -> listOf( + Method::class.id, java.util.List::class.id, Arrays::class.id, + Modifier::class.id, Collectors::class.id + ) + else -> error("Unknown util method for class $this: $id") + } +} + +// Note: for now always returns an empty list, because no util method +// requires static imports, but this may change in the future +@Suppress("unused", "unused_parameter") +private fun TestClassUtilMethodProvider.staticImportsByUtilMethod(id: MethodId): List = emptyList() \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/reports/TestsGenerationReport.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/reports/TestsGenerationReport.kt new file mode 100644 index 0000000000..7ca14aa3cd --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/reports/TestsGenerationReport.kt @@ -0,0 +1,124 @@ +package org.utbot.framework.codegen.reports + +import org.utbot.common.appendHtmlLine +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgTestMethodType +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.util.kClass +import kotlin.reflect.KClass + +typealias MethodGeneratedTests = MutableMap> +typealias ErrorsCount = Map + +data class TestsGenerationReport( + val executables: MutableSet = mutableSetOf(), + var successfulExecutions: MethodGeneratedTests = mutableMapOf(), + var timeoutExecutions: MethodGeneratedTests = mutableMapOf(), + var failedExecutions: MethodGeneratedTests = mutableMapOf(), + var artificiallyFailedExecutions: MethodGeneratedTests = mutableMapOf(), + var crashExecutions: MethodGeneratedTests = mutableMapOf(), + var errors: MutableMap = mutableMapOf() +) { + val classUnderTest: KClass<*> + get() = executables.firstOrNull()?.classId?.kClass + ?: error("No executables found in test report") + + val initialWarnings: MutableList<() -> String> = mutableListOf() + val hasWarnings: Boolean + get() = initialWarnings.isNotEmpty() + + val detailedStatistics: String + get() = buildString { + appendHtmlLine("Class: ${classUnderTest.qualifiedName}") + val testMethodsStatistic = executables.map { it.countTestMethods() } + val errors = executables.map { it.countErrors() } + val overallErrors = errors.sum() + + appendHtmlLine("Successful test methods: ${testMethodsStatistic.sumOf { it.successful }}") + appendHtmlLine( + "Failing because of unexpected exception test methods: ${testMethodsStatistic.sumOf { it.failing }}" + ) + appendHtmlLine( + "Failing because of exception (according to custom settings) test methods: " + + "${testMethodsStatistic.sumOf { it.artificiallyFailing }}" + ) + appendHtmlLine( + "Failing because of exceeding timeout test methods: ${testMethodsStatistic.sumOf { it.timeout }}" + ) + appendHtmlLine( + "Failing because of possible JVM crash test methods: ${testMethodsStatistic.sumOf { it.crashes }}" + ) + appendHtmlLine("Not generated because of internal errors test methods: $overallErrors") + } + + fun addMethodErrors(testSet: CgMethodTestSet, errors: Map) { + this.executables += testSet.executableUnderTest + this.errors[testSet.executableUnderTest] = errors + } + + fun addTestsByType(testSet: CgMethodTestSet, testMethods: List) { + with(testSet.executableUnderTest) { + executables += this + + testMethods.forEach { + when (it.type) { + CgTestMethodType.SUCCESSFUL, CgTestMethodType.PASSED_EXCEPTION -> updateExecutions(it, successfulExecutions) + CgTestMethodType.FAILING -> updateExecutions(it, failedExecutions) + CgTestMethodType.ARTIFICIAL -> updateExecutions(it, artificiallyFailedExecutions) + CgTestMethodType.TIMEOUT -> updateExecutions(it, timeoutExecutions) + CgTestMethodType.CRASH -> updateExecutions(it, crashExecutions) + CgTestMethodType.PARAMETRIZED -> { + // Parametrized tests are not supported in the tests report yet + // TODO JIRA:1507 + } + } + } + } + } + + fun countTestMethods() = executables.map { it.countTestMethods() }.sumOf { it.count } + + fun toString(isShort: Boolean): String = buildString { + appendHtmlLine("Target: ${classUnderTest.qualifiedName}") + if (initialWarnings.isNotEmpty()) { + initialWarnings.forEach { appendHtmlLine(it()) } + appendHtmlLine() + } + + appendHtmlLine("Overall test methods: ${countTestMethods()}") + + if (!isShort) { + appendHtmlLine(detailedStatistics) + } + } + + override fun toString(): String = toString(false) + + private fun ExecutableId.countTestMethods(): TestMethodStatistic = TestMethodStatistic( + testMethodsNumber(successfulExecutions), + testMethodsNumber(failedExecutions), + testMethodsNumber(artificiallyFailedExecutions), + testMethodsNumber(timeoutExecutions), + testMethodsNumber(crashExecutions), + ) + + private fun ExecutableId.countErrors(): Int = errors.getOrDefault(this, emptyMap()).values.sum() + + private fun ExecutableId.testMethodsNumber(executables: MethodGeneratedTests): Int = + executables.getOrDefault(this, emptySet()).size + + private fun ExecutableId.updateExecutions(it: CgTestMethod, executions: MethodGeneratedTests) { + executions.getOrPut(this) { mutableSetOf() } += it + } + + private data class TestMethodStatistic( + val successful: Int, + val failing: Int, + val artificiallyFailing: Int, + val timeout: Int, + val crashes: Int, + ) { + val count: Int = successful + failing + artificiallyFailing + timeout + crashes + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/CgNameGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/CgNameGenerator.kt new file mode 100644 index 0000000000..09608ccc98 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/CgNameGenerator.kt @@ -0,0 +1,190 @@ +package org.utbot.framework.codegen.services + +import org.utbot.framework.codegen.services.language.isLanguageKeyword +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.tree.infiniteInts +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.isArray + +/** + * Interface for method and variable name generators + */ +interface CgNameGenerator { + /** + * Generate a variable name given a [base] name. + * @param isMock denotes whether a variable represents a mock object or not + * @param isStatic denotes whether a variable represents a static variable or not + */ + fun variableName(base: String, isMock: Boolean = false, isStatic: Boolean = false): String + + /** + * Convert a given class id to a string that can serve + * as a part of a variable name + */ + fun nameFrom(id: ClassId): String = + when { + id.isAnonymous -> id.prettifiedName + id.isArray -> id.prettifiedName + id.simpleName.isScreamingSnakeCase() -> id.simpleName.fromScreamingSnakeCaseToCamelCase() // special case for enum instances + else -> id.simpleName.decapitalize() + } + + /** + * Generate a variable name given a [type] of variable and a [base] name + * If [base] is not null, then use it to generate name + * Otherwise, fall back to generating a name by [type] + * @param isMock denotes whether a variable represents a mock object or not + */ + fun variableName(type: ClassId, base: String? = null, isMock: Boolean = false): String + + /** + * Generate a new test method name. + */ + fun testMethodNameFor(executableId: ExecutableId, customName: String? = null): String + + /** + * Generates a new parameterized test method name by data provider method name. + */ + fun parameterizedTestMethodName(dataProviderMethodName: String): String + + /** + * Generates a new data for parameterized test provider method name + */ + fun dataProviderMethodNameFor(executableId: ExecutableId): String + + /** + * Generate a new error method name + */ + fun errorMethodNameFor(executableId: ExecutableId): String +} + +/** + * Class that generates names for methods and variables + * To avoid name collisions it uses existing names information from CgContext + */ +class CgNameGeneratorImpl(val context: CgContext) + : CgNameGenerator, CgContextOwner by context { + + override fun variableName(base: String, isMock: Boolean, isStatic: Boolean): String { + val baseName = when { + isMock -> base + "Mock" + isStatic -> base + "Static" + else -> base + } + return when { + baseName in existingVariableNames -> nextIndexedVarName(baseName) + isLanguageKeyword(baseName, context.cgLanguageAssistant) -> createNameFromKeyword(baseName) + else -> baseName + }.also { + existingVariableNames = existingVariableNames.add(it) + } + } + + override fun variableName(type: ClassId, base: String?, isMock: Boolean): String { + val baseName = base?.fromScreamingSnakeCaseToCamelCase() ?: nameFrom(type) + return variableName(baseName.decapitalize(), isMock) + } + + override fun testMethodNameFor(executableId: ExecutableId, customName: String?): String { + val executableName = createExecutableName(executableId) + + // no index suffix allowed only when there's a vacant custom name + val name = if (customName != null && customName !in existingMethodNames) { + customName + } else { + val base = customName ?: "test${executableName.capitalize()}" + nextIndexedMethodName(base) + } + existingMethodNames += name + return name + } + + private val dataProviderMethodPrefix = "provideDataFor" + + override fun parameterizedTestMethodName(dataProviderMethodName: String) = + dataProviderMethodName.replace(dataProviderMethodPrefix, "parameterizedTestsFor") + + override fun dataProviderMethodNameFor(executableId: ExecutableId): String { + val executableName = createExecutableName(executableId) + val indexedName = nextIndexedMethodName(executableName.capitalize(), skipOne = true) + + existingMethodNames += indexedName + return "$dataProviderMethodPrefix$indexedName" + } + + override fun errorMethodNameFor(executableId: ExecutableId): String { + val executableName = createExecutableName(executableId) + val newName = when (val base = "test${executableName.capitalize()}_errors") { + !in existingMethodNames -> base + else -> nextIndexedMethodName(base) + } + existingMethodNames += newName + return newName + } + + /** + * Creates a new indexed variable name by [base] name. + */ + private fun nextIndexedVarName(base: String): String = + infiniteInts() + .map { "$base$it" } + .first { it !in existingVariableNames } + + /** + * Creates a new indexed methodName by [base] name. + * + * @param skipOne shows if we add "1" to first method name or not + */ + private fun nextIndexedMethodName(base: String, skipOne: Boolean = false): String = + infiniteInts() + .map { if (skipOne && it == 1) base else "$base$it" } + .first { it !in existingMethodNames } + + private fun createNameFromKeyword(baseName: String): String = when(codegenLanguage) { + CodegenLanguage.JAVA -> nextIndexedVarName(baseName) + CodegenLanguage.KOTLIN -> { + // use backticks for first variable with keyword name and use indexed names for all next such variables + if (baseName !in existingVariableNames) "`$baseName`" else nextIndexedVarName(baseName) + } + } + + private fun createExecutableName(executableId: ExecutableId): String { + return when (executableId) { + is ConstructorId -> executableId.classId.prettifiedName // TODO: maybe we need some suffix e.g. "Ctor"? + is MethodId -> executableId.name + } + } +} + +/** + * Checks names like JUST_COLOR. + * @see SCREAMING_SNAKE_CASE + */ +private fun String.isScreamingSnakeCase(): Boolean { + // the code below is a bit complicated, but we want to support non-latin letters too, + // so we cannot just use such regex: [A-Z0-9]+(_[A-Z0-9]+)* + return split("_").all { + word -> word.isNotEmpty() && word.all { c -> c.isUpperCase() || c.isDigit() } + } +} + +/** + * Transforms string in SCREAMING_SNAKE_CASE to camelCase. If string is not in SCREAMING_SNAKE_CASE, returns it as it is. + * @see [isScreamingSnakeCase] + */ +private fun String.fromScreamingSnakeCaseToCamelCase(): String { + if (!isScreamingSnakeCase()) return this + + val lowerCaseWords = split("_").map { it.toLowerCase() } + val firstWord = lowerCaseWords.first() + val otherWords = lowerCaseWords.drop(1).map { it.capitalize() } + + val transformedWords = listOf(firstWord) + otherWords + + return transformedWords.joinToString("") +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/access/CgCallableAccessManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/access/CgCallableAccessManager.kt new file mode 100644 index 0000000000..176c83977d --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/access/CgCallableAccessManager.kt @@ -0,0 +1,689 @@ +package org.utbot.framework.codegen.services.access + +import kotlinx.collections.immutable.PersistentList +import org.utbot.framework.codegen.domain.builtin.any +import org.utbot.framework.codegen.domain.builtin.anyOfClass +import org.utbot.framework.codegen.domain.builtin.getMethodId +import org.utbot.framework.codegen.domain.builtin.getTargetException +import org.utbot.framework.codegen.domain.builtin.invoke +import org.utbot.framework.codegen.domain.builtin.newInstance +import org.utbot.framework.codegen.domain.builtin.genericObjectClassId +import org.utbot.framework.codegen.domain.builtin.setAccessible +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgAllocateArray +import org.utbot.framework.codegen.domain.models.CgAssignment +import org.utbot.framework.codegen.domain.models.CgConstructorCall +import org.utbot.framework.codegen.domain.models.CgExecutableCall +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgFieldAccess +import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgSpread +import org.utbot.framework.codegen.domain.models.CgStatement +import org.utbot.framework.codegen.domain.models.CgStaticFieldAccess +import org.utbot.framework.codegen.domain.models.CgThisInstance +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.services.access.CgCallableAccessManagerImpl.FieldAccessorSuitability.* +import org.utbot.framework.codegen.tree.CgComponents.getStatementConstructorBy +import org.utbot.framework.codegen.tree.CgComponents.getVariableConstructorBy +import org.utbot.framework.codegen.tree.downcastIfNeeded +import org.utbot.framework.codegen.tree.getAmbiguousOverloadsOf +import org.utbot.framework.codegen.tree.importIfNeeded +import org.utbot.framework.codegen.tree.isUtil +import org.utbot.framework.codegen.tree.typeCast +import org.utbot.framework.codegen.util.at +import org.utbot.framework.codegen.util.canBeReadFrom +import org.utbot.framework.codegen.util.isAccessibleFrom +import org.utbot.framework.codegen.util.nullLiteral +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.BuiltinConstructorId +import org.utbot.framework.plugin.api.BuiltinMethodId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.exceptions +import org.utbot.framework.plugin.api.util.extensionReceiverParameterIndex +import org.utbot.framework.plugin.api.util.humanReadableName +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isAbstract +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.isSubtypeOf +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.method +import org.utbot.framework.plugin.api.util.objectArrayClassId +import org.utbot.framework.plugin.api.util.objectClassId + +typealias Block = PersistentList + +class CgIncompleteMethodCall(val method: MethodId, val caller: CgExpression?) + +/** + * Provides DSL methods for method and field access elements creation + * + * Checks the accessibility of methods and fields and replaces + * direct access with reflective access when needed + */ +interface CgCallableAccessManager { + operator fun CgExpression?.get(methodId: MethodId): CgIncompleteMethodCall + + operator fun ClassId.get(staticMethodId: MethodId): CgIncompleteMethodCall + + operator fun ConstructorId.invoke(vararg args: Any?): CgExecutableCall + + operator fun CgIncompleteMethodCall.invoke(vararg args: Any?): CgMethodCall + + // non-static fields + operator fun CgExpression.get(fieldId: FieldId): CgExpression + + // static fields + operator fun ClassId.get(fieldId: FieldId): CgStaticFieldAccess +} + +class CgCallableAccessManagerImpl(val context: CgContext) : CgCallableAccessManager, + CgContextOwner by context { + + private val statementConstructor by lazy { getStatementConstructorBy(context) } + + private val variableConstructor by lazy { getVariableConstructorBy(context) } + + override operator fun CgExpression?.get(methodId: MethodId): CgIncompleteMethodCall = + CgIncompleteMethodCall(methodId, this) + + override operator fun ClassId.get(staticMethodId: MethodId): CgIncompleteMethodCall = + CgIncompleteMethodCall(staticMethodId, null) + + override operator fun ConstructorId.invoke(vararg args: Any?): CgExecutableCall { + val resolvedArgs = args.resolve() + val constructorCall = if (this canBeCalledWith resolvedArgs) { + CgConstructorCall(this, resolvedArgs.guardedForDirectCallOf(this)) + } else { + callWithReflection(resolvedArgs) + } + newConstructorCall(this) + return constructorCall + } + + override operator fun CgIncompleteMethodCall.invoke(vararg args: Any?): CgMethodCall { + val resolvedArgs = args.resolve() + val methodCall = if (method.canBeCalledWith(caller, resolvedArgs)) { + CgMethodCall( + caller = caller?.let { downcastIfNeeded(method.classId, caller) }, + executableId = method, + arguments = resolvedArgs.guardedForDirectCallOf(method) + ).takeCallerFromArgumentsIfNeeded() + } else { + method.callWithReflection(caller, resolvedArgs) + } + newMethodCall(method) + return methodCall + } + + override operator fun CgExpression.get(fieldId: FieldId): CgExpression { + return when (val suitability = fieldId.accessSuitability(this)) { + // receiver (a.k.a. caller) is suitable, so we access field directly + is Suitable -> CgFieldAccess(this, fieldId) + // receiver has to be type cast, and then we can access the field + is RequiresTypeCast -> { + CgFieldAccess( + caller = typeCast(suitability.targetType, this), + fieldId + ) + } + // we can access the field only via reflection + + // NOTE that current implementation works only if field access is located + // in the right part of the assignment. However, obtaining this construction + // as an "l-value" seems to be an error in assemble models or somewhere else. + is ReflectionOnly -> fieldId.accessWithReflection(this) + } + } + + override operator fun ClassId.get(fieldId: FieldId): CgStaticFieldAccess = CgStaticFieldAccess(fieldId) + + private fun newMethodCall(methodId: MethodId) { + if (isUtil(methodId)) requiredUtilMethods += methodId + importIfNeeded(methodId) + + //Builtin methods does not have jClass, so [methodId.method] will crash on it, + //so we need to collect required exceptions manually from source codes + if (methodId is BuiltinMethodId) { + methodId.findExceptionTypes() + .forEach { addExceptionIfNeeded(it) } + return + } + + if (methodId == getTargetException) { + addExceptionIfNeeded(Throwable::class.id) + } + + methodId.method.exceptionTypes.forEach { addExceptionIfNeeded(it.id) } + } + + private fun newConstructorCall(constructorId: ConstructorId) { + importIfNeeded(constructorId.classId) + + // Builtin constructors do not have jClass, so [constructorId.exceptions] will crash on it, + // so we need to collect required exceptions manually from source codes (see BuiltinConstructorId.findExceptionTypes()). + + if (constructorId is BuiltinConstructorId) { + constructorId.findExceptionTypes().forEach { addExceptionIfNeeded(it) } + return + } + + for (exception in constructorId.exceptions) { + addExceptionIfNeeded(exception) + } + } + + private infix fun CgExpression.canBeReceiverOf(executable: MethodId): Boolean = + when { + // this executable can be called on its 'this' instance + currentTestClass == executable.classId && this isThisInstanceOf currentTestClass -> true + + // this executable can be called on an object of this class or any of its subtypes + this.type isSubtypeOf executable.classId -> true + + // this executable can be called on builtin type + this.type is BuiltinClassId && this.type in builtinCallersWithoutReflection -> true + + // receiver can be downcasted before call + else -> executable.isAccessibleFrom(testClassPackageName) + } + + // For some builtin types we need to clarify + // that it is allowed to call an executable without reflection. + // + // This approach is used, for example, to render the constructions with stubs + // like `doNothing().when(entityManagerMock).persist(any())`. + private val builtinCallersWithoutReflection = setOf(genericObjectClassId) + + /** + * For Kotlin extension functions, real caller is one of the arguments in JVM method (and declaration class is omitted), + * thus we should move it from arguments to caller + * + * For example, if we have `Int.f(a: Int)` declared in `Main.kt`, the JVM method signature will be `MainKt.f(Int, Int)` + * and in Kotlin we should render this not like `MainKt.f(a, b)` but like `a.f(b)` + */ + private fun CgMethodCall.takeCallerFromArgumentsIfNeeded(): CgMethodCall { + if (codegenLanguage == CodegenLanguage.KOTLIN) { + // TODO: reflection calls for util and some of mockito methods produce exceptions => here we suppose that + // methods for BuiltinClasses are not extensions by default (which should be true as long as we suppose them to be java methods) + if (executableId.classId !is BuiltinClassId) { + executableId.extensionReceiverParameterIndex?.let { receiverIndex -> + require(caller == null) { "${executableId.humanReadableName} is an extension function but it already has a non-static caller provided" } + val args = arguments.toMutableList() + return CgMethodCall(args.removeAt(receiverIndex), executableId, args, typeParameters) + } + } + } + + return this + } + + private infix fun CgExpression.canBeArgOf(type: ClassId): Boolean { + // TODO: SAT-1210 support generics so that we wouldn't need to check specific cases such as this one + if (this is CgExecutableCall && (executableId == any || executableId == anyOfClass)) { + return true + } + return this == nullLiteral() && type.isAccessibleFrom(testClassPackageName) + || this.type isSubtypeOf type + } + + private infix fun CgExpression?.isThisInstanceOf(classId: ClassId): Boolean = + this is CgThisInstance && this.type == classId + + /** + * Check whether @receiver (list of expressions) is a valid list of arguments for [executableId] + * + * First, we check all arguments except for the last one. + * It is done to consider the last argument separately since it can be a vararg, + * which requires some additional checks. + * + * For the last argument there can be several cases: + * - Last argument is not of array type - then we simply check this argument as all the others + * - Last argument is of array type: + * - Given arguments and parameters have the same size + * - Last argument is an array and it matches last parameter array type + * - Last argument is a single element of a vararg parameter - then we check + * if argument's type matches the vararg element's type + * - Given arguments and parameters have different size (last parameter is vararg) - then we + * check if all of the given arguments match the vararg element's type + * + */ + private infix fun List.canBeArgsOf(executableId: ExecutableId): Boolean { + val paramTypes = executableId.parameters + + // no arguments case + if (paramTypes.isEmpty()) { + return this.isEmpty() + } + + val paramTypesExceptLast = paramTypes.dropLast(1) + val lastParamType = paramTypes.last() + + // considering all arguments except the last one + for ((arg, paramType) in (this zip paramTypesExceptLast)) { + if (!(arg canBeArgOf paramType)) return false + } + + // when the last parameter is not of array type + if (!lastParamType.isArray) { + val lastArg = this.last() + return lastArg canBeArgOf lastParamType + } + + // when arguments and parameters have equal size + if (size == paramTypes.size) { + val lastArg = this.last() + return when { + // last argument matches last param type + lastArg canBeArgOf lastParamType -> true + // last argument is a single element of a vararg parameter + lastArg canBeArgOf lastParamType.elementClassId!! -> true + else -> false + } + } + + // when arguments size is greater than the parameters size + // meaning that the last parameter is vararg + return subList(paramTypes.size - 1, size).all { + it canBeArgOf lastParamType.elementClassId!! + } + } + + /** + * @return true if a method can be called with the given arguments without reflection + */ + private fun MethodId.canBeCalledWith(caller: CgExpression?, args: List): Boolean { + // Check method accessibility. + if (!isAccessibleFrom(testClassPackageName)) { + return false + } + + // Check arguments suitability. + if (!(args canBeArgsOf this)) { + return false + } + + // If method is static, then it may not have a caller. + if (this.isStatic && caller == null) { + return true + } + + if (this.isStatic && caller != null && codegenLanguage == CodegenLanguage.KOTLIN) { + error("In Kotlin, unlike Java, static methods cannot be called on an instance: $this") + } + + // If method is from current test class, then it may not have a caller. + if (this.classId == currentTestClass && caller == null) { + return true + } + + requireNotNull(caller) { "Method must have a caller, unless it is the method of the current test class or a static method: $this" } + return caller canBeReceiverOf this + } + + private fun FieldId.accessSuitability(accessor: CgExpression): FieldAccessorSuitability { + // Check field accessibility. + if (!canBeReadFrom(context, accessor.type)) { + return ReflectionOnly + } + + if (this.isStatic && codegenLanguage == CodegenLanguage.KOTLIN) { + error("In Kotlin, unlike Java, static fields cannot be accessed by an object: $this") + } + + // if field is declared in the current test class + if (this.declaringClass == currentTestClass) { + return when { + // field of the current class can be accessed with `this` reference + accessor isThisInstanceOf currentTestClass -> Suitable + // field of the current class can be accessed by the instance of this class + accessor.type isSubtypeOf currentTestClass -> Suitable + // in any other case, we have to use reflection + else -> ReflectionOnly + } + } + + if (this.declaringClass == accessor.type) { + // if the field was declared in class `T`, and the accessor is __exactly__ of this type (not a subtype), + // then we can safely access the field + return Suitable + } + + val fieldDeclaringClassType = this.declaringClass + val accessorType = accessor.type + + if (fieldDeclaringClassType is BuiltinClassId || accessorType is BuiltinClassId) { + return Suitable + } + + // The rest of the logic of this method processes hidden fields. + // We cannot check the fields of builtin classes, so we assume that we work correctly with them, + // because they are well known library classes (e.g. Mockito) that are unlikely to have hidden fields. + + return when { + accessorType isSubtypeOf fieldDeclaringClassType -> { + if (this isFieldHiddenIn accessorType.jClass) { + // We know that declaring class of field is accessible, + // because it was checked in `isAccessibleFrom` call at the start of this method, + // so we can safely do a type cast. + RequiresTypeCast(fieldDeclaringClassType) + } else { + Suitable + } + } + fieldDeclaringClassType isSubtypeOf accessorType -> { + // We know that declaring class of field is accessible, + // because it was checked in `isAccessibleFrom` call at the start of this method, + // so we can safely do a type cast. + RequiresTypeCast(fieldDeclaringClassType) + } + // Accessor type is not subtype or supertype of the field's declaring class. + // So the only remaining option to access the field is to use reflection. + else -> ReflectionOnly + } + } + + /** + * Check if the field represented by @receiver is hidden when accessed from an instance of [subclass]. + * + * Brief description: this method collects all types from hierarchy in between [subclass] (inclusive) + * up to the class where the field is declared (exclusive) and checks if any of these classes declare + * a field with the same name (thus hiding the given field). For examples and more details read documentation below. + * + * The **contract** of this method is as follows: + * [subclass] must be a subclass of `this.declaringClass` (and they **must not** be the same class). + * That is because if they are equal (we try to access field from instance of its own class), + * then the field is not hidden. And if [subclass] is actually not a subclass, but a superclass of a class + * where the field is declared, then this check makes no sense (superclass cannot hide field of a subclass). + * Lastly, if these classes are not related in terms of inheritance, then there must be some error, + * because such checks also make no sense. + * + * **Examples**. + * + * For example, given classes: + * ``` + * class A { int x; } + * class B extends A { int x; } + * ``` + * we can say that field `x` of class `A` is hidden when we are dealing with instances of a subclass `B`: + * ``` + * B b = new B(); + * b.x = 10; + * ``` + * There is no way to access field `x` of class `A` from variable `b` without using type casts or reflection. + * + * So the result of [isFieldHiddenIn] for field `x` of class `A` and a subclass `B` would be `true`. + * + * **NOTE** that there can be more complicated cases. For example, interfaces can also have fields (they are always static). + * Fields of an interface will be available to the classes and interfaces that inherit from it. + * That means that when checking if a field is hidden we have to take **both** classes and interfaces into account. + * + * **However**, there is an important detail. We **do not** consider superclasses and superinterfaces + * of the type where the field (represented by @receiver) was declared. Consider the following example: + * ``` + * class A { int x; } + * class B extends A { int x; } + * class C extends B { } + * ``` + * If we are checking if the field `x` of class `B` is hidden when accessed by an instance of class `C`, + * then [isFieldHiddenIn] will return `false`, because the field `x` of class `A` does not stand in the way + * and is itself hidden by `B.x`. That is why we **can** access `B.x` from a variable of type `C`. + * + * Lastly, another **important** example: + * ``` + * class A { int x; } + * interface I1 { int x = 10; } + * class B extends A implements I1 { } + * ``` + * Unlike previous examples, here we have class `B` which has different "branches" of supertypes. + * On the one hand we have superclass `A` and on the other we have interface `I1`. + * `A` and `I1` do not have any relationship with each other, but `B` **can** access fields `x` from both of them. + * However, it **must** be done with a type cast (to `A` or `I1`), because otherwise accessing field `x` will + * be ambiguous. Method [isFieldHiddenIn] **does consider** such cases as well. + * + * **These examples show** that when checking if a field is hidden we **must** consider all supertypes + * in all "branches" **up to** the type where the field is declared (**exclusively**). + * Then we check if any of these types has a field with the same name. + * If such field is found, then the field is hidden, otherwise - not. + */ + private infix fun FieldId.isFieldHiddenIn(subclass: Class<*>): Boolean { + // see the documentation of this method for more details on this requirement + require(subclass.id != this.declaringClass) { + "A given subclass must not be equal to the declaring class of the field: $subclass" + } + + // supertypes (classes and interfaces) from subclass (inclusive) up to superclass (exclusive) + val supertypes = sequence { + var types = generateSequence(subclass) { it.superclass } + .takeWhile { it != declaringClass.jClass } + .toList() + while (types.isNotEmpty()) { + yieldAll(types) + types = types.flatMap { it.interfaces.toList() } + } + } + + // check if any of the collected supertypes declare a field with the same name + val fieldHidingTypes = supertypes.toList() + .filter { type -> + val fieldNames = type.declaredFields.map { it.name } + this.name in fieldNames + } + + // if we found at least one type that hides the field, then the field is hidden + return fieldHidingTypes.isNotEmpty() + } + + /** + * @return true if a constructor can be called with the given arguments without reflection + */ + private infix fun ConstructorId.canBeCalledWith(args: List): Boolean = + isAccessibleFrom(testClassPackageName) && !classId.isAbstract && args canBeArgsOf this + + private fun List.guardedForDirectCallOf(executable: ExecutableId): List { + if (executable is BuiltinMethodId) { + // We assume that we do not have ambiguous overloads for builtin methods + return this + } + + val ambiguousOverloads = executable.classId + .getAmbiguousOverloadsOf(executable) + .filterNot { it == executable } + .toList() + + val isEmptyAmbiguousOverloads = ambiguousOverloads.isEmpty() + + return if (isEmptyAmbiguousOverloads) this else castAmbiguousArguments(executable, this, ambiguousOverloads) + } + + private fun castAmbiguousArguments( + executable: ExecutableId, + args: List, + ambiguousOverloads: List + ): List = + args.withIndex().map { (i ,arg) -> + val targetType = executable.parameters[i] + + // always cast nulls + if (arg == nullLiteral()) return@map typeCast(targetType, arg) + + // in case arg type exactly equals target type, do nothing + if (arg.type == targetType) return@map arg + + // arg type is subtype of target type + // check other overloads for ambiguous types + val typesInOverloadings = ambiguousOverloads.map { it.parameters[i] } + val ancestors = typesInOverloadings.filter { arg.type.isSubtypeOf(it) } + + if (ancestors.isNotEmpty()) typeCast(targetType, arg) else arg + } + + /** + * Receives a list of [CgExpression]. + * Transforms it into a list of [CgExpression] where: + * - array and literal values are cast to [java.lang.Object] + * - other values remain as they were + * + * @return a list of [CgExpression] where each expression can be + * used as an argument of reflective call to a method or constructor + */ + private fun List.guardedForReflectiveCall(): List = + map { + when { + it is CgValue && it.type.isArray -> typeCast(objectClassId, it) + it == nullLiteral() -> typeCast(objectClassId, it) + else -> it + } + } + + private fun FieldId.accessWithReflection(accessor: CgExpression?): CgMethodCall { + val field = declaredFieldRefs[this] + ?: statementConstructor.createFieldVariable(this).also { + declaredFieldRefs = declaredFieldRefs.put(this, it) + +it[setAccessible](true) + } + return field[getMethodId](accessor) + } + + private fun MethodId.callWithReflection(caller: CgExpression?, args: List): CgMethodCall { + containsReflectiveCall = true + val method = getExecutableRefVariable(args) + val arguments = args.guardedForReflectiveCall().toTypedArray() + val argumentsArrayVariable = convertVarargToArray(method, arguments) + + return method[invoke](caller, CgSpread(argumentsArrayVariable.type, argumentsArrayVariable)) + } + + private fun ConstructorId.callWithReflection(args: List): CgExecutableCall { + containsReflectiveCall = true + val constructor = getExecutableRefVariable(args) + val arguments = args.guardedForReflectiveCall().toTypedArray() + val argumentsArrayVariable = convertVarargToArray(constructor, arguments) + + return constructor[newInstance](argumentsArrayVariable) + } + + private fun ExecutableId.getExecutableRefVariable(arguments: List): CgVariable { + return declaredExecutableRefs[this] + ?: statementConstructor.createExecutableVariable(this, arguments).also { + declaredExecutableRefs = declaredExecutableRefs.put(this, it) + +it[setAccessible](true) + } + } + + private fun convertVarargToArray(reflectionCallVariable: CgVariable, arguments: Array): CgVariable { + val argumentsArrayVariable = variableConstructor.newVar( + baseType = objectArrayClassId, + baseName = "${reflectionCallVariable.name}Arguments" + ) { + CgAllocateArray( + type = objectArrayClassId, + elementType = objectClassId, + size = arguments.size + ) + } + + for ((i, argument) in arguments.withIndex()) { + +CgAssignment(argumentsArrayVariable.at(i), argument) + } + + return argumentsArrayVariable + } + + private fun BuiltinConstructorId.findExceptionTypes(): Set { + // At the moment we do not have builtin ids for constructors that throw exceptions, + // so we have this trivial when-expression. But if we ever add ids for such constructors, + // then we **must** specify their exceptions here, so that we take them into account when generating code. + @Suppress("UNUSED_EXPRESSION") + return when (this) { + else -> emptySet() + } + } + + //WARN: if you make changes in the following sets of exceptions, + //don't forget to change them in hardcoded [UtilMethods] as well + private fun BuiltinMethodId.findExceptionTypes(): Set { + // TODO: at the moment we treat BuiltinMethodIds that are not util method ids + // as if they have no exceptions. This should be fixed by storing exception types in BuiltinMethodId + // or allowing us to access actual java.lang.Class for classes from mockito and other libraries + // (this could be possibly solved by using user project's class loaders in UtContext) + if (!isUtil(this)) return emptySet() + + with(utilMethodProvider) { + return when (this@findExceptionTypes) { + getEnumConstantByNameMethodId -> setOf(IllegalAccessException::class.id) + getStaticFieldValueMethodId, + setStaticFieldMethodId -> setOf(java.lang.IllegalAccessException::class.id, java.lang.NoSuchFieldException::class.id) + getFieldValueMethodId, + setFieldMethodId -> setOf( + java.lang.ClassNotFoundException::class.id, + java.lang.IllegalAccessException::class.id, + java.lang.NoSuchFieldException::class.id, + java.lang.reflect.InvocationTargetException::class.id, + java.lang.NoSuchMethodException::class.id + ) + createInstanceMethodId -> setOf(Exception::class.id) + getUnsafeInstanceMethodId -> setOf(ClassNotFoundException::class.id, NoSuchFieldException::class.id, IllegalAccessException::class.id) + createArrayMethodId -> setOf(ClassNotFoundException::class.id) + deepEqualsMethodId, + arraysDeepEqualsMethodId, + iterablesDeepEqualsMethodId, + streamsDeepEqualsMethodId, + mapsDeepEqualsMethodId, + hasCustomEqualsMethodId, + getArrayLengthMethodId, + consumeBaseStreamMethodId, + getLambdaCapturedArgumentTypesMethodId, + getLambdaCapturedArgumentValuesMethodId, + getInstantiatedMethodTypeMethodId, + getLambdaMethodMethodId, + getSingleAbstractMethodMethodId -> emptySet() + buildStaticLambdaMethodId, + buildLambdaMethodId -> setOf(Throwable::class.id) + getLookupInMethodId -> setOf( + IllegalAccessException::class.id, + NoSuchFieldException::class.id, + java.lang.NoSuchMethodException::class.id, + java.lang.reflect.InvocationTargetException::class.id + ) + else -> error("Unknown util method ${this@findExceptionTypes}") + } + } + } + + /** + * This sealed class describes different extents of suitability (or matching) + * between an expression (in the role of a field accessor) and a field. + * + * In other words, this class and its inheritors describe if a given object (accessor) + * can be used to access a field, and if so, then are there any additional actions required (like type cast). + */ + private sealed class FieldAccessorSuitability { + /** + * Field can be accessed by a given accessor directly + */ + object Suitable : FieldAccessorSuitability() + + /** + * Field can be accessed by a given accessor, but it has to be type cast to the [targetType] + */ + class RequiresTypeCast(val targetType: ClassId) : FieldAccessorSuitability() + + /** + * Field can only be accessed by a given accessor via reflection. + * For example, if the accessor's type is inaccessible from the current package, + * so we cannot declare a variable of this type or perform a type cast. + * But there may be other cases. For example, we also cannot use + * anonymous classes' names in the code, so reflection may be required. + */ + object ReflectionOnly : FieldAccessorSuitability() + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/access/CgFieldStateManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/access/CgFieldStateManager.kt new file mode 100644 index 0000000000..33fea7ed75 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/access/CgFieldStateManager.kt @@ -0,0 +1,264 @@ +package org.utbot.framework.codegen.services.access + +import org.utbot.framework.codegen.domain.builtin.forName +import org.utbot.framework.codegen.domain.builtin.getArrayElement +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgGetJavaClass +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.tree.CgComponents.getCallableAccessManagerBy +import org.utbot.framework.codegen.tree.CgComponents.getStatementConstructorBy +import org.utbot.framework.codegen.tree.CgFieldState +import org.utbot.framework.codegen.tree.CgStatementConstructor +import org.utbot.framework.codegen.tree.FieldStateCache +import org.utbot.framework.codegen.tree.classCgClassId +import org.utbot.framework.codegen.tree.getFieldVariableName +import org.utbot.framework.codegen.tree.getStaticFieldVariableName +import org.utbot.framework.codegen.tree.needExpectedDeclaration +import org.utbot.framework.codegen.util.at +import org.utbot.framework.codegen.util.isAccessibleFrom +import org.utbot.framework.codegen.util.canBeReadFrom +import org.utbot.framework.codegen.util.stringLiteral +import org.utbot.framework.fields.ArrayElementAccess +import org.utbot.framework.fields.FieldAccess +import org.utbot.framework.fields.FieldPath +import org.utbot.framework.fields.ModifiedFields +import org.utbot.framework.fields.StateModificationInfo +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtSymbolicExecution +import org.utbot.framework.plugin.api.util.* +import org.utbot.framework.util.hasThisInstance +import org.utbot.fuzzer.UtFuzzedExecution +import java.lang.reflect.Array + +interface CgFieldStateManager { + fun rememberInitialEnvironmentState(info: StateModificationInfo) + fun rememberFinalEnvironmentState(info: StateModificationInfo) +} + +internal class CgFieldStateManagerImpl(val context: CgContext) + : CgContextOwner by context, + CgFieldStateManager, + CgCallableAccessManager by getCallableAccessManagerBy(context), + CgStatementConstructor by getStatementConstructorBy(context) { + + override fun rememberInitialEnvironmentState(info: StateModificationInfo) { + rememberThisInstanceState(info, FieldState.INITIAL) + rememberArgumentsState(info, FieldState.INITIAL) + rememberStaticFieldsState(info, FieldState.INITIAL) + } + + override fun rememberFinalEnvironmentState(info: StateModificationInfo) { + rememberThisInstanceState(info, FieldState.FINAL) + rememberArgumentsState(info, FieldState.FINAL) + rememberStaticFieldsState(info, FieldState.FINAL) + } + + /** + * [variablePrefix] is used as a name prefix for variables + * that contain the initial or final states of some fields. + */ + private enum class FieldState(val variablePrefix: String) { + INITIAL("initial"), + FINAL("final") + } + + private fun rememberThisInstanceState(info: StateModificationInfo, state: FieldState) { + when (currentExecution) { + is UtSymbolicExecution -> { + if (!(currentExecution!! as UtSymbolicExecution).hasThisInstance()) { + return + } + val thisInstance = context.thisInstance!! + val modifiedFields = info.thisInstance + // by now this instance variable must have already been created + saveFieldsState(thisInstance, modifiedFields, statesCache.thisInstance, state) + } + is UtFuzzedExecution -> { + return + } + else -> { + return + } + } + } + + private fun rememberArgumentsState(info: StateModificationInfo, state: FieldState) { + // by now variables of all arguments must have already been created + for ((i, argument) in context.methodArguments.withIndex()) { + if (i > info.parameters.lastIndex) break + + // TODO no one understands what is going on here; need to rewrite it one day or add docs + val modifiedFields = info.parameters[i] + saveFieldsState(argument, modifiedFields, statesCache.arguments[i], state) + } + } + + private fun rememberStaticFieldsState(info: StateModificationInfo, state: FieldState) { + for ((classId, modifiedStaticFields) in info.staticFields) { + saveStaticFieldsState(classId, modifiedStaticFields, state) + statesCache.classesWithStaticFields[classId]!!.before + } + } + + private fun getStaticFieldStateVariableName(owner: ClassId, path: FieldPath, state: FieldState): String = + state.variablePrefix + getStaticFieldVariableName(owner, path).capitalize() + + private fun getFieldStateVariableName(owner: CgValue, path: FieldPath, state: FieldState): String = + state.variablePrefix + getFieldVariableName(owner, path).capitalize() + + /** + * For initial state we only need to create variables for ref type field values, + * because in order to assert inequality of ref type fields we need both initial + * and final state variables. + * + * On the contrary, when the field is not of ref type, + * then we will only need its final state and the assertion will make sure + * that this final state is equal to its expected value. + * + * Assertion examples: + * - ref type fields: + * + * `assertFalse(initialState == finalState);` + * + * - non-ref type fields: + * + * `assertEquals(5, finalState);` + */ + private fun saveFieldsState( + owner: CgValue, + modifiedFields: ModifiedFields, + cache: FieldStateCache, + state: FieldState + ) { + if (modifiedFields.isEmpty()) return + emptyLineIfNeeded() + val fields = when (state) { + FieldState.INITIAL -> modifiedFields + .filter { it.path.elements.isNotEmpty() && !it.path.fieldType.isPrimitive } + .filter { needExpectedDeclaration(it.after) } + FieldState.FINAL -> modifiedFields + } + for ((path, before, after) in fields) { + val customName = getFieldStateVariableName(owner, path, state) + val variable = variableForFieldState(owner, path, customName) + when (state) { + FieldState.INITIAL -> cache.before[path] = CgFieldState(variable, before) + FieldState.FINAL -> cache.after[path] = CgFieldState(variable, after) + } + } + } + + private fun saveStaticFieldsState( + owner: ClassId, + modifiedFields: ModifiedFields, + state: FieldState + ) { + if (modifiedFields.isNotEmpty()) { + emptyLineIfNeeded() + } + for (modifiedField in modifiedFields) { + val (path, before, after) = modifiedField + val customName = getStaticFieldStateVariableName(owner, path, state) + val variable = variableForStaticFieldState(owner, path, customName) + val cache = statesCache.classesWithStaticFields[owner]!! + when (state) { + FieldState.INITIAL -> cache.before[path] = CgFieldState(variable, before) + FieldState.FINAL -> cache.after[path] = CgFieldState(variable, after) + } + } + } + + private fun CgExpression.getFieldBy(fieldPath: FieldPath, customName: String? = null): CgVariable { + val path = fieldPath.elements + var lastAccessibleIndex = path.lastIndex + + // type of current accessed element, starting from current expression and followed by elements from path + var curType = type + for ((index, fieldPathElement) in path.withIndex()) { + when (fieldPathElement) { + is FieldAccess -> { + if (!fieldPathElement.field.canBeReadFrom(context, curType)) { + lastAccessibleIndex = index - 1 + break + } + + // if previous field has type that does not have current field, this field is inaccessible + if (index > 0 && !path[index - 1].type.hasField(fieldPathElement.field)) { + lastAccessibleIndex = index - 1 + break + } + } + is ArrayElementAccess -> { + // cannot use direct array access from not array type + if (!curType.isArray) { + lastAccessibleIndex = index - 1 + break + } + } + } + + curType = fieldPathElement.type + } + + var index = 0 + var currentFieldType = this.type + + val lastPublicAccessor = generateSequence(this) { prev -> + if (index > lastAccessibleIndex) return@generateSequence null + val newElement = path[index++] + currentFieldType = newElement.type + when (newElement) { + is FieldAccess -> prev[newElement.field] + is ArrayElementAccess -> prev.at(newElement.index) + } + }.last() + + if (index == path.size) { + return newVar(currentFieldType, customName) { lastPublicAccessor } + } + + val lastPublicFieldVariable = lastPublicAccessor as? CgVariable ?: newVar(currentFieldType) { lastPublicAccessor } + return generateSequence(lastPublicFieldVariable) { prev -> + if (index > path.lastIndex) return@generateSequence null + val passedPath = FieldPath(path.subList(0, index + 1)) + val name = if (index == path.lastIndex) customName else getFieldVariableName(prev, passedPath) + + val newElement = path[index++] + val expression = when (newElement) { + is FieldAccess -> { + val fieldId = newElement.field + utilsClassId[getFieldValue](prev, fieldId.declaringClass.name, fieldId.name) + } + is ArrayElementAccess -> { + Array::class.id[getArrayElement](prev, newElement.index) + } + } + newVar(newElement.type, name) { expression } + }.last() + } + + private fun variableForFieldState(owner: CgValue, fieldPath: FieldPath, customName: String? = null): CgVariable { + return owner.getFieldBy(fieldPath, customName) + } + + private fun variableForStaticFieldState(owner: ClassId, fieldPath: FieldPath, customName: String?): CgVariable { + val firstField = (fieldPath.elements.first() as FieldAccess).field + val firstAccessor = if (owner.isAccessibleFrom(testClassPackageName) && firstField.canBeReadFrom(context, owner)) { + owner[firstField] + } else { + // TODO: there is a function getClassOf() for these purposes, but it is not accessible from here for now + val ownerClass = if (owner isAccessibleFrom testClassPackageName) { + CgGetJavaClass(owner) + } else { + newVar(classCgClassId) { Class::class.id[forName](owner.name) } + } + newVar(objectClassId) { utilsClassId[getStaticFieldValue](ownerClass, stringLiteral(firstField.name)) } + } + val path = fieldPath.elements + val remainingPath = fieldPath.copy(elements = path.drop(1)) + return firstAccessor.getFieldBy(remainingPath, customName) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/MockFrameworkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/MockFrameworkManager.kt new file mode 100644 index 0000000000..f6e75a5e58 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/MockFrameworkManager.kt @@ -0,0 +1,494 @@ +package org.utbot.framework.codegen.services.framework + +import org.utbot.framework.codegen.domain.MockitoStaticMocking +import org.utbot.framework.codegen.domain.NoStaticMocking +import org.utbot.framework.codegen.domain.builtin.any +import org.utbot.framework.codegen.domain.builtin.anyBoolean +import org.utbot.framework.codegen.domain.builtin.anyByte +import org.utbot.framework.codegen.domain.builtin.anyChar +import org.utbot.framework.codegen.domain.builtin.anyDouble +import org.utbot.framework.codegen.domain.builtin.anyFloat +import org.utbot.framework.codegen.domain.builtin.anyInt +import org.utbot.framework.codegen.domain.builtin.anyLong +import org.utbot.framework.codegen.domain.builtin.anyOfClass +import org.utbot.framework.codegen.domain.builtin.anyShort +import org.utbot.framework.codegen.domain.builtin.argumentMatchersClassId +import org.utbot.framework.codegen.domain.builtin.doNothingMethodId +import org.utbot.framework.codegen.domain.builtin.mockMethodId +import org.utbot.framework.codegen.domain.builtin.mockedConstructionContextClassId +import org.utbot.framework.codegen.domain.builtin.mockitoClassId +import org.utbot.framework.codegen.domain.builtin.thenReturnMethodId +import org.utbot.framework.codegen.domain.builtin.whenMethodId +import org.utbot.framework.codegen.domain.builtin.whenStubberMethodId +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgAnonymousFunction +import org.utbot.framework.codegen.domain.models.CgAssignment +import org.utbot.framework.codegen.domain.models.CgConstructorCall +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgExecutableCall +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration +import org.utbot.framework.codegen.domain.models.CgRunnable +import org.utbot.framework.codegen.domain.models.CgStatement +import org.utbot.framework.codegen.domain.models.CgStatementExecutableCall +import org.utbot.framework.codegen.domain.models.CgStaticRunnable +import org.utbot.framework.codegen.domain.models.CgSwitchCase +import org.utbot.framework.codegen.domain.models.CgSwitchCaseLabel +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.services.CgNameGenerator +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.services.access.CgCallableAccessManagerImpl +import org.utbot.framework.codegen.tree.CgComponents.getNameGeneratorBy +import org.utbot.framework.codegen.tree.CgComponents.getVariableConstructorBy +import org.utbot.framework.codegen.tree.CgStatementConstructor +import org.utbot.framework.codegen.tree.CgStatementConstructorImpl +import org.utbot.framework.codegen.tree.CgVariableConstructor +import org.utbot.framework.codegen.tree.hasAmbiguousOverloadsOf +import org.utbot.framework.codegen.tree.importIfNeeded +import org.utbot.framework.codegen.util.isAccessibleFrom +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.TypeParameters +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation +import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation +import org.utbot.framework.plugin.api.util.atomicIntegerClassId +import org.utbot.framework.plugin.api.util.atomicIntegerGet +import org.utbot.framework.plugin.api.util.atomicIntegerGetAndIncrement +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.byteClassId +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.voidClassId +import java.util.* + +abstract class CgVariableConstructorComponent(val context: CgContext) : + CgContextOwner by context, + CgCallableAccessManager by CgCallableAccessManagerImpl(context), + CgStatementConstructor by CgStatementConstructorImpl(context) { + + val nameGenerator: CgNameGenerator = getNameGeneratorBy(context) + val variableConstructor: CgVariableConstructor by lazy { getVariableConstructorBy(context) } + + fun mockitoArgumentMatchersFor(executable: ExecutableId): Array = + executable.parameters.map { + val matcher = it.mockitoAnyMatcher(executable.classId.hasAmbiguousOverloadsOf(executable)) + if (matcher != anyOfClass) argumentMatchersClassId[matcher]() else matchByClass(it) + }.toTypedArray() + + /** + * Clears all resources required for [currentExecution]. + */ + open fun clearExecutionResources() { + // do nothing by default + } + + // TODO: implement other similar methods like thenThrow, etc. + fun CgMethodCall.thenReturn(returnType: ClassId, vararg args: CgValue) { + val castedArgs = args + // guard args to reuse typecast creation logic + .map { if (it.type == returnType) it else guardExpression(returnType, it).expression } + .toTypedArray() + + +this[thenReturnMethodId](*castedArgs) + } + + fun ClassId.mockitoAnyMatcher(withExplicitClass: Boolean): MethodId = + when (this) { + byteClassId -> anyByte + charClassId -> anyChar + shortClassId -> anyShort + intClassId -> anyInt + longClassId -> anyLong + floatClassId -> anyFloat + doubleClassId -> anyDouble + booleanClassId -> anyBoolean + // we cannot match by string here + // because anyString accepts only non-nullable strings but argument could be null + else -> if (withExplicitClass) anyOfClass else any + } + + private fun matchByClass(id: ClassId): CgMethodCall = + argumentMatchersClassId[anyOfClass](getClassOf(id)) +} + +class MockFrameworkManager(context: CgContext) : CgVariableConstructorComponent(context) { + + private val objectMocker = MockitoMocker(context) + private val staticMocker = when (context.staticsMocking) { + is NoStaticMocking -> null + is MockitoStaticMocking -> MockitoStaticMocker(context, objectMocker) + else -> null + } + + /** + * Precondition: in the given [model] flag [UtCompositeModel.isMock] must be true. + * @return a variable representing a created mock object. + */ + fun createMockFor(model: UtCompositeModel, baseName: String): CgVariable = withMockFramework { + require(model.isMock) { "Mock model is expected in MockObjectConstructor" } + + objectMocker.createMock(model, baseName) + } + + fun createMockForVariable(model: UtCompositeModel, variable: CgVariable) = + withMockFramework { + require(model.isMock) { "Mock model $model is expected in MockObjectConstructor" } + objectMocker.mockForVariable(model, variable) + } + + fun mockNewInstance(mock: UtNewInstanceInstrumentation) { + staticMocker?.mockNewInstance(mock) + } + + fun mockStaticMethodsOfClass(classId: ClassId, methodMocks: List) { + staticMocker?.mockStaticMethodsOfClass(classId, methodMocks) + } + + override fun clearExecutionResources() { + staticMocker?.clearExecutionResources() + } + + internal fun getAndClearMethodResources(): List? = + if (staticMocker is MockitoStaticMocker) staticMocker.copyAndClearMockResources() else null +} + +private abstract class ObjectMocker( + context: CgContext +) : CgVariableConstructorComponent(context) { + abstract fun createMock(model: UtCompositeModel, baseName: String): CgVariable + + abstract fun mock(clazz: CgExpression): CgMethodCall + + abstract fun `when`(call: CgExecutableCall): CgMethodCall +} + +private abstract class StaticMocker( + context: CgContext +) : CgVariableConstructorComponent(context) { + abstract fun mockNewInstance(mock: UtNewInstanceInstrumentation) + abstract fun mockStaticMethodsOfClass(classId: ClassId, methodMocks: List) +} + +private class MockitoMocker(context: CgContext) : ObjectMocker(context) { + + override fun createMock(model: UtCompositeModel, baseName: String): CgVariable { + val modelClass = getClassOf(model.classId) + val mockObject = + newVar(model.classId, model = model, baseName = baseName, isMock = true) { mock(modelClass) } + + mockForVariable(model, mockObject) + + return mockObject + } + + fun mockForVariable(model: UtCompositeModel, mockObject: CgVariable) { + for ((executable, values) in model.mocks) { + val matchers = mockitoArgumentMatchersFor(executable) + + if (executable.returnType == voidClassId) { + when (executable) { + // All constructors are considered like void methods, but it is proposed to be changed to test constructors better. + is ConstructorId -> continue + // Sometimes void methods are called explicitly, e.g. annotated with @Mock fields in Spring test classes. + // We would like to mark that this field is used and must not be removed from test class. + // Without `doNothing` call Intellij Idea suggests removing this field as unused. + is MethodId -> { + +mockitoClassId[doNothingMethodId]()[whenStubberMethodId](mockObject)[executable](*matchers) + } + else -> error("Only MethodId and ConstructorId was expected to appear in simple mocker but got $executable") + } + } else { + when (executable) { + is MethodId -> { + if (executable.parameters.any { !it.isAccessibleFrom(testClassPackageName) }) { + error("Cannot mock method $executable with not accessible parameters" ) + } + + if (!executable.isAccessibleFrom(testClassPackageName)) { + error("Cannot mock method $executable as it is not accessible from package $testClassPackageName") + } + + val results = values + .map { value -> + // Sometimes we need mocks returning itself, e.g. for StringBuilder.append method + if (value != model) variableConstructor.getOrCreateVariable(value) else mockObject + } + .toTypedArray() + `when`(mockObject[executable](*matchers)).thenReturn(executable.returnType, *results) + } + else -> error("Only MethodId was expected to appear in simple mocker but got $executable") + } + } + } + } + + override fun mock(clazz: CgExpression): CgMethodCall = + mockitoClassId[mockMethodId](clazz) + + override fun `when`(call: CgExecutableCall): CgMethodCall = + mockitoClassId[whenMethodId](call) +} + +private class MockitoStaticMocker(context: CgContext, private val mocker: ObjectMocker) : StaticMocker(context) { + private val resources = mutableListOf() + private val mockedStaticForMethods = mutableMapOf() + private val mockedStaticConstructions = mutableSetOf() + + override fun mockNewInstance(mock: UtNewInstanceInstrumentation) { + val classId = mock.classId + if (classId in mockedStaticConstructions) return + + val mockClassCounter = CgDeclaration( + atomicIntegerClassId, + nameGenerator.variableName(MOCK_CLASS_COUNTER_NAME), + CgConstructorCall(ConstructorId(atomicIntegerClassId, emptyList()), emptyList()) + ) + + val mocksExecutablesAnswers = mock + .instances + .filterIsInstance() + .filter { it.isMock } + .map { + // If there are no expected answers for the particular executable + // (this executable is never invoked during the execution, for example), + // we do not need to consider this executable at all. + it.mocks.filterTo(mutableMapOf()) { executableAnswers -> executableAnswers.value.isNotEmpty() } + } + + val modelClass = getClassOf(classId) + + val mockConstructionInitializer = mockConstruction( + modelClass, + classId, + mocksExecutablesAnswers, + mockClassCounter.variable + ) + + if (mockConstructionInitializer.isMockClassCounterRequired) { + // We should insert the counter declaration only if we use this counter, for better readability. + +mockClassCounter + } + + // TODO this behavior diverges with expected by the symbolic engine, + // see https://github.com/UnitTestBot/UTBotJava/issues/1953 for more details + val mockedConstructionDeclaration = CgDeclaration( + MockitoStaticMocking.mockedConstructionClassId, + nameGenerator.variableName(MOCKED_CONSTRUCTION_NAME), + mockConstructionInitializer.mockConstructionCall + ) + + importIfNeeded(MockitoStaticMocking.mockedConstructionClassId) + importIfNeeded(classId) + + resources += mockedConstructionDeclaration + +CgAssignment(mockedConstructionDeclaration.variable, mockConstructionInitializer.mockConstructionCall) + mockedStaticConstructions += classId + } + + override fun mockStaticMethodsOfClass(classId: ClassId, methodMocks: List) { + for ((methodId, values) in methodMocks) { + if (methodId.parameters.any { !it.isAccessibleFrom(testClassPackageName) }) { + error("Cannot mock static method $methodId with not accessible parameters" ) + } + + val matchers = mockitoArgumentMatchersFor(methodId) + val mockedStaticDeclaration = getOrCreateMockStatic(classId) + val mockedStaticVariable = mockedStaticDeclaration.variable + val methodRunnable = if (matchers.isEmpty()) { + CgStaticRunnable(type = methodId.returnType, classId, methodId) + } else { + CgAnonymousFunction( + type = methodId.returnType, + parameters = emptyList(), + listOf( + CgStatementExecutableCall( + CgMethodCall( + caller = null, + methodId, + matchers.toList() + ) + ) + ) + ) + } + // void method + if (methodId.returnType == voidClassId) { + // we do not generate additional code for void methods because they do nothing by default + continue + } + + if (!methodId.isAccessibleFrom(testClassPackageName)) { + error("Cannot mock static method $methodId as it is not accessible from package $testClassPackageName") + } + + val results = values.map { variableConstructor.getOrCreateVariable(it) }.toTypedArray() + `when`(mockedStaticVariable, methodRunnable).thenReturn(methodId.returnType, *results) + } + } + + override fun clearExecutionResources() { + resources.clear() + mockedStaticForMethods.clear() + mockedStaticConstructions.clear() + } + + private fun getOrCreateMockStatic(classId: ClassId): CgDeclaration = + mockedStaticForMethods.getOrPut(classId) { + val modelClass = getClassOf(classId) + val classMockStaticCall = mockStatic(modelClass) + val mockedStaticVariableName = nameGenerator.variableName(MOCKED_STATIC_NAME) + CgDeclaration( + MockitoStaticMocking.mockedStaticClassId, + mockedStaticVariableName, + classMockStaticCall + ).also { + resources += it + +CgAssignment(it.variable, classMockStaticCall) + + importIfNeeded(MockitoStaticMocking.mockedStaticClassId) + importIfNeeded(classId) + } + } + + private fun mockConstruction( + clazz: CgExpression, + classId: ClassId, + mocksWhenAnswers: List>>, + mockClassCounter: CgVariable + ): MockConstructionBlock { + val mockParameter = variableConstructor.declareParameter( + classId, + nameGenerator.variableName( + classId, + isMock = true + ) + ) + + val mockAnswerStatements = mutableMapOf>() + + for ((index, mockWhenAnswers) in mocksWhenAnswers.withIndex()) { + val statements = mutableListOf() + for ((executable, values) in mockWhenAnswers) { + // For now, all constructors are considered like void methods, but it is proposed to be changed + // for better constructors testing. + if (executable.returnType == voidClassId) continue + + require(values.isNotEmpty()) { + "Expected at least one mocked answer for $executable but got 0" + } + + when (executable) { + is MethodId -> { + val matchers = mockitoArgumentMatchersFor(executable) + val results = values.map { variableConstructor.getOrCreateVariable(it) }.toTypedArray() + statements += CgStatementExecutableCall( + mocker.`when`(mockParameter[executable](*matchers))[thenReturnMethodId](*results) + ) + } + is ConstructorId -> error("Expected MethodId but got ConstructorId $executable") + } + } + + mockAnswerStatements[index] = statements + } + + val answerValues = mockAnswerStatements.values.let { + val uniqueMockingStatements = it.distinct() + + // If we have only one unique mocking statement, we do not need switch-case with all statements - we can + // use only this unique statement. + if (uniqueMockingStatements.size == 1) { + uniqueMockingStatements + } else { + it + } + } + // If we have no more than one branch or all branches are empty, + // it means we do not need this switch and mock counter itself at all. + val atMostOneBranchOrAllEmpty = answerValues.size <= 1 || answerValues.all { statements -> statements.isEmpty() } + val mockConstructionBody = if (atMostOneBranchOrAllEmpty) { + answerValues.singleOrNull() ?: emptyList() + } else { + val caseLabels = mockAnswerStatements.map { (index, statements) -> + CgSwitchCaseLabel(CgLiteral(intClassId, index), statements) + } + val switchCase = CgSwitchCase(mockClassCounter[atomicIntegerGet](), caseLabels) + + listOf(switchCase, CgStatementExecutableCall(mockClassCounter[atomicIntegerGetAndIncrement]())) + } + + val contextParameter = variableConstructor.declareParameter( + mockedConstructionContextClassId, + nameGenerator.variableName("context") + ) + + val answersBlock = CgAnonymousFunction( + voidClassId, + listOf(mockParameter, contextParameter).map { CgParameterDeclaration(it, isVararg = false) }, + mockConstructionBody + ) + + importIfNeeded(mockedConstructionContextClassId) + importIfNeeded(classId) + + return MockConstructionBlock( + mockitoClassId[MockitoStaticMocking.mockConstructionMethodId](clazz, answersBlock), + !atMostOneBranchOrAllEmpty + ) + } + + /** + * Represents a body for invocation of the [MockitoStaticMocking.mockConstructionMethodId] method and information + * whether we need to use a counter for different mocking invocations + * (i.e., on each mocking we expect possibly different results). + */ + private data class MockConstructionBlock( + val mockConstructionCall: CgMethodCall, + val isMockClassCounterRequired: Boolean + ) + + private fun mockStatic(clazz: CgExpression): CgMethodCall = + mockitoClassId[MockitoStaticMocking.mockStaticMethodId](clazz) + + private fun `when`( + mockedStatic: CgVariable, + runnable: CgExpression, + ): CgMethodCall { + val typeParams = when (runnable) { + is CgRunnable, is CgAnonymousFunction -> listOf(runnable.type) + else -> error("Unsupported runnable type: $runnable") + } + return CgMethodCall( + mockedStatic, + MockitoStaticMocking.mockedStaticWhen(nullable = mockedStatic.type.isNullable), + listOf(runnable), + TypeParameters(typeParams), + ) + } + + + fun copyAndClearMockResources(): List? { + val copiedResources = resources.toList() + clearExecutionResources() + + return copiedResources.ifEmpty { null } + } + + companion object { + private const val MOCKED_CONSTRUCTION_NAME = "mockedConstruction" + private const val MOCKED_STATIC_NAME = "mockedStatic" + private const val MOCK_CLASS_COUNTER_NAME = "mockClassCounter" + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/TestFrameworkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/TestFrameworkManager.kt new file mode 100644 index 0000000000..a36ee470cf --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/TestFrameworkManager.kt @@ -0,0 +1,563 @@ +package org.utbot.framework.codegen.services.framework + +import org.utbot.framework.codegen.domain.Junit4 +import org.utbot.framework.codegen.domain.Junit5 +import org.utbot.framework.codegen.domain.TestNg +import org.utbot.framework.codegen.domain.context.TestClassContext +import org.utbot.framework.codegen.domain.builtin.forName +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.* +import org.utbot.framework.codegen.domain.models.AnnotationTarget.* +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.tree.CgComponents.getCallableAccessManagerBy +import org.utbot.framework.codegen.tree.CgComponents.getStatementConstructorBy +import org.utbot.framework.codegen.tree.addToListMethodId +import org.utbot.framework.codegen.tree.argumentsClassId +import org.utbot.framework.codegen.tree.argumentsMethodId +import org.utbot.framework.codegen.tree.classCgClassId +import org.utbot.framework.codegen.tree.importIfNeeded +import org.utbot.framework.codegen.tree.setArgumentsArrayElement +import org.utbot.framework.codegen.util.isAccessibleFrom +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.codegen.util.stringLiteral +import org.utbot.framework.plugin.api.BuiltinMethodId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.util.booleanArrayClassId +import org.utbot.framework.plugin.api.util.byteArrayClassId +import org.utbot.framework.plugin.api.util.charArrayClassId +import org.utbot.framework.plugin.api.util.doubleArrayClassId +import org.utbot.framework.plugin.api.util.floatArrayClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intArrayClassId +import org.utbot.framework.plugin.api.util.longArrayClassId +import org.utbot.framework.plugin.api.util.shortArrayClassId +import org.utbot.framework.plugin.api.util.stringClassId +import java.util.concurrent.TimeUnit + +@Suppress("MemberVisibilityCanBePrivate") +abstract class TestFrameworkManager(val context: CgContext) + : CgContextOwner by context, + CgCallableAccessManager by getCallableAccessManagerBy(context) { + + val assertions = context.testFramework.assertionsClass + + val assertSame = context.testFramework.assertSame + + val assertEquals = context.testFramework.assertEquals + val assertFloatEquals = context.testFramework.assertFloatEquals + val assertDoubleEquals = context.testFramework.assertDoubleEquals + + val assertNull = context.testFramework.assertNull + val assertNotNull = context.testFramework.assertNotNull + val assertTrue = context.testFramework.assertTrue + val assertFalse = context.testFramework.assertFalse + + val fail = context.testFramework.fail + val kotlinFail = context.testFramework.kotlinFail + + val assertArrayEquals = context.testFramework.assertArrayEquals + val assertBooleanArrayEquals = context.testFramework.assertBooleanArrayEquals + val assertByteArrayEquals = context.testFramework.assertByteArrayEquals + val assertCharArrayEquals = context.testFramework.assertCharArrayEquals + val assertShortArrayEquals = context.testFramework.assertShortArrayEquals + val assertIntArrayEquals = context.testFramework.assertIntArrayEquals + val assertLongArrayEquals = context.testFramework.assertLongArrayEquals + val assertFloatArrayEquals = context.testFramework.assertFloatArrayEquals + val assertDoubleArrayEquals = context.testFramework.assertDoubleArrayEquals + + // Points to the class, into which data provider methods in parametrized tests should be put (current or outermost). + // It is needed, because data provider methods are static and thus may not be put into inner classes, e.g. in JUnit5 + // all data providers should be placed in the outermost class. + protected abstract val dataProviderMethodsHolder: TestClassContext + + protected val statementConstructor = getStatementConstructorBy(context) + + abstract fun addAnnotationForNestedClasses() + + /** + * Determines whether appearance of expected exception in test method breaks current test execution or not. + */ + abstract val isExpectedExceptionExecutionBreaking: Boolean + + protected open val timeoutArgumentName: String = "timeout" + + open fun assertEquals(expected: CgValue, actual: CgValue) { + +assertions[assertEquals](expected, actual) + } + + open fun assertSame(expected: CgValue, actual: CgValue) { + +assertions[assertSame](expected, actual) + } + + open fun assertFloatEquals(expected: CgExpression, actual: CgExpression, delta: Any) { + +assertions[assertFloatEquals](expected, actual, delta) + } + + open fun assertDoubleEquals(expected: CgExpression, actual: CgExpression, delta: Any) { + +assertions[assertDoubleEquals](expected, actual, delta) + } + + open fun getArrayEqualsAssertion(arrayType: ClassId, expected: CgExpression, actual: CgExpression): CgMethodCall = + when (arrayType) { + booleanArrayClassId -> assertions[assertBooleanArrayEquals](expected, actual) + byteArrayClassId -> assertions[assertByteArrayEquals](expected, actual) + charArrayClassId -> assertions[assertCharArrayEquals](expected, actual) + shortArrayClassId -> assertions[assertShortArrayEquals](expected, actual) + intArrayClassId -> assertions[assertIntArrayEquals](expected, actual) + longArrayClassId -> assertions[assertLongArrayEquals](expected, actual) + floatArrayClassId -> assertions[assertFloatArrayEquals](expected, actual) + doubleArrayClassId -> assertions[assertDoubleArrayEquals](expected, actual) + else -> assertions[assertArrayEquals](expected, actual) + } + + fun assertArrayEquals(arrayType: ClassId, expected: CgExpression, actual: CgExpression) { + +getArrayEqualsAssertion(arrayType, expected, actual) + } + + open fun getDeepEqualsAssertion(expected: CgExpression, actual: CgExpression): CgMethodCall { + requiredUtilMethods += setOf( + utilMethodProvider.deepEqualsMethodId, + utilMethodProvider.arraysDeepEqualsMethodId, + utilMethodProvider.iterablesDeepEqualsMethodId, + utilMethodProvider.streamsDeepEqualsMethodId, + utilMethodProvider.mapsDeepEqualsMethodId, + utilMethodProvider.hasCustomEqualsMethodId + ) + // TODO we cannot use common assertEquals because of using custom deepEquals + // For this reason we have to use assertTrue here + // Unfortunately, if test with assertTrue fails, it gives non informative message false != true + // Thus, we need to provide custom message to assertTrue showing compared objects correctly + // SAT-1345 + return assertions[assertTrue](utilsClassId[deepEquals](expected, actual)) + } + + @Suppress("unused") + fun assertDeepEquals(expected: CgExpression, actual: CgExpression) { + +getDeepEqualsAssertion(expected, actual) + } + + open fun getFloatArrayEqualsAssertion(expected: CgExpression, actual: CgExpression, delta: Any): CgMethodCall = + assertions[assertFloatArrayEquals](expected, actual, delta) + + fun assertFloatArrayEquals(expected: CgExpression, actual: CgExpression, delta: Any) { + +getFloatArrayEqualsAssertion(expected, actual, delta) + } + + open fun getDoubleArrayEqualsAssertion(expected: CgExpression, actual: CgExpression, delta: Any): CgMethodCall = + assertions[assertDoubleArrayEquals](expected, actual, delta) + + fun assertDoubleArrayEquals(expected: CgExpression, actual: CgExpression, delta: Any) { + +getDoubleArrayEqualsAssertion(expected, actual, delta) + } + + fun assertNull(actual: CgExpression) { + +assertions[assertNull](actual) + } + + fun assertBoolean(expected: Boolean, actual: CgExpression) { + if (expected) { + +assertions[assertTrue](actual) + } else { + +assertions[assertFalse](actual) + } + } + + fun assertBoolean(actual: CgExpression) = assertBoolean(expected = true, actual) + + fun fail(actual: CgExpression) { + // failure assertion may be implemented in different packages in Java and Kotlin + // more details at https://stackoverflow.com/questions/52967039/junit-5-assertions-fail-can-not-infer-type-in-kotlin + when (codegenLanguage) { + CodegenLanguage.JAVA -> +assertions[fail](actual) + CodegenLanguage.KOTLIN -> +assertions[kotlinFail](actual) + } + } + + // Exception expectation differs between test frameworks + // JUnit4 requires to add a specific argument to the test method annotation + // JUnit5 requires using method assertThrows() + // TestNg allows both approaches, we use similar to JUnit5 + abstract fun expectException(exception: ClassId, block: () -> Unit) + + /** + * Creates annotations for data provider method in parameterized tests + */ + abstract fun addDataProviderAnnotations(dataProviderMethodName: String) + + /** + * Creates declaration of argList collection in parameterized tests. + */ + abstract fun createArgList(length: Int): CgVariable + + abstract fun addParameterizedTestAnnotations(dataProviderMethodName: String?) + + abstract fun passArgumentsToArgsVariable(argsVariable: CgVariable, argsArray: CgVariable, executionIndex: Int) + + /** + * Most frameworks don't have special timeout assertion, so only tested + * method call is generated, while timeout is set using + * [timeout argument][timeoutArgumentName] of the test annotation + * + * @see setTestExecutionTimeout + */ + open fun expectTimeout(timeoutMs: Long, block: () -> Unit): Unit = block() + + open fun setTestExecutionTimeout(timeoutMs: Long) { + val timeout = CgNamedAnnotationArgument( + name = timeoutArgumentName, + value = timeoutMs.resolve() + ) + val testAnnotation = collectedMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId } + + if (testAnnotation is CgMultipleArgsAnnotation) { + testAnnotation.arguments += timeout + } else { + statementConstructor.addAnnotation( + classId = testFramework.testAnnotationId, + namedArguments = listOf(timeout), + target = Method, + ) + } + } + + + /** + * Add a short test's description depending on the test framework type: + */ + abstract fun addTestDescription(description: String) + + abstract fun disableTestMethod(reason: String) + + /** + * Adds @DisplayName annotation. + * + * Should be used only with JUnit 5. + * @see issue-576 on GitHub + */ + open fun addDisplayName(name: String) { + statementConstructor.addAnnotation(Junit5.displayNameClassId, stringLiteral(name), Method) + } + + protected fun ClassId.toExceptionClass(): CgExpression = + if (isAccessibleFrom(testClassPackageName)) { + CgGetJavaClass(this) + } else { + statementConstructor.newVar(classCgClassId) { Class::class.id[forName](name) } + } + + fun addDataProvider(dataProvider: CgMethod) { + dataProviderMethodsHolder.cgDataProviderMethods += dataProvider + } +} + +internal class TestNgManager(context: CgContext) : TestFrameworkManager(context) { + override val dataProviderMethodsHolder: TestClassContext + get() = currentTestClassContext + + override fun addAnnotationForNestedClasses() { } + + override val isExpectedExceptionExecutionBreaking: Boolean = false + + override val timeoutArgumentName: String = "timeOut" + + private val assertThrows: BuiltinMethodId + get() { + require(testFramework is TestNg) { "According to settings, TestNg was expected, but got: $testFramework" } + + return testFramework.assertThrows + } + + override fun assertEquals(expected: CgValue, actual: CgValue) = super.assertEquals(actual, expected) + + override fun assertFloatEquals(expected: CgExpression, actual: CgExpression, delta: Any) = + super.assertFloatEquals(actual, expected, delta) + + override fun assertDoubleEquals(expected: CgExpression, actual: CgExpression, delta: Any) = + super.assertDoubleEquals(actual, expected, delta) + + override fun getArrayEqualsAssertion(arrayType: ClassId, expected: CgExpression, actual: CgExpression): CgMethodCall = + super.getArrayEqualsAssertion(arrayType, actual, expected) + + override fun getDeepEqualsAssertion(expected: CgExpression, actual: CgExpression): CgMethodCall = + super.getDeepEqualsAssertion(actual, expected) + + override fun getFloatArrayEqualsAssertion(expected: CgExpression, actual: CgExpression, delta: Any): CgMethodCall = + super.getFloatArrayEqualsAssertion(actual, expected, delta) + + override fun getDoubleArrayEqualsAssertion(expected: CgExpression, actual: CgExpression, delta: Any): CgMethodCall = + super.getDoubleArrayEqualsAssertion(actual, expected, delta) + + override fun expectException(exception: ClassId, block: () -> Unit) { + require(testFramework is TestNg) { "According to settings, TestNg was expected, but got: $testFramework" } + val lambda = statementConstructor.lambda(testFramework.throwingRunnableClassId) { + runWithoutCollectingExceptions(block) + } + +assertions[assertThrows](exception.toExceptionClass(), lambda) + } + + override fun addDataProviderAnnotations(dataProviderMethodName: String) { + val nameArgument = CgNamedAnnotationArgument("name", stringLiteral(dataProviderMethodName)) + statementConstructor.addAnnotation( + classId = testFramework.methodSourceAnnotationId, + namedArguments = listOf(nameArgument), + target = Method, + ) + } + + override fun createArgList(length: Int) = + statementConstructor.newVar(testFramework.argListClassId, "argList") { + CgAllocateArray(testFramework.argListClassId, Array::class.java.id, length) + } + + override fun addParameterizedTestAnnotations(dataProviderMethodName: String?) { + val dataProviderArgument = + CgNamedAnnotationArgument("dataProvider", CgLiteral(stringClassId, dataProviderMethodName)) + statementConstructor.addAnnotation( + classId = testFramework.parameterizedTestAnnotationId, + namedArguments = listOf(dataProviderArgument), + target = Method, + ) + } + + override fun passArgumentsToArgsVariable(argsVariable: CgVariable, argsArray: CgVariable, executionIndex: Int) = + setArgumentsArrayElement(argsVariable, executionIndex, argsArray, statementConstructor) + + /** + * Supplements TestNG @Test annotation with a description. + * It looks like @Test(description="...") + * + * @see issue-576 on GitHub + */ + private fun addDescriptionAnnotation(description: String) { + val testAnnotation = + collectedMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId } + + val descriptionArgument = CgNamedAnnotationArgument("description", stringLiteral(description)) + if (testAnnotation is CgMultipleArgsAnnotation) { + testAnnotation.arguments += descriptionArgument + } else { + statementConstructor.addAnnotation( + classId = testFramework.testAnnotationId, + namedArguments = listOf(descriptionArgument), + target = Method, + ) + } + } + + override fun addTestDescription(description: String) = addDescriptionAnnotation(description) + + override fun disableTestMethod(reason: String) { + require(testFramework is TestNg) { "According to settings, TestNg was expected, but got: $testFramework" } + + val disabledAnnotationArgument = CgNamedAnnotationArgument( + name = "enabled", + value = false.resolve() + ) + + val descriptionArgumentName = "description" + val descriptionTestAnnotationArgument = CgNamedAnnotationArgument( + name = descriptionArgumentName, + value = reason.resolve() + ) + + val testAnnotation = collectedMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId } + if (testAnnotation is CgMultipleArgsAnnotation) { + testAnnotation.arguments += disabledAnnotationArgument + + val alreadyExistingDescriptionAnnotationArgument = testAnnotation.arguments.singleOrNull { + it.name == descriptionArgumentName + } + + // append new description to existing one + if (alreadyExistingDescriptionAnnotationArgument != null) { + val gluedDescription = with(alreadyExistingDescriptionAnnotationArgument) { + require(value is CgLiteral && value.value is String) { + "Expected description to be String literal but got ${value.type}" + } + + listOf(value.value, reason).joinToString("; ").resolve() + } + + testAnnotation.arguments.map { + if (it.name != descriptionArgumentName) return@map it + + CgNamedAnnotationArgument( + name = descriptionArgumentName, + value = gluedDescription + ) + } + } else { + testAnnotation.arguments += descriptionTestAnnotationArgument + } + } else { + statementConstructor.addAnnotation( + classId = testFramework.testAnnotationId, + namedArguments =listOf(disabledAnnotationArgument, descriptionTestAnnotationArgument), + target = Method, + ) + } + } +} + +internal class Junit4Manager(context: CgContext) : TestFrameworkManager(context) { + private val parametrizedTestsNotSupportedError: Nothing + get() = error("Parametrized tests are not supported for JUnit4") + + override val dataProviderMethodsHolder: TestClassContext + get() = parametrizedTestsNotSupportedError + + override fun addAnnotationForNestedClasses() { } + + override val isExpectedExceptionExecutionBreaking: Boolean = true + + override fun expectException(exception: ClassId, block: () -> Unit) { + require(testFramework is Junit4) { "According to settings, JUnit4 was expected, but got: $testFramework" } + + require(exception.isAccessibleFrom(testClassPackageName)) { + "Exception $exception is not accessible from package $testClassPackageName" + } + + val expected = CgNamedAnnotationArgument( + name = "expected", + value = createGetClassExpression(exception, codegenLanguage) + ) + val testAnnotation = collectedMethodAnnotations.singleOrNull { it.classId == testFramework.testAnnotationId } + if (testAnnotation is CgMultipleArgsAnnotation) { + // TODO: think about argument priorities, currently we always add on the last position + testAnnotation.arguments += expected + } else { + // TODO: what is we have `CgSingleArgAnnotation` without argument name and would like to add one more? + statementConstructor.addAnnotation( + classId = testFramework.testAnnotationId, + namedArguments = listOf(expected), + target = Method, + ) + } + block() + } + + override fun addDataProviderAnnotations(dataProviderMethodName: String) = + parametrizedTestsNotSupportedError + + override fun createArgList(length: Int) = + parametrizedTestsNotSupportedError + + override fun addParameterizedTestAnnotations(dataProviderMethodName: String?) = + parametrizedTestsNotSupportedError + + override fun passArgumentsToArgsVariable(argsVariable: CgVariable, argsArray: CgVariable, executionIndex: Int) = + parametrizedTestsNotSupportedError + + override fun addTestDescription(description: String) = Unit + + override fun disableTestMethod(reason: String) { + require(testFramework is Junit4) { "According to settings, JUnit4 was expected, but got: $testFramework" } + + val reasonArgument = CgNamedAnnotationArgument(name = "value", value = reason.resolve()) + statementConstructor.addAnnotation( + classId = testFramework.ignoreAnnotationClassId, + namedArguments = listOf(reasonArgument), + target = Method, + ) + } +} + +internal class Junit5Manager(context: CgContext) : TestFrameworkManager(context) { + override val dataProviderMethodsHolder: TestClassContext + get() = outerMostTestClassContext + + override fun addAnnotationForNestedClasses() { + require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } + statementConstructor.addAnnotation(testFramework.nestedTestClassAnnotationId, Class) + } + + override val isExpectedExceptionExecutionBreaking: Boolean = false + + private val assertThrows: BuiltinMethodId + get() { + require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } + return testFramework.assertThrows + } + + override fun expectException(exception: ClassId, block: () -> Unit) { + require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } + val lambda = statementConstructor.lambda(testFramework.executableClassId) { + runWithoutCollectingExceptions(block) + } + +assertions[assertThrows](exception.toExceptionClass(), lambda) + } + + override fun addDataProviderAnnotations(dataProviderMethodName: String) { } + + override fun createArgList(length: Int) = + statementConstructor.newVar(testFramework.argListClassId, "argList") { + val constructor = ConstructorId(testFramework.argListClassId, emptyList()) + constructor.invoke() + } + + override fun addParameterizedTestAnnotations(dataProviderMethodName: String?) { + statementConstructor.addAnnotation(testFramework.parameterizedTestAnnotationId, Method) + statementConstructor.addAnnotation( + testFramework.methodSourceAnnotationId, + "${outerMostTestClass.canonicalName}#$dataProviderMethodName", + Method, + ) + } + + override fun passArgumentsToArgsVariable(argsVariable: CgVariable, argsArray: CgVariable, executionIndex: Int) { + +argsVariable[addToListMethodId]( + argumentsClassId[argumentsMethodId](argsArray) + ) + } + + + override fun expectTimeout(timeoutMs : Long, block: () -> Unit) { + require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } + val lambda = statementConstructor.lambda(testFramework.executableClassId) { block() } + importIfNeeded(testFramework.durationClassId) + val duration = CgMethodCall(null, testFramework.ofMillis, listOf(timeoutMs.resolve())) + +assertions[testFramework.assertTimeoutPreemptively](duration, lambda) + } + + override fun addDisplayName(name: String) { + require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } + statementConstructor.addAnnotation(testFramework.displayNameClassId, name, Method) + } + + override fun setTestExecutionTimeout(timeoutMs: Long) { + require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } + + importIfNeeded(testFramework.timeunitClassId) + + statementConstructor.addAnnotation( + classId = Junit5.timeoutClassId, + namedArguments = listOf( + CgNamedAnnotationArgument( + name = "value", + value = timeoutMs.resolve(), + ), + CgNamedAnnotationArgument( + name = "unit", + value = CgEnumConstantAccess(testFramework.timeunitClassId, TimeUnit.MILLISECONDS.name), + ), + ), + target = Method, + ) + } + + override fun addTestDescription(description: String) = addDisplayName(description) + + override fun disableTestMethod(reason: String) { + require(testFramework is Junit5) { "According to settings, JUnit5 was expected, but got: $testFramework" } + + val reasonArgument = CgNamedAnnotationArgument(name = "value", value = reason.resolve()) + statementConstructor.addAnnotation( + classId = testFramework.disabledAnnotationClassId, + namedArguments = listOf(reasonArgument), + target = Method, + ) + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/CgLanguageAssistant.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/CgLanguageAssistant.kt new file mode 100644 index 0000000000..df92c641f3 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/CgLanguageAssistant.kt @@ -0,0 +1,76 @@ +package org.utbot.framework.codegen.services.language + +import org.utbot.framework.codegen.domain.context.TestClassContext +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.renderer.CgPrinter +import org.utbot.framework.codegen.renderer.CgAbstractRenderer +import org.utbot.framework.codegen.renderer.CgRendererContext +import org.utbot.framework.codegen.services.CgNameGenerator +import org.utbot.framework.codegen.services.CgNameGeneratorImpl +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.services.access.CgCallableAccessManagerImpl +import org.utbot.framework.codegen.services.access.CgFieldStateManager +import org.utbot.framework.codegen.services.access.CgFieldStateManagerImpl +import org.utbot.framework.codegen.tree.CgCustomAssertConstructor +import org.utbot.framework.codegen.tree.CgMethodConstructor +import org.utbot.framework.codegen.tree.CgSimpleCustomAssertConstructor +import org.utbot.framework.codegen.tree.CgStatementConstructor +import org.utbot.framework.codegen.tree.CgStatementConstructorImpl +import org.utbot.framework.codegen.tree.CgVariableConstructor +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage + +interface CgLanguageAssistant { + + companion object { + fun getByCodegenLanguage(language: CodegenLanguage) = when (language) { + CodegenLanguage.JAVA -> JavaCgLanguageAssistant + CodegenLanguage.KOTLIN -> KotlinCgLanguageAssistant + else -> throw UnsupportedOperationException() + } + } + + val outerMostTestClassContent: TestClassContext? + + val extension: String + + val languageKeywords: Set + + fun testClassName( + testClassCustomName: String?, + testClassPackageName: String, + classUnderTest: ClassId + ): Pair + + fun getNameGeneratorBy(context: CgContext): CgNameGenerator + fun getCallableAccessManagerBy(context: CgContext): CgCallableAccessManager + fun getStatementConstructorBy(context: CgContext): CgStatementConstructor + + fun getVariableConstructorBy(context: CgContext): CgVariableConstructor + + fun getMethodConstructorBy(context: CgContext): CgMethodConstructor + + fun getCustomAssertConstructorBy(context: CgContext): CgCustomAssertConstructor + + fun getCgFieldStateManager(context: CgContext): CgFieldStateManager + + fun getLanguageTestFrameworkManager(): LanguageTestFrameworkManager + fun cgRenderer(context: CgRendererContext, printer: CgPrinter): CgAbstractRenderer +} + +abstract class AbstractCgLanguageAssistant : CgLanguageAssistant { + override val outerMostTestClassContent: TestClassContext? get() = null + + override fun getNameGeneratorBy(context: CgContext): CgNameGenerator = CgNameGeneratorImpl(context) + override fun getCallableAccessManagerBy(context: CgContext): CgCallableAccessManager = + CgCallableAccessManagerImpl(context) + override fun getStatementConstructorBy(context: CgContext): CgStatementConstructor = CgStatementConstructorImpl(context) + + override fun getVariableConstructorBy(context: CgContext): CgVariableConstructor = CgVariableConstructor(context) + + override fun getMethodConstructorBy(context: CgContext): CgMethodConstructor = CgMethodConstructor(context) + override fun getCgFieldStateManager(context: CgContext): CgFieldStateManager = CgFieldStateManagerImpl(context) + + override fun getCustomAssertConstructorBy(context: CgContext): CgCustomAssertConstructor = + CgSimpleCustomAssertConstructor(context) +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/JavaCgLanguageAssistant.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/JavaCgLanguageAssistant.kt new file mode 100644 index 0000000000..dcdce897cd --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/JavaCgLanguageAssistant.kt @@ -0,0 +1,36 @@ +package org.utbot.framework.codegen.services.language + +import org.utbot.framework.codegen.renderer.CgPrinter +import org.utbot.framework.codegen.renderer.CgAbstractRenderer +import org.utbot.framework.codegen.renderer.CgJavaRenderer +import org.utbot.framework.codegen.renderer.CgRendererContext +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.JVMTestFrameworkManager +import org.utbot.framework.plugin.api.utils.ClassNameUtils.generateTestClassName + +object JavaCgLanguageAssistant : AbstractCgLanguageAssistant() { + + override val extension: String + get() = ".java" + + override val languageKeywords: Set = setOf( + "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", + "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "goto", + "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", + "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", + "throw", "throws", "transient", "try", "void", "volatile", "while", "null", "false", "true" + ) + + override fun testClassName( + testClassCustomName: String?, + testClassPackageName: String, + classUnderTest: ClassId + ): Pair { + return generateTestClassName(testClassCustomName, testClassPackageName, classUnderTest) + } + + override fun getLanguageTestFrameworkManager() = JVMTestFrameworkManager() + + override fun cgRenderer(context: CgRendererContext, printer: CgPrinter): CgAbstractRenderer = + CgJavaRenderer(context, printer) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/KeywordsUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/KeywordsUtil.kt new file mode 100644 index 0000000000..10dc13e7b6 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/KeywordsUtil.kt @@ -0,0 +1,4 @@ +package org.utbot.framework.codegen.services.language + +fun isLanguageKeyword(word: String, codegenLanguageAssistant: CgLanguageAssistant): Boolean = + word in codegenLanguageAssistant.languageKeywords diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/KotlinCgLanguageAssistant.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/KotlinCgLanguageAssistant.kt new file mode 100644 index 0000000000..98a444e841 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/KotlinCgLanguageAssistant.kt @@ -0,0 +1,47 @@ +package org.utbot.framework.codegen.services.language + +import org.utbot.framework.codegen.renderer.CgPrinter +import org.utbot.framework.codegen.renderer.CgAbstractRenderer +import org.utbot.framework.codegen.renderer.CgKotlinRenderer +import org.utbot.framework.codegen.renderer.CgRendererContext +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.JVMTestFrameworkManager +import org.utbot.framework.plugin.api.utils.ClassNameUtils.generateTestClassName + +object KotlinCgLanguageAssistant : AbstractCgLanguageAssistant() { + + override val extension: String + get() = ".kt" + + override val languageKeywords: Set = setOf( + "as", "as?", "break", "class", "continue", "do", "else", "false", "for", "fun", "if", "in", "!in", "interface", + "is", "!is", "null", "object", "package", "return", "super", "this", "throw", "true", "try", "typealias", + "typeof", "val", "var", "when", "while" + ) + + override fun testClassName( + testClassCustomName: String?, + testClassPackageName: String, + classUnderTest: ClassId + ): Pair { + return generateTestClassName(testClassCustomName, testClassPackageName, classUnderTest) + } + + override fun getLanguageTestFrameworkManager() = JVMTestFrameworkManager() + + override fun cgRenderer(context: CgRendererContext, printer: CgPrinter): CgAbstractRenderer = + CgKotlinRenderer(context, printer) + + @Suppress("unused") + private val kotlinSoftKeywords = setOf( + "by", "catch", "constructor", "delegate", "dynamic", "field", "file", "finally", "get", "import", "init", + "param", "property", "receiver", "set", "setparam", "value", "where" + ) + + @Suppress("unused") + private val kotlinModifierKeywords = setOf( + "actual", "abstract", "annotation", "companion", "const", "crossinline", "data", "enum", "expect", "external", + "final", "infix", "inline", "inner", "internal", "lateinit", "noinline", "open", "operator", "out", "override", + "private", "protected", "public", "reified", "sealed", "suspend", "tailrec", "vararg" + ) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/LanguageTestFrameworkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/LanguageTestFrameworkManager.kt new file mode 100644 index 0000000000..beb6de6a41 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/services/language/LanguageTestFrameworkManager.kt @@ -0,0 +1,12 @@ +package org.utbot.framework.codegen.services.language + +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.services.framework.TestFrameworkManager + +abstract class LanguageTestFrameworkManager { + + open val testFrameworks: List = emptyList() + abstract fun managerByFramework(context: CgContext): TestFrameworkManager + abstract val defaultTestFramework: TestFramework +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/Builders.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/Builders.kt new file mode 100644 index 0000000000..89838ade7d --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/Builders.kt @@ -0,0 +1,235 @@ +package org.utbot.framework.codegen.tree + +import org.utbot.framework.codegen.domain.Import +import org.utbot.framework.codegen.domain.models.CgAnnotation +import org.utbot.framework.codegen.domain.models.CgAssignment +import org.utbot.framework.codegen.domain.models.CgClass +import org.utbot.framework.codegen.domain.models.CgClassBody +import org.utbot.framework.codegen.domain.models.CgClassFile +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgDoWhileLoop +import org.utbot.framework.codegen.domain.models.CgDocumentationComment +import org.utbot.framework.codegen.domain.models.CgElement +import org.utbot.framework.codegen.domain.models.CgErrorTestMethod +import org.utbot.framework.codegen.domain.models.CgExceptionHandler +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgFieldDeclaration +import org.utbot.framework.codegen.domain.models.CgForEachLoop +import org.utbot.framework.codegen.domain.models.CgForLoop +import org.utbot.framework.codegen.domain.models.CgLoop +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.codegen.domain.models.CgMethodsCluster +import org.utbot.framework.codegen.domain.models.CgNestedClassesRegion +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration +import org.utbot.framework.codegen.domain.models.CgParameterizedTestDataProviderMethod +import org.utbot.framework.codegen.domain.models.CgReferenceExpression +import org.utbot.framework.codegen.domain.models.CgStatement +import org.utbot.framework.codegen.domain.models.CgStaticsRegion +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgTestMethodType +import org.utbot.framework.codegen.domain.models.CgTryCatch +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.domain.models.CgWhileLoop +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.voidClassId + +interface CgBuilder { + fun build(): T +} + +// Code entities + +class CgClassFileBuilder : CgBuilder { + val imports: MutableList = mutableListOf() + lateinit var declaredClass: CgClass + + override fun build() = CgClassFile(imports, declaredClass) +} + +fun buildClassFile(init: CgClassFileBuilder.() -> Unit) = CgClassFileBuilder().apply(init).build() + +class CgClassBuilder : CgBuilder { + lateinit var id: ClassId + var documentation: CgDocumentationComment? = null + val annotations: MutableList = mutableListOf() + var superclass: ClassId? = null + val interfaces: MutableList = mutableListOf() + var isStatic: Boolean = false + var isNested: Boolean = false + lateinit var body: CgClassBody + + override fun build() = CgClass(id, documentation, annotations, superclass, interfaces, body, isStatic, isNested) +} + +fun buildClass(init: CgClassBuilder.() -> Unit) = CgClassBuilder().apply(init).build() + +class CgClassBodyBuilder(val classId: ClassId) : CgBuilder { + val methodRegions: MutableList = mutableListOf() + val staticDeclarationRegions: MutableList = mutableListOf() + val nestedClassRegions: MutableList> = mutableListOf() + val fields: MutableSet = mutableSetOf() + + override fun build() = CgClassBody(classId, methodRegions, staticDeclarationRegions, nestedClassRegions, fields) +} + +fun buildClassBody(classId: ClassId, init: CgClassBodyBuilder.() -> Unit) = CgClassBodyBuilder(classId).apply(init).build() +// Methods + +interface CgMethodBuilder : CgBuilder { + val name: String + val returnType: ClassId + val parameters: List + val statements: List + val exceptions: Set + val annotations: List + val documentation: CgDocumentationComment +} + +class CgTestMethodBuilder : CgMethodBuilder { + override lateinit var name: String + override val returnType: ClassId = voidClassId + override lateinit var parameters: List + override lateinit var statements: List + override val exceptions: MutableSet = mutableSetOf() + override val annotations: MutableList = mutableListOf() + lateinit var methodType: CgTestMethodType + override var documentation: CgDocumentationComment = CgDocumentationComment(emptyList()) + + override fun build() = CgTestMethod( + name, + returnType, + parameters, + statements, + exceptions, + annotations, + type = methodType, + documentation = documentation, + ) +} + +fun buildTestMethod(init: CgTestMethodBuilder.() -> Unit) = CgTestMethodBuilder().apply(init).build() + +class CgErrorTestMethodBuilder : CgMethodBuilder { + override lateinit var name: String + override val returnType: ClassId = voidClassId + override val parameters: List = emptyList() + override lateinit var statements: List + override val exceptions: Set = emptySet() + override val annotations: List = emptyList() + override var documentation: CgDocumentationComment = CgDocumentationComment(emptyList()) + + override fun build() = CgErrorTestMethod(name, statements, documentation) +} + +fun buildErrorTestMethod(init: CgErrorTestMethodBuilder.() -> Unit) = CgErrorTestMethodBuilder().apply(init).build() + +class CgParameterizedTestDataProviderBuilder : CgMethodBuilder { + override lateinit var name: String + + override lateinit var returnType: ClassId + override val parameters: List = mutableListOf() + override lateinit var statements: List + override val annotations: MutableList = mutableListOf() + override val exceptions: MutableSet = mutableSetOf() + override var documentation: CgDocumentationComment = CgDocumentationComment(emptyList()) + + override fun build() = CgParameterizedTestDataProviderMethod(name, statements, returnType, annotations, exceptions) +} + +fun buildParameterizedTestDataProviderMethod( + init: CgParameterizedTestDataProviderBuilder.() -> Unit +) = CgParameterizedTestDataProviderBuilder().apply(init).build() + +// Variable declaration + +class CgDeclarationBuilder : CgBuilder { + lateinit var variableType: ClassId + lateinit var variableName: String + var initializer: CgExpression? = null + var isMutable: Boolean = false + + override fun build() = CgDeclaration(variableType, variableName, initializer, isMutable) +} + +fun buildDeclaration(init: CgDeclarationBuilder.() -> Unit) = CgDeclarationBuilder().apply(init).build() + +// Variable assignment +class CgAssignmentBuilder : CgBuilder { + lateinit var lValue: CgExpression + lateinit var rValue: CgExpression + + override fun build() = CgAssignment(lValue, rValue) +} + +fun buildAssignment(init: CgAssignmentBuilder.() -> Unit) = CgAssignmentBuilder().apply(init).build() + +class CgTryCatchBuilder : CgBuilder { + lateinit var statements: List + private val handlers: MutableList = mutableListOf() + var finally: List? = null + var resources: List? = null + + override fun build(): CgTryCatch = CgTryCatch(statements, handlers, finally, resources) +} + +fun buildTryCatch(init: CgTryCatchBuilder.() -> Unit): CgTryCatch = CgTryCatchBuilder().apply(init).build() + +// Loops +interface CgLoopBuilder : CgBuilder { + val condition: CgExpression + val statements: List +} + +class CgForLoopBuilder : CgLoopBuilder { + lateinit var initialization: CgDeclaration + override lateinit var condition: CgExpression + lateinit var update: CgStatement + override lateinit var statements: List + + override fun build() = CgForLoop(initialization, condition, update, statements) +} + +fun buildForLoop(init: CgForLoopBuilder.() -> Unit) = CgForLoopBuilder().apply(init).build() + +class CgWhileLoopBuilder : CgLoopBuilder { + override lateinit var condition: CgExpression + override val statements: MutableList = mutableListOf() + + override fun build() = CgWhileLoop(condition, statements) +} + +fun buildWhileLoop(init: CgWhileLoopBuilder.() -> Unit) = CgWhileLoopBuilder().apply(init).build() + +class CgDoWhileLoopBuilder : CgLoopBuilder { + override lateinit var condition: CgExpression + override val statements: MutableList = mutableListOf() + + override fun build() = CgDoWhileLoop(condition, statements) +} + +fun buildDoWhileLoop(init: CgDoWhileLoopBuilder.() -> Unit) = CgDoWhileLoopBuilder().apply(init).build() + +class CgForEachLoopBuilder : CgLoopBuilder { + override lateinit var condition: CgExpression + override lateinit var statements: List + lateinit var iterable: CgReferenceExpression + + override fun build(): CgForEachLoop = CgForEachLoop(condition, statements, iterable) +} + +fun buildCgForEachLoop(init: CgForEachLoopBuilder.() -> Unit): CgForEachLoop = + CgForEachLoopBuilder().apply(init).build() + +class CgExceptionHandlerBuilder { + lateinit var exception: CgVariable + lateinit var statements: List + + fun build(): CgExceptionHandler { + return CgExceptionHandler(exception, statements) + } +} + +fun buildExceptionHandler(init: CgExceptionHandlerBuilder.() -> Unit): CgExceptionHandler { + return CgExceptionHandlerBuilder().apply(init).build() +} + diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgAbstractTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgAbstractTestClassConstructor.kt new file mode 100644 index 0000000000..11ec2c751d --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgAbstractTestClassConstructor.kt @@ -0,0 +1,225 @@ +package org.utbot.framework.codegen.tree + +import mu.KotlinLogging +import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.domain.builtin.TestClassUtilMethodProvider +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgAuxiliaryClass +import org.utbot.framework.codegen.domain.models.CgClass +import org.utbot.framework.codegen.domain.models.CgClassBody +import org.utbot.framework.codegen.domain.models.CgClassFile +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.CgRegion +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgTestMethodCluster +import org.utbot.framework.codegen.domain.models.CgTripleSlashMultilineComment +import org.utbot.framework.codegen.domain.models.CgUtilEntity +import org.utbot.framework.codegen.domain.models.CgUtilMethod +import org.utbot.framework.codegen.domain.models.TestClassModel +import org.utbot.framework.codegen.renderer.importUtilMethodDependencies +import org.utbot.framework.codegen.reports.TestsGenerationReport +import org.utbot.framework.codegen.services.CgNameGenerator +import org.utbot.framework.codegen.services.framework.TestFrameworkManager +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtExecutionFailure +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtMethodTestSet +import org.utbot.framework.plugin.api.util.description +import org.utbot.framework.util.calculateSize + +abstract class CgAbstractTestClassConstructor(val context: CgContext): + CgContextOwner by context, + CgStatementConstructor by CgComponents.getStatementConstructorBy(context) { + + companion object { + private val logger = KotlinLogging.logger {} + } + + init { + CgComponents.clearContextRelatedStorage() + } + + val testsGenerationReport = TestsGenerationReport() + + protected val methodConstructor: CgMethodConstructor = CgComponents.getMethodConstructorBy(context) + protected val nameGenerator: CgNameGenerator = CgComponents.getNameGeneratorBy(context) + protected val testFrameworkManager: TestFrameworkManager = CgComponents.getTestFrameworkManagerBy(context) + + /** + * Constructs a file with the test class corresponding to [TestClassModel]. + */ + open fun construct(testClassModel: T): CgClassFile { + return buildClassFile { + this.declaredClass = withTestClassScope { constructTestClass(testClassModel) } + imports += context.collectedImports + } + } + + /** + * Constructs [CgClass] corresponding to [TestClassModel]. + */ + open fun constructTestClass(testClassModel: T): CgClass { + return buildClass { + id = currentTestClass + + if (currentTestClass != outerMostTestClass) { + isNested = true + isStatic = testFramework.nestedClassesShouldBeStatic + testFrameworkManager.addAnnotationForNestedClasses() + } + + body = constructTestClassBody(testClassModel) + + // It is important that annotations, superclass and interfaces assignment is run after + // all methods are generated so that all necessary info is already present in the context + with (currentTestClassContext) { + annotations += collectedTestClassAnnotations + superclass = testClassSuperclass + interfaces += collectedTestClassInterfaces + } + } + } + + abstract fun constructTestClassBody(testClassModel: T): CgClassBody + + abstract fun constructTestSet(testSet: CgMethodTestSet): List>? + + protected fun createTest( + testSet: CgMethodTestSet, + regions: MutableList> + ) { + val (_, _, clustersInfo) = testSet + + // `stateAfter` is not accounted here, because usually most of it is not rendered + val executionToSizeCache = testSet.executions.associateWith { execution -> + execution.stateBefore.calculateSize() + + ((execution.result as? UtExecutionSuccess)?.model?.calculateSize() ?: 1) + } + + for ((clusterSummary, executionIndices) in clustersInfo) { + val currentTestCaseTestMethods = mutableListOf() + emptyLineIfNeeded() + val (checkedRange, needLimitExceedingComments) = if (executionIndices.last - executionIndices.first >= UtSettings.maxTestsPerMethodInRegion) { + IntRange(executionIndices.first, executionIndices.first + (UtSettings.maxTestsPerMethodInRegion - 1).coerceAtLeast(0)) to true + } else { + executionIndices to false + } + + testSet.executions + .withIndex() + .toList() + .slice(checkedRange) + .sortedWith( + // NPE tests are rendered last, because oftentimes they are meaningless in a sense + // that they pass `null` somewhere where `null` is never passed in production + compareBy> { (_, execution) -> + if ((execution.result as? UtExecutionFailure)?.exception is NullPointerException) 1 else -1 + } + // we place "smaller" tests earlier, since they are easier to read + .thenComparingInt { (_, execution) -> executionToSizeCache.getValue(execution) } + ).forEach { (i, execution) -> + withExecutionIdScope(i) { + currentTestCaseTestMethods += methodConstructor.createTestMethod(testSet, execution) + } + } + + val comments = listOf("Actual number of generated tests (${executionIndices.last - executionIndices.first}) exceeds per-method limit (${UtSettings.maxTestsPerMethodInRegion})", + "The limit can be configured in '{HOME_DIR}/.utbot/settings.properties' with 'maxTestsPerMethod' property") + + val clusterHeader = clusterSummary?.header + var clusterContent = clusterSummary?.content + ?.split('\n') + ?.let { CgTripleSlashMultilineComment(if (needLimitExceedingComments) {it.toMutableList() + comments} else {it}) } + if (clusterContent == null && needLimitExceedingComments) { + clusterContent = CgTripleSlashMultilineComment(comments) + } + regions += CgTestMethodCluster(clusterHeader, clusterContent, currentTestCaseTestMethods) + + testsGenerationReport.addTestsByType(testSet, currentTestCaseTestMethods) + } + } + + protected fun processFailure(testSet: CgMethodTestSet, failure: Throwable) { + logger.warn(failure) { "Code generation error" } + codeGenerationErrors + .getOrPut(testSet) { mutableMapOf() } + .merge(failure.description, 1, Int::plus) + } + + /** + * This method collects a list of util entities (methods and classes) needed by the class. + * By the end of the test method generation [requiredUtilMethods] may not contain all the needed. + * That's because some util methods may not be directly used in tests, but they may be used from other util methods. + * We define such method dependencies in [MethodId.methodDependencies]. + * + * Once all dependencies are collected, required methods are added back to [requiredUtilMethods], + * because during the work of this method they are being removed from this list, so we have to put them back in. + * + * Also, some util methods may use some classes that also need to be generated. + * That is why we collect information about required classes using [MethodId.classDependencies]. + * + * @return a list of [CgUtilEntity] representing required util methods and classes (including their own dependencies). + */ + protected fun collectUtilEntities(): List { + val utilMethods = mutableListOf() + // Some util methods depend on other util methods or some auxiliary classes. + // Using this loop we make sure that all the util method dependencies are taken into account. + val requiredClasses = mutableSetOf() + while (requiredUtilMethods.isNotEmpty()) { + val method = requiredUtilMethods.first() + requiredUtilMethods.remove(method) + if (method.name !in existingMethodNames) { + utilMethods += CgUtilMethod(method) + // we only need imports from util methods if these util methods are declared in the test class + if (utilMethodProvider is TestClassUtilMethodProvider) { + importUtilMethodDependencies(method) + } + existingMethodNames += method.name + requiredUtilMethods += method.methodDependencies() + requiredClasses += method.classDependencies() + } + } + // Collect all util methods back into requiredUtilMethods. + // Now there will also be util methods that weren't present in requiredUtilMethods at first, + // but were needed for the present util methods to work. + requiredUtilMethods += utilMethods.map { method -> method.id } + + val auxiliaryClasses = requiredClasses.map { CgAuxiliaryClass(it) } + + return utilMethods + auxiliaryClasses + } + + /** + * Engine errors + codegen errors for a given [UtMethodTestSet] + */ + protected val CgMethodTestSet.allErrors: Map + get() = errors + codeGenerationErrors.getOrDefault(this, mapOf()) + + /** + * If @receiver is an util method, then returns a list of util method ids that @receiver depends on + * Otherwise, an empty list is returned + */ + private fun MethodId.methodDependencies(): List = when (this) { + createInstance -> listOf(getUnsafeInstance) + deepEquals -> listOf(arraysDeepEquals, iterablesDeepEquals, streamsDeepEquals, mapsDeepEquals, hasCustomEquals) + arraysDeepEquals, iterablesDeepEquals, streamsDeepEquals, mapsDeepEquals -> listOf(deepEquals) + buildLambda, buildStaticLambda -> listOf( + getLookupIn, getSingleAbstractMethod, getLambdaMethod, + getLambdaCapturedArgumentTypes, getInstantiatedMethodType, getLambdaCapturedArgumentValues + ) + else -> emptyList() + } + + /** + * If @receiver is an util method, then returns a list of auxiliary class ids that @receiver depends on. + * Otherwise, an empty list is returned. + */ + private fun MethodId.classDependencies(): List = when (this) { + buildLambda, buildStaticLambda -> listOf(capturedArgumentClass) + else -> emptyList() + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgComponents.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgComponents.kt new file mode 100644 index 0000000000..5b9876f1ef --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgComponents.kt @@ -0,0 +1,71 @@ +package org.utbot.framework.codegen.tree + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.services.CgNameGenerator +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.services.framework.MockFrameworkManager +import org.utbot.framework.codegen.services.framework.TestFrameworkManager +import java.util.* + +object CgComponents { + + /** + * Clears all stored data for current [CgContext]. + * As far as context is created per class under test, + * no related data is required after it's processing. + */ + fun clearContextRelatedStorage() { + nameGenerators.clear() + statementConstructors.clear() + callableAccessManagers.clear() + testFrameworkManagers.clear() + mockFrameworkManagers.clear() + variableConstructors.clear() + methodConstructors.clear() + customAssertConstructors.clear() + } + + private val nameGenerators: IdentityHashMap = IdentityHashMap() + + private val callableAccessManagers: IdentityHashMap = IdentityHashMap() + private val testFrameworkManagers: IdentityHashMap = IdentityHashMap() + private val mockFrameworkManagers: IdentityHashMap = IdentityHashMap() + + private val statementConstructors: IdentityHashMap = IdentityHashMap() + private val variableConstructors: IdentityHashMap = IdentityHashMap() + private val methodConstructors: IdentityHashMap = IdentityHashMap() + private val customAssertConstructors: IdentityHashMap = IdentityHashMap() + + fun getNameGeneratorBy(context: CgContext): CgNameGenerator = nameGenerators.getOrPut(context) { + context.cgLanguageAssistant.getNameGeneratorBy(context) + } + + fun getCallableAccessManagerBy(context: CgContext): CgCallableAccessManager = callableAccessManagers.getOrPut(context) { + context.cgLanguageAssistant.getCallableAccessManagerBy(context) + } + + fun getStatementConstructorBy(context: CgContext): CgStatementConstructor = statementConstructors.getOrPut(context) { + context.cgLanguageAssistant.getStatementConstructorBy(context) + } + + fun getTestFrameworkManagerBy(context: CgContext): TestFrameworkManager = + testFrameworkManagers.getOrDefault( + context, + context.cgLanguageAssistant.getLanguageTestFrameworkManager().managerByFramework(context) + ) + + fun getMockFrameworkManagerBy(context: CgContext): MockFrameworkManager = + mockFrameworkManagers.getOrPut(context) { MockFrameworkManager(context) } + + fun getVariableConstructorBy(context: CgContext): CgVariableConstructor = variableConstructors.getOrPut(context) { + context.cgLanguageAssistant.getVariableConstructorBy(context) + } + + fun getMethodConstructorBy(context: CgContext): CgMethodConstructor = methodConstructors.getOrPut(context) { + context.cgLanguageAssistant.getMethodConstructorBy(context) + } + + fun getCustomAssertConstructorBy(context: CgContext): CgCustomAssertConstructor = customAssertConstructors.getOrPut(context) { + context.cgLanguageAssistant.getCustomAssertConstructorBy(context) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgCustomAssertConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgCustomAssertConstructor.kt new file mode 100644 index 0000000000..86dc612905 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgCustomAssertConstructor.kt @@ -0,0 +1,19 @@ +package org.utbot.framework.codegen.tree + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.plugin.api.UtCustomModel + +interface CgCustomAssertConstructor { + val context: CgContext + fun tryConstructCustomAssert(expected: UtCustomModel, actual: CgVariable): Boolean +} + +class CgSimpleCustomAssertConstructor( + override val context: CgContext +) : CgCustomAssertConstructor, + CgContextOwner by context { + override fun tryConstructCustomAssert(expected: UtCustomModel, actual: CgVariable): Boolean = + false +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt new file mode 100644 index 0000000000..fb1c7acb62 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt @@ -0,0 +1,2064 @@ +package org.utbot.framework.codegen.tree + +import mu.KotlinLogging +import org.utbot.common.WorkaroundReason +import org.utbot.common.isStatic +import org.utbot.common.workaround +import org.utbot.framework.UtSettings +import org.utbot.framework.assemble.assemble +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour.PASS +import org.utbot.framework.codegen.domain.builtin.closeMethodIdOrNull +import org.utbot.framework.codegen.domain.builtin.forName +import org.utbot.framework.codegen.domain.builtin.getClass +import org.utbot.framework.codegen.domain.builtin.getTargetException +import org.utbot.framework.codegen.domain.builtin.invoke +import org.utbot.framework.codegen.domain.builtin.newInstance +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.AnnotationTarget +import org.utbot.framework.codegen.domain.models.CgAllocateArray +import org.utbot.framework.codegen.domain.models.CgArrayElementAccess +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgDocumentationComment +import org.utbot.framework.codegen.domain.models.CgEqualTo +import org.utbot.framework.codegen.domain.models.CgErrorTestMethod +import org.utbot.framework.codegen.domain.models.CgExecutableCall +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgFieldAccess +import org.utbot.framework.codegen.domain.models.CgGetJavaClass +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.CgMultilineComment +import org.utbot.framework.codegen.domain.models.CgNotNullAssertion +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration +import org.utbot.framework.codegen.domain.models.CgParameterKind +import org.utbot.framework.codegen.domain.models.CgParameterizedTestDataProviderMethod +import org.utbot.framework.codegen.domain.models.CgRegion +import org.utbot.framework.codegen.domain.models.CgSimpleRegion +import org.utbot.framework.codegen.domain.models.CgSingleLineComment +import org.utbot.framework.codegen.domain.models.CgStatement +import org.utbot.framework.codegen.domain.models.CgStaticFieldAccess +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgTestMethodType +import org.utbot.framework.codegen.domain.models.CgTestMethodType.ARTIFICIAL +import org.utbot.framework.codegen.domain.models.CgTestMethodType.CRASH +import org.utbot.framework.codegen.domain.models.CgTestMethodType.FAILING +import org.utbot.framework.codegen.domain.models.CgTestMethodType.PARAMETRIZED +import org.utbot.framework.codegen.domain.models.CgTestMethodType.PASSED_EXCEPTION +import org.utbot.framework.codegen.domain.models.CgTestMethodType.SUCCESSFUL +import org.utbot.framework.codegen.domain.models.CgTestMethodType.TIMEOUT +import org.utbot.framework.codegen.domain.models.CgTryCatch +import org.utbot.framework.codegen.domain.models.CgTypeCast +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.domain.models.convertDocToCg +import org.utbot.framework.codegen.domain.models.toStatement +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.services.access.CgFieldStateManagerImpl +import org.utbot.framework.codegen.services.framework.TestFrameworkManager +import org.utbot.framework.codegen.tree.CgComponents.getCallableAccessManagerBy +import org.utbot.framework.codegen.tree.CgComponents.getCustomAssertConstructorBy +import org.utbot.framework.codegen.tree.CgComponents.getMockFrameworkManagerBy +import org.utbot.framework.codegen.tree.CgComponents.getNameGeneratorBy +import org.utbot.framework.codegen.tree.CgComponents.getStatementConstructorBy +import org.utbot.framework.codegen.tree.CgComponents.getTestFrameworkManagerBy +import org.utbot.framework.codegen.tree.CgComponents.getVariableConstructorBy +import org.utbot.framework.codegen.util.canBeReadFrom +import org.utbot.framework.codegen.util.canBeReadViaGetterFrom +import org.utbot.framework.codegen.util.canBeSetFrom +import org.utbot.framework.codegen.util.equalTo +import org.utbot.framework.codegen.util.escapeControlChars +import org.utbot.framework.codegen.util.getter +import org.utbot.framework.codegen.util.inc +import org.utbot.framework.codegen.util.length +import org.utbot.framework.codegen.util.lessThan +import org.utbot.framework.codegen.util.nullLiteral +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.fields.ExecutionStateAnalyzer +import org.utbot.framework.fields.FieldPath +import org.utbot.framework.plugin.api.ArtificialError +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.BuiltinMethodId +import org.utbot.framework.plugin.api.CgClassId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.InstrumentedProcessDeathException +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.framework.plugin.api.TypeParameters +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtClassRefModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtConcreteExecutionFailure +import org.utbot.framework.plugin.api.UtCustomModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtExecutionFailure +import org.utbot.framework.plugin.api.UtExecutionResult +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtExecutionWithInstrumentation +import org.utbot.framework.plugin.api.UtExplicitlyThrownException +import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtModelWithCompositeOrigin +import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtOverflowFailure +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtSandboxFailure +import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation +import org.utbot.framework.plugin.api.UtStreamConsumingFailure +import org.utbot.framework.plugin.api.UtTaintAnalysisFailure +import org.utbot.framework.plugin.api.UtTimeoutException +import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.framework.plugin.api.isMockModel +import org.utbot.framework.plugin.api.isNotNull +import org.utbot.framework.plugin.api.isNull +import org.utbot.framework.plugin.api.onFailure +import org.utbot.framework.plugin.api.onSuccess +import org.utbot.framework.plugin.api.util.IndentUtil.TAB +import org.utbot.framework.plugin.api.util.allSuperTypes +import org.utbot.framework.plugin.api.util.baseStreamClassId +import org.utbot.framework.plugin.api.util.doubleArrayClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.doubleStreamClassId +import org.utbot.framework.plugin.api.util.doubleStreamToArrayMethodId +import org.utbot.framework.plugin.api.util.doubleWrapperClassId +import org.utbot.framework.plugin.api.util.executable +import org.utbot.framework.plugin.api.util.floatArrayClassId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.floatWrapperClassId +import org.utbot.framework.plugin.api.util.hasField +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.intStreamClassId +import org.utbot.framework.plugin.api.util.intStreamToArrayMethodId +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.isInaccessibleViaReflection +import org.utbot.framework.plugin.api.util.isInnerClassEnclosingClassReference +import org.utbot.framework.plugin.api.util.isIterableOrMap +import org.utbot.framework.plugin.api.util.isPrimitive +import org.utbot.framework.plugin.api.util.isPrimitiveArray +import org.utbot.framework.plugin.api.util.isPrimitiveWrapper +import org.utbot.framework.plugin.api.util.isRefType +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.isSubtypeOf +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.jField +import org.utbot.framework.plugin.api.util.kClass +import org.utbot.framework.plugin.api.util.longStreamClassId +import org.utbot.framework.plugin.api.util.longStreamToArrayMethodId +import org.utbot.framework.plugin.api.util.objectArrayClassId +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.streamClassId +import org.utbot.framework.plugin.api.util.streamToArrayMethodId +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.framework.plugin.api.util.wrapIfPrimitive +import org.utbot.framework.util.isUnit +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.ParameterizedType +import java.security.AccessControlException + +private const val DEEP_EQUALS_MAX_DEPTH = 5 // TODO move it to plugin settings? + +open class CgMethodConstructor(val context: CgContext) : CgContextOwner by context, + CgCallableAccessManager by getCallableAccessManagerBy(context), + CgStatementConstructor by getStatementConstructorBy(context) { + + companion object { + private val logger = KotlinLogging.logger {} + } + + protected val nameGenerator = getNameGeneratorBy(context) + protected val testFrameworkManager = getTestFrameworkManagerBy(context) + + protected val variableConstructor = getVariableConstructorBy(context) + private val customAssertConstructor = getCustomAssertConstructorBy(context) + private val mockFrameworkManager = getMockFrameworkManagerBy(context) + + private val floatDelta: Float = 1e-6f + private val doubleDelta = 1e-6 + + // a model for execution result (it is lateinit because execution can fail, + // and we need it only on assertions generation stage + lateinit var resultModel: UtModel + + lateinit var methodType: CgTestMethodType + + private val fieldsOfExecutionResults = mutableMapOf, MutableList>() + + /** + * Contains whether [UtStreamConsumingFailure] is in [CgMethodTestSet] for parametrized tests. + * See [WorkaroundReason.CONSUME_DIRTY_STREAMS]. + */ + private var containsStreamConsumingFailureForParametrizedTests: Boolean = false + + protected fun setupInstrumentation() { + val instrumentation = when (val execution = currentExecution) { + is UtExecutionWithInstrumentation -> execution.instrumentation + else -> return + } + if (instrumentation.isEmpty()) return + + if (generateWarningsForStaticMocking && forceStaticMocking == ForceStaticMocking.DO_NOT_FORCE) { + // warn user about possible flaky tests + multilineComment(forceStaticMocking.warningMessage) + return + } + + instrumentation + .filterIsInstance() + .forEach { mockFrameworkManager.mockNewInstance(it) } + instrumentation + .filterIsInstance() + .groupBy { it.methodId.classId } + .forEach { (classId, methodMocks) -> mockFrameworkManager.mockStaticMethodsOfClass(classId, methodMocks) } + + if (generateWarningsForStaticMocking && forceStaticMocking == ForceStaticMocking.FORCE) { + // warn user about forced using static mocks + multilineComment(forceStaticMocking.warningMessage) + } + } + + /** + * Create variables for initial values of the static fields and store them in [prevStaticFieldValues] + * in order to use these variables at the end of the test to restore the initial static fields state. + * + * Note: + * Later in the test method we also cache the 'before' and 'after' states of fields (including static fields). + * This cache is stored in [statesCache]. + * + * However, it is _not_ the same cache as [prevStaticFieldValues]. + * The [statesCache] should not be confused with [prevStaticFieldValues] cache. + * + * The difference is that [prevStaticFieldValues] contains the static field states before we made _any_ changes. + * On the other hand, [statesCache] contains 'before' and 'after' states where the 'before' state is + * the state that we _specifically_ set up in order to cover a certain branch. + * + * Thus, this method only caches an actual initial static fields state in order to recover it + * at the end of the test, and it has nothing to do with the 'before' and 'after' caches. + */ + protected fun rememberInitialStaticFields(statics: Map) { + val accessibleStaticFields = statics.accessibleFields() + for ((field, _) in accessibleStaticFields) { + val declaringClass = field.declaringClass + val fieldAccessible = field.canBeReadFrom(context, declaringClass) + + // prevValue is nullable if not accessible because of getStaticFieldValue(..) : Any? + val prevValue = newVar( + CgClassId(field.type, isNullable = !fieldAccessible), + "prev${field.name.capitalize()}" + ) { + if (fieldAccessible) { + declaringClass[field] + } else { + val declaringClassVar = newVar(classCgClassId) { + Class::class.id[forName](declaringClass.name) + } + utilsClassId[getStaticFieldValue](declaringClassVar, field.name) + } + } + // remember the previous value of a static field to recover it at the end of the test + prevStaticFieldValues[field] = prevValue + } + } + + protected fun substituteStaticFields(statics: Map, isParametrized: Boolean = false) { + val accessibleStaticFields = statics.accessibleFields() + for ((field, model) in accessibleStaticFields) { + val declaringClass = field.declaringClass + val fieldAccessible = field.canBeSetFrom(context, declaringClass) + + val fieldValue = if (isParametrized) { + currentMethodParameters[CgParameterKind.Statics(model)] + } else { + variableConstructor.getOrCreateVariable(model, field.name) + } + + if (fieldAccessible) { + declaringClass[field] `=` fieldValue + } else { + val declaringClassVar = newVar(classCgClassId) { + Class::class.id[forName](declaringClass.name) + } + +utilsClassId[setStaticField](declaringClassVar, field.name, fieldValue) + } + } + } + + protected fun recoverStaticFields() { + for ((field, prevValue) in prevStaticFieldValues.accessibleFields()) { + if (field.canBeSetFrom(context, field.declaringClass)) { + field.declaringClass[field] `=` prevValue + } else { + val declaringClass = getClassOf(field.declaringClass) + +utilsClassId[setStaticField](declaringClass, field.name, prevValue) + } + } + } + + private fun Map.accessibleFields(): Map = filterKeys { !it.isInaccessibleViaReflection } + + /** + * Generates result assertions for unit tests. + */ + protected open fun generateResultAssertions() { + when (val executable = currentExecutableToCall) { + is ConstructorId -> generateConstructorCall(executable, currentExecution!!) + is BuiltinMethodId -> error("Unexpected BuiltinMethodId $executable while generating result assertions") + is MethodId -> { + emptyLineIfNeeded() + val currentExecution = currentExecution!! + val executionResult = currentExecution.result + + // build assertions + executionResult + .onSuccess { resultModel -> + methodType = SUCCESSFUL + + // TODO possible engine bug - void method return type and result model not UtVoidModel + if (resultModel.isUnit() || executable.returnType == voidClassId) { + +thisInstance[executable](*methodArguments.toTypedArray()) + } else { + this.resultModel = resultModel + assertEquality(resultModel, actual, emptyLineIfNeeded = true) + } + } + .onFailure { exception -> processExecutionFailure(exception, executionResult) } + } + else -> {} // TODO: check this specific case + } + } + + private fun processExecutionFailure(exceptionFromAnalysis: Throwable, executionResult: UtExecutionResult) { + val (methodInvocationBlock, expectedException) = constructExceptionProducingBlock(exceptionFromAnalysis, executionResult) + + when (methodType) { + SUCCESSFUL -> error("Unexpected successful without exception method type for execution with exception $expectedException") + PASSED_EXCEPTION -> { + // TODO consider rendering message in a comment + // expectedException.message?.let { +comment(it.escapeControlChars()) } + testFrameworkManager.expectException(expectedException::class.id) { + methodInvocationBlock() + } + } + TIMEOUT -> { + writeWarningAboutTimeoutExceeding() + testFrameworkManager.expectTimeout(hangingTestsTimeout.timeoutMs) { + methodInvocationBlock() + } + } + CRASH -> when (expectedException) { + is InstrumentedProcessDeathException -> { + writeWarningAboutCrash() + methodInvocationBlock() + } + is AccessControlException -> { + // exception from sandbox + writeWarningAboutFailureTest(expectedException) + } + else -> error("Unexpected crash suite for failing execution with $expectedException exception") + } + ARTIFICIAL -> { + methodInvocationBlock() + + val failureMessage = prepareArtificialFailureMessage(executionResult) + testFrameworkManager.fail(failureMessage) + } + FAILING -> { + writeWarningAboutFailureTest(expectedException) + methodInvocationBlock() + } + PARAMETRIZED -> error("Unexpected $PARAMETRIZED method type for failing execution with $expectedException exception") + } + } + + // TODO: ISSUE-1546 introduce some kind of language-specific string interpolation in Codegen + // and render arguments that cause overflow (but it requires engine enhancements) + private fun prepareArtificialFailureMessage(executionResult: UtExecutionResult): CgLiteral { + when (executionResult) { + is UtOverflowFailure -> { + val failureMessage = "Overflow detected in \'${currentExecutableToCall!!.name}\' call" + return CgLiteral(stringClassId, failureMessage) + } + is UtTaintAnalysisFailure -> { + return CgLiteral(stringClassId, executionResult.exception.message) + } + else -> error("$executionResult is not supported in artificial errors") + } + } + + private fun constructExceptionProducingBlock( + exceptionFromAnalysis: Throwable, + executionResult: UtExecutionResult + ): Pair<() -> Unit, Throwable> { + if (executionResult is UtStreamConsumingFailure) { + return constructStreamConsumingBlock() to executionResult.rootCauseException + } + + return { + with(currentExecutableToCall) { + when (this) { + is MethodId -> thisInstance[this](*methodArguments.toTypedArray()).intercepted() + is ConstructorId -> this(*methodArguments.toTypedArray()).intercepted() + else -> {} // TODO: check this specific case + } + } + } to exceptionFromAnalysis + } + + private fun constructStreamConsumingBlock(): () -> Unit { + val executable = currentExecutableToCall + + require((executable is MethodId) && (executable.returnType isSubtypeOf baseStreamClassId)) { + "Unexpected non-stream returning executable $executable" + } + + val allSuperTypesOfReturn = executable.returnType.allSuperTypes().toSet() + + val streamConsumingMethodId = when { + // The order is important since all streams implement BaseStream + intStreamClassId in allSuperTypesOfReturn -> intStreamToArrayMethodId + longStreamClassId in allSuperTypesOfReturn -> longStreamToArrayMethodId + doubleStreamClassId in allSuperTypesOfReturn -> doubleStreamToArrayMethodId + streamClassId in allSuperTypesOfReturn -> streamToArrayMethodId + else -> { + // BaseStream, use util method to consume it + return { +utilsClassId[consumeBaseStream](actual) } + } + } + + return { +actual[streamConsumingMethodId]() } + } + + protected open fun shouldTestPassWithException(execution: UtExecution, exception: Throwable): Boolean { + if (exception is AccessControlException) return false + // tests with timeout or crash should be processed differently + if (exception is TimeoutException || exception is InstrumentedProcessDeathException) return false + if (exception is ArtificialError) return false + if (UtSettings.treatAssertAsErrorSuite && exception is AssertionError) return false + + val exceptionRequiresAssert = exception !is RuntimeException || runtimeExceptionTestsBehaviour == PASS + val exceptionIsExplicit = execution.result is UtExplicitlyThrownException + return exceptionRequiresAssert || exceptionIsExplicit + } + + protected fun shouldTestPassWithTimeoutException(execution: UtExecution, exception: Throwable): Boolean { + return execution.result is UtTimeoutException || exception is TimeoutException + } + + protected fun writeWarningAboutTimeoutExceeding() { + +CgMultilineComment( + listOf( + "This execution may take longer than the ${hangingTestsTimeout.timeoutMs} ms timeout", + " and therefore fail due to exceeding the timeout." + ) + ) + } + + protected fun writeWarningAboutFailureTest(exception: Throwable) { + require(currentExecutableToCall is ExecutableId) + val executableName = "${currentExecutableToCall!!.classId.name}.${currentExecutableToCall!!.name}" + + val warningLine = "This test fails because method [$executableName] produces [$exception]" + .lines() + .map { it.escapeControlChars() } + .toMutableList() + + +CgMultilineComment(warningLine + collectNeededStackTraceLines( + exception, + executableToStartCollectingFrom = currentExecutableToCall!! + )) + } + + protected open fun collectNeededStackTraceLines( + exception: Throwable, + executableToStartCollectingFrom: ExecutableId + ): List { + val executableName = "${executableToStartCollectingFrom.classId.name}.${executableToStartCollectingFrom.name}" + + val neededStackTraceLines = mutableListOf() + var executableCallFound = false + exception.stackTrace.reversed().forEach { stackTraceElement -> + val line = stackTraceElement.toString() + if (line.startsWith(executableName)) { + executableCallFound = true + } + if (executableCallFound) { + neededStackTraceLines += TAB + line + } + } + if (!executableCallFound) + logger.warn(exception) { "Failed to find executable call in stack trace" } + + return neededStackTraceLines.reversed() + } + + protected fun writeWarningAboutCrash() { + +CgSingleLineComment("This invocation possibly crashes JVM") + } + + /** + * Generates result assertions in parameterized tests for successful executions + * and just runs the method if all executions are unsuccessful. + */ + private fun generateAssertionsForParameterizedTest() { + emptyLineIfNeeded() + + when (val executable = currentExecutableToCall) { + is ConstructorId -> generateConstructorCall(executable, currentExecution!!) + is MethodId -> { + val executionResult = currentExecution!!.result + + executionResult + .onSuccess { resultModel -> + if (resultModel.isUnit()) { + +thisInstance[executable](*methodArguments.toTypedArray()) + } else { + //"generic" expected variable is represented with a wrapper if + //actual result is primitive to support cases with exceptions. + this.resultModel = if (resultModel is UtPrimitiveModel) assemble(resultModel) else resultModel + + val expectedVariable = currentMethodParameters[CgParameterKind.ExpectedResult]!! + val expectedExpression = CgNotNullAssertion(expectedVariable) + + assertEquality(expectedExpression, actual) + } + } + .onFailure { + workaround(WorkaroundReason.CONSUME_DIRTY_STREAMS) { + if (containsStreamConsumingFailureForParametrizedTests) { + constructStreamConsumingBlock().invoke() + } else { + thisInstance[executable](*methodArguments.toTypedArray()).intercepted() + } + } + } + } + else -> {} // TODO: check this specific case + } + } + + /** + * Generates assertions for field states. + * + * Note: not supported in parameterized tests. + */ + protected fun generateFieldStateAssertions() { + val thisInstanceCache = statesCache.thisInstance + for (path in thisInstanceCache.paths) { + assertStatesByPath(thisInstanceCache, path) + } + for (argumentCache in statesCache.arguments) { + for (path in argumentCache.paths) { + assertStatesByPath(argumentCache, path) + } + } + for ((_, staticFieldCache) in statesCache.classesWithStaticFields) { + for (path in staticFieldCache.paths) { + assertStatesByPath(staticFieldCache, path) + } + } + } + + /** + * If the given field is _not_ of reference type, then the variable for its 'before' state + * is not created, because we only need its final state to make an assertion. + * For reference type fields, in turn, we make an assertion assertFalse(before == after). + */ + private fun assertStatesByPath(cache: FieldStateCache, path: FieldPath) { + emptyLineIfNeeded() + val beforeVariable = cache.before[path]?.variable + val (afterVariable, afterModel) = cache.after[path]!! + + // TODO: remove the following after the issue fix + // We do not generate some assertions for Enums due to [https://github.com/UnitTestBot/UTBotJava/issues/1704]. + val beforeModel = cache.before[path]?.model + if (beforeModel !is UtEnumConstantModel && afterModel is UtEnumConstantModel) { + return + } + + if (afterModel !is UtReferenceModel) { + assertEquality( + expected = afterModel, + actual = afterVariable, + expectedVariableName = "expected" + afterVariable.name.capitalize() + ) + } else { + if (beforeVariable != null) + testFrameworkManager.assertBoolean(false, beforeVariable equalTo afterVariable) + // TODO: fail here + } + } + + private fun assertDeepEquals( + expectedModel: UtModel, + expected: CgVariable?, + actual: CgVariable, + depth: Int, + visitedModels: MutableSet, + expectedModelField: FieldId? = null, + ) { + val modelWithField = ModelWithField(expectedModel, expectedModelField) + if (modelWithField in visitedModels) return + + @Suppress("NAME_SHADOWING") + var expected = expected + if (expected == null) { + require(!needExpectedDeclaration(expectedModel)) + expected = actual + } + + visitedModels += modelWithField + + with(testFrameworkManager) { + if (expectedModel.isMockModel()) { + currentBlock += assertions[assertSame](expected, actual).toStatement() + return + } + + if (depth >= DEEP_EQUALS_MAX_DEPTH) { + currentBlock += CgSingleLineComment("Current deep equals depth exceeds max depth $DEEP_EQUALS_MAX_DEPTH") + currentBlock += getDeepEqualsAssertion(expected, actual).toStatement() + return + } + + when (expectedModel) { + is UtPrimitiveModel -> { + currentBlock += when { + (expected.type == floatClassId || expected.type == floatWrapperClassId) -> + assertions[assertFloatEquals]( // cast have to be not safe here because of signature + typeCast(floatClassId, expected, isSafetyCast = false), + typeCast(floatClassId, actual, isSafetyCast = false), + floatDelta + ) + (expected.type == doubleClassId || expected.type == doubleWrapperClassId) -> + assertions[assertDoubleEquals]( // cast have to be not safe here because of signature + typeCast(doubleClassId, expected, isSafetyCast = false), + typeCast(doubleClassId, actual, isSafetyCast = false), + doubleDelta + ) + expectedModel.value is Boolean -> { + when (parametrizedTestSource) { + ParametrizedTestSource.DO_NOT_PARAMETRIZE -> + if (expectedModel.value as Boolean) { + assertions[assertTrue](actual) + } else { + assertions[assertFalse](actual) + } + ParametrizedTestSource.PARAMETRIZE -> + assertions[assertEquals](expected, actual) + } + } + // other primitives and string + else -> { + require(expected.type.isPrimitive || expected.type == stringClassId) { + "Expected primitive or String but got ${expected.type}" + } + assertions[assertEquals](expected, actual) + } + }.toStatement() + } + is UtEnumConstantModel -> { + currentBlock += assertions[assertEquals]( + expected, + actual + ).toStatement() + } + is UtClassRefModel -> { + // TODO this stuff is needed because Kotlin has javaclass property instead of Java getClass method + // probably it is better to change getClass method behaviour in the future + val actualObject: CgVariable = when (codegenLanguage) { + CodegenLanguage.KOTLIN -> newVar( + baseType = objectClassId, + baseName = nameGenerator.variableName("actualObject"), + init = { CgTypeCast(objectClassId, actual) } + ) + else -> actual + } + + currentBlock += assertions[assertEquals]( + CgGetJavaClass(expected.type), + actualObject[getClass]() + ).toStatement() + } + is UtNullModel -> { + currentBlock += assertions[assertNull](actual).toStatement() + } + is UtArrayModel -> { + val arrayInfo = expectedModel.collectArrayInfo() + val nestedElementClassId = arrayInfo.nestedElementClassId + ?: error("Expected element class id from array ${arrayInfo.classId} but null found") + + if (!arrayInfo.isPrimitiveArray) { + // array of objects, have to use deep equals + + // We can't use for loop here because array model can contain different models + // and there is no a general approach to process it in loop + // For example, actual can be Object[3] and + // actual[0] instance of Point[][] + // actual[1] instance of int[][][] + // actual[2] instance of Object[] + + addArraysLengthAssertion(expected, actual) + currentBlock += getDeepEqualsAssertion(expected, actual).toStatement() + return + } + + // It does not work for Double and Float because JUnit does not have equals overloading with wrappers + if (nestedElementClassId == floatClassId || nestedElementClassId == doubleClassId) { + floatingPointArraysDeepEquals(arrayInfo, expected, actual) + return + } + + // common primitive array, can use default array equals + addArraysLengthAssertion(expected, actual) + currentBlock += getArrayEqualsAssertion( + expectedModel.classId, + typeCast(expectedModel.classId, expected, isSafetyCast = true), + typeCast(expectedModel.classId, actual, isSafetyCast = true) + ).toStatement() + } + is UtAssembleModel -> { + if (expectedModel.classId.isPrimitiveWrapper) { + currentBlock += assertions[assertEquals](expected, actual).toStatement() + return + } + + // UtCompositeModel deep equals is much more easier and human friendly + expectedModel.origin?.let { + assertDeepEquals(it, expected, actual, depth, visitedModels) + return + } + + // special case for strings as they are constructed from UtAssembleModel but can be compared with equals + if (expectedModel.classId == stringClassId) { + currentBlock += assertions[assertEquals]( + expected, + actual + ).toStatement() + return + } + + // We cannot implement deep equals for not field set model + // because if modification was made by direct field access, we can compare modifications by field access too + // (like in modification expected.value = 5 we can assert equality expected.value and actual.value), + // but in other cases we don't know what fields do we need to compare + // (like if modification was List add() method invocation) + + // We can add some heuristics to process standard assemble models like List, Set and Map. + // So, there is a space for improvements + if (expectedModel.modificationsChain.isEmpty() || expectedModel.modificationsChain.any { it !is UtDirectSetFieldModel }) { + currentBlock += getDeepEqualsAssertion(expected, actual).toStatement() + return + } + + for (modificationStep in expectedModel.modificationsChain) { + modificationStep as UtDirectSetFieldModel + val fieldId = modificationStep.fieldId + val fieldModel = modificationStep.fieldModel + + // we should not process enclosing class + // (actually, we do not do it properly anyway) + if (fieldId.isInnerClassEnclosingClassReference) continue + + traverseFieldRecursively( + fieldId, + fieldModel, + expected, + actual, + depth, + visitedModels + ) + } + } + is UtCompositeModel -> assertDeepEqualsForComposite( + expected = expected, + actual = actual, + expectedModel = expectedModel, + depth = depth, + visitedModels = visitedModels + ) + is UtCustomModel -> assertDeepEqualsForComposite( + expected = expected, + actual = actual, + expectedModel = expectedModel.origin + ?: error("Can't generate equals assertion for custom expected model without origin [$expectedModel]"), + depth = depth, + visitedModels = visitedModels + ) + is UtLambdaModel -> Unit // we do not check equality of lambdas + is UtVoidModel -> { + // Unit result is considered in generateResultAssertions method + error("Unexpected UtVoidModel in deep equals") + } + else -> {} + } + } + } + + private fun TestFrameworkManager.assertDeepEqualsForComposite( + expected: CgVariable, + actual: CgVariable, + expectedModel: UtCompositeModel, + depth: Int, + visitedModels: MutableSet + ) { + // Basically, to compare two iterables or maps, we need to iterate over them and compare each entry. + // But it leads to a lot of trash code in each test method, and it is more clear to use + // outer deep equals here + if (expected.isIterableOrMap()) { + currentBlock += CgSingleLineComment( + "${expected.type.canonicalName} is iterable or Map, use outer deep equals to iterate over" + ) + currentBlock += getDeepEqualsAssertion(expected, actual).toStatement() + + return + } + + // We can use overridden equals if we have one, but not for mocks. + if (expected.hasNotParametrizedCustomEquals() && !expectedModel.isMock) { + // We rely on already existing equals + currentBlock += CgSingleLineComment("${expected.type.canonicalName} has overridden equals method") + currentBlock += assertions[assertEquals](expected, actual).toStatement() + + return + } + + for ((fieldId, fieldModel) in expectedModel.fields) { + // we should not process enclosing class + // (actually, we do not do it properly anyway) + if (fieldId.isInnerClassEnclosingClassReference) continue + + traverseFieldRecursively( + fieldId, + fieldModel, + expected, + actual, + depth, + visitedModels + ) + } + } + + private fun TestFrameworkManager.addArraysLengthAssertion( + expected: CgVariable, + actual: CgVariable, + ): CgDeclaration { + val cgGetLengthDeclaration = CgDeclaration( + intClassId, + nameGenerator.variableName("${expected.name}Size"), + expected.length(this@CgMethodConstructor) + ) + currentBlock += cgGetLengthDeclaration + currentBlock += assertions[assertEquals]( + cgGetLengthDeclaration.variable, + actual.length(this@CgMethodConstructor) + ).toStatement() + + return cgGetLengthDeclaration + } + + /** + * Generate deep equals for float and double any-dimensional arrays (DOES NOT includes wrappers) + */ + private fun TestFrameworkManager.floatingPointArraysDeepEquals( + expectedArrayInfo: ClassIdArrayInfo, + expected: CgVariable, + actual: CgVariable, + ) { + val cgGetLengthDeclaration = addArraysLengthAssertion(expected, actual) + + val nestedElementClassId = expectedArrayInfo.nestedElementClassId + ?: error("Expected from floating point array ${expectedArrayInfo.classId} to contain elements but null found") + require(nestedElementClassId == floatClassId || nestedElementClassId == doubleClassId) { + "Expected float or double ClassId but `$nestedElementClassId` found" + } + + if (expectedArrayInfo.isSingleDimensionalArray) { + // we can use array equals for all single dimensional arrays + currentBlock += when (nestedElementClassId) { + floatClassId -> getFloatArrayEqualsAssertion( + typeCast(floatArrayClassId, expected, isSafetyCast = true), + typeCast(floatArrayClassId, actual, isSafetyCast = true), + floatDelta + ) + else -> getDoubleArrayEqualsAssertion( + typeCast(doubleArrayClassId, expected, isSafetyCast = true), + typeCast(doubleArrayClassId, actual, isSafetyCast = true), + doubleDelta + ) + }.toStatement() + } else { + // we can't use array equals for multidimensional double and float arrays + // so we need to go deeper to single-dimensional array + forLoop { + val (i, init) = variableConstructor.loopInitialization(intClassId, "i", initializer = 0) + initialization = init + condition = i lessThan cgGetLengthDeclaration.variable.resolve() + update = i.inc() + + statements = block { + val expectedNestedElement = newVar( + baseType = expected.type.elementClassId!!, + baseName = nameGenerator.variableName("${expected.name}NestedElement"), + init = { CgArrayElementAccess(expected, i) } + ) + + val actualNestedElement = newVar( + baseType = actual.type.elementClassId!!, + baseName = nameGenerator.variableName("${actual.name}NestedElement"), + init = { CgArrayElementAccess(actual, i) } + ) + + emptyLine() + + ifStatement( + CgEqualTo(expectedNestedElement, nullLiteral()), + trueBranch = { +assertions[assertNull](actualNestedElement).toStatement() }, + falseBranch = { + floatingPointArraysDeepEquals( + expectedArrayInfo.getNested(), + expectedNestedElement, + actualNestedElement, + ) + } + ) + } + } + } + } + + private fun CgVariable.isIterableOrMap(): Boolean = type.isIterableOrMap + + /** + * Some classes have overridden equals method, but it doesn't work properly. + * For example, List has overridden equals method but it relies on T equals. + * So, if T doesn't override equals, assertEquals with List fails. + * Therefore, all standard collections and map can fail. + * We overapproximate this assumption for all parametrized classes because we can't be sure that + * overridden equals doesn't rely on type parameters equals. + */ + private fun CgVariable.hasNotParametrizedCustomEquals(): Boolean { + if (type.jClass.overridesEquals()) { + // type parameters is list of class type parameters - empty if class is not generic + val typeParameters = type.kClass.typeParameters + + return typeParameters.isEmpty() + } + + return false + } + + private fun traverseFieldRecursively( + fieldId: FieldId, + fieldModel: UtModel, + expected: CgVariable, + actual: CgVariable, + depth: Int, + visitedModels: MutableSet + ) { + // if field is static, it is represents itself in "before" and + // "after" state: no need to assert its equality to itself. + if (fieldId.isStatic) { + return + } + + // if model is already processed, so we don't want to add new statements + val modelWithField = ModelWithField(fieldModel, fieldId) + if (modelWithField in visitedModels) { + currentBlock += testFrameworkManager.getDeepEqualsAssertion(expected, actual).toStatement() + return + } + + when (parametrizedTestSource) { + ParametrizedTestSource.DO_NOT_PARAMETRIZE -> { + traverseField(fieldId, fieldModel, expected, actual, depth, visitedModels) + } + + ParametrizedTestSource.PARAMETRIZE -> { + traverseFieldForParametrizedTest(fieldId, fieldModel, expected, actual, depth, visitedModels) + } + } + } + + private fun traverseField( + fieldId: FieldId, + fieldModel: UtModel, + expected: CgVariable, + actual: CgVariable, + depth: Int, + visitedModels: MutableSet + ) { + // fieldModel is not visited and will be marked in assertDeepEquals call + val fieldName = fieldId.name + var expectedVariable: CgVariable? = null + + if (needExpectedDeclaration(fieldModel)) { + val expectedFieldDeclaration = createDeclarationForFieldFromVariable(fieldId, expected, fieldName) + + currentBlock += expectedFieldDeclaration + expectedVariable = expectedFieldDeclaration.variable + } + + val actualFieldDeclaration = createDeclarationForFieldFromVariable(fieldId, actual, fieldName) + currentBlock += actualFieldDeclaration + + assertDeepEquals( + fieldModel, + expectedVariable, + actualFieldDeclaration.variable, + depth + 1, + visitedModels, + fieldId, + ) + emptyLineIfNeeded() + } + + private fun traverseFieldForParametrizedTest( + fieldId: FieldId, + fieldModel: UtModel, + expected: CgVariable, + actual: CgVariable, + depth: Int, + visitedModels: MutableSet + ) { + val fieldResultModels = fieldsOfExecutionResults[fieldId to depth] + val nullResultModelInExecutions = fieldResultModels?.find { it.isNull() } + val notNullResultModelInExecutions = fieldResultModels?.find { it.isNotNull() } + + val hasNullResultModel = nullResultModelInExecutions != null + val hasNotNullResultModel = notNullResultModelInExecutions != null + + val needToSubstituteFieldModel = fieldModel is UtNullModel && hasNotNullResultModel + + val fieldModelForAssert = if (needToSubstituteFieldModel) notNullResultModelInExecutions!! else fieldModel + + // fieldModel is not visited and will be marked in assertDeepEquals call + val fieldName = fieldId.name + var expectedVariable: CgVariable? = null + + val needExpectedDeclaration = needExpectedDeclaration(fieldModelForAssert) + if (needExpectedDeclaration) { + val expectedFieldDeclaration = createDeclarationForFieldFromVariable(fieldId, expected, fieldName) + + currentBlock += expectedFieldDeclaration + expectedVariable = expectedFieldDeclaration.variable + } + + val actualFieldDeclaration = createDeclarationForFieldFromVariable(fieldId, actual, fieldName) + currentBlock += actualFieldDeclaration + + if (needExpectedDeclaration && hasNullResultModel) { + ifStatement( + CgEqualTo(expectedVariable!!, nullLiteral()), + trueBranch = { +testFrameworkManager.assertions[testFramework.assertNull](actualFieldDeclaration.variable).toStatement() }, + falseBranch = { + assertDeepEquals( + fieldModelForAssert, + expectedVariable, + actualFieldDeclaration.variable, + depth + 1, + visitedModels, + fieldId, + ) + } + ) + } else { + assertDeepEquals( + fieldModelForAssert, + expectedVariable, + actualFieldDeclaration.variable, + depth + 1, + visitedModels, + fieldId, + ) + } + emptyLineIfNeeded() + } + + private fun collectExecutionsResultFields() { + for (model in successfulExecutionsModels) { + when (model) { + is UtCompositeModel -> collectExecutionsResultFieldsRecursively(model, 0) + + is UtModelWithCompositeOrigin -> model.origin?.let { + collectExecutionsResultFieldsRecursively(it, 0) + } + + // Lambdas do not have fields. They have captured values, but we do not consider them here. + is UtLambdaModel, + is UtNullModel, + is UtPrimitiveModel, + is UtArrayModel, + is UtClassRefModel, + is UtEnumConstantModel, + is UtVoidModel -> { + // only [UtCompositeModel] and [UtAssembleModel] have fields to traverse + } + else -> {} + } + } + } + + private fun collectExecutionsResultFieldsRecursively(model: UtCompositeModel, depth: Int) { + for ((fieldId, fieldModel) in model.fields) { + collectExecutionsResultFieldsRecursively(fieldId, fieldModel, depth) + } + } + + private fun collectExecutionsResultFieldsRecursively( + fieldId: FieldId, + fieldModel: UtModel, + depth: Int, + ) { + if (depth >= DEEP_EQUALS_MAX_DEPTH) { + return + } + + val fieldKey = fieldId to depth + fieldsOfExecutionResults.getOrPut(fieldKey) { mutableListOf() } += fieldModel + + when (fieldModel) { + is UtCompositeModel -> collectExecutionsResultFieldsRecursively(fieldModel, depth + 1) + + is UtModelWithCompositeOrigin -> fieldModel.origin?.let { + collectExecutionsResultFieldsRecursively(it, depth + 1) + } + + // Lambdas do not have fields. They have captured values, but we do not consider them here. + is UtLambdaModel, + is UtNullModel, + is UtPrimitiveModel, + is UtArrayModel, + is UtClassRefModel, + is UtEnumConstantModel, + is UtVoidModel -> { + // only [UtCompositeModel] and [UtAssembleModel] have fields to traverse + } + else -> {} + } + } + + @Suppress("UNUSED_ANONYMOUS_PARAMETER") + private fun createDeclarationForFieldFromVariable( + fieldId: FieldId, + variable: CgVariable, + fieldName: String + ): CgDeclaration { + val expectedFieldDeclaration = createDeclarationForNewVarAndUpdateVariableScopeOrGetExistingVariable( + baseType = fieldId.type, + baseName = "${variable.name}${fieldName.capitalize()}", + init = { fieldId.getAccessExpression(variable) } + ).either( + { declaration -> declaration }, + { unexpectedExistingVariable -> + error( + "Unexpected existing variable for field $fieldName with type ${fieldId.type} " + + "from expected variable ${variable.name} with type ${variable.type}" + ) + } + ) + return expectedFieldDeclaration + } + + private fun FieldId.getAccessExpression(variable: CgVariable): CgExpression = + // Can directly access field only if it is declared in variable class (or in its ancestors) + // and is accessible from current package + if (variable.type.hasField(this) && canBeReadFrom(context, variable.type)) { + if (jField.isStatic) CgStaticFieldAccess(this) else CgFieldAccess(variable, this) + } else if (context.codegenLanguage == CodegenLanguage.JAVA && + !jField.isStatic && canBeReadViaGetterFrom(context) + ) { + variable[getter]() + } else { + utilsClassId[getFieldValue](variable, this.declaringClass.name, this.name) + } + + /** + * Stores array information about ClassId. + * @property classId ClassId itself. + * @property nestedElementClassId the most nested element ClassId if it is array and null otherwise. + * @property dimensions 0 for non-arrays and number of dimensions in case of arrays. + */ + private data class ClassIdArrayInfo(val classId: ClassId, val nestedElementClassId: ClassId?, val dimensions: Int) { + val isArray get(): Boolean = dimensions > 0 + + val isPrimitiveArray get(): Boolean = isArray && nestedElementClassId!!.isPrimitive + + val isSingleDimensionalArray get(): Boolean = dimensions == 1 + + fun getNested(): ClassIdArrayInfo { + require(dimensions > 0) { "Trying to get nested array from not array type $classId" } + + return copy(dimensions = dimensions - 1) + } + } + + private val UtArrayModel.isArray: Boolean + get() = this.classId.isArray + + private fun UtArrayModel.collectArrayInfo(): ClassIdArrayInfo { + if (!isArray) return ClassIdArrayInfo( + classId = classId, + nestedElementClassId = null, + dimensions = 0 + ) + + val nestedElementClassIdList = generateSequence(classId.elementClassId) { it.elementClassId }.toList() + val dimensions = nestedElementClassIdList.size + val nestedElementClassId = nestedElementClassIdList.last() + + return ClassIdArrayInfo(classId, nestedElementClassId, dimensions) + } + + fun assertEquality( + expected: UtModel, + actual: CgVariable, + expectedVariableName: String = "expected", + emptyLineIfNeeded: Boolean = false, + ) { + val successfullyConstructedCustomAssert = expected is UtCustomModel && + customAssertConstructor.tryConstructCustomAssert(expected, actual) + + if (!successfullyConstructedCustomAssert) { + val expectedVariable = variableConstructor.getOrCreateVariable(expected, expectedVariableName) + if (emptyLineIfNeeded) emptyLineIfNeeded() + assertEquality(expectedVariable, actual) + } + } + + open fun assertEquality(expected: CgValue, actual: CgVariable) { + when { + expected.type.isArray -> { + // TODO: How to compare arrays of Float and Double wrappers? + // TODO: For example, JUnit5 does not have an assertEquals() overload for these wrappers. + // TODO: So for now we compare arrays of these wrappers as arrays of Objects, but that is probably wrong. + when (expected.type.elementClassId!!) { + floatClassId -> testFrameworkManager.assertFloatArrayEquals( + typeCast(floatArrayClassId, expected, isSafetyCast = true), + typeCast(floatArrayClassId, actual, isSafetyCast = true), + floatDelta + ) + doubleClassId -> testFrameworkManager.assertDoubleArrayEquals( + typeCast(doubleArrayClassId, expected, isSafetyCast = true), + typeCast(doubleArrayClassId, actual, isSafetyCast = true), + doubleDelta + ) + else -> { + val targetType = when { + expected.type.isPrimitiveArray -> expected.type + actual.type.isPrimitiveArray -> actual.type + else -> objectArrayClassId + } + if (targetType.isPrimitiveArray) { + // we can use simple arrayEquals for primitive arrays + testFrameworkManager.assertArrayEquals( + targetType, + typeCast(targetType, expected, isSafetyCast = true), + typeCast(targetType, actual, isSafetyCast = true) + ) + } else { + // array of objects, have to use deep equals + + when (expected) { + is CgLiteral -> testFrameworkManager.assertEquals(expected, actual) + is CgNotNullAssertion -> generateForNotNullAssertion(expected, actual) + else -> { + require(resultModel is UtArrayModel) { + "Result model have to be UtArrayModel to generate arrays assertion " + + "but `${resultModel::class}` found" + } + generateDeepEqualsOrNullAssertion(expected, actual) + } + } + } + } + } + } + else -> when { + (expected.type == floatClassId || expected.type == floatWrapperClassId) -> { + testFrameworkManager.assertFloatEquals( + typeCast(floatClassId, expected, isSafetyCast = true), + typeCast(floatClassId, actual, isSafetyCast = true), + floatDelta + ) + } + (expected.type == doubleClassId || expected.type == doubleWrapperClassId) -> { + testFrameworkManager.assertDoubleEquals( + typeCast(doubleClassId, expected, isSafetyCast = true), + typeCast(doubleClassId, actual, isSafetyCast = true), + doubleDelta + ) + } + expected == nullLiteral() -> testFrameworkManager.assertNull(actual) + expected is CgLiteral && expected.value is Boolean -> { + when (parametrizedTestSource) { + ParametrizedTestSource.DO_NOT_PARAMETRIZE -> + testFrameworkManager.assertBoolean(expected.value, actual) + ParametrizedTestSource.PARAMETRIZE -> + testFrameworkManager.assertEquals(expected, actual) + } + } + else -> { + when (expected) { + is CgLiteral -> testFrameworkManager.assertEquals(expected, actual) + is CgNotNullAssertion -> generateForNotNullAssertion(expected, actual) + else -> generateDeepEqualsOrNullAssertion(expected, actual) + } + } + } + } + } + + private fun generateForNotNullAssertion(expected: CgNotNullAssertion, actual: CgVariable) { + require(expected.expression is CgVariable) { + "Only CgVariable wrapped in CgNotNullAssertion is supported in deepEquals" + } + generateDeepEqualsOrNullAssertion(expected.expression, actual) + } + + private fun generateConstructorCall(currentExecutableId: ConstructorId, currentExecution: UtExecution) { + // we cannot generate any assertions for constructor testing + // but we need to generate a constructor call + val constructorCall = currentExecutableId as ConstructorId + val executionResult = currentExecution.result + + executionResult + .onSuccess { + methodType = SUCCESSFUL + + actual = newVar(constructorCall.classId, "actual") { + constructorCall(*methodArguments.toTypedArray()) + } + } + .onFailure { exception -> processExecutionFailure(exception, executionResult) } + } + + // Class is required to verify, if current model has already been analyzed in deepEquals. + // Using model without related field (if it is present) in comparison is incorrect, + // for example, for [UtNullModel] as they are equal to each other.. + private data class ModelWithField( + val fieldModel: UtModel, + val relatedField: FieldId?, + ) + + /** + * We can't use standard deepEquals method in parametrized tests + * because nullable objects require different asserts. + * See https://github.com/UnitTestBot/UTBotJava/issues/252 for more details. + */ + private fun generateDeepEqualsOrNullAssertion( + expected: CgValue, + actual: CgVariable, + ) { + when (parametrizedTestSource) { + ParametrizedTestSource.DO_NOT_PARAMETRIZE -> generateDeepEqualsAssertion(expected, actual) + ParametrizedTestSource.PARAMETRIZE -> { + collectExecutionsResultFields() + + when { + actual.type.isPrimitive -> generateDeepEqualsAssertion(expected, actual) + else -> ifStatement( + CgEqualTo(expected, nullLiteral()), + trueBranch = { + workaround(WorkaroundReason.CONSUME_DIRTY_STREAMS) { + if (containsStreamConsumingFailureForParametrizedTests) { + constructStreamConsumingBlock().invoke() + } else { + +testFrameworkManager.assertions[testFramework.assertNull](actual).toStatement() + } + } + }, + falseBranch = { + +testFrameworkManager.assertions[testFrameworkManager.assertNotNull](actual).toStatement() + generateDeepEqualsAssertion(expected, actual) + } + ) + } + } + } + } + + private fun generateDeepEqualsAssertion( + expected: CgValue, + actual: CgVariable, + ) { + require(expected is CgVariable) { + "Expected value have to be Literal or Variable but `${expected::class}` found" + } + + assertDeepEquals( + resultModel, + expected, + actual, + depth = 0, + visitedModels = hashSetOf() + ) + } + + protected fun recordActualResult() { + val executionResult = currentExecution!!.result + + executionResult.onSuccess { resultModel -> + when (val executable = currentExecutableToCall) { + is ConstructorId -> { + // there is nothing to generate for constructors + return + } + is BuiltinMethodId -> error("Unexpected BuiltinMethodId $executable while generating actual result") + is MethodId -> { + // TODO possible engine bug - void method return type and result model not UtVoidModel + if (resultModel.isUnit() || executable.returnType == voidClassId) return + + emptyLineIfNeeded() + + actual = newVar( + CgClassId(resultModel.classId, isNullable = resultModel is UtNullModel), + "actual" + ) { + thisInstance[executable](*methodArguments.toTypedArray()) + } + } + else -> {} // TODO: check this specific case + } + }.onFailure { + processActualInvocationFailure(executionResult) + } + } + + private fun processActualInvocationFailure(executionResult: UtExecutionResult) { + when (executionResult) { + is UtStreamConsumingFailure -> processStreamConsumingException(executionResult.rootCauseException) + else -> {} // Do nothing for now + } + } + + private fun processStreamConsumingException(innerException: Throwable) { + val executable = currentExecutableToCall + + require((executable is MethodId) && (executable.returnType isSubtypeOf baseStreamClassId)) { + "Unexpected exception $innerException during stream consuming in non-stream returning executable $executable" + } + + emptyLineIfNeeded() + + actual = newVar( + CgClassId(executable.returnType, isNullable = false), + "actual" + ) { + thisInstance[executable](*methodArguments.toTypedArray()) + } + } + + open fun createTestMethod(testSet: CgMethodTestSet, execution: UtExecution): CgTestMethod = + withTestMethodScope(execution) { + val testMethodName = nameGenerator.testMethodNameFor(testSet.executableUnderTest, execution.testMethodName) + if (execution.testMethodName == null) { + execution.testMethodName = testMethodName + } + // TODO: remove this line when SAT-1273 is completed + execution.displayName = execution.displayName?.let { "${testSet.executableUnderTest.name}: $it" } + testMethod(testMethodName, execution.displayName) { + //Enum constants are static, but there is no need to store and recover value for them + val statics = currentExecution!!.stateBefore + .statics + .filterNot { it.value is UtEnumConstantModel } + .filterNot { it.value.classId.outerClass?.isEnum == true } + + rememberInitialStaticFields(statics) + val stateAnalyzer = ExecutionStateAnalyzer(execution) + val modificationInfo = stateAnalyzer.findModifiedFields() + val fieldStateManager = CgFieldStateManagerImpl(context) + // TODO: move such methods to another class and leave only 2 public methods: remember initial and final states + val mainBody = { + substituteStaticFields(statics) + setupInstrumentation() + // build this instance + thisInstance = execution.stateBefore.thisInstance?.let { + variableConstructor.getOrCreateVariable(it) + } + // build arguments + for ((index, param) in execution.stateBefore.parameters.withIndex()) { + val name = paramNames[execution.executableToCall ?: testSet.executableUnderTest]?.get(index) + methodArguments += variableConstructor.getOrCreateVariable(param, name) + } + + if (requiresFieldStateAssertions()) { + // we should generate field assertions only for successful tests + // that does not break the current test execution after invocation of method under test + fieldStateManager.rememberInitialEnvironmentState(modificationInfo) + } + + recordActualResult() + generateResultAssertions() + + if (requiresFieldStateAssertions()) { + // we should generate field assertions only for successful tests + // that does not break the current test execution after invocation of method under test + fieldStateManager.rememberFinalEnvironmentState(modificationInfo) + generateFieldStateAssertions() + } + } + + if (statics.isNotEmpty()) { + +tryBlock { + mainBody() + }.finally { + recoverStaticFields() + } + } else { + mainBody() + } + + mockFrameworkManager.getAndClearMethodResources()?.let { resources -> + val closeFinallyBlock = resources.map { + val variable = it.variable + variable.type.closeMethodIdOrNull?.let { closeMethod -> + CgMethodCall(variable, closeMethod, arguments = emptyList()).toStatement() + } ?: error("Resource $variable was expected to be auto closeable but it is not") + } + + val tryWithMocksFinallyClosing = CgTryCatch(currentBlock, handlers = emptyList(), closeFinallyBlock) + currentBlock = currentBlock.clear() + resources.forEach { + // First argument for mocked resource declaration initializer is a target type. + // Pass this argument as a type parameter for the mocked resource + + // TODO this type parameter (required for Kotlin test) is unused until the proper implementation + // of generics in code generation https://github.com/UnitTestBot/UTBotJava/issues/88 + @Suppress("UNUSED_VARIABLE") + val typeParameter = when (val firstArg = (it.initializer as CgMethodCall).arguments.first()) { + is CgGetJavaClass -> firstArg.classId + is CgVariable -> firstArg.type + else -> error("Unexpected mocked resource declaration argument $firstArg") + } + + +CgDeclaration( + it.variableType, + it.variableName, + initializer = nullLiteral(), + isMutable = true, + ) + } + +tryWithMocksFinallyClosing + } + } + } + + private fun requiresFieldStateAssertions(): Boolean = + !methodType.isThrowing || + (methodType == PASSED_EXCEPTION && !testFrameworkManager.isExpectedExceptionExecutionBreaking) + + private val expectedResultVarName = "expectedResult" + private val expectedErrorVarName = "expectedError" + + /** + * Returns `null` if parameterized test method can't be created for [testSet] + * (for example, when there are multiple distinct [CgMethodTestSet.executablesToCall]). + */ + fun createParameterizedTestMethod(testSet: CgMethodTestSet, dataProviderMethodName: String): CgTestMethod? { + //TODO: orientation on generic execution may be misleading, but what is the alternative? + //may be a heuristic to select a model with minimal number of internal nulls should be used + val genericExecution = chooseGenericExecution(testSet.executions) + + workaround(WorkaroundReason.CONSUME_DIRTY_STREAMS) { + containsStreamConsumingFailureForParametrizedTests = testSet.executions.any { + it.result is UtStreamConsumingFailure + } + } + + val statics = genericExecution.stateBefore.statics + + return withTestMethodScope(genericExecution) { + val testName = nameGenerator.parameterizedTestMethodName(dataProviderMethodName) + withNameScope { + val testParameterDeclarations = + createParameterDeclarations(testSet, genericExecution) ?: return@withNameScope null + + methodType = PARAMETRIZED + testMethod( + testName, + displayName = null, + testParameterDeclarations, + parameterized = true, + dataProviderMethodName + ) { + rememberInitialStaticFields(statics) + substituteStaticFields(statics, isParametrized = true) + + // build this instance + thisInstance = genericExecution.stateBefore.thisInstance?.let { + currentMethodParameters[CgParameterKind.ThisInstance] + } + + // build arguments for method under test and parameterized test + for (index in genericExecution.stateBefore.parameters.indices) { + methodArguments += currentMethodParameters[CgParameterKind.Argument(index)]!! + } + + if (containsFailureExecution(testSet) || statics.isNotEmpty()) { + var currentTryBlock = tryBlock { + recordActualResult() + generateAssertionsForParameterizedTest() + } + + if (containsFailureExecution(testSet)) { + val expectedErrorVariable = currentMethodParameters[CgParameterKind.ExpectedException] + ?: error("Test set $testSet contains failure execution, but test method signature has no error parameter") + currentTryBlock = + if (containsReflectiveCall) { + currentTryBlock.catch(InvocationTargetException::class.java.id) { exception -> + testFrameworkManager.assertBoolean( + expectedErrorVariable.isInstance(exception[getTargetException]()) + ) + } + } else { + currentTryBlock.catch(Throwable::class.java.id) { throwable -> + testFrameworkManager.assertBoolean( + expectedErrorVariable.isInstance(throwable) + ) + } + } + } + + if (statics.isNotEmpty()) { + currentTryBlock = currentTryBlock.finally { + recoverStaticFields() + } + } + +currentTryBlock + } else { + recordActualResult() + generateAssertionsForParameterizedTest() + } + } + } + } + } + + private fun chooseGenericExecution(executions: List): UtExecution { + return executions + .firstOrNull { it.result is UtExecutionSuccess && (it.result as UtExecutionSuccess).model !is UtNullModel } + ?: executions + .firstOrNull { it.result is UtExecutionSuccess } ?: executions.first() + } + + /** + * Returns `null` if parameter declarations can't be created for [testSet] + * (for example, when there are multiple distinct [CgMethodTestSet.executablesToCall]). + */ + private fun createParameterDeclarations( + testSet: CgMethodTestSet, + genericExecution: UtExecution, + ): List? { + val executableToCall = testSet.executablesToCall.singleOrNull() ?: return null + val executableUnderTestParameters = executableToCall.executable.parameters + + return mutableListOf().apply { + // this instance + genericExecution.stateBefore.thisInstance?.let { + val type = wrapTypeIfRequired(it.classId) + val thisInstance = CgParameterDeclaration( + parameter = declareParameter( + type = type, + name = nameGenerator.variableName(type) + ), + isReferenceType = true + ) + this += thisInstance + currentMethodParameters[CgParameterKind.ThisInstance] = thisInstance.parameter + } + + // arguments + for (index in genericExecution.stateBefore.parameters.indices) { + val argumentName = paramNames[executableToCall]?.get(index) + val paramType = executableUnderTestParameters[index].parameterizedType + + val argumentType = when { + paramType is Class<*> && paramType.isArray -> paramType.id + paramType is ParameterizedType -> paramType.id + else -> ClassId(paramType.typeName) + } + + val argument = CgParameterDeclaration( + parameter = declareParameter( + type = argumentType, + name = nameGenerator.variableName(argumentType, argumentName), + ), + isReferenceType = argumentType.isRefType + ) + this += argument + currentMethodParameters[CgParameterKind.Argument(index)] = argument.parameter + } + + val statics = genericExecution.stateBefore.statics + if (statics.isNotEmpty()) { + for ((fieldId, model) in statics) { + val staticType = wrapTypeIfRequired(model.classId) + val static = CgParameterDeclaration( + parameter = declareParameter( + type = staticType, + name = nameGenerator.variableName(fieldId.name, isStatic = true) + ), + isReferenceType = staticType.isRefType + ) + this += static + currentMethodParameters[CgParameterKind.Statics(model)] = static.parameter + } + } + + val expectedResultClassId = wrapTypeIfRequired(testSet.getCommonResultTypeOrNull() ?: return null) + if (expectedResultClassId != voidClassId) { + val wrappedType = wrapIfPrimitive(expectedResultClassId) + //We are required to wrap the type of expected result if it is primitive + //to support nulls for throwing exceptions executions. + val expectedResult = CgParameterDeclaration( + parameter = declareParameter( + type = wrappedType, + name = nameGenerator.variableName(expectedResultVarName) + ), + isReferenceType = wrappedType.isRefType + ) + this += expectedResult + currentMethodParameters[CgParameterKind.ExpectedResult] = expectedResult.parameter + } + + val containsFailureExecution = containsFailureExecution(testSet) + if (containsFailureExecution) { + val classClassId = Class::class.id + val expectedException = CgParameterDeclaration( + parameter = declareParameter( + type = BuiltinClassId( + simpleName = classClassId.simpleName, + canonicalName = classClassId.canonicalName, + packageName = classClassId.packageName, + typeParameters = TypeParameters(listOf(Throwable::class.java.id)) + ), + name = nameGenerator.variableName(expectedErrorVarName) + ), + // exceptions are always reference type + isReferenceType = true, + ) + this += expectedException + currentMethodParameters[CgParameterKind.ExpectedException] = expectedException.parameter + } + } + } + + /** + * Constructs data provider method for parameterized tests. + * + * The body of this method is constructed manually, statement by statement. + * Standard logic for generating each test case parameter code is used. + */ + fun createParameterizedTestDataProvider( + testSet: CgMethodTestSet, + dataProviderMethodName: String + ): CgParameterizedTestDataProviderMethod { + return withDataProviderScope { + dataProviderMethod(dataProviderMethodName) { + val argListLength = testSet.executions.size + val argListVariable = testFrameworkManager.createArgList(argListLength) + + emptyLine() + + for ((execIndex, execution) in testSet.executions.withIndex()) { + // create a block for current test case + innerBlock { + val arguments = createExecutionArguments(testSet, execution) + createArgumentsCallRepresentation(execIndex, argListVariable, arguments) + } + } + + emptyLineIfNeeded() + + returnStatement { argListVariable } + } + } + } + + private fun createExecutionArguments(testSet: CgMethodTestSet, execution: UtExecution): List { + val arguments = mutableListOf() + + execution.stateBefore.thisInstance?.let { + arguments += variableConstructor.getOrCreateVariable(it) + } + + for ((paramIndex, paramModel) in execution.stateBefore.parameters.withIndex()) { + val argumentName = paramNames[execution.executableToCall ?: testSet.executableUnderTest]?.get(paramIndex) + arguments += variableConstructor.getOrCreateVariable(paramModel, argumentName) + } + + val statics = execution.stateBefore.statics + for ((field, model) in statics) { + arguments += variableConstructor.getOrCreateVariable(model, field.name) + } + + val method = currentExecutableToCall!! + val needsReturnValue = method.returnType != voidClassId + val containsFailureExecution = containsFailureExecution(testSet) + execution.result + .onSuccess { + if (needsReturnValue) { + arguments += variableConstructor.getOrCreateVariable(it) + } + if (containsFailureExecution) { + arguments += nullLiteral() + } + } + .onFailure { + if (needsReturnValue) { + arguments += nullLiteral() + } + if (containsFailureExecution) { + arguments += CgGetJavaClass(it::class.id) + } + } + + emptyLineIfNeeded() + + return arguments + } + + protected fun withTestMethodScope(execution: UtExecution, block: () -> R): R { + clearTestMethodScope() + currentExecution = execution + determineExecutionType() + statesCache = EnvironmentFieldStateCache.emptyCacheFor(execution) +// modelToUsageCountInMethod = countUsages(ignoreAssembleOrigin = true) { counter -> +// execution.mapAllModels(counter) +// } + return try { + block() + } finally { + clearTestMethodScope() + } + } + + private fun withDataProviderScope(block: () -> R): R { + clearMethodScope() + return try { + block() + } finally { + clearMethodScope() + } + } + + /** + * This function makes sure some information about the method currently being generated is empty. + * It clears only the information that is relevant to all kinds of methods: + * - test methods + * - data provider methods + * - and any other kinds of methods that may be added in the future + */ + private fun clearMethodScope() { + collectedExceptions.clear() + collectedMethodAnnotations.clear() + } + + /** + * This function makes sure some information about the **test method** currently being generated is empty. + * It is used at the start of test method generation and right after it. + */ + private fun clearTestMethodScope() { + clearMethodScope() + prevStaticFieldValues.clear() + thisInstance = null + methodArguments.clear() + currentExecution = null + containsReflectiveCall = false + mockFrameworkManager.clearExecutionResources() + currentMethodParameters.clear() + } + + /** + * Generates a collection of [CgStatement] to prepare arguments + * for current execution in parameterized tests. + */ + private fun createArgumentsCallRepresentation( + executionIndex: Int, + argsVariable: CgVariable, + arguments: List, + ) { + val argsArray = newVar(objectArrayClassId, "testCaseObjects") { + CgAllocateArray(objectArrayClassId, objectClassId, arguments.size) + } + for ((i, argument) in arguments.withIndex()) { + setArgumentsArrayElement(argsArray, i, argument, this) + } + testFrameworkManager.passArgumentsToArgsVariable(argsVariable, argsArray, executionIndex) + } + + private fun containsFailureExecution(testSet: CgMethodTestSet) = + testSet.executions.any { it.result is UtExecutionFailure } + + + /** + * Determines [CgTestMethodType] for current execution according to its success or failure. + */ + private fun determineExecutionType() { + val currentExecution = currentExecution!! + + currentExecution.result + .onSuccess { methodType = SUCCESSFUL } + .onFailure { exception -> + methodType = when { + shouldTestPassWithException(currentExecution, exception) -> PASSED_EXCEPTION + shouldTestPassWithTimeoutException(currentExecution, exception) -> TIMEOUT + else -> when (exception) { + is ArtificialError -> ARTIFICIAL + is InstrumentedProcessDeathException -> CRASH + is AccessControlException -> CRASH // exception from sandbox + else -> FAILING + } + } + } + } + + protected fun testMethod( + methodName: String, + displayName: String?, + params: List = emptyList(), + parameterized: Boolean = false, + dataProviderMethodName: String? = null, + body: () -> Unit, + ): CgTestMethod { + if (parameterized) { + testFrameworkManager.addParameterizedTestAnnotations(dataProviderMethodName) + } else { + addAnnotation(testFramework.testAnnotationId, AnnotationTarget.Method) + } + + displayName?.let { + testFrameworkManager.addTestDescription(displayName) + } + + val result = currentExecution!!.result + if (result is UtTimeoutException) { + testFrameworkManager.setTestExecutionTimeout(hangingTestsTimeout.timeoutMs) + } + + if (result is UtTimeoutException && !enableTestsTimeout) { + testFrameworkManager.disableTestMethod( + "Disabled due to failing by exceeding the timeout" + ) + } + + if (result is UtConcreteExecutionFailure) { + testFrameworkManager.disableTestMethod( + "Disabled due to possible JVM crash" + ) + } + + if (result is UtSandboxFailure) { + testFrameworkManager.disableTestMethod( + "Disabled due to sandbox" + ) + } + + val testMethod = buildTestMethod { + name = methodName + parameters = params + statements = block(body) + // Exceptions and annotations assignment must run after the statements block is build, + // because we collect info about exceptions and required annotations while building the statements + exceptions += collectedExceptions + annotations += collectedMethodAnnotations + methodType = this@CgMethodConstructor.methodType + val docComment = currentExecution?.summary?.map { convertDocToCg(it) }?.toMutableList() ?: mutableListOf() + + documentation = CgDocumentationComment(docComment) + documentation = if (parameterized) { + CgDocumentationComment(text = null) + } else { + CgDocumentationComment(docComment) + } + } + testMethods += testMethod + return testMethod + } + + private fun dataProviderMethod(dataProviderMethodName: String, body: () -> Unit): CgParameterizedTestDataProviderMethod { + return buildParameterizedTestDataProviderMethod { + name = dataProviderMethodName + returnType = testFramework.argListClassId + statements = block(body) + // Exceptions and annotations assignment must run after the statements block is build, + // because we collect info about exceptions and required annotations while building the statements + testFrameworkManager.addDataProviderAnnotations(dataProviderMethodName) + exceptions += collectedExceptions + annotations += collectedMethodAnnotations + } + } + + fun errorMethod(testSet: CgMethodTestSet, errors: Map): CgRegion { + val name = nameGenerator.errorMethodNameFor(testSet.executableUnderTest) + val body = block { + comment("Couldn't generate some tests. List of errors:") + comment() + errors.entries.sortedByDescending { it.value }.forEach { (message, repeated) -> + val multilineMessage = message + .split("\r") // split stacktrace from concrete if present + .flatMap { line -> + line + .split(" ") + .windowed(size = 10, step = 10, partialWindows = true) { + it.joinToString(separator = " ") + } + } + comment("$repeated occurrences of:") + + if (multilineMessage.size <= 1) { + // wrap one liner with line comment + multilineMessage.singleOrNull()?.let { comment(it) } + } else { + // wrap all lines with multiline comment + multilineComment(multilineMessage) + } + + emptyLine() + } + } + val errorTestMethod = CgErrorTestMethod(name, body) + return CgSimpleRegion("Errors report for ${testSet.executableUnderTest.name}", listOf(errorTestMethod)) + } + + private fun CgExecutableCall.wrapReflectiveCall() { + +tryBlock { + +this@wrapReflectiveCall + }.catch(InvocationTargetException::class.id) { e -> + throwStatement { + e[getTargetException]() + } + } + } + + /** + * Intercept calls to [java.lang.reflect.Method.invoke] and to [java.lang.reflect.Constructor.newInstance] + * in order to wrap these calls in a try-catch block that will handle [InvocationTargetException] + * that may be thrown by these calls. + */ + protected fun CgExecutableCall.intercepted() { + val executableToWrap = when (executableId) { + is MethodId -> invoke + is ConstructorId -> newInstance + } + if (executableId == executableToWrap) { + this.wrapReflectiveCall() + } else { + +this + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSimpleTestClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSimpleTestClassConstructor.kt new file mode 100644 index 0000000000..f7207630e4 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSimpleTestClassConstructor.kt @@ -0,0 +1,202 @@ +package org.utbot.framework.codegen.tree + +import org.utbot.framework.codegen.domain.Junit4 +import org.utbot.framework.codegen.domain.Junit5 +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.TestNg +import org.utbot.framework.codegen.domain.builtin.TestClassUtilMethodProvider +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgClassBody +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.CgMethodsCluster +import org.utbot.framework.codegen.domain.models.CgRealNestedClassesRegion +import org.utbot.framework.codegen.domain.models.CgRegion +import org.utbot.framework.codegen.domain.models.CgSimpleRegion +import org.utbot.framework.codegen.domain.models.CgStaticsRegion +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.SimpleTestClassModel +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtSymbolicExecution +import org.utbot.framework.plugin.api.util.humanReadableName +import org.utbot.fuzzer.UtFuzzedExecution + +/** + * This test class constructor is used for pure Java/Kotlin applications. + */ +open class CgSimpleTestClassConstructor(context: CgContext): CgAbstractTestClassConstructor(context) { + + override fun constructTestClassBody(testClassModel: SimpleTestClassModel): CgClassBody = + buildClassBody(currentTestClass) { + val notYetConstructedTestSets = testClassModel.methodTestSets.toMutableList() + + for (nestedClass in testClassModel.nestedClasses) { + // It is not possible to run tests for both outer and inner class in JUnit4 at once, + // so we locate all test methods in outer test class for JUnit4. + // see https://stackoverflow.com/questions/69770700/how-to-run-tests-from-outer-class-and-nested-inner-classes-simultaneously-in-jun + // or https://stackoverflow.com/questions/28230277/test-cases-in-inner-class-and-outer-class-with-junit4 + when (testFramework) { + Junit4 -> { + notYetConstructedTestSets += collectTestSetsFromInnerClasses(nestedClass) + } + Junit5, + TestNg -> { + nestedClassRegions += CgRealNestedClassesRegion( + "Tests for ${nestedClass.classUnderTest.simpleName}", + listOf(withNestedClassScope(nestedClass) { constructTestClass(nestedClass) }) + ) + } + } + } + + for ((testSetIndex, testSet) in notYetConstructedTestSets.withIndex()) { + updateExecutableUnderTest(testSet.executableUnderTest) + withTestSetIdScope(testSetIndex) { + val currentMethodUnderTestRegions = constructTestSet(testSet) ?: return@withTestSetIdScope + val executableUnderTestCluster = CgMethodsCluster( + "Test suites for executable $currentExecutableUnderTest", + currentMethodUnderTestRegions + ) + methodRegions += executableUnderTestCluster + } + } + + val currentTestClassDataProviderMethods = currentTestClassContext.cgDataProviderMethods + if (currentTestClassDataProviderMethods.isNotEmpty()) { + staticDeclarationRegions += + CgStaticsRegion( + "Data provider methods for parametrized tests", + currentTestClassDataProviderMethods, + ) + } + + if (currentTestClass == outerMostTestClass) { + val utilEntities = collectUtilEntities() + // If utilMethodProvider is TestClassUtilMethodProvider, then util entities should be declared + // in the test class. Otherwise, util entities will be located elsewhere (e.g. another class). + if (utilMethodProvider is TestClassUtilMethodProvider && utilEntities.isNotEmpty()) { + staticDeclarationRegions += CgStaticsRegion("Util methods", utilEntities) + } + } + } + + override fun constructTestSet(testSet: CgMethodTestSet): List>? { + val regions = mutableListOf>() + + if (testSet.executions.any()) { + successfulExecutionsModels = testSet + .executions + .filter { it.result is UtExecutionSuccess } + .map { (it.result as UtExecutionSuccess).model } + + runCatching { + when (context.parametrizedTestSource) { + ParametrizedTestSource.DO_NOT_PARAMETRIZE -> createTest(testSet, regions) + ParametrizedTestSource.PARAMETRIZE -> + createParametrizedTestAndDataProvider( + testSet, + regions + ).let { parametrizedTestCreatedSuccessfully -> + if (!parametrizedTestCreatedSuccessfully) createTest(testSet, regions) + } + } + }.onFailure { e -> processFailure(testSet, e) } + } + + val errors = testSet.allErrors + if (errors.isNotEmpty()) { + regions += methodConstructor.errorMethod(testSet, errors) + testsGenerationReport.addMethodErrors(testSet, errors) + } + + return if (regions.any()) regions else null + } + + private fun collectTestSetsFromInnerClasses(model: SimpleTestClassModel): List { + val testSets = model.methodTestSets.toMutableList() + for (nestedClass in model.nestedClasses) { + testSets += collectTestSetsFromInnerClasses(nestedClass) + } + + return testSets + } + + /** + * Returns `false` if parameterized test method can't be created for [testSet] + * (for example, when there are multiple distinct [CgMethodTestSet.executablesToCall]). + */ + private fun createParametrizedTestAndDataProvider( + testSet: CgMethodTestSet, + regions: MutableList> + ) : Boolean { + val (methodUnderTest, _, _) = testSet + + for (preparedTestSet in testSet.prepareTestSetsForParameterizedTestGeneration()) { + val dataProviderMethodName = nameGenerator.dataProviderMethodNameFor(preparedTestSet.executableUnderTest) + + val parameterizedTestMethod = + methodConstructor.createParameterizedTestMethod(preparedTestSet, dataProviderMethodName) ?: return false + + testFrameworkManager.addDataProvider( + methodConstructor.createParameterizedTestDataProvider(preparedTestSet, dataProviderMethodName) + ) + + regions += CgSimpleRegion( + "Parameterized test for method ${methodUnderTest.humanReadableName}", + listOf(parameterizedTestMethod), + ) + } + + regions += CgSimpleRegion( + "SYMBOLIC EXECUTION: additional tests for symbolic executions for method ${methodUnderTest.humanReadableName} that cannot be presented as parameterized", + collectAdditionalSymbolicTestsForParametrizedMode(testSet), + ) + + regions += CgSimpleRegion( + "FUZZER: Tests for method ${methodUnderTest.humanReadableName} that cannot be presented as parameterized", + collectFuzzerTestsForParameterizedMode(testSet), + ) + + return true + } + + /** + * Collects standard tests for fuzzer executions in parametrized mode. + * This is a requirement from [https://github.com/UnitTestBot/UTBotJava/issues/1137]. + */ + private fun collectFuzzerTestsForParameterizedMode(testSet: CgMethodTestSet): List { + val testMethods = mutableListOf() + + testSet.executions + .filterIsInstance() + .withIndex() + .forEach { (index, execution) -> + withExecutionIdScope(index) { + testMethods += methodConstructor.createTestMethod(testSet, execution) + } + } + + return testMethods + } + + /** + * Collects standard tests for symbolic executions that can't be included into parametrized tests. + * This is a requirement from [https://github.com/UnitTestBot/UTBotJava/issues/1231]. + */ + private fun collectAdditionalSymbolicTestsForParametrizedMode(testSet: CgMethodTestSet): List { + val testMethods = mutableListOf() + + testSet.executions + .filterIsInstance() + .filter { it.containsMocking } + .withIndex() + .forEach { (index, execution) -> + withExecutionIdScope(index) { + testMethods += methodConstructor.createTestMethod(testSet, execution) + } + } + + return testMethods + } +} + diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgStatementConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgStatementConstructor.kt new file mode 100644 index 0000000000..fc6a262dae --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgStatementConstructor.kt @@ -0,0 +1,595 @@ +package org.utbot.framework.codegen.tree + +import org.utbot.framework.codegen.domain.builtin.forName +import org.utbot.framework.codegen.domain.builtin.mockMethodId +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.util.isAccessibleFrom +import org.utbot.framework.codegen.util.nullLiteral +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.executable +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.isNotSubtypeOf +import org.utbot.framework.plugin.api.util.isSubtypeOf +import org.utbot.framework.plugin.api.util.objectArrayClassId +import org.utbot.framework.plugin.api.util.objectClassId +import fj.data.Either +import org.utbot.framework.codegen.domain.builtin.getDeclaredConstructor +import org.utbot.framework.codegen.domain.builtin.getDeclaredField +import org.utbot.framework.codegen.domain.builtin.getDeclaredMethod +import org.utbot.framework.codegen.domain.models.* +import org.utbot.framework.codegen.domain.models.AnnotationTarget +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.tree.CgComponents.getCallableAccessManagerBy +import org.utbot.framework.codegen.tree.CgComponents.getNameGeneratorBy +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.classClassId +import org.utbot.framework.plugin.api.util.constructorClassId +import org.utbot.framework.plugin.api.util.fieldClassId +import org.utbot.framework.plugin.api.util.isPrimitive +import org.utbot.framework.plugin.api.util.methodClassId +import org.utbot.framework.plugin.api.util.denotableType +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isEnum +import org.utbot.framework.plugin.api.util.supertypeOfAnonymousClass +import java.lang.reflect.Constructor +import java.lang.reflect.Method +import kotlin.reflect.KFunction +import kotlin.reflect.jvm.kotlinFunction + +interface CgStatementConstructor { + + fun newVar(baseType: ClassId, baseName: String? = null, init: () -> CgExpression): CgVariable = + newVar(baseType, model = null, baseName, isMutable = false, init) + + fun newVar( + baseType: ClassId, + baseName: String? = null, + isMutable: Boolean = false, + init: () -> CgExpression + ): CgVariable = + newVar(baseType, model = null, baseName, isMutable = isMutable, init) + + fun newVar( + baseType: ClassId, + model: UtModel? = null, + baseName: String? = null, + isMutable: Boolean = false, + init: () -> CgExpression + ): CgVariable = newVar(baseType, model, baseName, isMock = false, isMutable = isMutable, init) + + fun newVar( + baseType: ClassId, + model: UtModel? = null, + baseName: String? = null, + isMock: Boolean = false, + isMutable: Boolean = false, + init: () -> CgExpression + ): CgVariable + + fun createDeclarationForNewVarAndUpdateVariableScopeOrGetExistingVariable( + baseType: ClassId, + model: UtModel? = null, + baseName: String? = null, + isMock: Boolean = false, + isMutableVar: Boolean = false, + init: () -> CgExpression + ): Either + + // assignment + infix fun CgExpression.`=`(value: Any?) + infix fun CgExpression.and(other: CgExpression): CgLogicalAnd + infix fun CgExpression.or(other: CgExpression): CgLogicalOr + fun ifStatement(condition: CgExpression, trueBranch: () -> Unit, falseBranch: (() -> Unit)? = null): CgIfStatement + fun forLoop(init: CgForLoopBuilder.() -> Unit) + fun whileLoop(condition: CgExpression, statements: () -> Unit) + fun doWhileLoop(condition: CgExpression, statements: () -> Unit) + fun forEachLoop(init: CgForEachLoopBuilder.() -> Unit) + + fun getClassOf(classId: ClassId): CgExpression + + /** + * Create a variable of type [java.lang.reflect.Field] by the given [FieldId]. + */ + fun createFieldVariable(fieldId: FieldId): CgVariable + + + /** + * Given an [executableId] that represents method or constructor and a list of arguments for it, + * create a variable of type [java.lang.reflect.Method] or [java.lang.reflect.Constructor]. + * This created variable is returned. + */ + fun createExecutableVariable(executableId: ExecutableId, arguments: List): CgVariable + + fun tryBlock(init: () -> Unit): CgTryCatch + fun tryBlock(init: () -> Unit, resources: List?): CgTryCatch + fun CgTryCatch.catch(exception: ClassId, init: (CgVariable) -> Unit): CgTryCatch + fun CgTryCatch.finally(init: () -> Unit): CgTryCatch + + fun CgExpression.isInstance(value: CgExpression): CgIsInstance + + fun innerBlock(init: () -> Unit): CgInnerBlock + +// fun CgTryCatchBuilder.statements(init: () -> Unit) +// fun CgTryCatchBuilder.handler(exception: ClassId, init: (CgVariable) -> Unit) + + fun comment(text: String): CgComment + fun comment(): CgComment + + fun multilineComment(lines: List): CgComment + + fun lambda(type: ClassId, vararg parameters: CgVariable, body: () -> Unit): CgAnonymousFunction + + fun addAnnotation(classId: ClassId, argument: Any?, target: AnnotationTarget): CgAnnotation + + fun addAnnotation( + classId: ClassId, + namedArguments: List, + target: AnnotationTarget, + ): CgAnnotation + + fun addAnnotation( + classId: ClassId, + target: AnnotationTarget, + buildArguments: MutableList>.() -> Unit = {}, + ): CgAnnotation + + fun returnStatement(expression: () -> CgExpression) + + // Throw statement + fun throwStatement(exception: () -> CgExpression): CgThrowStatement + + fun emptyLine() + fun emptyLineIfNeeded() + + // utils + + fun declareParameter(type: ClassId, name: String): CgVariable = declareVariable(type, name) + + fun declareVariable(type: ClassId, name: String): CgVariable + + fun guardExpression(baseType: ClassId, expression: CgExpression): ExpressionWithType + + fun wrapTypeIfRequired(baseType: ClassId): ClassId +} + +internal class CgStatementConstructorImpl(context: CgContext) : + CgStatementConstructor, + CgContextOwner by context, + CgCallableAccessManager by getCallableAccessManagerBy(context) { + + private val nameGenerator = getNameGeneratorBy(context) + + override fun createDeclarationForNewVarAndUpdateVariableScopeOrGetExistingVariable( + baseType: ClassId, + model: UtModel?, + baseName: String?, + isMock: Boolean, + isMutableVar: Boolean, + init: () -> CgExpression + ): Either { + // check if we can use base type and initializer directly + // if not, fall back to using reflection + val baseExpr = init() + val (type, expr) = when (baseExpr) { + is CgEnumConstantAccess -> guardEnumConstantAccess(baseExpr) + is CgAllocateArray -> guardArrayAllocation(baseExpr) + is CgArrayInitializer -> guardArrayInitializer(baseExpr) + is CgExecutableCall -> guardExecutableCall(baseType, baseExpr) + else -> guardExpression(baseType, baseExpr) + } + + val classRef = classRefOrNull(type, expr) + if (classRef in declaredClassRefs) { + return Either.right(declaredClassRefs[classRef]!!) + } + + val name = when { + classRef != null && baseName == null -> { + val base = classRef.prettifiedName.decapitalize() + nameGenerator.variableName(base + "Clazz") + } + // we use baseType here intentionally + else -> nameGenerator.variableName(baseType, baseName, isMock) + } + + importIfNeeded(type) + + val declaration = buildDeclaration { + variableType = type + variableName = name + initializer = expr + isMutable = isMutableVar + } + + classRef?.let { declaredClassRefs = declaredClassRefs.put(it, declaration.variable) } + rememberVariableForModel(declaration.variable, model) + + return Either.left(declaration) + } + + override fun newVar( + baseType: ClassId, + model: UtModel?, + baseName: String?, + isMock: Boolean, + isMutable: Boolean, + init: () -> CgExpression + ): CgVariable { + // it is important that we use a denotable type for declaration, because that allows + // us to avoid creating `Object` variables for instances of anonymous classes, + // where we can instead use the supertype of the anonymous class + val declarationOrVar: Either = + createDeclarationForNewVarAndUpdateVariableScopeOrGetExistingVariable( + baseType.denotableType, + model, + baseName, + isMock, + isMutable, + init + ) + + return declarationOrVar.either( + { declaration -> + currentBlock += declaration + + declaration.variable + }, + { variable -> variable } + ) + } + + override fun CgExpression.`=`(value: Any?) { + currentBlock += buildAssignment { + lValue = this@`=` + rValue = value.resolve() + } + } + + override fun CgExpression.and(other: CgExpression): CgLogicalAnd = + CgLogicalAnd(this, other) + + override fun CgExpression.or(other: CgExpression): CgLogicalOr = + CgLogicalOr(this, other) + + override fun ifStatement(condition: CgExpression, trueBranch: () -> Unit, falseBranch: (() -> Unit)?): CgIfStatement { + val trueBranchBlock = block(trueBranch) + val falseBranchBlock = falseBranch?.let { block(it) } + return CgIfStatement(condition, trueBranchBlock, falseBranchBlock).also { + currentBlock += it + } + } + + override fun forLoop(init: CgForLoopBuilder.() -> Unit) = withNameScope { + currentBlock += buildForLoop(init) + } + + override fun whileLoop(condition: CgExpression, statements: () -> Unit) { + currentBlock += buildWhileLoop { + this.condition = condition + this.statements += block(statements) + } + } + + override fun doWhileLoop(condition: CgExpression, statements: () -> Unit) { + currentBlock += buildDoWhileLoop { + this.condition = condition + this.statements += block(statements) + } + } + + override fun forEachLoop(init: CgForEachLoopBuilder.() -> Unit) = withNameScope { + currentBlock += buildCgForEachLoop(init) + } + + /** + * @return expression for [java.lang.Class] of the given [classId] + */ + override fun getClassOf(classId: ClassId): CgExpression { + return if (classId isAccessibleFrom testClassPackageName) { + CgGetJavaClass(classId) + } else { + newVar(classCgClassId) { classClassId[forName](classId.name) } + } + } + + + override fun createFieldVariable(fieldId: FieldId): CgVariable { + val declaringClass = newVar(classClassId) { classClassId[forName](fieldId.declaringClass.name) } + val name = fieldId.name + "Field" + return newVar(fieldClassId, name) { + declaringClass[getDeclaredField](fieldId.name) + } + } + + override fun createExecutableVariable(executableId: ExecutableId, arguments: List): CgVariable { + val declaringClass = newVar(classClassId) { classClassId[forName](executableId.classId.name) } + val argTypes = (arguments zip executableId.parameters).map { (argument, parameterType) -> + val baseName = when (argument) { + is CgVariable -> "${argument.name}Type" + else -> "${parameterType.prettifiedName.decapitalize()}Type" + } + newVar(classCgClassId, baseName) { + if (parameterType.isPrimitive) { + CgGetJavaClass(parameterType) + } else { + classClassId[forName](parameterType.name) + } + } + } + + return when (executableId) { + is MethodId -> { + val name = executableId.name + "Method" + newVar(methodClassId, name) { + declaringClass[getDeclaredMethod](executableId.name, *argTypes.toTypedArray()) + } + } + is ConstructorId -> { + val name = executableId.classId.prettifiedName.decapitalize() + "Constructor" + newVar(constructorClassId, name) { + declaringClass[getDeclaredConstructor](*argTypes.toTypedArray()) + } + } + } + } + + override fun tryBlock(init: () -> Unit): CgTryCatch = tryBlock(init, null) + + override fun tryBlock(init: () -> Unit, resources: List?): CgTryCatch = + buildTryCatch { + statements = block(init) + this.resources = resources + } + + override fun CgTryCatch.catch(exception: ClassId, init: (CgVariable) -> Unit): CgTryCatch { + val newHandler = buildExceptionHandler { + val e = declareVariable(exception, nameGenerator.variableName(exception.simpleName.decapitalize())) + this.exception = e + this.statements = block { init(e) } + } + return this.copy(handlers = handlers + newHandler) + } + + override fun CgTryCatch.finally(init: () -> Unit): CgTryCatch { + val finallyBlock = block(init) + return this.copy(finally = finallyBlock) + } + + override fun CgExpression.isInstance(value: CgExpression): CgIsInstance { + require(this.type == classClassId) { + "isInstance method can be called on object with type $classClassId only, but actual type is ${this.type}" + } + + //TODO: we should better process it as this[isInstanceMethodId](value) as it is a call + return CgIsInstance(this, value) + } + + override fun innerBlock(init: () -> Unit): CgInnerBlock = + CgInnerBlock(block(init)).also { + currentBlock += it + } + + override fun comment(text: String): CgComment = + CgSingleLineComment(text).also { + currentBlock += it + } + + override fun comment(): CgComment = + CgSingleLineComment("").also { + currentBlock += it + } + + override fun multilineComment(lines: List): CgComment = + CgMultilineComment(lines).also { + currentBlock += it + } + + override fun lambda(type: ClassId, vararg parameters: CgVariable, body: () -> Unit): CgAnonymousFunction { + return withNameScope { + for (parameter in parameters) { + declareParameter(parameter.type, parameter.name) + } + val paramDeclarations = parameters.map { CgParameterDeclaration(it) } + CgAnonymousFunction(type, paramDeclarations, block(body)) + } + } + + override fun addAnnotation(classId: ClassId, argument: Any?, target: AnnotationTarget): CgAnnotation { + val annotation = CgSingleArgAnnotation(classId, argument.resolve(), target) + addAnnotation(annotation) + return annotation + } + + override fun addAnnotation( + classId: ClassId, + namedArguments: List, + target: AnnotationTarget, + ): CgAnnotation { + val annotation = CgMultipleArgsAnnotation(classId, namedArguments.toMutableList(), target) + + addAnnotation(annotation) + return annotation + } + + override fun addAnnotation( + classId: ClassId, + target: AnnotationTarget, + buildArguments: MutableList>.() -> Unit, + ): CgAnnotation { + val arguments = mutableListOf>() + .apply(buildArguments) + .map { (name, value) -> CgNamedAnnotationArgument(name, value) } + val annotation = CgMultipleArgsAnnotation(classId, arguments.toMutableList(), target) + + addAnnotation(annotation) + return annotation + } + + private fun addAnnotation(annotation: CgAnnotation) { + when (annotation.target) { + AnnotationTarget.Method -> collectedMethodAnnotations.add(annotation) + AnnotationTarget.Class -> currentTestClassContext.collectedTestClassAnnotations.add(annotation) + // TODO: possibly introduce some scope cache like in methods and constructions processing. + // It may allow to avoid return type in CgAnnotation method. + AnnotationTarget.Field -> { } + } + + importIfNeeded(annotation.classId) + } + + override fun returnStatement(expression: () -> CgExpression) { + currentBlock += CgReturnStatement(expression()) + } + + // Throw statement + + override fun throwStatement(exception: () -> CgExpression): CgThrowStatement = + CgThrowStatement(exception()).also { currentBlock += it } + + override fun emptyLine() { + currentBlock += CgEmptyLine + } + + /** + * Add an empty line if the current block has at least one statement + * and the current last statement is not an empty line. + */ + override fun emptyLineIfNeeded() { + val lastStatement = currentBlock.lastOrNull() ?: return + if (lastStatement is CgEmptyLine) return + emptyLine() + } + + override fun declareVariable(type: ClassId, name: String): CgVariable = + CgVariable(name, type).also { + rememberVariableForModel(it) + } + + override fun wrapTypeIfRequired(baseType: ClassId): ClassId = + when { + baseType.isAccessibleFrom(testClassPackageName) -> baseType + baseType.isAnonymous && baseType.supertypeOfAnonymousClass.isAccessibleFrom(testClassPackageName) -> + baseType.supertypeOfAnonymousClass + + else -> objectClassId + } + + // utils + + private fun classRefOrNull(type: ClassId, expr: CgExpression): ClassId? { + if (type == classClassId && expr is CgGetClass) return expr.classId + + if (type == classClassId && expr is CgExecutableCall && expr.executableId == forName) { + val name = (expr.arguments.getOrNull(0) as? CgLiteral)?.value as? String + + if (name != null) { + return BuiltinClassId.getBuiltinClassByNameOrNull(name) ?: ClassId(name) + } + } + + return null + } + + private fun guardEnumConstantAccess(access: CgEnumConstantAccess): ExpressionWithType { + val (enumAccessClass, constant) = access + + return if (enumAccessClass.isAccessibleFrom(testClassPackageName)) { + ExpressionWithType(enumAccessClass, access) + } else { + val enumClass: ClassId = + if (enumAccessClass.isEnum) enumAccessClass else enumAccessClass.outerClass!!.id + val enumClassVariable = newVar(classCgClassId) { + classClassId[forName](enumClass.name) + } + + ExpressionWithType(objectClassId, utilsClassId[getEnumConstantByName](enumClassVariable, constant)) + } + } + + private fun guardArrayAllocation(allocation: CgAllocateArray): ExpressionWithType { + return guardArrayCreation(allocation.type, allocation.size, allocation) + } + + private fun guardArrayInitializer(initializer: CgArrayInitializer): ExpressionWithType { + return guardArrayCreation(initializer.type, initializer.size, initializer) + } + + private fun guardArrayCreation(arrayType: ClassId, arraySize: Int, initialization: CgExpression): ExpressionWithType { + // TODO: check if this is the right way to check array type accessibility + return if (arrayType.isAccessibleFrom(testClassPackageName)) { + ExpressionWithType(arrayType, initialization) + } else { + ExpressionWithType( + objectArrayClassId, + utilsClassId[createArray](arrayType.elementClassId!!.name, arraySize) + ) + } + } + + private val ExecutableId.kotlinFunction: KFunction<*>? + get() { + return when (val executable = this.executable) { + is Method -> executable.kotlinFunction + is Constructor<*> -> executable.kotlinFunction + else -> error("Unknown Executable type: ${this::class}") + } + } + + private fun guardExecutableCall(baseType: ClassId, call: CgExecutableCall): ExpressionWithType { + // TODO: SAT-1210 support generics so that we wouldn't need to obtain kotlinFunction + // TODO: in order to check whether we are working with a TypeVariable or not +// val returnType = runCatching { call.executableId.kotlinFunction }.getOrNull()?.returnType?.javaType + + if (call.executableId == mockMethodId && call.arguments[0] is CgGetJavaClass) { + // call represents a call to mock() method + val wrappedType = wrapTypeIfRequired(baseType) + return ExpressionWithType(wrappedType, call) + } + return guardExpression(baseType, call) + } + + override fun guardExpression(baseType: ClassId, expression: CgExpression): ExpressionWithType { + val type: ClassId + val expr: CgExpression + + val typeAccessible = baseType.isAccessibleFrom(testClassPackageName) + + when { + expression.type isSubtypeOf baseType && typeAccessible -> { + type = baseType + expr = expression + } + expression.type isSubtypeOf baseType && !typeAccessible -> { + type = if (expression.type.isArray) objectArrayClassId else objectClassId + expr = expression + } + expression.type isNotSubtypeOf baseType && typeAccessible -> { + // consider util methods getField and getStaticField + // TODO should we consider another cases? + val isGetFieldUtilMethod = (expression is CgMethodCall && expression.executableId.isGetFieldUtilMethod) + val shouldCastBeSafety = expression == nullLiteral() || isGetFieldUtilMethod + + expr = typeCast(baseType, expression, shouldCastBeSafety) + type = expr.type + } + expression.type isNotSubtypeOf baseType && !typeAccessible -> { + type = if (expression.type.isArray) objectArrayClassId else objectClassId + expr = if (expression is CgMethodCall && isUtil(expression.executableId)) { + CgErrorWrapper("${expression.executableId.name} failed", expression) + } else { + expression + } + } + else -> error("Impossible case") + } + + return ExpressionWithType(type, expr) + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt new file mode 100644 index 0000000000..922c3f1a65 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgVariableConstructor.kt @@ -0,0 +1,590 @@ +package org.utbot.framework.codegen.tree + +import mu.KotlinLogging +import org.utbot.common.isStatic +import org.utbot.framework.codegen.domain.builtin.forName +import org.utbot.framework.codegen.domain.builtin.setArrayElement +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.* +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.tree.CgComponents.getCallableAccessManagerBy +import org.utbot.framework.codegen.tree.CgComponents.getMockFrameworkManagerBy +import org.utbot.framework.codegen.tree.CgComponents.getNameGeneratorBy +import org.utbot.framework.codegen.tree.CgComponents.getStatementConstructorBy +import org.utbot.framework.codegen.util.at +import org.utbot.framework.codegen.util.canBeSetFrom +import org.utbot.framework.codegen.util.canBeSetViaSetterFrom +import org.utbot.framework.codegen.util.fieldThatIsGotWith +import org.utbot.framework.codegen.util.fieldThatIsSetWith +import org.utbot.framework.codegen.util.inc +import org.utbot.framework.codegen.util.isAccessibleFrom +import org.utbot.framework.codegen.util.lessThan +import org.utbot.framework.codegen.util.nullLiteral +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.codegen.util.setter +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtClassRefModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtCustomModel +import org.utbot.framework.plugin.api.UtDirectGetFieldModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtStatementCallModel +import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.booleanWrapperClassId +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.classClassId +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.plugin.api.util.executable +import org.utbot.framework.plugin.api.util.jField +import org.utbot.framework.plugin.api.util.findFieldByIdOrNull +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.isEnum +import org.utbot.framework.plugin.api.util.isPrimitiveWrapperOrString +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.primitiveWrappers +import org.utbot.framework.plugin.api.util.primitives +import org.utbot.framework.plugin.api.util.replaceWithWrapperIfPrimitive +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.plugin.api.util.supertypeOfAnonymousClass +import org.utbot.framework.plugin.api.util.wrapperByPrimitive + +/** + * Constructs CgValue or CgVariable given a UtModel + */ +open class CgVariableConstructor(val context: CgContext) : + CgContextOwner by context, + CgCallableAccessManager by getCallableAccessManagerBy(context), + CgStatementConstructor by getStatementConstructorBy(context) { + + companion object { + private val logger = KotlinLogging.logger {} + } + + private val nameGenerator = getNameGeneratorBy(context) + private val mockFrameworkManager = getMockFrameworkManagerBy(context) + + /** + * Take already created CgValue or construct either a new [CgVariable] or new [CgLiteral] for the given model. + * + * Here we consider reference models and other models separately. + * + * It is important, because variables for reference models are constructed differently from the others. + * The difference is that the reference model variables are put into [valueByModel] cache + * not after the whole object is set up, but right after the variable has been initialized. + * For example, when we do `A a = new A()` we already have the variable, and it is stored in [valueByModel]. + * After that, we set all the necessary fields and call all the necessary methods. + * + * On the other hand, non-reference model variables are put into [valueByModel] after they have been fully set up. + * + * The point of this early caching of reference model variables is that classes may be recursive. + * For example, we may want to create a looped object: `a.value = a`. + * If we did not cache the variable `a` on its instantiation, then we would try to create `a` from its model + * from scratch. Since this object is recursively pointing to itself, this process would lead + * to a stack overflow. Specifically to avoid this, we cache reference model variables right after + * their instantiation. + * + * We use [valueByModelId] for [UtReferenceModel] by id to not create new variable in case state before + * was not transformed. + */ + open fun getOrCreateVariable(model: UtModel, name: String? = null): CgValue = + valueByUtModelWrapper.getOrPut(model.wrap()) { + constructValueByModel(model, name) + } + + private fun constructValueByModel(model: UtModel, name: String?): CgValue { + // name could be taken from existing names, or be specified manually, or be created from generator + val baseName = name ?: nameGenerator.nameFrom(model.classId) + + return when (model) { + is UtCompositeModel -> constructComposite(model, baseName) + is UtAssembleModel -> constructAssemble(model, baseName) + is UtArrayModel -> constructArray(model, baseName) + is UtEnumConstantModel -> constructEnumConstant(model, baseName) + is UtClassRefModel -> constructClassRef(model, baseName) + is UtLambdaModel -> constructLambda(model, baseName) + is UtNullModel -> nullLiteral() + is UtPrimitiveModel -> CgLiteral(model.classId, model.value) + is UtCustomModel -> { + logger.error { "Unexpected behaviour: value for UtCustomModel [$model] is constructed by base CgVariableConstructor" } + constructValueByModel( + model.origin ?: error("Can't construct value for UtCustomModel without origin [$model]"), name + ) + } + is UtReferenceModel -> error("Unexpected UtReferenceModel: ${model::class}") + is UtVoidModel -> error("Unexpected UtVoidModel: ${model::class}") + else -> error("Unexpected UtModel: ${model::class}") + } + } + + private fun constructLambda(model: UtLambdaModel, baseName: String): CgVariable { + val lambdaMethodId = model.lambdaMethodId + val capturedValues = model.capturedValues + return newVar(model.samType, baseName) { + if (lambdaMethodId.isStatic) { + constructStaticLambda(model, capturedValues) + } else { + constructLambda(model, capturedValues) + } + } + } + + private fun constructStaticLambda(model: UtLambdaModel, capturedValues: List): CgMethodCall { + val capturedArguments = capturedValues.map { + utilMethodProvider.capturedArgumentConstructorId(getClassOf(it.classId), getOrCreateVariable(it)) + } + return utilsClassId[buildStaticLambda]( + getClassOf(model.samType), + getClassOf(model.declaringClass), + model.lambdaName, + *capturedArguments.toTypedArray() + ) + } + + private fun constructLambda(model: UtLambdaModel, capturedValues: List): CgMethodCall { + require(capturedValues.isNotEmpty()) { + "Non-static lambda must capture `this` instance, so there must be at least one captured value" + } + val capturedThisInstance = getOrCreateVariable(capturedValues.first()) + val capturedArguments = capturedValues + .subList(1, capturedValues.size) + .map { utilMethodProvider.capturedArgumentConstructorId(getClassOf(it.classId), getOrCreateVariable(it)) } + return utilsClassId[buildLambda]( + getClassOf(model.samType), + getClassOf(model.declaringClass), + model.lambdaName, + capturedThisInstance, + *capturedArguments.toTypedArray() + ) + } + + private fun constructComposite(model: UtCompositeModel, baseName: String): CgVariable { + val obj = if (model.isMock) { + mockFrameworkManager.createMockFor(model, baseName) + } else { + val modelType = model.classId + val variableType = if (modelType.isAnonymous) modelType.supertypeOfAnonymousClass else modelType + newVar(variableType, baseName) { utilsClassId[createInstance](model.classId.name) } + } + + valueByUtModelWrapper[model.wrap()] = obj + + require(obj.type !is BuiltinClassId) { + "Unexpected BuiltinClassId ${obj.type} found while constructing from composite model" + } + + for ((fieldId, fieldModel) in model.fields) { + val variableForField = getOrCreateVariable(fieldModel, name = fieldId.name) + if (!variableForField.hasDefaultValue()) + setFieldValue(obj, fieldId, variableForField) + } + return obj + } + + fun setFieldValue(obj: CgValue, fieldId: FieldId, valueForField: CgValue) { + val field = fieldId.jField + val fieldFromVariableSpecifiedType = obj.type.findFieldByIdOrNull(fieldId) + + // we cannot set field directly if variable declared type does not have such field + // or we cannot directly create variable for field with the specified type (it is private, for example) + // Example: + // Object heapByteBuffer = createInstance("java.nio.HeapByteBuffer"); + // branchRegisterRequest.byteBuffer = heapByteBuffer; + // byteBuffer is field of type ByteBuffer and upper line is incorrect + val canFieldBeDirectlySetByVariableAndFieldTypeRestrictions = + fieldFromVariableSpecifiedType != null && fieldFromVariableSpecifiedType.type.id == valueForField.type + if (canFieldBeDirectlySetByVariableAndFieldTypeRestrictions && fieldId.canBeSetFrom(context, obj.type)) { + // TODO: check if it is correct to use declaringClass of a field here + val fieldAccess = if (field.isStatic) CgStaticFieldAccess(fieldId) else CgFieldAccess(obj, fieldId) + fieldAccess `=` valueForField + } else if (context.codegenLanguage == CodegenLanguage.JAVA && + !field.isStatic && fieldId.canBeSetViaSetterFrom(context) + ) { + +obj[fieldId.setter](valueForField) + } else { + // composite models must not have info about static fields, hence only non-static fields are set here + +utilsClassId[setField](obj, fieldId.declaringClass.name, fieldId.name, valueForField) + } + } + + private fun CgValue.hasDefaultValue(): Boolean { + if (this !is CgLiteral) { + return false; + } + + return when { + this.value == null -> true + (this.type == booleanClassId || this.type == booleanWrapperClassId) && this.value == false -> true + (this.type in primitives || this.type in primitiveWrappers) && this.value == 0 -> true + else -> false + } + } + + private fun constructAssemble(model: UtAssembleModel, baseName: String?): CgValue { + instantiateAssembleModel(model, baseName) + return constructAssembleForVariable(model) + } + + private fun instantiateAssembleModel(model: UtAssembleModel, baseName: String?) { + val statementCall = model.instantiationCall + val executable = statementCall.statement + val params = statementCall.params + val singleParamOrNull = params.singleOrNull() + + val isWrappingConstructor = + executable is ConstructorId && isPrimitiveWrapperOrString(model.classId) && singleParamOrNull != null && + replaceWithWrapperIfPrimitive(singleParamOrNull.classId) == model.classId + + // Don't use redundant constructors for primitives and String + val initExpr = when { + isWrappingConstructor -> cgLiteralForWrapper(params) + executable is ConstructorId && model.classId == stringClassId && singleParamOrNull is UtArrayModel + && singleParamOrNull.classId.elementClassId == charClassId + && List(singleParamOrNull.length) { i -> singleParamOrNull[i] }.all { it is UtPrimitiveModel } -> { + val stringValue = List(singleParamOrNull.length) { i -> (singleParamOrNull[i] as UtPrimitiveModel).value } + .joinToString(separator = "") + cgLiteralForWrapper(params = listOf(UtPrimitiveModel(stringValue))) + } + else -> createCgExecutableCallFromUtExecutableCall(statementCall) + } + + newVar(model.classId, model, baseName) { initExpr } + .also { valueByUtModelWrapper[model.wrap()] = it } + } + + fun constructAssembleForVariable(model: UtAssembleModel): CgValue { + for (statementModel in model.modificationsChain) { + when (statementModel) { + is UtDirectSetFieldModel -> { + val instance = getOrCreateVariable(statementModel.instance) + // fields here are supposed to be accessible, so we assign them directly without any checks + instance[statementModel.fieldId] `=` getOrCreateVariable( + model = statementModel.fieldModel, + name = statementModel.fieldId.name, + ) + } + is UtStatementCallModel -> { + val call = createCgExecutableCallFromUtExecutableCall(statementModel) + val equivalentFieldAccess = replaceCgExecutableCallWithFieldAccessIfNeeded(call) + val thrownException = statementModel.thrownConcreteException + + if (equivalentFieldAccess != null) +equivalentFieldAccess + else if (thrownException != null) { + +tryBlock { +call } + .catch(thrownException) { /* do nothing */ } + } else +call + } + } + } + + return valueByUtModelWrapper.getValue(model.wrap()) + } + + private fun createCgExecutableCallFromUtExecutableCall(statementModel: UtStatementCallModel): CgExecutableCall = + when (statementModel) { + is UtExecutableCallModel -> { + val executable = statementModel.executable + val paramNames = runCatching { + executable.executable.parameters.map { if (it.isNamePresent) it.name else null } + }.getOrNull() + val params = statementModel.params + val caller = statementModel.instance?.let { getOrCreateVariable(it) } + val args = params.mapIndexed { i, param -> + getOrCreateVariable(param, name = paramNames?.getOrNull(i)) + } + + when (executable) { + is MethodId -> caller[executable](*args.toTypedArray()) + is ConstructorId -> executable(*args.toTypedArray()) + } + } + is UtDirectGetFieldModel -> { + val instance = getOrCreateVariable(statementModel.instance) + val fieldAccess = statementModel.fieldAccess + utilsClassId[getFieldValue](instance, fieldAccess.fieldId.declaringClass.canonicalName, fieldAccess.fieldId.name) + } + } + + /** + * If executable is getter/setter that should be syntactically replaced with field access + * (e.g., getter/setter generated by Kotlin in Kotlin code), this method returns [CgStatement] + * with which [call] should be replaced. + * + * Otherwise, returns null. + */ + private fun replaceCgExecutableCallWithFieldAccessIfNeeded(call: CgExecutableCall): CgStatement? { + when (context.codegenLanguage) { + CodegenLanguage.JAVA -> return null + CodegenLanguage.KOTLIN -> { + if (call !is CgMethodCall) + return null + + val caller = call.caller ?: return null + + caller.type.fieldThatIsSetWith(call.executableId)?.let { + return CgAssignment(caller[it], call.arguments.single()) + } + caller.type.fieldThatIsGotWith(call.executableId)?.let { + require(call.arguments.isEmpty()) { + "Method $call was detected as getter for $it, but its arguments list isn't empty" + } + return caller[it] + } + + return null + } + } + } + + /** + * Makes a replacement of constructor call to instantiate a primitive wrapper + * with direct setting of the value. The reason is that in Kotlin constructors + * of primitive wrappers are private. + */ + private fun cgLiteralForWrapper(params: List): CgLiteral { + val paramModel = params.singleOrNull() + require(paramModel is UtPrimitiveModel) { "Incorrect param models for primitive wrapper" } + + val classId = wrapperByPrimitive[paramModel.classId] + ?: if (paramModel.classId == stringClassId) { + stringClassId + } else { + error("${paramModel.classId} is not a primitive wrapper or a string") + } + + return CgLiteral(classId, paramModel.value) + } + + private fun constructArray(arrayModel: UtArrayModel, baseName: String?): CgVariable { + val elementType = arrayModel.classId.elementClassId!! + val elementModels = (0 until arrayModel.length).map { + arrayModel.stores.getOrDefault(it, arrayModel.constModel) + } + + val allPrimitives = elementModels.all { it is UtPrimitiveModel } + val allNulls = elementModels.all { it is UtNullModel } + // we can use array initializer if all elements are primitives or all of them are null, + // and the size of an array is not greater than the fixed maximum size + val canInitWithValues = (allPrimitives || allNulls) && elementModels.size <= MAX_ARRAY_INITIALIZER_SIZE + + val initializer = if (canInitWithValues) { + val elements = elementModels.map { model -> + when (model) { + is UtPrimitiveModel -> model.value.resolve() + is UtNullModel -> null.resolve() + else -> error("Non primitive or null model $model is unexpected in array initializer") + } + } + arrayInitializer(arrayModel.classId, elementType, elements) + } else { + CgAllocateArray(arrayModel.classId, elementType, arrayModel.length) + } + + val array = newVar(arrayModel.classId, baseName) { initializer } + + valueByUtModelWrapper[arrayModel.wrap()] = array + + if (canInitWithValues) { + return array + } + + if (arrayModel.length <= 0) return array + if (arrayModel.length == 1) { + // take first element value if it is present, otherwise use default value from model + val elementModel = arrayModel[0] + if (elementModel isNotDefaultValueOf elementType) { + array.setArrayElement(0, getOrCreateVariable(elementModel)) + } + } else { + val indexedValuesFromStores = + if (arrayModel.stores.size == arrayModel.length) { + // do not use constModel because stores fully cover array + arrayModel.stores.entries.filter { (_, element) -> element isNotDefaultValueOf elementType } + } else { + // fill array if constModel is not default type value + if (arrayModel.constModel isNotDefaultValueOf elementType) { + val defaultVariable = getOrCreateVariable(arrayModel.constModel, "defaultValue") + basicForLoop(arrayModel.length) { i -> + array.setArrayElement(i, defaultVariable) + } + } + + // choose all not default values + val defaultValue = if (arrayModel.constModel isDefaultValueOf elementType) { + arrayModel.constModel + } else { + elementType.defaultValueModel() + } + arrayModel.stores.entries.filter { (_, element) -> element != defaultValue } + } + + // set all values from stores manually + indexedValuesFromStores + .sortedBy { it.key } + .forEach { (index, element) -> array.setArrayElement(index, getOrCreateVariable(element)) } + } + + return array + } + + /** + * Splits sorted by indices pairs of index and value from stores to continuous by index chunks + * [indexedValuesFromStores] have to be sorted by key + * + * Сan not be used now but will be useful in case of storing stores in generated code + */ + @Suppress("unused") + private fun splitSettingFromStoresToForLoops( + array: CgVariable, + indexedValuesFromStores: List> + ) { + val ranges = mutableListOf() + + var start = 0 + for (i in 0 until indexedValuesFromStores.lastIndex) { + if (indexedValuesFromStores[i + 1].key - indexedValuesFromStores[i].key > 1) { + ranges += start..i + start = i + 1 + } + if (i == indexedValuesFromStores.lastIndex - 1) { + ranges += start..indexedValuesFromStores.lastIndex + } + } + + for (range in ranges) { + // IntRange stores end inclusively but sublist takes it exclusively + setStoresRange(array, indexedValuesFromStores.subList(range.first, range.last + 1)) + } + } + + /** + * [indexedValuesFromStores] have to be continuous sorted range + */ + private fun setStoresRange( + array: CgVariable, + indexedValuesFromStores: List> + ) { + if (indexedValuesFromStores.size < 3) { + // range is too small, better set manually + indexedValuesFromStores.forEach { (index, element) -> + array.setArrayElement(index, getOrCreateVariable(element)) + } + } else { + val minIndex = indexedValuesFromStores.first().key + val maxIndex = indexedValuesFromStores.last().key + + var indicesIndex = 0 + // we use until form of for loop so need to shift upper border + basicForLoop(start = minIndex, until = maxIndex + 1) { i -> + // use already sorted indices + val (_, value) = indexedValuesFromStores[indicesIndex++] + array.setArrayElement(i, getOrCreateVariable(value)) + } + } + } + + private fun constructEnumConstant(model: UtEnumConstantModel, baseName: String?): CgVariable { + val classId = model.classId + + require(classId.isEnum || + classId.isAnonymous && classId.outerClass?.isEnum == true) { + "Enum constant model $model should be a enum or an anonymous class that overrides enum methods" + } + + return newVar(classId, baseName) { + CgEnumConstantAccess(classId, model.value.name) + } + } + + private fun constructClassRef(model: UtClassRefModel, baseName: String?): CgVariable { + val classId = model.value + val init = if (classId.isAccessibleFrom(testClassPackageName)) { + CgGetJavaClass(classId) + } else { + classClassId[forName](classId.name) + } + + return newVar(Class::class.id, baseName) { init } + } + + private fun basicForLoop(start: Any, until: Any, body: (i: CgExpression) -> Unit) { + forLoop { + val (i, init) = loopInitialization(intClassId, "i", start.resolve()) + initialization = init + condition = i lessThan until.resolve() + update = i.inc() + statements = block { body(i) } + } + } + + /** + * A for-loop performing 'n' iterations starting with 0 + * + * for (int i = 0; i < n; i++) { + * ... + * } + */ + private fun basicForLoop(until: Any, body: (i: CgExpression) -> Unit) { + basicForLoop(start = 0, until, body) + } + + /** + * Create loop initializer expression + */ + internal fun loopInitialization( + variableType: ClassId, + baseVariableName: String, + initializer: Any? + ): Pair { + val declaration = CgDeclaration(variableType, baseVariableName.toVarName(), initializer.resolve()) + val variable = declaration.variable + rememberVariableForModel(variable) + return variable to declaration + } + + /** + * @receiver must represent a variable containing an array value. + * If an array was created with reflection, then the variable is of [Object] type. + * Otherwise, the variable is of the actual array type. + * + * Both cases are considered here. + * If the variable is [Object], we use reflection method to set an element. + * Otherwise, we set an element directly. + * + */ + private fun CgVariable.setArrayElement(index: Any, value: CgValue) { + val i = index.resolve() + // we have to use reflection if we cannot easily cast array element to array type + // (in case array does not have array type (maybe just object) or element is private class) + if (!type.isArray || (type != value.type && !value.type.isAccessibleFrom(testClassPackageName))) { + +java.lang.reflect.Array::class.id[setArrayElement](this, i, value) + } else { + val arrayElement = if (type == value.type) { + value + } else { + typeCast(type.elementClassId!!, value, isSafetyCast = true) + } + + this.at(i) `=` arrayElement + } + } + + private fun String.toVarName(): String = nameGenerator.variableName(this) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/ConstructorUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/ConstructorUtils.kt new file mode 100644 index 0000000000..bb7bd6c343 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/ConstructorUtils.kt @@ -0,0 +1,468 @@ +package org.utbot.framework.codegen.tree + +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.PersistentSet +import org.utbot.framework.codegen.domain.RegularImport +import org.utbot.framework.codegen.domain.StaticImport +import org.utbot.framework.codegen.domain.builtin.setArrayElement +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgAllocateInitializedArray +import org.utbot.framework.codegen.domain.models.CgArrayInitializer +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgTypeCast +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.util.at +import org.utbot.framework.codegen.util.isAccessibleFrom +import org.utbot.framework.fields.ArrayElementAccess +import org.utbot.framework.fields.FieldAccess +import org.utbot.framework.fields.FieldPath +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.BuiltinMethodId +import org.utbot.framework.plugin.api.CgClassId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.WildcardTypeParameter +import org.utbot.framework.plugin.api.util.arrayTypeOf +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.builtinStaticMethodId +import org.utbot.framework.plugin.api.util.byteClassId +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.denotableType +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.enclosingClass +import org.utbot.framework.plugin.api.util.executable +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.isRefType +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.isSubtypeOf +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.methodId +import org.utbot.framework.plugin.api.util.objectArrayClassId +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.signature +import org.utbot.framework.plugin.api.util.underlyingType +import soot.Scene +import java.lang.reflect.Method +import java.lang.reflect.Modifier + +data class EnvironmentFieldStateCache( + val thisInstance: FieldStateCache, + val arguments: Array, + val classesWithStaticFields: MutableMap +) { + companion object { + fun emptyCacheFor(execution: UtExecution): EnvironmentFieldStateCache { + val argumentsCache = Array(execution.stateBefore.parameters.size) { FieldStateCache() } + + val staticFields = execution.stateBefore.statics.keys + val classesWithStaticFields = staticFields.groupBy { it.declaringClass }.keys + val staticFieldsCache = mutableMapOf().apply { + for (classId in classesWithStaticFields) { + put(classId, FieldStateCache()) + } + } + + return EnvironmentFieldStateCache( + thisInstance = FieldStateCache(), + arguments = argumentsCache, + classesWithStaticFields = staticFieldsCache + ) + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as EnvironmentFieldStateCache + + if (thisInstance != other.thisInstance) return false + if (!arguments.contentEquals(other.arguments)) return false + if (classesWithStaticFields != other.classesWithStaticFields) return false + + return true + } + + override fun hashCode(): Int { + var result = thisInstance.hashCode() + result = 31 * result + arguments.contentHashCode() + result = 31 * result + classesWithStaticFields.hashCode() + return result + } +} + +class FieldStateCache { + val before: MutableMap = mutableMapOf() + val after: MutableMap = mutableMapOf() + + val paths: List + get() = (before.keys union after.keys).toList() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as FieldStateCache + + if (before != other.before) return false + if (after != other.after) return false + + return true + } + + override fun hashCode(): Int { + var result = before.hashCode() + result = 31 * result + after.hashCode() + return result + } +} + +data class CgFieldState(val variable: CgVariable, val model: UtModel) + +data class ExpressionWithType(val type: ClassId, val expression: CgExpression) + +/** + * Check if a method is an util method of the current class + */ +internal fun CgContextOwner.isUtil(method: MethodId): Boolean { + return method in utilMethodProvider.utilMethodIds +} + +val classCgClassId = CgClassId(Class::class.id, typeParameters = WildcardTypeParameter, isNullable = false) + +/** + * A [MethodId] to add an item into [ArrayList]. + */ +internal val addToListMethodId: MethodId + get() = methodId( + classId = ArrayList::class.id, + name = "add", + returnType = booleanClassId, + arguments = arrayOf(Object::class.id), + ) + +/** + * A [ClassId] of class `org.junit.jupiter.params.provider.Arguments` + */ +internal val argumentsClassId: BuiltinClassId + get() = BuiltinClassId( + simpleName = "Arguments", + canonicalName = "org.junit.jupiter.params.provider.Arguments", + packageName = "org.junit.jupiter.params.provider" + ) + +/** + * A [MethodId] to call JUnit Arguments method. + */ +internal val argumentsMethodId: BuiltinMethodId + get() = builtinStaticMethodId( + classId = argumentsClassId, + name = "arguments", + returnType = argumentsClassId, + // vararg of Objects + arguments = arrayOf(objectArrayClassId) + ) + +internal fun getStaticFieldVariableName(owner: ClassId, path: FieldPath): String { + val elements = mutableListOf() + elements += owner.simpleName.decapitalize() + // TODO: check how capitalize() works with numeric strings e.g. "0" + elements += path.toStringList().map { it.capitalize() } + return elements.joinToString("") +} + +internal fun getFieldVariableName(owner: CgValue, path: FieldPath): String { + val elements = mutableListOf() + if (owner is CgVariable) { + elements += owner.name + } + // TODO: check how capitalize() works with numeric strings e.g. "0" + elements += path.toStringList().map { it.capitalize() } + if (elements.size > 0) { + elements[0] = elements[0].decapitalize() + } + return elements.joinToString("") +} + +private fun FieldPath.toStringList(): List = + elements.map { + when (it) { + is FieldAccess -> it.field.name + is ArrayElementAccess -> it.index.toString() + } + } + +internal fun infiniteInts(): Sequence = + generateSequence(1) { it + 1 } + +internal const val MAX_ARRAY_INITIALIZER_SIZE = 10 + +private val simpleNamesNotRequiringImports by lazy { findSimpleNamesNotRequiringImports() } + +/** + * Some class names do not require imports, but may lead to simple names clash. + * For example, custom class `Compiler` may clash with [java.lang.Compiler]. + */ +private fun findSimpleNamesNotRequiringImports(): Set = + Scene.v().classes + .filter { it.packageName == "java.lang" } + .mapNotNullTo(mutableSetOf()) { it.shortName } + +/** + * Checks if we have already imported a class with such simple name. + * If so, we cannot import [type] (because it will be used with simple name and will be clashed with already imported) + * and should use its fully qualified name instead. + */ +private fun CgContextOwner.doesNotHaveSimpleNameClash(type: ClassId): Boolean = + importedClasses.none { it.simpleName == type.simpleName } && type.simpleName !in simpleNamesNotRequiringImports + +fun CgContextOwner.importIfNeeded(type: ClassId) { + // TODO: for now we consider that tests are generated in the same package as CUT, but this may change + val underlyingType = type.underlyingType + + underlyingType + .takeIf { (it.isRefType && it.packageName != testClassPackageName && it.packageName != "java.lang") || it.isNested } + // we cannot import inaccessible classes (builtin classes like JUnit are accessible here because they are public) + ?.takeIf { it.isAccessibleFrom(testClassPackageName) } + // don't import classes from default package + ?.takeIf { !it.isInDefaultPackage } + // cannot import anonymous classes + ?.takeIf { !it.isAnonymous } + // do not import if there is a simple name clash + ?.takeIf { doesNotHaveSimpleNameClash(it) } + ?.let { + importedClasses += it + collectedImports += it.toImport() + } + + // for nested classes we need to import enclosing class + if (underlyingType.isNested) { + importIfNeeded(underlyingType.enclosingClass!!) + } +} + +fun CgContextOwner.importIfNeeded(method: MethodId) { + val name = method.name + val packageName = method.classId.packageName + method.takeIf { it.isStatic && packageName != testClassPackageName && packageName != "java.lang" } + .takeIf { importedStaticMethods.none { it.name == name } } + // do not import method under test in order to specify the declaring class directly for its calls + .takeIf { currentExecutableUnderTest != method } + ?.let { + importedStaticMethods += method + collectedImports += StaticImport(method.classId.canonicalName, method.name) + } +} + +/** + * Casts [expression] to [targetType]. + * + * This method uses [denotableType] of the given [targetType] to ensure + * that we are using a denotable type in the type cast. + * + * Specifically, if we attempt to do a type cast to an anonymous class, + * then we will actually perform a type cast to its supertype. + * + * @see denotableType + * + * @param isSafetyCast shows if we should render "as?" instead of "as" in Kotlin + */ +internal fun CgContextOwner.typeCast( + targetType: ClassId, + expression: CgExpression, + isSafetyCast: Boolean = false +): CgExpression { + val denotableTargetType = targetType.denotableType + importIfNeeded(denotableTargetType) + return CgTypeCast(denotableTargetType, expression, isSafetyCast) +} + +/** + * Casts [expression] to [targetType] only if downcast is needed, + * e.g. this method will cast `Collection` to `Set`, but not vice-versa. + * + * @see typeCast + */ +internal fun CgContextOwner.downcastIfNeeded( + targetType: ClassId, + expression: CgExpression, + isSafetyCast: Boolean = false +): CgExpression = + if (expression.type isSubtypeOf targetType) expression + else typeCast(targetType, expression, isSafetyCast) + +/** + * Sets an element of arguments array in parameterized test, + * if test framework represents arguments as array. + */ +internal fun T.setArgumentsArrayElement( + array: CgVariable, + index: Int, + value: CgExpression, + constructor: CgStatementConstructor +) where T : CgContextOwner, T: CgCallableAccessManager { + when (array.type) { + objectClassId -> { + +java.lang.reflect.Array::class.id[setArrayElement](array, index, value) + } + else -> with(constructor) { array.at(index) `=` value } + } +} + +@Suppress("unused") +internal fun newArrayOf(elementType: ClassId, values: List): CgAllocateInitializedArray { + val arrayType = arrayTypeOf(elementType) + return CgAllocateInitializedArray(arrayInitializer(arrayType, elementType, values)) +} + +internal fun arrayInitializer(arrayType: ClassId, elementType: ClassId, values: List): CgArrayInitializer = + CgArrayInitializer(arrayType, elementType, values) + +internal fun Class<*>.overridesEquals(): Boolean = + when { + // Object does not override equals + this == Any::class.java -> false + id isSubtypeOf Map::class.id -> true + id isSubtypeOf Collection::class.id -> true + else -> declaredMethods.any { it.name == "equals" && it.parameterTypes.contentEquals(arrayOf(Any::class.java)) } + } + +/** + * Returns all methods of [this] class (including inherited), except base methods (i.e., if any method is overridden, + * only the latest overriding will be included). + * NOTE: for the reference [see also](https://stackoverflow.com/a/28408148) + */ +private fun Class<*>.allMethodsWithoutBaseMethods(): Set { + val collectedMethods = mutableSetOf(*methods) + val types = collectedMethods.map { it.signature }.associateWithTo(mutableMapOf()) { mutableSetOf() } + val access = Modifier.PUBLIC or Modifier.PROTECTED or Modifier.PRIVATE + + var currentClass: Class<*>? = this + while (currentClass != null) { + for (method in currentClass.declaredMethods) { + val modifiers = method.modifiers + + if (!Modifier.isStatic(modifiers)) { + when (modifiers and access) { + Modifier.PUBLIC -> continue + Modifier.PROTECTED -> { + if (types.putIfAbsent(method.signature, mutableSetOf()) != null) { + continue + } + } + Modifier.PRIVATE -> {} + else -> { // package-private + val pkg = types.computeIfAbsent(method.signature) { mutableSetOf() } + + if (pkg.isNotEmpty() && pkg.add(currentClass.getPackage())) { + break + } else { + continue + } + } + } + } + + collectedMethods += method + } + + currentClass = currentClass.superclass + } + + return collectedMethods +} + +// NOTE: this function does not consider executable return type because it is not important in our case +internal fun ClassId.getAmbiguousOverloadsOf(executableId: ExecutableId): Sequence { + val allExecutables = when (executableId) { + is MethodId -> { + // For method we should check all overloadings and inherited methods, but do not consider base methods + // in case of overriding (for example, for ArrayList#add we should not take List#add and AbstractCollection#add) + executableId.classId.jClass.allMethodsWithoutBaseMethods().map { it.executableId }.asSequence() + } + is ConstructorId -> allConstructors + } + + return allExecutables.filter { + it.name == executableId.name && it.parameters.size == executableId.executable.parameters.size + } +} + + +internal infix fun ClassId.hasAmbiguousOverloadsOf(executableId: ExecutableId): Boolean { + // TODO: for now we assume that builtin classes do not have ambiguous overloads + if (this is BuiltinClassId) return false + + return getAmbiguousOverloadsOf(executableId).toList().size > 1 +} + +private val defaultByPrimitiveType: Map = mapOf( + booleanClassId to false, + byteClassId to 0.toByte(), + charClassId to '\u0000', + shortClassId to 0.toShort(), + intClassId to 0, + longClassId to 0L, + floatClassId to 0.0f, + doubleClassId to 0.0 +) + +/** + * By 'default' here we mean a value that is used for a type in one of the two cases: + * - When we allocate an array of some type in the following manner: `new int[10]`, + * the array is filled with some value. In case of `int` this value is `0`, for `boolean` + * it is `false`, etc. + * - When we allocate an instance of some reference type e.g. `new A()` and the class `A` has field `int a`. + * If the constructor we use does not initialize `a` directly and `a` is not assigned to some value + * at the declaration site, then `a` will be assigned to some `default` value, e.g. `0` for `int`. + * + * Here we do not consider default values of nested arrays of multidimensional arrays, + * because they depend on the way the outer array is allocated: + * - An array allocated using `new int[10][]` will contain 10 `null` elements. + * - An array allocated using `new int[10][5]` will contain 10 arrays of 5 elements, + * where each element is `0`. + */ +internal infix fun UtModel.isDefaultValueOf(type: ClassId): Boolean = + when (this) { + is UtNullModel -> type.isRefType // null is default for ref types + is UtPrimitiveModel -> value == defaultByPrimitiveType[type] + else -> false + } + +internal infix fun UtModel.isNotDefaultValueOf(type: ClassId): Boolean = !this.isDefaultValueOf(type) + +/** + * If the model contains a store for the given [index], return the model of this store. + * Otherwise, return a [UtArrayModel.constModel] of this array model. + */ +internal operator fun UtArrayModel.get(index: Int): UtModel = stores[index] ?: constModel + +fun ClassId.toImport(): RegularImport = RegularImport(packageName, simpleNameWithEnclosingClasses) + +// Immutable collections utils + +internal operator fun PersistentList.plus(element: T): PersistentList = + this.add(element) + +internal operator fun PersistentList.plus(other: PersistentList): PersistentList = + this.addAll(other) + +internal operator fun PersistentSet.plus(element: T): PersistentSet = + this.add(element) + +internal operator fun PersistentSet.plus(other: PersistentSet): PersistentSet = + this.addAll(other) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/DeclarationUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/DeclarationUtils.kt new file mode 100644 index 0000000000..abf2e4d4b5 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/DeclarationUtils.kt @@ -0,0 +1,26 @@ +package org.utbot.framework.codegen.tree + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel + +/** + * Checks if an expected variable is needed, + * or it will be further asserted with assertNull or assertTrue/False. + */ +fun needExpectedDeclaration(model: UtModel): Boolean { + val representsNull = model is UtNullModel + val representsBoolean = model is UtPrimitiveModel && model.value is Boolean + return !(representsNull || representsBoolean) +} + +/** + * Contains all possible visibility modifiers that may be used in code generation. + */ +enum class VisibilityModifier { + PUBLIC, + PRIVATE, + PROTECTED, + INTERNAL, + PACKAGE_PRIVATE, +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/ututils/CgUtilClassConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/ututils/CgUtilClassConstructor.kt new file mode 100644 index 0000000000..1f2fb46003 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/ututils/CgUtilClassConstructor.kt @@ -0,0 +1,46 @@ +package org.utbot.framework.codegen.tree.ututils + +import org.utbot.framework.codegen.domain.builtin.selectUtilClassId +import org.utbot.framework.codegen.domain.models.CgAuxiliaryClass +import org.utbot.framework.codegen.domain.models.CgAuxiliaryNestedClassesRegion +import org.utbot.framework.codegen.domain.models.CgClassFile +import org.utbot.framework.codegen.domain.models.CgStaticsRegion +import org.utbot.framework.codegen.domain.models.CgUtilMethod +import org.utbot.framework.codegen.tree.buildClass +import org.utbot.framework.codegen.tree.buildClassBody +import org.utbot.framework.codegen.tree.buildClassFile +import org.utbot.framework.plugin.api.CodegenLanguage + +/** + * This class is used to construct a file containing an util class UtUtils. + * The util class is constructed when the argument `generateUtilClassFile` in the [CodeGenerator] is true. + */ +internal object CgUtilClassConstructor { + fun constructUtilsClassFile( + utilClassKind: UtilClassKind, + codegenLanguage: CodegenLanguage, + ): CgClassFile { + val utilMethodProvider = utilClassKind.utilMethodProvider + val utilsClassId = selectUtilClassId(codegenLanguage) + return buildClassFile { + // imports are empty, because we use fully qualified classes and static methods, + // so they will be imported once IDEA reformatting action has worked + declaredClass = buildClass { + id = utilsClassId + documentation = utilClassKind.utilClassDocumentation(codegenLanguage) + body = buildClassBody(utilsClassId) { + staticDeclarationRegions += CgStaticsRegion( + header = "Util methods", + content = utilMethodProvider.utilMethodIds.map { CgUtilMethod(it) }) + + nestedClassRegions += CgAuxiliaryNestedClassesRegion( + header = "Util classes", + nestedClasses = listOf( + CgAuxiliaryClass(utilMethodProvider.capturedArgumentClassId) + ) + ) + } + } + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/ututils/UtilClassKind.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/ututils/UtilClassKind.kt new file mode 100644 index 0000000000..7b318f9678 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/ututils/UtilClassKind.kt @@ -0,0 +1,149 @@ +package org.utbot.framework.codegen.tree.ututils + +import org.utbot.framework.codegen.domain.builtin.UtilClassFileMethodProvider +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgDocRegularLineStmt +import org.utbot.framework.codegen.domain.models.CgDocumentationComment +import org.utbot.framework.codegen.renderer.CgAbstractRenderer +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.MockFramework +import java.util.* + +/** + * A kind of util class. See the description of each kind at their respective classes. + * @property utilMethodProvider a [UtilClassFileMethodProvider] containing information about + * utilities that come from a separately generated UtUtils class + * (as opposed to utils that are declared directly in the test class, for example). + * @property mockFrameworkUsed a flag indicating if a mock framework was used. + * For detailed description see [CgContextOwner.mockFrameworkUsed]. + * @property mockFramework a framework used to create mocks + * @property priority when we generate multiple test classes, they can require different [UtilClassKind]. + * We will generate an util class corresponding to the kind with the greatest priority. + * For example, one test class may not use mocks, but the other one does. + * Then we will generate an util class with mocks, because it has a greater priority (see [UtUtilsWithMockito]). + */ +sealed class UtilClassKind( + internal val utilMethodProvider: UtilClassFileMethodProvider, + internal val mockFrameworkUsed: Boolean, + internal val mockFramework: MockFramework = MockFramework.MOCKITO, + private val priority: Int +) : Comparable { + + /** + * Contains comments specifying the version and the kind of util class being generated and + */ + fun utilClassDocumentation(codegenLanguage: CodegenLanguage): CgDocumentationComment + = CgDocumentationComment( + listOf( + CgDocRegularLineStmt(utilClassKindCommentText), + CgDocRegularLineStmt("$UTIL_CLASS_VERSION_COMMENT_PREFIX${utilClassVersion(codegenLanguage)}"), + ) + ) + + /** + * The version of util class being generated. + * For more details see [UtilClassFileMethodProvider.UTIL_CLASS_VERSION]. + */ + fun utilClassVersion(codegenLanguage: CodegenLanguage): String + = UtilClassFileMethodProvider(codegenLanguage).UTIL_CLASS_VERSION + + /** + * The text of comment specifying the kind of util class. + * At the moment, there are two kinds: [RegularUtUtils] (without Mockito) and [UtUtilsWithMockito]. + * + * This comment is needed when the plugin decides whether to overwrite an existing util class or not. + * When making that decision, it is important to determine if the existing class uses mocks or not, + * and this comment will help do that. + */ + abstract val utilClassKindCommentText: String + + /** + * A kind of regular UtUtils class. "Regular" here means that this class does not use a mock framework. + */ + class RegularUtUtils(val codegenLanguage: CodegenLanguage) : + UtilClassKind( + UtilClassFileMethodProvider(codegenLanguage), + mockFrameworkUsed = false, + priority = 0, + ) { + override val utilClassKindCommentText: String + get() = "This is a regular UtUtils class (without mock framework usage)" + } + + /** + * A kind of UtUtils class that uses a mock framework. At the moment the framework is Mockito. + */ + class UtUtilsWithMockito(val codegenLanguage: CodegenLanguage) : + UtilClassKind(UtilClassFileMethodProvider(codegenLanguage), mockFrameworkUsed = true, priority = 1) { + override val utilClassKindCommentText: String + get() = "This is UtUtils class with Mockito support" + } + + override fun compareTo(other: UtilClassKind): Int { + return priority.compareTo(other.priority) + } + + /** + * Construct an util class file as a [CgClassFile] and render it. + * @return the text of the generated util class file. + */ + fun getUtilClassText(codegenLanguage: CodegenLanguage): String { + val utilClassFile = CgUtilClassConstructor.constructUtilsClassFile(this, codegenLanguage) + val renderer = CgAbstractRenderer.makeRenderer(this, codegenLanguage) + utilClassFile.accept(renderer) + return renderer.toString() + } + + companion object { + + /** + * Class UtUtils will contain a comment specifying the version of this util class + * (if we ever change util methods, then util class will be different, hence the update of its version). + * This is a prefix that will go before the version in the comment. + */ + const val UTIL_CLASS_VERSION_COMMENT_PREFIX = "UtUtils class version: " + + fun utilClassKindByCommentOrNull( + comment: String, + codegenLanguage: CodegenLanguage + ) + : UtilClassKind? { + return when { + comment.contains(RegularUtUtils(codegenLanguage).utilClassKindCommentText) -> RegularUtUtils(codegenLanguage) + comment.contains(UtUtilsWithMockito(codegenLanguage).utilClassKindCommentText) -> UtUtilsWithMockito(codegenLanguage) + else -> null + } + } + + /** + * Check if an util class is required, and if so, what kind. + * @return `null` if [CgContext.utilMethodProvider] is not [UtilClassFileMethodProvider], + * because it means that util methods will be taken from some other provider (e.g. [TestClassUtilMethodProvider]). + */ + fun fromCgContextOrNull(context: CgContext): UtilClassKind? { + if (context.requiredUtilMethods.isEmpty()) return null + if (!context.mockFrameworkUsed) { + return RegularUtUtils(context.codegenLanguage) + } + return when (context.mockFramework) { + MockFramework.MOCKITO -> UtUtilsWithMockito(context.codegenLanguage) + // in case we will add any other mock frameworks, newer Kotlin compiler versions + // will report a non-exhaustive 'when', so we will not forget to support them here as well + } + } + + const val UT_UTILS_BASE_PACKAGE_NAME = "org.utbot.runtime.utils" + const val UT_UTILS_INSTANCE_NAME = "UtUtils" + const val PACKAGE_DELIMITER = "." + + /** + * List of package name components of UtUtils class. + * See whole package name at [UT_UTILS_BASE_PACKAGE_NAME]. + */ + fun utilsPackageNames(codegenLanguage: CodegenLanguage): List + = UT_UTILS_BASE_PACKAGE_NAME.split(PACKAGE_DELIMITER) + codegenLanguage.name.lowercase(Locale.getDefault()) + + fun utilsPackageFullName(codegenLanguage: CodegenLanguage): String + = utilsPackageNames(codegenLanguage).joinToString { PACKAGE_DELIMITER } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/ClassIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/ClassIdUtil.kt new file mode 100644 index 0000000000..1f54455325 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/ClassIdUtil.kt @@ -0,0 +1,50 @@ +package org.utbot.framework.codegen.util + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.util.isPackagePrivate +import org.utbot.framework.plugin.api.util.isProtected +import org.utbot.framework.plugin.api.util.isPublic +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.allDeclaredFieldIds +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isArray + +/** + * For now we will count class accessible if it is: + * - Public or package-private within package [packageName]. + * - It's outer class (if exists) is accessible too. + * NOTE: local and synthetic classes are considered as inaccessible. + * NOTE: protected classes cannot be accessed because test class does not extend any classes. + * + * @param packageName name of the package we check accessibility from + */ +infix fun ClassId.isAccessibleFrom(packageName: String): Boolean { + if (this.isLocal || this.isAnonymous || this.isSynthetic) { + return false + } + + val outerClassId = outerClass?.id + if (outerClassId != null && !outerClassId.isAccessibleFrom(packageName)) { + return false + } + + return if (this.isArray) { + elementClassId!!.isAccessibleFrom(packageName) + } else { + isPublic || (this.packageName == packageName && (isPackagePrivate || isProtected)) + } +} + +/** + * Returns field of [this], such that [methodId] is a getter for it (or null if methodId doesn't represent a getter) + */ +internal fun ClassId.fieldThatIsGotWith(methodId: ExecutableId): FieldId? = + allDeclaredFieldIds.singleOrNull { !it.isStatic && it.getter.describesSameMethodAs(methodId) } + +/** + * Returns field of [this], such that [methodId] is a setter for it (or null if methodId doesn't represent a setter) + */ +internal fun ClassId.fieldThatIsSetWith(methodId: ExecutableId): FieldId? = + allDeclaredFieldIds.singleOrNull { !it.isStatic && it.setter.describesSameMethodAs(methodId) } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/CommentUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/CommentUtil.kt new file mode 100644 index 0000000000..7c22717f15 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/CommentUtil.kt @@ -0,0 +1,10 @@ +package org.utbot.framework.codegen.util + +import org.utbot.framework.plugin.api.util.IndentUtil.TAB + +fun String.escapeControlChars() = + replace("\b", "\\b") + .replace("\n", "\\n") + .replace("\t", TAB) + .replace("\r", "\\r") + .replace("\\u","\\\\u") \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/DslUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/DslUtil.kt new file mode 100644 index 0000000000..1f830a0612 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/DslUtil.kt @@ -0,0 +1,98 @@ +package org.utbot.framework.codegen.util + +import org.utbot.framework.codegen.domain.models.CgArrayElementAccess +import org.utbot.framework.codegen.domain.models.CgDecrement +import org.utbot.framework.codegen.domain.models.CgEqualTo +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgGetLength +import org.utbot.framework.codegen.domain.models.CgGreaterThan +import org.utbot.framework.codegen.domain.models.CgIncrement +import org.utbot.framework.codegen.domain.models.CgLessThan +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.tree.CgMethodConstructor +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.byteClassId +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.stringClassId + +fun CgExpression.at(index: Any?): CgArrayElementAccess = + CgArrayElementAccess(this, index.resolve()) + +infix fun CgExpression.equalTo(other: Any?): CgEqualTo = + CgEqualTo(this, other.resolve()) + +infix fun CgExpression.lessThan(other: Any?): CgLessThan = + CgLessThan(this, other.resolve()) + +infix fun CgExpression.greaterThan(other: Any?): CgGreaterThan = + CgGreaterThan(this, other.resolve()) + +// Literals + +// TODO: is it OK to use Object as a type of null literal? +fun nullLiteral() = CgLiteral(objectClassId, null) + +fun intLiteral(num: Int) = CgLiteral(intClassId, num) + +fun longLiteral(num: Long) = CgLiteral(longClassId, num) + +fun byteLiteral(num: Byte) = CgLiteral(byteClassId, num) + +fun shortLiteral(num: Short) = CgLiteral(shortClassId, num) + +fun floatLiteral(num: Float) = CgLiteral(floatClassId, num) + +fun doubleLiteral(num: Double) = CgLiteral(doubleClassId, num) + +fun booleanLiteral(b: Boolean) = CgLiteral(booleanClassId, b) + +fun charLiteral(c: Char) = CgLiteral(charClassId, c) + +fun stringLiteral(string: String) = CgLiteral(stringClassId, string) + +// Get array length + +/** + * Returns length field access for array type variable and [UtilMethodProvider.getArrayLengthMethodId] call otherwise. + */ +internal fun CgVariable.length(methodConstructor: CgMethodConstructor): CgExpression { + val thisVariable = this + + return if (type.isArray) { + CgGetLength(thisVariable) + } else { + with(methodConstructor) { utilsClassId[getArrayLength](thisVariable) } + } +} + +// Increment and decrement + +fun CgVariable.inc(): CgIncrement = CgIncrement(this) + +fun CgVariable.dec(): CgDecrement = CgDecrement(this) + +fun Any?.resolve(): CgExpression = when (this) { + null -> nullLiteral() + is Int -> intLiteral(this) + is Long -> longLiteral(this) + is Byte -> byteLiteral(this) + is Short -> shortLiteral(this) + is Float -> floatLiteral(this) + is Double -> doubleLiteral(this) + is Boolean -> booleanLiteral(this) + is Char -> charLiteral(this) + is String -> stringLiteral(this) + is CgExpression -> this + else -> error("Expected primitive, string, null or CgExpression, but got: ${this::class}") +} + +fun Array<*>.resolve(): List = map { it.resolve() } + diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/ExecutableIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/ExecutableIdUtil.kt new file mode 100644 index 0000000000..af3cef3bc8 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/ExecutableIdUtil.kt @@ -0,0 +1,18 @@ +package org.utbot.framework.codegen.util + +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.util.isPackagePrivate +import org.utbot.framework.plugin.api.util.isProtected +import org.utbot.framework.plugin.api.util.isPublic + +/** + * For now we will count executable accessible if it is whether public + * or package-private inside target package [packageName]. + * + * @param packageName name of the package we check accessibility from + */ +infix fun ExecutableId.isAccessibleFrom(packageName: String): Boolean { + val isAccessibleFromPackageByModifiers = isPublic || (classId.packageName == packageName && (isPackagePrivate || isProtected)) + + return classId.isAccessibleFrom(packageName) && isAccessibleFromPackageByModifiers +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/FieldIdUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/FieldIdUtil.kt new file mode 100644 index 0000000000..d5ec74b822 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/util/FieldIdUtil.kt @@ -0,0 +1,83 @@ +package org.utbot.framework.codegen.util + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.isFinal +import org.utbot.framework.plugin.api.util.isPackagePrivate +import org.utbot.framework.plugin.api.util.isProtected +import org.utbot.framework.plugin.api.util.isPublic +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.voidClassId + +/** + * For now we will count field accessible if it is not private and its class is also accessible, + * because we generate tests in the same package with the class under test, + * which means we can access public, protected and package-private fields + * + * @param context context in which code is generated (it is needed because the method needs to know package and language) + * @param callerClassId object on which we try to access the field + */ +fun FieldId.isAccessibleFrom(packageName: String, callerClassId: ClassId): Boolean { + val isClassAccessible = declaringClass.isAccessibleFrom(packageName) + + /* + * There is a corner case which we ignore now. + * Protected fields are accessible in nested classes of inheritors. + */ + val isAccessibleByVisibility = + isPublic || + isPackagePrivate && callerClassId.packageName == packageName && declaringClass.packageName == packageName || + isProtected && declaringClass.packageName == packageName + val isAccessibleFromPackageByModifiers = isAccessibleByVisibility && !isSynthetic + + return isClassAccessible && isAccessibleFromPackageByModifiers +} + +internal fun FieldId.canBeReadViaGetterFrom(context: CgContext): Boolean = + declaringClass.allMethods.contains(getter) && getter.isAccessibleFrom(context.testClassPackageName) + +/** + * Returns whether you can read field's value without reflection + */ +internal fun FieldId.canBeReadFrom(context: CgContext, callerClassId: ClassId): Boolean { + if (context.codegenLanguage == CodegenLanguage.KOTLIN) { + // Kotlin will allow direct field access for non-static fields with accessible getter + if (!isStatic && canBeReadViaGetterFrom(context)) + return true + } + + return isAccessibleFrom(context.testClassPackageName, callerClassId) +} + +internal fun FieldId.canBeSetViaSetterFrom(context: CgContext): Boolean = + declaringClass.allMethods.contains(setter) && setter.isAccessibleFrom(context.testClassPackageName) + +/** + * Whether or not a field can be set without reflection + */ +internal fun FieldId.canBeSetFrom(context: CgContext, callerClassId: ClassId): Boolean { + if (context.codegenLanguage == CodegenLanguage.KOTLIN) { + // Kotlin will allow direct write access if both getter and setter is defined + // !isAccessibleFrom(context) is important here because above rule applies to final fields only if they are not accessible in Java terms + if (!isAccessibleFrom(context.testClassPackageName, callerClassId) && !isStatic && canBeReadViaGetterFrom(context) && canBeSetViaSetterFrom(context)) { + return true + } + } + + return isAccessibleFrom(context.testClassPackageName, callerClassId) && !isFinal +} + +/** + * The default getter method for field (the one which is generated by Kotlin compiler) + */ +val FieldId.getter: MethodId + get() = MethodId(declaringClass, "get${name.replaceFirstChar { it.uppercase() } }", type, emptyList()) + +/** + * The default setter method for field (the one which is generated by Kotlin compiler) + */ +val FieldId.setter: MethodId + get() = MethodId(declaringClass, "set${name.replaceFirstChar { it.uppercase() } }", voidClassId, listOf(type)) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/InstrumentationContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/InstrumentationContext.kt deleted file mode 100644 index 0233c7b6d9..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/InstrumentationContext.kt +++ /dev/null @@ -1,72 +0,0 @@ -package org.utbot.framework.concrete - -import org.utbot.framework.plugin.api.util.signature -import java.lang.reflect.Method -import java.util.IdentityHashMap - -/** - * Some information, which is computed after classes instrumentation. - * - * This information will be used later in `invoke` function. - */ -class InstrumentationContext { - /** - * Contains unique id for each method, which is required for this method mocking. - */ - val methodSignatureToId = mutableMapOf() - - object MockGetter { - data class MockContainer(private val values: List<*>) { - private var ptr: Int = 0 - fun hasNext(): Boolean = ptr < values.size - fun nextValue(): Any? = values[ptr++] - } - - /** - * Instance -> method -> list of values in the return order - */ - private val mocks = IdentityHashMap>() - private val callSites = HashMap>() - - /** - * Returns possibility of taking mock object of method with supplied [methodSignature] on an [obj] object. - */ - @JvmStatic - fun hasMock(obj: Any?, methodSignature: String): Boolean = - mocks[obj]?.get(methodSignature)?.hasNext() ?: false - - /** - * Returns the next value for mocked method with supplied [methodSignature] on an [obj] object. - * - * This function has only to be called from the instrumented bytecode everytime - * we need a next value for a mocked method. - */ - @JvmStatic - fun getMock(obj: Any?, methodSignature: String): Any? = - mocks[obj]?.get(methodSignature).let { container -> - container ?: error("Can't get mock container for method [$obj\$$methodSignature]") - container.nextValue() - } - - /** - * Returns current callSites for mocking new instance of [instanceType] contains [callSite] or not - */ - @JvmStatic - fun checkCallSite(instanceType: String, callSite: String): Boolean { - return callSites.getOrDefault(instanceType, emptySet()).contains(callSite) - } - - fun updateCallSites(instanceType: String, instanceCallSites: Set) { - callSites[instanceType] = instanceCallSites - } - - fun updateMocks(obj: Any?, methodSignature: String, values: List<*>) { - val methodMocks = mocks.getOrPut(obj) { mutableMapOf() } - methodMocks[methodSignature] = MockContainer(values) - } - - fun updateMocks(obj: Any?, method: Method, values: List<*>) { - updateMocks(obj, method.signature, values) - } - } -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/IterableConstructors.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/IterableConstructors.kt deleted file mode 100644 index 68955136e0..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/IterableConstructors.kt +++ /dev/null @@ -1,69 +0,0 @@ -package org.utbot.framework.concrete - -import org.utbot.framework.plugin.api.ConstructorId -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.framework.plugin.api.UtStatementModel -import org.utbot.framework.plugin.api.util.booleanClassId -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.objectClassId -import org.utbot.framework.util.valueToClassId - -internal class CollectionConstructor : UtAssembleModelConstructorBase() { - override fun UtAssembleModel.modifyChains( - internalConstructor: UtModelConstructorInterface, - instantiationChain: MutableList, - modificationChain: MutableList, - valueToConstructFrom: Any - ) { - @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") - valueToConstructFrom as java.util.Collection<*> - - // If [valueToConstructFrom] constructed incorrectly (some inner transient fields are null, etc.) this may fail. - // This value will be constructed as UtCompositeModel. - val models = valueToConstructFrom.map { internalConstructor.construct(it, valueToClassId(it)) } - - val classId = valueToConstructFrom::class.java.id - - instantiationChain += UtExecutableCallModel( - instance = null, - ConstructorId(classId, emptyList()), - emptyList(), - this - ) - - val addMethodId = MethodId(classId, "add", booleanClassId, listOf(objectClassId)) - - modificationChain += models.map { UtExecutableCallModel(this, addMethodId, listOf(it)) } - } -} - -internal class MapConstructor : UtAssembleModelConstructorBase() { - override fun UtAssembleModel.modifyChains( - internalConstructor: UtModelConstructorInterface, - instantiationChain: MutableList, - modificationChain: MutableList, - valueToConstructFrom: Any - ) { - valueToConstructFrom as java.util.AbstractMap<*, *> - - val keyToValueModels = valueToConstructFrom.map { (key, value) -> - internalConstructor.run { construct(key, valueToClassId(key)) to construct(value, valueToClassId(value)) } - } - - instantiationChain += UtExecutableCallModel( - instance = null, - ConstructorId(classId, emptyList()), - emptyList(), - this - ) - - val putMethodId = MethodId(classId, "put", objectClassId, listOf(objectClassId, objectClassId)) - - modificationChain += keyToValueModels.map { (key, value) -> - UtExecutableCallModel(this, putMethodId, listOf(key, value)) - } - } -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockController.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockController.kt deleted file mode 100644 index 679925c30e..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockController.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.utbot.framework.concrete - -import java.io.Closeable - -interface MockController : Closeable \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt deleted file mode 100644 index d1452b2cef..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MockValueConstructor.kt +++ /dev/null @@ -1,504 +0,0 @@ -package org.utbot.framework.concrete - -import org.utbot.common.findField -import org.utbot.common.findFieldOrNull -import org.utbot.common.invokeCatching -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.ConstructorId -import org.utbot.framework.plugin.api.ExecutableId -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.FieldMockTarget -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.MockId -import org.utbot.framework.plugin.api.MockInfo -import org.utbot.framework.plugin.api.MockTarget -import org.utbot.framework.plugin.api.ObjectMockTarget -import org.utbot.framework.plugin.api.ParameterMockTarget -import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtClassRefModel -import org.utbot.framework.plugin.api.UtCompositeModel -import org.utbot.framework.plugin.api.UtConcreteValue -import org.utbot.framework.plugin.api.UtDirectSetFieldModel -import org.utbot.framework.plugin.api.UtEnumConstantModel -import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.framework.plugin.api.UtMockValue -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation -import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtReferenceModel -import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation -import org.utbot.framework.plugin.api.UtVoidModel -import org.utbot.framework.plugin.api.isMockModel -import org.utbot.framework.plugin.api.util.constructor -import org.utbot.framework.plugin.api.util.executableId -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.method -import org.utbot.framework.plugin.api.util.utContext -import org.utbot.framework.util.anyInstance -import java.io.Closeable -import java.lang.reflect.Field -import java.lang.reflect.Modifier -import java.util.IdentityHashMap -import kotlin.reflect.KClass -import org.mockito.Mockito -import org.mockito.stubbing.Answer -import org.objectweb.asm.Type -import org.utbot.common.withAccessibility - -/** - * Constructs values (including mocks) from models. - * - * Uses model->constructed object reference-equality cache. - * - * This class is based on `ValueConstructor.kt`. The main difference is the ability to create mocked objects and mock - * static methods. - * - * Note that `clearState` was deleted! - */ -// TODO: JIRA:1379 -- Refactor ValueConstructor and MockValueConstructor -class MockValueConstructor( - private val instrumentationContext: InstrumentationContext -) : Closeable { - private val classLoader: ClassLoader - get() = utContext.classLoader - - val objectToModelCache: IdentityHashMap - get() { - val objectToModel = IdentityHashMap() - constructedObjects.forEach { (model, obj) -> - objectToModel[obj] = model - } - return objectToModel - } - - // TODO: JIRA:1379 -- replace UtReferenceModel with Int - private val constructedObjects = HashMap() - private val resultsCache = HashMap() - private val mockInfo = mutableListOf() - private var mockTarget: MockTarget? = null - private var mockCounter = 0 - - /** - * Controllers contain info about mocked methods and have to be closed to restore initial state. - */ - private val controllers = mutableListOf() - - /** - * Sets mock context (possible mock target) before block execution and restores previous one after block execution. - */ - private inline fun withMockTarget(target: MockTarget?, block: () -> T): T { - val old = mockTarget - try { - mockTarget = target - return block() - } finally { - mockTarget = old - } - } - - fun constructMethodParameters(models: List): List> = - models.mapIndexed { index, model -> - val target = mockTarget(model) { ParameterMockTarget(model.classId.name, index) } - construct(model, target) - } - - fun constructStatics(staticsBefore: Map): Map> = - staticsBefore.mapValues { (field, model) -> // TODO: refactor this - val target = FieldMockTarget(model.classId.name, field.declaringClass.name, owner = null, field.name) - construct(model, target) - } - - /** - * Main construction method. - * - * Takes care of nulls. Does not use cache, instead construct(Object/Array/List/FromAssembleModel) method - * uses cache directly. - * - * Takes mock creation context (possible mock target) to create mock if required. - */ - private fun construct(model: UtModel, target: MockTarget?): UtConcreteValue<*> = withMockTarget(target) { - when (model) { - is UtNullModel -> UtConcreteValue(null, model.classId.jClass) - is UtPrimitiveModel -> UtConcreteValue(model.value, model.classId.jClass) - is UtEnumConstantModel -> UtConcreteValue(model.value) - is UtClassRefModel -> UtConcreteValue(model.value) - is UtCompositeModel -> UtConcreteValue(constructObject(model), model.classId.jClass) - is UtArrayModel -> UtConcreteValue(constructArray(model)) - is UtAssembleModel -> UtConcreteValue(constructFromAssembleModel(model), model.classId.jClass) - is UtVoidModel -> UtConcreteValue(Unit) - } - } - - /** - * Constructs object by model, uses reference-equality cache. - * - * Returns null for mock cause cannot instantiate it. - */ - private fun constructObject(model: UtCompositeModel): Any { - constructedObjects[model]?.let { return it } - - this.mockTarget?.let { mockTarget -> - model.mocks.forEach { (methodId, models) -> - mockInfo += MockInfo(mockTarget, methodId, models.map { model -> - if (model.isMockModel()) { - val mockId = MockId("mock${++mockCounter}") - // Call to "construct" method still required to collect mock interaction - construct(model, ObjectMockTarget(model.classId.name, mockId)) - UtMockValue(mockId, model.classId.name) - } else { - construct(model, target = null) - } - }) - } - } - - - val javaClass = javaClass(model.classId) - - val classInstance = if (!model.isMock) { - val notMockInstance = javaClass.anyInstance - - constructedObjects[model] = notMockInstance - notMockInstance - } else { - val mockInstance = generateMockitoMock(javaClass, model.mocks) - - constructedObjects[model] = mockInstance - mockInstance - } - - model.fields.forEach { (field, fieldModel) -> - val declaredField = - javaClass.findFieldOrNull(field.name) ?: error("Can't find field: $field for $javaClass") - val accessible = declaredField.isAccessible - declaredField.isAccessible = true - - val modifiersField = Field::class.java.getDeclaredField("modifiers") - modifiersField.isAccessible = true - - val target = mockTarget(fieldModel) { - FieldMockTarget(fieldModel.classId.name, model.classId.name, UtConcreteValue(classInstance), field.name) - } - val value = construct(fieldModel, target).value - val instance = if (Modifier.isStatic(declaredField.modifiers)) null else classInstance - declaredField.set(instance, value) - declaredField.isAccessible = accessible - } - - return classInstance - } - - private fun generateMockitoAnswer(methodToValues: Map>): Answer<*> { - val pointers = methodToValues.mapValues { (_, _) -> 0 }.toMutableMap() - val concreteValues = methodToValues.mapValues { (_, models) -> - models.map { model -> - val mockId = MockId("mock${++mockCounter}") - val target = mockTarget(model) { ObjectMockTarget(model.classId.name, mockId) } - construct(model, target).value.takeIf { it != Unit } // if it is unit, then null should be returned - // This model has to be already constructed, so it is OK to pass null as a target - } - } - return Answer { invocation -> - with(invocation.method) { - pointers[executableId].let { pointer -> - concreteValues[executableId].let { values -> - if (pointer != null && values != null && pointer < values.size) { - pointers[executableId] = pointer + 1 - values[pointer] - } else { - invocation.callRealMethod() - } - } - } - } - } - } - - private fun generateMockitoMock(clazz: Class<*>, mocks: Map>): Any { - return Mockito.mock(clazz, generateMockitoAnswer(mocks)) - } - - private fun computeConcreteValuesForMethods( - methodToValues: Map>, - ): Map> = methodToValues.mapValues { (_, models) -> - models.map { mockAndGet(it) } - } - - /** - * Mocks methods on [instance] with supplied [methodToValues]. - * - * Also add new controllers to [controllers]. Each controller corresponds to one method. If it is a static method, then the controller - * must be closed. If it is a non-static method and you don't change the mocks behaviour on the passed instance, - * then the controller doesn't have to be closed - * - * @param [instance] must be non-`null` for non-static methods. - * @param [methodToValues] return values for methods. - */ - private fun mockMethods( - instance: Any?, - methodToValues: Map>, - ) { - controllers += computeConcreteValuesForMethods(methodToValues).map { (method, values) -> - if (method !is MethodId) { - throw IllegalArgumentException("Expected MethodId, but got: $method") - } - MethodMockController( - method.classId.jClass, - method.method, - instance, - values, - instrumentationContext - ) - } - - } - - /** - * Mocks static methods according to instrumentations. - */ - fun mockStaticMethods( - instrumentations: List, - ) { - val methodToValues = instrumentations.associate { it.methodId as ExecutableId to it.values } - mockMethods(null, methodToValues) - } - - /** - * Mocks new instances according to instrumentations - */ - fun mockNewInstances( - instrumentations: List, - ) { - controllers += instrumentations.map { mock -> - InstanceMockController( - mock.classId, - mock.instances.map { mockAndGet(it) }, - mock.callSites.map { Type.getType(it.jClass).internalName }.toSet() - ) - } - } - - /** - * Constructs array by model. - * - * Supports arrays of primitive, arrays of arrays and arrays of objects. - * - * Note: does not check isNull, but if isNull set returns empty array because for null array length set to 0. - */ - private fun constructArray(model: UtArrayModel): Any { - constructedObjects[model]?.let { return it } - - with(model) { - val elementClassId = classId.elementClassId ?: error( - "Provided incorrect UtArrayModel without elementClassId. ClassId: ${model.classId}, model: $model" - ) - return when (elementClassId.jvmName) { - "B" -> ByteArray(length) { primitive(constModel) }.apply { - stores.forEach { (index, model) -> this[index] = primitive(model) } - }.also { constructedObjects[model] = it } - "S" -> ShortArray(length) { primitive(constModel) }.apply { - stores.forEach { (index, model) -> this[index] = primitive(model) } - }.also { constructedObjects[model] = it } - "C" -> CharArray(length) { primitive(constModel) }.apply { - stores.forEach { (index, model) -> this[index] = primitive(model) } - }.also { constructedObjects[model] = it } - "I" -> IntArray(length) { primitive(constModel) }.apply { - stores.forEach { (index, model) -> this[index] = primitive(model) } - }.also { constructedObjects[model] = it } - "J" -> LongArray(length) { primitive(constModel) }.apply { - stores.forEach { (index, model) -> this[index] = primitive(model) } - }.also { constructedObjects[model] = it } - "F" -> FloatArray(length) { primitive(constModel) }.apply { - stores.forEach { (index, model) -> this[index] = primitive(model) } - }.also { constructedObjects[model] = it } - "D" -> DoubleArray(length) { primitive(constModel) }.apply { - stores.forEach { (index, model) -> this[index] = primitive(model) } - }.also { constructedObjects[model] = it } - "Z" -> BooleanArray(length) { primitive(constModel) }.apply { - stores.forEach { (index, model) -> this[index] = primitive(model) } - }.also { constructedObjects[model] = it } - else -> { - val javaClass = javaClass(elementClassId) - val instance = java.lang.reflect.Array.newInstance(javaClass, length) as Array<*> - constructedObjects[model] = instance - for (i in instance.indices) { - val elementModel = stores[i] ?: constModel - val value = construct(elementModel, null).value - try { - java.lang.reflect.Array.set(instance, i, value) - } catch (iae:IllegalArgumentException) { - throw IllegalArgumentException( - iae.message + " array: ${instance.javaClass.name}; value: ${value?.javaClass?.name}" , iae - ) - } - } - instance - } - } - } - } - - /** - * Constructs object with [UtAssembleModel]. - */ - private fun constructFromAssembleModel(assembleModel: UtAssembleModel): Any { - constructedObjects[assembleModel]?.let { return it } - - assembleModel.allStatementsChain.forEach { statementModel -> - when (statementModel) { - is UtExecutableCallModel -> updateWithExecutableCallModel(statementModel, assembleModel) - is UtDirectSetFieldModel -> updateWithDirectSetFieldModel(statementModel) - } - } - - return resultsCache[assembleModel] ?: error("Can't assemble model: $assembleModel") - } - - /** - * Updates instance state with [UtExecutableCallModel] invocation. - */ - private fun updateWithExecutableCallModel( - callModel: UtExecutableCallModel, - assembleModel: UtAssembleModel, - ) { - val executable = callModel.executable - val instanceValue = resultsCache[callModel.instance] - val params = callModel.params.map { value(it) } - - val result = when (executable) { - is MethodId -> executable.call(params, instanceValue) - is ConstructorId -> executable.call(params) - } - - // Ignore result if returnId is null. Otherwise add it to instance cache. - callModel.returnValue?.let { - checkNotNull(result) { "Tracked instance can't be null for call $executable in model $assembleModel" } - resultsCache[it] = result - - //If statement is final instantiating, add result to constructed objects cache - if (callModel == assembleModel.finalInstantiationModel) { - constructedObjects[assembleModel] = result - } - } - } - - /** - * Updates instance with [UtDirectSetFieldModel] execution. - */ - private fun updateWithDirectSetFieldModel(directSetterModel: UtDirectSetFieldModel) { - val instanceModel = directSetterModel.instance - val instance = resultsCache[instanceModel] ?: error("Model $instanceModel is not instantiated") - - val instanceClassId = instanceModel.classId - val fieldModel = directSetterModel.fieldModel - - val field = instance::class.java.findField(directSetterModel.fieldId.name) - val isAccessible = field.isAccessible - - try { - //set field accessible to support protected or package-private direct setters - field.isAccessible = true - - //prepare mockTarget for field if it is a mock - val mockTarget = mockTarget(fieldModel) { - FieldMockTarget( - fieldModel.classId.name, - instanceClassId.name, - UtConcreteValue(javaClass(instanceClassId).anyInstance), - field.name - ) - } - - //construct and set the value - val fieldValue = construct(fieldModel, mockTarget).value - field.set(instance, fieldValue) - } finally { - //restore accessibility property of the field - field.isAccessible = isAccessible - } - } - - /** - * Constructs value from [UtModel]. - */ - private fun value(model: UtModel) = construct(model, null).value - - private fun mockAndGet(model: UtModel): Any? { - val target = mockTarget(model) { // won't be called if model is not mockModel - val mockId = MockId("mock${++mockCounter}") - ObjectMockTarget(model.classId.name, mockId) - } - return construct(model, target).value - } - - private fun MethodId.call(args: List, instance: Any?): Any? = - method.run { - withAccessibility { - invokeCatching(obj = instance, args = args).getOrThrow() - } - } - - private fun ConstructorId.call(args: List): Any? = - constructor.run { - withAccessibility { - newInstance(*args.toTypedArray()) - } - } - - /** - * Fetches primitive value from NutsModel to create array of primitives. - */ - private inline fun primitive(model: UtModel): T = (model as UtPrimitiveModel).value as T - - private fun javaClass(id: ClassId) = kClass(id).java - - private fun kClass(id: ClassId) = - if (id.elementClassId != null) { - arrayClassOf(id.elementClassId!!) - } else { - when (id.jvmName) { - "B" -> Byte::class - "S" -> Short::class - "C" -> Char::class - "I" -> Int::class - "J" -> Long::class - "F" -> Float::class - "D" -> Double::class - "Z" -> Boolean::class - else -> classLoader.loadClass(id.name).kotlin - } - } - - private fun arrayClassOf(elementClassId: ClassId): KClass<*> = - if (elementClassId.elementClassId != null) { - val elementClass = arrayClassOf(elementClassId.elementClassId!!) - java.lang.reflect.Array.newInstance(elementClass.java, 0)::class - } else { - when (elementClassId.jvmName) { - "B" -> ByteArray::class - "S" -> ShortArray::class - "C" -> CharArray::class - "I" -> IntArray::class - "J" -> LongArray::class - "F" -> FloatArray::class - "D" -> DoubleArray::class - "Z" -> BooleanArray::class - else -> { - val elementClass = classLoader.loadClass(elementClassId.name) - java.lang.reflect.Array.newInstance(elementClass, 0)::class - } - } - } - - override fun close() { - controllers.forEach { it.close() } - } -} - -/** - * Creates mock target using init lambda if model represents mock or null otherwise. - */ -private fun mockTarget(model: UtModel, init: () -> MockTarget): MockTarget? = - if (model.isMockModel()) init() else null \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/PrimitiveWrapperConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/PrimitiveWrapperConstructor.kt deleted file mode 100644 index 4793d6377a..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/PrimitiveWrapperConstructor.kt +++ /dev/null @@ -1,36 +0,0 @@ -package org.utbot.framework.concrete - -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtStatementModel -import org.utbot.framework.plugin.api.util.constructorId -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.primitiveByWrapper -import org.utbot.framework.plugin.api.util.stringClassId - -internal class PrimitiveWrapperConstructor : UtAssembleModelConstructorBase() { - override fun UtAssembleModel.modifyChains( - internalConstructor: UtModelConstructorInterface, - instantiationChain: MutableList, - modificationChain: MutableList, - valueToConstructFrom: Any - ) { - checkClassCast(classId.jClass, valueToConstructFrom::class.java) - - instantiationChain += UtExecutableCallModel( - null, - constructorId(classId, classId.unbox()), - listOf(UtPrimitiveModel(valueToConstructFrom)), - this - ) - } -} - - -private fun ClassId.unbox() = if (this == stringClassId) { - stringClassId -} else { - primitiveByWrapper.getOrElse(this) { error("Unknown primitive wrapper: $this") } -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtAssembleModelConstructors.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtAssembleModelConstructors.kt deleted file mode 100644 index 0d22eb6268..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtAssembleModelConstructors.kt +++ /dev/null @@ -1,105 +0,0 @@ -package org.utbot.framework.concrete - -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtStatementModel -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.primitiveWrappers -import org.utbot.framework.plugin.api.util.voidWrapperClassId -import org.utbot.framework.util.nextModelName - -private val predefinedConstructors = mutableMapOf, () -> UtAssembleModelConstructorBase>( - /** - * Optionals - */ - java.util.OptionalInt::class.java to { OptionalIntConstructor() }, - java.util.OptionalLong::class.java to { OptionalLongConstructor() }, - java.util.OptionalDouble::class.java to { OptionalDoubleConstructor() }, - java.util.Optional::class.java to { OptionalConstructor() }, - - /** - * Lists - */ - java.util.LinkedList::class.java to { CollectionConstructor() }, - java.util.ArrayList::class.java to { CollectionConstructor() }, - java.util.AbstractList::class.java to { CollectionConstructor() }, - java.util.List::class.java to { CollectionConstructor() }, - java.util.Deque::class.java to { CollectionConstructor() }, - java.util.ArrayDeque::class.java to { CollectionConstructor() }, - java.util.concurrent.LinkedBlockingDeque::class.java to { CollectionConstructor() }, - - /** - * Sets - */ - java.util.HashSet::class.java to { CollectionConstructor() }, - java.util.LinkedHashSet::class.java to { CollectionConstructor() }, - java.util.AbstractSet::class.java to { CollectionConstructor() }, - java.util.Set::class.java to { CollectionConstructor() }, - - /** - * Maps - */ - java.util.HashMap::class.java to { MapConstructor() }, - java.util.TreeMap::class.java to { MapConstructor() }, - java.util.LinkedHashMap::class.java to { MapConstructor() }, - java.util.AbstractMap::class.java to { MapConstructor() }, - java.util.concurrent.ConcurrentMap::class.java to { MapConstructor() }, - java.util.concurrent.ConcurrentHashMap::class.java to { MapConstructor() }, - java.util.IdentityHashMap::class.java to { MapConstructor() }, - java.util.WeakHashMap::class.java to { MapConstructor() }, - - /** - * Hashtables - */ - java.util.Hashtable::class.java to { MapConstructor() }, - - - /** - * String wrapper - */ - java.lang.String::class.java.let { it to { PrimitiveWrapperConstructor() } }, - - /** - * TODO: JIRA:1405 -- Add assemble constructors for another standard classes as well. - */ -).apply { - /** - * Primitive wrappers - */ - this += primitiveWrappers - .filter { it != voidWrapperClassId } - .associate { it.jClass to { PrimitiveWrapperConstructor() } } -} - -internal fun findUtAssembleModelConstructor(classId: ClassId): UtAssembleModelConstructorBase? = - predefinedConstructors[classId.jClass]?.invoke() - -internal abstract class UtAssembleModelConstructorBase { - fun constructAssembleModel( - internalConstructor: UtModelConstructorInterface, - valueToConstructFrom: Any, - valueClassId: ClassId, - id: Int?, - init: (UtAssembleModel) -> Unit - ): UtAssembleModel { - val instantiationChain = mutableListOf() - val modificationChain = mutableListOf() - val baseName = valueClassId.simpleName.decapitalize() - return UtAssembleModel(id, valueClassId, nextModelName(baseName), instantiationChain, modificationChain) - .also(init) - .apply { modifyChains(internalConstructor, instantiationChain, modificationChain, valueToConstructFrom) } - } - - protected abstract fun UtAssembleModel.modifyChains( - internalConstructor: UtModelConstructorInterface, - instantiationChain: MutableList, - modificationChain: MutableList, - valueToConstructFrom: Any - ) -} - -internal fun UtAssembleModelConstructorBase.checkClassCast(expected: Class<*>, actual: Class<*>) { - require(expected.isAssignableFrom(actual)) { - "Can't cast $actual to $expected in $this assemble constructor." - } -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt deleted file mode 100644 index 29d4d51139..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtExecutionInstrumentation.kt +++ /dev/null @@ -1,288 +0,0 @@ -package org.utbot.framework.concrete - -import org.utbot.common.StopWatch -import org.utbot.common.ThreadBasedExecutor -import org.utbot.common.withAccessibility -import org.utbot.common.withRemovedFinalModifier -import org.utbot.framework.UtSettings -import org.utbot.framework.assemble.AssembleModelGenerator -import org.utbot.framework.plugin.api.Coverage -import org.utbot.framework.plugin.api.EnvironmentModels -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.Instruction -import org.utbot.framework.plugin.api.TimeoutException -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtExecutionFailure -import org.utbot.framework.plugin.api.UtExecutionResult -import org.utbot.framework.plugin.api.UtExecutionSuccess -import org.utbot.framework.plugin.api.UtExplicitlyThrownException -import org.utbot.framework.plugin.api.UtImplicitlyThrownException -import org.utbot.framework.plugin.api.UtInstrumentation -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation -import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation -import org.utbot.framework.plugin.api.UtTimeoutException -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.field -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.singleExecutableId -import org.utbot.framework.plugin.api.util.utContext -import org.utbot.framework.plugin.api.util.withUtContext -import org.utbot.framework.plugin.api.withReflection -import org.utbot.instrumentation.instrumentation.ArgumentList -import org.utbot.instrumentation.instrumentation.Instrumentation -import org.utbot.instrumentation.instrumentation.InvokeInstrumentation -import org.utbot.instrumentation.instrumentation.et.EtInstruction -import org.utbot.instrumentation.instrumentation.et.ExplicitThrowInstruction -import org.utbot.instrumentation.instrumentation.et.TraceHandler -import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter -import org.utbot.instrumentation.instrumentation.mock.MockClassVisitor -import java.security.ProtectionDomain -import java.util.IdentityHashMap -import kotlin.reflect.jvm.javaMethod - -/** - * Consists of the data needed to execute the method concretely. Also includes method arguments stored in models. - * - * @property [stateBefore] is necessary for construction of parameters of a concrete call. - * @property [instrumentation] is necessary for mocking static methods and new instances. - * @property [timeout] is timeout for specific concrete execution (in milliseconds). - * By default is initialized from [UtSettings.concreteExecutionTimeoutInChildProcess] - */ -data class UtConcreteExecutionData( - val stateBefore: EnvironmentModels, - val instrumentation: List, - val timeout: Long = UtSettings.concreteExecutionTimeoutInChildProcess -) - -class UtConcreteExecutionResult( - val stateAfter: EnvironmentModels, - val result: UtExecutionResult, - val coverage: Coverage -) { - private fun collectAllModels(): List { - val allModels = listOfNotNull(stateAfter.thisInstance).toMutableList() - allModels += stateAfter.parameters - allModels += stateAfter.statics.values - allModels += listOfNotNull((result as? UtExecutionSuccess)?.model) - return allModels - } - - private fun updateWithAssembleModels( - assembledUtModels: IdentityHashMap - ): UtConcreteExecutionResult { - val toAssemble: (UtModel) -> UtModel = { assembledUtModels.getOrDefault(it, it) } - - val resolvedStateAfter = EnvironmentModels( - stateAfter.thisInstance?.let { toAssemble(it) }, - stateAfter.parameters.map { toAssemble(it) }, - stateAfter.statics.mapValues { toAssemble(it.value) } - ) - val resolvedResult = - (result as? UtExecutionSuccess)?.model?.let { UtExecutionSuccess(toAssemble(it)) } ?: result - - return UtConcreteExecutionResult( - resolvedStateAfter, - resolvedResult, - coverage - ) - } - - /** - * Tries to convert all models from [UtExecutionResult] to [UtAssembleModel] if possible. - * - * @return [UtConcreteExecutionResult] with converted models. - */ - fun convertToAssemble( - methodUnderTest: UtMethod<*> - ): UtConcreteExecutionResult { - val allModels = collectAllModels() - - val modelsToAssembleModels = AssembleModelGenerator(methodUnderTest).createAssembleModels(allModels) - return updateWithAssembleModels(modelsToAssembleModels) - } -} - -object UtExecutionInstrumentation : Instrumentation { - private val delegateInstrumentation = InvokeInstrumentation() - - private val instrumentationContext = InstrumentationContext() - - private val traceHandler = TraceHandler() - private val pathsToUserClasses = mutableSetOf() - - override fun init(pathsToUserClasses: Set) { - this.pathsToUserClasses.clear() - this.pathsToUserClasses += pathsToUserClasses - } - - /** - * Ignores [arguments], because concrete arguments will be constructed - * from models passed via [parameters]. - * - * Argument [parameters] must be of type [UtConcreteExecutionData]. - */ - override fun invoke( - clazz: Class<*>, - methodSignature: String, - arguments: ArgumentList, - parameters: Any? - ): UtConcreteExecutionResult { - withReflection { - if (parameters !is UtConcreteExecutionData) { - throw IllegalArgumentException("Argument parameters must be of type UtConcreteExecutionData, but was: ${parameters?.javaClass}") - } - val (stateBefore, instrumentations, timeout) = parameters // smart cast to UtConcreteExecutionData - val parametersModels = listOfNotNull(stateBefore.thisInstance) + stateBefore.parameters - - val methodId = clazz.singleExecutableId(methodSignature) - val returnClassId = methodId.returnType - traceHandler.resetTrace() - - return MockValueConstructor(instrumentationContext).use { constructor -> - val params = constructor.constructMethodParameters(parametersModels) - val staticFields = constructor - .constructStatics( - stateBefore - .statics - .filterKeys { !it.isInaccessibleViaReflection } - ) - .mapValues { (_, value) -> value.value } - - val concreteExecutionResult = withStaticFields(staticFields) { - val staticMethodsInstrumentation = instrumentations.filterIsInstance() - constructor.mockStaticMethods(staticMethodsInstrumentation) - val newInstanceInstrumentation = instrumentations.filterIsInstance() - constructor.mockNewInstances(newInstanceInstrumentation) - - traceHandler.resetTrace() - val stopWatch = StopWatch() - val context = UtContext(utContext.classLoader, stopWatch) - val concreteResult = ThreadBasedExecutor.threadLocal.invokeWithTimeout(timeout, stopWatch) { - withUtContext(context) { - delegateInstrumentation.invoke(clazz, methodSignature, params.map { it.value }) - } - }?.getOrThrow() as? Result<*> ?: Result.failure(TimeoutException("Timeout $timeout elapsed")) - val traceList = traceHandler.computeInstructionList() - - val cache = constructor.objectToModelCache - val utCompositeModelStrategy = ConstructOnlyUserClassesOrCachedObjectsStrategy(pathsToUserClasses, cache) - val utModelConstructor = UtModelConstructor(cache, utCompositeModelStrategy) - utModelConstructor.run { - val concreteUtModelResult = concreteResult.fold({ - UtExecutionSuccess(construct(it, returnClassId)) - }) { - sortOutException(it) - } - - val stateAfterParametersWithThis = params.map { construct(it.value, it.clazz.id) } - val stateAfterStatics = (staticFields.keys/* + traceHandler.computePutStatics()*/) - .associateWith { fieldId -> - fieldId.field.run { construct(withAccessibility { get(null) }, fieldId.type) } - } - val (stateAfterThis, stateAfterParameters) = if (stateBefore.thisInstance == null) { - null to stateAfterParametersWithThis - } else { - stateAfterParametersWithThis.first() to stateAfterParametersWithThis.drop(1) - } - val stateAfter = EnvironmentModels(stateAfterThis, stateAfterParameters, stateAfterStatics) - UtConcreteExecutionResult( - stateAfter, - concreteUtModelResult, - traceList.toApiCoverage() - ) - } - } - - concreteExecutionResult - } - } - } - - private val inaccessibleViaReflectionFields = setOf( - "security" to "java.lang.System", - "fieldFilterMap" to "sun.reflect.Reflection", - "methodFilterMap" to "sun.reflect.Reflection" - ) - - private val FieldId.isInaccessibleViaReflection: Boolean - get() = (name to declaringClass.name) in inaccessibleViaReflectionFields - - private fun sortOutException(exception: Throwable): UtExecutionFailure { - if (exception is TimeoutException) { - return UtTimeoutException(exception) - } - val instrs = traceHandler.computeInstructionList() - val isNested = if (instrs.isEmpty()) { - false - } else { - instrs.first().callId != instrs.last().callId - } - return if (instrs.isNotEmpty() && instrs.last().instructionData is ExplicitThrowInstruction) { - UtExplicitlyThrownException(exception, isNested) - } else { - UtImplicitlyThrownException(exception, isNested) - } - - } - - override fun transform( - loader: ClassLoader?, - className: String, - classBeingRedefined: Class<*>?, - protectionDomain: ProtectionDomain, - classfileBuffer: ByteArray - ): ByteArray { - val instrumenter = Instrumenter(classfileBuffer, loader) - - traceHandler.registerClass(className) - instrumenter.visitInstructions(traceHandler.computeInstructionVisitor(className)) - - val mockClassVisitor = instrumenter.visitClass { writer -> - MockClassVisitor( - writer, - InstrumentationContext.MockGetter::getMock.javaMethod!!, - InstrumentationContext.MockGetter::checkCallSite.javaMethod!!, - InstrumentationContext.MockGetter::hasMock.javaMethod!! - ) - } - - mockClassVisitor.signatureToId.forEach { (method, id) -> - instrumentationContext.methodSignatureToId += method to id - } - - return instrumenter.classByteCode - } - - private fun withStaticFields(staticFields: Map, block: () -> T): T { - val savedFields = mutableMapOf() - try { - staticFields.forEach { (fieldId, value) -> - fieldId.field.run { - withRemovedFinalModifier { - savedFields[fieldId] = get(null) - set(null, value) - } - } - } - return block() - } finally { - savedFields.forEach { (fieldId, value) -> - fieldId.field.run { - withRemovedFinalModifier { - set(null, value) - } - } - } - } - } -} - -/** - * Transforms a list of internal [EtInstruction]s to a list of api [Instruction]s. - */ -private fun List.toApiCoverage(): Coverage = - Coverage( - map { Instruction(it.className, it.methodSignature, it.line, it.id) } - ) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt deleted file mode 100644 index 7a8c4ff2f4..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/UtModelConstructor.kt +++ /dev/null @@ -1,320 +0,0 @@ -package org.utbot.framework.concrete - -import org.utbot.common.asPathToFile -import org.utbot.common.withAccessibility -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtClassRefModel -import org.utbot.framework.plugin.api.UtCompositeModel -import org.utbot.framework.plugin.api.UtEnumConstantModel -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtReferenceModel -import org.utbot.framework.plugin.api.UtVoidModel -import org.utbot.framework.plugin.api.isMockModel -import org.utbot.framework.plugin.api.util.booleanClassId -import org.utbot.framework.plugin.api.util.byteClassId -import org.utbot.framework.plugin.api.util.charClassId -import org.utbot.framework.plugin.api.util.doubleClassId -import org.utbot.framework.plugin.api.util.fieldId -import org.utbot.framework.plugin.api.util.floatClassId -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.intClassId -import org.utbot.framework.plugin.api.util.isPrimitive -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.longClassId -import org.utbot.framework.plugin.api.util.objectClassId -import org.utbot.framework.plugin.api.util.shortClassId -import org.utbot.framework.util.valueToClassId -import java.lang.reflect.Modifier -import java.util.IdentityHashMap - -/** - * Represents common interface for model constructors. - */ -internal interface UtModelConstructorInterface { - /** - * Constructs a UtModel from a concrete [value] with a specific [classId]. - */ - fun construct(value: Any?, classId: ClassId): UtModel -} - -/** - * Constructs models from concrete values. - * - * Uses reflection to traverse fields recursively ignoring static final fields. Also uses object->constructed model - * reference-equality cache. - * - * @param objectToModelCache cache used for the model construction with respect to stateBefore. For each object, it first - * @param compositeModelStrategy decides whether we should construct a composite model for a certain value or not. - * searches in [objectToModelCache] for [UtReferenceModel.id]. - */ -internal class UtModelConstructor( - private val objectToModelCache: IdentityHashMap, - private val compositeModelStrategy: UtCompositeModelStrategy = AlwaysConstructStrategy -) : UtModelConstructorInterface { - private val constructedObjects = IdentityHashMap() - - private var unusedId = 0 - private val usedIds = objectToModelCache.values - .filterIsInstance() - .mapNotNull { it.id } - .toMutableSet() - - private fun computeUnusedIdAndUpdate(): Int { - while (unusedId in usedIds) { - unusedId++ - } - return unusedId.also { usedIds += it } - } - - private fun handleId(value: Any): Int { - return objectToModelCache[value]?.let { (it as? UtReferenceModel)?.id } ?: computeUnusedIdAndUpdate() - } - - /** - * Constructs a UtModel from a concrete [value] with a specific [classId]. The result can be a [UtAssembleModel] - * as well. - * - * Handles cache on stateBefore values. - */ - override fun construct(value: Any?, classId: ClassId): UtModel = - when (value) { - null -> UtNullModel(classId) - is Unit -> UtVoidModel - is Byte, - is Short, - is Char, - is Int, - is Long, - is Float, - is Double, - is Boolean -> if (classId.isPrimitive) UtPrimitiveModel(value) else constructFromAny(value) - is ByteArray -> constructFromByteArray(value) - is ShortArray -> constructFromShortArray(value) - is CharArray -> constructFromCharArray(value) - is IntArray -> constructFromIntArray(value) - is LongArray -> constructFromLongArray(value) - is FloatArray -> constructFromFloatArray(value) - is DoubleArray -> constructFromDoubleArray(value) - is BooleanArray -> constructFromBooleanArray(value) - is Array<*> -> constructFromArray(value) - is Enum<*> -> constructFromEnum(value) - is Class<*> -> constructFromClass(value) - else -> constructFromAny(value) - } - - // Q: Is there a way to get rid of duplicated code? - - private fun constructFromDoubleArray(array: DoubleArray): UtModel = - constructedObjects.getOrElse(array) { - val stores = mutableMapOf() - val utModel = - UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0.toDouble()), stores) - constructedObjects[array] = utModel - array.forEachIndexed { idx, value -> - stores[idx] = construct(value, doubleClassId) - } - utModel - } - - private fun constructFromFloatArray(array: FloatArray): UtModel = - constructedObjects.getOrElse(array) { - val stores = mutableMapOf() - val utModel = - UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0.toFloat()), stores) - constructedObjects[array] = utModel - array.forEachIndexed { idx, value -> - stores[idx] = construct(value, floatClassId) - } - utModel - } - - private fun constructFromLongArray(array: LongArray): UtModel = - constructedObjects.getOrElse(array) { - val stores = mutableMapOf() - val utModel = - UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0.toLong()), stores) - constructedObjects[array] = utModel - array.forEachIndexed { idx, value -> - stores[idx] = construct(value, longClassId) - } - utModel - } - - private fun constructFromIntArray(array: IntArray): UtModel = - constructedObjects.getOrElse(array) { - val stores = mutableMapOf() - val utModel = UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0), stores) - constructedObjects[array] = utModel - array.forEachIndexed { idx, value -> - stores[idx] = construct(value, intClassId) - } - utModel - } - - private fun constructFromCharArray(array: CharArray): UtModel = - constructedObjects.getOrElse(array) { - val stores = mutableMapOf() - val utModel = - UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0.toChar()), stores) - constructedObjects[array] = utModel - array.forEachIndexed { idx, value -> - stores[idx] = construct(value, charClassId) - } - utModel - } - - private fun constructFromShortArray(array: ShortArray): UtModel = - constructedObjects.getOrElse(array) { - val stores = mutableMapOf() - val utModel = - UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0.toShort()), stores) - constructedObjects[array] = utModel - array.forEachIndexed { idx, value -> - stores[idx] = construct(value, shortClassId) - } - utModel - } - - private fun constructFromByteArray(array: ByteArray): UtModel = - constructedObjects.getOrElse(array) { - val stores = mutableMapOf() - val utModel = - UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0.toByte()), stores) - constructedObjects[array] = utModel - array.forEachIndexed { idx, value -> - stores[idx] = construct(value, byteClassId) - } - utModel - } - - private fun constructFromBooleanArray(array: BooleanArray): UtModel = - constructedObjects.getOrElse(array) { - val stores = mutableMapOf() - val utModel = - UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(false), stores) - constructedObjects[array] = utModel - array.forEachIndexed { idx, value -> - stores[idx] = construct(value, booleanClassId) - } - utModel - } - - private fun constructFromArray(array: Array<*>): UtModel = - constructedObjects.getOrElse(array) { - val stores = mutableMapOf() - val utModel = - UtArrayModel(handleId(array), array::class.java.id, array.size, UtNullModel(objectClassId), stores) - constructedObjects[array] = utModel - array.forEachIndexed { idx, value -> - stores[idx] = construct(value, objectClassId) - } - utModel - } - - private fun constructFromEnum(enum: Enum<*>): UtModel = - constructedObjects.getOrElse(enum) { - val utModel = UtEnumConstantModel(enum::class.java.id, enum) - constructedObjects[enum] = utModel - utModel - } - - private fun constructFromClass(clazz: Class<*>): UtModel = - constructedObjects.getOrElse(clazz) { - val utModel = UtClassRefModel(clazz::class.java.id, clazz) - System.err.println("ClassRef: $clazz \t\tClassloader: ${clazz.classLoader}") - constructedObjects[clazz] = utModel - utModel - } - - /** - * First tries to construct UtAssembleModel. If failure, constructs UtCompositeModel. - */ - private fun constructFromAny(value: Any): UtModel = - constructedObjects.getOrElse(value) { - tryConstructUtAssembleModel(value) ?: constructCompositeModel(value) - } - - /** - * Constructs UtAssembleModel but does it only for predefined list of classes. - * - * Uses runtime class of an object. - */ - private fun tryConstructUtAssembleModel(value: Any): UtModel? = - findUtAssembleModelConstructor(value::class.java.id)?.let { assembleConstructor -> - try { - assembleConstructor.constructAssembleModel(this, value, valueToClassId(value), handleId(value)) { - constructedObjects[value] = it - } - } catch (e: Exception) { // If UtAssembleModel constructor failed, we need to remove model and return null - constructedObjects.remove(value) - null - } - } - - /** - * Constructs UtCompositeModel. - * - * Uses runtime javaClass to collect ALL fields, except final static fields, and builds this model recursively. - */ - private fun constructCompositeModel(value: Any): UtModel { - // value can be mock only if it was previously constructed from UtCompositeModel - val isMock = objectToModelCache[value]?.isMockModel() ?: false - - val javaClazz = if (isMock) objectToModelCache.getValue(value).classId.jClass else value::class.java - if (!compositeModelStrategy.shouldConstruct(value, javaClazz)) { - return UtCompositeModel( - handleId(value), - javaClazz.id, - isMock, - fields = mutableMapOf() // we don't want to construct any further fields. - ) - } - - val fields = mutableMapOf() - val utModel = UtCompositeModel(handleId(value), javaClazz.id, isMock, fields) - constructedObjects[value] = utModel - generateSequence(javaClazz) { it.superclass }.forEach { clazz -> - val allFields = clazz.declaredFields - allFields - .filter { !(Modifier.isFinal(it.modifiers) && Modifier.isStatic(it.modifiers)) } // TODO: what about static final fields? - .forEach { it.withAccessibility { fields[it.fieldId] = construct(it.get(value), it.type.id) } } - } - return utModel - } -} - -/** - * Decides, should we construct a UtCompositeModel from a value or not. - */ -internal interface UtCompositeModelStrategy { - fun shouldConstruct(value: Any, clazz: Class<*>): Boolean -} - -internal object AlwaysConstructStrategy : UtCompositeModelStrategy { - override fun shouldConstruct(value: Any, clazz: Class<*>): Boolean = true -} - -/** - * This class constructs only user classes or values which are already in [objectToModelCache]. - * - * [objectToModelCache] is a cache which we build in the time of creating concrete values from [UtModel]s. - */ -internal class ConstructOnlyUserClassesOrCachedObjectsStrategy( - private val userDependencyPaths: Set, - private val objectToModelCache: IdentityHashMap -) : UtCompositeModelStrategy { - /** - * Check whether [clazz] is a user class or [value] is in cache. - */ - override fun shouldConstruct(value: Any, clazz: Class<*>): Boolean = - isUserClass(clazz) || value in objectToModelCache - - private fun isUserClass(clazz: Class<*>): Boolean = - clazz.protectionDomain.codeSource?.let { it.location.path.asPathToFile() in userDependencyPaths } ?: false - -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/ApplicationContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/ApplicationContext.kt new file mode 100644 index 0000000000..8a17675ee5 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/ApplicationContext.kt @@ -0,0 +1,17 @@ +package org.utbot.framework.context + +import org.utbot.framework.codegen.generator.AbstractCodeGenerator +import org.utbot.framework.codegen.generator.CodeGeneratorParams + +interface ApplicationContext { + val mockerContext: MockerContext + val typeReplacer: TypeReplacer + val nonNullSpeculator: NonNullSpeculator + + fun createConcreteExecutionContext( + fullClasspath: String, + classpathWithoutDependencies: String + ): ConcreteExecutionContext + + fun createCodeGenerator(params: CodeGeneratorParams): AbstractCodeGenerator +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/ConcreteExecutionContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/ConcreteExecutionContext.kt new file mode 100644 index 0000000000..e0d7c360cf --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/ConcreteExecutionContext.kt @@ -0,0 +1,41 @@ +package org.utbot.framework.context + +import org.utbot.engine.MockStrategy +import org.utbot.framework.fuzzer.IdentityPreservingIdGenerator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConcreteContextLoadingResult +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation + +interface ConcreteExecutionContext { + val instrumentationFactory: UtExecutionInstrumentation.Factory<*> + + fun loadContext( + concreteExecutor: ConcreteExecutor, + ): ConcreteContextLoadingResult + + fun transformExecutionsBeforeMinimization( + executions: List, + methodUnderTest: ExecutableId, + ): List + + fun transformExecutionsAfterMinimization( + executions: List, + methodUnderTest: ExecutableId, + rerunExecutor: ConcreteExecutor, + ): List + + fun tryCreateFuzzingContext(params: FuzzingContextParams): JavaFuzzingContext + + data class FuzzingContextParams( + val concreteExecutor: ConcreteExecutor, + val classUnderTest: ClassId, + val idGenerator: IdentityPreservingIdGenerator, + val fuzzingStartTimeMillis: Long, + val fuzzingEndTimeMillis: Long, + val mockStrategy: MockStrategy, + ) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/JavaFuzzingContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/JavaFuzzingContext.kt new file mode 100644 index 0000000000..f383b2c9b6 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/JavaFuzzingContext.kt @@ -0,0 +1,28 @@ +package org.utbot.framework.context + +import org.utbot.framework.fuzzer.IdentityPreservingIdGenerator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult + +interface JavaFuzzingContext { + val classUnderTest: ClassId + val idGenerator: IdentityPreservingIdGenerator + val valueProvider: JavaValueProvider + + fun createStateBefore( + thisInstance: UtModel?, + parameters: List, + statics: Map, + executableToCall: ExecutableId, + ): EnvironmentModels + + fun handleFuzzedConcreteExecutionResult( + methodUnderTest: ExecutableId, + concreteExecutionResult: UtConcreteExecutionResult, + ) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/MockerContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/MockerContext.kt new file mode 100644 index 0000000000..a85f591f5d --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/MockerContext.kt @@ -0,0 +1,13 @@ +package org.utbot.framework.context + +interface MockerContext { + /** + * Shows if we have installed framework dependencies + */ + val mockFrameworkInstalled: Boolean + + /** + * Shows if we have installed static mocking tools + */ + val staticsMockingIsConfigured: Boolean +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/NonNullSpeculator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/NonNullSpeculator.kt new file mode 100644 index 0000000000..9b5c80df46 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/NonNullSpeculator.kt @@ -0,0 +1,28 @@ +package org.utbot.framework.context + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import soot.SootField + +/** + * Checks whether accessing [field] (with a method invocation or field access) speculatively + * cannot produce [NullPointerException] (according to its finality or accessibility). + * + * @see docs/SpeculativeFieldNonNullability.md for more information. + * + * NOTE: methods for both [FieldId] and [SootField] are provided, because for some lambdas + * class names in byte code and in Soot do not match, making conversion between two field + * representations not always possible, which in turn makes us to support both [FieldId] + * and [SootField] to be useful for both fuzzer and symbolic engine respectively. + */ +interface NonNullSpeculator { + fun speculativelyCannotProduceNullPointerException( + field: SootField, + classUnderTest: ClassId, + ): Boolean + + fun speculativelyCannotProduceNullPointerException( + field: FieldId, + classUnderTest: ClassId, + ): Boolean +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/TypeReplacer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/TypeReplacer.kt new file mode 100644 index 0000000000..25aec4d025 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/TypeReplacer.kt @@ -0,0 +1,17 @@ +package org.utbot.framework.context + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.TypeReplacementMode + +interface TypeReplacer { + /** + * Shows if there are any restrictions on type implementors. + */ + val typeReplacementMode: TypeReplacementMode + + /** + * Finds a type to replace the original abstract type + * if it is guided with some additional information. + */ + fun replaceTypeIfNeeded(classId: ClassId): ClassId? +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/CoverageFilteringConcreteExecutionContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/CoverageFilteringConcreteExecutionContext.kt new file mode 100644 index 0000000000..8fa66a3f7f --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/CoverageFilteringConcreteExecutionContext.kt @@ -0,0 +1,99 @@ +package org.utbot.framework.context.custom + +import mu.KotlinLogging +import org.utbot.common.hasOnClasspath +import org.utbot.common.tryLoadClass +import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.util.utContext +import java.io.File +import java.net.URLClassLoader + +/** + * Decorator of [delegateContext] that filters coverage before test set minimization + * (see [transformExecutionsBeforeMinimization]) to avoid generating too many tests that + * only increase coverage of third party libraries. + * + * This implementation: + * - always keeps instructions that are in class under test (even if other rules say otherwise) + * - filters out instructions in classes marked with annotations from [annotationsToIgnoreCoverage] + * - filters out instructions from classes that are not found in `classpathToIncludeCoverageFrom` + * + * Finally, if [keepOriginalCoverageOnEmptyFilteredCoverage] is `true` we restore original coverage + * for executions whose coverage becomes empty after filtering. + */ +class CoverageFilteringConcreteExecutionContext( + private val delegateContext: ConcreteExecutionContext, + classpathToIncludeCoverageFrom: String, + private val annotationsToIgnoreCoverage: Set, + private val keepOriginalCoverageOnEmptyFilteredCoverage: Boolean, +) : ConcreteExecutionContext by delegateContext { + private val urlsToIncludeCoverageFrom = classpathToIncludeCoverageFrom.split(File.pathSeparator) + .map { File(it).toURI().toURL() } + .toTypedArray() + + companion object { + private val logger = KotlinLogging.logger {} + } + + override fun transformExecutionsBeforeMinimization( + executions: List, + methodUnderTest: ExecutableId, + ): List { + @Suppress("NAME_SHADOWING") + val executions = delegateContext.transformExecutionsBeforeMinimization(executions, methodUnderTest) + + val classUnderTestId = methodUnderTest.classId + + val annotationsToIgnoreCoverage = + annotationsToIgnoreCoverage.mapNotNull { utContext.classLoader.tryLoadClass(it.name) } + + val classLoaderToIncludeCoverageFrom = URLClassLoader(urlsToIncludeCoverageFrom, null) + + val classesToIncludeCoverageFromCache = mutableMapOf() + + return executions.map { execution -> + val coverage = execution.coverage ?: return@map execution + + val filteredCoveredInstructions = + coverage.coveredInstructions + .filter { instruction -> + val instrClassName = instruction.className + + classesToIncludeCoverageFromCache.getOrPut(instrClassName) { + instrClassName == classUnderTestId.name || + (classLoaderToIncludeCoverageFrom.hasOnClasspath(instrClassName) && + !hasAnnotations(instrClassName, annotationsToIgnoreCoverage)) + } + } + .ifEmpty { + if (keepOriginalCoverageOnEmptyFilteredCoverage) { + logger.warn("Execution covered instruction list became empty. Proceeding with not filtered instruction list.") + coverage.coveredInstructions + } else { + logger.warn("Execution covered instruction list became empty. Proceeding with empty coverage.") + emptyList() + } + } + + execution.copy( + coverage = coverage.copy( + coveredInstructions = filteredCoveredInstructions + ) + ) + } + } + + private fun hasAnnotations(className: String, annotations: List>): Boolean = + utContext + .classLoader + .loadClass(className) + .annotations + .any { existingAnnotation -> + annotations.any { annotation -> + annotation.isInstance(existingAnnotation) + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/MockingJavaFuzzingContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/MockingJavaFuzzingContext.kt new file mode 100644 index 0000000000..1be43981ef --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/MockingJavaFuzzingContext.kt @@ -0,0 +1,58 @@ +package org.utbot.framework.context.custom + +import org.utbot.framework.context.JavaFuzzingContext +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.providers.AnyDepthNullValueProvider +import org.utbot.fuzzing.providers.AnyObjectValueProvider +import org.utbot.fuzzing.spring.unit.MockValueProvider +import org.utbot.fuzzing.spring.decorators.filterSeeds +import org.utbot.fuzzing.spring.decorators.filterTypes +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult + +/** + * Makes fuzzer to use mocks in accordance with [mockPredicate]. + * + * NOTE: + * - fuzzer won't mock types, that have *specific* value providers + * (i.e. ones that do not implement [AnyObjectValueProvider]) + * - fuzzer may still resort to mocks despite [mockPredicate] and *specific* + * value providers if it can't create other non-null values or at runtime + */ +fun JavaFuzzingContext.useMocks(mockPredicate: (FuzzedType) -> Boolean) = + MockingJavaFuzzingContext(delegateContext = this, mockPredicate) + +class MockingJavaFuzzingContext( + val delegateContext: JavaFuzzingContext, + val mockPredicate: (FuzzedType) -> Boolean, +) : JavaFuzzingContext by delegateContext { + private val mockValueProvider = MockValueProvider(delegateContext.idGenerator) + + override val valueProvider: JavaValueProvider = + + delegateContext.valueProvider + // NOTE: we first remove `AnyObjectValueProvider` and `NullValueProvider` from `delegateContext.valueProvider` + // and then add them back as a part of our `withFallback` so they have the same priority as + // `mockValueProvider`, otherwise mocks will never be used where `null` or new object can be used. + .except { it is AnyObjectValueProvider } + .withFallback( + mockValueProvider.filterTypes(mockPredicate) + .with( + delegateContext.valueProvider + .filterTypes { !mockPredicate(it) } + .filterSeeds { (it as? Seed.Simple)?.value?.model !is UtNullModel } + ) + .withFallback(mockValueProvider.with(AnyDepthNullValueProvider)) + ) + + override fun handleFuzzedConcreteExecutionResult( + methodUnderTest: ExecutableId, + concreteExecutionResult: UtConcreteExecutionResult + ) { + delegateContext.handleFuzzedConcreteExecutionResult(methodUnderTest, concreteExecutionResult) + mockValueProvider.addMockingCandidates(concreteExecutionResult.detectedMockingCandidates) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/RerunningConcreteExecutionContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/RerunningConcreteExecutionContext.kt new file mode 100644 index 0000000000..55feae324f --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/custom/RerunningConcreteExecutionContext.kt @@ -0,0 +1,63 @@ +package org.utbot.framework.context.custom + +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +import org.utbot.engine.executeConcretely +import org.utbot.framework.UtSettings +import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation + +class RerunningConcreteExecutionContext( + private val delegateContext: ConcreteExecutionContext, + private val maxRerunsPerMethod: Int, + private val rerunTimeoutInMillis: Long = 10L * UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis, +) : ConcreteExecutionContext by delegateContext { + companion object { + private val logger = KotlinLogging.logger {} + } + + override fun transformExecutionsAfterMinimization( + executions: List, + methodUnderTest: ExecutableId, + rerunExecutor: ConcreteExecutor, + ): List { + @Suppress("NAME_SHADOWING") + val executions = delegateContext.transformExecutionsAfterMinimization( + executions, + methodUnderTest, + rerunExecutor + ) + // it's better to rerun executions with non-empty coverage, + // because executions with empty coverage are often duplicated + .sortedBy { it.coverage?.coveredInstructions.isNullOrEmpty() } + return executions + .take(maxRerunsPerMethod) + .map { execution -> + runBlocking { + val result = try { + rerunExecutor.executeConcretely( + methodUnderTest = methodUnderTest, + stateBefore = execution.stateBefore, + instrumentation = emptyList(), + timeoutInMillis = rerunTimeoutInMillis, + isRerun = true, + ) + } catch (e: Throwable) { + // we can't update execution result if we don't have a result + logger.warn(e) { "Rerun failed, keeping original result for execution [$execution]" } + return@runBlocking execution + } + execution.copy( + stateBefore = result.stateBefore, + stateAfter = result.stateAfter, + result = result.result, + coverage = result.coverage, + ) + } + } + executions.drop(maxRerunsPerMethod) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt new file mode 100644 index 0000000000..3f54edd968 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleApplicationContext.kt @@ -0,0 +1,30 @@ +package org.utbot.framework.context.simple + +import org.utbot.framework.codegen.generator.AbstractCodeGenerator +import org.utbot.framework.codegen.generator.CodeGenerator +import org.utbot.framework.codegen.generator.CodeGeneratorParams +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.context.MockerContext +import org.utbot.framework.context.NonNullSpeculator +import org.utbot.framework.context.TypeReplacer + +/** + * A context to use when no specific data is required. + */ +class SimpleApplicationContext( + override val mockerContext: MockerContext = SimpleMockerContext( + mockFrameworkInstalled = true, + staticsMockingIsConfigured = true + ), + override val typeReplacer: TypeReplacer = SimpleTypeReplacer(), + override val nonNullSpeculator: NonNullSpeculator = SimpleNonNullSpeculator() +) : ApplicationContext { + override fun createConcreteExecutionContext( + fullClasspath: String, + classpathWithoutDependencies: String + ): ConcreteExecutionContext = SimpleConcreteExecutionContext(fullClasspath) + + override fun createCodeGenerator(params: CodeGeneratorParams): AbstractCodeGenerator = + CodeGenerator(params) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleConcreteExecutionContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleConcreteExecutionContext.kt new file mode 100644 index 0000000000..6f074d50f4 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleConcreteExecutionContext.kt @@ -0,0 +1,36 @@ +package org.utbot.framework.context.simple + +import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.context.ConcreteExecutionContext.FuzzingContextParams +import org.utbot.framework.context.JavaFuzzingContext +import org.utbot.framework.plugin.api.ConcreteContextLoadingResult +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.instrumentation.execution.SimpleUtExecutionInstrumentation +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation +import java.io.File + +class SimpleConcreteExecutionContext(fullClassPath: String) : ConcreteExecutionContext { + override val instrumentationFactory: UtExecutionInstrumentation.Factory<*> = + SimpleUtExecutionInstrumentation.Factory(fullClassPath.split(File.pathSeparator).toSet()) + + override fun loadContext( + concreteExecutor: ConcreteExecutor, + ): ConcreteContextLoadingResult = ConcreteContextLoadingResult.successWithoutExceptions() + + override fun transformExecutionsBeforeMinimization( + executions: List, + methodUnderTest: ExecutableId, + ): List = executions + + override fun transformExecutionsAfterMinimization( + executions: List, + methodUnderTest: ExecutableId, + rerunExecutor: ConcreteExecutor, + ): List = executions + + override fun tryCreateFuzzingContext(params: FuzzingContextParams): JavaFuzzingContext = + SimpleJavaFuzzingContext(params.classUnderTest, params.idGenerator) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleJavaFuzzingContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleJavaFuzzingContext.kt new file mode 100644 index 0000000000..fefed3bd7b --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleJavaFuzzingContext.kt @@ -0,0 +1,38 @@ +package org.utbot.framework.context.simple + +import org.utbot.framework.context.JavaFuzzingContext +import org.utbot.framework.fuzzer.IdentityPreservingIdGenerator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.defaultValueProviders +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult + +class SimpleJavaFuzzingContext( + override val classUnderTest: ClassId, + override val idGenerator: IdentityPreservingIdGenerator, +) : JavaFuzzingContext { + override val valueProvider: JavaValueProvider = + ValueProvider.of(defaultValueProviders(idGenerator)) + + override fun createStateBefore( + thisInstance: UtModel?, + parameters: List, + statics: Map, + executableToCall: ExecutableId, + ): EnvironmentModels = EnvironmentModels( + thisInstance = thisInstance, + parameters = parameters, + statics = statics, + executableToCall = executableToCall + ) + + override fun handleFuzzedConcreteExecutionResult( + methodUnderTest: ExecutableId, + concreteExecutionResult: UtConcreteExecutionResult + ) = Unit +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleMockerContext.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleMockerContext.kt new file mode 100644 index 0000000000..e7b0977d9e --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleMockerContext.kt @@ -0,0 +1,16 @@ +package org.utbot.framework.context.simple + +import org.utbot.framework.context.MockerContext + +class SimpleMockerContext( + override val mockFrameworkInstalled: Boolean, + staticsMockingIsConfigured: Boolean +) : MockerContext { + /** + * NOTE: Can only be `true` when [mockFrameworkInstalled], because + * situation when mock framework is not installed but static mocking + * is configured is semantically incorrect. + */ + override val staticsMockingIsConfigured: Boolean = + mockFrameworkInstalled && staticsMockingIsConfigured +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleNonNullSpeculator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleNonNullSpeculator.kt new file mode 100644 index 0000000000..5a0288fc01 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleNonNullSpeculator.kt @@ -0,0 +1,28 @@ +package org.utbot.framework.context.simple + +import org.utbot.framework.UtSettings +import org.utbot.framework.context.NonNullSpeculator +import org.utbot.framework.isFromTrustedLibrary +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.util.isFinal +import org.utbot.framework.plugin.api.util.isPublic +import soot.SootField + +class SimpleNonNullSpeculator : NonNullSpeculator { + override fun speculativelyCannotProduceNullPointerException( + field: SootField, + classUnderTest: ClassId, + ): Boolean = + !UtSettings.maximizeCoverageUsingReflection && + field.declaringClass.isFromTrustedLibrary() && + (field.isFinal || !field.isPublic) + + override fun speculativelyCannotProduceNullPointerException( + field: FieldId, + classUnderTest: ClassId + ): Boolean = + !UtSettings.maximizeCoverageUsingReflection && + field.declaringClass.isFromTrustedLibrary() && + (field.isFinal || !field.isPublic) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleTypeReplacer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleTypeReplacer.kt new file mode 100644 index 0000000000..3df1f5930e --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/simple/SimpleTypeReplacer.kt @@ -0,0 +1,11 @@ +package org.utbot.framework.context.simple + +import org.utbot.framework.context.TypeReplacer +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.TypeReplacementMode + +class SimpleTypeReplacer : TypeReplacer { + override val typeReplacementMode: TypeReplacementMode = TypeReplacementMode.AnyImplementor + + override fun replaceTypeIfNeeded(classId: ClassId): ClassId? = null +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ApplicationContextUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ApplicationContextUtils.kt new file mode 100644 index 0000000000..607015e059 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ApplicationContextUtils.kt @@ -0,0 +1,23 @@ +package org.utbot.framework.context.utils + +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.context.ConcreteExecutionContext.FuzzingContextParams +import org.utbot.fuzzing.JavaValueProvider + +fun ApplicationContext.transformConcreteExecutionContext( + transformer: (ConcreteExecutionContext) -> ConcreteExecutionContext +) = object : ApplicationContext by this { + override fun createConcreteExecutionContext( + fullClasspath: String, + classpathWithoutDependencies: String + ): ConcreteExecutionContext = transformer( + this@transformConcreteExecutionContext.createConcreteExecutionContext( + fullClasspath, classpathWithoutDependencies + ) + ) +} + +fun ApplicationContext.transformValueProvider( + transformer: FuzzingContextParams.(JavaValueProvider) -> JavaValueProvider +) = transformConcreteExecutionContext { it.transformValueProvider(transformer) } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ConcreteExecutionContextUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ConcreteExecutionContextUtils.kt new file mode 100644 index 0000000000..a2ff6a751c --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/ConcreteExecutionContextUtils.kt @@ -0,0 +1,28 @@ +package org.utbot.framework.context.utils + +import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.context.ConcreteExecutionContext.FuzzingContextParams +import org.utbot.framework.context.JavaFuzzingContext +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation + +fun ConcreteExecutionContext.transformInstrumentationFactory( + transformer: (UtExecutionInstrumentation.Factory<*>) -> UtExecutionInstrumentation.Factory<*> +) = object : ConcreteExecutionContext by this { + override val instrumentationFactory: UtExecutionInstrumentation.Factory<*> = + transformer(this@transformInstrumentationFactory.instrumentationFactory) +} + +fun ConcreteExecutionContext.transformJavaFuzzingContext( + transformer: FuzzingContextParams.(JavaFuzzingContext) -> JavaFuzzingContext +) = object : ConcreteExecutionContext by this { + override fun tryCreateFuzzingContext(params: FuzzingContextParams): JavaFuzzingContext = params.transformer( + this@transformJavaFuzzingContext.tryCreateFuzzingContext(params) + ) +} + +fun ConcreteExecutionContext.transformValueProvider( + transformer: FuzzingContextParams.(JavaValueProvider) -> JavaValueProvider +) = transformJavaFuzzingContext { javaFuzzingContext -> + javaFuzzingContext.transformValueProvider { valueProvider -> transformer(valueProvider) } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/JavaFuzzingContextUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/JavaFuzzingContextUtils.kt new file mode 100644 index 0000000000..4f4cf0714d --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/context/utils/JavaFuzzingContextUtils.kt @@ -0,0 +1,15 @@ +package org.utbot.framework.context.utils + +import org.utbot.framework.context.JavaFuzzingContext +import org.utbot.fuzzing.JavaValueProvider + +fun JavaFuzzingContext.transformValueProvider( + transformer: (JavaValueProvider) -> JavaValueProvider +) = object : JavaFuzzingContext by this { + override val valueProvider: JavaValueProvider = + transformer(this@transformValueProvider.valueProvider) +} + +fun JavaFuzzingContext.withValueProvider( + valueProvider: JavaValueProvider +) = transformValueProvider { it.with(valueProvider) } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/coverage/CoverageCalculator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/coverage/CoverageCalculator.kt index d7c61de465..c662da9b31 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/coverage/CoverageCalculator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/coverage/CoverageCalculator.kt @@ -1,192 +1,39 @@ package org.utbot.framework.coverage -import org.utbot.framework.plugin.api.UtMethod +import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.UtValueExecution -import org.utbot.framework.plugin.api.UtValueTestCase -import org.utbot.framework.plugin.api.util.signature -import org.utbot.framework.util.anyInstance +import org.utbot.framework.plugin.api.util.jClass import org.utbot.instrumentation.ConcreteExecutor -import org.utbot.instrumentation.execute import org.utbot.instrumentation.instrumentation.coverage.CoverageInfo import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation import org.utbot.instrumentation.instrumentation.coverage.collectCoverage import org.utbot.instrumentation.util.StaticEnvironment -import java.io.InputStream -import java.lang.reflect.InvocationTargetException -import kotlin.reflect.KClass -import kotlin.reflect.full.createInstance -import kotlin.reflect.full.declaredFunctions -import kotlin.reflect.full.instanceParameter -import org.jacoco.core.analysis.Analyzer -import org.jacoco.core.analysis.CoverageBuilder -import org.jacoco.core.analysis.IClassCoverage -import org.jacoco.core.analysis.ICounter -import org.jacoco.core.analysis.IMethodCoverage -import org.jacoco.core.data.ExecutionDataStore -import org.jacoco.core.data.SessionInfoStore -import org.jacoco.core.instr.Instrumenter -import org.jacoco.core.runtime.IRuntime -import org.jacoco.core.runtime.LoggerRuntime -import org.jacoco.core.runtime.RuntimeData +import kotlinx.coroutines.runBlocking -fun instrument(clazz: KClass<*>, instrumenter: Instrumenter): ByteArray = - clazz.asInputStream().use { - instrumenter.instrument(it, clazz.qualifiedName) - } - -fun calculateClassCoverage(targetClass: KClass<*>, testClass: KClass<*>): Coverage { - val targetName = targetClass.qualifiedName!! - val testClassName = testClass.qualifiedName!! - - // IRuntime instance to collect execution data - val runtime: IRuntime = LoggerRuntime() - - // create a modified version of target class with probes - val instrumenter = Instrumenter(runtime) - val instrumentedTarget = instrument(targetClass, instrumenter) - val instrumentedTestClass = instrument(testClass, instrumenter) - - // startup the runtime - val data = RuntimeData() - runtime.startup(data) - - // load class from byte[] instances - val memoryClassLoader = MemoryClassLoader() - memoryClassLoader.addDefinition(targetName, instrumentedTarget) - memoryClassLoader.addDefinition(testClassName, instrumentedTestClass) - - val instrumentedTests = memoryClassLoader.loadClass(testClassName).kotlin - - val tests = instrumentedTests.declaredFunctions - val testClassInstance = instrumentedTests.createInstance() - tests.forEach { - it.call(testClassInstance) - } - - // shutdown the runtime - val executionData = ExecutionDataStore() - val sessionInfos = SessionInfoStore() - data.collect(executionData, sessionInfos, false) - runtime.shutdown() - - // get coverage builder - val coverageBuilder = CoverageBuilder().apply { - val analyzer = Analyzer(executionData, this) - targetClass.asInputStream().use { - analyzer.analyzeClass(it, targetName) - } - } - - val methodsCoverage = coverageBuilder.classes - .single { it.qualifiedName == targetName } - .methods - .map { it.toMethodCoverage() } - - return methodsCoverage.toClassCoverage() -} - -fun calculateCoverage(clazz: KClass<*>, block: (KClass<*>) -> Unit): CoverageBuilder { - val targetName = clazz.qualifiedName!! - - // IRuntime instance to collect execution data - val runtime: IRuntime = LoggerRuntime() - - // create a modified version of target class with probes - val instrumenter = Instrumenter(runtime) - val instrumented = instrument(clazz, instrumenter) - - // startup the runtime - val data = RuntimeData() - runtime.startup(data) - - // load class from byte[] instances - val memoryClassLoader = MemoryClassLoader() - memoryClassLoader.addDefinition(targetName, instrumented) - val targetClass = memoryClassLoader.loadClass(targetName).kotlin - - // execute code - block(targetClass) - - // shutdown the runtime - val executionData = ExecutionDataStore() - val sessionInfos = SessionInfoStore() - data.collect(executionData, sessionInfos, false) - runtime.shutdown() - - // calculate coverage - return CoverageBuilder().apply { - val analyzer = Analyzer(executionData, this) - clazz.asInputStream().use { - analyzer.analyzeClass(it, targetName) - } - } -} - -fun KClass<*>.asInputStream(): InputStream = - java.getResourceAsStream("/${qualifiedName!!.replace('.', '/')}.class")!! - -class MemoryClassLoader : ClassLoader() { - private val definitions: MutableMap = HashMap() - - fun addDefinition(name: String, bytes: ByteArray) { - definitions[name] = bytes - } - - override fun loadClass(name: String, resolve: Boolean): Class<*> { - val bytes = definitions[name] - return if (bytes != null) { - defineClass(name, bytes, 0, bytes.size) - } else super.loadClass(name, resolve) - } -} - -fun classCoverage(testCases: List>): Coverage = - testCases.map { methodCoverageWithJaCoCo(it.method, it.executions) }.toClassCoverage() - -fun methodCoverageWithJaCoCo(utMethod: UtMethod<*>, executions: List>): Coverage { - val methodSignature = utMethod.callable.signature - val coverage = calculateCoverage(utMethod.clazz) { clazz -> - val method = clazz.declaredFunctions.single { it.signature == methodSignature } - val onInstance = method.instanceParameter != null - for (execution in executions) { - try { - if (onInstance) { - method.call(clazz.java.anyInstance, *execution.stateBefore.params.map { it.value }.toTypedArray()) - } else { - method.call(*execution.stateBefore.params.map { it.value }.toTypedArray()) - } - } catch (_: InvocationTargetException) { - } - } - } - val methodCoverage = coverage.classes - .single { it.qualifiedName == utMethod.clazz.qualifiedName } - .methods - .single { - "${it.name}${it.desc}" == methodSignature - } - - return methodCoverage.toMethodCoverage() -} - -fun methodCoverage(utMethod: UtMethod<*>, executions: List>, classpath: String): Coverage { - val methodSignature = utMethod.callable.signature - val clazz = utMethod.clazz - return ConcreteExecutor(CoverageInstrumentation, classpath).let { executor -> - val method = clazz.declaredFunctions.single { it.signature == methodSignature } - val onInstance = method.instanceParameter != null +fun methodCoverage(executable: ExecutableId, executions: List>, classpath: String): Coverage { + val methodSignature = executable.signature + val classId = executable.classId + return ConcreteExecutor(CoverageInstrumentation.Factory, classpath).let { executor -> for (execution in executions) { val args = execution.stateBefore.params.map { it.value }.toMutableList() - if (onInstance) { - args.add(0, clazz.java.anyInstance) + val caller = execution.stateBefore.caller + if (caller != null) { + args.add(0, caller.value) } val staticEnvironment = StaticEnvironment( execution.stateBefore.statics.map { it.key to it.value.value } ) - executor.execute(method, args.toTypedArray(), parameters = staticEnvironment) + runBlocking { + executor.executeAsync( + classId.name, + methodSignature, + args.toTypedArray(), + parameters = staticEnvironment + ) + } } - val coverage = executor.collectCoverage(clazz.java) + val coverage = executor.collectCoverage(classId.jClass) coverage.toMethodCoverage(methodSignature) } } @@ -201,11 +48,6 @@ fun CoverageInfo.toMethodCoverage(methodSignature: String): Coverage { ) } -fun IMethodCoverage.toMethodCoverage(): Coverage = - Coverage(branchCounter.toCounter(), instructionCounter.toCounter(), lineCounter.toCounter()) - -private fun ICounter.toCounter(): Counter = Counter(totalCount, coveredCount, missedCount) - data class Coverage( val branchCounter: Counter = Counter(), val instructionCounter: Counter = Counter(), @@ -214,18 +56,6 @@ data class Coverage( override fun toString() = "(branches: $branchCounter, instructions: $instructionCounter, lines: $lineCounter)" } -fun List.toClassCoverage(): Coverage { - var branchCounter = Counter() - var instructionCounter = Counter() - var lineCounter = Counter() - forEach { - branchCounter += it.branchCounter - instructionCounter += it.instructionCounter - lineCounter += it.lineCounter - } - return Coverage(branchCounter, instructionCounter, lineCounter) -} - operator fun Counter.plus(other: Counter): Counter = Counter( total + other.total, @@ -233,9 +63,6 @@ operator fun Counter.plus(other: Counter): Counter = missed + other.missed ) -private val IClassCoverage.qualifiedName: String - get() = this.name.replace('/', '.') - data class Counter(val total: Int = 0, val covered: Int = 0, val missed: Int = 0) { override fun toString() = "$covered/$total" } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt index 64d6424f7b..a8731dc14d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/fields/ExecutionStateAnalyzer.kt @@ -10,15 +10,22 @@ import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtClassRefModel import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtCustomModel import org.utbot.framework.plugin.api.UtEnumConstantModel import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtModelWithCompositeOrigin import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtSymbolicExecution import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isSubtypeOf import org.utbot.framework.util.UtModelVisitor import org.utbot.framework.util.hasThisInstance +import org.utbot.fuzzer.UtFuzzedExecution class ExecutionStateAnalyzer(val execution: UtExecution) { fun findModifiedFields(): StateModificationInfo { @@ -29,14 +36,24 @@ class ExecutionStateAnalyzer(val execution: UtExecution) { } private fun StateModificationInfo.analyzeThisInstance(): StateModificationInfo { - if (!execution.hasThisInstance()) { - return this + when (execution) { + is UtSymbolicExecution -> { + if (!execution.hasThisInstance()) { + return this + } + val thisInstanceBefore = execution.stateBefore.thisInstance!! + val thisInstanceAfter = execution.stateAfter.thisInstance!! + val info = analyzeModelStates(thisInstanceBefore, thisInstanceAfter) + val modifiedFields = getModifiedFields(info) + return this.copy(thisInstance = modifiedFields) + } + is UtFuzzedExecution -> { + return this + } + else -> { + return this + } } - val thisInstanceBefore = execution.stateBefore.thisInstance!! - val thisInstanceAfter = execution.stateAfter.thisInstance!! - val info = analyzeModelStates(thisInstanceBefore, thisInstanceAfter) - val modifiedFields = getModifiedFields(info) - return this.copy(thisInstance = modifiedFields) } private fun StateModificationInfo.analyzeParameters(): StateModificationInfo { @@ -52,25 +69,35 @@ class ExecutionStateAnalyzer(val execution: UtExecution) { } private fun StateModificationInfo.analyzeStatics(): StateModificationInfo { - if (execution.stateAfter == MissingState) return this - - val staticsBefore = execution.stateBefore.statics - val staticsAfter = execution.stateAfter.statics - - val staticFieldsByClass = execution.staticFields.groupBy { it.declaringClass } - val modificationsByClass = mutableMapOf() - for ((classId, fields) in staticFieldsByClass) { - val staticFieldModifications = mutableListOf() - for (field in fields) { - val before = staticsBefore[field]!! - val after = staticsAfter[field]!! - val path = FieldPath() + FieldAccess(field) - val info = analyzeModelStates(before, after, path) - staticFieldModifications += getModifiedFields(info) + when (execution) { + is UtSymbolicExecution -> { + if (execution.stateAfter == MissingState) return this + + val staticsBefore = execution.stateBefore.statics + val staticsAfter = execution.stateAfter.statics + + val staticFieldsByClass = execution.staticFields.groupBy { it.declaringClass } + val modificationsByClass = mutableMapOf() + for ((classId, fields) in staticFieldsByClass) { + val staticFieldModifications = mutableListOf() + for (field in fields) { + val before = staticsBefore[field]!! + val after = staticsAfter[field]!! + val path = FieldPath() + FieldAccess(field) + val info = analyzeModelStates(before, after, path) + staticFieldModifications += getModifiedFields(info) + } + modificationsByClass[classId] = staticFieldModifications + } + return this.copy(staticFields = modificationsByClass) + } + is UtFuzzedExecution -> { + return this + } + else -> { + return this } - modificationsByClass[classId] = staticFieldModifications } - return this.copy(staticFields = modificationsByClass) } private fun analyzeModelStates( @@ -79,31 +106,23 @@ class ExecutionStateAnalyzer(val execution: UtExecution) { initialPath: FieldPath = FieldPath() ): FieldStatesInfo { var modelBefore = before + var modelAfter = after if (before::class != after::class) { - if (before is UtAssembleModel && after is UtCompositeModel && before.origin != null) { - modelBefore = before.origin ?: unreachableBranch("We have already checked the origin for a null value") - } else { - doNotRun { - // it is ok because we might have modelBefore with some absent fields (i.e. statics), but - // modelAfter (constructed by concrete executor) will consist all these fields, - // therefore, AssembleModelGenerator won't be able to transform the given composite model - - val reason = if (before is UtAssembleModel && after is UtCompositeModel) { - "ModelBefore is an AssembleModel and ModelAfter " + - "is a CompositeModel, but modelBefore doesn't have an origin model." - } else { - "The model before and the model after have different types: " + - "model before is ${before::class}, but model after is ${after::class}." - } + if (before is UtModelWithCompositeOrigin) + modelBefore = before.origin ?: before + if (after is UtModelWithCompositeOrigin) + modelAfter = after.origin ?: after + } - error("Cannot analyze fields modification. $reason") - } + if (modelBefore::class != modelAfter::class) { + doNotRun { + error("Cannot analyze model fields modification, before: [$before], after: [$after]") + } - // remove it when we will fix assemble models in the resolver JIRA:1464 - workaround(WorkaroundReason.IGNORE_MODEL_TYPES_INEQUALITY) { - return FieldStatesInfo(fieldsBefore = emptyMap(), fieldsAfter = emptyMap()) - } + // remove it when we will fix assemble models in the resolver JIRA:1464 + workaround(WorkaroundReason.IGNORE_MODEL_TYPES_INEQUALITY) { + return FieldStatesInfo(fieldsBefore = emptyMap(), fieldsAfter = emptyMap()) } } @@ -114,7 +133,7 @@ class ExecutionStateAnalyzer(val execution: UtExecution) { modelBefore.accept(FieldStateVisitor(), dataBefore) val dataAfter = FieldData(FieldsVisitorMode.AFTER, fieldsAfter, initialPath, previousFields = fieldsBefore) - after.accept(FieldStateVisitor(), dataAfter) + modelAfter.accept(FieldStateVisitor(), dataAfter) return FieldStatesInfo(fieldsBefore, fieldsAfter) } @@ -160,23 +179,23 @@ private class FieldStateVisitor : UtModelVisitor() { ) } - override fun visit(element: UtClassRefModel, data: FieldData) { + override fun visit(element: UtNullModel, data: FieldData) { recordFieldState(data, element) } - override fun visit(element: UtEnumConstantModel, data: FieldData) { + override fun visit(element: UtPrimitiveModel, data: FieldData) { recordFieldState(data, element) } - override fun visit(element: UtNullModel, data: FieldData) { + override fun visit(element: UtVoidModel, data: FieldData) { recordFieldState(data, element) } - override fun visit(element: UtPrimitiveModel, data: FieldData) { + override fun visit(element: UtClassRefModel, data: FieldData) { recordFieldState(data, element) } - override fun visit(element: UtVoidModel, data: FieldData) { + override fun visit(element: UtEnumConstantModel, data: FieldData) { recordFieldState(data, element) } @@ -210,6 +229,14 @@ private class FieldStateVisitor : UtModelVisitor() { } } + override fun visit(element: UtLambdaModel, data: FieldData) { + recordFieldState(data, element) + } + + override fun visit(element: UtCustomModel, data: FieldData) { + recordFieldState(data, element) + } + private fun recordFieldState(data: FieldData, model: UtModel) { val fields = data.fields val path = data.path @@ -227,6 +254,13 @@ private class FieldStateVisitor : UtModelVisitor() { // sometimes we don't have initial state of the field, e.g. if it is static and we didn't `touch` it // during the analysis, but the concrete executor included it in the modelAfter val initial = previousFields[path] ?: return false + + // TODO usvm-sbft: In USVM descriptors for classes, enums, and throwables don't implement `UTestRefDescriptor` + // and don't have `refId`, which causes `UtReferenceModel.id` to diverge in `stateBefore` and `stateAfter` + if (initial is UtClassRefModel) return initial.value != ((model as? UtClassRefModel)?.value) + if (initial is UtEnumConstantModel) return initial.value != ((model as? UtEnumConstantModel)?.value) + if (initial.classId isSubtypeOf java.lang.Throwable::class.id) return initial.classId != model.classId + return initial != model } } @@ -239,5 +273,7 @@ fun UtModel.accept(visitor: UtModelVisitor, data: D) = visitor.run { is UtPrimitiveModel -> visit(element, data) is UtReferenceModel -> visit(element, data) is UtVoidModel -> visit(element, data) + // PythonModel, JsUtModel may be here + else -> throw UnsupportedOperationException() } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/GreedyEssential.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/GreedyEssential.kt index bbbcafd17f..4f229e3712 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/GreedyEssential.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/GreedyEssential.kt @@ -10,7 +10,8 @@ private inline class LineNumber(val number: Int) * [Greedy essential algorithm](CONFLUENCE:Test+Minimization) */ class GreedyEssential private constructor( - executionToCoveredLines: Map> + executionToCoveredLines: Map>, + executionToPriority: Map ) { private val executionToUsefulLines: Map> = executionToCoveredLines @@ -23,8 +24,11 @@ class GreedyEssential private constructor( .mapValues { it.value.toMutableSet() } private val executionByPriority = - PriorityQueue(compareByDescending> { it.second }.thenBy { it.first.number }) - .apply { + PriorityQueue( + compareBy> { executionToPriority[it.first] } + .thenByDescending { it.second } + .thenBy { it.first.number } + ).apply { addAll( executionToCoveredLines .keys @@ -89,12 +93,16 @@ class GreedyEssential private constructor( * * @return retained execution ids. */ - fun minimize(executions: Map>): List { + fun minimize(executions: Map>, executionToPriority: Map = mapOf()): List { val convertedExecutions = executions .entries .associate { (execution, lines) -> ExecutionNumber(execution) to lines.map { LineNumber(it) } } - val prioritizer = GreedyEssential(convertedExecutions) + val convertedExecutionToPriority = executionToPriority + .entries + .associate { (execution, priority) -> ExecutionNumber(execution) to priority } + + val prioritizer = GreedyEssential(convertedExecutions, convertedExecutionToPriority) val list = mutableListOf() while (prioritizer.hasMore()) { list.add(prioritizer.getExecutionAndRemove()) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt index b1b0eead6c..847dca32e4 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt @@ -1,23 +1,16 @@ package org.utbot.framework.minimization import org.utbot.framework.UtSettings -import org.utbot.framework.plugin.api.EnvironmentModels -import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtClassRefModel -import org.utbot.framework.plugin.api.UtCompositeModel import org.utbot.framework.plugin.api.UtConcreteExecutionFailure -import org.utbot.framework.plugin.api.UtDirectSetFieldModel -import org.utbot.framework.plugin.api.UtEnumConstantModel -import org.utbot.framework.plugin.api.UtExecutableCallModel import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtExecutionFailure import org.utbot.framework.plugin.api.UtExecutionResult -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtStatementModel -import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtSymbolicExecution +import org.utbot.framework.util.calculateSize +import org.utbot.fuzzer.UtFuzzedExecution +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor + /** * Minimizes [executions] in each test suite independently. Test suite is computed with [executionToTestSuite] function. @@ -25,7 +18,8 @@ import org.utbot.framework.plugin.api.UtVoidModel * We have 4 different test suites: * * Regression suite * * Error suite (invocations in which implicitly thrown unchecked exceptions reached to the top) - * * Crash suite (invocations in which the child process crashed or unexpected exception in our code occurred) + * * Crash suite (invocations in which the instrumented process crashed or unexpected exception in our code occurred) + * * Artificial error suite (invocations in which some custom exception like overflow detection occurred) * * Timeout suite * * We want to minimize tests independently in each of these suites. @@ -48,13 +42,31 @@ fun minimizeTestCase( fun minimizeExecutions(executions: List): List { val unknownCoverageExecutions = - executions.indices.filter { executions[it].coverage?.coveredInstructions?.isEmpty() ?: true }.toSet() - // ^^^ here we add executions with empty or null coverage, because it happens only if a concrete execution failed, - // so we don't know the actual coverage for such executions - val mapping = buildMapping(executions.filterIndexed { idx, _ -> idx !in unknownCoverageExecutions }) - val usedExecutionIndexes = (GreedyEssential.minimize(mapping) + unknownCoverageExecutions).toSet() + executions + .filter { it.coverage?.coveredInstructions.isNullOrEmpty() } + .groupBy { + it.result.javaClass to ( + (it.result as? UtExecutionSuccess)?.model ?: (it.result as? UtExecutionFailure)?.exception + )?.javaClass + } + .values + .flatMap { executionsGroup -> + val executionToSize = executionsGroup.associateWith { it.stateBefore.calculateSize() } + executionsGroup + .sortedBy { executionToSize[it] } + .take(UtSettings.maxUnknownCoverageExecutionsPerMethodPerResultType) + } + .toSet() + // Here we add executions with empty or null coverage, because if concrete execution failed, we don't know actual coverage. + // The amount of such executions is limited with [UtSettings.maxUnknownCoverageExecutionsPerMethodPerResultType]. + + val filteredExecutions = filterOutDuplicateCoverages(executions - unknownCoverageExecutions) + val (mapping, executionToPriorityMapping) = buildMapping(filteredExecutions) - val usedMinimizedExecutions = executions.filterIndexed { idx, _ -> idx in usedExecutionIndexes } + val usedFilteredExecutionIndexes = GreedyEssential.minimize(mapping, executionToPriorityMapping).toSet() + val usedFilteredExecutions = filteredExecutions.filterIndexed { idx, _ -> idx in usedFilteredExecutionIndexes } + + val usedMinimizedExecutions = usedFilteredExecutions + unknownCoverageExecutions return if (UtSettings.minimizeCrashExecutions) { usedMinimizedExecutions.filteredCrashExecutions() @@ -63,6 +75,18 @@ fun minimizeExecutions(executions: List): List { } } +private fun filterOutDuplicateCoverages(executions: List): List { + val (executionIdxToCoveredEdgesMap, _) = buildMapping(executions) + return executions + .withIndex() + // we need to group by coveredEdges and not just Coverage to not miss exceptional edges that buildMapping() function adds + .groupBy( + keySelector = { indexedExecution -> executionIdxToCoveredEdgesMap[indexedExecution.index] }, + valueTransform = { indexedExecution -> indexedExecution.value } + ).values + .map { executionsWithEqualCoverage -> executionsWithEqualCoverage.chooseOneExecution() } +} + /** * Groups the [executions] by their `paths` on `first` [branchInstructionsNumber] `branch` instructions. * @@ -84,15 +108,15 @@ private fun groupByBranchInstructions( executions: List, branchInstructionsNumber: Int ): Collection> { - val instructionToPossibleNextInstructions = mutableMapOf>() + val instructionToPossibleNextInstructions = mutableMapOf>() for (execution in executions) { execution.coverage?.let { coverage -> val coveredInstructionIds = coverage.coveredInstructions.map { it.id } - for (i in 0 until coveredInstructionIds.size - 1) { + for (i in coveredInstructionIds.indices) { instructionToPossibleNextInstructions .getOrPut(coveredInstructionIds[i]) { mutableSetOf() } - .add(coveredInstructionIds[i + 1]) + .add(coveredInstructionIds.getOrNull(i + 1)) } } } @@ -108,6 +132,7 @@ private fun groupByBranchInstructions( * 2. {2, 3, 2, 6} * 3. {2, 3, 4, 3} * branch instructions are {2 -> (3, 4, 5, 6), 3 -> (2, 4), 4 -> (2, 3)} + * * we will build these lists representing their behaviour: * 1. {2 -> 3, 3 -> 2} (because of {__2__, __3__, 2, 4, 2, 5}) * 2. {2 -> 3, 3 -> 2} (because of {__2__, __3__, 2, 6}) @@ -140,13 +165,14 @@ private fun groupExecutionsByTestSuite( executions.groupBy { executionToTestSuite(it) }.values /** - * Builds a mapping from execution id to edges id. + * Builds a mapping from execution id to edges id and from execution id to its priority. */ -private fun buildMapping(executions: List): Map> { +private fun buildMapping(executions: List): Pair>, Map> { // (inst1, instr2) -> edge id --- edge represents as a pair of instructions, which are connected by this edge - val allCoveredEdges = mutableMapOf, Int>() + val allCoveredEdges = mutableMapOf, Int>() val thrownExceptions = mutableMapOf() val mapping = mutableMapOf>() + val executionToPriorityMapping = mutableMapOf() executions.forEachIndexed { idx, execution -> @@ -157,20 +183,17 @@ private fun buildMapping(executions: List): Map> { execution.result, thrownExceptions ).let { instructions -> - for (i in 0 until instructions.size - 1) { - allCoveredEdges.putIfAbsent(instructions[i] to instructions[i + 1], allCoveredEdges.size) + val edges = instructions.indices.map { i -> + allCoveredEdges.getOrPut(instructions[i] to instructions.getOrNull(i + 1)) { allCoveredEdges.size } } - val edges = mutableListOf() - for (i in 0 until instructions.size - 1) { - edges += allCoveredEdges[instructions[i] to instructions[i + 1]]!! - } mapping[idx] = edges + executionToPriorityMapping[idx] = execution.getExecutionPriority() } } } - return mapping + return Pair(mapping, executionToPriorityMapping) } /** @@ -185,50 +208,20 @@ private fun List.filteredCrashExecutions(): List { val notCrashExecutions = filterNot { it.result is UtConcreteExecutionFailure } - return notCrashExecutions + crashExecutions.chooseMinimalCrashExecution() -} - -/** - * As for now crash execution can only be produced by Concrete Executor, it does not have [UtExecution.stateAfter] and - * [UtExecution.result] is [UtExecutionFailure], so we check only [UtExecution.stateBefore]. - */ -private fun List.chooseMinimalCrashExecution(): UtExecution = minByOrNull { - it.stateBefore.calculateSize() -} ?: error("Cannot find minimal crash execution within empty executions") - -private fun EnvironmentModels.calculateSize(): Int { - val thisInstanceSize = thisInstance?.calculateSize() ?: 0 - val parametersSize = parameters.sumOf { it.calculateSize() } - val staticsSize = statics.values.sumOf { it.calculateSize() } - - return thisInstanceSize + parametersSize + staticsSize + return notCrashExecutions + crashExecutions.chooseOneExecution() } /** - * We assume that "size" for "common" models is 1, 0 for [UtVoidModel] (as they do not return anything) and - * [UtPrimitiveModel] and [UtNullModel] (we use them as literals in codegen), summarising for all statements for [UtAssembleModel] and - * summarising for all fields and mocks for [UtCompositeModel]. As [UtCompositeModel] could be recursive, we need to - * store it in [used]. Moreover, if we already calculate size for [this], it means that we will use already created - * variable by this model and do not need to create it again, so size should be equal to 0. + * Chooses one execution with the highest [execution priority][getExecutionPriority]. If multiple executions + * have the same priority, then the one with the [smallest][calculateSize] [UtExecution.stateBefore] is chosen. + * + * Only [UtExecution.stateBefore] is considered, because [UtExecution.result] and [UtExecution.stateAfter] + * don't represent true picture as they are limited by [construction depth][UtModelConstructor.maxDepth] and their + * sizes can't be calculated for crushed executions. */ -private fun UtModel.calculateSize(used: MutableSet = mutableSetOf()): Int { - if (this in used) return 0 - - used += this - - return when (this) { - is UtNullModel, is UtPrimitiveModel, UtVoidModel -> 0 - is UtClassRefModel, is UtEnumConstantModel, is UtArrayModel -> 1 - is UtAssembleModel -> 1 + allStatementsChain.sumOf { it.calculateSize(used) } - is UtCompositeModel -> 1 + fields.values.sumOf { it.calculateSize(used) } - } -} - -private fun UtStatementModel.calculateSize(used: MutableSet = mutableSetOf()): Int = - when (this) { - is UtDirectSetFieldModel -> 1 + fieldModel.calculateSize(used) - is UtExecutableCallModel -> 1 + params.sumOf { it.calculateSize(used) } - } +private fun List.chooseOneExecution(): UtExecution = minWithOrNull( + compareBy({ it.getExecutionPriority() }, { it.stateBefore.calculateSize() }) +) ?: error("Cannot find minimal execution within empty executions") /** * Extends the [instructionsWithoutExtra] with one extra instruction if the [result] is @@ -256,4 +249,17 @@ private fun addExtraIfLastInstructionIsException( * Takes an exception name, a class name, a method signature and a line number from exception. */ private fun Throwable.exceptionToInfo(): String = - this::class.java.name + (this.stackTrace.firstOrNull()?.run { className + methodName + lineNumber } ?: "null") \ No newline at end of file + this::class.java.name + (this.stackTrace.firstOrNull()?.run { className + methodName + lineNumber } ?: "null") + +/** + * Returns an execution priority. [UtSymbolicExecution] has the highest priority + * over other executions like [UtFuzzedExecution], [UtFailedExecution], etc. + * + * NOTE! Smaller number represents higher priority. + * + * See [https://github.com/UnitTestBot/UTBotJava/issues/1504] for more details. + */ +private fun UtExecution.getExecutionPriority(): Int = when (this) { + is UtSymbolicExecution -> 0 + else -> 1 +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/AnalysisMode.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/AnalysisMode.kt deleted file mode 100644 index 4b23912483..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/AnalysisMode.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.utbot.framework.modifications - -/** - * Restrictions on demanded modificators - */ -enum class AnalysisMode { - - /** - * Search for all field modificators - */ - AllModificators, - - /** - * Search setters and possible direct accesses - */ - SettersAndDirectAccessors, - - /** - * Search constructors only - */ - Constructors, -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/AnalysisUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/AnalysisUtils.kt deleted file mode 100644 index 0d08ae5c52..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/AnalysisUtils.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.utbot.framework.modifications - -import org.utbot.engine.canRetrieveBody -import org.utbot.engine.jimpleBody -import soot.SootMethod -import soot.jimple.JimpleBody - -/** - * Retrieves Jimple body of SootMethod. - */ -fun retrieveJimpleBody(sootMethod: SootMethod): JimpleBody? = - if (sootMethod.canRetrieveBody()) sootMethod.jimpleBody() else null \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/ConstructorAnalyzer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/ConstructorAnalyzer.kt deleted file mode 100644 index bfc5aa9b66..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/ConstructorAnalyzer.kt +++ /dev/null @@ -1,261 +0,0 @@ -package org.utbot.framework.modifications - -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.ConstructorId -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.id -import org.utbot.framework.plugin.api.util.isArray -import org.utbot.framework.plugin.api.util.isRefType -import org.utbot.framework.plugin.api.util.jClass -import soot.Scene -import soot.SootMethod -import soot.Type -import soot.jimple.InvokeExpr -import soot.jimple.JimpleBody -import soot.jimple.ParameterRef -import soot.jimple.StaticFieldRef -import soot.jimple.internal.JAssignStmt -import soot.jimple.internal.JIdentityStmt -import soot.jimple.internal.JInstanceFieldRef -import soot.jimple.internal.JInvokeStmt -import soot.jimple.internal.JReturnStmt -import soot.jimple.internal.JReturnVoidStmt -import soot.jimple.internal.JimpleLocal - -/** - * Information about constructor required to use it - * in assemble model construction process. - * - * @param params describes the params to call constructor with - * @param setFields describes fields set to required value in constructor - * @param affectedFields describes all fields affected in constructor - * */ -data class ConstructorAssembleInfo( - val constructorId: ConstructorId, - val params: Map, - val setFields: Set, - val affectedFields: Set -) - -/** - * Analyzer of constructors based on Soot. - */ -class ConstructorAnalyzer { - private val scene = Scene.v() - - /** - * Verifies that [constructorId] can be used in assemble models. - * Analyses Soot representation of constructor for that. - */ - fun isAppropriate(constructorId: ConstructorId): Boolean { - val sootConstructor = sootConstructor(constructorId) ?: return false - return isAppropriate(sootConstructor) - } - - /** - * Retrieves information about [constructorId] params and modified fields from Soot. - */ - fun analyze(constructorId: ConstructorId): ConstructorAssembleInfo { - val setFields = mutableSetOf() - val affectedFields = mutableSetOf() - - val sootConstructor = sootConstructor(constructorId) - ?: error("Soot representation of $constructorId is not found.") - val params = analyze(sootConstructor, setFields, affectedFields) - - return ConstructorAssembleInfo(constructorId, params, setFields, affectedFields) - } - - //A cache of constructors been analyzed if they are appropriate or not - private val analyzedConstructors: MutableMap = mutableMapOf() - - /** - *Verifies that the body of this constructor - * contains only statements matching pattern - * this.a = something - * where "a" is an argument of the constructor. - */ - private fun isAppropriate(sootConstructor: SootMethod): Boolean { - if (sootConstructor in analyzedConstructors) { - return analyzedConstructors[sootConstructor]!! - } - analyzedConstructors[sootConstructor] = false - - val jimpleBody = retrieveJimpleBody(sootConstructor) ?: return false - if (hasSuspiciousInstructions(jimpleBody) || modifiesStatics(jimpleBody)) { - return false - } - - //find all invoked constructors (support of inheritance), verify they are appropriate - val invocations = invocations(jimpleBody).map { it.method } - invocations.forEach { constructor -> - if (!constructor.isConstructor || !isAppropriate(constructor)) { - return false - } - } - - analyzedConstructors[sootConstructor] = true - return true - } - - /** - * Verifies that assignment has only parameter variable in right part. - * - * Parameter variables differ form other by the first symbol: it is not $. - */ - private fun JAssignStmt.isPrimitive(): Boolean { - val jimpleLocal = this.rightOp as? JimpleLocal ?: return false - return jimpleLocal.name.first() != '$' - } - - private val visitedConstructors = mutableSetOf() - - private fun analyze( - sootConstructor: SootMethod, - setFields: MutableSet, - affectedFields: MutableSet, - ): Map { - if (sootConstructor in visitedConstructors) { - return emptyMap() - } - visitedConstructors.add(sootConstructor) - - val jimpleBody = retrieveJimpleBody(sootConstructor) ?: return emptyMap() - analyzeAssignments(jimpleBody, setFields, affectedFields) - - val indexOfLocals = jimpleVariableIndices(jimpleBody) - val indexedFields = indexToField(sootConstructor).toMutableMap() - - for (invocation in invocations(jimpleBody)) { - val invokedIndexedFields = analyze(invocation.method, setFields, affectedFields) - - for ((index, argument) in invocation.args.withIndex()) { - val fieldId = invokedIndexedFields[index] ?: continue - val fieldIndex = indexOfLocals[argument] ?: continue - - indexedFields[fieldIndex] = fieldId - } - } - - return indexedFields - } - - /** - * Analyze assignments if they are primitive and allow - * to set a field into required value so on. - */ - private fun analyzeAssignments( - jimpleBody: JimpleBody, - setFields: MutableSet, - affectedFields: MutableSet, - ) { - for (assn in assignments(jimpleBody)) { - val leftPart = assn.leftOp as? JInstanceFieldRef ?: continue - - val fieldId = FieldId(leftPart.field.declaringClass.id, leftPart.field.name) - if (assn.isPrimitive()) { - setFields.add(fieldId) - } else { - affectedFields.add(fieldId) - } - } - } - - /** - * Matches an index of constructor argument with a [FieldId]. - */ - private fun indexToField(sootConstructor: SootMethod): Map { - val jimpleBody = retrieveJimpleBody(sootConstructor) ?: return emptyMap() - val assignments = assignments(jimpleBody) - - val indexedFields = mutableMapOf() - for (assn in assignments) { - val jimpleLocal = assn.rightOp as? JimpleLocal ?: continue - - val field = (assn.leftOp as JInstanceFieldRef).field - val parameterIndex = jimpleBody.locals.indexOfFirst { it.name == jimpleLocal.name } - indexedFields[parameterIndex - 1] = FieldId(field.declaringClass.id, field.name) - } - - return indexedFields - } - - /** - * Matches Jimple variable name with an index in current constructor. - */ - private fun jimpleVariableIndices(jimpleBody: JimpleBody) = jimpleBody.units - .filterIsInstance() - .filter { it.leftOp is JimpleLocal && it.rightOp is ParameterRef } - .associate { it.leftOp as JimpleLocal to (it.rightOp as ParameterRef).index } - - private val sootConstructorCache = mutableMapOf() - - private fun sootConstructor(constructorId: ConstructorId): SootMethod? { - if (constructorId in sootConstructorCache) { - return sootConstructorCache[constructorId] - } - val sootClass = scene.getSootClass(constructorId.classId.name) - val allConstructors = sootClass.methods.filter { it.isConstructor } - val sootConstructor = allConstructors.firstOrNull { sameParameterTypes(it, constructorId) } - - if (sootConstructor != null) { - sootConstructorCache[constructorId] = sootConstructor - return sootConstructor - } - - return null - } - - private fun hasSuspiciousInstructions(jimpleBody: JimpleBody): Boolean = - jimpleBody.units.any { - it !is JIdentityStmt - && !(it is JAssignStmt && it.rightBox.value !is InvokeExpr) - && it !is JInvokeStmt - && it !is JReturnStmt - && it !is JReturnVoidStmt - } - - private fun modifiesStatics(jimpleBody: JimpleBody): Boolean = - jimpleBody.units.any { it is JAssignStmt && it.leftOp is StaticFieldRef } - - private fun assignments(jimpleBody: JimpleBody) = - jimpleBody.units - .filterIsInstance() - - private fun invocations(jimpleBody: JimpleBody): List = - jimpleBody.units - .filterIsInstance() - .map { it.invokeExpr } - - private fun sameParameterTypes(sootMethod: SootMethod, constructorId: ConstructorId): Boolean { - val sootConstructorTypes = sootMethod.parameterTypes - val constructorTypes = constructorId.parameters.map { getParameterType(it) } - - val sootConstructorParamsCount = sootConstructorTypes.count() - val constructorParamsCount = constructorTypes.count() - - if (sootConstructorParamsCount != constructorParamsCount) return false - for (i in 0 until sootConstructorParamsCount) { - if (sootConstructorTypes[i] != constructorTypes[i]) return false - } - - return true - } - - /** - * Restores [Type] by [ClassId] if possible. - * - * Note: we return null if restore process failed. Possibly we need to - * enlarge a set of cases types we can deal with in the future. - */ - private fun getParameterType(type: ClassId): Type? = - try { - when { - type.isRefType -> scene.getRefType(type.name) - type.isArray -> scene.getType(type.jClass.canonicalName) - else -> scene.getType(type.name) - } - } catch (e: Exception) { - null - } -} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/DirectAccessorsAnalyzer.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/DirectAccessorsAnalyzer.kt deleted file mode 100644 index e4ccdbd77e..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/DirectAccessorsAnalyzer.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.utbot.framework.modifications - -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.DirectFieldAccessId -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.util.fieldId -import org.utbot.framework.plugin.api.util.jClass -import java.lang.reflect.Field -import java.lang.reflect.Modifier - -/** - * Analyzer of direct accessors to public fields. - */ -class DirectAccessorsAnalyzer { - /** - * Collect all direct accesses to fields in classes. - */ - fun collectDirectAccesses(classIds: Set): Set = - classIds - .flatMap { classId -> collectFieldsInPackage(classId) } - .map { fieldId -> DirectFieldAccessId(fieldId.declaringClass, directSetterName(fieldId), fieldId) } - .toSet() - - /** - * Collect all fields with different non-private modifiers from class [classId]. - */ - private fun collectFieldsInPackage(classId: ClassId): Set { - val clazz = classId.jClass - - val fieldIds = mutableSetOf() - fieldIds += clazz.fields - fieldIds += clazz.declaredFields.filterNot { Modifier.isPrivate(it.modifiers) } - - return fieldIds.map { it.fieldId }.toSet() - } - - /** - * Creates a name of direct value setting to field. - */ - private fun directSetterName(field: FieldId) = "direct_set_${field.name}" -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/UtBotFieldsModificatorsSearcher.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/UtBotFieldsModificatorsSearcher.kt deleted file mode 100644 index 4633995979..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/modifications/UtBotFieldsModificatorsSearcher.kt +++ /dev/null @@ -1,60 +0,0 @@ -package org.utbot.framework.modifications - -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.StatementId - -class UtBotFieldsModificatorsSearcher { - - private var statementsStorage = StatementsStorage() - - fun update(classIds: Set) = statementsStorage.update(classIds) - - fun delete(classIds: Set) = statementsStorage.delete(classIds) - - /** - * Finds field modificators. - * - * @param analysisMode represents which type of modificators (e.g. setters) are considered. - * @param packageName describes a location of package-private methods that need to be considered. - */ - fun findModificators(analysisMode: AnalysisMode, packageName: String? = null): Map> { - val modificators = findModificators(analysisMode) - if (packageName == null) { - return modificators - } - - val filteredModifications = mutableMapOf>() - for ((fieldId, statements) in modificators) { - val filteredStmts = statements.filter { it.classId.packageName.startsWith(packageName) }.toSet() - filteredModifications[fieldId] = filteredStmts - } - - return filteredModifications - } - - private fun findModificators(analysisMode: AnalysisMode): Map> { - statementsStorage.updateCaches() - return findModificatorsInCache(analysisMode) - } - - /** - * Requests modifications in storage and does the inversion - * of storage map into a FieldId -> Set one. - */ - private fun findModificatorsInCache(analysisMode: AnalysisMode): Map> { - val modifications = mutableMapOf>() - - for (statementWithInfo in statementsStorage.items.filter { it.value.isRoot }) { - val statementId = statementWithInfo.key - - val modifiedFields = statementsStorage.find(statementId, analysisMode) - - modifiedFields.forEach { - modifications[it]?.add(statementId) ?: modifications.put(it, mutableSetOf(statementId)) - } - } - - return modifications - } -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/JVMTestFrameworkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/JVMTestFrameworkManager.kt new file mode 100644 index 0000000000..4bc9f33249 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/JVMTestFrameworkManager.kt @@ -0,0 +1,25 @@ +package org.utbot.framework.plugin.api + +import org.utbot.framework.codegen.domain.Junit4 +import org.utbot.framework.codegen.domain.Junit5 +import org.utbot.framework.codegen.domain.TestNg +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.services.framework.Junit4Manager +import org.utbot.framework.codegen.services.framework.Junit5Manager +import org.utbot.framework.codegen.services.framework.TestNgManager +import org.utbot.framework.codegen.services.language.LanguageTestFrameworkManager + +class JVMTestFrameworkManager : LanguageTestFrameworkManager() { + + override fun managerByFramework(context: CgContext) = when (context.testFramework) { + is Junit4 -> Junit4Manager(context) + is Junit5 -> Junit5Manager(context) + is TestNg -> TestNgManager(context) + else -> throw UnsupportedOperationException("Incorrect TestFramework ${context.testFramework}") + } + + override val defaultTestFramework = Junit5 + + override val testFrameworks = listOf(Junit4, Junit5, TestNg) + +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/MethodDescriptionUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/MethodDescriptionUtil.kt new file mode 100644 index 0000000000..33d5b1142c --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/MethodDescriptionUtil.kt @@ -0,0 +1,25 @@ +package org.utbot.framework.plugin.api + +import org.utbot.framework.plugin.api.util.declaringClazz +import kotlin.reflect.KFunction +import kotlin.reflect.KParameter +import kotlin.reflect.jvm.javaType + +// Note that rules for obtaining signature here should correlate with PsiMethod.signature() +fun KFunction<*>.methodDescription() = + MethodDescription( + this.name, + this.declaringClazz.name, + this.parameters.filter { it.kind != KParameter.Kind.INSTANCE }.map { it.type.javaType.typeName } + ) + +// Similar to MethodId, but significantly simplified -- used only to match methods from psi and their reflections +data class MethodDescription(val name: String, val containingClass: String?, val parameterTypes: List) { + + fun normalized() = this.copy( + containingClass = containingClass?.replace("$", "."), // normalize names of nested classes + parameterTypes = parameterTypes.map { + it?.replace("$", ".") + } + ) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SootUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SootUtils.kt deleted file mode 100644 index a4be349dce..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SootUtils.kt +++ /dev/null @@ -1,144 +0,0 @@ -package org.utbot.framework.plugin.api - -import org.utbot.api.mock.UtMock -import org.utbot.common.FileUtil -import org.utbot.engine.UtNativeStringWrapper -import org.utbot.engine.overrides.Boolean -import org.utbot.engine.overrides.Byte -import org.utbot.engine.overrides.Character -import org.utbot.engine.overrides.Class -import org.utbot.engine.overrides.Integer -import org.utbot.engine.overrides.Long -import org.utbot.engine.overrides.PrintStream -import org.utbot.engine.overrides.Short -import org.utbot.engine.overrides.System -import org.utbot.engine.overrides.UtArrayMock -import org.utbot.engine.overrides.UtLogicMock -import org.utbot.engine.overrides.strings.UtString -import org.utbot.engine.overrides.strings.UtStringBuffer -import org.utbot.engine.overrides.strings.UtStringBuilder -import org.utbot.engine.overrides.collections.AssociativeArray -import org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray -import org.utbot.engine.overrides.collections.UtArrayList -import org.utbot.engine.overrides.collections.UtGenericAssociative -import org.utbot.engine.overrides.collections.UtHashMap -import org.utbot.engine.overrides.collections.UtHashSet -import org.utbot.engine.overrides.collections.UtLinkedList -import org.utbot.engine.overrides.UtOverrideMock -import org.utbot.engine.overrides.collections.Collection -import org.utbot.engine.overrides.collections.List -import org.utbot.engine.overrides.collections.UtGenericStorage -import org.utbot.engine.overrides.collections.UtOptional -import org.utbot.engine.overrides.collections.UtOptionalDouble -import org.utbot.engine.overrides.collections.UtOptionalInt -import org.utbot.engine.overrides.collections.UtOptionalLong -import org.utbot.engine.overrides.collections.AbstractCollection -import org.utbot.engine.overrides.stream.Arrays -import org.utbot.engine.overrides.stream.Stream -import org.utbot.engine.overrides.stream.UtStream -import java.io.File -import java.nio.file.Path -import kotlin.reflect.KClass -import soot.G -import soot.PackManager -import soot.Scene -import soot.SootClass -import soot.options.Options - -/** -Convert code to Jimple - */ -fun runSoot(buildDir: Path, classpath: String?) { - G.reset() - val options = Options.v() - - options.apply { - set_prepend_classpath(true) - // set true to debug. Disabled because of a bug when two different variables - // from the source code have the same name in the jimple body. - setPhaseOption("jb", "use-original-names:false") - set_soot_classpath( - FileUtil.isolateClassFiles(*classesToLoad).absolutePath - + if (!classpath.isNullOrEmpty()) File.pathSeparator + "$classpath" else "" - ) - set_src_prec(Options.src_prec_only_class) - set_process_dir(listOf("$buildDir")) - set_keep_line_number(true) - set_ignore_classpath_errors(true) // gradle/build/resources/main does not exists, but it's not a problem - set_output_format(Options.output_format_jimple) - /** - * In case of Java8, set_full_resolver(true) fails with "soot.SootResolver$SootClassNotFoundException: - * couldn't find class: javax.crypto.BadPaddingException (is your soot-class-path set properly?)". - * To cover that, set_allow_phantom_refs(true) is required - */ - set_allow_phantom_refs(true) // Java8 related - set_full_resolver(true) - } - - addBasicClasses(*classesToLoad) - - Scene.v().loadNecessaryClasses() - PackManager.v().runPacks() - // we need this to create hierarchy of classes - Scene.v().classes.forEach { - if (it.resolvingLevel() < SootClass.HIERARCHY) - it.setResolvingLevel(SootClass.HIERARCHY) - } -} - -private fun addBasicClasses(vararg classes: KClass<*>) { - classes.forEach { - Scene.v().addBasicClass(it.qualifiedName, SootClass.BODIES) - } -} - -private val classesToLoad = arrayOf( - AbstractCollection::class, - UtMock::class, - UtOverrideMock::class, - UtLogicMock::class, - UtArrayMock::class, - Boolean::class, - Byte::class, - Character::class, - Class::class, - Integer::class, - Long::class, - Short::class, - System::class, - UtOptional::class, - UtOptionalInt::class, - UtOptionalLong::class, - UtOptionalDouble::class, - UtArrayList::class, - UtArrayList.UtArrayListIterator::class, - UtLinkedList::class, - UtLinkedList.UtLinkedListIterator::class, - UtLinkedList.ReverseIteratorWrapper::class, - UtHashSet::class, - UtHashSet.UtHashSetIterator::class, - UtHashMap::class, - UtHashMap.Entry::class, - UtHashMap.LinkedEntryIterator::class, - UtHashMap.LinkedEntrySet::class, - UtHashMap.LinkedHashIterator::class, - UtHashMap.LinkedKeyIterator::class, - UtHashMap.LinkedKeySet::class, - UtHashMap.LinkedValueIterator::class, - UtHashMap.LinkedValues::class, - RangeModifiableUnlimitedArray::class, - AssociativeArray::class, - UtGenericStorage::class, - UtGenericAssociative::class, - PrintStream::class, - UtNativeStringWrapper::class, - UtString::class, - UtStringBuilder::class, - UtStringBuffer::class, - Stream::class, - Arrays::class, - Collection::class, - List::class, - UtStream::class, - UtStream.UtStreamIterator::class -) \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SymbolicEngineTestGeneratorService.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SymbolicEngineTestGeneratorService.kt deleted file mode 100644 index 2f65896fa5..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/SymbolicEngineTestGeneratorService.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.utbot.framework.plugin.api - -class SymbolicEngineTestGeneratorService : TestGeneratorService { - override val displayName: String = "Symbolic engine" - override val serviceProvider: TestCaseGenerator = UtBotTestCaseGenerator -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt new file mode 100644 index 0000000000..634a66f6e8 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt @@ -0,0 +1,388 @@ +package org.utbot.framework.plugin.api + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.yield +import mu.KLogger +import mu.KotlinLogging +import org.utbot.common.* +import org.utbot.engine.EngineController +import org.utbot.engine.Mocker +import org.utbot.engine.UtBotSymbolicEngine +import org.utbot.engine.util.mockListeners.ForceMockListener +import org.utbot.engine.util.mockListeners.ForceStaticMockListener +import org.utbot.framework.TestSelectionStrategyType +import org.utbot.framework.UtSettings +import org.utbot.framework.UtSettings.checkSolverTimeoutMillis +import org.utbot.framework.UtSettings.disableCoroutinesDebug +import org.utbot.framework.UtSettings.utBotGenerationTimeoutInMillis +import org.utbot.framework.UtSettings.warmupConcreteExecution +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.context.simple.SimpleApplicationContext +import org.utbot.framework.context.simple.SimpleMockerContext +import org.utbot.framework.plugin.api.utils.checkFrameworkDependencies +import org.utbot.framework.minimization.minimizeTestCase +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.framework.plugin.services.JdkInfo +import org.utbot.framework.util.Conflict +import org.utbot.framework.util.ConflictTriggers +import org.utbot.framework.util.SootUtils +import org.utbot.framework.util.jimpleBody +import org.utbot.framework.util.toModel +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation +import org.utbot.instrumentation.warmup +import org.utbot.taint.TaintConfigurationProvider +import java.io.File +import java.nio.file.Path +import kotlin.coroutines.cancellation.CancellationException +import kotlin.math.min + +/** + * Generates test cases: one by one or a whole set for the method under test. + * + * Note: the instantiating of [TestCaseGenerator] may take some time, + * because it requires initializing Soot for the current [buildDirs] and [classpath]. + * + * @param jdkInfo specifies the JRE and the runtime library version used for analysing system classes and user's code. + * @param forceSootReload forces to reinitialize Soot even if the previous buildDirs equals to [buildDirs] and previous + * classpath equals to [classpath]. This is the case for plugin scenario, as the source code may be modified. + */ +open class TestCaseGenerator( + private val buildDirs: List, + private val classpath: String?, + private val dependencyPaths: String, + private val jdkInfo: JdkInfo, + val engineActions: MutableList<(UtBotSymbolicEngine) -> Unit> = mutableListOf(), + val isCanceled: () -> Boolean = { false }, + val forceSootReload: Boolean = true, + val applicationContext: ApplicationContext = SimpleApplicationContext( + SimpleMockerContext( + mockFrameworkInstalled = true, + staticsMockingIsConfigured = true + ) + ), +) { + private val logger: KLogger = KotlinLogging.logger {} + private val timeoutLogger: KLogger = KotlinLogging.logger(logger.name + ".timeout") + protected val concreteExecutionContext = applicationContext.createConcreteExecutionContext( + fullClasspath = classpathForEngine, + classpathWithoutDependencies = buildDirs.joinToString(File.pathSeparator) + ) + + protected val classpathForEngine: String + get() = (buildDirs + listOfNotNull(classpath)).joinToString(File.pathSeparator) + + init { + if (!isCanceled()) { + checkFrameworkDependencies(dependencyPaths) + + logger.trace("Initializing ${this.javaClass.name} with buildDirs = ${buildDirs.joinToString(File.pathSeparator)}, classpath = $classpath") + + + if (disableCoroutinesDebug) { + System.setProperty(kotlinx.coroutines.DEBUG_PROPERTY_NAME, kotlinx.coroutines.DEBUG_PROPERTY_VALUE_OFF) + } + + timeoutLogger.trace().measureTime({ "Soot initialization"} ) { + SootUtils.runSoot(buildDirs, classpath, forceSootReload, jdkInfo) + } + + //warmup + if (warmupConcreteExecution) { + // force pool to create an appropriate executor + // TODO ensure that instrumented process that starts here is properly terminated + ConcreteExecutor( + concreteExecutionContext.instrumentationFactory, + classpathForEngine, + ).apply { + warmup() + } + } + } + } + + fun minimizeExecutions( + methodUnderTest: ExecutableId, + executions: List, + rerunExecutor: ConcreteExecutor, + ): List = + when (UtSettings.testMinimizationStrategyType) { + TestSelectionStrategyType.DO_NOT_MINIMIZE_STRATEGY -> executions + TestSelectionStrategyType.COVERAGE_STRATEGY -> + concreteExecutionContext.transformExecutionsAfterMinimization( + minimizeTestCase( + concreteExecutionContext.transformExecutionsBeforeMinimization( + executions, + methodUnderTest, + ), + executionToTestSuite = { it.result::class.java } + ), + methodUnderTest, + rerunExecutor = rerunExecutor, + ) + } + + @Throws(CancellationException::class) + fun generateAsync( + controller: EngineController, + method: ExecutableId, + mockStrategy: MockStrategyApi, + chosenClassesToMockAlways: Set = Mocker.javaDefaultClasses.mapTo(mutableSetOf()) { it.id }, + executionTimeEstimator: ExecutionTimeEstimator = ExecutionTimeEstimator(utBotGenerationTimeoutInMillis, 1), + userTaintConfigurationProvider: TaintConfigurationProvider? = null, + ): Flow = flow { + if (isCanceled()) + return@flow + + val contextLoadingResult = loadConcreteExecutionContext() + emitAll(flowOf(*contextLoadingResult.utErrors.toTypedArray())) + if (!contextLoadingResult.contextLoaded) + return@flow + + try { + val engine = createSymbolicEngine( + controller, + method, + mockStrategy, + chosenClassesToMockAlways, + applicationContext, + executionTimeEstimator, + userTaintConfigurationProvider, + ) + engineActions.map { engine.apply(it) } + engineActions.clear() + emitAll(defaultTestFlow(engine, executionTimeEstimator.userTimeout)) + } catch (e: Exception) { + logger.error(e) {"Generate async failed"} + throw e + } + } + + fun generate( + methods: List, + mockStrategy: MockStrategyApi, + chosenClassesToMockAlways: Set = Mocker.javaDefaultClasses.mapTo(mutableSetOf()) { it.id }, + methodsGenerationTimeout: Long = utBotGenerationTimeoutInMillis, + userTaintConfigurationProvider: TaintConfigurationProvider? = null, + generate: (engine: UtBotSymbolicEngine) -> Flow = defaultTestFlow(methodsGenerationTimeout) + ): List = ConcreteExecutor.defaultPool.use { _ -> // TODO: think on appropriate way to close instrumented processes + if (isCanceled()) return@use methods.map { UtMethodTestSet(it) } + + val contextLoadingResult = loadConcreteExecutionContext() + + val method2errors: Map> = methods.associateWith { + contextLoadingResult.utErrors.associateTo(mutableMapOf()) { it.description to 1 } + } + + if (!contextLoadingResult.contextLoaded) + return@use methods.map { method -> UtMethodTestSet(method, errors = method2errors.getValue(method)) } + + val executionStartInMillis = System.currentTimeMillis() + val executionTimeEstimator = ExecutionTimeEstimator(methodsGenerationTimeout, methods.size) + + val currentUtContext = utContext + + val method2controller = methods.associateWith { EngineController() } + val method2executions = methods.associateWith { mutableListOf() } + + val conflictTriggers = ConflictTriggers() + val forceMockListener = ForceMockListener.create(this, conflictTriggers) + val forceStaticMockListener = ForceStaticMockListener.create(this, conflictTriggers) + + runIgnoringCancellationException { + runBlockingWithCancellationPredicate(isCanceled) { + for ((method, controller) in method2controller) { + controller.job = launch(currentUtContext) { + if (!isActive) return@launch + + try { + //yield one to + yield() + + val engine: UtBotSymbolicEngine = createSymbolicEngine( + controller, + method, + mockStrategy, + chosenClassesToMockAlways, + applicationContext, + executionTimeEstimator, + userTaintConfigurationProvider, + ) + + engineActions.map { engine.apply(it) } + engineActions.clear() + + generate(engine) + .catch { + logger.error(it) { "Error in flow" } + } + .collect { + when (it) { + is UtExecution -> { + if (it is UtSymbolicExecution && + (conflictTriggers.triggered(Conflict.ForceMockHappened) || + conflictTriggers.triggered(Conflict.ForceStaticMockHappened)) + ) { + it.containsMocking = true + } + method2executions.getValue(method) += it + } + is UtError -> { + method2errors.getValue(method).merge(it.description, 1, Int::plus) + logger.error(it.error) { "UtError occurred" } + } + } + } + } catch (e: Exception) { + logger.error(e) {"Error in engine"} + throw e + } + } + controller.paused = true + conflictTriggers.reset(Conflict.ForceMockHappened, Conflict.ForceStaticMockHappened) + } + + // All jobs are in the method2controller now (paused). execute them with timeout + + GlobalScope.launch { + logger.debug("test generator global scope lifecycle check started") + while (isActive) { + var activeCount = 0 + for ((method, controller) in method2controller) { + if (!controller.job!!.isActive) continue + activeCount++ + + method2controller.values.forEach { it.paused = true } + controller.paused = false + + logger.info { "Resuming method $method" } + val startTime = System.currentTimeMillis() + while (controller.job!!.isActive && + (System.currentTimeMillis() - startTime) < executionTimeEstimator.timeslotForOneToplevelMethodTraversalInMillis + ) { + updateLifecycle( + executionStartInMillis, + executionTimeEstimator, + method2controller.values, + this + ) + yield() + } + } + if (activeCount == 0) break + } + logger.debug("test generator global scope lifecycle check ended") + } + } + } + + forceMockListener.detach(this, forceMockListener) + forceStaticMockListener.detach(this, forceStaticMockListener) + + return@use methods.map { method -> + UtMethodTestSet( + method, + minimizeExecutions( + method, + method2executions.getValue(method), + rerunExecutor = ConcreteExecutor(concreteExecutionContext.instrumentationFactory, classpathForEngine) + ), + jimpleBody(method), + method2errors.getValue(method) + ) + } + } + + private fun createSymbolicEngine( + controller: EngineController, + method: ExecutableId, + mockStrategyApi: MockStrategyApi, + chosenClassesToMockAlways: Set, + applicationContext: ApplicationContext, + executionTimeEstimator: ExecutionTimeEstimator, + userTaintConfigurationProvider: TaintConfigurationProvider? = null, + ): UtBotSymbolicEngine { + logger.debug("Starting symbolic execution for $method --$mockStrategyApi--") + return UtBotSymbolicEngine( + controller, + method, + classpathForEngine, + dependencyPaths = dependencyPaths, + mockStrategy = mockStrategyApi.toModel(), + chosenClassesToMockAlways = chosenClassesToMockAlways, + applicationContext = applicationContext, + concreteExecutionContext = concreteExecutionContext, + solverTimeoutInMillis = executionTimeEstimator.updatedSolverCheckTimeoutMillis, + userTaintConfigurationProvider = userTaintConfigurationProvider, + ) + } + + // CONFLUENCE:The+UtBot+Java+timeouts + + class ExecutionTimeEstimator(val userTimeout: Long, methodsUnderTestNumber: Int) { + // Cut the timeout from the user in two halves + private val halfTimeUserExpectsToWaitInMillis = userTimeout / 2 + + // If the half is too much for concrete execution, decrease the concrete timeout + val concreteExecutionBudgetInMillis = min(halfTimeUserExpectsToWaitInMillis, 300L * methodsUnderTestNumber) + + // The symbolic execution time is the reminder but not longer than checkSolverTimeoutMillis times methods number + val symbolicExecutionTimeout = userTimeout - concreteExecutionBudgetInMillis + + //Allow traverse at least one method for the symbolic execution timeout + val timeslotForOneToplevelMethodTraversalInMillis = + symbolicExecutionTimeout / (methodsUnderTestNumber * 2) + + // Axillary field + private val symbolicExecutionTimePerMethod = (symbolicExecutionTimeout / methodsUnderTestNumber).toInt() + + // Now we calculate the solver timeout. Each method is supposed to get some time in worst-case scenario + val updatedSolverCheckTimeoutMillis = if (symbolicExecutionTimePerMethod < checkSolverTimeoutMillis) + symbolicExecutionTimePerMethod else checkSolverTimeoutMillis + } + + private fun updateLifecycle( + executionStartInMillis: Long, + executionTimeEstimator: ExecutionTimeEstimator, + controllers: Collection, + timeoutCheckerCoroutine: CoroutineScope + ) { + val timePassed = System.currentTimeMillis() - executionStartInMillis + + if (timePassed > executionTimeEstimator.userTimeout) { + timeoutLogger.trace { + "Out of concrete execution time limit (" + + "$timePassed > ${executionTimeEstimator.userTimeout}" + + "). Cancelling coroutines" + } + controllers.forEach { it.job!!.cancel("Timeout") } + timeoutCheckerCoroutine.cancel("Timeout") + } else if (!controllers.firstOrNull()!!.executeConcretely && + timePassed > executionTimeEstimator.symbolicExecutionTimeout + ) { + timeoutLogger.trace { + "We are out of time (" + + "$timePassed > ${executionTimeEstimator.symbolicExecutionTimeout}" + + "). Switching to the concrete execution (extra ${executionTimeEstimator.concreteExecutionBudgetInMillis} ms)" + } + controllers.forEach { it.executeConcretely = true } + } + } + + private fun loadConcreteExecutionContext(): ConcreteContextLoadingResult { + // force pool to create an appropriate executor + val concreteExecutor = ConcreteExecutor(concreteExecutionContext.instrumentationFactory, classpathForEngine) + return concreteExecutionContext.loadContext(concreteExecutor) + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt new file mode 100644 index 0000000000..b62ecace4d --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt @@ -0,0 +1,77 @@ +package org.utbot.framework.plugin.api + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flattenConcat +import kotlinx.coroutines.flow.flowOf +import org.utbot.engine.UtBotSymbolicEngine +import org.utbot.framework.UtSettings +import org.utbot.framework.process.generated.GenerateParams + +/** + * Constructs [TestFlow] for customization and creates flow producer. + */ +fun testFlow(block: TestFlow.() -> Unit): UtBotSymbolicEngine.() -> Flow = { TestFlow(block).build(this) } + +/** + * Creates default flow that uses [UtSettings] for customization. + */ +fun defaultTestFlow(timeout: Long) = testFlow { + isSymbolicEngineEnabled = true + generationTimeout = timeout + isFuzzingEnabled = UtSettings.useFuzzing + if (generationTimeout > 0) { + fuzzingValue = UtSettings.fuzzingTimeoutInMillis.coerceIn(0, generationTimeout) / generationTimeout.toDouble() + } +} + +/** + * Creates default flow that uses [UtSettings] for customization. + */ +fun defaultTestFlow(engine: UtBotSymbolicEngine, timeout: Long) = defaultTestFlow(timeout).invoke(engine) + +/** + * TestFlow helps construct flows with different settings but with common sequence of test generation process. + * + * Use [testFlow] to set a custom test flow or [defaultTestFlow] to create flow based on [UtSettings]. + */ +class TestFlow internal constructor(block: TestFlow.() -> Unit) { + var generationTimeout = 0L + set(value) { + field = maxOf(0, value) + } + var isSymbolicEngineEnabled = true + var isFuzzingEnabled = false + var fuzzingValue: Double = 0.1 + set(value) { + field = value.coerceIn(0.0, 1.0) + } + + init { + apply(block) + } + + /* + Constructs default flow that is having the following steps at the moment: + 1. If fuzzer is enabled then run it before symbolic execution for [fuzzingValue] * [generationTimeout] ms. + 2. Run symbolic execution for the rest time. + 3. If both (fuzzer and symbolic execution) are off then do nothing. + */ + fun build(engine: UtBotSymbolicEngine): Flow { + return when { + generationTimeout == 0L -> emptyFlow() + isFuzzingEnabled -> { + when (val value = if (isSymbolicEngineEnabled) (fuzzingValue * generationTimeout).toLong() else generationTimeout) { + 0L -> engine.traverse() + generationTimeout -> engine.fuzzing(System.currentTimeMillis() + value) + else -> flowOf( + engine.fuzzing(System.currentTimeMillis() + value), + engine.traverse() + ).flattenConcat() + } + } + isSymbolicEngineEnabled -> engine.traverse() + else -> emptyFlow() + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt deleted file mode 100644 index 9c078dd7e2..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGenerator.kt +++ /dev/null @@ -1,448 +0,0 @@ -package org.utbot.framework.plugin.api - -import org.utbot.common.FileUtil -import org.utbot.common.bracket -import org.utbot.common.runBlockingWithCancellationPredicate -import org.utbot.common.runIgnoringCancellationException -import org.utbot.common.trace -import org.utbot.engine.EngineController -import org.utbot.engine.MockStrategy -import org.utbot.engine.Mocker -import org.utbot.engine.UtBotSymbolicEngine -import org.utbot.engine.jimpleBody -import org.utbot.engine.pureJavaSignature -import org.utbot.framework.TestSelectionStrategyType -import org.utbot.framework.UtSettings -import org.utbot.framework.UtSettings.checkSolverTimeoutMillis -import org.utbot.framework.UtSettings.disableCoroutinesDebug -import org.utbot.framework.UtSettings.utBotGenerationTimeoutInMillis -import org.utbot.framework.UtSettings.warmupConcreteExecution -import org.utbot.framework.codegen.model.util.checkFrameworkDependencies -import org.utbot.framework.concrete.UtConcreteExecutionData -import org.utbot.framework.concrete.UtExecutionInstrumentation -import org.utbot.framework.concrete.UtModelConstructor -import org.utbot.framework.minimization.minimizeTestCase -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.intArrayClassId -import org.utbot.framework.plugin.api.util.signature -import org.utbot.framework.plugin.api.util.utContext -import org.utbot.framework.plugin.api.util.withUtContext -import org.utbot.instrumentation.ConcreteExecutor -import org.utbot.instrumentation.warmup.Warmup -import java.io.File -import java.nio.file.Path -import java.util.IdentityHashMap -import kotlin.coroutines.cancellation.CancellationException -import kotlin.math.min -import kotlin.reflect.KCallable -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.flattenConcat -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.yield -import mu.KotlinLogging -import org.utbot.engine.* -import soot.Scene -import soot.jimple.JimpleBody -import soot.toolkits.graph.ExceptionalUnitGraph - -object UtBotTestCaseGenerator : TestCaseGenerator { - - private val logger = KotlinLogging.logger {} - private val timeoutLogger = KotlinLogging.logger(logger.name + ".timeout") - - lateinit var configureEngine: (UtBotSymbolicEngine) -> Unit - lateinit var isCanceled: () -> Boolean - - //properties to save time on soot initialization - private var previousBuildDir: Path? = null - private var previousClasspath: String? = null - private var previousTimestamp: Long? = null - private var dependencyPaths: String = "" - - override fun init( - buildDir: Path, - classpath: String?, - dependencyPaths: String, - isCanceled: () -> Boolean - ) = init( - buildDir, - classpath, - dependencyPaths, - configureEngine = {}, - isCanceled - ) - - fun init( - buildDir: Path, - classpath: String?, - dependencyPaths: String, - configureEngine: (UtBotSymbolicEngine) -> Unit, - isCanceled: () -> Boolean - ) { - this.isCanceled = isCanceled - this.configureEngine = configureEngine - if (isCanceled()) return - - checkFrameworkDependencies(dependencyPaths) - - logger.trace("Initializing ${this.javaClass.name} with buildDir = $buildDir, classpath = $classpath") - - //optimization: maxLastModifiedRecursivelyMillis can take time - val timestamp = if (UtSettings.classfilesCanChange) maxLastModifiedRecursivelyMillis(buildDir, classpath) else 0 - - if (buildDir == previousBuildDir && classpath == previousClasspath && timestamp == previousTimestamp) { - logger.info { "Ignoring soot initialization because parameters are the same as on previous initialization" } - return - } - - if (disableCoroutinesDebug) { - System.setProperty(kotlinx.coroutines.DEBUG_PROPERTY_NAME, kotlinx.coroutines.DEBUG_PROPERTY_VALUE_OFF) - } - - timeoutLogger.trace().bracket("Soot initialization") { - runSoot(buildDir, classpath) - } - - previousBuildDir = buildDir - previousClasspath = classpath - previousTimestamp = timestamp - this.dependencyPaths = dependencyPaths - - //warmup - if (warmupConcreteExecution) { - ConcreteExecutor( - UtExecutionInstrumentation, - classpathForEngine, - dependencyPaths - ).apply { - classLoader = utContext.classLoader - withUtContext(UtContext(Warmup::class.java.classLoader)) { - runBlocking { - constructExecutionsForWarmup().forEach { (method, data) -> - executeAsync(method, emptyArray(), data) - } - } - } - warmup() - } - } - } - - private fun constructExecutionsForWarmup(): Sequence, UtConcreteExecutionData>> = - UtModelConstructor(IdentityHashMap()).run { - sequenceOf( - Warmup::doWarmup1 to UtConcreteExecutionData( - EnvironmentModels( - construct(Warmup(5), Warmup::class.java.id), - listOf(construct(Warmup(1), Warmup::class.java.id)), - emptyMap() - ), emptyList() - ), - Warmup::doWarmup2 to UtConcreteExecutionData( - EnvironmentModels( - construct(Warmup(1), Warmup::class.java.id), - listOf(construct(intArrayOf(1, 2, 3), intArrayClassId)), - emptyMap() - ), emptyList() - ), - Warmup::doWarmup2 to UtConcreteExecutionData( - EnvironmentModels( - construct(Warmup(1), Warmup::class.java.id), - listOf(construct(intArrayOf(1, 2, 3, 4, 5, 6), intArrayClassId)), - emptyMap() - ), emptyList() - ), - ) - } - - private val classpathForEngine: String - get() = previousBuildDir!!.toString() + (previousClasspath?.let { File.pathSeparator + it } ?: "") - - private fun maxLastModifiedRecursivelyMillis(buildDir: Path, classpath: String?): Long { - val paths = mutableListOf() - paths += buildDir.toFile() - if (classpath != null) { - paths += classpath.split(File.pathSeparatorChar).map { File(it) } - } - return FileUtil.maxLastModifiedRecursivelyMillis(paths) - } - - @Throws(CancellationException::class) - fun generateAsync( - controller: EngineController, - method: UtMethod<*>, - mockStrategy: MockStrategyApi, - chosenClassesToMockAlways: Set = Mocker.javaDefaultClasses.mapTo(mutableSetOf()) { it.id }, - executionTimeEstimator: ExecutionTimeEstimator = ExecutionTimeEstimator(utBotGenerationTimeoutInMillis, 1) - ): Flow { - val engine = createSymbolicEngine( - controller, - method, - mockStrategy, - chosenClassesToMockAlways, - executionTimeEstimator - ) - return createDefaultFlow(engine) - } - - private fun createSymbolicEngine( - controller: EngineController, - method: UtMethod<*>, - mockStrategy: MockStrategyApi, - chosenClassesToMockAlways: Set, - executionTimeEstimator: ExecutionTimeEstimator - ): UtBotSymbolicEngine { - // TODO: create classLoader from buildDir/classpath and migrate from UtMethod to MethodId? - logger.debug("Starting symbolic execution for $method --$mockStrategy--") - val graph = graph(method) - - return UtBotSymbolicEngine( - controller, - method, - graph, - classpathForEngine, - dependencyPaths = dependencyPaths, - mockStrategy = apiToModel(mockStrategy), - chosenClassesToMockAlways = chosenClassesToMockAlways, - solverTimeoutInMillis = executionTimeEstimator.updatedSolverCheckTimeoutMillis - ) - } - - private fun createDefaultFlow(engine: UtBotSymbolicEngine): Flow { - var flow = engine.traverse() - if (UtSettings.useFuzzing) { - flow = flowOf( - engine.fuzzing(System.currentTimeMillis() + UtSettings.fuzzingTimeoutInMillis), - flow, - ).flattenConcat() - } - return flow - } - - // CONFLUENCE:The+UtBot+Java+timeouts - - class ExecutionTimeEstimator(val userTimeout: Long, methodsUnderTestNumber: Int) { - // Cut the timeout from the user in two halves - private val halfTimeUserExpectsToWaitInMillis = userTimeout / 2 - - // If the half is too much for concrete execution, decrease the concrete timeout - var concreteExecutionBudgetInMillis = - min(halfTimeUserExpectsToWaitInMillis, 300L * methodsUnderTestNumber) - - // The symbolic execution time is the reminder but not longer than checkSolverTimeoutMillis times methods number - val symbolicExecutionTimeout = userTimeout - concreteExecutionBudgetInMillis - - //Allow traverse at least one method for the symbolic execution timeout - val timeslotForOneToplevelMethodTraversalInMillis = - symbolicExecutionTimeout / (methodsUnderTestNumber * 2) - - // Axillary field - private val symbolicExecutionTimePerMethod = (symbolicExecutionTimeout / methodsUnderTestNumber).toInt() - - // Now we calculate the solver timeout. Each method is supposed to get some time in worst-case scenario - val updatedSolverCheckTimeoutMillis = if (symbolicExecutionTimePerMethod < checkSolverTimeoutMillis) - symbolicExecutionTimePerMethod else checkSolverTimeoutMillis - - init { - // Update the concrete execution time, if symbolic execution time is small - // because of UtSettings.checkSolverTimeoutMillis - concreteExecutionBudgetInMillis = userTimeout - symbolicExecutionTimeout - require(symbolicExecutionTimeout > 10) - require(concreteExecutionBudgetInMillis > 10) - } - } - - fun generateForSeveralMethods( - methods: List>, - mockStrategy: MockStrategyApi, - chosenClassesToMockAlways: Set = Mocker.javaDefaultClasses.mapTo(mutableSetOf()) { it.id }, - methodsGenerationTimeout: Long = utBotGenerationTimeoutInMillis, - generate: (engine: UtBotSymbolicEngine) -> Flow = ::createDefaultFlow - ): List { - if (isCanceled()) return methods.map { UtTestCase(it) } - - val executionStartInMillis = System.currentTimeMillis() - val executionTimeEstimator = ExecutionTimeEstimator(methodsGenerationTimeout, methods.size) - - val currentUtContext = utContext - - val method2controller = methods.associateWith { EngineController() } - val method2executions = methods.associateWith { mutableListOf() } - val method2errors = methods.associateWith { mutableMapOf() } - - runIgnoringCancellationException { - runBlockingWithCancellationPredicate(isCanceled) { - for ((method, controller) in method2controller) { - controller.job = launch(currentUtContext) { - if (!isActive) return@launch - - //yield one to - yield() - - val engine: UtBotSymbolicEngine = createSymbolicEngine( - controller, - method, - mockStrategy, - chosenClassesToMockAlways, - executionTimeEstimator - ).apply(configureEngine) - - generate(engine).collect { - when (it) { - is UtExecution -> method2executions.getValue(method) += it - is UtError -> method2errors.getValue(method).merge(it.description, 1, Int::plus) - } - } - } - controller.paused = true - } - - // All jobs are in the method2controller now (paused). execute them with timeout - - GlobalScope.launch { - while (isActive) { - var activeCount = 0 - for ((method, controller) in method2controller) { - if (!controller.job!!.isActive) continue - activeCount++ - - method2controller.values.forEach { it.paused = true } - controller.paused = false - - logger.info { "|> Resuming method $method" } - val startTime = System.currentTimeMillis() - while (controller.job!!.isActive && - (System.currentTimeMillis() - startTime) < executionTimeEstimator.timeslotForOneToplevelMethodTraversalInMillis - ) { - updateLifecycle( - executionStartInMillis, - executionTimeEstimator, - method2controller.values, - this - ) - yield() - } - } - if (activeCount == 0) break - } - } - } - } - ConcreteExecutor.defaultPool.close() // TODO: think on appropriate way to close child processes - - - return methods.map { method -> - UtTestCase( - method, - minimizeExecutions(method2executions.getValue(method)), - jimpleBody(method), - method2errors.getValue(method) - ) - } - } - - private fun updateLifecycle( - executionStartInMillis: Long, - executionTimeEstimator: ExecutionTimeEstimator, - controllers: Collection, - timeoutCheckerCoroutine: CoroutineScope - ) { - val timePassed = System.currentTimeMillis() - executionStartInMillis - - if (timePassed > executionTimeEstimator.userTimeout) { - timeoutLogger.trace { - "Out of concrete execution time limit (" + - "$timePassed > ${executionTimeEstimator.userTimeout}" + - "). Cancelling coroutines" - } - controllers.forEach { it.job!!.cancel("Timeout") } - timeoutCheckerCoroutine.cancel("Timeout") - } else if (!controllers.firstOrNull()!!.executeConcretely && - timePassed > executionTimeEstimator.symbolicExecutionTimeout - ) { - timeoutLogger.trace { - "We are out of time (" + - "$timePassed > ${executionTimeEstimator.symbolicExecutionTimeout}" + - "). Switching to the concrete execution (extra ${executionTimeEstimator.concreteExecutionBudgetInMillis} ms)" - } - controllers.forEach { it.executeConcretely = true } - } - } - - override fun generate(method: UtMethod<*>, mockStrategy: MockStrategyApi): UtTestCase { - logger.trace { "UtSettings:${System.lineSeparator()}" + UtSettings.toString() } - - if (isCanceled()) return UtTestCase(method) - - val executions = mutableListOf() - val errors = mutableMapOf() - - runIgnoringCancellationException { - runBlockingWithCancellationPredicate(isCanceled) { - generateAsync(EngineController(), method, mockStrategy).collect { - when (it) { - is UtExecution -> executions += it - is UtError -> errors.merge(it.description, 1, Int::plus) - } - } - } - } - - val minimizedExecutions = minimizeExecutions(executions) - return UtTestCase(method, minimizedExecutions, jimpleBody(method), errors) - } - - - private fun minimizeExecutions(executions: List): List = - if (UtSettings.testMinimizationStrategyType == TestSelectionStrategyType.DO_NOT_MINIMIZE_STRATEGY) { - executions - } else { - minimizeTestCase(executions) { it.result::class.java } - } - - - fun apiToModel(mockStrategyApi: MockStrategyApi): MockStrategy = - when (mockStrategyApi) { - MockStrategyApi.NO_MOCKS -> MockStrategy.NO_MOCKS - MockStrategyApi.OTHER_PACKAGES -> MockStrategy.OTHER_PACKAGES - MockStrategyApi.OTHER_CLASSES -> MockStrategy.OTHER_CLASSES - else -> error("Cannot map API Mock Strategy model to Engine model: $mockStrategyApi") - } - - private fun graph(method: UtMethod<*>): ExceptionalUnitGraph { - val className = method.clazz.java.name - val clazz = Scene.v().classes.singleOrNull { it.name == className } - ?: error("No such $className found in the Scene") - val signature = method.callable.signature - val sootMethod = clazz.methods.singleOrNull { it.pureJavaSignature == signature } - ?: error("No such $signature found") - if (!sootMethod.canRetrieveBody()) { - error("No method body for $sootMethod found") - } - val methodBody = sootMethod.jimpleBody() - val graph = methodBody.graph() - - logger.trace { "JIMPLE for $method:\n${methodBody}" } - - return graph - } - - fun jimpleBody(method: UtMethod<*>): JimpleBody { - val clazz = Scene.v().classes.single { it.name == method.clazz.java.name } - val signature = method.callable.signature - val sootMethod = clazz.methods.single { it.pureJavaSignature == signature } - - return sootMethod.jimpleBody() - } -} - -fun JimpleBody.graph() = ExceptionalUnitGraph(this) - diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/utils/ClassNameUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/utils/ClassNameUtils.kt new file mode 100644 index 0000000000..c816bc65ab --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/utils/ClassNameUtils.kt @@ -0,0 +1,22 @@ +package org.utbot.framework.plugin.api.utils + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.nameWithEnclosingClassesAsContigousString + +object ClassNameUtils { + fun generateTestClassName( + testClassCustomName: String?, + testClassPackageName: String, + classUnderTest: ClassId, + ): Pair { + val packagePrefix = if (testClassPackageName.isNotEmpty()) "$testClassPackageName." else "" + val simpleName = testClassCustomName ?: generateTestClassShortName(classUnderTest) + val name = "$packagePrefix$simpleName" + return Pair(name, simpleName) + } + + fun generateTestClassShortName(classUnderTest: ClassId): String = + "${classUnderTest.nameWithEnclosingClassesAsContigousString}Test" +} + + diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/utils/DependencyPatterns.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/utils/DependencyPatterns.kt new file mode 100644 index 0000000000..8a4f3e1729 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/utils/DependencyPatterns.kt @@ -0,0 +1,107 @@ +package org.utbot.framework.plugin.api.utils + +import org.utbot.framework.codegen.domain.Junit4 +import org.utbot.framework.codegen.domain.Junit5 +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.codegen.domain.TestNg +import org.utbot.framework.plugin.api.MockFramework + +data class Patterns( + val moduleLibraryPatterns: List, + val libraryPatterns: List, +) + +fun TestFramework.patterns(): Patterns { + val moduleLibraryPatterns = when (this) { + Junit4 -> junit4ModulePatterns + Junit5 -> junit5ModulePatterns + TestNg -> testNgModulePatterns + else -> throw UnsupportedOperationException("Unknown test framework $this") + } + val libraryPatterns = when (this) { + Junit4 -> junit4Patterns + Junit5 -> junit5Patterns + TestNg -> testNgPatterns + else -> throw UnsupportedOperationException("Unknown test framework $this") + } + + return Patterns(moduleLibraryPatterns, libraryPatterns) +} + + +fun TestFramework.parametrizedTestsPatterns(): Patterns { + val moduleLibraryPatterns = when (this) { + Junit4 -> emptyList() + Junit5 -> emptyList() // emptyList here because JUnit5 module may not be enough for parametrized tests if :junit-jupiter-params: is not installed + TestNg -> testNgModulePatterns + else -> throw UnsupportedOperationException("Unknown test framework $this") + } + val libraryPatterns = when (this) { + Junit4 -> emptyList() + Junit5 -> junit5ParametrizedTestsPatterns + TestNg -> testNgPatterns + else -> throw UnsupportedOperationException("Unknown test framework $this") + } + + return Patterns(moduleLibraryPatterns, libraryPatterns) +} + + +fun MockFramework.patterns(): Patterns { + val moduleLibraryPatterns = when (this) { + MockFramework.MOCKITO -> mockitoModulePatterns + } + val libraryPatterns = when (this) { + MockFramework.MOCKITO -> mockitoPatterns + } + + return Patterns(moduleLibraryPatterns, libraryPatterns) +} + +fun MockFramework.manifestPatterns(): Patterns { + val moduleLibraryPatterns = when (this) { + MockFramework.MOCKITO -> mockitoManifestPatterns + } + val libraryPatterns = when (this) { + MockFramework.MOCKITO -> mockitoManifestPatterns + } + return Patterns(moduleLibraryPatterns, libraryPatterns) +} + +val JUNIT_4_JAR_PATTERN = Regex("junit-4(\\.1[2-9])(\\.[0-9]+)?") +val JUNIT_4_MVN_PATTERN = Regex("junit:junit:4(\\.1[2-9])(\\.[0-9]+)?") +val junit4Patterns = listOf(JUNIT_4_JAR_PATTERN, JUNIT_4_MVN_PATTERN) +val junit4ModulePatterns = listOf(JUNIT_4_JAR_PATTERN, JUNIT_4_MVN_PATTERN) + +val JUNIT_5_JAR_PATTERN = Regex("junit-jupiter-5(\\.[0-9]+){1,2}") +val JUNIT_5_MVN_PATTERN = Regex("org\\.junit\\.jupiter:junit-jupiter-api:5(\\.[0-9]+){1,2}") +val JUNIT_5_BASIC_PATTERN = Regex("JUnit5\\.4") +val junit5Patterns = listOf(JUNIT_5_JAR_PATTERN, JUNIT_5_MVN_PATTERN, JUNIT_5_BASIC_PATTERN) + +val JUNIT_5_PARAMETRIZED_JAR_PATTERN = Regex("junit-jupiter-params-5(\\.[0-9]+){1,2}") +val JUNIT_5_PARAMETRIZED_MVN_PATTERN = Regex("org\\.junit\\.jupiter\\.junit-jupiter-params:5(\\.[0-9]+){1,2}") +val junit5ParametrizedTestsPatterns = listOf(JUNIT_5_JAR_PATTERN, JUNIT_5_BASIC_PATTERN, + JUNIT_5_PARAMETRIZED_JAR_PATTERN, JUNIT_5_PARAMETRIZED_MVN_PATTERN) + +val JUNIT5_BASIC_MODULE_PATTERN = Regex("junit-jupiter") +val junit5ModulePatterns = listOf(JUNIT5_BASIC_MODULE_PATTERN) + +val TEST_NG_JAR_PATTERN = Regex("testng-[0-9](\\.[0-9]+){2}") +val TEST_NG_MVN_PATTERN = Regex("org\\.testng:testng:[0-9](\\.[0-9]+){2}") +val TEST_NG_BASIC_PATTERN = Regex("testng") +val testNgPatterns = listOf(TEST_NG_JAR_PATTERN, TEST_NG_MVN_PATTERN, TEST_NG_BASIC_PATTERN) + +val TEST_NG_BASIC_MODULE_PATTERN = Regex("testng") +val testNgModulePatterns = listOf(TEST_NG_BASIC_MODULE_PATTERN) + +val MOCKITO_JAR_PATTERN = Regex("mockito-core-[3-9](\\.[0-9]+){2}") +val MOCKITO_MVN_PATTERN = Regex("org\\.mockito:mockito-core:[3-9](\\.[0-9]+){2}") +val mockitoPatterns = listOf(MOCKITO_JAR_PATTERN, MOCKITO_MVN_PATTERN) +val mockitoModulePatterns = listOf(MOCKITO_JAR_PATTERN, MOCKITO_MVN_PATTERN) + +val MOCKITO_MANIFEST_PATTERN = Regex("mockito-core") +val mockitoManifestPatterns = listOf(MOCKITO_MANIFEST_PATTERN) + +const val MOCKITO_EXTENSIONS_FOLDER = "mockito-extensions" +const val MOCKITO_MOCKMAKER_FILE_NAME = "org.mockito.plugins.MockMaker" +val MOCKITO_EXTENSIONS_FILE_CONTENT = "mock-maker-inline" diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/utils/DependencyUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/utils/DependencyUtils.kt new file mode 100644 index 0000000000..285c30e33f --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/utils/DependencyUtils.kt @@ -0,0 +1,97 @@ +package org.utbot.framework.plugin.api.utils + +import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation +import org.utbot.framework.plugin.api.MockFramework +import java.io.File +import java.util.jar.JarFile +import mu.KotlinLogging + +private val logger = KotlinLogging.logger {} + +/** + * Checks that dependency paths contains some frameworks + * and their versions correspond to our requirements. + * + * Note: [UtExecutionInstrumentation] must be in dependency path too + * as it is used by Engine in the instrumented process in Concrete Executor. + */ +fun checkFrameworkDependencies(dependencyPaths: String?) { + if (dependencyPaths.isNullOrEmpty()) { + error("Dependency paths is empty, no test framework and mock framework to generate tests") + } + + //TODO: JIRA:1659 + // check (somehow) that [UtExecutionInstrumentation] is in dependency path: in one of jars or folders + + val dependencyPathsSequence = dependencyPaths.splitToSequence(File.pathSeparatorChar) + + val dependencyNames = dependencyPathsSequence + .mapNotNull { findDependencyName(it) } + .map { it.toLowerCase() } + .toSet() + +//todo: stopped working after transition to Java 11. Ask Egor to take a look. +// val testFrameworkPatterns = TestFramework.allItems.map { it.patterns() } +// val testFrameworkFound = dependencyNames.matchesAnyOf(testFrameworkPatterns) || +// dependencyPathsSequence.any { checkDependencyIsFatJar(it) } +// +// if (!testFrameworkFound) { +// error(""" +// Test frameworks are not found in dependency path $dependencyPaths, dependency names are: +// ${dependencyNames.joinToString(System.lineSeparator())} +// """ +// ) +// } + + val mockFrameworkPatterns = MockFramework.allItems.map { it.manifestPatterns() } + val mockFrameworkFound = dependencyNames.matchesAnyOf(mockFrameworkPatterns) || + dependencyPathsSequence.any { checkDependencyIsFatJar(it) } + + if (!mockFrameworkFound) { + error(""" + Mock frameworks are not found in dependency path $dependencyPaths, dependency names are: + ${dependencyNames.joinToString(System.lineSeparator())} + """ + ) + } +} + +private fun Set.matchesAnyOf(patterns: List): Boolean { + val expressions = patterns.flatMap { it.moduleLibraryPatterns + it.libraryPatterns } + return any { libraryName -> + expressions.any { expr -> libraryName.let { expr.containsMatchIn(it) } } + } +} + +private fun findDependencyName(jarPath: String): String? { + try { + val attributes = JarFile(jarPath).manifest.mainAttributes + + val bundleName = attributes.getValue("Bundle-SymbolicName") + val bundleVersion = attributes.getValue("Bundle-Version") + val moduleName = attributes.getValue("Automatic-Module-Name") + val implementationTitle = attributes.getValue("Implementation-Title") + val implementationVersion = attributes.getValue("Implementation-Version") + + if (bundleName != null) return "$bundleName:$bundleVersion" + if (moduleName != null) return "$moduleName:$implementationTitle:$implementationVersion" + } catch (e: Exception) { + logger.warn { "Unexpected error during parsing $jarPath manifest file $e" } + } + + return null +} + +// We consider Fat JARs contain test frameworks and mock frameworks in the dependencies. +private fun checkDependencyIsFatJar(jarPath: String): Boolean { + try { + val attributes = JarFile(jarPath).manifest.mainAttributes + val jarType = attributes.getValue("JAR-Type") + + return jarType == "Fat JAR" + } catch (e: Exception) { + logger.warn { "Unexpected error during parsing $jarPath manifest file $e" } + } + + return false +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/GenerateTestsAndSarifReportFacade.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/GenerateTestsAndSarifReportFacade.kt index 5673f5f630..26547c212b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/GenerateTestsAndSarifReportFacade.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/GenerateTestsAndSarifReportFacade.kt @@ -1,15 +1,17 @@ package org.utbot.framework.plugin.sarif -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.NoStaticMocking -import org.utbot.framework.codegen.model.ModelBasedTestCodeGenerator -import org.utbot.framework.plugin.api.UtBotTestCaseGenerator -import org.utbot.framework.plugin.api.UtTestCase +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.NoStaticMocking +import org.utbot.framework.codegen.generator.CodeGenerator +import org.utbot.framework.codegen.generator.CodeGeneratorParams +import org.utbot.framework.codegen.services.language.CgLanguageAssistant +import org.utbot.framework.plugin.api.TestCaseGenerator +import org.utbot.framework.plugin.api.UtMethodTestSet +import org.utbot.framework.plugin.api.util.id import org.utbot.sarif.SarifReport import org.utbot.sarif.SourceFindingStrategy -import org.utbot.summary.summarize +import org.utbot.summary.summarizeAll import java.io.File -import java.net.URLClassLoader import java.nio.file.Path /** @@ -17,26 +19,20 @@ import java.nio.file.Path * Stores common logic between gradle and maven plugins. */ class GenerateTestsAndSarifReportFacade( - val sarifProperties: SarifExtensionProvider, - val sourceFindingStrategy: SourceFindingStrategy + private val sarifProperties: SarifExtensionProvider, + private val sourceFindingStrategy: SourceFindingStrategy, + private val testCaseGenerator: TestCaseGenerator, ) { - /** * Generates tests and a SARIF report for the class [targetClass]. * Requires withUtContext() { ... }. */ - fun generateForClass( - targetClass: TargetClassWrapper, - workingDirectory: Path, - runtimeClasspath: String - ) { - initializeEngine(runtimeClasspath, workingDirectory) - - val testCases = generateTestCases(targetClass, workingDirectory) - val testClassBody = generateTestCode(targetClass, testCases) + fun generateForClass(targetClass: TargetClassWrapper, workingDirectory: Path) { + val testSets = generateTestSets(targetClass, workingDirectory) + val testClassBody = generateTestCode(targetClass, testSets) targetClass.testsCodeFile.writeText(testClassBody) - generateReport(targetClass, testCases, testClassBody, sourceFindingStrategy) + generateReport(targetClass, testSets, testClassBody, sourceFindingStrategy) } companion object { @@ -53,50 +49,41 @@ class GenerateTestsAndSarifReportFacade( mergedSarifReportFile.writeText(mergedReport) if (verbose) { println("SARIF report was saved to \"${mergedSarifReportFile.path}\"") - println("You can open it using the VS Code extension \"Sarif Viewer\"") } } } - // internal + private fun generateTestSets(targetClass: TargetClassWrapper, workingDirectory: Path): List = + testCaseGenerator + .generate( + targetClass.targetMethods, + sarifProperties.mockStrategy, + sarifProperties.classesToMockAlways, + sarifProperties.generationTimeout + ).summarizeAll(workingDirectory, targetClass.sourceCodeFile) - private val dependencyPaths by lazy { - val thisClassLoader = this::class.java.classLoader as URLClassLoader - thisClassLoader.urLs.joinToString(File.pathSeparator) { it.path } - } - - private fun initializeEngine(classPath: String, workingDirectory: Path) { - UtBotTestCaseGenerator.init(workingDirectory, classPath, dependencyPaths) { false } - } - - private fun generateTestCases(targetClass: TargetClassWrapper, workingDirectory: Path): List = - UtBotTestCaseGenerator.generateForSeveralMethods( - targetClass.targetMethods(), - sarifProperties.mockStrategy, - sarifProperties.classesToMockAlways, - sarifProperties.generationTimeout - ).map { - it.summarize(targetClass.sourceCodeFile, workingDirectory) - } - - private fun generateTestCode(targetClass: TargetClassWrapper, testCases: List): String = + private fun generateTestCode(targetClass: TargetClassWrapper, testSets: List): String = initializeCodeGenerator(targetClass) - .generateAsString(testCases, targetClass.testsCodeFile.nameWithoutExtension) + .generateAsString(testSets, targetClass.testsCodeFile.nameWithoutExtension) - private fun initializeCodeGenerator(targetClass: TargetClassWrapper) = - ModelBasedTestCodeGenerator().apply { - val isNoStaticMocking = sarifProperties.staticsMocking is NoStaticMocking - val isForceStaticMocking = sarifProperties.forceStaticMocking == ForceStaticMocking.FORCE - init( - classUnderTest = targetClass.classUnderTest.java, + private fun initializeCodeGenerator(targetClass: TargetClassWrapper): CodeGenerator { + val isNoStaticMocking = sarifProperties.staticsMocking is NoStaticMocking + val isForceStaticMocking = sarifProperties.forceStaticMocking == ForceStaticMocking.FORCE + + return CodeGenerator( + CodeGeneratorParams( + classUnderTest = targetClass.classUnderTest.id, + projectType = sarifProperties.projectType, testFramework = sarifProperties.testFramework, mockFramework = sarifProperties.mockFramework, staticsMocking = sarifProperties.staticsMocking, forceStaticMocking = sarifProperties.forceStaticMocking, generateWarningsForStaticMocking = isNoStaticMocking && isForceStaticMocking, - codegenLanguage = sarifProperties.codegenLanguage + codegenLanguage = sarifProperties.codegenLanguage, + cgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage(sarifProperties.codegenLanguage), ) - } + ) + } /** * Creates a SARIF report for the class [targetClass]. @@ -104,11 +91,11 @@ class GenerateTestsAndSarifReportFacade( */ private fun generateReport( targetClass: TargetClassWrapper, - testCases: List, + testSets: List, testClassBody: String, sourceFinding: SourceFindingStrategy ) { - val sarifReport = SarifReport(testCases, testClassBody, sourceFinding).createReport() + val sarifReport = SarifReport(testSets, testClassBody, sourceFinding).createReport().toJson() targetClass.sarifReportFile.writeText(sarifReport) } } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/SarifExtensionProvider.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/SarifExtensionProvider.kt index 46e65a514a..f71370b066 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/SarifExtensionProvider.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/SarifExtensionProvider.kt @@ -1,7 +1,16 @@ package org.utbot.framework.plugin.sarif import org.utbot.engine.Mocker -import org.utbot.framework.codegen.* +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.Junit4 +import org.utbot.framework.codegen.domain.Junit5 +import org.utbot.framework.codegen.domain.MockitoStaticMocking +import org.utbot.framework.codegen.domain.NoStaticMocking +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.codegen.domain.ProjectType.* +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.codegen.domain.TestNg import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.MockFramework @@ -39,6 +48,13 @@ interface SarifExtensionProvider { */ val markGeneratedTestsDirectoryAsTestSourcesRoot: Boolean + /** + * Generate tests for private methods or not. + */ + val testPrivateMethods: Boolean + + val projectType: ProjectType + val testFramework: TestFramework val mockFramework: MockFramework @@ -64,6 +80,15 @@ interface SarifExtensionProvider { // transform functions + fun projectTypeParse(projectType: String): ProjectType = + when (projectType.toLowerCase()) { + "purejvm" -> PureJvm + "spring" -> Spring + "python" -> Python + "javascript" -> JavaScript + else -> error("Parameter projectType == '$projectType', but it can take only 'pureJvm', 'spring', 'python' or 'javascript'") + } + fun testFrameworkParse(testFramework: String): TestFramework = when (testFramework.toLowerCase()) { "junit4" -> Junit4 diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/TargetClassWrapper.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/TargetClassWrapper.kt index e0e51c2ed7..758c6953e9 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/TargetClassWrapper.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/TargetClassWrapper.kt @@ -1,10 +1,10 @@ package org.utbot.framework.plugin.sarif -import org.utbot.framework.plugin.api.UtMethod +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.util.isPrivate +import org.utbot.framework.plugin.api.util.executableId import java.io.File -import kotlin.reflect.KCallable import kotlin.reflect.KClass -import kotlin.reflect.jvm.kotlinFunction /** * Contains information about the class for which we are creating a SARIF report. @@ -14,13 +14,21 @@ data class TargetClassWrapper( val classUnderTest: KClass<*>, val sourceCodeFile: File, val testsCodeFile: File, - val sarifReportFile: File + val sarifReportFile: File, + val testPrivateMethods: Boolean = false ) { /** * Returns the methods of the class [classUnderTest] declared by the user. */ - fun targetMethods() = - classUnderTest.java.declaredMethods.map { - UtMethod(it.kotlinFunction as KCallable<*>, classUnderTest) + val targetMethods: List = run { + val allDeclaredMethods = classUnderTest.java.declaredMethods + val neededDeclaredMethods = if (testPrivateMethods) { + allDeclaredMethods.toList() + } else { + allDeclaredMethods.filter { + !it.executableId.isPrivate + } } + neededDeclaredMethods.map { it.executableId } + } } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/util/ClassUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/util/ClassUtil.kt index b8bdf1144f..a5858209f7 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/util/ClassUtil.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/sarif/util/ClassUtil.kt @@ -56,7 +56,7 @@ object ClassUtil { val clazz = classLoader.tryLoadClass(classFqn) ?: return null val sourceFileName = withUtContext(UtContext(classLoader)) { - Instrumenter.computeSourceFileName(clazz) // finds the file name in bytecode + Instrumenter.adapter.computeSourceFileName(clazz) // finds the file name in bytecode } ?: return null val candidates = sourceCodeFiles.filter { sourceCodeFile -> sourceCodeFile.endsWith(File(sourceFileName)) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt new file mode 100644 index 0000000000..2be3d2f84d --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt @@ -0,0 +1,323 @@ +package org.utbot.framework.process + +import com.jetbrains.rd.framework.IProtocol +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +import org.utbot.analytics.AnalyticsConfigureUtil +import org.utbot.common.* +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.HangingTestsTimeout +import org.utbot.framework.codegen.domain.MockitoStaticMocking +import org.utbot.framework.codegen.domain.NoStaticMocking +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.testFrameworkByName +import org.utbot.framework.codegen.generator.AbstractCodeGenerator +import org.utbot.framework.codegen.generator.CodeGeneratorParams +import org.utbot.framework.codegen.reports.TestsGenerationReport +import org.utbot.framework.codegen.services.language.CgLanguageAssistant +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.MethodDescription +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.method +import org.utbot.framework.plugin.api.utils.ClassNameUtils +import org.utbot.framework.plugin.services.JdkInfo +import org.utbot.framework.process.generated.* +import org.utbot.framework.process.kryo.KryoHelper +import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter +import org.utbot.rd.IdleWatchdog +import org.utbot.rd.ClientProtocolBuilder +import org.utbot.rd.RdSettingsContainerFactory +import org.utbot.rd.generated.settingsModel +import org.utbot.sarif.RdSourceFindingStrategyFacade +import org.utbot.sarif.SarifReport +import org.utbot.summary.summarizeAll +import org.utbot.taint.TaintConfigurationProviderUserRules +import java.io.File +import java.nio.file.Paths +import kotlin.reflect.jvm.kotlinFunction +import kotlin.time.Duration.Companion.seconds + +private val messageFromMainTimeoutMillis = 120.seconds +private val logger = KotlinLogging.logger {} + +@Suppress("unused") +object EngineProcessMain + +// use log4j2.configurationFile property to set log4j configuration +suspend fun main(args: Array) = runBlocking { + logger.info("-----------------------------------------------------------------------") + logger.info("-------------------NEW ENGINE PROCESS STARTED--------------------------") + logger.info("-----------------------------------------------------------------------") + + ClientProtocolBuilder().withProtocolTimeout(messageFromMainTimeoutMillis).start(args) { + AbstractSettings.setupFactory(RdSettingsContainerFactory(protocol.settingsModel)) + val kryoHelper = KryoHelper(lifetime) + engineProcessModel.setup(kryoHelper, it, protocol) + } +} + +private lateinit var testGenerator: TestCaseGenerator +private val testSets: MutableMap> = mutableMapOf() +private val testGenerationReports: MutableList = mutableListOf() +private var idCounter: Long = 0 + +private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatchdog, realProtocol: IProtocol) { + val model = this + watchdog.measureTimeForActiveCall(setupUtContext, "UtContext setup") { params -> + val urls = params.classpathForUrlsClassloader.map { File(it).toURI().toURL() }.toTypedArray() + // - First, we try to load class/resource with platform class loader `ClassLoader.getSystemClassLoader().parent` + // - at this step we load classes like + // - `java.util.ArrayList` (from boostrap classloader) + // - `javax.sql.DataSource` (from platform classloader) + // - Next, we try to load class/resource from user class path + // - at this step we load classes like class under test and other classes from user project and its dependencies + // - Finally, if all else fails we try to load class/resource from UtBot classpath + // - at this step we load classes from UtBot project and its dependencies (e.g. Mockito if user doesn't have it, see #2545) + val classLoader = FallbackClassLoader( + urls = urls, + fallback = ClassLoader.getSystemClassLoader(), + commonParent = ClassLoader.getSystemClassLoader().parent, + ) + UtContext.setUtContext(UtContext(classLoader)) + } + watchdog.measureTimeForActiveCall(createTestGenerator, "Creating Test Generator") { params -> + AnalyticsConfigureUtil.configureML() + Instrumenter.adapter = RdInstrumenter(realProtocol.rdInstrumenterAdapter) + val applicationContext: ApplicationContext = kryoHelper.readObject(params.applicationContext) + + testGenerator = TestCaseGenerator( + buildDirs = params.buildDir.map { Paths.get(it) }, + classpath = params.classpath, + dependencyPaths = params.dependencyPaths, + jdkInfo = JdkInfo(Paths.get(params.jdkInfo.path), params.jdkInfo.version), + applicationContext = applicationContext, + isCanceled = { + runBlocking { + model.isCancelled.startSuspending(Unit) + } + } + ) + } + watchdog.measureTimeForActiveCall(findTestClassName, "Creating test class name") { params -> + val classUnderTest: ClassId = kryoHelper.readObject(params.classUnderTest) + val testClassName = ClassNameUtils.generateTestClassShortName(classUnderTest) + TestClassNameResult(testClassName) + } + watchdog.measureTimeForActiveCall(generate, "Generating tests") { params -> + val methods: List = kryoHelper.readObject(params.methods) + logger.debug() + .measureTime({ "starting generation for ${methods.size} methods, starting with ${methods.first()}" }) { + val generateFlow = testFlow { + generationTimeout = params.generationTimeout + isSymbolicEngineEnabled = params.isSymbolicEngineEnabled + isFuzzingEnabled = params.isFuzzingEnabled + fuzzingValue = params.fuzzingValue + } + + val userTaintConfigurationProvider = params.taintConfigPath?.let { taintConfigPath -> + TaintConfigurationProviderUserRules(taintConfigPath) + } + + val result = testGenerator.generate( + methods, + MockStrategyApi.valueOf(params.mockStrategy), + kryoHelper.readObject(params.chosenClassesToMockAlways), + params.timeout, + userTaintConfigurationProvider, + generate = generateFlow, + ) + .summarizeAll(Paths.get(params.searchDirectory), null) + .filterNot { it.executions.isEmpty() && it.errors.isEmpty() } + + val id = ++idCounter + + testSets[id] = result + GenerateResult(result.size, id) + } + } + watchdog.measureTimeForActiveCall(render, "Rendering tests") { params -> + val codeGenerator = createCodeGenerator(kryoHelper, params, testGenerator.applicationContext) + + codeGenerator.generateAsStringWithTestReport(testSets[params.testSetsId]!!).let { + testGenerationReports.add(it.testsGenerationReport) + RenderResult(it.generatedCode, it.utilClassKind?.javaClass?.simpleName) + } + } + watchdog.measureTimeForActiveCall(obtainClassId, "Obtain class id in UtContext") { binaryName -> + kryoHelper.writeObject(UtContext.currentContext()!!.classLoader.loadClass(binaryName).id) + } + watchdog.measureTimeForActiveCall(findMethodsInClassMatchingSelected, "Find methods in Class") { params -> + val classId = kryoHelper.readObject(params.classId) + val selectedMethodDescriptions = + params.methodDescriptions.map { MethodDescription(it.name, it.containingClass, it.parametersTypes) } + FindMethodsInClassMatchingSelectedResult(kryoHelper.writeObject(classId.jClass.allNestedClasses.flatMap { clazz -> + clazz.id.allMethods.mapNotNull { it.method.kotlinFunction } + .sortedWith(compareBy { selectedMethodDescriptions.indexOf(it.methodDescription()) }) + .filter { it.methodDescription().normalized() in selectedMethodDescriptions }.map { it.executableId } + })) + } + watchdog.measureTimeForActiveCall(findMethodParamNames, "Find method parameters names") { params -> + val classId = kryoHelper.readObject(params.classId) + val bySignatureRaw = kryoHelper.readObject>>>(params.bySignature) + val byMethodDescription = bySignatureRaw.associate { it.first to it.second } + FindMethodParamNamesResult(kryoHelper.writeObject(classId.jClass.allNestedClasses.flatMap { clazz -> clazz.id.allMethods.mapNotNull { it.method.kotlinFunction } } + .mapNotNull { method -> byMethodDescription[method.methodDescription()]?.let { params -> method.executableId to params } } + .toMap())) + } + watchdog.measureTimeForActiveCall(writeSarifReport, "Writing Sarif report") { params -> + val reportFilePath = Paths.get(params.reportFilePath) + reportFilePath.parent.toFile().mkdirs() + val sarifReport = SarifReport( + testSets[params.testSetsId]!!, + params.generatedTestsCode, + RdSourceFindingStrategyFacade(params.testSetsId, realProtocol.rdSourceFindingStrategy) + ).createReport().toJson() + reportFilePath.toFile().writeText(sarifReport) + sarifReport + } + watchdog.measureTimeForActiveCall(generateTestReport, "Generating test report") { params -> + val eventLogMessage = params.eventLogMessage + val testPackageName: String? = params.testPackageName + var hasWarnings = false + val reports = testGenerationReports + if (reports.isEmpty()) return@measureTimeForActiveCall GenerateTestReportResult( + "No tests were generated", + null, + true + ) + val isMultiPackage = params.isMultiPackage + val (notifyMessage, statistics) = if (reports.size == 1) { + val report = reports.first() + processInitialWarnings(report, params) + + val message = buildString { + appendHtmlLine(report.toString(isShort = true)) + + val classUnderTestPackageName = report.classUnderTest.java.nameOfPackage + + destinationWarningMessage(testPackageName, classUnderTestPackageName)?.let { + hasWarnings = true + appendHtmlLine(it) + appendHtmlLine() + } + eventLogMessage?.let { + appendHtmlLine(it) + } + } + hasWarnings = hasWarnings || report.hasWarnings + Pair(message, report.detailedStatistics) + } else { + val accumulatedReport = reports.first() + processInitialWarnings(accumulatedReport, params) + + val message = buildString { + appendHtmlLine("${reports.sumOf { it.countTestMethods() }} tests generated for ${reports.size} classes.") + + if (accumulatedReport.initialWarnings.isNotEmpty()) { + accumulatedReport.initialWarnings.forEach { appendHtmlLine(it()) } + appendHtmlLine() + } + + // TODO maybe add statistics info here + + for (report in reports) { + val classUnderTestPackageName = report.classUnderTest.java.nameOfPackage + + hasWarnings = hasWarnings || report.hasWarnings + if (!isMultiPackage) { + val destinationWarning = destinationWarningMessage(testPackageName, classUnderTestPackageName) + if (destinationWarning != null) { + hasWarnings = true + appendHtmlLine(destinationWarning) + appendHtmlLine() + } + } + } + eventLogMessage?.let { + appendHtmlLine(it) + } + } + + Pair(message, null) + } + GenerateTestReportResult(notifyMessage, statistics, hasWarnings) + } + watchdog.measureTimeForActiveCall(perform, "Performing dynamic task") { params -> + val task = kryoHelper.readObject>(params.engineProcessTask) + val result = task.perform() + kryoHelper.writeObject(result) + } +} + +private fun processInitialWarnings(report: TestsGenerationReport, params: GenerateTestReportArgs) { + val hasInitialWarnings = params.hasInitialWarnings + + if (!hasInitialWarnings) { + return + } + + report.apply { + params.forceMockWarning?.let { + initialWarnings.add { it } + } + params.forceStaticMockWarnings?.let { + initialWarnings.add { it } + } + params.testFrameworkWarning?.let { + initialWarnings.add { it } + } + } +} + +private fun destinationWarningMessage(testPackageName: String?, classUnderTestPackageName: String): String? { + return if (!testPackageName.isNullOrEmpty() && classUnderTestPackageName != testPackageName) { + """ + Warning: Destination package $testPackageName does not match package of the class $classUnderTestPackageName. + This may cause unnecessary usage of reflection for protected or package-private fields and methods access. + """.trimIndent() + } else { + null + } +} + +private fun createCodeGenerator(kryoHelper: KryoHelper, params: RenderParams, applicationContext: ApplicationContext): AbstractCodeGenerator { + with(params) { + val classUnderTest: ClassId = kryoHelper.readObject(classUnderTest) + val paramNames: MutableMap> = kryoHelper.readObject(paramNames) + val testFramework = testFrameworkByName(testFramework) + val staticMocking = if (staticsMocking.startsWith("No")) NoStaticMocking else MockitoStaticMocking + val forceStaticMocking: ForceStaticMocking = kryoHelper.readObject(forceStaticMocking) + val projectType = ProjectType.valueOf(projectType) + + return applicationContext.createCodeGenerator(CodeGeneratorParams( + classUnderTest = classUnderTest, + projectType = projectType, + generateUtilClassFile = generateUtilClassFile, + paramNames = paramNames, + testFramework = testFramework, + mockFramework = MockFramework.valueOf(mockFramework), + codegenLanguage = CodegenLanguage.valueOf(codegenLanguage), + cgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage( + CodegenLanguage.valueOf( + codegenLanguage + ) + ), + parameterizedTestSource = ParametrizedTestSource.valueOf(parameterizedTestSource), + staticsMocking = staticMocking, + forceStaticMocking = forceStaticMocking, + generateWarningsForStaticMocking = generateWarningsForStaticMocking, + runtimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.valueOf( + runtimeExceptionTestsBehaviour + ), + hangingTestsTimeout = HangingTestsTimeout(hangingTestsTimeout), + enableTestsTimeout = enableTestsTimeout, + testClassPackageName = testClassPackageName, + )) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessTask.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessTask.kt new file mode 100644 index 0000000000..d42c3f65b0 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessTask.kt @@ -0,0 +1,14 @@ +package org.utbot.framework.process + +/** + * Implementations of this interface can be passed to engine process for execution and should + * be used for adding feature-specific (e.g. Spring-specific) tasks without inflating core UtBot codebase. + * + * Such tasks are serialised with kryo when passed between processes, meaning that for successful execution same + * implementation of [EngineProcessTask] should be present on the classpath of both parent process and engine process. + * + * @param R result type of the task (should be present on the classpath of both processes). + */ +interface EngineProcessTask { + fun perform(): R +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/RdInstrumenter.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/RdInstrumenter.kt new file mode 100644 index 0000000000..e7d033181b --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/RdInstrumenter.kt @@ -0,0 +1,30 @@ +package org.utbot.framework.process + +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +import org.utbot.common.logException +import org.utbot.framework.process.generated.ComputeSourceFileByClassArguments +import org.utbot.framework.process.generated.RdInstrumenterAdapter +import org.utbot.instrumentation.instrumentation.instrumenter.InstrumenterAdapter +import org.utbot.rd.startBlocking +import java.io.File +import java.nio.file.Path + +private val logger = KotlinLogging.logger { } + +class RdInstrumenter(private val rdInstrumenterAdapter: RdInstrumenterAdapter) : InstrumenterAdapter() { + override fun computeSourceFileByClass( + clazz: Class<*>, + directoryToSearchRecursively: Path + ): File? { + val canonicalClassName = clazz.canonicalName + logger.debug { "starting computeSourceFileByClass for class - $canonicalClassName" } + val result = logger.logException { + val arguments = ComputeSourceFileByClassArguments(canonicalClassName) + + rdInstrumenterAdapter.computeSourceFileByClass.startBlocking(arguments) + } + logger.debug { "computeSourceFileByClass result for $canonicalClassName from idea: $result" } + return result?.let { File(it) } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt new file mode 100644 index 0000000000..c399ead4df --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt @@ -0,0 +1,1483 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.framework.process.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [EngineProcessModel.kt:31] + */ +class EngineProcessModel private constructor( + private val _setupUtContext: RdCall, + private val _createTestGenerator: RdCall, + private val _isCancelled: RdCall, + private val _findTestClassName: RdCall, + private val _generate: RdCall, + private val _render: RdCall, + private val _obtainClassId: RdCall, + private val _findMethodsInClassMatchingSelected: RdCall, + private val _findMethodParamNames: RdCall, + private val _writeSarifReport: RdCall, + private val _generateTestReport: RdCall, + private val _perform: RdCall +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + serializers.register(JdkInfo) + serializers.register(TestGeneratorParams) + serializers.register(TestClassNameParams) + serializers.register(TestClassNameResult) + serializers.register(GenerateParams) + serializers.register(GenerateResult) + serializers.register(RenderParams) + serializers.register(RenderResult) + serializers.register(SetupContextParams) + serializers.register(MethodDescription) + serializers.register(FindMethodsInClassMatchingSelectedArguments) + serializers.register(FindMethodsInClassMatchingSelectedResult) + serializers.register(FindMethodParamNamesArguments) + serializers.register(FindMethodParamNamesResult) + serializers.register(WriteSarifReportArguments) + serializers.register(GenerateTestReportArgs) + serializers.register(GenerateTestReportResult) + serializers.register(PerformParams) + } + + + @JvmStatic + @JvmName("internalCreateModel") + @Deprecated("Use create instead", ReplaceWith("create(lifetime, protocol)")) + internal fun createModel(lifetime: Lifetime, protocol: IProtocol): EngineProcessModel { + @Suppress("DEPRECATION") + return create(lifetime, protocol) + } + + @JvmStatic + @Deprecated("Use protocol.engineProcessModel or revise the extension scope instead", ReplaceWith("protocol.engineProcessModel")) + fun create(lifetime: Lifetime, protocol: IProtocol): EngineProcessModel { + EngineProcessRoot.register(protocol.serializers) + + return EngineProcessModel() + } + + + const val serializationHash = 7072495177628793247L + + } + override val serializersOwner: ISerializersOwner get() = EngineProcessModel + override val serializationHash: Long get() = EngineProcessModel.serializationHash + + //fields + val setupUtContext: RdCall get() = _setupUtContext + val createTestGenerator: RdCall get() = _createTestGenerator + val isCancelled: RdCall get() = _isCancelled + val findTestClassName: RdCall get() = _findTestClassName + val generate: RdCall get() = _generate + val render: RdCall get() = _render + val obtainClassId: RdCall get() = _obtainClassId + val findMethodsInClassMatchingSelected: RdCall get() = _findMethodsInClassMatchingSelected + val findMethodParamNames: RdCall get() = _findMethodParamNames + val writeSarifReport: RdCall get() = _writeSarifReport + val generateTestReport: RdCall get() = _generateTestReport + val perform: RdCall get() = _perform + //methods + //initializer + init { + _setupUtContext.async = true + _createTestGenerator.async = true + _isCancelled.async = true + _findTestClassName.async = true + _generate.async = true + _render.async = true + _obtainClassId.async = true + _findMethodsInClassMatchingSelected.async = true + _findMethodParamNames.async = true + _writeSarifReport.async = true + _generateTestReport.async = true + _perform.async = true + } + + init { + bindableChildren.add("setupUtContext" to _setupUtContext) + bindableChildren.add("createTestGenerator" to _createTestGenerator) + bindableChildren.add("isCancelled" to _isCancelled) + bindableChildren.add("findTestClassName" to _findTestClassName) + bindableChildren.add("generate" to _generate) + bindableChildren.add("render" to _render) + bindableChildren.add("obtainClassId" to _obtainClassId) + bindableChildren.add("findMethodsInClassMatchingSelected" to _findMethodsInClassMatchingSelected) + bindableChildren.add("findMethodParamNames" to _findMethodParamNames) + bindableChildren.add("writeSarifReport" to _writeSarifReport) + bindableChildren.add("generateTestReport" to _generateTestReport) + bindableChildren.add("perform" to _perform) + } + + //secondary constructor + private constructor( + ) : this( + RdCall(SetupContextParams, FrameworkMarshallers.Void), + RdCall(TestGeneratorParams, FrameworkMarshallers.Void), + RdCall(FrameworkMarshallers.Void, FrameworkMarshallers.Bool), + RdCall(TestClassNameParams, TestClassNameResult), + RdCall(GenerateParams, GenerateResult), + RdCall(RenderParams, RenderResult), + RdCall(FrameworkMarshallers.String, FrameworkMarshallers.ByteArray), + RdCall(FindMethodsInClassMatchingSelectedArguments, FindMethodsInClassMatchingSelectedResult), + RdCall(FindMethodParamNamesArguments, FindMethodParamNamesResult), + RdCall(WriteSarifReportArguments, FrameworkMarshallers.String), + RdCall(GenerateTestReportArgs, GenerateTestReportResult), + RdCall(PerformParams, FrameworkMarshallers.ByteArray) + ) + + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("EngineProcessModel (") + printer.indent { + print("setupUtContext = "); _setupUtContext.print(printer); println() + print("createTestGenerator = "); _createTestGenerator.print(printer); println() + print("isCancelled = "); _isCancelled.print(printer); println() + print("findTestClassName = "); _findTestClassName.print(printer); println() + print("generate = "); _generate.print(printer); println() + print("render = "); _render.print(printer); println() + print("obtainClassId = "); _obtainClassId.print(printer); println() + print("findMethodsInClassMatchingSelected = "); _findMethodsInClassMatchingSelected.print(printer); println() + print("findMethodParamNames = "); _findMethodParamNames.print(printer); println() + print("writeSarifReport = "); _writeSarifReport.print(printer); println() + print("generateTestReport = "); _generateTestReport.print(printer); println() + print("perform = "); _perform.print(printer); println() + } + printer.print(")") + } + //deepClone + override fun deepClone(): EngineProcessModel { + return EngineProcessModel( + _setupUtContext.deepClonePolymorphic(), + _createTestGenerator.deepClonePolymorphic(), + _isCancelled.deepClonePolymorphic(), + _findTestClassName.deepClonePolymorphic(), + _generate.deepClonePolymorphic(), + _render.deepClonePolymorphic(), + _obtainClassId.deepClonePolymorphic(), + _findMethodsInClassMatchingSelected.deepClonePolymorphic(), + _findMethodParamNames.deepClonePolymorphic(), + _writeSarifReport.deepClonePolymorphic(), + _generateTestReport.deepClonePolymorphic(), + _perform.deepClonePolymorphic() + ) + } + //contexts +} +val IProtocol.engineProcessModel get() = getOrCreateExtension(EngineProcessModel::class) { @Suppress("DEPRECATION") EngineProcessModel.create(lifetime, this) } + + + +/** + * #### Generated from [EngineProcessModel.kt:106] + */ +data class FindMethodParamNamesArguments ( + val classId: ByteArray, + val bySignature: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = FindMethodParamNamesArguments::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): FindMethodParamNamesArguments { + val classId = buffer.readByteArray() + val bySignature = buffer.readByteArray() + return FindMethodParamNamesArguments(classId, bySignature) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: FindMethodParamNamesArguments) { + buffer.writeByteArray(value.classId) + buffer.writeByteArray(value.bySignature) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as FindMethodParamNamesArguments + + if (!(classId contentEquals other.classId)) return false + if (!(bySignature contentEquals other.bySignature)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + classId.contentHashCode() + __r = __r*31 + bySignature.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("FindMethodParamNamesArguments (") + printer.indent { + print("classId = "); classId.print(printer); println() + print("bySignature = "); bySignature.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:110] + */ +data class FindMethodParamNamesResult ( + val paramNames: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = FindMethodParamNamesResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): FindMethodParamNamesResult { + val paramNames = buffer.readByteArray() + return FindMethodParamNamesResult(paramNames) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: FindMethodParamNamesResult) { + buffer.writeByteArray(value.paramNames) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as FindMethodParamNamesResult + + if (!(paramNames contentEquals other.paramNames)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + paramNames.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("FindMethodParamNamesResult (") + printer.indent { + print("paramNames = "); paramNames.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:99] + */ +data class FindMethodsInClassMatchingSelectedArguments ( + val classId: ByteArray, + val methodDescriptions: List +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = FindMethodsInClassMatchingSelectedArguments::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): FindMethodsInClassMatchingSelectedArguments { + val classId = buffer.readByteArray() + val methodDescriptions = buffer.readList { MethodDescription.read(ctx, buffer) } + return FindMethodsInClassMatchingSelectedArguments(classId, methodDescriptions) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: FindMethodsInClassMatchingSelectedArguments) { + buffer.writeByteArray(value.classId) + buffer.writeList(value.methodDescriptions) { v -> MethodDescription.write(ctx, buffer, v) } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as FindMethodsInClassMatchingSelectedArguments + + if (!(classId contentEquals other.classId)) return false + if (methodDescriptions != other.methodDescriptions) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + classId.contentHashCode() + __r = __r*31 + methodDescriptions.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("FindMethodsInClassMatchingSelectedArguments (") + printer.indent { + print("classId = "); classId.print(printer); println() + print("methodDescriptions = "); methodDescriptions.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:103] + */ +data class FindMethodsInClassMatchingSelectedResult ( + val executableIds: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = FindMethodsInClassMatchingSelectedResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): FindMethodsInClassMatchingSelectedResult { + val executableIds = buffer.readByteArray() + return FindMethodsInClassMatchingSelectedResult(executableIds) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: FindMethodsInClassMatchingSelectedResult) { + buffer.writeByteArray(value.executableIds) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as FindMethodsInClassMatchingSelectedResult + + if (!(executableIds contentEquals other.executableIds)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + executableIds.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("FindMethodsInClassMatchingSelectedResult (") + printer.indent { + print("executableIds = "); executableIds.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:49] + */ +data class GenerateParams ( + val methods: ByteArray, + val mockStrategy: String, + val chosenClassesToMockAlways: ByteArray, + val timeout: Long, + val generationTimeout: Long, + val isSymbolicEngineEnabled: Boolean, + val isFuzzingEnabled: Boolean, + val fuzzingValue: Double, + val searchDirectory: String, + val taintConfigPath: String? +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = GenerateParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): GenerateParams { + val methods = buffer.readByteArray() + val mockStrategy = buffer.readString() + val chosenClassesToMockAlways = buffer.readByteArray() + val timeout = buffer.readLong() + val generationTimeout = buffer.readLong() + val isSymbolicEngineEnabled = buffer.readBool() + val isFuzzingEnabled = buffer.readBool() + val fuzzingValue = buffer.readDouble() + val searchDirectory = buffer.readString() + val taintConfigPath = buffer.readNullable { buffer.readString() } + return GenerateParams(methods, mockStrategy, chosenClassesToMockAlways, timeout, generationTimeout, isSymbolicEngineEnabled, isFuzzingEnabled, fuzzingValue, searchDirectory, taintConfigPath) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GenerateParams) { + buffer.writeByteArray(value.methods) + buffer.writeString(value.mockStrategy) + buffer.writeByteArray(value.chosenClassesToMockAlways) + buffer.writeLong(value.timeout) + buffer.writeLong(value.generationTimeout) + buffer.writeBool(value.isSymbolicEngineEnabled) + buffer.writeBool(value.isFuzzingEnabled) + buffer.writeDouble(value.fuzzingValue) + buffer.writeString(value.searchDirectory) + buffer.writeNullable(value.taintConfigPath) { buffer.writeString(it) } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as GenerateParams + + if (!(methods contentEquals other.methods)) return false + if (mockStrategy != other.mockStrategy) return false + if (!(chosenClassesToMockAlways contentEquals other.chosenClassesToMockAlways)) return false + if (timeout != other.timeout) return false + if (generationTimeout != other.generationTimeout) return false + if (isSymbolicEngineEnabled != other.isSymbolicEngineEnabled) return false + if (isFuzzingEnabled != other.isFuzzingEnabled) return false + if (fuzzingValue != other.fuzzingValue) return false + if (searchDirectory != other.searchDirectory) return false + if (taintConfigPath != other.taintConfigPath) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + methods.contentHashCode() + __r = __r*31 + mockStrategy.hashCode() + __r = __r*31 + chosenClassesToMockAlways.contentHashCode() + __r = __r*31 + timeout.hashCode() + __r = __r*31 + generationTimeout.hashCode() + __r = __r*31 + isSymbolicEngineEnabled.hashCode() + __r = __r*31 + isFuzzingEnabled.hashCode() + __r = __r*31 + fuzzingValue.hashCode() + __r = __r*31 + searchDirectory.hashCode() + __r = __r*31 + if (taintConfigPath != null) taintConfigPath.hashCode() else 0 + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("GenerateParams (") + printer.indent { + print("methods = "); methods.print(printer); println() + print("mockStrategy = "); mockStrategy.print(printer); println() + print("chosenClassesToMockAlways = "); chosenClassesToMockAlways.print(printer); println() + print("timeout = "); timeout.print(printer); println() + print("generationTimeout = "); generationTimeout.print(printer); println() + print("isSymbolicEngineEnabled = "); isSymbolicEngineEnabled.print(printer); println() + print("isFuzzingEnabled = "); isFuzzingEnabled.print(printer); println() + print("fuzzingValue = "); fuzzingValue.print(printer); println() + print("searchDirectory = "); searchDirectory.print(printer); println() + print("taintConfigPath = "); taintConfigPath.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:65] + */ +data class GenerateResult ( + val notEmptyCases: Int, + val testSetsId: Long +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = GenerateResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): GenerateResult { + val notEmptyCases = buffer.readInt() + val testSetsId = buffer.readLong() + return GenerateResult(notEmptyCases, testSetsId) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GenerateResult) { + buffer.writeInt(value.notEmptyCases) + buffer.writeLong(value.testSetsId) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as GenerateResult + + if (notEmptyCases != other.notEmptyCases) return false + if (testSetsId != other.testSetsId) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + notEmptyCases.hashCode() + __r = __r*31 + testSetsId.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("GenerateResult (") + printer.indent { + print("notEmptyCases = "); notEmptyCases.print(printer); println() + print("testSetsId = "); testSetsId.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:118] + */ +data class GenerateTestReportArgs ( + val eventLogMessage: String?, + val testPackageName: String?, + val isMultiPackage: Boolean, + val forceMockWarning: String?, + val forceStaticMockWarnings: String?, + val testFrameworkWarning: String?, + val hasInitialWarnings: Boolean +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = GenerateTestReportArgs::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): GenerateTestReportArgs { + val eventLogMessage = buffer.readNullable { buffer.readString() } + val testPackageName = buffer.readNullable { buffer.readString() } + val isMultiPackage = buffer.readBool() + val forceMockWarning = buffer.readNullable { buffer.readString() } + val forceStaticMockWarnings = buffer.readNullable { buffer.readString() } + val testFrameworkWarning = buffer.readNullable { buffer.readString() } + val hasInitialWarnings = buffer.readBool() + return GenerateTestReportArgs(eventLogMessage, testPackageName, isMultiPackage, forceMockWarning, forceStaticMockWarnings, testFrameworkWarning, hasInitialWarnings) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GenerateTestReportArgs) { + buffer.writeNullable(value.eventLogMessage) { buffer.writeString(it) } + buffer.writeNullable(value.testPackageName) { buffer.writeString(it) } + buffer.writeBool(value.isMultiPackage) + buffer.writeNullable(value.forceMockWarning) { buffer.writeString(it) } + buffer.writeNullable(value.forceStaticMockWarnings) { buffer.writeString(it) } + buffer.writeNullable(value.testFrameworkWarning) { buffer.writeString(it) } + buffer.writeBool(value.hasInitialWarnings) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as GenerateTestReportArgs + + if (eventLogMessage != other.eventLogMessage) return false + if (testPackageName != other.testPackageName) return false + if (isMultiPackage != other.isMultiPackage) return false + if (forceMockWarning != other.forceMockWarning) return false + if (forceStaticMockWarnings != other.forceStaticMockWarnings) return false + if (testFrameworkWarning != other.testFrameworkWarning) return false + if (hasInitialWarnings != other.hasInitialWarnings) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + if (eventLogMessage != null) eventLogMessage.hashCode() else 0 + __r = __r*31 + if (testPackageName != null) testPackageName.hashCode() else 0 + __r = __r*31 + isMultiPackage.hashCode() + __r = __r*31 + if (forceMockWarning != null) forceMockWarning.hashCode() else 0 + __r = __r*31 + if (forceStaticMockWarnings != null) forceStaticMockWarnings.hashCode() else 0 + __r = __r*31 + if (testFrameworkWarning != null) testFrameworkWarning.hashCode() else 0 + __r = __r*31 + hasInitialWarnings.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("GenerateTestReportArgs (") + printer.indent { + print("eventLogMessage = "); eventLogMessage.print(printer); println() + print("testPackageName = "); testPackageName.print(printer); println() + print("isMultiPackage = "); isMultiPackage.print(printer); println() + print("forceMockWarning = "); forceMockWarning.print(printer); println() + print("forceStaticMockWarnings = "); forceStaticMockWarnings.print(printer); println() + print("testFrameworkWarning = "); testFrameworkWarning.print(printer); println() + print("hasInitialWarnings = "); hasInitialWarnings.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:127] + */ +data class GenerateTestReportResult ( + val notifyMessage: String, + val statistics: String?, + val hasWarnings: Boolean +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = GenerateTestReportResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): GenerateTestReportResult { + val notifyMessage = buffer.readString() + val statistics = buffer.readNullable { buffer.readString() } + val hasWarnings = buffer.readBool() + return GenerateTestReportResult(notifyMessage, statistics, hasWarnings) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GenerateTestReportResult) { + buffer.writeString(value.notifyMessage) + buffer.writeNullable(value.statistics) { buffer.writeString(it) } + buffer.writeBool(value.hasWarnings) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as GenerateTestReportResult + + if (notifyMessage != other.notifyMessage) return false + if (statistics != other.statistics) return false + if (hasWarnings != other.hasWarnings) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + notifyMessage.hashCode() + __r = __r*31 + if (statistics != null) statistics.hashCode() else 0 + __r = __r*31 + hasWarnings.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("GenerateTestReportResult (") + printer.indent { + print("notifyMessage = "); notifyMessage.print(printer); println() + print("statistics = "); statistics.print(printer); println() + print("hasWarnings = "); hasWarnings.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:32] + */ +data class JdkInfo ( + val path: String, + val version: Int +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = JdkInfo::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): JdkInfo { + val path = buffer.readString() + val version = buffer.readInt() + return JdkInfo(path, version) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: JdkInfo) { + buffer.writeString(value.path) + buffer.writeInt(value.version) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as JdkInfo + + if (path != other.path) return false + if (version != other.version) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + path.hashCode() + __r = __r*31 + version.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("JdkInfo (") + printer.indent { + print("path = "); path.print(printer); println() + print("version = "); version.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:94] + */ +data class MethodDescription ( + val name: String, + val containingClass: String?, + val parametersTypes: List +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = MethodDescription::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): MethodDescription { + val name = buffer.readString() + val containingClass = buffer.readNullable { buffer.readString() } + val parametersTypes = buffer.readList { buffer.readNullable { buffer.readString() } } + return MethodDescription(name, containingClass, parametersTypes) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: MethodDescription) { + buffer.writeString(value.name) + buffer.writeNullable(value.containingClass) { buffer.writeString(it) } + buffer.writeList(value.parametersTypes) { v -> buffer.writeNullable(v) { buffer.writeString(it) } } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as MethodDescription + + if (name != other.name) return false + if (containingClass != other.containingClass) return false + if (parametersTypes != other.parametersTypes) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + name.hashCode() + __r = __r*31 + if (containingClass != null) containingClass.hashCode() else 0 + __r = __r*31 + parametersTypes.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("MethodDescription (") + printer.indent { + print("name = "); name.print(printer); println() + print("containingClass = "); containingClass.print(printer); println() + print("parametersTypes = "); parametersTypes.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:132] + */ +data class PerformParams ( + val engineProcessTask: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = PerformParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): PerformParams { + val engineProcessTask = buffer.readByteArray() + return PerformParams(engineProcessTask) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: PerformParams) { + buffer.writeByteArray(value.engineProcessTask) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as PerformParams + + if (!(engineProcessTask contentEquals other.engineProcessTask)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + engineProcessTask.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("PerformParams (") + printer.indent { + print("engineProcessTask = "); engineProcessTask.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:69] + */ +data class RenderParams ( + val testSetsId: Long, + val classUnderTest: ByteArray, + val projectType: String, + val paramNames: ByteArray, + val generateUtilClassFile: Boolean, + val testFramework: String, + val mockFramework: String, + val codegenLanguage: String, + val parameterizedTestSource: String, + val staticsMocking: String, + val forceStaticMocking: ByteArray, + val generateWarningsForStaticMocking: Boolean, + val runtimeExceptionTestsBehaviour: String, + val hangingTestsTimeout: Long, + val enableTestsTimeout: Boolean, + val testClassPackageName: String +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = RenderParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): RenderParams { + val testSetsId = buffer.readLong() + val classUnderTest = buffer.readByteArray() + val projectType = buffer.readString() + val paramNames = buffer.readByteArray() + val generateUtilClassFile = buffer.readBool() + val testFramework = buffer.readString() + val mockFramework = buffer.readString() + val codegenLanguage = buffer.readString() + val parameterizedTestSource = buffer.readString() + val staticsMocking = buffer.readString() + val forceStaticMocking = buffer.readByteArray() + val generateWarningsForStaticMocking = buffer.readBool() + val runtimeExceptionTestsBehaviour = buffer.readString() + val hangingTestsTimeout = buffer.readLong() + val enableTestsTimeout = buffer.readBool() + val testClassPackageName = buffer.readString() + return RenderParams(testSetsId, classUnderTest, projectType, paramNames, generateUtilClassFile, testFramework, mockFramework, codegenLanguage, parameterizedTestSource, staticsMocking, forceStaticMocking, generateWarningsForStaticMocking, runtimeExceptionTestsBehaviour, hangingTestsTimeout, enableTestsTimeout, testClassPackageName) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: RenderParams) { + buffer.writeLong(value.testSetsId) + buffer.writeByteArray(value.classUnderTest) + buffer.writeString(value.projectType) + buffer.writeByteArray(value.paramNames) + buffer.writeBool(value.generateUtilClassFile) + buffer.writeString(value.testFramework) + buffer.writeString(value.mockFramework) + buffer.writeString(value.codegenLanguage) + buffer.writeString(value.parameterizedTestSource) + buffer.writeString(value.staticsMocking) + buffer.writeByteArray(value.forceStaticMocking) + buffer.writeBool(value.generateWarningsForStaticMocking) + buffer.writeString(value.runtimeExceptionTestsBehaviour) + buffer.writeLong(value.hangingTestsTimeout) + buffer.writeBool(value.enableTestsTimeout) + buffer.writeString(value.testClassPackageName) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as RenderParams + + if (testSetsId != other.testSetsId) return false + if (!(classUnderTest contentEquals other.classUnderTest)) return false + if (projectType != other.projectType) return false + if (!(paramNames contentEquals other.paramNames)) return false + if (generateUtilClassFile != other.generateUtilClassFile) return false + if (testFramework != other.testFramework) return false + if (mockFramework != other.mockFramework) return false + if (codegenLanguage != other.codegenLanguage) return false + if (parameterizedTestSource != other.parameterizedTestSource) return false + if (staticsMocking != other.staticsMocking) return false + if (!(forceStaticMocking contentEquals other.forceStaticMocking)) return false + if (generateWarningsForStaticMocking != other.generateWarningsForStaticMocking) return false + if (runtimeExceptionTestsBehaviour != other.runtimeExceptionTestsBehaviour) return false + if (hangingTestsTimeout != other.hangingTestsTimeout) return false + if (enableTestsTimeout != other.enableTestsTimeout) return false + if (testClassPackageName != other.testClassPackageName) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + testSetsId.hashCode() + __r = __r*31 + classUnderTest.contentHashCode() + __r = __r*31 + projectType.hashCode() + __r = __r*31 + paramNames.contentHashCode() + __r = __r*31 + generateUtilClassFile.hashCode() + __r = __r*31 + testFramework.hashCode() + __r = __r*31 + mockFramework.hashCode() + __r = __r*31 + codegenLanguage.hashCode() + __r = __r*31 + parameterizedTestSource.hashCode() + __r = __r*31 + staticsMocking.hashCode() + __r = __r*31 + forceStaticMocking.contentHashCode() + __r = __r*31 + generateWarningsForStaticMocking.hashCode() + __r = __r*31 + runtimeExceptionTestsBehaviour.hashCode() + __r = __r*31 + hangingTestsTimeout.hashCode() + __r = __r*31 + enableTestsTimeout.hashCode() + __r = __r*31 + testClassPackageName.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("RenderParams (") + printer.indent { + print("testSetsId = "); testSetsId.print(printer); println() + print("classUnderTest = "); classUnderTest.print(printer); println() + print("projectType = "); projectType.print(printer); println() + print("paramNames = "); paramNames.print(printer); println() + print("generateUtilClassFile = "); generateUtilClassFile.print(printer); println() + print("testFramework = "); testFramework.print(printer); println() + print("mockFramework = "); mockFramework.print(printer); println() + print("codegenLanguage = "); codegenLanguage.print(printer); println() + print("parameterizedTestSource = "); parameterizedTestSource.print(printer); println() + print("staticsMocking = "); staticsMocking.print(printer); println() + print("forceStaticMocking = "); forceStaticMocking.print(printer); println() + print("generateWarningsForStaticMocking = "); generateWarningsForStaticMocking.print(printer); println() + print("runtimeExceptionTestsBehaviour = "); runtimeExceptionTestsBehaviour.print(printer); println() + print("hangingTestsTimeout = "); hangingTestsTimeout.print(printer); println() + print("enableTestsTimeout = "); enableTestsTimeout.print(printer); println() + print("testClassPackageName = "); testClassPackageName.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:87] + */ +data class RenderResult ( + val generatedCode: String, + val utilClassKind: String? +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = RenderResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): RenderResult { + val generatedCode = buffer.readString() + val utilClassKind = buffer.readNullable { buffer.readString() } + return RenderResult(generatedCode, utilClassKind) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: RenderResult) { + buffer.writeString(value.generatedCode) + buffer.writeNullable(value.utilClassKind) { buffer.writeString(it) } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as RenderResult + + if (generatedCode != other.generatedCode) return false + if (utilClassKind != other.utilClassKind) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + generatedCode.hashCode() + __r = __r*31 + if (utilClassKind != null) utilClassKind.hashCode() else 0 + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("RenderResult (") + printer.indent { + print("generatedCode = "); generatedCode.print(printer); println() + print("utilClassKind = "); utilClassKind.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:91] + */ +data class SetupContextParams ( + val classpathForUrlsClassloader: List +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = SetupContextParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SetupContextParams { + val classpathForUrlsClassloader = buffer.readList { buffer.readString() } + return SetupContextParams(classpathForUrlsClassloader) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SetupContextParams) { + buffer.writeList(value.classpathForUrlsClassloader) { v -> buffer.writeString(v) } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as SetupContextParams + + if (classpathForUrlsClassloader != other.classpathForUrlsClassloader) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + classpathForUrlsClassloader.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SetupContextParams (") + printer.indent { + print("classpathForUrlsClassloader = "); classpathForUrlsClassloader.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:43] + */ +data class TestClassNameParams ( + val classUnderTest: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = TestClassNameParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): TestClassNameParams { + val classUnderTest = buffer.readByteArray() + return TestClassNameParams(classUnderTest) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: TestClassNameParams) { + buffer.writeByteArray(value.classUnderTest) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as TestClassNameParams + + if (!(classUnderTest contentEquals other.classUnderTest)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + classUnderTest.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("TestClassNameParams (") + printer.indent { + print("classUnderTest = "); classUnderTest.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:46] + */ +data class TestClassNameResult ( + val testClassName: String +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = TestClassNameResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): TestClassNameResult { + val testClassName = buffer.readString() + return TestClassNameResult(testClassName) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: TestClassNameResult) { + buffer.writeString(value.testClassName) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as TestClassNameResult + + if (testClassName != other.testClassName) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + testClassName.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("TestClassNameResult (") + printer.indent { + print("testClassName = "); testClassName.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:36] + */ +data class TestGeneratorParams ( + val buildDir: Array, + val classpath: String?, + val dependencyPaths: String, + val jdkInfo: JdkInfo, + val applicationContext: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = TestGeneratorParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): TestGeneratorParams { + val buildDir = buffer.readArray {buffer.readString()} + val classpath = buffer.readNullable { buffer.readString() } + val dependencyPaths = buffer.readString() + val jdkInfo = JdkInfo.read(ctx, buffer) + val applicationContext = buffer.readByteArray() + return TestGeneratorParams(buildDir, classpath, dependencyPaths, jdkInfo, applicationContext) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: TestGeneratorParams) { + buffer.writeArray(value.buildDir) { buffer.writeString(it) } + buffer.writeNullable(value.classpath) { buffer.writeString(it) } + buffer.writeString(value.dependencyPaths) + JdkInfo.write(ctx, buffer, value.jdkInfo) + buffer.writeByteArray(value.applicationContext) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as TestGeneratorParams + + if (!(buildDir contentDeepEquals other.buildDir)) return false + if (classpath != other.classpath) return false + if (dependencyPaths != other.dependencyPaths) return false + if (jdkInfo != other.jdkInfo) return false + if (!(applicationContext contentEquals other.applicationContext)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + buildDir.contentDeepHashCode() + __r = __r*31 + if (classpath != null) classpath.hashCode() else 0 + __r = __r*31 + dependencyPaths.hashCode() + __r = __r*31 + jdkInfo.hashCode() + __r = __r*31 + applicationContext.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("TestGeneratorParams (") + printer.indent { + print("buildDir = "); buildDir.print(printer); println() + print("classpath = "); classpath.print(printer); println() + print("dependencyPaths = "); dependencyPaths.print(printer); println() + print("jdkInfo = "); jdkInfo.print(printer); println() + print("applicationContext = "); applicationContext.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [EngineProcessModel.kt:113] + */ +data class WriteSarifReportArguments ( + val testSetsId: Long, + val reportFilePath: String, + val generatedTestsCode: String +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = WriteSarifReportArguments::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): WriteSarifReportArguments { + val testSetsId = buffer.readLong() + val reportFilePath = buffer.readString() + val generatedTestsCode = buffer.readString() + return WriteSarifReportArguments(testSetsId, reportFilePath, generatedTestsCode) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: WriteSarifReportArguments) { + buffer.writeLong(value.testSetsId) + buffer.writeString(value.reportFilePath) + buffer.writeString(value.generatedTestsCode) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as WriteSarifReportArguments + + if (testSetsId != other.testSetsId) return false + if (reportFilePath != other.reportFilePath) return false + if (generatedTestsCode != other.generatedTestsCode) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + testSetsId.hashCode() + __r = __r*31 + reportFilePath.hashCode() + __r = __r*31 + generatedTestsCode.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("WriteSarifReportArguments (") + printer.indent { + print("testSetsId = "); testSetsId.print(printer); println() + print("reportFilePath = "); reportFilePath.print(printer); println() + print("generatedTestsCode = "); generatedTestsCode.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessRoot.Generated.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessRoot.Generated.kt new file mode 100644 index 0000000000..b49abf70e0 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessRoot.Generated.kt @@ -0,0 +1,61 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.framework.process.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [EngineProcessModel.kt:6] + */ +class EngineProcessRoot private constructor( +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + EngineProcessRoot.register(serializers) + EngineProcessModel.register(serializers) + RdInstrumenterAdapter.register(serializers) + RdSourceFindingStrategy.register(serializers) + } + + + + + + const val serializationHash = 2863869932420445069L + + } + override val serializersOwner: ISerializersOwner get() = EngineProcessRoot + override val serializationHash: Long get() = EngineProcessRoot.serializationHash + + //fields + //methods + //initializer + //secondary constructor + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("EngineProcessRoot (") + printer.print(")") + } + //deepClone + override fun deepClone(): EngineProcessRoot { + return EngineProcessRoot( + ) + } + //contexts +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdInstrumenterAdapter.Generated.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdInstrumenterAdapter.Generated.kt new file mode 100644 index 0000000000..c7ce7a8188 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdInstrumenterAdapter.Generated.kt @@ -0,0 +1,151 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.framework.process.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [EngineProcessModel.kt:8] + */ +class RdInstrumenterAdapter private constructor( + private val _computeSourceFileByClass: RdCall +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + serializers.register(ComputeSourceFileByClassArguments) + } + + + @JvmStatic + @JvmName("internalCreateModel") + @Deprecated("Use create instead", ReplaceWith("create(lifetime, protocol)")) + internal fun createModel(lifetime: Lifetime, protocol: IProtocol): RdInstrumenterAdapter { + @Suppress("DEPRECATION") + return create(lifetime, protocol) + } + + @JvmStatic + @Deprecated("Use protocol.rdInstrumenterAdapter or revise the extension scope instead", ReplaceWith("protocol.rdInstrumenterAdapter")) + fun create(lifetime: Lifetime, protocol: IProtocol): RdInstrumenterAdapter { + EngineProcessRoot.register(protocol.serializers) + + return RdInstrumenterAdapter() + } + + private val __StringNullableSerializer = FrameworkMarshallers.String.nullable() + + const val serializationHash = 1502978559314472937L + + } + override val serializersOwner: ISerializersOwner get() = RdInstrumenterAdapter + override val serializationHash: Long get() = RdInstrumenterAdapter.serializationHash + + //fields + val computeSourceFileByClass: RdCall get() = _computeSourceFileByClass + //methods + //initializer + init { + _computeSourceFileByClass.async = true + } + + init { + bindableChildren.add("computeSourceFileByClass" to _computeSourceFileByClass) + } + + //secondary constructor + private constructor( + ) : this( + RdCall(ComputeSourceFileByClassArguments, __StringNullableSerializer) + ) + + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("RdInstrumenterAdapter (") + printer.indent { + print("computeSourceFileByClass = "); _computeSourceFileByClass.print(printer); println() + } + printer.print(")") + } + //deepClone + override fun deepClone(): RdInstrumenterAdapter { + return RdInstrumenterAdapter( + _computeSourceFileByClass.deepClonePolymorphic() + ) + } + //contexts +} +val IProtocol.rdInstrumenterAdapter get() = getOrCreateExtension(RdInstrumenterAdapter::class) { @Suppress("DEPRECATION") RdInstrumenterAdapter.create(lifetime, this) } + + + +/** + * #### Generated from [EngineProcessModel.kt:9] + */ +data class ComputeSourceFileByClassArguments ( + val canonicalClassName: String +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = ComputeSourceFileByClassArguments::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): ComputeSourceFileByClassArguments { + val canonicalClassName = buffer.readString() + return ComputeSourceFileByClassArguments(canonicalClassName) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: ComputeSourceFileByClassArguments) { + buffer.writeString(value.canonicalClassName) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as ComputeSourceFileByClassArguments + + if (canonicalClassName != other.canonicalClassName) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + canonicalClassName.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("ComputeSourceFileByClassArguments (") + printer.indent { + print("canonicalClassName = "); canonicalClassName.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdSourceFindingStrategy.Generated.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdSourceFindingStrategy.Generated.kt new file mode 100644 index 0000000000..42da5b6e9b --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/RdSourceFindingStrategy.Generated.kt @@ -0,0 +1,177 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.framework.process.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [EngineProcessModel.kt:17] + */ +class RdSourceFindingStrategy private constructor( + private val _testsRelativePath: RdCall, + private val _getSourceRelativePath: RdCall, + private val _getSourceFile: RdCall +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + serializers.register(SourceStrategyMethodArgs) + } + + + @JvmStatic + @JvmName("internalCreateModel") + @Deprecated("Use create instead", ReplaceWith("create(lifetime, protocol)")) + internal fun createModel(lifetime: Lifetime, protocol: IProtocol): RdSourceFindingStrategy { + @Suppress("DEPRECATION") + return create(lifetime, protocol) + } + + @JvmStatic + @Deprecated("Use protocol.rdSourceFindingStrategy or revise the extension scope instead", ReplaceWith("protocol.rdSourceFindingStrategy")) + fun create(lifetime: Lifetime, protocol: IProtocol): RdSourceFindingStrategy { + EngineProcessRoot.register(protocol.serializers) + + return RdSourceFindingStrategy() + } + + private val __StringNullableSerializer = FrameworkMarshallers.String.nullable() + + const val serializationHash = 3794277837200536292L + + } + override val serializersOwner: ISerializersOwner get() = RdSourceFindingStrategy + override val serializationHash: Long get() = RdSourceFindingStrategy.serializationHash + + //fields + val testsRelativePath: RdCall get() = _testsRelativePath + val getSourceRelativePath: RdCall get() = _getSourceRelativePath + val getSourceFile: RdCall get() = _getSourceFile + //methods + //initializer + init { + _testsRelativePath.async = true + _getSourceRelativePath.async = true + _getSourceFile.async = true + } + + init { + bindableChildren.add("testsRelativePath" to _testsRelativePath) + bindableChildren.add("getSourceRelativePath" to _getSourceRelativePath) + bindableChildren.add("getSourceFile" to _getSourceFile) + } + + //secondary constructor + private constructor( + ) : this( + RdCall(FrameworkMarshallers.Long, FrameworkMarshallers.String), + RdCall(SourceStrategyMethodArgs, FrameworkMarshallers.String), + RdCall(SourceStrategyMethodArgs, __StringNullableSerializer) + ) + + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("RdSourceFindingStrategy (") + printer.indent { + print("testsRelativePath = "); _testsRelativePath.print(printer); println() + print("getSourceRelativePath = "); _getSourceRelativePath.print(printer); println() + print("getSourceFile = "); _getSourceFile.print(printer); println() + } + printer.print(")") + } + //deepClone + override fun deepClone(): RdSourceFindingStrategy { + return RdSourceFindingStrategy( + _testsRelativePath.deepClonePolymorphic(), + _getSourceRelativePath.deepClonePolymorphic(), + _getSourceFile.deepClonePolymorphic() + ) + } + //contexts +} +val IProtocol.rdSourceFindingStrategy get() = getOrCreateExtension(RdSourceFindingStrategy::class) { @Suppress("DEPRECATION") RdSourceFindingStrategy.create(lifetime, this) } + + + +/** + * #### Generated from [EngineProcessModel.kt:18] + */ +data class SourceStrategyMethodArgs ( + val testSetId: Long, + val classFqn: String, + val extension: String? +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = SourceStrategyMethodArgs::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SourceStrategyMethodArgs { + val testSetId = buffer.readLong() + val classFqn = buffer.readString() + val extension = buffer.readNullable { buffer.readString() } + return SourceStrategyMethodArgs(testSetId, classFqn, extension) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SourceStrategyMethodArgs) { + buffer.writeLong(value.testSetId) + buffer.writeString(value.classFqn) + buffer.writeNullable(value.extension) { buffer.writeString(it) } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as SourceStrategyMethodArgs + + if (testSetId != other.testSetId) return false + if (classFqn != other.classFqn) return false + if (extension != other.extension) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + testSetId.hashCode() + __r = __r*31 + classFqn.hashCode() + __r = __r*31 + if (extension != null) extension.hashCode() else 0 + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SourceStrategyMethodArgs (") + printer.indent { + print("testSetId = "); testSetId.print(printer); println() + print("classFqn = "); classFqn.print(printer); println() + print("extension = "); extension.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/EngineUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/EngineUtils.kt index 572094f0c2..af216dfae2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/EngineUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/EngineUtils.kt @@ -1,95 +1,37 @@ package org.utbot.framework.util -import org.utbot.common.Reflection -import org.utbot.engine.ValueConstructor -import org.utbot.framework.plugin.api.ExecutableId +import mu.KotlinLogging +import org.utbot.common.logException +import org.utbot.framework.plugin.api.util.constructor.ValueConstructor import org.utbot.framework.plugin.api.MissingState -import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtSymbolicExecution import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.framework.plugin.api.UtValueTestCase +import org.utbot.framework.plugin.api.UtMethodTestSet +import org.utbot.framework.plugin.api.UtMethodValueTestSet import org.utbot.framework.plugin.api.UtVoidModel -import org.utbot.framework.plugin.api.classId -import org.utbot.framework.plugin.api.id -import org.utbot.framework.plugin.api.util.constructorId -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.methodId -import org.utbot.framework.plugin.api.util.objectClassId import java.util.concurrent.atomic.AtomicInteger -import soot.SootMethod -import soot.jimple.JimpleBody -@Suppress("DEPRECATION") -val Class<*>.anyInstance: Any - get() { -// val defaultCtor = declaredConstructors.singleOrNull { it.parameterCount == 0} -// if (defaultCtor != null) { -// try { -// defaultCtor.isAccessible = true -// return defaultCtor.newInstance() -// } catch (e : Throwable) { -// logger.warn(e) { "Can't create object with default ctor. Fallback to Unsafe." } -// } -// } - return Reflection.unsafe.allocateInstance(this) +private val logger = KotlinLogging.logger { } -// val constructors = runCatching { -// arrayOf(getDeclaredConstructor()) -// }.getOrElse { declaredConstructors } -// -// return constructors.asSequence().mapNotNull { constructor -> -// runCatching { -// val parameters = constructor.parameterTypes.map { defaultParameterValue(it) } -// val isAccessible = constructor.isAccessible -// try { -// constructor.isAccessible = true -// constructor.newInstance(*parameters.toTypedArray()) -// } finally { -// constructor.isAccessible = isAccessible -// } -// }.getOrNull() -// }.firstOrNull() ?: error("Failed to create instance of $this") - } - -/** - * Gets method or constructor id of SootMethod. - */ -val SootMethod.executableId: ExecutableId - get() = when { - isConstructor -> constructorId( - classId = declaringClass.id, - arguments = parameterTypes.map { it.classId }.toTypedArray() - ) - else -> methodId( - classId = declaringClass.id, - name = name, - returnType = returnType.classId, - arguments = parameterTypes.map { it.classId }.toTypedArray() - ) - } - -val modelIdCounter = AtomicInteger(0) val instanceCounter = AtomicInteger(0) fun nextModelName(base: String): String = "$base${instanceCounter.incrementAndGet()}" -fun UtTestCase.toValueTestCase(jimpleBody: JimpleBody? = null): UtValueTestCase<*> { - val valueExecutions = executions.map { ValueConstructor().construct(it) } - return UtValueTestCase(method, valueExecutions, jimpleBody, errors) +fun UtMethodTestSet.toValueTestCase(): UtMethodValueTestSet<*> { + val valueExecutions = executions.map { ValueConstructor().construct(it) } // TODO: make something about UTExecution + return UtMethodValueTestSet(method, valueExecutions, errors) } fun UtModel.isUnit(): Boolean = this is UtVoidModel -fun UtExecution.hasThisInstance(): Boolean = when { +fun UtSymbolicExecution.hasThisInstance(): Boolean = when { stateBefore.thisInstance == null && stateAfter.thisInstance == null -> false stateBefore.thisInstance != null && stateAfter.thisInstance != null -> true stateAfter == MissingState -> false // An execution must either have this instance or not. // This instance cannot be absent before execution and appear after // as well as it cannot be present before execution and disappear after. - else -> error("Execution configuration must not change between states") -} - -internal fun valueToClassId(value: Any?) = value?.let { it::class.java.id } ?: objectClassId \ No newline at end of file + else -> logger.logException { error("Execution configuration must not change between states") } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/ImplicitlyDeclaredMethods.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/ImplicitlyDeclaredMethods.kt new file mode 100644 index 0000000000..0b9b85225b --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/ImplicitlyDeclaredMethods.kt @@ -0,0 +1,50 @@ +package org.utbot.framework.util + +import org.utbot.framework.codegen.util.fieldThatIsGotWith +import org.utbot.framework.codegen.util.fieldThatIsSetWith +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.util.isData +import org.utbot.framework.plugin.api.util.isEnum +import org.utbot.framework.plugin.api.util.isFromKotlin +import org.utbot.framework.plugin.api.util.isKotlinFile + +/** + * Returns whether this method could be implicitly generated by compiler, or not. + * + * Note that here we can only judge this by method name and kind of class (data class, enum, etc). + * There seems to be no (at least, easy) way to check from bytecode if this method was actually overridden by user, + * so this function will return true even if the matching method is not autogenerated but written explicitly by user. + */ +val ExecutableId.isKnownImplicitlyDeclaredMethod: Boolean + get() = + when { + // this check is needed because reflection is not fully supported for file facades -- see KT-16479, + // by now we assume that such classes can't contain autogenerated methods + classId.isKotlinFile -> false + isKotlinGetterOrSetter -> true + classId.isEnum -> name in KnownImplicitlyDeclaredMethods.enumImplicitlyDeclaredMethodNames + classId.isData -> KnownImplicitlyDeclaredMethods.dataClassImplicitlyDeclaredMethodNameRegexps.any { it.matches(name) } + else -> false + } + +internal val ExecutableId.isKotlinGetterOrSetter: Boolean + get() = classId.isFromKotlin && + (classId.fieldThatIsGotWith(this) != null || classId.fieldThatIsSetWith(this) != null) + +/** + * Contains names of methods that are always autogenerated by compiler and thus it is unlikely that + * one would want to generate tests for them. + */ +private object KnownImplicitlyDeclaredMethods { + /** List with names of enum methods that are generated by compiler */ + val enumImplicitlyDeclaredMethodNames = listOf("values", "valueOf") + + /** List with regexps that match names of methods that are generated by Kotlin compiler for data classes */ + val dataClassImplicitlyDeclaredMethodNameRegexps = listOf( + "equals", + "hashCode", + "toString", + "copy", + "component[1-9][0-9]*" + ).map { it.toRegex() } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/MockStrategyUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/MockStrategyUtils.kt new file mode 100644 index 0000000000..e281a4902e --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/MockStrategyUtils.kt @@ -0,0 +1,12 @@ +package org.utbot.framework.util + +import org.utbot.engine.MockStrategy +import org.utbot.framework.plugin.api.MockStrategyApi + +fun MockStrategyApi.toModel(): MockStrategy = + when (this) { + MockStrategyApi.NO_MOCKS -> MockStrategy.NO_MOCKS + MockStrategyApi.OTHER_PACKAGES -> MockStrategy.OTHER_PACKAGES + MockStrategyApi.OTHER_CLASSES -> MockStrategy.OTHER_CLASSES + else -> error("Cannot map API Mock Strategy model to Engine model: $this") + } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/SizeUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SizeUtils.kt new file mode 100644 index 0000000000..d6da8d7f25 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SizeUtils.kt @@ -0,0 +1,66 @@ +package org.utbot.framework.util + +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtClassRefModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtCustomModel +import org.utbot.framework.plugin.api.UtDirectGetFieldModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.UtVoidModel + +fun EnvironmentModels.calculateSize(): Int { + val thisInstanceSize = thisInstance?.calculateSize() ?: 0 + val parametersSize = parameters.sumOf { it.calculateSize() } + val staticsSize = statics.values.sumOf { it.calculateSize() } + + return thisInstanceSize + parametersSize + staticsSize +} + +/** + * We assume that "size" for "common" models is 1, 0 for [UtVoidModel] (as they do not return anything) and + * [UtPrimitiveModel] and 2 for [UtNullModel] (we use them as literals in codegen), summarising for + * all statements for [UtAssembleModel] and summarising for all fields and mocks for [UtCompositeModel]. + * + * As [UtReferenceModel] could be recursive, we need to store it in [used]. Moreover, if we have already + * calculated the size for [this] model and [this] is [UtReferenceModel], then in codegen we would have already + * created variable for [this] model and do not need to create it again, so size should be equal to 0. + */ +fun UtModel.calculateSize(used: MutableSet = mutableSetOf()): Int { + if (this in used) return 0 + + if (this is UtReferenceModel) + used += this + + return when (this) { + // `null` is assigned size of `2` to encourage use of empty mocks which have size of `1` over `null`s + is UtNullModel -> 2 + is UtPrimitiveModel, UtVoidModel -> 0 + is UtClassRefModel, is UtEnumConstantModel, is UtArrayModel, is UtCustomModel -> 1 + is UtAssembleModel -> { + 1 + instantiationCall.calculateSize(used) + modificationsChain.sumOf { it.calculateSize(used) } + } + is UtCompositeModel -> 1 + (fields.values + mocks.values.flatten()).sumOf { it.calculateSize(used) } + is UtLambdaModel -> 1 + capturedValues.sumOf { it.calculateSize(used) } + // PythonModel, JsUtModel, UtSpringContextModel may be here + else -> 0 + } +} + +private fun UtStatementModel.calculateSize(used: MutableSet = mutableSetOf()): Int = + when (this) { + is UtExecutableCallModel -> 1 + params.sumOf { it.calculateSize(used) } + (instance?.calculateSize(used) ?: 0) + is UtDirectSetFieldModel -> 1 + fieldModel.calculateSize(used) + instance.calculateSize(used) + + // -2 is added to encourage use of non-hardcoded values (including compensation for one extra `UtAssembleModel`) + is UtDirectGetFieldModel -> (-2 + instance.calculateSize(used)).coerceAtLeast(0) + } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt new file mode 100644 index 0000000000..dcb18951e4 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/SootUtils.kt @@ -0,0 +1,233 @@ +package org.utbot.framework.util + +import org.utbot.common.FileUtil +import org.utbot.engine.jimpleBody +import org.utbot.engine.pureJavaSignature +import org.utbot.framework.UtSettings +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.services.JdkInfo +import soot.G +import soot.PackManager +import soot.Scene +import soot.SootClass +import soot.SootMethod +import soot.jimple.JimpleBody +import soot.options.Options +import soot.toolkits.graph.ExceptionalUnitGraph +import java.io.File +import java.nio.file.Path +import java.util.function.Consumer + +object SootUtils { + /** + * Runs Soot in tests if it hasn't already been done. + * + * @param jdkInfo specifies the JRE and the runtime library version used for analysing system classes and user's + * code. + * @param forceReload forces to reinitialize Soot even if the [previousBuildDirs] equals to the class buildDir. + */ + fun runSoot(clazz: Class<*>, forceReload: Boolean, jdkInfo: JdkInfo) { + val buildDir = FileUtil.locateClassPath(clazz) ?: FileUtil.isolateClassFiles(clazz) + val buildDirPath = buildDir.toPath() + + runSoot(listOf(buildDirPath), null, forceReload, jdkInfo) + } + + + /** + * @param jdkInfo specifies the JRE and the runtime library version used for analysing system classes and user's + * code. + * @param forceReload forces to reinitialize Soot even if the [previousBuildDirs] equals to [buildDirPaths] and + * [previousClassPath] equals to [classPath]. + */ + fun runSoot(buildDirPaths: List, classPath: String?, forceReload: Boolean, jdkInfo: JdkInfo) { + synchronized(this) { + if (buildDirPaths != previousBuildDirs || classPath != previousClassPath || forceReload) { + initSoot(buildDirPaths, classPath, jdkInfo) + previousBuildDirs = buildDirPaths + previousClassPath = classPath + } + } + } + + private var previousBuildDirs: List? = null + private var previousClassPath: String? = null +} + +/** + * This option is only needed to fast changing from other tools. + */ +var sootOptionConfiguration: Consumer = Consumer { _ -> } + +/** + * Convert code to Jimple + */ +private fun initSoot(buildDirs: List, classpath: String?, jdkInfo: JdkInfo) { + G.reset() + val options = Options.v() + + G.v().initJdk(G.JreInfo(jdkInfo.path.toString(), jdkInfo.version)) // init Soot with the right jdk + + options.apply { + set_prepend_classpath(true) + // set true to debug. Disabled because of a bug when two different variables + // from the source code have the same name in the jimple body. + setPhaseOption("jb", "use-original-names:false") + sootOptionConfiguration.accept(this) + set_soot_classpath( + FileUtil.isolateClassFiles(*classesToLoad).absolutePath + + if (!classpath.isNullOrEmpty()) File.pathSeparator + "$classpath" else "" + ) + set_src_prec(Options.src_prec_only_class) + set_process_dir(buildDirs.map { it.toString() }) + set_keep_line_number(true) + set_ignore_classpath_errors(true) // gradle/build/resources/main does not exists, but it's not a problem + set_output_format(Options.output_format_jimple) + /** + * In case of Java8, set_full_resolver(true) fails with "soot.SootResolver$SootClassNotFoundException: + * couldn't find class: javax.crypto.BadPaddingException (is your soot-class-path set properly?)". + * To cover that, set_allow_phantom_refs(true) is required + */ + set_allow_phantom_refs(true) // Java8 related + set_full_resolver(true) + } + + addBasicClasses(*classesToLoad) + + Scene.v().loadNecessaryClasses() + PackManager.v().runPacks() + // these options are moved out from forEach loop because they are slow due to rd communication + val removeUtBotClassesFromHierarchy = UtSettings.removeUtBotClassesFromHierarchy + val removeSootClassesFromHierarchy = UtSettings.removeSootClassesFromHierarchy + // we need this to create hierarchy of classes + Scene.v().classes.toList().forEach { + val isUtBotPackage = it.packageName.startsWith(UTBOT_PACKAGE_PREFIX) + + // remove our own classes from the soot scene + if (removeUtBotClassesFromHierarchy && isUtBotPackage) { + val isOverriddenPackage = it.packageName.startsWith(UTBOT_OVERRIDDEN_PACKAGE_PREFIX) + val isExamplesPackage = it.packageName.startsWith(UTBOT_EXAMPLES_PACKAGE_PREFIX) + val isApiPackage = it.packageName.startsWith(UTBOT_API_PACKAGE_PREFIX) + val isVisiblePackage = it.packageName.startsWith(UTBOT_FRAMEWORK_API_VISIBLE_PACKAGE) + + // remove if it is not a part of the examples (CUT), not a part of our API, not an override and not from visible for soot + if (!isOverriddenPackage && !isExamplesPackage && !isApiPackage && !isVisiblePackage) { + Scene.v().removeClass(it) + return@forEach + } + } + + // remove soot's classes from the scene, because we don't wont to analyze them + if (removeSootClassesFromHierarchy && it.packageName.startsWith(SOOT_PACKAGE_PREFIX)) { + Scene.v().removeClass(it) + return@forEach + } + + if (it.resolvingLevel() < SootClass.HIERARCHY) { + it.setResolvingLevel(SootClass.HIERARCHY) + } + } +} + +fun JimpleBody.graph() = ExceptionalUnitGraph(this) + +val ExecutableId.sootMethod: SootMethod + get() = sootMethodOrNull ?: error("Class contains not only one method with the required signature.") + +val ExecutableId.sootMethodOrNull: SootMethod? + get() { + val clazz = Scene.v().getSootClass(classId.name) + return clazz.methods.singleOrNull { it.pureJavaSignature == signature } + } + + +fun jimpleBody(method: ExecutableId): JimpleBody = + method.sootMethod.jimpleBody() + + +private fun addBasicClasses(vararg classes: Class<*>) { + classes.forEach { + Scene.v().addBasicClass(it.name, SootClass.BODIES) + } +} + +private val classesToLoad = arrayOf( + org.utbot.engine.overrides.collections.AbstractCollection::class, + org.utbot.api.mock.UtMock::class, + org.utbot.engine.overrides.UtOverrideMock::class, + org.utbot.engine.overrides.UtLogicMock::class, + org.utbot.engine.overrides.UtArrayMock::class, + org.utbot.engine.overrides.Boolean::class, + org.utbot.engine.overrides.Byte::class, + org.utbot.engine.overrides.Character::class, + org.utbot.engine.overrides.Class::class, + org.utbot.engine.overrides.Integer::class, + org.utbot.engine.overrides.Long::class, + org.utbot.engine.overrides.Short::class, + org.utbot.engine.overrides.System::class, + org.utbot.engine.overrides.Throwable::class, + org.utbot.engine.overrides.AutoCloseable::class, + org.utbot.engine.overrides.AccessController::class, + org.utbot.engine.overrides.collections.UtOptional::class, + org.utbot.engine.overrides.collections.UtOptionalInt::class, + org.utbot.engine.overrides.collections.UtOptionalLong::class, + org.utbot.engine.overrides.collections.UtOptionalDouble::class, + org.utbot.engine.overrides.collections.UtArrayList::class, + org.utbot.engine.overrides.collections.UtArrayList.UtArrayListSimpleIterator::class, + org.utbot.engine.overrides.collections.UtArrayList.UtArrayListIterator::class, + org.utbot.engine.overrides.collections.UtLinkedList::class, + org.utbot.engine.overrides.collections.UtLinkedListWithNullableCheck::class, + org.utbot.engine.overrides.collections.UtLinkedList.UtLinkedListIterator::class, + org.utbot.engine.overrides.collections.UtLinkedList.UtReverseIterator::class, + org.utbot.engine.overrides.collections.UtHashSet::class, + org.utbot.engine.overrides.collections.UtHashSet.UtHashSetIterator::class, + org.utbot.engine.overrides.collections.UtHashMap::class, + org.utbot.engine.overrides.collections.UtHashMap.Entry::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedEntryIterator::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedEntrySet::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedHashIterator::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedKeyIterator::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedKeySet::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedValueIterator::class, + org.utbot.engine.overrides.collections.UtHashMap.LinkedValues::class, + org.utbot.engine.overrides.collections.RangeModifiableUnlimitedArray::class, + org.utbot.engine.overrides.collections.AssociativeArray::class, + org.utbot.engine.overrides.collections.UtGenericStorage::class, + org.utbot.engine.overrides.collections.UtGenericAssociative::class, + org.utbot.engine.overrides.PrintStream::class, + org.utbot.engine.overrides.strings.UtString::class, + org.utbot.engine.overrides.strings.UtStringBuilder::class, + org.utbot.engine.overrides.strings.UtStringBuffer::class, + org.utbot.engine.overrides.threads.UtThread::class, + org.utbot.engine.overrides.threads.UtThreadGroup::class, + org.utbot.engine.overrides.threads.UtCompletableFuture::class, + org.utbot.engine.overrides.threads.CompletableFuture::class, + org.utbot.engine.overrides.threads.Executors::class, + org.utbot.engine.overrides.threads.UtExecutorService::class, + org.utbot.engine.overrides.threads.UtCountDownLatch::class, + org.utbot.engine.overrides.stream.Stream::class, + org.utbot.engine.overrides.stream.Arrays::class, + org.utbot.engine.overrides.collections.Collection::class, + org.utbot.engine.overrides.collections.List::class, + org.utbot.framework.plugin.api.visible.UtStreamConsumingException::class, + org.utbot.engine.overrides.stream.UtStream::class, + org.utbot.engine.overrides.stream.UtIntStream::class, + org.utbot.engine.overrides.stream.UtLongStream::class, + org.utbot.engine.overrides.stream.UtDoubleStream::class, + org.utbot.engine.overrides.stream.UtStream.UtStreamIterator::class, + org.utbot.engine.overrides.stream.UtIntStream.UtIntStreamIterator::class, + org.utbot.engine.overrides.stream.UtLongStream.UtLongStreamIterator::class, + org.utbot.engine.overrides.stream.UtDoubleStream.UtDoubleStreamIterator::class, + org.utbot.engine.overrides.stream.IntStream::class, + org.utbot.engine.overrides.stream.LongStream::class, + org.utbot.engine.overrides.stream.DoubleStream::class, + org.utbot.framework.plugin.api.OverflowDetectionError::class, + org.utbot.framework.plugin.api.TaintAnalysisError::class +).map { it.java }.toTypedArray() + +private const val UTBOT_PACKAGE_PREFIX = "org.utbot" +private const val UTBOT_EXAMPLES_PACKAGE_PREFIX = "$UTBOT_PACKAGE_PREFIX.examples" +private const val UTBOT_API_PACKAGE_PREFIX = "$UTBOT_PACKAGE_PREFIX.api" +private const val UTBOT_OVERRIDDEN_PACKAGE_PREFIX = "$UTBOT_PACKAGE_PREFIX.engine.overrides" +internal const val UTBOT_FRAMEWORK_API_VISIBLE_PACKAGE = "$UTBOT_PACKAGE_PREFIX.framework.plugin.api.visible" +private const val SOOT_PACKAGE_PREFIX = "soot." \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/TestUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/TestUtils.kt index c8778f8cf6..7c95e724f2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/TestUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/TestUtils.kt @@ -11,6 +11,7 @@ import java.io.File import java.net.URLClassLoader import java.nio.file.Files import java.nio.file.Paths +import java.util.* import java.util.concurrent.TimeUnit fun List.singleStaticMethod(methodName: String) = @@ -139,4 +140,29 @@ fun compileClassFile(className: String, snippet: Snippet): File { require(javacFinished) { "Javac can't complete in $timeout sec" } return File(workdir.toFile(), "${sourceCodeFile.nameWithoutExtension}.class") +} + +enum class Conflict { + ForceMockHappened, + ForceStaticMockHappened, + TestFrameworkConflict, +} + +class ConflictTriggers( + private val triggers: MutableMap = EnumMap(Conflict::class.java).also { map -> + Conflict.values().forEach { conflict -> map[conflict] = false } + } +) : MutableMap by triggers { + val anyTriggered: Boolean + get() = triggers.values.any { it } + + fun triggered(conflict: Conflict): Boolean { + return triggers[conflict] ?: false + } + + fun reset(vararg conflicts: Conflict) { + for (conflict in conflicts) { + triggers[conflict] = false + } + } } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/ThrowableUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/ThrowableUtils.kt deleted file mode 100644 index ce92971319..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/ThrowableUtils.kt +++ /dev/null @@ -1,4 +0,0 @@ -package org.utbot.framework.util - -val Throwable.description - get() = message?.replace('\n', '\t') ?: "" \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt new file mode 100644 index 0000000000..a0e00573e4 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt @@ -0,0 +1,61 @@ +package org.utbot.framework.util + +import org.utbot.framework.assemble.AssembleModelGenerator +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.MissingState +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtModel +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import java.util.IdentityHashMap + +/** + * Tries to convert all models from [UtExecutionResult] to [UtAssembleModel] if possible. + * + * @return [UtConcreteExecutionResult] with converted models. + */ +fun UtConcreteExecutionResult.convertToAssemble(packageName: String): UtConcreteExecutionResult { + val allModels = collectAllModels() + + val modelsToAssembleModels = AssembleModelGenerator(packageName).createAssembleModels(allModels) + return updateWithAssembleModels(modelsToAssembleModels) +} + +private fun UtConcreteExecutionResult.updateWithAssembleModels( + assembledUtModels: IdentityHashMap +): UtConcreteExecutionResult { + val toAssemble: (UtModel) -> UtModel = { assembledUtModels.getOrDefault(it, it) } + + val resolvedStateBefore = stateBefore.resolveState(toAssemble) + val resolvedStateAfter = stateAfter.resolveState(toAssemble) + val resolvedResult = (result as? UtExecutionSuccess)?.model?.let { UtExecutionSuccess(toAssemble(it)) } ?: result + + return copy( + stateBefore = resolvedStateBefore, + stateAfter = resolvedStateAfter, + result = resolvedResult, + ) +} + +private fun EnvironmentModels.resolveState(toAssemble: (UtModel) -> UtModel): EnvironmentModels = + if (this is MissingState) { + MissingState + } else { + copy( + thisInstance = thisInstance?.let { toAssemble(it) }, + parameters = parameters.map { toAssemble(it) }, + statics = statics.mapValues { toAssemble(it.value) }, + ) + } + +private fun UtConcreteExecutionResult.collectAllModels(): List { + val allModels = mutableListOf() + + allModels += stateBefore.utModels + allModels += stateAfter.utModels + allModels += listOfNotNull((result as? UtExecutionSuccess)?.model) + + return allModels +} + +private val EnvironmentModels.utModels: List + get() = listOfNotNull(thisInstance) + parameters + statics.values \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt index b6d91a8ede..e4621e64c9 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtModelVisitor.kt @@ -4,7 +4,9 @@ import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtClassRefModel import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtCustomModel import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel @@ -21,8 +23,6 @@ abstract class UtModelVisitor { abstract fun visit(element: UtModel, data: D) - abstract fun visit(element: UtClassRefModel, data: D) - abstract fun visit(element: UtEnumConstantModel, data: D) abstract fun visit(element: UtNullModel, data: D) abstract fun visit(element: UtPrimitiveModel, data: D) abstract fun visit(element: UtVoidModel, data: D) @@ -30,15 +30,23 @@ abstract class UtModelVisitor { open fun visit(element: UtReferenceModel, data: D) { if (!canTraverseReferenceModel(element)) return when (element) { + is UtClassRefModel -> visit(element, data) + is UtEnumConstantModel -> visit(element, data) is UtArrayModel -> visit(element, data) is UtAssembleModel -> visit(element, data) is UtCompositeModel -> visit(element, data) + is UtLambdaModel -> visit(element, data) + is UtCustomModel -> visit(element, data) } } + abstract fun visit(element: UtClassRefModel, data: D) + abstract fun visit(element: UtEnumConstantModel, data: D) protected abstract fun visit(element: UtArrayModel, data: D) protected abstract fun visit(element: UtAssembleModel, data: D) protected abstract fun visit(element: UtCompositeModel, data: D) + protected abstract fun visit(element: UtLambdaModel, data: D) + protected abstract fun visit(element: UtCustomModel, data: D) /** * Returns true when we can traverse the given model. diff --git a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt deleted file mode 100644 index 89f9c21aaf..0000000000 --- a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FallbackModelProvider.kt +++ /dev/null @@ -1,106 +0,0 @@ -package org.utbot.fuzzer - -import org.utbot.engine.isPublic -import org.utbot.framework.concrete.UtModelConstructor -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtCompositeModel -import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.framework.plugin.api.UtStatementModel -import org.utbot.framework.plugin.api.util.defaultValueModel -import org.utbot.framework.plugin.api.util.executableId -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.isArray -import org.utbot.framework.plugin.api.util.isIterable -import org.utbot.framework.plugin.api.util.isPrimitive -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.kClass -import org.utbot.fuzzer.providers.AbstractModelProvider -import java.util.* -import java.util.function.IntSupplier -import kotlin.collections.ArrayList -import kotlin.collections.HashMap -import kotlin.reflect.KClass - -/** - * Provides some simple default models of any class. - * - * Used as a fallback implementation until other providers cover every type. - */ -open class FallbackModelProvider( - private val idGenerator: IntSupplier -): AbstractModelProvider() { - - override fun toModel(classId: ClassId): UtModel { - return createModelByClassId(classId) - } - - fun toModel(klazz: KClass<*>): UtModel = createSimpleModelByKClass(klazz) - - private fun createModelByClassId(classId: ClassId): UtModel { - val modelConstructor = UtModelConstructor(IdentityHashMap()) - val defaultConstructor = classId.jClass.constructors.firstOrNull { - it.parameters.isEmpty() && it.isPublic - } - return when { - classId.isPrimitive -> - classId.defaultValueModel() - classId.isArray -> - UtArrayModel( - id = idGenerator.asInt, - classId, - length = 0, - classId.elementClassId!!.defaultValueModel(), - mutableMapOf() - ) - classId.isIterable -> { - @Suppress("RemoveRedundantQualifierName") // ArrayDeque must be taken from java, not from kotlin - val defaultInstance = when { - defaultConstructor != null -> defaultConstructor.newInstance() - classId.jClass.isAssignableFrom(java.util.ArrayList::class.java) -> ArrayList() - classId.jClass.isAssignableFrom(java.util.TreeSet::class.java) -> TreeSet() - classId.jClass.isAssignableFrom(java.util.HashMap::class.java) -> HashMap() - classId.jClass.isAssignableFrom(java.util.ArrayDeque::class.java) -> java.util.ArrayDeque() - classId.jClass.isAssignableFrom(java.util.BitSet::class.java) -> BitSet() - else -> null - } - if (defaultInstance != null) - modelConstructor.construct(defaultInstance, classId) - else - createSimpleModelByKClass(classId.kClass) - } - else -> - createSimpleModelByKClass(classId.kClass) - } - } - - private fun createSimpleModelByKClass(kclass: KClass<*>): UtModel { - val defaultConstructor = kclass.java.constructors.firstOrNull { - it.parameters.isEmpty() && it.isPublic // check constructor is public - } - return if (kclass.isAbstract) { // sealed class is abstract by itself - UtNullModel(kclass.java.id) - } else if (defaultConstructor != null) { - val chain = mutableListOf() - val model = UtAssembleModel( - id = idGenerator.asInt, - kclass.id, - kclass.id.toString(), - chain - ) - chain.add( - UtExecutableCallModel(model, defaultConstructor.executableId, listOf(), model) - ) - model - } else { - UtCompositeModel( - id = idGenerator.asInt, - kclass.id, - isMock = false - ) - } - } -} diff --git a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt index 9efdcb7ad4..d925ae8428 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/fuzzer/FuzzerFunctions.kt @@ -1,16 +1,9 @@ package org.utbot.fuzzer -import org.utbot.framework.plugin.api.classId -import org.utbot.framework.plugin.api.util.booleanClassId -import org.utbot.framework.plugin.api.util.byteClassId -import org.utbot.framework.plugin.api.util.charClassId -import org.utbot.framework.plugin.api.util.doubleClassId -import org.utbot.framework.plugin.api.util.floatClassId -import org.utbot.framework.plugin.api.util.intClassId -import org.utbot.framework.plugin.api.util.longClassId -import org.utbot.framework.plugin.api.util.shortClassId -import org.utbot.framework.plugin.api.util.stringClassId import mu.KotlinLogging +import org.utbot.framework.plugin.api.classId +import org.utbot.framework.plugin.api.util.* +import org.utbot.framework.util.executableId import soot.BooleanType import soot.ByteType import soot.CharType @@ -27,20 +20,7 @@ import soot.jimple.Constant import soot.jimple.IntConstant import soot.jimple.InvokeExpr import soot.jimple.NullConstant -import soot.jimple.internal.AbstractSwitchStmt -import soot.jimple.internal.ImmediateBox -import soot.jimple.internal.JAssignStmt -import soot.jimple.internal.JCastExpr -import soot.jimple.internal.JEqExpr -import soot.jimple.internal.JGeExpr -import soot.jimple.internal.JGtExpr -import soot.jimple.internal.JIfStmt -import soot.jimple.internal.JLeExpr -import soot.jimple.internal.JLookupSwitchStmt -import soot.jimple.internal.JLtExpr -import soot.jimple.internal.JNeExpr -import soot.jimple.internal.JTableSwitchStmt -import soot.jimple.internal.JVirtualInvokeExpr +import soot.jimple.internal.* import soot.toolkits.graph.ExceptionalUnitGraph private val logger = KotlinLogging.logger {} @@ -50,7 +30,7 @@ private val logger = KotlinLogging.logger {} */ fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set { return graph.body.units.reversed().asSequence() - .filter { it is JIfStmt || it is JAssignStmt || it is AbstractSwitchStmt} + .filter { it is JIfStmt || it is JAssignStmt || it is AbstractSwitchStmt || it is JInvokeStmt } .flatMap { unit -> unit.useBoxes.map { unit to it.value } } @@ -64,11 +44,16 @@ fun collectConstantsForFuzzer(graph: ExceptionalUnitGraph): Set try { finder.find(graph, unit, value) } catch (e: Exception) { - logger.warn(e) { "Cannot process constant value of type '${value.type}}'" } + // ignore known values that don't have a value field and can be met + if (value !is NullConstant) { + logger.warn(e) { "Cannot process constant value of type '${value.type}'" } + } emptyList() } }.let { result -> @@ -98,6 +83,10 @@ private object ConstantsFromIfStatement: ConstantsFinder { // 1. compare (result = local compare constant) // 2. if result if (unit is JAssignStmt) { + // Accepts only those assignments statements that uses compare operation on the right + if (unit.rightOp !is JCmpExpr) { + return emptyList() + } useBoxes = unit.rightOp.useBoxes.mapNotNull { (it as? ImmediateBox)?.value } ifStatement = nextDirectUnit(graph, unit) as? JIfStmt } @@ -113,8 +102,8 @@ private object ConstantsFromIfStatement: ConstantsFinder { val exactValue = value.plainValue val local = useBoxes[(valueIndex + 1) % 2] var op = sootIfToFuzzedOp(ifStatement) - if (valueIndex == 0) { - op = op.reverseOrElse { it } + if (valueIndex == 0 && op is FuzzedContext.Comparison) { + op = op.reverse() } // Soot loads any integer type as an Int, // therefore we try to guess target type using second value @@ -146,17 +135,21 @@ private object ConstantsFromCast: ConstantsFinder { if (next is JAssignStmt) { val const = next.useBoxes.findFirstInstanceOf() if (const != null) { - val op = (nextDirectUnit(graph, next) as? JIfStmt)?.let(::sootIfToFuzzedOp) ?: FuzzedOp.NONE - val exactValue = const.plainValue as Number - return listOfNotNull( - when (value.op.type) { - is ByteType -> FuzzedConcreteValue(byteClassId, exactValue.toByte(), op) - is ShortType -> FuzzedConcreteValue(shortClassId, exactValue.toShort(), op) - is IntType -> FuzzedConcreteValue(intClassId, exactValue.toInt(), op) - is FloatType -> FuzzedConcreteValue(floatClassId, exactValue.toFloat(), op) - else -> null + val op = (nextDirectUnit(graph, next) as? JIfStmt)?.let(::sootIfToFuzzedOp) ?: FuzzedContext.Unknown + when (val exactValue = const.plainValue) { + is Number -> return listOfNotNull( + when (value.op.type) { + is ByteType -> FuzzedConcreteValue(byteClassId, exactValue.toByte(), op) + is ShortType -> FuzzedConcreteValue(shortClassId, exactValue.toShort(), op) + is IntType -> FuzzedConcreteValue(intClassId, exactValue.toInt(), op) + is FloatType -> FuzzedConcreteValue(floatClassId, exactValue.toFloat(), op) + else -> null + } + ) + is String -> { + return listOfNotNull(FuzzedConcreteValue(stringClassId, exactValue, op)) } - ) + } } } return emptyList() @@ -170,12 +163,12 @@ private object ConstantsFromSwitchCase: ConstantsFinder { val result = mutableListOf() if (unit is JTableSwitchStmt) { for (i in unit.lowIndex..unit.highIndex) { - result.add(FuzzedConcreteValue(intClassId, i, FuzzedOp.EQ)) + result.add(FuzzedConcreteValue(intClassId, i, FuzzedContext.Comparison.EQ)) } } if (unit is JLookupSwitchStmt) { unit.lookupValues.asSequence().filterIsInstance().forEach { - result.add(FuzzedConcreteValue(intClassId, it.value, FuzzedOp.EQ)) + result.add(FuzzedConcreteValue(intClassId, it.value, FuzzedContext.Comparison.EQ)) } } return result @@ -204,8 +197,8 @@ private object StringConstant: ConstantsFinder { // if string constant is called from String class let's pass it as modification if (value.method.declaringClass.name == "java.lang.String") { val stringConstantWasPassedAsArg = unit.useBoxes.findFirstInstanceOf()?.plainValue - if (stringConstantWasPassedAsArg != null) { - return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsArg, FuzzedOp.CH)) + if (stringConstantWasPassedAsArg != null && stringConstantWasPassedAsArg is String) { + return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsArg, FuzzedContext.Call(value.method.executableId))) } val stringConstantWasPassedAsThis = graph.getPredsOf(unit) ?.filterIsInstance() @@ -213,13 +206,48 @@ private object StringConstant: ConstantsFinder { ?.useBoxes ?.findFirstInstanceOf() ?.plainValue - if (stringConstantWasPassedAsThis != null) { - return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsThis, FuzzedOp.CH)) + if (stringConstantWasPassedAsThis != null && stringConstantWasPassedAsThis is String) { + return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsThis, FuzzedContext.Call(value.method.executableId))) + } + } + return emptyList() + } +} + +/** + * Finds strings that are used inside Pattern's methods. + * + * Due to compiler optimizations it should work when a string is assigned to a variable or static final field. + */ +private object RegexByVarStringConstant: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (unit !is JAssignStmt || value !is JStaticInvokeExpr) return emptyList() + if (value.method.declaringClass.name == "java.util.regex.Pattern") { + val stringConstantWasPassedAsArg = unit.useBoxes.findFirstInstanceOf()?.plainValue + if (stringConstantWasPassedAsArg != null && stringConstantWasPassedAsArg is String) { + return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsArg, FuzzedContext.Call(value.method.executableId))) } } return emptyList() } +} +/** + * Finds strings that are used inside DateFormat's constructors. + * + * Due to compiler optimizations it should work when a string is assigned to a variable or static final field. + */ +private object DateFormatByVarStringConstant: ConstantsFinder { + override fun find(graph: ExceptionalUnitGraph, unit: Unit, value: Value): List { + if (unit !is JInvokeStmt || value !is JSpecialInvokeExpr) return emptyList() + if (value.method.isConstructor && value.method.declaringClass.name == "java.text.SimpleDateFormat") { + val stringConstantWasPassedAsArg = unit.useBoxes.findFirstInstanceOf()?.plainValue + if (stringConstantWasPassedAsArg != null && stringConstantWasPassedAsArg is String) { + return listOf(FuzzedConcreteValue(stringClassId, stringConstantWasPassedAsArg, FuzzedContext.Call(value.method.executableId))) + } + } + return emptyList() + } } private object ConstantsAsIs: ConstantsFinder { @@ -241,13 +269,13 @@ private val Constant.plainValue get() = javaClass.getField("value")[this] private fun sootIfToFuzzedOp(unit: JIfStmt) = when (unit.condition) { - is JEqExpr -> FuzzedOp.NE - is JNeExpr -> FuzzedOp.EQ - is JGtExpr -> FuzzedOp.LE - is JGeExpr -> FuzzedOp.LT - is JLtExpr -> FuzzedOp.GE - is JLeExpr -> FuzzedOp.GT - else -> FuzzedOp.NONE + is JEqExpr -> FuzzedContext.Comparison.NE + is JNeExpr -> FuzzedContext.Comparison.EQ + is JGtExpr -> FuzzedContext.Comparison.LE + is JGeExpr -> FuzzedContext.Comparison.LT + is JLtExpr -> FuzzedContext.Comparison.GE + is JLeExpr -> FuzzedContext.Comparison.GT + else -> FuzzedContext.Unknown } private fun nextDirectUnit(graph: ExceptionalUnitGraph, unit: Unit): Unit? = graph.getSuccsOf(unit).takeIf { it.size == 1 }?.first() \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/sarif/DataClasses.kt b/utbot-framework/src/main/kotlin/org/utbot/sarif/DataClasses.kt index 1109f46d89..9731658270 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/sarif/DataClasses.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/sarif/DataClasses.kt @@ -1,7 +1,13 @@ package org.utbot.sarif -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.annotation.JsonValue +import com.fasterxml.jackson.annotation.* +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.module.SimpleModule +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue /** * Useful links: @@ -14,6 +20,12 @@ data class Sarif( val runs: List ) { companion object { + + private val jsonMapper = jacksonObjectMapper() + .registerModule(SimpleModule() + .addDeserializer(SarifLocationWrapper::class.java, SarifLocationWrapperDeserializer()) + ) + private const val defaultSchema = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json" private const val defaultVersion = @@ -24,7 +36,23 @@ data class Sarif( fun fromRun(run: SarifRun) = Sarif(defaultSchema, defaultVersion, listOf(run)) + + fun fromJson(reportInJson: String): Sarif = + jsonMapper.readValue(reportInJson) } + + fun toJson(): String = + jsonMapper + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .writerWithDefaultPrettyPrinter() + .writeValueAsString(this) + + operator fun plus(other: Sarif): Sarif = + this.copy(runs = this.runs + other.runs) + + @JsonIgnore + fun getAllResults(): List = + runs.flatMap { it.results } } /** @@ -96,10 +124,20 @@ data class SarifResult( val ruleId: String, val level: Level, val message: Message, - val locations: List = listOf(), + val locations: List = listOf(), val relatedLocations: List = listOf(), val codeFlows: List = listOf() -) +) { + /** + * Returns the total number of locations in all [codeFlows]. + */ + fun totalCodeFlowLocations() = + codeFlows.sumOf { codeFlow -> + codeFlow.threadFlows.sumOf { threadFlow -> + threadFlow.locations.size + } + } +} /** * The severity of the result. "Error" for detected unchecked exceptions. @@ -120,14 +158,41 @@ data class Message( val markdown: String? = null ) -// physical location +// location /** * [Documentation](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning#location-object) */ +sealed class SarifLocationWrapper + data class SarifPhysicalLocationWrapper( - val physicalLocation: SarifPhysicalLocation, -) + val physicalLocation: SarifPhysicalLocation // this name used in the SarifLocationWrapperDeserializer +) : SarifLocationWrapper() + +data class SarifLogicalLocationsWrapper( + val logicalLocations: List // this name used in the SarifLocationWrapperDeserializer +) : SarifLocationWrapper() + +/** + * Custom JSON deserializer for sealed class [SarifLocationWrapper]. + * Returns [SarifPhysicalLocationWrapper] or [SarifLogicalLocationsWrapper]. + */ +class SarifLocationWrapperDeserializer : JsonDeserializer() { + override fun deserialize(jp: JsonParser, context: DeserializationContext?): SarifLocationWrapper { + val node: JsonNode = jp.codec.readTree(jp) + val isPhysicalLocation = node.get("physicalLocation") != null // field name + val isLogicalLocations = node.get("logicalLocations") != null // field name + return when { + isPhysicalLocation -> { + jacksonObjectMapper().readValue(node.toString()) + } + isLogicalLocations -> { + return jacksonObjectMapper().readValue(node.toString()) + } + else -> error("SarifLocationWrapperDeserializer: Cannot parse ${node.toPrettyString()}") + } + } +} /** * [Documentation](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning#physicallocation-object) @@ -142,11 +207,44 @@ data class SarifArtifact( val uriBaseId: String = "%SRCROOT%" ) +/** + * All fields should be one-based. + */ data class SarifRegion( val startLine: Int, val endLine: Int? = null, val startColumn: Int? = null, val endColumn: Int? = null +) { + companion object { + /** + * Makes [startColumn] the first non-whitespace character in [startLine] in the [text]. + * If the [text] contains less than [startLine] lines, [startColumn] == null. + * @param startLine should be one-based + */ + fun withStartLine(text: String, startLine: Int): SarifRegion { + val neededLine = text.split('\n').getOrNull(startLine - 1) // to zero-based + val startColumn = neededLine?.run { + takeWhile { it.toString().isBlank() }.length + 1 // to one-based + } + val safeStartLine = if (startLine < 1) { + logger.warn { "For some reason startLine < 1, so now it is equal to 1" } + 1 // we don't want to fail, so just set the line number to 1 + } else { + startLine + } + return SarifRegion(startLine = safeStartLine, startColumn = startColumn) + } + } +} + +// logical locations + +/** + * [Documentation](https://github.com/microsoft/sarif-tutorials/blob/main/docs/2-Basics.md#physical-and-logical-locations) + */ +data class SarifLogicalLocation( + val fullyQualifiedName: String ) // related locations diff --git a/utbot-framework/src/main/kotlin/org/utbot/sarif/RdSourceFindingStrategy.kt b/utbot-framework/src/main/kotlin/org/utbot/sarif/RdSourceFindingStrategy.kt new file mode 100644 index 0000000000..5b38c1e18c --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/sarif/RdSourceFindingStrategy.kt @@ -0,0 +1,24 @@ +package org.utbot.sarif + +import kotlinx.coroutines.runBlocking +import org.utbot.framework.process.generated.RdSourceFindingStrategy +import org.utbot.framework.process.generated.SourceStrategyMethodArgs +import java.io.File + +class RdSourceFindingStrategyFacade( + private val testSetsId: Long, + private val realStrategy: RdSourceFindingStrategy +) : SourceFindingStrategy() { + override val testsRelativePath: String + get() = runBlocking { realStrategy.testsRelativePath.startSuspending(testSetsId) } + + override fun getSourceRelativePath(classFqn: String, extension: String?): String = runBlocking { + realStrategy.getSourceRelativePath.startSuspending(SourceStrategyMethodArgs(testSetsId, classFqn, extension)) + } + + override fun getSourceFile(classFqn: String, extension: String?): File? = runBlocking { + realStrategy.getSourceFile.startSuspending(SourceStrategyMethodArgs(testSetsId, classFqn, extension))?.let { + File(it) + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/sarif/SarifReport.kt b/utbot-framework/src/main/kotlin/org/utbot/sarif/SarifReport.kt index 286476b075..0d0d8d7121 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/sarif/SarifReport.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/sarif/SarifReport.kt @@ -1,19 +1,15 @@ package org.utbot.sarif -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue +import mu.KotlinLogging import org.utbot.common.PathUtil.fileExtension import org.utbot.common.PathUtil.toPath import org.utbot.framework.UtSettings -import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtExecutionFailure -import org.utbot.framework.plugin.api.UtExecutionResult -import org.utbot.framework.plugin.api.UtImplicitlyThrownException -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtOverflowFailure -import org.utbot.framework.plugin.api.UtTestCase +import org.utbot.framework.plugin.api.* +import org.utbot.instrumentation.process.InstrumentedProcessMain +import java.nio.file.Path +import kotlin.io.path.nameWithoutExtension + +internal val logger = KotlinLogging.logger {} /** * Used for the SARIF report creation by given test cases and generated tests code. @@ -24,62 +20,69 @@ import org.utbot.framework.plugin.api.UtTestCase * [Sample report](https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning#example-with-minimum-required-properties) */ class SarifReport( - private val testCases: List, + private val testSets: List, private val generatedTestsCode: String, private val sourceFinding: SourceFindingStrategy ) { - companion object { - /** * Merges several SARIF reports given as JSON-strings into one */ fun mergeReports(reports: List): String = reports.fold(Sarif.empty()) { sarif: Sarif, report: String -> - sarif.copy(runs = sarif.runs + report.jsonToSarif().runs) - }.sarifToJson() - - // internal - - private fun String.jsonToSarif(): Sarif = - jacksonObjectMapper().readValue(this) + sarif + Sarif.fromJson(report) + }.toJson() - private fun Sarif.sarifToJson(): String = - jacksonObjectMapper() - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .writerWithDefaultPrettyPrinter() - .writeValueAsString(this) + /** + * Minimizes SARIF results between several reports. + * + * More complex version of the [SarifReport.minimizeResults]. + */ + fun minimizeSarifResults(srcPathToSarif: MutableMap): MutableMap { + val pathToSarifResult = srcPathToSarif.entries.flatMap { (path, sarif) -> + sarif.getAllResults().map { sarifResult -> + path to sarifResult + } + } + val groupedResults = pathToSarifResult.groupBy { (_, sarifResult) -> + sarifResult.ruleId to sarifResult.locations + } + val minimizedResults = groupedResults.map { (_, sarifResultsGroup) -> + sarifResultsGroup.minByOrNull { (_, sarifResult) -> + sarifResult.totalCodeFlowLocations() + }!! + } + val groupedByPath = minimizedResults + .groupBy { (path, _) -> path } + .mapValues { (_, resultsWithPath) -> + resultsWithPath.map { (_, sarifResult) -> sarifResult } // remove redundant path + } + val pathToSarifTool = srcPathToSarif.mapValues { (_, sarif) -> + sarif.runs.first().tool + } + val paths = pathToSarifTool.keys intersect groupedByPath.keys + return paths.associateWith { path -> + val sarifTool = pathToSarifTool[path]!! + val sarifResults = groupedByPath[path]!! + Sarif.fromRun(SarifRun(sarifTool, sarifResults)) + }.toMutableMap() + } } /** - * Creates a SARIF report and returns it as string - */ - fun createReport(): String = - constructSarif().sarifToJson() - - // internal - - private val defaultLineNumber = 1 // if the line in the source code where the exception is thrown is unknown - - /** - * [Read more about links to locations](https://github.com/microsoft/sarif-tutorials/blob/main/docs/3-Beyond-basics.md#msg-links-location) + * Creates a SARIF report. */ - private val relatedLocationId = 1 // for attaching link to generated test in related locations - - private fun shouldProcessUncheckedException(result: UtExecutionResult) = (result is UtImplicitlyThrownException) - || ((result is UtOverflowFailure) && UtSettings.treatOverflowAsError) - - private fun constructSarif(): Sarif { + fun createReport(): Sarif { val sarifResults = mutableListOf() val sarifRules = mutableSetOf() - for (testCase in testCases) { - for (execution in testCase.executions) { - if (shouldProcessUncheckedException(execution.result)) { - val (sarifResult, sarifRule) = processUncheckedException( - method = testCase.method, + for (testSet in testSets) { + for (execution in testSet.executions) { + if (shouldProcessExecutionResult(execution.result)) { + val (sarifResult, sarifRule) = processExecutionFailure( + method = testSet.method, utExecution = execution, - uncheckedException = execution.result as UtExecutionFailure + executionFailure = execution.result as UtExecutionFailure ) sarifResults += sarifResult sarifRules += sarifRule @@ -90,44 +93,107 @@ class SarifReport( return Sarif.fromRun( SarifRun( SarifTool.fromRules(sarifRules.toList()), - sarifResults + minimizeResults(sarifResults) ) ) } - private fun processUncheckedException( - method: UtMethod<*>, + // internal + + private val defaultLineNumber = 1 // if the line in the source code where the exception is thrown is unknown + + /** + * [Read more about links to locations](https://github.com/microsoft/sarif-tutorials/blob/main/docs/3-Beyond-basics.md#msg-links-location) + */ + private val relatedLocationId = 1 // for attaching link to generated test in related locations + + private val stackTraceLengthForStackOverflow = 50 // stack overflow error may have too many elements + + /** + * Minimizes detected errors and removes duplicates. + * + * Between two [SarifResult]s with the same `ruleId` and `locations` + * it chooses the one with the shorter length of the execution trace. + * + * __Example:__ + * + * The SARIF report for the code below contains only one unchecked exception in `methodB`. + * But without minimization, the report will contain two results: for `methodA` and for `methodB`. + * + * ``` + * class Example { + * int methodA(int a) { + * return methodB(a); + * } + * int methodB(int b) { + * return 1 / b; + * } + * } + * ``` + */ + private fun minimizeResults(sarifResults: List): List { + val groupedResults = sarifResults.groupBy { sarifResult -> + Pair(sarifResult.ruleId, sarifResult.locations) + } + val minimizedResults = groupedResults.map { (_, sarifResultsGroup) -> + sarifResultsGroup.minByOrNull { sarifResult -> + sarifResult.totalCodeFlowLocations() + }!! + } + return minimizedResults + } + + private fun processExecutionFailure( + method: ExecutableId, utExecution: UtExecution, - uncheckedException: UtExecutionFailure + executionFailure: UtExecutionFailure ): Pair { - val exceptionName = uncheckedException.exception::class.java.simpleName - val ruleId = "utbot.unchecked.$exceptionName" + val exceptionName = executionFailure.exception::class.java.simpleName + val ruleId = "utbot.exception.$exceptionName" - val methodName = method.callable.name - val classFqn = method.clazz.qualifiedName + val methodName = method.name + val classFqn = method.classId.name val methodArguments = utExecution.stateBefore.parameters .joinToString(prefix = "", separator = ", ", postfix = "") { it.preview() } + val errorMessage = when (executionFailure) { + is UtTimeoutException -> { + "Unexpected behavior: ${executionFailure.exception.message}" + } + is UtTaintAnalysisFailure -> { + executionFailure.exception.message + } + else -> { + val exceptionSimpleName = executionFailure.exception::class.java.simpleName + val exceptionMessage = executionFailure.exception.message + if (exceptionMessage == null) { + "Unexpected $exceptionSimpleName" + } else { + "Unexpected $exceptionSimpleName: $exceptionMessage" + } + } + } + val sarifResult = SarifResult( ruleId, Level.Error, Message( text = """ - Unchecked exception: ${uncheckedException.exception}. + $errorMessage. Test case: `$methodName($methodArguments)` [Generated test for this case]($relatedLocationId) """.trimIndent() ), - getLocations(utExecution, classFqn), + getLocations(method, utExecution, classFqn), getRelatedLocations(utExecution), - getCodeFlows(method, utExecution, uncheckedException) + getCodeFlows(method, utExecution, executionFailure) ) val sarifRule = SarifRule( ruleId, exceptionName, SarifRule.Description( - text = "Unchecked $exceptionName detected." + text = "Unexpected $exceptionName detected." ), SarifRule.Description( text = "Seems like an exception $exceptionName might be thrown." @@ -140,29 +206,35 @@ class SarifReport( return Pair(sarifResult, sarifRule) } - private fun getLocations(utExecution: UtExecution, classFqn: String?): List { + private fun getLocations( + method: ExecutableId, + utExecution: UtExecution, + classFqn: String? + ): List { if (classFqn == null) return listOf() - val sourceRelativePath = sourceFinding.getSourceRelativePath(classFqn) - val sourceRegion = SarifRegion( - startLine = extractLineNumber(utExecution) ?: defaultLineNumber - ) + val (startLine, classWithErrorFqn) = getLastLineNumberWithClassFqn(method, utExecution, classFqn) + val sourceCode = sourceFinding.getSourceFile(classWithErrorFqn)?.readText() ?: "" + val sourceRegion = SarifRegion.withStartLine(sourceCode, startLine) + val sourceRelativePath = sourceFinding.getSourceRelativePath(classWithErrorFqn) return listOf( SarifPhysicalLocationWrapper( SarifPhysicalLocation(SarifArtifact(sourceRelativePath), sourceRegion) + ), + SarifLogicalLocationsWrapper( + listOf(SarifLogicalLocation(classWithErrorFqn)) // class name without method name ) ) } private fun getRelatedLocations(utExecution: UtExecution): List { - val lineNumber = generatedTestsCode.split('\n').indexOfFirst { line -> - utExecution.testMethodName?.let { testMethodName -> - line.contains(testMethodName) - } ?: false - } - val sourceRegion = SarifRegion( - startLine = if (lineNumber != -1) lineNumber + 1 else defaultLineNumber - ) + val startLine = utExecution.testMethodName?.let { testMethodName -> + val neededLine = generatedTestsCode.split('\n').indexOfFirst { line -> + line.contains("$testMethodName(") + } + if (neededLine == -1) null else neededLine + 1 // to one-based + } ?: defaultLineNumber + val sourceRegion = SarifRegion.withStartLine(generatedTestsCode, startLine) return listOf( SarifRelatedPhysicalLocationWrapper( relatedLocationId, @@ -172,42 +244,34 @@ class SarifReport( } private fun getCodeFlows( - method: UtMethod<*>, + method: ExecutableId, utExecution: UtExecution, - uncheckedException: UtExecutionFailure + executionFailure: UtExecutionFailure ): List { - /* Example of a typical stack trace: - - java.lang.Math.multiplyExact(Math.java:867) - - com.abc.Util.multiply(Util.java:10) - - com.abc.Util.multiply(Util.java:6) - - com.abc.Main.example(Main.java:11) // <- `lastMethodCallIndex` - - sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) - - ... - */ - val stackTrace = uncheckedException.exception.stackTrace + val stackTraceResolved = filterStackTrace(method, executionFailure) + .mapNotNull { findStackTraceElementLocation(it) } + .toMutableList() - val lastMethodCallIndex = stackTrace.indexOfLast { - it.className == method.clazz.qualifiedName && it.methodName == method.callable.name + if (stackTraceResolved.isEmpty() && utExecution is UtSymbolicExecution) { + stackTraceResolved += stackTraceFromSymbolicSteps(utExecution) // fallback logic } - if (lastMethodCallIndex == -1) - return listOf() - // taking all elements before the last `method` call - val stackTraceFiltered = stackTrace.take(lastMethodCallIndex + 1) - val stackTraceResolved = stackTraceFiltered.mapNotNull { - findStackTraceElementLocation(it) - }.toMutableList() - if (stackTraceResolved.isEmpty()) - return listOf() // empty stack trace is not shown + if (stackTraceResolved.isEmpty()) { // still empty + stackTraceResolved += stackTraceFromCoverage(utExecution) // fallback logic (2) + } // prepending stack trace by `method` call in generated tests val methodCallLocation: SarifPhysicalLocation? = - findMethodCallInTestBody(utExecution.testMethodName, method.callable.name) + findMethodCallInTestBody(utExecution.testMethodName, method.name, utExecution) if (methodCallLocation != null) { + val testFileName = sourceFinding.testsRelativePath.toPath().fileName + val testClassName = testFileName.nameWithoutExtension + val testMethodName = utExecution.testMethodName + val methodCallLineNumber = methodCallLocation.region.startLine val methodCallLocationWrapper = SarifFlowLocationWrapper( SarifFlowLocation( message = Message( - text = "${sourceFinding.testsRelativePath.toPath().fileName}:${methodCallLocation.region.startLine}" + text = "$testClassName.$testMethodName($testFileName:$methodCallLineNumber)" ), physicalLocation = methodCallLocation ) @@ -222,12 +286,122 @@ class SarifReport( ) } + private fun filterStackTrace( + method: ExecutableId, + executionFailure: UtExecutionFailure + ): List { + /* Example of a typical stack trace: + - java.lang.Math.multiplyExact(Math.java:867) + - com.abc.Util.multiply(Util.java:10) + - com.abc.Util.multiply(Util.java:6) + - com.abc.Main.example(Main.java:11) // <- `lastMethodCallIndex` + - sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + - ... + */ + var stackTrace = executionFailure.exception.stackTrace.toList() + + val lastMethodCallIndex = stackTrace.indexOfLast { + it.className == method.classId.name && it.methodName == method.name + } + if (lastMethodCallIndex != -1) { + // taking all elements before the last `method` call + stackTrace = stackTrace.take(lastMethodCallIndex + 1) + } else { // no `method` call in the stack trace + if (executionFailure.exception !is StackOverflowError) { + stackTrace = listOf() // (likely) the stack trace contains only our internal calls + } + } + + if (executionFailure.exception is StackOverflowError) { + stackTrace = stackTrace.takeLast(stackTraceLengthForStackOverflow) + } + + val stackTraceFiltered = stackTrace.filter { + // filter all internal calls related to the instrumentation process + val forbiddenPackage = InstrumentedProcessMain::class.java.`package`.name + !it.className.startsWith(forbiddenPackage) + } + + return stackTraceFiltered + } + + /** + * Constructs the stack trace from the symbolic path. + */ + private fun stackTraceFromSymbolicSteps(utExecution: UtSymbolicExecution): List { + val depth2LastStep = mutableListOf() + for (step in utExecution.symbolicSteps) { + if (step.callDepth >= depth2LastStep.size) { + depth2LastStep.add(step) + } else if (step.callDepth >= 0) { + depth2LastStep[step.callDepth] = step + while (step.callDepth != depth2LastStep.size - 1) { + depth2LastStep.removeLast() + } + } + } + + val sarifExecutionTrace = depth2LastStep.map { step -> + resolveStackTraceElementByNames( + classFqn = step.method.declaringClass.name, + methodName = step.method.name, + lineNumber = step.lineNumber, + ) + } + + return sarifExecutionTrace.reversed() // to stack trace + } + + /** + * Constructs the stack trace from the list of covered instructions. + */ + private fun stackTraceFromCoverage(utExecution: UtExecution): List { + val coveredInstructions = utExecution.coverage?.coveredInstructions + ?: return listOf() + + val executionTrace = coveredInstructions.groupBy { instruction -> + instruction.internalName to instruction.methodSignature // group by method + }.map { (_, instructionsForOneMethod) -> + instructionsForOneMethod.last() // we need only last to construct the stack trace + } + + val sarifExecutionTrace = executionTrace.map { instruction -> + resolveStackTraceElementByNames( + classFqn = instruction.className, + methodName = instruction.methodSignature.substringBefore('('), + lineNumber = instruction.lineNumber + ) + } + + return sarifExecutionTrace.reversed() // to stack trace + } + + private fun resolveStackTraceElementByNames( + classFqn: String, + methodName: String, + lineNumber: Int + ): SarifFlowLocationWrapper { + val sourceFilePath = sourceFinding.getSourceRelativePath(classFqn) + val sourceFileName = sourceFilePath.substringAfterLast('/') + + return SarifFlowLocationWrapper( + SarifFlowLocation( + message = Message("$classFqn.$methodName($sourceFileName:$lineNumber)"), + physicalLocation = SarifPhysicalLocation( + SarifArtifact(uri = sourceFilePath), + SarifRegion(startLine = lineNumber) // lineNumber is one-based + ) + ) + ) + } + private fun findStackTraceElementLocation(stackTraceElement: StackTraceElement): SarifFlowLocationWrapper? { val lineNumber = stackTraceElement.lineNumber if (lineNumber < 1) return null val extension = stackTraceElement.fileName?.toPath()?.fileExtension val relativePath = sourceFinding.getSourceRelativePath(stackTraceElement.className, extension) + val sourceCode = sourceFinding.getSourceFile(stackTraceElement.className, extension)?.readText() ?: "" return SarifFlowLocationWrapper( SarifFlowLocation( message = Message( @@ -235,38 +409,64 @@ class SarifReport( ), physicalLocation = SarifPhysicalLocation( SarifArtifact(relativePath), - SarifRegion(lineNumber) + SarifRegion.withStartLine(sourceCode, lineNumber) ) ) ) } - private fun findMethodCallInTestBody(testMethodName: String?, methodName: String): SarifPhysicalLocation? { + private val testsBodyLines by lazy { + generatedTestsCode.split('\n') + } + + private fun findMethodCallInTestBody( + testMethodName: String?, + methodName: String, + utExecution: UtExecution, + ): SarifPhysicalLocation? { if (testMethodName == null) return null + if (utExecution.result is UtSandboxFailure) // if there is no method call in test + return getRelatedLocations(utExecution).firstOrNull()?.physicalLocation // searching needed test - val testsBodyLines = generatedTestsCode.split('\n') val testMethodStartsAt = testsBodyLines.indexOfFirst { line -> line.contains(testMethodName) } if (testMethodStartsAt == -1) return null + /* + * ... + * public void testMethodName() { // <- `testMethodStartsAt` + * ... + * className.methodName(...) // <- needed `startLine` + * ... + * } + */ // searching needed method call - val publicMethodCallPattern = "$methodName(" - val privateMethodCallPattern = Regex("""$methodName.*\.invoke\(""") // using reflection - val methodCallLineNumber = testsBodyLines + // Regex("[^:]*") satisfies every character except ':' + // It is necessary to avoid strings from the stacktrace, such as "className.methodName(FileName.java:10)" + val publicMethodCallPattern = Regex("""$methodName\([^:]*\)""") + val privateMethodCallPattern = Regex("""$methodName.*\.invoke\([^:]*\)""") // using reflection + val methodCallShiftInTestMethod = testsBodyLines .drop(testMethodStartsAt + 1) // for search after it .indexOfFirst { line -> line.contains(publicMethodCallPattern) || line.contains(privateMethodCallPattern) } - if (methodCallLineNumber == -1) + if (methodCallShiftInTestMethod == -1) return null + // `startLine` consists of: + // shift to the testMethod call (+ testMethodStartsAt) + // the line with testMethodName (+ 1) + // shift to the method call (+ methodCallShiftInTestMethod) + // to one-based (+ 1) + val startLine = testMethodStartsAt + 1 + methodCallShiftInTestMethod + 1 + return SarifPhysicalLocation( SarifArtifact(sourceFinding.testsRelativePath), - SarifRegion(startLine = methodCallLineNumber + 1 + testMethodStartsAt + 1) + SarifRegion.withStartLine(generatedTestsCode, startLine) ) } @@ -286,10 +486,63 @@ class SarifReport( return "..." } - private fun extractLineNumber(utExecution: UtExecution): Int? = - try { - utExecution.path.lastOrNull()?.stmt?.javaSourceStartLineNumber + /** + * Returns the number of the last line in the execution path + * And the name of the class in which it is located. + */ + private fun getLastLineNumberWithClassFqn( + method: ExecutableId, + utExecution: UtExecution, + defaultClassFqn: String + ): Pair { + val lastSymbolicStep = (utExecution as? UtSymbolicExecution)?.symbolicSteps?.lastOrNull() + if (lastSymbolicStep != null) { + return Pair( + lastSymbolicStep.lineNumber, + lastSymbolicStep.method.declaringClass.name + ) + } + + val coveredInstructions = utExecution.coverage?.coveredInstructions + val lastCoveredInstruction = coveredInstructions?.lastOrNull() + if (lastCoveredInstruction != null) { + return Pair( + lastCoveredInstruction.lineNumber, // .lineNumber is one-based + lastCoveredInstruction.className + ) + } + + // if for some reason we can't extract the last line from the coverage + val lastPathElementLineNumber = try { + // path/fullPath might be empty when engine executes in another process - + // soot entities cannot be passed to the main process because kryo cannot deserialize them + (utExecution as? UtSymbolicExecution)?.path?.lastOrNull()?.stmt?.javaSourceStartLineNumber // one-based } catch (t: Throwable) { null } + if (lastPathElementLineNumber != null) { + return Pair(lastPathElementLineNumber, defaultClassFqn) + } + + val methodDefinitionLine = getMethodDefinitionLineNumber(method) + return Pair(methodDefinitionLine ?: defaultLineNumber, defaultClassFqn) + } + + private fun getMethodDefinitionLineNumber(method: ExecutableId): Int? { + val sourceFile = sourceFinding.getSourceFile(method.classId.canonicalName) + val lineNumber = sourceFile?.readLines()?.indexOfFirst { line -> + line.contains(" ${method.name}(") // method definition + } + return if (lineNumber == null || lineNumber == -1) null else lineNumber + 1 // to one-based + } + + private fun shouldProcessExecutionResult(result: UtExecutionResult): Boolean { + val implicitlyThrown = result is UtImplicitlyThrownException + val overflowFailure = result is UtOverflowFailure && UtSettings.treatOverflowAsError + val taintAnalysisFailure = result is UtTaintAnalysisFailure && UtSettings.useTaintAnalysis + val assertionError = result is UtExplicitlyThrownException && result.exception is AssertionError + val sandboxFailure = result is UtSandboxFailure + val timeoutException = result is UtTimeoutException + return implicitlyThrown || overflowFailure || taintAnalysisFailure || assertionError || sandboxFailure || timeoutException + } } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/sarif/SourceFindingStrategy.kt b/utbot-framework/src/main/kotlin/org/utbot/sarif/SourceFindingStrategy.kt index 88a87466ee..b2ef8b82df 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/sarif/SourceFindingStrategy.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/sarif/SourceFindingStrategy.kt @@ -4,6 +4,7 @@ import org.utbot.common.PathUtil import org.utbot.common.PathUtil.classFqnToPath import org.utbot.common.PathUtil.fileExtension import org.utbot.common.PathUtil.toPath +import java.io.File /** * Defines the search strategy for the source files. Used when creating a SARIF report. @@ -20,6 +21,11 @@ abstract class SourceFindingStrategy { * Returns a path to the source file by given [classFqn]. */ abstract fun getSourceRelativePath(classFqn: String, extension: String? = null): String + + /** + * Returns the source file by given [classFqn]. + */ + abstract fun getSourceFile(classFqn: String, extension: String? = null): File? } /** @@ -41,13 +47,13 @@ class SourceFindingStrategyDefault( ) : SourceFindingStrategy() { /** - * Tries to construct the relative path to tests (against `projectRootPath`) using the `testsFilePath` + * Tries to construct the relative path to tests (against `projectRootPath`) using the `testsFilePath`. */ override val testsRelativePath = PathUtil.safeRelativize(projectRootPath, testsFilePath) ?: testsFilePath.toPath().fileName.toString() /** - * Tries to guess the relative path (against `projectRootPath`) to the source file containing the class [classFqn] + * Tries to guess the relative path (against `projectRootPath`) to the source file containing the class [classFqn]. */ override fun getSourceRelativePath(classFqn: String, extension: String?): String { val fileExtension = extension ?: sourceExtension @@ -56,6 +62,16 @@ class SourceFindingStrategyDefault( return relativePath ?: (classFqnToPath(classFqn) + fileExtension) } + /** + * Tries to find the source file containing the class [classFqn]. + * Returns null if the file does not exist. + */ + override fun getSourceFile(classFqn: String, extension: String?): File? { + val fileExtension = extension ?: sourceExtension + val absolutePath = resolveClassFqn(sourceFilesDirectory, classFqn, fileExtension) + return absolutePath?.let(::File) + } + // internal private val sourceExtension = sourceFilePath.toPath().fileExtension @@ -64,8 +80,9 @@ class SourceFindingStrategyDefault( PathUtil.removeClassFqnFromPath(sourceFilePath, sourceClassFqn) /** - * Resolves [classFqn] against [absolutePath] and checks if a resolved path exists - * Example: resolveClassFqn("C:/project/src/", "com.Main") = "C:/project/src/com/Main.java" + * Resolves [classFqn] against [absolutePath] and checks if a resolved path exists. + * + * Example: resolveClassFqn("C:/project/src/", "com.Main") = "C:/project/src/com/Main.java". */ private fun resolveClassFqn(absolutePath: String?, classFqn: String, extension: String = ".java"): String? { if (absolutePath == null) diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/TaintConfigurationProvider.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/TaintConfigurationProvider.kt new file mode 100644 index 0000000000..e0e9b101a4 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/TaintConfigurationProvider.kt @@ -0,0 +1,158 @@ +package org.utbot.taint + +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.internal.synchronized +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.cbor.Cbor +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.encodeToByteArray +import mu.KotlinLogging +import org.utbot.common.PathUtil.toPath +import org.utbot.common.utBotTempDirectory +import org.utbot.taint.model.TaintConfiguration +import org.utbot.taint.parser.yaml.TaintYamlParser +import java.io.File + +const val MODIFICATION_TIME_INVALID = 0L +const val TAINT_CONFIGURATION_RESOURCES_PATH = "taint/config.yaml" +const val TAINT_CONFIGURATION_CACHED_DEFAULT_NAME = "taint-config-cached" + +val lockResourcesCachedFile = Object() + +private val logger = KotlinLogging.logger {} + +/** + * Provide the [TaintConfiguration]. + */ +interface TaintConfigurationProvider { + + /** + * Returns the time that the configuration was last modified. + * + * Uses the same format as the [java.io.File.lastModified]. + */ + fun lastModified(): Long + + /** + * Provides [TaintConfiguration]. + */ + fun getConfiguration(): TaintConfiguration +} + +/** + * Provides an empty [TaintConfiguration]. + */ +object TaintConfigurationProviderEmpty : TaintConfigurationProvider { + override fun lastModified() = MODIFICATION_TIME_INVALID + override fun getConfiguration() = TaintConfiguration() // no rules +} + +/** + * Reads and parses [TaintConfiguration] from the [configPath]. + * + * @param configPath relative path to the .yaml file in resources + */ +class TaintConfigurationProviderResources( + private val configPath: String = TAINT_CONFIGURATION_RESOURCES_PATH +) : TaintConfigurationProvider { + + override fun lastModified(): Long { + val selfJarFile = File(javaClass.protectionDomain.codeSource.location.toURI().path) + return selfJarFile.lastModified() + } + + override fun getConfiguration(): TaintConfiguration { + val configUrls = javaClass.classLoader.getResources(configPath).toList() + val configUrl = when (configUrls.size) { + 0 -> return TaintConfiguration() + 1 -> configUrls.single() + else -> error("Cannot choose between several taint configurations: $configUrls") + } + val yamlInput = configUrl.readText() + val yamlConfig = TaintYamlParser.parse(yamlInput) + return YamlTaintConfigurationAdapter.convert(yamlConfig) + } +} + +/** + * Reads and parses [TaintConfiguration] from the [configPath]. + * + * @param configPath relative path to the .yaml file in the user's project. + */ +class TaintConfigurationProviderUserRules(private val configPath: String) : TaintConfigurationProvider { + + override fun lastModified(): Long { + val configFile = configPath.toPath().toFile() + if (!configFile.exists()) { + return MODIFICATION_TIME_INVALID + } + return configFile.lastModified() + } + + override fun getConfiguration(): TaintConfiguration { + val configFile = configPath.toPath().toFile() + if (!configFile.exists()) { + logger.warn { "Taint analysis configuration not found: file $configPath doesn't exist." } + return TaintConfiguration() + } else { + logger.info { "Used taint analysis configuration from file $configPath." } + } + val yamlInput = configFile.readText() + val yamlConfig = TaintYamlParser.parse(yamlInput) + return YamlTaintConfigurationAdapter.convert(yamlConfig) + } +} + +/** + * Combines taint configurations from several providers. + */ +class TaintConfigurationProviderCombiner(private val inners: List) : TaintConfigurationProvider { + + override fun lastModified(): Long { + return inners.maxOfOrNull { it.lastModified() } ?: MODIFICATION_TIME_INVALID + } + + override fun getConfiguration(): TaintConfiguration = + inners.fold(TaintConfiguration()) { resultConfig, configProvider -> + resultConfig + configProvider.getConfiguration() + } +} + +/** + * Stores binary configuration file to the utbot temp directory to reduce parsing time in the future. + * + * @param nameSuffix the cached file will have "-$nameSuffix" suffix + * @param inner provider to cache + */ +class TaintConfigurationProviderCached( + nameSuffix: String, + private val inner: TaintConfigurationProvider, +) : TaintConfigurationProvider { + + private val cachedConfigName = "$TAINT_CONFIGURATION_CACHED_DEFAULT_NAME-$nameSuffix" + + private val cachedConfigFile = utBotTempDirectory.resolve(cachedConfigName).toFile() + + override fun lastModified(): Long { + if (!cachedConfigFile.exists()) { + return MODIFICATION_TIME_INVALID + } + return cachedConfigFile.lastModified() + } + + @OptIn(InternalCoroutinesApi::class, ExperimentalSerializationApi::class) + override fun getConfiguration(): TaintConfiguration = + if (!cachedConfigFile.exists() || cachedConfigFile.lastModified() < inner.lastModified()) { + val config = inner.getConfiguration() + val bytes = Cbor.encodeToByteArray(config) + synchronized(lockResourcesCachedFile) { + cachedConfigFile.writeBytes(bytes) + } + config + } else { + val bytes = synchronized(lockResourcesCachedFile) { + cachedConfigFile.readBytes() + } + Cbor.decodeFromByteArray(bytes) + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/TaintContext.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/TaintContext.kt new file mode 100644 index 0000000000..2ba3be0187 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/TaintContext.kt @@ -0,0 +1,11 @@ +package org.utbot.taint + +import org.utbot.taint.model.TaintConfiguration + +/** + * Mutable state that is modified during the taint analyzer work. + */ +class TaintContext( + val markManager: TaintMarkManager, + val configuration: TaintConfiguration, +) diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/TaintMarkManager.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/TaintMarkManager.kt new file mode 100644 index 0000000000..d1144af812 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/TaintMarkManager.kt @@ -0,0 +1,144 @@ +package org.utbot.taint + +import kotlinx.collections.immutable.persistentListOf +import org.utbot.engine.* +import org.utbot.engine.pc.* +import org.utbot.engine.symbolic.SymbolicStateUpdate +import org.utbot.taint.TaintUtil.isEmpty +import org.utbot.taint.model.TaintMark +import org.utbot.taint.model.TaintMarks +import org.utbot.taint.model.TaintMarksAll +import org.utbot.taint.model.TaintMarksSet + +/** + * Manages taint vectors. + */ +class TaintMarkManager(private val markRegistry: TaintMarkRegistry) { + + /** + * Returns true if taint vector at address [addr] in [memory] contains taint [mark]. + */ + fun containsMark(memory: Memory, addr: UtAddrExpression, mark: TaintMark): UtBoolExpression { + val taintVector = memory.taintVector(addr).toLongValue() + val taintMarkId = markRegistry.idByMark(mark) + val taintMarkVector = mkLong(taintMarkId).toLongValue() + return mkNot(mkEq(And(taintVector, taintMarkVector), emptyTaintVector)) + } + + /** + * Returns true if taint vector at address [addr] in [memory] contains any taint mark. + */ + fun containsAnyMark(memory: Memory, addr: UtAddrExpression): UtBoolExpression { + val taintVector = memory.taintVector(addr).toLongValue() + return mkNot(mkEq(taintVector, emptyTaintVector.toLongValue())) + } + + /** + * Replaces the taint vector at address [addr] with a new taint vector constructed from [marks]. + * Replaces only if [condition] is true. + */ + fun setMarks( + memory: Memory, + addr: UtAddrExpression, + marks: TaintMarks, + condition: UtBoolExpression + ): SymbolicStateUpdate { + if (marks.isEmpty()) { + return SymbolicStateUpdate() + } + + val newTaintVector = constructTaintVector(marks) + val oldTaintVector = memory.taintVector(addr) + + val taintUpdateExpr = UtIteExpression( + condition, + thenExpr = newTaintVector, + elseExpr = oldTaintVector + ) + + return updateAddr(addr, taintUpdateExpr) + } + + /** + * Removes taint [marks] from the taint vector at address [addr] in [memory] if [condition] is true. + */ + fun clearMarks( + memory: Memory, + addr: UtAddrExpression, + marks: TaintMarks, + condition: UtBoolExpression + ): SymbolicStateUpdate { + if (marks.isEmpty()) { + return SymbolicStateUpdate() + } + + val taintMarks = constructTaintVector(marks).toLongValue() + val taintMarksNegated = UtBvNotExpression(taintMarks).toLongValue() + val oldTaintVectorExpr = memory.taintVector(addr) + val newTaintVectorExpr = And(taintMarksNegated, oldTaintVectorExpr.toLongValue()) + + val taintUpdateExpr = UtIteExpression( + condition, + thenExpr = newTaintVectorExpr, + elseExpr = oldTaintVectorExpr + ) + + return updateAddr(addr, taintUpdateExpr) + } + + /** + * Passes taint [marks] contained in the taint vector at [fromAddr] + * from [fromAddr] to [toAddr] in [memory] if [condition] is true. + */ + fun passMarks( + memory: Memory, + fromAddr: UtAddrExpression, + toAddr: UtAddrExpression, + marks: TaintMarks, + condition: UtBoolExpression + ): SymbolicStateUpdate { + if (marks.isEmpty()) { + return SymbolicStateUpdate() + } + + val taintMarks = constructTaintVector(marks).toLongValue() + val oldTaintVectorFrom = memory.taintVector(fromAddr).toLongValue() + val intersection = And(taintMarks, oldTaintVectorFrom) + val hasAtLeastOneMark = mkNot(mkEq(intersection, emptyTaintVector)) + + val oldTaintVectorExprTo = memory.taintVector(toAddr) + val newTaintVectorExprTo = Or(oldTaintVectorExprTo.toLongValue(), intersection.toLongValue()) + + val taintUpdateExpr = UtIteExpression( + mkAnd(condition, hasAtLeastOneMark), + thenExpr = newTaintVectorExprTo, + elseExpr = oldTaintVectorExprTo + ) + + return updateAddr(toAddr, taintUpdateExpr) + } + + // internal + + private val emptyTaintVector = mkLong(0L) + + /** + * Returns taint vector that represents [marks]. + */ + private fun constructTaintVector(marks: TaintMarks): UtBvExpression { + val taintedLongValue = when (marks) { + TaintMarksAll -> -1L // 0b11..1111 + is TaintMarksSet -> marks.marks.fold(initial = 0L) { acc, mark -> + acc or markRegistry.idByMark(mark) + } + } + return mkLong(taintedLongValue) + } + + private fun updateAddr(addr: UtAddrExpression, updateTaintVectorExpr: UtExpression): SymbolicStateUpdate = + SymbolicStateUpdate( + memoryUpdates = MemoryUpdate( + taintArrayUpdate = persistentListOf(addr to updateTaintVectorExpr) + ) + ) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/TaintMarkRegistry.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/TaintMarkRegistry.kt new file mode 100644 index 0000000000..2c6f00c678 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/TaintMarkRegistry.kt @@ -0,0 +1,29 @@ +package org.utbot.taint + +import com.google.common.collect.BiMap +import com.google.common.collect.HashBiMap +import org.utbot.taint.model.TaintMark + +/** + * Conversion between the taint mark name and 64-bit taint vector (some power of 2). + * Can contain a maximum of 64 different marks. + */ +class TaintMarkRegistry { + + fun idByMark(mark: TaintMark): Long = + taintMarkToIdBiMap.getOrPut(mark) { + nextId.also { nextId *= 2 } + } + + fun containsMark(mark: TaintMark): Boolean = + mark in taintMarkToIdBiMap + + fun markById(id: Long): TaintMark = // TODO: return null? + taintMarkToIdBiMap.inverse()[id] ?: error("Unknown mark id: $id") + + // internal + + private var nextId: Long = 1 // 2 ** 0 + + private val taintMarkToIdBiMap: BiMap = HashBiMap.create() +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/TaintUtil.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/TaintUtil.kt new file mode 100644 index 0000000000..ca88ec1e6b --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/TaintUtil.kt @@ -0,0 +1,25 @@ +package org.utbot.taint + +import org.utbot.taint.model.* + +object TaintUtil { + + /** + * Chooses base, argN or result corresponding to [this] entity. + */ + fun TaintEntity.chooseRelatedValue(base: T, args: List, result: T): T? = + when (this) { + TaintEntityThis -> base + is TaintEntityArgument -> args.elementAtOrNull(index.toInt() - 1) + TaintEntityReturn -> result + } + + /** + * Check if [this] does not contain marks. + */ + fun TaintMarks.isEmpty(): Boolean = + when (this) { + TaintMarksAll -> false + is TaintMarksSet -> marks.isEmpty() + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/YamlTaintConfigurationAdapter.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/YamlTaintConfigurationAdapter.kt new file mode 100644 index 0000000000..7fab87f974 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/YamlTaintConfigurationAdapter.kt @@ -0,0 +1,132 @@ +package org.utbot.taint + +import org.utbot.taint.model.* +import org.utbot.taint.parser.yaml.* + +/** + * Converts objects from .yaml config to model data classes. + */ +object YamlTaintConfigurationAdapter { + + fun convert(configuration: YamlTaintConfiguration) = + TaintConfiguration( + configuration.sources.map { it.convert() }, + configuration.passes.map { it.convert() }, + configuration.cleaners.map { it.convert() }, + configuration.sinks.map { it.convert() }, + ) + + // internal + + private fun YamlTaintSource.convert() = + TaintSource( + methodFqn.convert(), + addTo.convert(), + marks.convert(), + signature.convert(), + conditions.convert() + ) + + private fun YamlTaintPass.convert() = + TaintPass( + methodFqn.convert(), + getFrom.convert(), + addTo.convert(), + marks.convert(), + signature.convert(), + conditions.convert() + ) + + private fun YamlTaintCleaner.convert() = + TaintCleaner( + methodFqn.convert(), + removeFrom.convert(), + marks.convert(), + signature.convert(), + conditions.convert() + ) + + private fun YamlTaintSink.convert() = + TaintSink( + methodFqn.convert(), + check.convert(), + marks.convert(), + signature.convert(), + conditions.convert() + ) + + private fun YamlTaintSignature.convert(): TaintSignature = + when (this) { + YamlTaintSignatureAny -> TaintSignatureAny + is YamlTaintSignatureList -> TaintSignatureList( + argumentTypes.map { it.convert() } + ) + } + + private fun YamlTaintConditions.convert(): TaintCondition = + when (this) { + YamlNoTaintConditions -> ConditionTrue + is YamlTaintConditionsMap -> ConditionAnd( + entityToCondition.map { (entity, condition) -> + condition.convert(entity) + } + ) + } + + private fun YamlTaintCondition.convert(entity: YamlTaintEntity): TaintCondition = + when (this) { + is YamlTaintConditionEqualValue -> { + ConditionEqualValue(entity.convert(), argumentValue.convert()) + } + is YamlTaintConditionIsType -> { + ConditionIsType(entity.convert(), argumentType.convert()) + } + is YamlTaintConditionNot -> { + ConditionNot(inner.convert(entity)) + } + is YamlTaintConditionOr -> { + ConditionOr(inners.map { it.convert(entity) }) + } + } + + private fun YamlMethodFqn.convert() = + MethodFqnValue(packageNames, className, methodName) + + private fun YamlTaintEntities.convert(): TaintEntities = + when (this) { + is YamlTaintEntitiesSet -> { + TaintEntitiesSet(entities.map { it.convert() }.toSet()) + } + } + + private fun YamlTaintMarks.convert(): TaintMarks = + when (this) { + YamlTaintMarksAll -> TaintMarksAll + is YamlTaintMarksSet -> TaintMarksSet(marks.map { it.convert() }.toSet()) + } + + private fun YamlTaintEntity.convert(): TaintEntity = + when (this) { + is YamlTaintEntityArgument -> TaintEntityArgument(index) + YamlTaintEntityReturn -> TaintEntityReturn + YamlTaintEntityThis -> TaintEntityThis + } + + private fun YamlTaintMark.convert() = + TaintMark(name) + + private fun YamlArgumentType.convert(): ArgumentType = + when (this) { + YamlArgumentTypeAny -> ArgumentTypeAny + is YamlArgumentTypeString -> ArgumentTypeString(typeFqn) + } + + private fun YamlArgumentValue.convert(): ArgumentValue = + when (this) { + YamlArgumentValueNull -> ArgumentValueNull + is YamlArgumentValueBoolean -> ArgumentValueBoolean(value) + is YamlArgumentValueLong -> ArgumentValueLong(value) + is YamlArgumentValueDouble -> ArgumentValueDouble(value) + is YamlArgumentValueString -> ArgumentValueString(value) + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/model/DataClasses.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/model/DataClasses.kt new file mode 100644 index 0000000000..85166eb920 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/model/DataClasses.kt @@ -0,0 +1,47 @@ +package org.utbot.taint.model + +import kotlinx.serialization.Serializable + +// Data classes corresponding to the parsed objects. +// See [org.utbot.taint.parser] for more details. + +@Serializable sealed interface MethodFqn +@Serializable data class MethodFqnValue( + val packageNames: List, + val className: String, + val methodName: String +) : MethodFqn +@Serializable data class MethodFqnRegex(val regex: String) : MethodFqn + +@Serializable +sealed interface TaintEntities { + val entities: Collection +} +@Serializable data class TaintEntitiesSet(override val entities: Set) : TaintEntities + +@Serializable sealed interface TaintMarks +@Serializable object TaintMarksAll : TaintMarks +@Serializable data class TaintMarksSet(val marks: Set) : TaintMarks + +@Serializable sealed interface TaintEntity +@Serializable object TaintEntityThis : TaintEntity +@Serializable object TaintEntityReturn : TaintEntity +@Serializable data class TaintEntityArgument(/** one-based */ val index: UInt) : TaintEntity + +@Serializable data class TaintMark(val name: String) + +@Serializable sealed interface ArgumentValue +@Serializable object ArgumentValueNull : ArgumentValue +@Serializable data class ArgumentValueBoolean(val value: Boolean) : ArgumentValue +@Serializable data class ArgumentValueLong(val value: Long) : ArgumentValue +@Serializable data class ArgumentValueDouble(val value: Double) : ArgumentValue +@Serializable data class ArgumentValueString(val value: String) : ArgumentValue + +@Serializable sealed interface ArgumentType +@Serializable object ArgumentTypeAny : ArgumentType +@Serializable data class ArgumentTypeString(val typeFqn: String) : ArgumentType +@Serializable data class ArgumentTypeRegex(val typeFqnRegex: String) : ArgumentType + +@Serializable sealed interface TaintSignature +@Serializable object TaintSignatureAny : TaintSignature +@Serializable data class TaintSignatureList(val argumentTypes: List) : TaintSignature diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/model/SymbolicMethodData.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/model/SymbolicMethodData.kt new file mode 100644 index 0000000000..b1ecd766ff --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/model/SymbolicMethodData.kt @@ -0,0 +1,26 @@ +package org.utbot.taint.model + +import org.utbot.engine.SymbolicValue +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.taint.TaintUtil.chooseRelatedValue + +/** + * Contains symbolic values for [methodId]. + */ +data class SymbolicMethodData( + val methodId: ExecutableId, + val base: SymbolicValue?, + val args: List, + val result: SymbolicValue? +) { + /** + * Returns symbolic value (base, argN or result) corresponding to the [entity]. + */ + fun choose(entity: TaintEntity): SymbolicValue? = + entity.chooseRelatedValue(base, args, result) + + companion object { + fun constructInvalid(methodId: ExecutableId): SymbolicMethodData = + SymbolicMethodData(methodId, base = null, args = listOf(), result = null) + } +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/model/TaintCondition.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/model/TaintCondition.kt new file mode 100644 index 0000000000..bce7ce2e09 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/model/TaintCondition.kt @@ -0,0 +1,130 @@ +package org.utbot.taint.model + +import kotlinx.serialization.Serializable +import org.utbot.engine.* +import org.utbot.engine.pc.* +import soot.Scene + +/** + * Condition that imposed on the taint rule. It must be met to trigger the rule. + */ +@Serializable +sealed interface TaintCondition { + fun toBoolExpr(traverser: Traverser, methodData: SymbolicMethodData): UtBoolExpression +} + +/** + * Returns always true. + */ +@Serializable +object ConditionTrue : TaintCondition { + override fun toBoolExpr(traverser: Traverser, methodData: SymbolicMethodData): UtBoolExpression = + UtTrue +} + +/** + * The [entity] must be equal to [argumentValue]. + */ +@Serializable +class ConditionEqualValue( + private val entity: TaintEntity, + private val argumentValue: ArgumentValue +) : TaintCondition { + override fun toBoolExpr(traverser: Traverser, methodData: SymbolicMethodData): UtBoolExpression { + val symbolicValue = methodData.choose(entity) ?: return UtFalse + // TODO: support java.lang.Boolean, java.lang.Integer, etc. + return when (argumentValue) { + ArgumentValueNull -> { + val referenceValue = symbolicValue as? ReferenceValue + ?: return UtFalse + addrEq(referenceValue.addr, nullObjectAddr) + } + is ArgumentValueBoolean -> { + val primitiveValue = symbolicValue as? PrimitiveValue + ?: return UtFalse + mkEq(primitiveValue, mkBool(argumentValue.value).toBoolValue()) + } + is ArgumentValueLong -> { + val primitiveValue = symbolicValue as? PrimitiveValue + ?: return UtFalse + // conversions like int -> long will be made automatically by utbot + mkEq(primitiveValue, mkLong(argumentValue.value).toLongValue()) + } + is ArgumentValueDouble -> { + val primitiveValue = symbolicValue as? PrimitiveValue + ?: return UtFalse + // conversion float -> double will be made automatically by utbot + mkEq(primitiveValue, mkDouble(argumentValue.value).toDoubleValue()) + } + is ArgumentValueString -> { + val objectValue = symbolicValue as? ObjectValue + ?: return UtFalse + // TODO: compare not only length + val symbolicLength = traverser.getIntFieldValue(objectValue, STRING_LENGTH) + mkEq(symbolicLength, mkInt(argumentValue.value.length)) + } + } + } +} + +/** + * The [entity] must be [argumentType] at the runtime. + */ +@Serializable +class ConditionIsType( + private val entity: TaintEntity, + private val argumentType: ArgumentType +) : TaintCondition { + override fun toBoolExpr(traverser: Traverser, methodData: SymbolicMethodData): UtBoolExpression { + val symbolicValue = methodData.choose(entity) ?: return UtFalse + return when (argumentType) { + ArgumentTypeAny -> UtTrue + is ArgumentTypeString -> { + when (symbolicValue) { + is PrimitiveValue -> { + // If the method receives and the user calls it with the argument, + // utbot will automatically create a symbolic value with the type, + // so there is no need to handle conversions like int -> long here. + val argumentType = Scene.v().getTypeUnsafe(argumentType.typeFqn) + mkBool(symbolicValue.type == argumentType) + } + is ReferenceValue -> { + val argumentRefType = Scene.v().getRefTypeUnsafe(argumentType.typeFqn) + ?: return UtFalse + val typeStorage = traverser.typeResolver.constructTypeStorage(argumentRefType, useConcreteType = false) + // not `.isConstraintOrNull()` because null objects are not allowed (like java `instanceof`) + traverser.typeRegistry.typeConstraint(symbolicValue.addr, typeStorage).isConstraint() + } + } + } + is ArgumentTypeRegex -> UtTrue // TODO: support regex type constraints + } + } +} + +/** + * Negates [inner]. + */ +@Serializable +class ConditionNot(private val inner: TaintCondition) : TaintCondition { + override fun toBoolExpr(traverser: Traverser, methodData: SymbolicMethodData): UtBoolExpression = + mkNot(inner.toBoolExpr(traverser, methodData)) +} + +/** + * Combines [inners] with `or` operator. + */ +@Serializable +class ConditionOr(private val inners: List) : TaintCondition { + override fun toBoolExpr(traverser: Traverser, methodData: SymbolicMethodData): UtBoolExpression = + mkOr(inners.map { it.toBoolExpr(traverser, methodData) }) +} + +/** + * Combines [inners] with `and` operator. + */ +@Serializable +class ConditionAnd(private val inners: List) : TaintCondition { + override fun toBoolExpr(traverser: Traverser, methodData: SymbolicMethodData): UtBoolExpression = + mkAnd(inners.map { it.toBoolExpr(traverser, methodData) }) +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/model/TaintConfiguration.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/model/TaintConfiguration.kt new file mode 100644 index 0000000000..5ed0e8478a --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/model/TaintConfiguration.kt @@ -0,0 +1,155 @@ +package org.utbot.taint.model + +import kotlinx.serialization.Serializable +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId + +/** + * Storage for all taint rules. + */ +@Serializable +data class TaintConfiguration( + private val sources: List = listOf(), + private val passes: List = listOf(), + private val cleaners: List = listOf(), + private val sinks: List = listOf(), +) { + + fun getSourcesBy(executableId: ExecutableId): List = + sources.filter { + equalParameters(executableId, it.signature) && equalMethodNames(executableId, it.methodFqn) + } + + fun getPassesBy(executableId: ExecutableId): List = + passes.filter { + equalParameters(executableId, it.signature) && equalMethodNames(executableId, it.methodFqn) + } + + fun getCleanersBy(executableId: ExecutableId): List = + cleaners.filter { + equalParameters(executableId, it.signature) && equalMethodNames(executableId, it.methodFqn) + } + + fun getSinksBy(executableId: ExecutableId): List = + sinks.filter { + equalParameters(executableId, it.signature) && equalMethodNames(executableId, it.methodFqn) + } + + operator fun plus(other: TaintConfiguration): TaintConfiguration = + TaintConfiguration( + sources = sources + other.sources, + passes = passes + other.passes, + cleaners = cleaners + other.cleaners, + sinks = sinks + other.sinks + ) + + // internal + + private fun equalParameters(executableId: ExecutableId, signature: TaintSignature): Boolean = + when (signature) { + is TaintSignatureAny -> true + is TaintSignatureList -> { + val equalSizes = signature.argumentTypes.size == executableId.parameters.size + val equalElementwise = (signature.argumentTypes zip executableId.parameters).all { (type, parameter) -> + equalTypes(parameter, type) + } + equalSizes && equalElementwise + } + } + + private fun equalTypes(classId: ClassId, argumentType: ArgumentType): Boolean = + when (argumentType) { + ArgumentTypeAny -> true + is ArgumentTypeString -> argumentType.typeFqn == classId.name + // TODO: constructing Regex many times can lead to a decrease in performance + is ArgumentTypeRegex -> classId.name.matches(Regex(argumentType.typeFqnRegex)) + } + + private fun equalMethodNames(executableId: ExecutableId, methodFqn: MethodFqn): Boolean = + when (methodFqn) { + is MethodFqnRegex -> { + val executableFqn = executableId.getMethodFqn()?.run { + packageNames.plus(className).plus(methodName).joinToString(".") + } + // TODO: constructing Regex many times can lead to a decrease in performance + executableFqn?.matches(Regex(methodFqn.regex)) ?: false + } + is MethodFqnValue -> methodFqn == executableId.getMethodFqn() + } + + private fun ExecutableId.getMethodFqn(): MethodFqnValue? = + runCatching { + val packages = if (classId.packageName == "") + listOf() + else + classId.packageName.split('.') + MethodFqnValue(packages, classId.simpleName, name) + }.getOrNull() +} + +/** + * @param methodFqn method fully qualified name + * @param addTo objects to be marked + * @param marks marks that should be added to the objects from the [addTo] list + * @param signature list of argument types (at compile time) + * @param condition condition that must be met to trigger this rule + */ +@Serializable +data class TaintSource( + val methodFqn: MethodFqn, + val addTo: TaintEntities, + val marks: TaintMarks, + val signature: TaintSignature, + val condition: TaintCondition, +) + +/** + * @param methodFqn method fully qualified name + * @param getFrom the sources of marks + * @param addTo objects that will be marked if any element from [getFrom] has any mark + * @param marks actual marks that can be passed from one object to another + * @param signature list of argument types (at compile time) + * @param condition condition that must be met to trigger this rule + */ +@Serializable +data class TaintPass( + val methodFqn: MethodFqn, + val getFrom: TaintEntities, + val addTo: TaintEntities, + val marks: TaintMarks, + val signature: TaintSignature, + val condition: TaintCondition, +) + +/** + * @param methodFqn method fully qualified name + * @param removeFrom objects from which marks should be removed + * @param marks marks to be removed + * @param signature list of argument types (at compile time) + * @param condition condition that must be met to trigger this rule + */ +@Serializable +data class TaintCleaner( + val methodFqn: MethodFqn, + val removeFrom: TaintEntities, + val marks: TaintMarks, + val signature: TaintSignature, + val condition: TaintCondition, +) + +/** + * @param methodFqn method fully qualified name + * @param check objects that will be checked for marks + * @param marks when one of the [marks] is found in one of the objects from the [check], + * the analysis will report the problem found + * @param signature list of argument types (at compile time) + * @param condition condition that must be met to trigger this rule + */ +@Serializable +data class TaintSink( + val methodFqn: MethodFqn, + val check: TaintEntities, + val marks: TaintMarks, + val signature: TaintSignature, + val condition: TaintCondition, +) \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/Constants.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/Constants.kt new file mode 100644 index 0000000000..b177c97ca1 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/Constants.kt @@ -0,0 +1,30 @@ +package org.utbot.taint.parser.yaml + +/** + * String constants in YAML configuration file. + */ +object Constants { + const val KEY_SOURCES = "sources" + const val KEY_PASSES = "passes" + const val KEY_CLEANERS = "cleaners" + const val KEY_SINKS = "sinks" + + const val KEY_ADD_TO = "add-to" + const val KEY_GET_FROM = "get-from" + const val KEY_REMOVE_FROM = "remove-from" + const val KEY_CHECK = "check" + const val KEY_MARKS = "marks" + + const val KEY_SIGNATURE = "signature" + const val KEY_CONDITIONS = "conditions" + + const val KEY_CONDITION_NOT = "not" + + const val KEY_THIS = "this" + const val KEY_RETURN = "return" + const val KEY_ARG = "arg" + + const val ARGUMENT_TYPE_PREFIX = "<" + const val ARGUMENT_TYPE_SUFFIX = ">" + const val ARGUMENT_TYPE_UNDERSCORE = "_" +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/MethodArgumentParser.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/MethodArgumentParser.kt new file mode 100644 index 0000000000..90ff757ad2 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/MethodArgumentParser.kt @@ -0,0 +1,76 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.YamlNode +import com.charleskorn.kaml.YamlNull +import com.charleskorn.kaml.YamlScalar +import com.charleskorn.kaml.YamlScalarFormatException +import org.utbot.taint.parser.yaml.Constants.ARGUMENT_TYPE_PREFIX +import org.utbot.taint.parser.yaml.Constants.ARGUMENT_TYPE_SUFFIX +import org.utbot.taint.parser.yaml.Constants.ARGUMENT_TYPE_UNDERSCORE +import kotlin.contracts.ExperimentalContracts + +@OptIn(ExperimentalContracts::class) +object MethodArgumentParser { + + /** + * This method is expected to be called only if the [isArgumentType] method returned `true`. + */ + fun parseArgumentType(node: YamlNode): YamlArgumentType { + validate(node is YamlScalar, "The argument type node should be a scalar", node) + return when (val typeDescription = node.content) { + ARGUMENT_TYPE_UNDERSCORE -> YamlArgumentTypeAny + else -> { + val typeFqn = typeDescription.removeSurrounding(ARGUMENT_TYPE_PREFIX, ARGUMENT_TYPE_SUFFIX) + YamlArgumentTypeString(typeFqn) + } + } + } + + /** + * This method is expected to be called only if the [isArgumentValue] method returned `true`. + */ + fun parseArgumentValue(node: YamlNode): YamlArgumentValue { + return when (node) { + is YamlNull -> YamlArgumentValueNull + is YamlScalar -> { + val conversions: List<(YamlScalar) -> YamlArgumentValue> = listOf( + { YamlArgumentValueBoolean(it.toBoolean()) }, + { YamlArgumentValueLong(it.toLong()) }, + { YamlArgumentValueDouble(it.toDouble()) }, + { YamlArgumentValueString(it.content) }, + ) + + for (conversion in conversions) { + try { + return conversion(node) + } catch (_: YamlScalarFormatException) { + continue + } + } + throw TaintParseError("All conversions failed for the argument value node", node) + } + + else -> { + throw TaintParseError("The argument value node should be a null or a scalar", node) + } + } + } + + /** + * Checks that the [node] can be parsed to [YamlArgumentType]. + */ + fun isArgumentType(node: YamlNode): Boolean { + val content = (node as? YamlScalar)?.content ?: return false + + val isUnderscore = content == ARGUMENT_TYPE_UNDERSCORE + val isInBrackets = content.startsWith(ARGUMENT_TYPE_PREFIX) && content.endsWith(ARGUMENT_TYPE_SUFFIX) + + return isUnderscore || isInBrackets + } + + /** + * Checks that the [node] can be parsed to [YamlArgumentValue]. + */ + fun isArgumentValue(node: YamlNode): Boolean = + (node is YamlNull) || (node is YamlScalar) && !isArgumentType(node) +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/Model.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/Model.kt new file mode 100644 index 0000000000..95b5953668 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/Model.kt @@ -0,0 +1,90 @@ +package org.utbot.taint.parser.yaml + +/** + * See "docs/TaintAnalysis.md" for configuration format. + */ +data class YamlTaintConfiguration( + val sources: List, + val passes: List, + val cleaners: List, + val sinks: List +) + +data class YamlTaintSource( + val methodFqn: YamlMethodFqn, + val addTo: YamlTaintEntities, + val marks: YamlTaintMarks, + val signature: YamlTaintSignature = YamlTaintSignatureAny, + val conditions: YamlTaintConditions = YamlNoTaintConditions, +) + +data class YamlTaintPass( + val methodFqn: YamlMethodFqn, + val getFrom: YamlTaintEntities, + val addTo: YamlTaintEntities, + val marks: YamlTaintMarks, + val signature: YamlTaintSignature = YamlTaintSignatureAny, + val conditions: YamlTaintConditions = YamlNoTaintConditions, +) + +data class YamlTaintCleaner( + val methodFqn: YamlMethodFqn, + val removeFrom: YamlTaintEntities, + val marks: YamlTaintMarks, + val signature: YamlTaintSignature = YamlTaintSignatureAny, + val conditions: YamlTaintConditions = YamlNoTaintConditions, +) + +data class YamlTaintSink( + val methodFqn: YamlMethodFqn, + val check: YamlTaintEntities, + val marks: YamlTaintMarks, + val signature: YamlTaintSignature = YamlTaintSignatureAny, + val conditions: YamlTaintConditions = YamlNoTaintConditions, +) + +data class YamlMethodFqn( + val packageNames: List, + val className: String, + val methodName: String +) + +sealed interface YamlTaintEntities +data class YamlTaintEntitiesSet(val entities: Set) : YamlTaintEntities + +sealed interface YamlTaintMarks +object YamlTaintMarksAll : YamlTaintMarks +data class YamlTaintMarksSet(val marks: Set) : YamlTaintMarks + +sealed interface YamlTaintSignature +object YamlTaintSignatureAny : YamlTaintSignature +data class YamlTaintSignatureList(val argumentTypes: List) : YamlTaintSignature + +sealed interface YamlTaintConditions +object YamlNoTaintConditions : YamlTaintConditions +data class YamlTaintConditionsMap(val entityToCondition: Map) : YamlTaintConditions + +sealed interface YamlTaintEntity +object YamlTaintEntityThis : YamlTaintEntity +data class YamlTaintEntityArgument(/** one-based */ val index: UInt) : YamlTaintEntity + +object YamlTaintEntityReturn : YamlTaintEntity + +data class YamlTaintMark(val name: String) + +sealed interface YamlTaintCondition +data class YamlTaintConditionEqualValue(val argumentValue: YamlArgumentValue) : YamlTaintCondition +data class YamlTaintConditionIsType(val argumentType: YamlArgumentType) : YamlTaintCondition +data class YamlTaintConditionNot(val inner: YamlTaintCondition) : YamlTaintCondition +data class YamlTaintConditionOr(val inners: List) : YamlTaintCondition + +sealed interface YamlArgumentValue +object YamlArgumentValueNull : YamlArgumentValue +data class YamlArgumentValueBoolean(val value: Boolean) : YamlArgumentValue +data class YamlArgumentValueLong(val value: Long) : YamlArgumentValue +data class YamlArgumentValueDouble(val value: Double) : YamlArgumentValue +data class YamlArgumentValueString(val value: String) : YamlArgumentValue + +sealed interface YamlArgumentType +object YamlArgumentTypeAny : YamlArgumentType +data class YamlArgumentTypeString(val typeFqn: String) : YamlArgumentType diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintConditionParser.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintConditionParser.kt new file mode 100644 index 0000000000..9e88e3cfe6 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintConditionParser.kt @@ -0,0 +1,92 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.* +import org.utbot.taint.parser.yaml.MethodArgumentParser.isArgumentType +import org.utbot.taint.parser.yaml.MethodArgumentParser.isArgumentValue +import org.utbot.taint.parser.yaml.MethodArgumentParser.parseArgumentType +import org.utbot.taint.parser.yaml.MethodArgumentParser.parseArgumentValue +import org.utbot.taint.parser.yaml.TaintEntityParser.taintEntityByName +import kotlin.contracts.ExperimentalContracts + +@OptIn(ExperimentalContracts::class) +object TaintConditionParser { + + /** + * Expects a [YamlMap] with (or without) a key [Constants.KEY_CONDITIONS]. + * + * __Input example:__ + * + * ```yaml + * conditions: + * this: + * arg1: [ "in", "out" ] + * arg2: 227 + * return: [ "", { not: } ] + * ``` + */ + fun parseConditionsKey(ruleMap: YamlMap): YamlTaintConditions = + ruleMap.get(Constants.KEY_CONDITIONS)?.let(TaintConditionParser::parseConditions) + ?: YamlNoTaintConditions + + /** + * Expects a [YamlMap] with taint entities as keys. + * + * __Input example:__ + * + * ```yaml + * this: + * arg1: [ "in", "out" ] + * arg2: 227 + * return: [ "", { not: } ] + * ``` + */ + fun parseConditions(node: YamlNode): YamlTaintConditions { + validate(node is YamlMap, "The conditions node should be a map", node) + return if (node.entries.isEmpty()) { + YamlNoTaintConditions + } else { + val entityToCondition = node.entries.map { (taintEntityNameScalar, conditionNode) -> + taintEntityByName(taintEntityNameScalar.content) to parseCondition(conditionNode) + }.toMap() + YamlTaintConditionsMap(entityToCondition) + } + } + + /** + * Expects a [YamlNode] that describes one condition. + */ + fun parseCondition(node: YamlNode): YamlTaintCondition = + when (node) { + // example: `null` + is YamlNull -> { + YamlTaintConditionEqualValue(parseArgumentValue(node)) + } + + // examples: `227`, `"some string"`, `` + is YamlScalar -> { + when { + isArgumentType(node) -> YamlTaintConditionIsType(parseArgumentType(node)) + isArgumentValue(node) -> YamlTaintConditionEqualValue(parseArgumentValue(node)) + else -> throw TaintParseError("The condition scalar should be a type or a value", node) + } + } + + // examples: `[ true, 1, "yes" ]`, `[ "", { not: } ]` + is YamlList -> { + val innerConditions = node.items.map(TaintConditionParser::parseCondition) + YamlTaintConditionOr(innerConditions) + } + + // examples: `{ not: null }`, `{ not: [1, 2, 3] }` + is YamlMap -> { + validateYamlMapKeys(node, setOf(Constants.KEY_CONDITION_NOT)) + val innerNode = node.get(Constants.KEY_CONDITION_NOT)!! + val innerCondition = parseCondition(innerNode) + YamlTaintConditionNot(innerCondition) + } + + else -> { + throw TaintParseError("The condition node has an unknown type", node) + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintConfigurationParser.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintConfigurationParser.kt new file mode 100644 index 0000000000..bddd1ddfe7 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintConfigurationParser.kt @@ -0,0 +1,42 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.YamlMap +import com.charleskorn.kaml.YamlNode +import org.utbot.taint.parser.yaml.Constants.KEY_CLEANERS +import org.utbot.taint.parser.yaml.Constants.KEY_PASSES +import org.utbot.taint.parser.yaml.Constants.KEY_SINKS +import org.utbot.taint.parser.yaml.Constants.KEY_SOURCES +import kotlin.contracts.ExperimentalContracts + +@OptIn(ExperimentalContracts::class) +object TaintConfigurationParser { + + /** + * Expects a [YamlMap] with keys [KEY_SOURCES], [KEY_PASSES], [KEY_CLEANERS] and [KEY_SINKS]. + * + * __Input example:__ + * + * ```yaml + * sources: [ ... ] + * passes: [ ... ] + * cleaners: [ ... ] + * sinks: [ ... ] + * ``` + */ + fun parseConfiguration(node: YamlNode): YamlTaintConfiguration { + validate(node is YamlMap, "The root node should be a map", node) + validateYamlMapKeys(node, setOf(KEY_SOURCES, KEY_PASSES, KEY_CLEANERS, KEY_SINKS)) + + val sourcesNode = node.get(KEY_SOURCES) + val passesNode = node.get(KEY_PASSES) + val cleanersNode = node.get(KEY_CLEANERS) + val sinksNode = node.get(KEY_SINKS) + + val sources = sourcesNode?.let(TaintRuleParser::parseSources) ?: listOf() + val passes = passesNode?.let(TaintRuleParser::parsePasses) ?: listOf() + val cleaners = cleanersNode?.let(TaintRuleParser::parseCleaners) ?: listOf() + val sinks = sinksNode?.let(TaintRuleParser::parseSinks) ?: listOf() + + return YamlTaintConfiguration(sources, passes, cleaners, sinks) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintEntityParser.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintEntityParser.kt new file mode 100644 index 0000000000..a0d8866325 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintEntityParser.kt @@ -0,0 +1,52 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.YamlList +import com.charleskorn.kaml.YamlNode +import com.charleskorn.kaml.YamlScalar +import kotlin.contracts.ExperimentalContracts + +@OptIn(ExperimentalContracts::class) +object TaintEntityParser { + + /** + * Expects a [YamlScalar] (single taint entity) or a [YamlList] (several taint entities). + * + * __Input example:__ + * + * `[ this, arg1, arg7, return ]` + */ + fun parseTaintEntities(node: YamlNode): YamlTaintEntities = + when (node) { + is YamlScalar -> { + YamlTaintEntitiesSet(setOf(taintEntityByName(node.content))) + } + + is YamlList -> { + validate(node.items.isNotEmpty(), "The taint entities set should contain at least one value", node) + val entities = node.items.map { innerNode -> + validate(innerNode is YamlScalar, "The taint entity name should be a scalar", node) + taintEntityByName(innerNode.content) + } + YamlTaintEntitiesSet(entities.toSet()) + } + + else -> { + throw TaintParseError("The taint-entities node should be a scalar or a list", node) + } + } + + /** + * Constructs [YamlTaintEntity] by the given [name] &ndash "this", "return" or "argN". + */ + fun taintEntityByName(name: String): YamlTaintEntity = + when (name) { + Constants.KEY_THIS -> YamlTaintEntityThis + Constants.KEY_RETURN -> YamlTaintEntityReturn + else -> { + val index = name.removePrefix(Constants.KEY_ARG).toUIntOrNull() + ?: throw TaintParseError("Method argument should be like `arg` + index, but is `$name`") + validate(index >= 1u, "Method arguments indexes are numbered from one, but index = `$index`") + YamlTaintEntityArgument(index) + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintMarkParser.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintMarkParser.kt new file mode 100644 index 0000000000..ec5449826e --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintMarkParser.kt @@ -0,0 +1,40 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.YamlList +import com.charleskorn.kaml.YamlNode +import com.charleskorn.kaml.YamlScalar +import kotlin.contracts.ExperimentalContracts + +@OptIn(ExperimentalContracts::class) +object TaintMarkParser { + + /** + * Expects a [YamlScalar] (single mark) or a [YamlList] (several marks). + * + * __Input example:__ + * + * `[ sensitive-data, xss ]` + */ + fun parseTaintMarks(node: YamlNode): YamlTaintMarks = + when (node) { + is YamlScalar -> { + YamlTaintMarksSet(setOf(YamlTaintMark(node.content))) + } + + is YamlList -> { + if (node.items.isEmpty()) { + YamlTaintMarksAll + } else { + val marks = node.items.map { innerNode -> + validate(innerNode is YamlScalar, "The mark name should be a scalar", innerNode) + YamlTaintMark(innerNode.content) + } + YamlTaintMarksSet(marks.toSet()) + } + } + + else -> { + throw TaintParseError("The marks node should be a scalar or a list", node) + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintRuleParser.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintRuleParser.kt new file mode 100644 index 0000000000..6909c32eaf --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintRuleParser.kt @@ -0,0 +1,242 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.YamlList +import com.charleskorn.kaml.YamlMap +import com.charleskorn.kaml.YamlNode +import org.utbot.taint.parser.yaml.TaintConditionParser.parseConditionsKey +import org.utbot.taint.parser.yaml.TaintEntityParser.parseTaintEntities +import org.utbot.taint.parser.yaml.TaintMarkParser.parseTaintMarks +import org.utbot.taint.parser.yaml.TaintSignatureParser.parseSignatureKey +import kotlin.contracts.ExperimentalContracts + +@OptIn(ExperimentalContracts::class) +object TaintRuleParser { + + /** + * Recursively parses source rules. + * @see parseRules + */ + fun parseSources(node: YamlNode): List = + parseRules(node, TaintRuleParser::isSourceRule, TaintRuleParser::parseSourceRule) + + /** + * Recursively parses pass-through rules. + * @see parseRules + */ + fun parsePasses(node: YamlNode): List = + parseRules(node, TaintRuleParser::isPassRule, TaintRuleParser::parsePassRule) + + /** + * Recursively parses cleaner rules. + * @see parseRules + */ + fun parseCleaners(node: YamlNode): List = + parseRules(node, TaintRuleParser::isCleanerRule, TaintRuleParser::parseCleanerRule) + + /** + * Recursively parses sink rules. + * @see parseRules + */ + fun parseSinks(node: YamlNode): List = + parseRules(node, TaintRuleParser::isSinkRule, TaintRuleParser::parseSinkRule) + + /** + * Recursively parses rules (sources/passes/cleaners/sinks). + * + * Expects a [YamlMap] (single rule) or a YamlList (several rules). + * + * __Input example:__ + * + * ```yaml + * - java.lang.String: + * - isEmpty: { ... } + * - concat: { ... } + * ``` + */ + private fun parseRules( + node: YamlNode, + isRule: YamlNode.() -> Boolean, + parseRule: (YamlNode, List) -> Rule, + currentPath: List = listOf() + ): List { + if (node.isRule()) { + val rule = parseRule(node, currentPath) + return listOf(rule) + } + + validate(node is YamlList, "The rules should be stored as a list", node) + return node.items.flatMap { innerNode -> + validate(innerNode is YamlMap, "The rule node should be a map with the key `method name part`", innerNode) + validate(innerNode.entries.size == 1, "The rule map should contain only one key", innerNode) + val (nextNamePart, nextNode) = innerNode.entries.toList().first() + parseRules(nextNode, isRule, parseRule, currentPath = currentPath + nextNamePart.content) + } + } + + + /** + * Checks that the [node] can be parsed to [YamlTaintSource]. + */ + fun isSourceRule(node: YamlNode): Boolean = + node.containsKey(Constants.KEY_ADD_TO) && node.containsKey(Constants.KEY_MARKS) + + /** + * Checks that the [node] can be parsed to [YamlTaintPass]. + */ + fun isPassRule(node: YamlNode): Boolean = + node.containsKey(Constants.KEY_GET_FROM) && node.containsKey(Constants.KEY_ADD_TO) && node.containsKey(Constants.KEY_MARKS) + + /** + * Checks that the [node] can be parsed to [YamlTaintCleaner]. + */ + fun isCleanerRule(node: YamlNode): Boolean = + node.containsKey(Constants.KEY_REMOVE_FROM) && node.containsKey(Constants.KEY_MARKS) + + /** + * Checks that the [node] can be parsed to [YamlTaintSink]. + */ + fun isSinkRule(node: YamlNode): Boolean = + node.containsKey(Constants.KEY_CHECK) && node.containsKey(Constants.KEY_MARKS) + + /** + * Checks that [this] is [YamlMap] and contains [key] as a key. + */ + private fun YamlNode.containsKey(key: String): Boolean = + (this as? YamlMap)?.get(key) != null + + + /** + * This method is expected to be called only if the [isSourceRule] method returned `true`. + * + * __Input example:__ + * + * ```yaml + * signature: [ ... ] + * conditions: [ ... ] + * add-to: ... + * marks: ... + * ``` + */ + fun parseSourceRule(node: YamlNode, methodNameParts: List): YamlTaintSource { + validate(node is YamlMap, "The source-rule node should be a map", node) + validateYamlMapKeys( + node, + setOf(Constants.KEY_SIGNATURE, Constants.KEY_CONDITIONS, Constants.KEY_ADD_TO, Constants.KEY_MARKS) + ) + + val methodFqn = getMethodFqnFromParts(methodNameParts) + val signature = parseSignatureKey(node) + val conditions = parseConditionsKey(node) + val addTo = parseTaintEntities(node[Constants.KEY_ADD_TO]!!) + val marks = parseTaintMarks(node[Constants.KEY_MARKS]!!) + + return YamlTaintSource(methodFqn, addTo, marks, signature, conditions) + } + + /** + * This method is expected to be called only if the [isPassRule] method returned `true`. + * + * __Input example:__ + * + * ```yaml + * signature: [ ... ] + * conditions: [ ... ] + * get-from: [ ... ] + * add-to: ... + * marks: ... + * ``` + */ + fun parsePassRule(node: YamlNode, methodNameParts: List): YamlTaintPass { + validate(node is YamlMap, "The pass-rule node should be a map", node) + validateYamlMapKeys( + node, + setOf( + Constants.KEY_SIGNATURE, + Constants.KEY_CONDITIONS, + Constants.KEY_GET_FROM, + Constants.KEY_ADD_TO, + Constants.KEY_MARKS + ) + ) + + val methodFqn = getMethodFqnFromParts(methodNameParts) + val signature = parseSignatureKey(node) + val conditions = parseConditionsKey(node) + val getFrom = parseTaintEntities(node[Constants.KEY_GET_FROM]!!) + val addTo = parseTaintEntities(node[Constants.KEY_ADD_TO]!!) + val marks = parseTaintMarks(node[Constants.KEY_MARKS]!!) + + return YamlTaintPass(methodFqn, getFrom, addTo, marks, signature, conditions) + } + + /** + * This method is expected to be called only if the [isCleanerRule] method returned `true`. + * + * __Input example:__ + * + * ```yaml + * signature: [ ... ] + * conditions: [ ... ] + * remove-from: ... + * marks: ... + * ``` + */ + fun parseCleanerRule(node: YamlNode, methodNameParts: List): YamlTaintCleaner { + validate(node is YamlMap, "The cleaner-rule node should be a map", node) + validateYamlMapKeys( + node, + setOf(Constants.KEY_SIGNATURE, Constants.KEY_CONDITIONS, Constants.KEY_REMOVE_FROM, Constants.KEY_MARKS) + ) + + val methodFqn = getMethodFqnFromParts(methodNameParts) + val signature = parseSignatureKey(node) + val conditions = parseConditionsKey(node) + val removeFrom = parseTaintEntities(node[Constants.KEY_REMOVE_FROM]!!) + val marks = parseTaintMarks(node[Constants.KEY_MARKS]!!) + + return YamlTaintCleaner(methodFqn, removeFrom, marks, signature, conditions) + } + + /** + * This method is expected to be called only if the [isSinkRule] method returned `true`. + * + * __Input example:__ + * + * ```yaml + * signature: [ ... ] + * conditions: [ ... ] + * check: ... + * marks: ... + * ``` + */ + fun parseSinkRule(node: YamlNode, methodNameParts: List): YamlTaintSink { + validate(node is YamlMap, "The sink-rule node should be a map", node) + validateYamlMapKeys( + node, + setOf(Constants.KEY_SIGNATURE, Constants.KEY_CONDITIONS, Constants.KEY_CHECK, Constants.KEY_MARKS) + ) + + val methodFqn = getMethodFqnFromParts(methodNameParts) + val signature = parseSignatureKey(node) + val conditions = parseConditionsKey(node) + val check = parseTaintEntities(node[Constants.KEY_CHECK]!!) + val marks = parseTaintMarks(node[Constants.KEY_MARKS]!!) + + return YamlTaintSink(methodFqn, check, marks, signature, conditions) + } + + /** + * Constructs [YamlMethodFqn] from the given [methodNameParts]. + * + * __Input example:__ + * + * `["org.example", "server", "Controller", "start"]` + */ + private fun getMethodFqnFromParts(methodNameParts: List): YamlMethodFqn { + val parts = methodNameParts.flatMap { it.split('.') } + validate(parts.size >= 2, "Method fqn should contain at least the class name and the method name") + val packageNames = parts.dropLast(2) + val (className, methodName) = parts.takeLast(2) + return YamlMethodFqn(packageNames, className, methodName) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintSignatureParser.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintSignatureParser.kt new file mode 100644 index 0000000000..8e5b082f4b --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintSignatureParser.kt @@ -0,0 +1,37 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.YamlList +import com.charleskorn.kaml.YamlMap +import com.charleskorn.kaml.YamlNode +import org.utbot.taint.parser.yaml.MethodArgumentParser.isArgumentType +import org.utbot.taint.parser.yaml.MethodArgumentParser.parseArgumentType +import kotlin.contracts.ExperimentalContracts + +@OptIn(ExperimentalContracts::class) +object TaintSignatureParser { + + /** + * Expects a [YamlMap] with (or without) a key [Constants.KEY_SIGNATURE]. + * + * __Input example:__ + * + * `signature: [ _, _, ]` + */ + fun parseSignatureKey(ruleMap: YamlMap): YamlTaintSignature = + ruleMap.get(Constants.KEY_SIGNATURE)?.let(TaintSignatureParser::parseSignature) + ?: YamlTaintSignatureAny + + /** + * Expects a [YamlList] with argument types as keys. + * + * __Input example:__ + * + * `[ _, _, ]` + */ + fun parseSignature(node: YamlNode): YamlTaintSignature { + validate(node is YamlList, "The signature node should be a list", node) + validate(node.items.all(::isArgumentType), "All items should be argument types", node) + val argumentTypes = node.items.map(::parseArgumentType) + return YamlTaintSignatureList(argumentTypes) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintYamlParser.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintYamlParser.kt new file mode 100644 index 0000000000..ed8ef9faba --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/TaintYamlParser.kt @@ -0,0 +1,20 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.Yaml +import com.charleskorn.kaml.YamlException + +/** + * YAML configuration file parser. + */ +object TaintYamlParser { + /** + * Parses the YAML configuration file to our data classes. + * + * @throws YamlException + * @throws TaintParseError + */ + fun parse(yamlInput: String): YamlTaintConfiguration { + val rootNode = Yaml.default.parseToYamlNode(yamlInput) + return TaintConfigurationParser.parseConfiguration(rootNode) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/Validation.kt b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/Validation.kt new file mode 100644 index 0000000000..4595dd9b31 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/taint/parser/yaml/Validation.kt @@ -0,0 +1,34 @@ +package org.utbot.taint.parser.yaml + +import com.charleskorn.kaml.YamlMap +import com.charleskorn.kaml.YamlNode +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +class TaintParseError( + message: String, + node: YamlNode? = null +) : RuntimeException(message + (if (node != null) "\nYaml node: ${node.contentToString()}" else "")) + +@ExperimentalContracts +fun validate(condition: Boolean, reason: String, node: YamlNode? = null) { + contract { + returns() implies condition + } + if (!condition) { + throw TaintParseError(reason, node) + } +} + +/** + * Validates that the [node] contains keys only from [allowedKeys]. + * + * Does not require the presence of all [allowedKeys] in the [node]. + */ +fun validateYamlMapKeys(node: YamlMap, allowedKeys: Set) { + val actualKeys = node.entries.keys.map { it.content }.toSet() + val unknownKeys = actualKeys - allowedKeys + if (unknownKeys.isNotEmpty()) { + throw TaintParseError("Unknown keys was encountered: $unknownKeys", node) + } +} diff --git a/utbot-framework/src/main/kotlin/org/z3/Z3Extension.kt b/utbot-framework/src/main/kotlin/org/z3/Z3Extension.kt index 39fb296e85..ad63ebc5d5 100644 --- a/utbot-framework/src/main/kotlin/org/z3/Z3Extension.kt +++ b/utbot-framework/src/main/kotlin/org/z3/Z3Extension.kt @@ -5,7 +5,7 @@ package com.microsoft.z3 import java.lang.Integer.parseUnsignedInt import java.lang.Long.parseUnsignedLong -fun Model.eval(expr: Expr): Expr = this.eval(expr, true) +fun Model.eval(expr: Expr<*>): Expr<*> = this.eval(expr, true) fun BitVecNum.toIntNum(unsigned: Boolean = false): Any = when (sortSize) { Byte.SIZE_BITS -> parseUnsignedLong(this.toBinaryString(), 2).toByte() @@ -44,7 +44,7 @@ fun FPNum.toFloatingPointNum(): Number = when (sort) { else -> error("Unknown type: $sort") } -fun Context.mkSeqNth(s: SeqExpr, index: Expr): Expr { +fun Context.mkSeqNth(s: SeqExpr<*>, index: Expr<*>): Expr<*> { this.checkContextMatch(s, index) return Expr.create(this, Native.mkSeqNth(nCtx(), s.nativeObject, index.nativeObject)) } \ No newline at end of file diff --git a/utbot-framework/src/main/resources/taint/config.yaml b/utbot-framework/src/main/resources/taint/config.yaml new file mode 100644 index 0000000000..bc708767de --- /dev/null +++ b/utbot-framework/src/main/resources/taint/config.yaml @@ -0,0 +1,124 @@ +sources: + - java.util.Scanner.next: + add-to: return + marks: user-input + - java.io.BufferedReader.readLine: + add-to: return + marks: user-input + - javax.servlet.http.HttpServletRequest.getParameter: + add-to: return + marks: user-input + - java.util.Properties.getProperty: + add-to: return + marks: user-input + - java.sql.ResultSet.getString: + add-to: return + marks: user-input + - javax.servlet.http.HttpServletRequest.getQueryString: + add-to: return + marks: user-input + +cleaners: + - java.lang.String.isEmpty: + remove-from: this + marks: [ ] + conditions: + return: true + +passes: + - java.lang.String.getBytes: + get-from: this + add-to: return + marks: [ ] + conditions: + this: { not: "" } + - java.lang.String.split: + get-from: this + add-to: return + marks: [ ] + conditions: + this: { not: "" } + - java.lang.String.concat: + get-from: this + add-to: return + marks: [ ] + conditions: + this: { not: "" } + - java.lang.String.concat: + get-from: arg1 + add-to: return + marks: [ ] + conditions: + arg1: { not: "" } + - java.lang.StringBuilder.append: + get-from: arg1 + add-to: this + marks: [ ] + conditions: + arg1: { not: "" } + - java.lang.StringBuilder.toString: + get-from: this + add-to: return + marks: [ ] + + - java.sql.Connection.prepareStatement: + get-from: arg1 + add-to: [ this, return ] + marks: [ ] + - java.sql.PreparedStatement.setString: + get-from: arg2 + add-to: this + marks: [ ] + + - java.sql.Statement.addBatch: + get-from: arg1 + add-to: this + marks: [ ] + + - java.io.ByteArrayOutputStream.writeData: + get-from: arg1 + add-to: this + marks: [ ] + - java.io.ByteArrayOutputStream.toByteArray: + get-from: this + add-to: return + marks: [ ] + - java.io.ByteArrayInputStream.: + get-from: arg1 + add-to: [ this, return ] + marks: [ ] + - java.io.ObjectInputStream.: + get-from: arg1 + add-to: [ this, return ] + marks: [ ] + - java.io.ObjectInputStream.readObject: + get-from: this + add-to: return + marks: [ ] + +sinks: + - java.sql.Statement.execute: + check: arg1 + marks: user-input + - java.sql.Statement.executeUpdate: + check: arg1 + marks: user-input + - java.sql.Statement.executeBatch: + check: this + marks: user-input + - java.sql.Statement.executeQuery: + check: arg1 + marks: user-input + + - java.sql.PreparedStatement.execute: + check: this + marks: user-input + - java.sql.PreparedStatement.executeUpdate: + check: this + marks: user-input + - java.sql.PreparedStatement.executeBatch: + check: this + marks: user-input + - java.sql.PreparedStatement.executeQuery: + check: this + marks: user-input diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/ArrayOfPrimitiveArrays.java b/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/ArrayOfPrimitiveArrays.java deleted file mode 100644 index 7339f09ad3..0000000000 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/ArrayOfPrimitiveArrays.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.utbot.examples.assemble.arrays; - -/** - * A class with a two-dimensional array field. - */ -public class ArrayOfPrimitiveArrays { - public int[][] array; -} diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/AssignedArray.java b/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/AssignedArray.java deleted file mode 100644 index 76235ed977..0000000000 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/AssignedArray.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.utbot.examples.assemble.arrays; - -/** - * A class with an array with a default value. - */ -public class AssignedArray { - public int[] array = new int[10]; -} diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/MethodUnderTest.java b/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/MethodUnderTest.java deleted file mode 100644 index dad8f455cf..0000000000 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/MethodUnderTest.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.utbot.examples.assemble.arrays; - -/** - * A test class with fake method under test. - */ -public class MethodUnderTest { - - public void methodUnderTest() { - } -} diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/PrimitiveArray.java b/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/PrimitiveArray.java deleted file mode 100644 index 5cb668b128..0000000000 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/arrays/PrimitiveArray.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.utbot.examples.assemble.arrays; - -/** - * A class with an array with elements of primitive type. - */ -public class PrimitiveArray { - public int[] array; -} diff --git a/utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultPackagePrivateField.java b/utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultPackagePrivateField.java deleted file mode 100644 index e28bf48c35..0000000000 --- a/utbot-framework/src/test/java/org/utbot/examples/assemble/defaults/DefaultPackagePrivateField.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.utbot.examples.assemble.defaults; - -public class DefaultPackagePrivateField { - int z = 10; -} diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/KotlinWrappers.kt b/utbot-framework/src/test/java/org/utbot/examples/manual/KotlinWrappers.kt deleted file mode 100644 index 4e0e761fa0..0000000000 --- a/utbot-framework/src/test/java/org/utbot/examples/manual/KotlinWrappers.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.utbot.examples.manual - -import org.utbot.common.FileUtil -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtPrimitiveModel -import java.nio.file.Path - -object SootUtils { - @JvmStatic - fun runSoot(clazz: Class<*>) { - val buildDir = FileUtil.locateClassPath(clazz.kotlin) ?: FileUtil.isolateClassFiles(clazz.kotlin) - val buildDirPath = buildDir.toPath() - - if (buildDirPath != previousBuildDir) { - org.utbot.framework.plugin.api.runSoot(buildDirPath, null) - previousBuildDir = buildDirPath - } - } - - private var previousBuildDir: Path? = null -} - -fun fields( - classId: ClassId, - vararg fields: Pair -): MutableMap { - return fields - .associate { - val fieldId = FieldId(classId, it.first) - val fieldValue = when (val value = it.second) { - is UtModel -> value - else -> UtPrimitiveModel(value) - } - fieldId to fieldValue - } - .toMutableMap() -} \ No newline at end of file diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java b/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java deleted file mode 100644 index 89ffcb5157..0000000000 --- a/utbot-framework/src/test/java/org/utbot/examples/manual/UtBotJavaApiTest.java +++ /dev/null @@ -1,1375 +0,0 @@ -package org.utbot.examples.manual; - -import org.utbot.examples.assemble.DirectAccess; -import org.utbot.examples.assemble.PrimitiveFields; -import org.utbot.examples.assemble.arrays.ArrayOfComplexArrays; -import org.utbot.examples.assemble.arrays.ArrayOfPrimitiveArrays; -import org.utbot.examples.assemble.arrays.AssignedArray; -import org.utbot.examples.assemble.arrays.ComplexArray; -import org.utbot.examples.manual.examples.ArrayOfComplexArraysExample; -import org.utbot.examples.manual.examples.ArrayOfPrimitiveArraysExample; -import org.utbot.examples.manual.examples.AssignedArrayExample; -import org.utbot.examples.manual.examples.ClassRefExample; -import org.utbot.examples.manual.examples.DirectAccessExample; -import org.utbot.examples.manual.examples.MultiMethodExample; -import org.utbot.examples.manual.examples.ProvidedExample; -import org.utbot.examples.manual.examples.StringSwitchExample; -import org.utbot.examples.manual.examples.Trivial; -import org.utbot.examples.manual.examples.customer.B; -import org.utbot.examples.manual.examples.customer.C; -import org.utbot.examples.manual.examples.customer.Demo9; -import org.utbot.external.api.UtBotJavaApi; -import org.utbot.external.api.TestMethodInfo; -import org.utbot.external.api.UtModelFactory; -import org.utbot.framework.codegen.ForceStaticMocking; -import org.utbot.framework.codegen.Junit4; -import org.utbot.framework.codegen.MockitoStaticMocking; -import org.utbot.framework.plugin.api.ClassId; -import org.utbot.framework.plugin.api.CodegenLanguage; -import org.utbot.framework.plugin.api.EnvironmentModels; -import org.utbot.framework.plugin.api.MockStrategyApi; -import org.utbot.framework.plugin.api.UtArrayModel; -import org.utbot.framework.plugin.api.UtClassRefModel; -import org.utbot.framework.plugin.api.UtCompositeModel; -import org.utbot.framework.plugin.api.UtModel; -import org.utbot.framework.plugin.api.UtNullModel; -import org.utbot.framework.plugin.api.UtPrimitiveModel; -import org.utbot.framework.plugin.api.UtTestCase; -import org.utbot.framework.plugin.api.util.UtContext; -import org.utbot.framework.util.Snippet; -import java.io.File; -import java.lang.reflect.Method; -import java.net.URISyntaxException; -import java.net.URLClassLoader; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import kotlin.Pair; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import static org.utbot.examples.manual.PredefinedGeneratorParameters.destinationClassName; -import static org.utbot.examples.manual.PredefinedGeneratorParameters.getMethodByName; -import static org.utbot.external.api.UtModelFactoryKt.classIdForType; -import static org.utbot.framework.plugin.api.MockFramework.MOCKITO; -import static org.utbot.framework.plugin.api.util.IdUtilKt.getIntArrayClassId; -import static org.utbot.framework.util.TestUtilsKt.compileClassAndGetClassPath; -import static org.utbot.framework.util.TestUtilsKt.compileClassFile; - -class PredefinedGeneratorParameters { - - static String destinationClassName = "GeneratedTest"; - - static Method getMethodByName(Class clazz, String name, Class... parameters) { - try { - return clazz.getDeclaredMethod(name, parameters); - } catch (NoSuchMethodException ignored) { - Assertions.fail(); - } - throw new RuntimeException(); - } -} - -public class UtBotJavaApiTest { - private AutoCloseable context; - private UtModelFactory modelFactory; - - @BeforeEach - public void setUp() { - SootUtils.runSoot(PrimitiveFields.class); - context = UtContext.Companion.setUtContext(new UtContext(PrimitiveFields.class.getClassLoader())); - modelFactory = new UtModelFactory(); - } - - @AfterEach - public void tearDown() { - try { - context.close(); - modelFactory = null; - } catch (Exception e) { - Assertions.fail(); - } - } - - @Test - public void testMultiMethodClass() { - - UtBotJavaApi.setStopConcreteExecutorOnExit(false); - - String classpath = getClassPath(MultiMethodExample.class); - String dependencyClassPath = getDependencyClassPath(); - - UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( - classIdForType(MultiMethodExample.class) - ); - - EnvironmentModels initialState = new EnvironmentModels( - classUnderTestModel, - Collections.emptyList(), - Collections.emptyMap() - ); - - EnvironmentModels thirdMethodState = new EnvironmentModels( - classUnderTestModel, - Collections.singletonList(new UtPrimitiveModel("some")), - Collections.emptyMap() - ); - - - Method firstMethodUnderTest = getMethodByName( - MultiMethodExample.class, - "firstMethod" - ); - - Method secondMethodUnderTest = getMethodByName( - MultiMethodExample.class, - "secondMethod" - ); - - Method thirdMethodUnderTest = getMethodByName( - MultiMethodExample.class, - "thirdMethod", - String.class - ); - - TestMethodInfo firstTestMethodInfo = new TestMethodInfo( - firstMethodUnderTest, - initialState); - TestMethodInfo secondTestMethodInfo = new TestMethodInfo( - secondMethodUnderTest, - initialState); - TestMethodInfo thirdTestMethodInfo = new TestMethodInfo( - thirdMethodUnderTest, - thirdMethodState); - - List utTestCases = UtBotJavaApi.generateTestCases( - Arrays.asList(firstTestMethodInfo, secondTestMethodInfo, thirdTestMethodInfo), - MultiMethodExample.class, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResult = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases, - destinationClassName, - classpath, - dependencyClassPath, - MultiMethodExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE, - MultiMethodExample.class.getPackage().getName() - ); - - Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); - compileClassFile(destinationClassName, snippet1); - String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generate( - Arrays.asList(firstTestMethodInfo, secondTestMethodInfo, thirdTestMethodInfo), - Collections.emptyList(), - destinationClassName, - classpath, - dependencyClassPath, - MultiMethodExample.class - ); - - Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); - compileClassFile(destinationClassName, snippet2); - } - - @Test - public void testCustomPackage() { - - UtBotJavaApi.setStopConcreteExecutorOnExit(false); - - String classpath = getClassPath(DirectAccess.class); - String dependencyClassPath = getDependencyClassPath(); - - HashMap fields = new HashMap<>(); - fields.put("stringClass", modelFactory.produceClassRefModel(String.class)); - - UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( - classIdForType(ClassRefExample.class), - fields - ); - - UtClassRefModel classRefModel = new UtClassRefModel( - classIdForType(Class.class), Class.class - ); - - EnvironmentModels initialState = new EnvironmentModels( - classUnderTestModel, - Collections.singletonList(classRefModel), - Collections.emptyMap() - ); - - Method methodUnderTest = getMethodByName( - ClassRefExample.class, - "assertInstance", - Class.class - ); - - List utTestCases = UtBotJavaApi.generateTestCases( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState) - ), - ClassRefExample.class, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResult = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases, - destinationClassName, - classpath, - dependencyClassPath, - ClassRefExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE, - "some.custom.name" - ); - - Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); - compileClassFile(destinationClassName, snippet1); - - String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generate( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState) - ), - Collections.emptyList(), - destinationClassName, - classpath, - dependencyClassPath, - ClassRefExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE, - "some.custom.name" - ); - - Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); - compileClassFile(destinationClassName, snippet2); - } - - @Test - public void testOnObjectWithAssignedArrayField() { - - UtBotJavaApi.setStopConcreteExecutorOnExit(false); - - String classpath = getClassPath(DirectAccess.class); - String dependencyClassPath = getDependencyClassPath(); - - ClassId classIdAssignedArray = classIdForType(AssignedArray.class); - - HashMap values = new HashMap<>(); - values.put(0, new UtPrimitiveModel(1)); - values.put(1, new UtPrimitiveModel(2)); - - UtArrayModel utArrayModel = modelFactory.produceArrayModel( - getIntArrayClassId(), - 10, - new UtPrimitiveModel(0), - values - ); - - UtCompositeModel compositeModel = modelFactory.produceCompositeModel( - classIdAssignedArray, - Collections.singletonMap("array", utArrayModel) - ); - - UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( - classIdForType(AssignedArrayExample.class) - ); - - EnvironmentModels initialState = new EnvironmentModels( - classUnderTestModel, - Collections.singletonList(compositeModel), - Collections.emptyMap() - ); - - - Method methodUnderTest = getMethodByName( - AssignedArrayExample.class, - "foo", - AssignedArray.class - ); - - List utTestCases = UtBotJavaApi.generateTestCases( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState) - ), - AssignedArrayExample.class, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResult = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases, - destinationClassName, - classpath, - dependencyClassPath, - AssignedArrayExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); - compileClassFile(destinationClassName, snippet1); - - String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generate( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState) - ), - Collections.emptyList(), - destinationClassName, - classpath, - dependencyClassPath, - AssignedArrayExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); - compileClassFile(destinationClassName, snippet2); - } - - @Test - public void testClassRef() { - - UtBotJavaApi.setStopConcreteExecutorOnExit(false); - - String classpath = getClassPath(DirectAccess.class); - String dependencyClassPath = getDependencyClassPath(); - - HashMap fields = new HashMap<>(); - fields.put("stringClass", modelFactory.produceClassRefModel(String.class)); - - UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( - classIdForType(ClassRefExample.class), - fields - ); - - UtClassRefModel classRefModel = new UtClassRefModel( - classIdForType(Class.class), Class.class - ); - - EnvironmentModels initialState = new EnvironmentModels( - classUnderTestModel, - Collections.singletonList(classRefModel), - Collections.emptyMap() - ); - - Method methodUnderTest = getMethodByName( - ClassRefExample.class, - "assertInstance", - Class.class - ); - - List utTestCases = UtBotJavaApi.generateTestCases( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState) - ), - ClassRefExample.class, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResult = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases, - destinationClassName, - classpath, - dependencyClassPath, - ClassRefExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - - Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); - compileClassFile(destinationClassName, snippet1); - - String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generate( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState) - ), - Collections.emptyList(), - destinationClassName, - classpath, - dependencyClassPath, - ClassRefExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); - compileClassFile(destinationClassName, snippet2); - } - - @Test - public void testObjectWithPublicFields() { - - UtBotJavaApi.setStopConcreteExecutorOnExit(false); - - String classpath = getClassPath(DirectAccess.class); - String dependencyClassPath = getDependencyClassPath(); - - ClassId testClassId = classIdForType(DirectAccess.class); - ClassId innerClassId = classIdForType(PrimitiveFields.class); - - HashMap primitiveFields = new HashMap<>(); - primitiveFields.put("a", new UtPrimitiveModel(2)); - primitiveFields.put("b", new UtPrimitiveModel(4)); - - HashMap fields = new HashMap<>(); - fields.put("a", new UtPrimitiveModel(2)); - fields.put("b", new UtPrimitiveModel(4)); - fields.put("s", - modelFactory.produceCompositeModel( - innerClassId, - primitiveFields, - Collections.emptyMap() - ) - ); - - UtCompositeModel compositeModel = modelFactory.produceCompositeModel( - testClassId, - fields, - Collections.emptyMap() - ); - - // This class does not contain any fields. Using overloads - UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( - classIdForType(DirectAccessExample.class) - ); - - EnvironmentModels initialState = new EnvironmentModels( - classUnderTestModel, - Collections.singletonList(compositeModel), - Collections.emptyMap() - ); - - - Method methodUnderTest = getMethodByName( - DirectAccessExample.class, - "foo", - DirectAccess.class - ); - - List utTestCases = UtBotJavaApi.generateTestCases( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState) - ), - DirectAccessExample.class, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResult = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases, - destinationClassName, - classpath, - dependencyClassPath, - DirectAccessExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); - compileClassFile(destinationClassName, snippet1); - - String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generate( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState) - ), - Collections.emptyList(), - destinationClassName, - classpath, - dependencyClassPath, - DirectAccessExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); - compileClassFile(destinationClassName, snippet2); - } - - @Test - public void testObjectWithPublicFieldsWithAssembleModel() { - - UtBotJavaApi.setStopConcreteExecutorOnExit(false); - - String classpath = getClassPath(DirectAccess.class); - String dependencyClassPath = getDependencyClassPath(); - - ClassId testClassId = classIdForType(DirectAccess.class); - ClassId innerClassId = classIdForType(PrimitiveFields.class); - - HashMap primitiveFields = new HashMap<>(); - primitiveFields.put("a", new UtPrimitiveModel(2)); - primitiveFields.put("b", new UtPrimitiveModel(4)); - - HashMap fields = new HashMap<>(); - fields.put("a", new UtPrimitiveModel(2)); - fields.put("b", new UtPrimitiveModel(4)); - fields.put("s", - modelFactory.produceCompositeModel( - innerClassId, - primitiveFields, - Collections.emptyMap() - ) - ); - - UtCompositeModel compositeModel = modelFactory.produceCompositeModel( - testClassId, - fields, - Collections.emptyMap() - ); - - // This class does not contain any fields. Using overloads - UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( - classIdForType(DirectAccessExample.class) - ); - - EnvironmentModels initialState = new EnvironmentModels( - classUnderTestModel, - Collections.singletonList(compositeModel), - Collections.emptyMap() - ); - - Method methodUnderTest = getMethodByName( - DirectAccessExample.class, - "foo", - DirectAccess.class - ); - - List utTestCases = UtBotJavaApi.generateTestCases( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState) - ), - DirectAccessExample.class, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResult = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases, - destinationClassName, - classpath, - dependencyClassPath, - DirectAccessExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); - compileClassFile(destinationClassName, snippet1); - } - - - @Test - public void testOnObjectWithArrayOfPrimitiveArrays() { - - UtBotJavaApi.setStopConcreteExecutorOnExit(false); - - String classpath = getClassPath(ArrayOfPrimitiveArraysExample.class); - String dependencyClassPath = getDependencyClassPath(); - - ClassId cidPrimitiveArrays = getIntArrayClassId(); - ClassId cidPrimitiveArraysOuter = new ClassId("[[I", cidPrimitiveArrays); - ClassId classIdArrayOfPrimitiveArraysClass = classIdForType(ArrayOfPrimitiveArrays.class); - ClassId cidArrayOfPrimitiveArraysTest = classIdForType(ArrayOfPrimitiveArraysExample.class); - - HashMap arrayParameters = new HashMap<>(); - arrayParameters.put(0, new UtPrimitiveModel(88)); - arrayParameters.put(1, new UtPrimitiveModel(42)); - - UtArrayModel innerArrayOfPrimitiveArrayModel = modelFactory.produceArrayModel( - cidPrimitiveArrays, - 2, - new UtPrimitiveModel(0), - arrayParameters - ); - - HashMap enclosingArrayParameters = new HashMap<>(); - enclosingArrayParameters.put(0, innerArrayOfPrimitiveArrayModel); - enclosingArrayParameters.put(1, innerArrayOfPrimitiveArrayModel); - - UtArrayModel enclosingArrayOfPrimitiveArrayModel = modelFactory.produceArrayModel( - cidPrimitiveArraysOuter, - 1, - new UtNullModel(getIntArrayClassId()), - enclosingArrayParameters - ); - - UtCompositeModel cmArrayOfPrimitiveArrays = modelFactory.produceCompositeModel( - classIdArrayOfPrimitiveArraysClass, - Collections.singletonMap("array", enclosingArrayOfPrimitiveArrayModel) - ); - - UtCompositeModel testClassCompositeModel = modelFactory.produceCompositeModel( - cidArrayOfPrimitiveArraysTest - ); - - EnvironmentModels initialState = new EnvironmentModels( - testClassCompositeModel, - Collections.singletonList(cmArrayOfPrimitiveArrays), - Collections.emptyMap() - ); - - Method methodUnderTest = getMethodByName( - ArrayOfPrimitiveArraysExample.class, - "assign10", - ArrayOfPrimitiveArrays.class - ); - - List utTestCases = UtBotJavaApi.generateTestCases( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState) - ), - ArrayOfPrimitiveArraysExample.class, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResult = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases, - destinationClassName, - classpath, - dependencyClassPath, - ArrayOfPrimitiveArraysExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet = new Snippet(CodegenLanguage.JAVA, generationResult); - compileClassFile(destinationClassName, snippet); - - String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generate( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState) - ), - Collections.emptyList(), - destinationClassName, - classpath, - dependencyClassPath, - ArrayOfPrimitiveArraysExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); - compileClassFile(destinationClassName, snippet2); - } - - /** - * The test is inspired by the API customers - */ - @Test - public void testProvided3() { - - UtBotJavaApi.setStopConcreteExecutorOnExit(false); - - String classpath = getClassPath(ArrayOfComplexArraysExample.class); - String dependencyClassPath = getDependencyClassPath(); - - UtCompositeModel cClassModel = modelFactory. - produceCompositeModel( - classIdForType(C.class), - Collections.singletonMap("integer", new UtPrimitiveModel(1)) - ); - - UtCompositeModel bClassModel = modelFactory - .produceCompositeModel( - classIdForType(B.class), - Collections.singletonMap("c", cClassModel) - ); - - UtCompositeModel demo9Model = modelFactory. - produceCompositeModel( - classIdForType(Demo9.class), - Collections.singletonMap("b0", bClassModel) - ); - - EnvironmentModels environmentModels = new EnvironmentModels( - demo9Model, - Collections.singletonList(bClassModel), - Collections.emptyMap() - ); - - Method methodUnderTest = getMethodByName( - Demo9.class, - "test", - B.class - ); - - List utTestCases = UtBotJavaApi.generateTestCases( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - environmentModels - ) - ), - Demo9.class, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResult = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases, - destinationClassName, - classpath, - dependencyClassPath, - Demo9.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); - compileClassFile(destinationClassName, snippet1); - - String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generate( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - environmentModels - ) - ), - Collections.emptyList(), - destinationClassName, - classpath, - dependencyClassPath, - Demo9.class - ); - - Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); - compileClassFile(destinationClassName, snippet2); - } - - @Test - public void testCustomAssertion() { - String classpath = getClassPath(Trivial.class); - String dependencyClassPath = getDependencyClassPath(); - - UtCompositeModel model = modelFactory. - produceCompositeModel( - classIdForType(Trivial.class) - ); - - EnvironmentModels environmentModels = new EnvironmentModels( - model, - Collections.singletonList(new UtPrimitiveModel(2)), - Collections.emptyMap() - ); - - Method methodUnderTest = getMethodByName( - Trivial.class, - "aMethod", - int.class - ); - - List utTestCases = UtBotJavaApi.generateTestCases( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - environmentModels - ) - ), - Trivial.class, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResult = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases, - destinationClassName, - classpath, - dependencyClassPath, - Trivial.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); - compileClassFile(destinationClassName, snippet1); - - String generationResult2 = UtBotJavaApi.generate( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - environmentModels - ) - ), - Collections.emptyList(), - destinationClassName, - classpath, - dependencyClassPath, - Trivial.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResult2); - compileClassFile(destinationClassName, snippet2); - } - - /** - * The test is inspired by the API customers - */ - @Test - public void testProvided3Reused() { - - UtBotJavaApi.setStopConcreteExecutorOnExit(true); - - String dependencyClassPath = getDependencyClassPath(); - - // The method in the class contains only one parameter - String classSourceAsString = - "public class Demo9Recompiled {" + - " public int test(int b1) {" + - " return 0;" + - " }" + - "}"; - - String demo9RecompiledClassName = "Demo9Recompiled"; - Pair classpathToClassLoader = - compileClassAndGetClassPath(new Pair<>(demo9RecompiledClassName, classSourceAsString)); - - - String classpath = classpathToClassLoader.getFirst(); - ClassLoader classLoader = classpathToClassLoader.getSecond(); - - Class compiledClass = null; - - try { - compiledClass = classLoader.loadClass(demo9RecompiledClassName); - } catch (ClassNotFoundException e) { - Assertions.fail("Failed to load a class; Classpath: " + classpathToClassLoader.getFirst()); - } - - if (compiledClass == null) { - Assertions.fail("Failed to load the class"); - } - - UtCompositeModel demo9Model = modelFactory. - produceCompositeModel( - classIdForType(compiledClass), - Collections.emptyMap() - ); - - EnvironmentModels environmentModels = new EnvironmentModels( - demo9Model, - Collections.singletonList(new UtPrimitiveModel(3)), - Collections.emptyMap() - ); - - Method methodUnderTest = getMethodByName( - compiledClass, - "test", - int.class - ); - - List utTestCases = UtBotJavaApi.generateTestCases( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - environmentModels - ) - ), - compiledClass, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases, - destinationClassName, - classpath, - dependencyClassPath, - compiledClass, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); - compileClassFile(destinationClassName, snippet); - - // The test compiles and everything goes well. - // Let's recompile the initial clas file - - // The method below has an extra parameter - classSourceAsString = - "public class Demo9Recompiled {" + - " public int test(int b1, String s) {" + - " return 0;" + - " }" + - "}"; - - Pair stringClassLoaderPair = - compileClassAndGetClassPath(new Pair<>(demo9RecompiledClassName, classSourceAsString), classpath); - - ClassLoader reloadedClassLoader = stringClassLoaderPair.getSecond(); - - Class recompiledClass = null; - - try { - recompiledClass = reloadedClassLoader.loadClass(demo9RecompiledClassName); - } catch (ClassNotFoundException e) { - Assertions.fail("Failed to load the class after recompilation; classpath: " + stringClassLoaderPair.getFirst()); - } - - if (recompiledClass == null) { - Assertions.fail("Failed to load the class after recompilation"); - } - - EnvironmentModels environmentModels2 = new EnvironmentModels( - demo9Model, - Arrays.asList(new UtPrimitiveModel(4), new UtPrimitiveModel("Some String")), - Collections.emptyMap() - ); - - Method methodUnderTest2 = getMethodByName( - recompiledClass, - "test", - int.class, - String.class - ); - - List utTestCases1 = UtBotJavaApi.generateTestCases( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest2, - environmentModels - ) - ), - recompiledClass, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResultWithConcreteExecutionOnly2 = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases1, - destinationClassName, - classpath, - dependencyClassPath, - recompiledClass, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly2); - compileClassFile(destinationClassName, snippet2); - } - - @Test - public void testProvided1() { - - UtBotJavaApi.setStopConcreteExecutorOnExit(false); - - String classpath = getClassPath(ArrayOfComplexArraysExample.class); - String dependencyClassPath = getDependencyClassPath(); - - UtCompositeModel providedTestModel = modelFactory.produceCompositeModel( - classIdForType(ProvidedExample.class), - Collections.emptyMap(), - Collections.emptyMap() - ); - - List parameters = Arrays.asList( - new UtPrimitiveModel(5), - new UtPrimitiveModel(9), - new UtPrimitiveModel("Some Text") - ); - - EnvironmentModels initialState = new EnvironmentModels( - providedTestModel, - parameters, - Collections.emptyMap() - ); - - Method methodUnderTest = getMethodByName( - ProvidedExample.class, - "test0", - int.class, int.class, String.class - ); - - List utTestCases = UtBotJavaApi.generateTestCases( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState - ) - ), - ProvidedExample.class, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResult = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases, - destinationClassName, - classpath, - dependencyClassPath, - ProvidedExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); - compileClassFile(destinationClassName, snippet1); - - String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generate( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState - ) - ), - Collections.emptyList(), - destinationClassName, - classpath, - dependencyClassPath, - ProvidedExample.class - ); - - Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); - compileClassFile(destinationClassName, snippet2); - } - - @Test - public void testOnObjectWithArrayOfComplexArrays() { - - UtBotJavaApi.setStopConcreteExecutorOnExit(false); - - String classpath = getClassPath(ArrayOfComplexArraysExample.class); - String dependencyClassPath = getDependencyClassPath(); - - UtCompositeModel cmArrayOfComplexArrays = createArrayOfComplexArraysModel(); - - UtCompositeModel testClassCompositeModel = modelFactory.produceCompositeModel( - classIdForType(ArrayOfComplexArraysExample.class) - ); - - EnvironmentModels initialState = new EnvironmentModels( - testClassCompositeModel, - Collections.singletonList(cmArrayOfComplexArrays), - Collections.emptyMap() - ); - - Method methodUnderTest = getMethodByName( - ArrayOfComplexArraysExample.class, - "getValue", - ArrayOfComplexArrays.class - ); - - List utTestCases = UtBotJavaApi.generateTestCases( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState - ) - ), - ArrayOfComplexArraysExample.class, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L - ); - - String generationResult = UtBotJavaApi.generate( - Collections.emptyList(), - utTestCases, - destinationClassName, - classpath, - dependencyClassPath, - ArrayOfComplexArraysExample.class, - Junit4.INSTANCE, - MOCKITO, - CodegenLanguage.JAVA, - MockitoStaticMocking.INSTANCE, - false, - ForceStaticMocking.DO_NOT_FORCE - ); - - Snippet snippet1 = new Snippet(CodegenLanguage.JAVA, generationResult); - compileClassFile(destinationClassName, snippet1); - - String generationResultWithConcreteExecutionOnly = UtBotJavaApi.generate( - Collections.singletonList( - new TestMethodInfo( - methodUnderTest, - initialState - ) - ), - Collections.emptyList(), - destinationClassName, - classpath, - dependencyClassPath, - ArrayOfComplexArraysExample.class - ); - - Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generationResultWithConcreteExecutionOnly); - compileClassFile(destinationClassName, snippet2); - } - - @Test - public void testFuzzingSimple() { - UtBotJavaApi.setStopConcreteExecutorOnExit(false); - - String classpath = getClassPath(StringSwitchExample.class); - String dependencyClassPath = getDependencyClassPath(); - - UtCompositeModel classUnderTestModel = modelFactory.produceCompositeModel( - classIdForType(StringSwitchExample.class) - ); - - Method methodUnderTest = getMethodByName(StringSwitchExample.class, "validate", String.class, int.class, int.class); - - IdentityHashMap models = modelFactory.produceAssembleModel( - methodUnderTest, - StringSwitchExample.class, - Collections.singletonList(classUnderTestModel) - ); - - EnvironmentModels methodState = new EnvironmentModels( - models.get(classUnderTestModel), - Arrays.asList(new UtPrimitiveModel("initial model"), new UtPrimitiveModel(-10), new UtPrimitiveModel(0)), - Collections.emptyMap() - ); - - TestMethodInfo methodInfo = new TestMethodInfo( - methodUnderTest, - methodState); - List utTestCases1 = UtBotJavaApi.fuzzingTestCases( - Collections.singletonList( - methodInfo - ), - StringSwitchExample.class, - classpath, - dependencyClassPath, - MockStrategyApi.OTHER_PACKAGES, - 3000L, - (type) -> { - if (int.class.equals(type) || Integer.class.equals(type)) { - return Arrays.asList(0, Integer.MIN_VALUE, Integer.MAX_VALUE); - } - return null; - } - ); - - String generate = UtBotJavaApi.generate( - Collections.singletonList(methodInfo), - utTestCases1, - destinationClassName, - classpath, - dependencyClassPath, - StringSwitchExample.class - ); - - Snippet snippet2 = new Snippet(CodegenLanguage.JAVA, generate); - compileClassFile(destinationClassName, snippet2); - } - - @NotNull - private String getClassPath(Class clazz) { - return clazz.getProtectionDomain().getCodeSource().getLocation().getPath(); - } - - @NotNull - private String getDependencyClassPath() { - return Arrays.stream(((URLClassLoader) Thread.currentThread(). - getContextClassLoader()).getURLs()).map(url -> - { - try { - return new File(url.toURI()).toString(); - } catch (URISyntaxException e) { - Assertions.fail(e); - } - throw new RuntimeException(); - }).collect(Collectors.joining(File.pathSeparator)); - } - - public UtCompositeModel createArrayOfComplexArraysModel() { - ClassId classIdOfArrayOfComplexArraysClass = classIdForType(ArrayOfComplexArrays.class); - ClassId classIdOfComplexArray = classIdForType(ComplexArray.class); - ClassId classIdOfPrimitiveFieldsClass = classIdForType(PrimitiveFields.class); - - ClassId classIdOfArrayOfPrimitiveFieldsClass = new ClassId("[L" + classIdOfPrimitiveFieldsClass.getCanonicalName() + ";", classIdOfPrimitiveFieldsClass); - - Map elementsOfComplexArrayArray = new HashMap<>(); - - Map elementsWithPrimitiveFieldsClasses = new HashMap<>(); - - elementsWithPrimitiveFieldsClasses.put(0, modelFactory.produceCompositeModel( - classIdOfPrimitiveFieldsClass, - Collections.singletonMap("a", new UtPrimitiveModel(5)), - Collections.emptyMap() - )); - - elementsWithPrimitiveFieldsClasses.put(1, modelFactory.produceCompositeModel( - classIdOfPrimitiveFieldsClass, - Collections.singletonMap("b", new UtPrimitiveModel(4)), - Collections.emptyMap() - )); - - UtArrayModel arrayOfPrimitiveFieldsModel = modelFactory.produceArrayModel( - classIdOfArrayOfPrimitiveFieldsClass, - 2, - new UtNullModel(classIdOfPrimitiveFieldsClass), - elementsWithPrimitiveFieldsClasses - ); - - UtCompositeModel complexArrayClassModel = modelFactory.produceCompositeModel( - classIdOfComplexArray, - Collections.singletonMap("array", arrayOfPrimitiveFieldsModel) - ); - - elementsOfComplexArrayArray.put(1, complexArrayClassModel); - - ClassId classIdOfArraysOfComplexArrayClass = new ClassId("[L" + classIdOfComplexArray.getCanonicalName() + ";", classIdOfComplexArray); - - UtArrayModel arrayOfComplexArrayClasses = modelFactory.produceArrayModel( - classIdOfArraysOfComplexArrayClass, - 2, - new UtNullModel(classIdOfComplexArray), - elementsOfComplexArrayArray - ); - - return modelFactory.produceCompositeModel( - classIdOfArrayOfComplexArraysClass, - Collections.singletonMap("array", arrayOfComplexArrayClasses)); - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/ArrayOfComplexArraysExample.java b/utbot-framework/src/test/java/org/utbot/examples/manual/examples/ArrayOfComplexArraysExample.java deleted file mode 100644 index d2392e36ea..0000000000 --- a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/ArrayOfComplexArraysExample.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.utbot.examples.manual.examples; -import org.utbot.examples.assemble.arrays.ArrayOfComplexArrays; - -public class ArrayOfComplexArraysExample { - public int getValue(ArrayOfComplexArrays a) { - return a.array[0].array[0].getB(); - } -} diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/AssignedArrayExample.java b/utbot-framework/src/test/java/org/utbot/examples/manual/examples/AssignedArrayExample.java deleted file mode 100644 index c4b5b0f032..0000000000 --- a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/AssignedArrayExample.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.utbot.examples.manual.examples; - -import org.utbot.examples.assemble.arrays.AssignedArray; - -public class AssignedArrayExample { - public void foo(AssignedArray aa) { - aa.array[9] = 42; - } -} diff --git a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/Trivial.java b/utbot-framework/src/test/java/org/utbot/examples/manual/examples/Trivial.java deleted file mode 100644 index 7bc2779d50..0000000000 --- a/utbot-framework/src/test/java/org/utbot/examples/manual/examples/Trivial.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.utbot.examples.manual.examples; - -public class Trivial { - public int aMethod(int a) { - return a; - } -} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractModelBasedTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractModelBasedTest.kt deleted file mode 100644 index cac05041c3..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractModelBasedTest.kt +++ /dev/null @@ -1,186 +0,0 @@ -@file:Suppress("NestedLambdaShadowedImplicitParameter") - -package org.utbot.examples - -import org.utbot.common.ClassLocation -import org.utbot.common.FileUtil.findPathToClassFiles -import org.utbot.common.FileUtil.locateClass -import org.utbot.common.WorkaroundReason.HACK -import org.utbot.common.findField -import org.utbot.common.workaround -import org.utbot.engine.prettify -import org.utbot.framework.UtSettings.checkSolverTimeoutMillis -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.MockStrategyApi.NO_MOCKS -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtBotTestCaseGenerator -import org.utbot.framework.plugin.api.UtCompositeModel -import org.utbot.framework.plugin.api.UtDirectSetFieldModel -import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtExecutionResult -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.framework.plugin.api.exceptionOrNull -import org.utbot.framework.plugin.api.getOrThrow -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.defaultValueModel -import org.utbot.framework.plugin.api.util.executableId -import org.utbot.framework.plugin.api.util.fieldId -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.withUtContext -import java.nio.file.Path -import kotlin.reflect.KClass -import kotlin.reflect.KFunction -import kotlin.reflect.KFunction1 -import kotlin.reflect.KFunction2 -import kotlin.reflect.KFunction3 -import org.junit.jupiter.api.Assertions.assertTrue - -internal abstract class AbstractModelBasedTest( - testClass: KClass<*>, - testCodeGeneration: Boolean = true, - languagePipelines: List = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN) - ) -) : CodeTestCaseGeneratorTest(testClass, testCodeGeneration, languagePipelines) { - protected fun check( - method: KFunction2<*, *, *>, - branches: ExecutionsNumberMatcher, - vararg matchers: (UtModel, UtExecutionResult) -> Boolean, - mockStrategy: MockStrategyApi = NO_MOCKS - ) = internalCheck(method, mockStrategy, branches, matchers) - - protected fun check( - method: KFunction3<*, *, *, *>, - branches: ExecutionsNumberMatcher, - vararg matchers: (UtModel, UtModel, UtExecutionResult) -> Boolean, - mockStrategy: MockStrategyApi = NO_MOCKS - ) = internalCheck(method, mockStrategy, branches, matchers) - - protected fun checkStatic( - method: KFunction1<*, *>, - branches: ExecutionsNumberMatcher, - vararg matchers: (UtModel, UtExecutionResult) -> Boolean, - mockStrategy: MockStrategyApi = NO_MOCKS - ) = internalCheck(method, mockStrategy, branches, matchers) - - protected fun checkStaticsAfter( - method: KFunction2<*, *, *>, - branches: ExecutionsNumberMatcher, - vararg matchers: (UtModel, StaticsModelType, UtExecutionResult) -> Boolean, - mockStrategy: MockStrategyApi = NO_MOCKS - ) = internalCheck( - method, mockStrategy, branches, matchers, - arguments = ::withStaticsAfter - ) - - private fun internalCheck( - method: KFunction<*>, - mockStrategy: MockStrategyApi, - branches: ExecutionsNumberMatcher, - matchers: Array>, - arguments: (UtExecution) -> List = ::withResult - ) { - workaround(HACK) { - // @todo change to the constructor parameter - checkSolverTimeoutMillis = 0 - } - val utMethod = UtMethod.from(method) - - withUtContext(UtContext(utMethod.clazz.java.classLoader)) { - val testCase = executions(utMethod, mockStrategy) - - assertTrue(testCase.errors.isEmpty()) { - "We have errors: ${testCase.errors.entries.map { "${it.value}: ${it.key}" }.prettify()}" - } - - val executions = testCase.executions - assertTrue(branches(executions.size)) { - "Branch count matcher '$branches' fails for #executions=${executions.size}: ${executions.prettify()}" - } - executions.checkMatchers(matchers, arguments) - - processTestCase(testCase) - } - } - - private fun List.checkMatchers( - matchers: Array>, - arguments: (UtExecution) -> List - ) { - val exceptions = mutableMapOf>() - val notMatched = matchers.indices.filter { i -> - this.none { ex -> - runCatching { invokeMatcher(matchers[i], arguments(ex)) } - .onFailure { exceptions.merge(i, listOf(it)) { v1, v2 -> v1 + v2 } } - .getOrDefault(false) - } - } - - val matchersNumbers = notMatched.map { it + 1 } - assertTrue(notMatched.isEmpty()) { - "Execution matchers $matchersNumbers not match to ${this.prettify()}, found exceptions: ${exceptions.prettify()}" - } - } - - private fun executions( - method: UtMethod<*>, - mockStrategy: MockStrategyApi - ): UtTestCase { - val classLocation = locateClass(method.clazz) - if (classLocation != previousClassLocation) { - buildDir = findPathToClassFiles(classLocation) - previousClassLocation = classLocation - } - UtBotTestCaseGenerator.init(buildDir, classpath = null, dependencyPaths = System.getProperty("java.class.path")) - return UtBotTestCaseGenerator.generate(method, mockStrategy) - } - - protected inline fun UtExecutionResult.isException(): Boolean = exceptionOrNull() is T - - /** - * Finds mocked method and returns values. - */ - protected fun UtModel.mocksMethod(method: KFunction<*>): List? { - if (this !is UtCompositeModel) return null - if (!isMock) return null - return mocks[method.executableId] - } - - protected inline fun UtExecutionResult.primitiveValue(): T = getOrThrow().primitiveValue() - - /** - * Finds field model in [UtCompositeModel] and [UtAssembleModel]. For assemble model supports direct field access only. - */ - protected fun UtModel.findField(fieldName: String): UtModel = - findField(this.classId.jClass.findField(fieldName).fieldId) - - /** - * Finds field model in [UtCompositeModel] and [UtAssembleModel]. For assemble model supports direct field access only. - */ - @Suppress("MemberVisibilityCanBePrivate") - protected fun UtModel.findField(fieldId: FieldId): UtModel = when (this) { - is UtCompositeModel -> this.fields[fieldId]!! - is UtAssembleModel -> { - val fieldAccess = this.allStatementsChain - .filterIsInstance() - .singleOrNull { it.fieldId == fieldId } - fieldAccess?.fieldModel ?: fieldId.type.defaultValueModel() - } - else -> error("Can't get ${fieldId.name} from $this") - } - - companion object { - private var previousClassLocation: ClassLocation? = null - private lateinit var buildDir: Path - } -} - -private fun withResult(ex: UtExecution) = ex.stateBefore.parameters + ex.result -private fun withStaticsAfter(ex: UtExecution) = ex.stateBefore.parameters + ex.stateAfter.statics + ex.result - -private typealias StaticsModelType = Map \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt deleted file mode 100644 index 1ab652db79..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/AbstractTestCaseGeneratorTest.kt +++ /dev/null @@ -1,2828 +0,0 @@ -@file:Suppress("NestedLambdaShadowedImplicitParameter") - -package org.utbot.examples - -import org.utbot.common.ClassLocation -import org.utbot.common.FileUtil.clearTempDirectory -import org.utbot.common.FileUtil.findPathToClassFiles -import org.utbot.common.FileUtil.locateClass -import org.utbot.engine.prettify -import org.utbot.framework.TestSelectionStrategyType -import org.utbot.framework.UtSettings -import org.utbot.framework.UtSettings.checkCoverageInCodeGenerationTests -import org.utbot.framework.UtSettings.daysLimitForTempFiles -import org.utbot.framework.UtSettings.testDisplayName -import org.utbot.framework.UtSettings.testName -import org.utbot.framework.UtSettings.testSummary -import org.utbot.framework.codegen.BaseTestCodeGeneratorPipeline -import org.utbot.framework.codegen.ClassStages -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.codegen.ExecutionStatus -import org.utbot.framework.codegen.StageStatusCheck -import org.utbot.framework.codegen.TestExecution -import org.utbot.framework.coverage.Coverage -import org.utbot.framework.coverage.counters -import org.utbot.framework.coverage.methodCoverage -import org.utbot.framework.coverage.toAtLeast -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.DocClassLinkStmt -import org.utbot.framework.plugin.api.DocCodeStmt -import org.utbot.framework.plugin.api.DocMethodLinkStmt -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.DocStatement -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.FieldMockTarget -import org.utbot.framework.plugin.api.MockId -import org.utbot.framework.plugin.api.MockInfo -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.MockStrategyApi.NO_MOCKS -import org.utbot.framework.plugin.api.ObjectMockTarget -import org.utbot.framework.plugin.api.ParameterMockTarget -import org.utbot.framework.plugin.api.UtBotTestCaseGenerator -import org.utbot.framework.plugin.api.UtCompositeModel -import org.utbot.framework.plugin.api.UtConcreteValue -import org.utbot.framework.plugin.api.UtInstrumentation -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtMockValue -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.framework.plugin.api.UtValueExecution -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.executableId -import org.utbot.framework.plugin.api.util.withUtContext -import org.utbot.framework.util.toValueTestCase -import org.utbot.summary.summarize -import java.io.File -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.reflect.KClass -import kotlin.reflect.KFunction -import kotlin.reflect.KFunction0 -import kotlin.reflect.KFunction1 -import kotlin.reflect.KFunction2 -import kotlin.reflect.KFunction3 -import kotlin.reflect.KFunction4 -import kotlin.reflect.KFunction5 -import mu.KotlinLogging -import org.junit.jupiter.api.Assertions.assertTrue -import org.utbot.framework.PathSelectorType - -val logger = KotlinLogging.logger {} - -abstract class AbstractTestCaseGeneratorTest( - testClass: KClass<*>, - testCodeGeneration: Boolean = true, - languagePipelines: List = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN) - ) -) : CodeTestCaseGeneratorTest(testClass, testCodeGeneration, languagePipelines) { - // contains already analyzed by the engine methods - private val analyzedMethods: MutableMap = mutableMapOf() - - val searchDirectory: Path = Paths.get("../utbot-sample/src/main/java") - - init { - UtSettings.checkSolverTimeoutMillis = 0 - UtSettings.checkNpeInNestedMethods = true - UtSettings.checkNpeInNestedNotPrivateMethods = true - UtSettings.substituteStaticsWithSymbolicVariable = true - UtSettings.useAssembleModelGenerator = true - UtSettings.saveRemainingStatesForConcreteExecution = false - UtSettings.useFuzzing = false - } - - // checks paramsBefore and result - protected inline fun check( - method: KFunction1<*, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun check( - method: KFunction2<*, T, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun check( - method: KFunction3<*, T1, T2, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun check( - method: KFunction4<*, T1, T2, T3, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun check( - method: KFunction5<*, T1, T2, T3, T4, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // check paramsBefore and Result, suitable to check exceptions - protected inline fun checkWithException( - method: KFunction1<*, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - arguments = ::withException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithException( - method: KFunction2<*, T, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithException( - method: KFunction3<*, T1, T2, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithException( - method: KFunction4<*, T1, T2, T3, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithException( - method: KFunction5<*, T1, T2, T3, T4, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // check this, paramsBefore and result value - protected inline fun checkWithThis( - method: KFunction1, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withThisAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithThis( - method: KFunction2, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, - arguments = ::withThisAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithThis( - method: KFunction3, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, - arguments = ::withThisAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithThis( - method: KFunction4, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, T3, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, - arguments = ::withThisAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithThis( - method: KFunction5, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, T3, T4, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, - arguments = ::withThisAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithThisAndException( - method: KFunction1, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withThisAndException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithThisAndException( - method: KFunction2, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, - arguments = ::withThisAndException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithThisAndException( - method: KFunction3, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, - arguments = ::withThisAndException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithThisAndException( - method: KFunction4, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, T3, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, - arguments = ::withThisAndException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithThisAndException( - method: KFunction5, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, T3, T4, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, - arguments = ::withThisAndException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // checks paramsBefore, mocks and result value - protected inline fun checkMocksInStaticMethod( - method: KFunction0, - branches: ExecutionsNumberMatcher, - vararg matchers: (Mocks, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - arguments = ::withMocks, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocksInStaticMethod( - method: KFunction1, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, Mocks, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withMocks, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocksInStaticMethod( - method: KFunction2, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, Mocks, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withMocks, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocksInStaticMethod( - method: KFunction3, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, Mocks, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withMocks, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocksInStaticMethod( - method: KFunction4, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, Mocks, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withMocks, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // checks paramsBefore, mocks and result value - protected inline fun checkMocks( - method: KFunction1<*, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (Mocks, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - arguments = ::withMocks, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocks( - method: KFunction2<*, T, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, Mocks, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withMocks, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocks( - method: KFunction3<*, T1, T2, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, Mocks, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withMocks, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocks( - method: KFunction4<*, T1, T2, T3, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, Mocks, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withMocks, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocks( - method: KFunction5<*, T1, T2, T3, T4, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, Mocks, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withMocks, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // check paramsBefore, mocks and instrumentation and result value - protected inline fun checkMocksAndInstrumentation( - method: KFunction1<*, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (Mocks, Instrumentation, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - arguments = ::withMocksAndInstrumentation, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocksAndInstrumentation( - method: KFunction2<*, T, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, Mocks, Instrumentation, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withMocksAndInstrumentation, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocksAndInstrumentation( - method: KFunction3<*, T1, T2, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, Mocks, Instrumentation, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withMocksAndInstrumentation, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocksAndInstrumentation( - method: KFunction4<*, T1, T2, T3, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, Mocks, Instrumentation, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withMocksAndInstrumentation, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocksAndInstrumentation( - method: KFunction5<*, T1, T2, T3, T4, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, Mocks, Instrumentation, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withMocksAndInstrumentation, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // check this, paramsBefore, mocks, instrumentation and return value - protected inline fun checkMocksInstrumentationAndThis( - method: KFunction1, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, Mocks, Instrumentation, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withMocksInstrumentationAndThis, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocksInstrumentationAndThis( - method: KFunction2, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, Mocks, Instrumentation, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, - arguments = ::withMocksInstrumentationAndThis, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocksInstrumentationAndThis( - method: KFunction3, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, Mocks, Instrumentation, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, - arguments = ::withMocksInstrumentationAndThis, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocksInstrumentationAndThis( - method: KFunction4, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, T3, Mocks, Instrumentation, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, - arguments = ::withMocksInstrumentationAndThis, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMocksInstrumentationAndThis( - method: KFunction5, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, T3, T4, Mocks, Instrumentation, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, - arguments = ::withMocksInstrumentationAndThis, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // checks paramsBefore and return value for static methods - protected inline fun checkStaticMethod( - method: KFunction0, - branches: ExecutionsNumberMatcher, - vararg matchers: (R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethod( - method: KFunction1, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethod( - method: KFunction2, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethod( - method: KFunction3, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethod( - method: KFunction4, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // checks paramsBefore and Result, suitable for exceptions check - protected inline fun checkStaticMethodWithException( - method: KFunction0, - branches: ExecutionsNumberMatcher, - vararg matchers: (Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - arguments = ::withException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodWithException( - method: KFunction1, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodWithException( - method: KFunction2, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodWithException( - method: KFunction3, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodWithException( - method: KFunction4, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, Result) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withException, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // check arguments, statics and return value - protected inline fun checkStatics( - method: KFunction1<*, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - arguments = ::withStaticsBefore, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStatics( - method: KFunction2<*, T, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withStaticsBefore, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStatics( - method: KFunction3<*, T1, T2, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withStaticsBefore, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStatics( - method: KFunction4<*, T1, T2, T3, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withStaticsBefore, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStatics( - method: KFunction5<*, T1, T2, T3, T4, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withStaticsBefore, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsAfter( - method: KFunction1<*, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - arguments = ::withStaticsAfter, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsAfter( - method: KFunction2<*, T, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withStaticsAfter, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsAfter( - method: KFunction3<*, T1, T2, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withStaticsAfter, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsAfter( - method: KFunction4<*, T1, T2, T3, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withStaticsAfter, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsAfter( - method: KFunction5<*, T1, T2, T3, T4, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withStaticsAfter, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkThisAndStaticsAfter( - method: KFunction1, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withThisAndStaticsAfter, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkThisAndStaticsAfter( - method: KFunction2, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, - arguments = ::withThisAndStaticsAfter, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkThisAndStaticsAfter( - method: KFunction3, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, - arguments = ::withThisAndStaticsAfter, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkThisAndStaticsAfter( - method: KFunction4, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, T3, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, - arguments = ::withThisAndStaticsAfter, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkThisAndStaticsAfter( - method: KFunction5, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, T3, T4, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, - arguments = ::withThisAndStaticsAfter, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // checks paramsBefore, staticsBefore and return value for static methods - protected inline fun checkStaticsInStaticMethod( - method: KFunction0, - branches: ExecutionsNumberMatcher, - vararg matchers: (StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - arguments = ::withStaticsBefore, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsInStaticMethod( - method: KFunction1, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withStaticsBefore, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsInStaticMethod( - method: KFunction2, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withStaticsBefore, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsInStaticMethod( - method: KFunction3, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withStaticsBefore, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsInStaticMethod( - method: KFunction4, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withStaticsBefore, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsInStaticMethod( - method: KFunction5, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, T5, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withStaticsBefore, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // check this, arguments and result value - protected inline fun checkStaticsWithThis( - method: KFunction1, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withThisStaticsBeforeAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsWithThis( - method: KFunction2, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, - arguments = ::withThisStaticsBeforeAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsWithThis( - method: KFunction3, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, - arguments = ::withThisStaticsBeforeAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsWithThis( - method: KFunction4, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, T3, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, - arguments = ::withThisStaticsBeforeAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticsWithThis( - method: KFunction5, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, T3, T4, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, - arguments = ::withThisStaticsBeforeAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkParamsMutationsAndResult( - method: KFunction2<*, T, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withParamsMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkParamsMutationsAndResult( - method: KFunction3<*, T1, T2, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T1, T2, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withParamsMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkParamsMutationsAndResult( - method: KFunction4<*, T1, T2, T3, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T1, T2, T3, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withParamsMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkParamsMutationsAndResult( - method: KFunction5<*, T1, T2, T3, T4, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, T1, T2, T3, T4, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withParamsMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // checks mutations in the parameters - protected inline fun checkParamsMutations( - method: KFunction2<*, T, *>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withParamsMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkParamsMutations( - method: KFunction3<*, T1, T2, *>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T1, T2) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withParamsMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkParamsMutations( - method: KFunction4<*, T1, T2, T3, *>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T1, T2, T3) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withParamsMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkParamsMutations( - method: KFunction5<*, T1, T2, T3, T4, *>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, T1, T2, T3, T4) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withParamsMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // checks mutations in the parameters and statics for static method - protected fun checkStaticMethodMutation( - method: KFunction0<*>, - branches: ExecutionsNumberMatcher, - vararg matchers: (StaticsType, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - arguments = ::withMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodMutation( - method: KFunction1, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, StaticsType, T, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodMutation( - method: KFunction2, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, StaticsType, T1, T2, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodMutation( - method: KFunction3, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, StaticsType, T1, T2, T3, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodMutation( - method: KFunction4, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, StaticsType, T1, T2, T3, T4, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodMutationAndResult( - method: KFunction0, - branches: ExecutionsNumberMatcher, - vararg matchers: (StaticsType, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - arguments = ::withMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodMutationAndResult( - method: KFunction1, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, StaticsType, T, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodMutationAndResult( - method: KFunction2, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, StaticsType, T1, T2, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodMutationAndResult( - method: KFunction3, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, StaticsType, T1, T2, T3, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkStaticMethodMutationAndResult( - method: KFunction4, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, StaticsType, T1, T2, T3, T4, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - - // checks mutations in the parameters and statics - protected inline fun checkMutations( - method: KFunction2<*, T, *>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, StaticsType, T, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMutations( - method: KFunction3<*, T1, T2, *>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, StaticsType, T1, T2, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMutations( - method: KFunction4<*, T1, T2, T3, *>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, StaticsType, T1, T2, T3, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMutations( - method: KFunction5<*, T1, T2, T3, T4, *>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, StaticsType, T1, T2, T3, T4, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withMutations, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // checks mutations in the parameters and statics - protected inline fun checkMutationsAndResult( - method: KFunction1<*, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (StaticsType, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - arguments = ::withMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMutationsAndResult( - method: KFunction2<*, T, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, StaticsType, T, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMutationsAndResult( - method: KFunction3<*, T1, T2, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, StaticsType, T1, T2, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - arguments = ::withMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMutationsAndResult( - method: KFunction4<*, T1, T2, T3, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, StaticsType, T1, T2, T3, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - arguments = ::withMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkMutationsAndResult( - method: KFunction5<*, T1, T2, T3, T4, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, StaticsType, T1, T2, T3, T4, StaticsType, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - arguments = ::withMutationsAndResult, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - // checks mutations in this, parameters and statics - protected inline fun checkAllMutationsWithThis( - method: KFunction1, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, StaticsType, T, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - arguments = ::withMutationsAndThis, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkAllMutationsWithThis( - method: KFunction2, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, StaticsType, T, T1, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, - arguments = ::withMutationsAndThis, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkAllMutationsWithThis( - method: KFunction3, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, StaticsType, T, T1, T2, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, - arguments = ::withMutationsAndThis, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkAllMutationsWithThis( - method: KFunction4, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, T3, StaticsType, T, T1, T2, T3, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, - arguments = ::withMutationsAndThis, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkAllMutationsWithThis( - method: KFunction5, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, T1, T2, T3, T4, StaticsType, T, T1, T2, T3, T4, StaticsType) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, - arguments = ::withMutationsAndThis, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - //region checks substituting statics with symbolic variable or not - protected inline fun checkWithoutStaticsSubstitution( - method: KFunction1<*, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithoutStaticsSubstitution( - method: KFunction2<*, T, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T::class, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithoutStaticsSubstitution( - method: KFunction3<*, T1, T2, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithoutStaticsSubstitution( - method: KFunction4<*, T1, T2, T3, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - protected inline fun checkWithoutStaticsSubstitution( - method: KFunction5<*, T1, T2, T3, T4, R>, - branches: ExecutionsNumberMatcher, - vararg matchers: (T1, T2, T3, T4, R?) -> Boolean, - coverage: CoverageMatcher = Full, - mockStrategy: MockStrategyApi = NO_MOCKS, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) = internalCheck( - method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, - additionalDependencies = additionalDependencies, - summaryTextChecks = summaryTextChecks, - summaryNameChecks = summaryNameChecks, - summaryDisplayNameChecks = summaryDisplayNameChecks - ) - - //endregion - - fun checkAllCombinations(method: KFunction<*>) { - val failed = mutableListOf() - val succeeded = mutableListOf() - - allTestFrameworkConfigurations - .filterNot { it.isDisabled } - .forEach { config -> - runCatching { - internalCheckForCodeGeneration(method, config) - }.onFailure { - failed += config - }.onSuccess { - succeeded += config - } - } - - // TODO check that all generated classes have different content JIRA:1415 - - logger.info { "Total configurations: ${succeeded.size + failed.size}. Failed: ${failed.size}." } - - require(failed.isEmpty()) { - val separator = System.lineSeparator() - val failedConfigurations = failed.joinToString(prefix = separator, separator = separator) - - "Failed configurations: $failedConfigurations" - } - } - - @Suppress("ControlFlowWithEmptyBody", "UNUSED_VARIABLE") - private fun internalCheckForCodeGeneration( - method: KFunction<*>, - testFrameworkConfiguration: TestFrameworkConfiguration - ) { - withSettingsFromTestFrameworkConfiguration(testFrameworkConfiguration) { - with(testFrameworkConfiguration) { - - val utMethod = - UtMethod.from(method).also { computeAdditionalDependenciesClasspathAndBuildDir(it, emptyArray()) } - val utContext = UtContext(utMethod.clazz.java.classLoader) - - clearTempDirectory(daysLimitForTempFiles) - - withUtContext(utContext) { - val methodWithStrategy = - MethodWithMockStrategy(utMethod, mockStrategy, resetNonFinalFieldsAfterClinit) - - val (testCase, coverage) = analyzedMethods.getOrPut(methodWithStrategy) { - walk(utMethod, mockStrategy) - } - - if (checkCoverageInCodeGenerationTests) { - // TODO JIRA:1407 - } - - val testClass = testCase.method.clazz - val stageStatusCheck = StageStatusCheck( - firstStage = CodeGeneration, - lastStage = TestExecution, - status = ExecutionStatus.SUCCESS - ) - val classStages = listOf(ClassStages(testClass, stageStatusCheck, listOf(testCase))) - - BaseTestCodeGeneratorPipeline(testFrameworkConfiguration).runClassesCodeGenerationTests(classStages) - } - } - } - } - - inline fun internalCheck( - method: KFunction, - mockStrategy: MockStrategyApi, - branches: ExecutionsNumberMatcher, - matchers: Array>, - coverageMatcher: CoverageMatcher, - vararg classes: KClass<*>, - noinline arguments: (UtValueExecution<*>) -> List = ::withResult, - additionalDependencies: Array> = emptyArray(), - summaryTextChecks: List<(List?) -> Boolean> = listOf(), - summaryNameChecks: List<(String?) -> Boolean> = listOf(), - summaryDisplayNameChecks: List<(String?) -> Boolean> = listOf() - ) { - if (UtSettings.checkAllCombinationsForEveryTestInSamples) { - checkAllCombinations(method) - } - - val utMethod = UtMethod.from(method) - val additionalDependenciesClassPath = - computeAdditionalDependenciesClasspathAndBuildDir(utMethod, additionalDependencies) - - withUtContext(UtContext(utMethod.clazz.java.classLoader)) { - val (testCase, coverage) = if (coverageMatcher is DoNotCalculate) { - MethodResult(executions(utMethod, mockStrategy, additionalDependenciesClassPath), Coverage()) - } else { - walk(utMethod, mockStrategy, additionalDependenciesClassPath) - } - testCase.summarize(searchDirectory) - val valueTestCase = testCase.toValueTestCase(UtBotTestCaseGenerator.jimpleBody(utMethod)) - - assertTrue(testCase.errors.isEmpty()) { - "We have errors: ${ - testCase.errors.entries.map { "${it.value}: ${it.key}" }.prettify() - }" - } - - val valueExecutions = valueTestCase.executions - assertTrue(branches(valueExecutions.size)) { - "Branch count matcher '$branches' fails for ${valueExecutions.size}: ${valueExecutions.prettify()}" - } - - valueExecutions.checkTypes(R::class, classes.toList()) - - if (testSummary) { - valueExecutions.checkSummaryMatchers(summaryTextChecks) - valueExecutions.checkCommentsForBasicErrors() - } - if (testName) { - valueExecutions.checkNameMatchers(summaryNameChecks) - valueExecutions.checkNamesForBasicErrors() - } - if (testDisplayName) { - valueExecutions.checkDisplayNameMatchers(summaryDisplayNameChecks) - } - - valueExecutions.checkMatchers(matchers, arguments) - assertTrue(coverageMatcher(coverage)) { - "Coverage matcher '$coverageMatcher' fails for $coverage (at least: ${coverage.toAtLeast()})" - } - - processTestCase(testCase) - } - } - - fun List>.checkTypes( - resultType: KClass<*>, - argumentTypes: List> - ) { - for (execution in this) { - val typesWithArgs = argumentTypes.zip(execution.stateBefore.params.map { it.type }) - - assertTrue(typesWithArgs.all { it.second::class.isInstance(it.first::class) }) { "Param types do not match" } - - execution.returnValue.getOrNull()?.let { returnValue -> - assertTrue(resultType::class.isInstance(returnValue::class)) { "Return type does not match" } - } - } - } - - fun List>.checkMatchers( - matchers: Array>, - arguments: (UtValueExecution<*>) -> List - ) { - val notMatched = matchers.indices.filter { i -> - this.none { ex -> - runCatching { invokeMatcher(matchers[i], arguments(ex)) }.getOrDefault(false) - } - } - - val matchersNumbers = notMatched.map { it + 1 } - - assertTrue(notMatched.isEmpty()) { "Execution matchers $matchersNumbers not match to ${this.prettify()}" } - - // Uncomment if you want to check that each value matches at least one matcher. -// val notMatchedValues = this.filter { ex -> -// matchers.none { matcher -> -// runCatching { -// invokeMatcher(matcher, arguments(ex)) -// }.getOrDefault(false) -// } -// } -// assertTrue(notMatchedValues.isEmpty()) { "Values not match to matchers, ${notMatchedValues.prettify()}" } - } - - fun List>.checkSummaryMatchers(summaryTextChecks: List<(List?) -> Boolean>) { - val notMatched = summaryTextChecks.indices.filter { i -> - this.none { ex -> summaryTextChecks[i](ex.summary) } - } - assertTrue(notMatched.isEmpty()) { - "Summary matchers ${notMatched.map { it + 1 }} not match for \n${ - this.joinToString(separator = "\n\n") { - "Next summary start ".padEnd(50, '-') + "\n" + - prettifyDocStatementList(it.summary ?: emptyList()) - } - }" - } - } - - private fun prettifyDocStatementList(docStmts: List): String { - val flattenStmts = flattenDocStatements(docStmts) - return flattenStmts.joinToString(separator = "\n") { "${it.javaClass.simpleName.padEnd(20)} content:$it;" } - } - - fun List>.checkNameMatchers(nameTextChecks: List<(String?) -> Boolean>) { - val notMatched = nameTextChecks.indices.filter { i -> - this.none { execution -> nameTextChecks[i](execution.testMethodName) } - } - assertTrue(notMatched.isEmpty()) { - "Test method name matchers ${notMatched.map { it + 1 }} not match for ${map { it.testMethodName }.prettify()}" - } - } - - fun List>.checkDisplayNameMatchers(displayNameTextChecks: List<(String?) -> Boolean>) { - val notMatched = displayNameTextChecks.indices.filter { i -> - this.none { ex -> displayNameTextChecks[i](ex.displayName) } - } - assertTrue(notMatched.isEmpty()) { - "Test display name matchers ${notMatched.map { it + 1 }} not match for ${map { it.displayName }.prettify()}" - } - } - - fun List>.checkCommentsForBasicErrors() { - val emptyLines = this.filter { - it.summary?.contains("\n\n") ?: false - } - assertTrue(emptyLines.isEmpty()) { "Empty lines in the comments: ${emptyLines.map { it.summary }.prettify()}" } - } - - fun List>.checkNamesForBasicErrors() { - val wrongASTNodeConversion = this.filter { - it.testMethodName?.contains("null") ?: false - } - assertTrue(wrongASTNodeConversion.isEmpty()) { - "Null in AST node conversion in the names: ${wrongASTNodeConversion.map { it.testMethodName }.prettify()}" - } - } - - fun walk( - method: UtMethod<*>, - mockStrategy: MockStrategyApi, - additionalDependenciesClassPath: String = "" - ): MethodResult { - val testCase = executions(method, mockStrategy, additionalDependenciesClassPath) - val jimpleBody = UtBotTestCaseGenerator.jimpleBody(method) - val methodCoverage = methodCoverage( - method, - testCase.toValueTestCase(jimpleBody).executions, - buildDir.toString() + File.pathSeparator + additionalDependenciesClassPath - ) - return MethodResult(testCase, methodCoverage) - } - - fun executions( - method: UtMethod<*>, - mockStrategy: MockStrategyApi, - additionalDependenciesClassPath: String - ): UtTestCase { - UtBotTestCaseGenerator.init(buildDir, additionalDependenciesClassPath, System.getProperty("java.class.path")) - return UtBotTestCaseGenerator.generate(method, mockStrategy) - } - - fun executionsModel( - method: UtMethod<*>, - mockStrategy: MockStrategyApi, - additionalDependencies: Array> = emptyArray() - ): UtTestCase { - val additionalDependenciesClassPath = - computeAdditionalDependenciesClasspathAndBuildDir(method, additionalDependencies) - UtBotTestCaseGenerator.init(buildDir, additionalDependenciesClassPath, System.getProperty("java.class.path")) - withUtContext(UtContext(method.clazz.java.classLoader)) { - return UtBotTestCaseGenerator.generate(method, mockStrategy) - } - } - - companion object { - private var previousClassLocation: ClassLocation? = null - private lateinit var buildDir: Path - - fun computeAdditionalDependenciesClasspathAndBuildDir( - utMethod: UtMethod<*>, - additionalDependencies: Array> - ): String { - val additionalDependenciesClassPath = additionalDependencies - .map { locateClass(it) } - .joinToString(File.pathSeparator) { findPathToClassFiles(it).toAbsolutePath().toString() } - val classLocation = locateClass(utMethod.clazz) - if (classLocation != previousClassLocation) { - buildDir = findPathToClassFiles(classLocation) - previousClassLocation = classLocation - } - return additionalDependenciesClassPath - } - } - - data class MethodWithMockStrategy( - val method: UtMethod<*>, - val mockStrategy: MockStrategyApi, - val substituteStatics: Boolean - ) - - data class MethodResult(val testCase: UtTestCase, val coverage: Coverage) -} - -@Suppress("UNCHECKED_CAST") -// TODO please use matcher.reflect().call(...) when it will be ready, currently call isn't supported in kotlin reflect -internal fun invokeMatcher(matcher: Function, params: List) = when (matcher) { - is Function1<*, *> -> (matcher as Function1).invoke(params[0]) - is Function2<*, *, *> -> (matcher as Function2).invoke(params[0], params[1]) - is Function3<*, *, *, *> -> (matcher as Function3).invoke( - params[0], params[1], params[2] - ) - is Function4<*, *, *, *, *> -> (matcher as Function4).invoke( - params[0], params[1], params[2], params[3] - ) - is Function5<*, *, *, *, *, *> -> (matcher as Function5).invoke( - params[0], params[1], params[2], params[3], params[4], - ) - is Function6<*, *, *, *, *, *, *> -> (matcher as Function6).invoke( - params[0], params[1], params[2], params[3], params[4], params[5], - ) - is Function7<*, *, *, *, *, *, *, *> -> (matcher as Function7).invoke( - params[0], params[1], params[2], params[3], params[4], params[5], params[6], - ) - else -> error("Function with arity > 7 not supported") -} - -fun ge(count: Int) = ExecutionsNumberMatcher("ge $count") { it >= count } -fun eq(count: Int) = ExecutionsNumberMatcher("eq $count") { it == count } -fun between(bounds: IntRange) = ExecutionsNumberMatcher("$bounds") { it in bounds } -val ignoreExecutionsNumber = ExecutionsNumberMatcher("Do not calculate") { it > 0 } - -fun atLeast(percents: Int) = AtLeast(percents) - -fun signatureOf(function: Function<*>): String { - function as KFunction - return function.executableId.signature -} - -fun MockInfo.mocksMethod(method: Function<*>) = signatureOf(method) == this.method.signature - -fun List.singleMockOrNull(field: String, method: Function<*>): MockInfo? = - singleOrNull { (it.mock as? FieldMockTarget)?.field == field && it.mocksMethod(method) } - -fun List.singleMock(field: String, method: Function<*>): MockInfo = - single { (it.mock as? FieldMockTarget)?.field == field && it.mocksMethod(method) } - -fun List.singleMock(id: MockId, method: Function<*>): MockInfo = - single { (it.mock as? ObjectMockTarget)?.id == id && it.mocksMethod(method) } - -inline fun MockInfo.value(index: Int = 0): T = - when (val value = (values[index] as? UtConcreteValue<*>)?.value) { - is T -> value - else -> error("Unsupported type: ${values[index]}") - } - -fun MockInfo.mockValue(index: Int = 0): UtMockValue = values[index] as UtMockValue - -fun MockInfo.isParameter(num: Int): Boolean = (mock as? ParameterMockTarget)?.index == num - -inline fun Result<*>.isException(): Boolean = exceptionOrNull() is T - -inline fun UtCompositeModel.mockValues(methodName: String): List = - mocks.filterKeys { it.name == methodName }.values.flatten().map { it.primitiveValue() } - -inline fun UtModel.primitiveValue(): T = - (this as? UtPrimitiveModel)?.value as? T ?: error("Can't transform $this to ${T::class}") - -class ExecutionsNumberMatcher(private val description: String, private val cmp: (Int) -> Boolean) { - operator fun invoke(x: Int) = cmp(x) - override fun toString() = description -} - -sealed class CoverageMatcher(private val description: String, private val cmp: (Coverage) -> Boolean) { - operator fun invoke(c: Coverage) = cmp(c) - override fun toString() = description -} - -object Full : CoverageMatcher("full coverage", { it.counters.all { it.total == it.covered } }) - -class AtLeast(percents: Int) : CoverageMatcher("at least $percents% coverage", - { it.counters.all { 100 * it.covered >= percents * it.total } }) - -object DoNotCalculate : CoverageMatcher("Do not calculate", { true }) - -class FullWithAssumptions(assumeCallsNumber: Int) : CoverageMatcher( - "full coverage except failed assume calls", - { it.instructionCounter.let { it.covered >= it.total - assumeCallsNumber } } -) { - init { - require(assumeCallsNumber > 0) { - "Non-positive number of assume calls $assumeCallsNumber passed (for zero calls use Full coverage matcher" - } - } -} - -// simple matchers -fun withResult(ex: UtValueExecution<*>) = ex.paramsBefore + ex.evaluatedResult -fun withException(ex: UtValueExecution<*>) = ex.paramsBefore + ex.returnValue -fun withStaticsBefore(ex: UtValueExecution<*>) = ex.paramsBefore + ex.staticsBefore + ex.evaluatedResult -fun withStaticsAfter(ex: UtValueExecution<*>) = ex.paramsBefore + ex.staticsAfter + ex.evaluatedResult -fun withThisAndStaticsAfter(ex: UtValueExecution<*>) = listOf(ex.callerBefore) + ex.paramsBefore + ex.staticsAfter + ex.evaluatedResult -fun withThisAndResult(ex: UtValueExecution<*>) = listOf(ex.callerBefore) + ex.paramsBefore + ex.evaluatedResult -fun withThisStaticsBeforeAndResult(ex: UtValueExecution<*>) = - listOf(ex.callerBefore) + ex.paramsBefore + ex.staticsBefore + ex.evaluatedResult - -fun withThisAndException(ex: UtValueExecution<*>) = listOf(ex.callerBefore) + ex.paramsBefore + ex.returnValue -fun withMocks(ex: UtValueExecution<*>) = ex.paramsBefore + listOf(ex.mocks) + ex.evaluatedResult -fun withMocksAndInstrumentation(ex: UtValueExecution<*>) = - ex.paramsBefore + listOf(ex.mocks) + listOf(ex.instrumentation) + ex.evaluatedResult - -fun withMocksInstrumentationAndThis(ex: UtValueExecution<*>) = - listOf(ex.callerBefore) + ex.paramsBefore + listOf(ex.mocks) + listOf(ex.instrumentation) + ex.evaluatedResult - -// mutations -fun withParamsMutations(ex: UtValueExecution<*>) = ex.paramsBefore + ex.paramsAfter -fun withMutations(ex: UtValueExecution<*>) = ex.paramsBefore + ex.staticsBefore + ex.paramsAfter + ex.staticsAfter -fun withParamsMutationsAndResult(ex: UtValueExecution<*>) = ex.paramsBefore + ex.paramsAfter + ex.evaluatedResult -fun withMutationsAndResult(ex: UtValueExecution<*>) = - ex.paramsBefore + ex.staticsBefore + ex.paramsAfter + ex.staticsAfter + ex.evaluatedResult - -fun withMutationsAndThis(ex: UtValueExecution<*>) = - mutableListOf().apply { - add(ex.callerBefore) - addAll(ex.paramsBefore) - add(ex.staticsBefore) - - add(ex.callerAfter) - addAll(ex.paramsAfter) - add(ex.staticsAfter) - - add(ex.returnValue) - } - -private val UtValueExecution<*>.callerBefore get() = stateBefore.caller!!.value -private val UtValueExecution<*>.paramsBefore get() = stateBefore.params.map { it.value } -private val UtValueExecution<*>.staticsBefore get() = stateBefore.statics - -private val UtValueExecution<*>.callerAfter get() = stateAfter.caller!!.value -private val UtValueExecution<*>.paramsAfter get() = stateAfter.params.map { it.value } -private val UtValueExecution<*>.staticsAfter get() = stateAfter.statics - -private val UtValueExecution<*>.evaluatedResult get() = returnValue.getOrNull() - -fun keyContain(vararg keys: String) = { summary: String? -> - if (summary != null) { - keys.all { it in summary } - } else false -} - -fun keyMatch(keyText: String) = { summary: String? -> - keyText == summary -} - - -private fun flattenDocStatements(summary: List): List { - val flatten = mutableListOf() - for (s in summary) { - when (s) { - is DocPreTagStatement -> flatten.addAll(flattenDocStatements(s.content)) - is DocClassLinkStmt -> flatten.add(s) - is DocMethodLinkStmt -> flatten.add(s) - is DocCodeStmt -> flatten.add(s) - is DocRegularStmt -> flatten.add(s) - } - } - return flatten -} - -fun keyContain(vararg keys: DocStatement) = { summary: List? -> - summary?.let { keys.all { key -> key in flattenDocStatements(it) } } ?: false -} - -fun keyMatch(keyStmt: List) = { summary: List? -> - summary?.let { keyStmt == summary } ?: false -} - - -fun Map>.findByName(name: String) = entries.single { name == it.key.name }.value.value -fun Map>.singleValue() = values.single().value - -internal typealias StaticsType = Map> -private typealias Mocks = List -private typealias Instrumentation = List - -inline fun withoutConcrete(block: () -> T): T { - val prev = UtSettings.useConcreteExecution - UtSettings.useConcreteExecution = false - try { - return block() - } finally { - UtSettings.useConcreteExecution = prev - } -} - -inline fun withSolverTimeoutInMillis(timeoutInMillis: Int, block: () -> T): T { - val prev = UtSettings.checkSolverTimeoutMillis - UtSettings.checkSolverTimeoutMillis = timeoutInMillis - try { - return block() - } finally { - UtSettings.checkSolverTimeoutMillis = prev - } -} - -inline fun withoutMinimization(block: () -> T): T { - val prev = UtSettings.testMinimizationStrategyType - UtSettings.testMinimizationStrategyType = TestSelectionStrategyType.DO_NOT_MINIMIZE_STRATEGY - try { - return block() - } finally { - UtSettings.testMinimizationStrategyType = prev - } -} - -inline fun withSettingsFromTestFrameworkConfiguration( - config: TestFrameworkConfiguration, - block: () -> T -): T { - val substituteStaticsWithSymbolicVariable = UtSettings.substituteStaticsWithSymbolicVariable - UtSettings.substituteStaticsWithSymbolicVariable = config.resetNonFinalFieldsAfterClinit - try { - return block() - } finally { - UtSettings.substituteStaticsWithSymbolicVariable = substituteStaticsWithSymbolicVariable - } -} - -inline fun withoutSubstituteStaticsWithSymbolicVariable(block: () -> T) { - val substituteStaticsWithSymbolicVariable = UtSettings.substituteStaticsWithSymbolicVariable - UtSettings.substituteStaticsWithSymbolicVariable = false - try { - block() - } finally { - UtSettings.substituteStaticsWithSymbolicVariable = substituteStaticsWithSymbolicVariable - } -} - -inline fun withPushingStateFromPathSelectorForConcrete(block: () -> T): T { - val prev = UtSettings.saveRemainingStatesForConcreteExecution - UtSettings.saveRemainingStatesForConcreteExecution = true - try { - return block() - } finally { - UtSettings.saveRemainingStatesForConcreteExecution = prev - } -} - -inline fun withTreatingOverflowAsError(block: () -> T): T { - val prev = UtSettings.treatOverflowAsError - UtSettings.treatOverflowAsError = true - try { - return block() - } finally { - UtSettings.treatOverflowAsError = prev - } -} - -inline fun withRewardModelPath(rewardModelPath: String, block: () -> T): T { - val prev = UtSettings.rewardModelPath - UtSettings.rewardModelPath = rewardModelPath - try { - return block() - } finally { - UtSettings.rewardModelPath = prev - } -} - -inline fun withPathSelectorType(pathSelectorType: PathSelectorType, block: () -> T): T { - val prev = UtSettings.pathSelectorType - UtSettings.pathSelectorType = pathSelectorType - try { - return block() - } finally { - UtSettings.pathSelectorType = prev - } -} - -inline fun withFeaturePath(featurePath: String, block: () -> T): T { - val prevFeaturePath = UtSettings.featurePath - val prevEnableFeatureProcess = UtSettings.enableFeatureProcess - - UtSettings.featurePath = featurePath - UtSettings.enableFeatureProcess = true - - try { - return block() - } finally { - UtSettings.featurePath = prevFeaturePath - UtSettings.enableFeatureProcess = prevEnableFeatureProcess - } -} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/CodeTestCaseGeneratorTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/CodeTestCaseGeneratorTest.kt deleted file mode 100644 index 900aa0d896..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/CodeTestCaseGeneratorTest.kt +++ /dev/null @@ -1,196 +0,0 @@ -package org.utbot.examples - -import org.utbot.common.FileUtil -import org.utbot.common.packageName -import org.utbot.common.withAccessibility -import org.utbot.framework.UtSettings -import org.utbot.framework.codegen.BaseTestCodeGeneratorPipeline.Companion.defaultCodegenPipeline -import org.utbot.framework.codegen.ClassStages -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.codegen.ExecutionStatus -import org.utbot.framework.codegen.Stage -import org.utbot.framework.codegen.StageStatusCheck -import org.utbot.framework.codegen.TestExecution -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.instrumentation.ConcreteExecutor -import kotlin.reflect.KClass -import mu.KotlinLogging -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.MethodOrderer -import org.junit.jupiter.api.Order -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.TestInfo -import org.junit.jupiter.api.TestInstance -import org.junit.jupiter.api.TestMethodOrder -import org.junit.jupiter.api.extension.BeforeAllCallback -import org.junit.jupiter.api.extension.ExtendWith -import org.junit.jupiter.api.extension.ExtensionContext -import org.junit.jupiter.api.fail -import org.junit.jupiter.engine.descriptor.ClassTestDescriptor -import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -@TestMethodOrder(MethodOrderer.OrderAnnotation::class) -@ExtendWith(CodeTestCaseGeneratorTest.Companion.ReadRunningTestsNumberBeforeAllTestsCallback::class) -abstract class CodeTestCaseGeneratorTest( - private val testClass: KClass<*>, - private var testCodeGeneration: Boolean = true, - private val languagesLastStages: List = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN) - ) -) { - private val testCases: MutableList = arrayListOf() - - data class CodeGenerationLanguageLastStage(val language: CodegenLanguage, val lastStage: Stage = TestExecution) - - fun processTestCase(testCase: UtTestCase) { - if (testCodeGeneration) testCases += testCase - } - - protected fun withEnabledTestingCodeGeneration(testCodeGeneration: Boolean, block: () -> Unit) { - val prev = this.testCodeGeneration - - try { - this.testCodeGeneration = testCodeGeneration - block() - } finally { - this.testCodeGeneration = prev - } - } - - // save all generated test cases from current class to test code generation - private fun addTestCase(pkg: Package) { - if (testCodeGeneration) { - packageResult.getOrPut(pkg) { mutableListOf() } += CodeGenerationTestCases( - testClass, - testCases, - languagesLastStages - ) - } - } - - private fun cleanAfterProcessingPackage(pkg: Package) { - // clean test cases after cur package processing - packageResult[pkg]?.clear() - - // clean cur package test classes info - testClassesAmountByPackage[pkg] = 0 - processedTestClassesAmountByPackage[pkg] = 0 - } - - @Test - @Order(Int.MAX_VALUE) - fun processTestCases(testInfo: TestInfo) { - val pkg = testInfo.testClass.get().`package` - addTestCase(pkg) - - // check if tests are inside package and current test is not the last one - if (runningTestsNumber > 1 && !isPackageFullyProcessed(testInfo.testClass.get())) { - logger.info("Package $pkg is not fully processed yet, code generation will be tested later") - return - } - ConcreteExecutor.defaultPool.close() - - FileUtil.clearTempDirectory(UtSettings.daysLimitForTempFiles) - - val result = packageResult[pkg] ?: return - try { - val pipelineErrors = mutableListOf() - languages.map { language -> - try { - // choose all test cases that should be tested with current language - val resultsWithCurLanguage = result.filter { codeGenerationTestCases -> - codeGenerationTestCases.languagePipelines.any { it.language == language } - } - - // for each test class choose code generation pipeline stages - val classStages = resultsWithCurLanguage.map { codeGenerationTestCases -> - ClassStages( - codeGenerationTestCases.testClass, - StageStatusCheck( - firstStage = CodeGeneration, - lastStage = codeGenerationTestCases.languagePipelines.single { it.language == language }.lastStage, - status = ExecutionStatus.SUCCESS - ), - codeGenerationTestCases.testCases - ) - } - - language.defaultCodegenPipeline.runClassesCodeGenerationTests(classStages) - } catch (e: RuntimeException) { - pipelineErrors.add(e.message) - } - } - - if (pipelineErrors.isNotEmpty()) - fail { pipelineErrors.joinToString(System.lineSeparator()) } - } finally { - cleanAfterProcessingPackage(pkg) - } - } - - companion object { - - init { - // trigger old temporary file deletion - FileUtil.OldTempFileDeleter - } - - private val packageResult: MutableMap> = mutableMapOf() - - private var allRunningTestClasses: List = mutableListOf() - - private val languages = listOf(CodegenLanguage.JAVA, CodegenLanguage.KOTLIN) - - data class CodeGenerationTestCases( - val testClass: KClass<*>, - val testCases: List, - val languagePipelines: List - ) - - class ReadRunningTestsNumberBeforeAllTestsCallback : BeforeAllCallback { - override fun beforeAll(extensionContext: ExtensionContext) { - val clazz = Class.forName("org.junit.jupiter.engine.descriptor.AbstractExtensionContext") - val field = clazz.getDeclaredField("testDescriptor") - runningTestsNumber = field.withAccessibility { - val testDescriptor = field.get(extensionContext.parent.get()) - // get all running tests and filter disabled - allRunningTestClasses = (testDescriptor as JupiterEngineDescriptor).children - .map { it as ClassTestDescriptor } - .filter { it.testClass.getAnnotation(Disabled::class.java) == null } - .toList() - allRunningTestClasses.size - } - } - } - - private var processedTestClassesAmountByPackage: MutableMap = mutableMapOf() - private var testClassesAmountByPackage: MutableMap = mutableMapOf() - - private var runningTestsNumber: Int = 0 - - private val logger = KotlinLogging.logger { } - - private fun getTestPackageSize(packageName: String): Int = - // filter all not disabled tests classes - allRunningTestClasses - .filter { it.testClass.packageName == packageName } - .distinctBy { it.testClass.name.substringBeforeLast("Kt") } - .size - - private fun isPackageFullyProcessed(testClass: Class<*>): Boolean { - val currentPackage = testClass.`package` - - if (currentPackage !in testClassesAmountByPackage) - testClassesAmountByPackage[currentPackage] = getTestPackageSize(currentPackage.name) - - processedTestClassesAmountByPackage.merge(currentPackage, 1, Int::plus) - - val processed = processedTestClassesAmountByPackage[currentPackage]!! - val total = testClassesAmountByPackage[currentPackage]!! - return processed == total - } - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/TestUtil.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/TestUtil.kt deleted file mode 100644 index af3674a2bb..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/TestUtil.kt +++ /dev/null @@ -1,90 +0,0 @@ -package org.utbot.examples - -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.Junit4 -import org.utbot.framework.codegen.MockitoStaticMocking -import org.utbot.framework.codegen.NoStaticMocking -import org.utbot.framework.codegen.ParametrizedTestSource -import org.utbot.framework.codegen.RuntimeExceptionTestsBehaviour -import org.utbot.framework.codegen.StaticsMocking -import org.utbot.framework.codegen.TestFramework -import org.utbot.framework.codegen.TestNg -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.MockStrategyApi.NO_MOCKS -import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_CLASSES - - -data class TestFrameworkConfiguration( - val testFramework: TestFramework, - val mockFramework: MockFramework, - val mockStrategy: MockStrategyApi, - val staticsMocking: StaticsMocking, - val parametrizedTestSource: ParametrizedTestSource, - val codegenLanguage: CodegenLanguage, - val forceStaticMocking: ForceStaticMocking, - val resetNonFinalFieldsAfterClinit: Boolean = true, - val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.PASS, - val enableTestsTimeout: Boolean = false // our tests should not fail due to timeout -) { - val isDisabled: Boolean - get() = run { - // TODO Any? JIRA:1366 - if (codegenLanguage == CodegenLanguage.KOTLIN) return true - - // TODO a problem with try-with-resources JIRA:1329 - if (codegenLanguage == CodegenLanguage.KOTLIN && staticsMocking == MockitoStaticMocking) return true - - // TODO There is no assertArrayEquals JIRA:1416 - if (testFramework == TestNg) return true - - // because otherwise the code generator will not create mocks even for mandatory to mock classes - if (forceStaticMocking == ForceStaticMocking.FORCE && staticsMocking == NoStaticMocking) return true - - // TODO find if mocks are used during the analysis JIRA:1418 - if (parametrizedTestSource == ParametrizedTestSource.PARAMETRIZE) return true - - // junit4 doesn't support parametrized tests - if (testFramework == Junit4 && parametrizedTestSource == ParametrizedTestSource.PARAMETRIZE) return true - - // if we want to generate mocks for every class but CUT, we must have specified staticsMocking - if (mockStrategy == OTHER_CLASSES && staticsMocking == NoStaticMocking) return true - - return false - } -} - -val allTestFrameworkConfigurations: List = run { - val possibleConfiguration = mutableListOf() - - for (mockStrategy in listOf(NO_MOCKS, OTHER_CLASSES)) { - for (testFramework in TestFramework.allItems) { - val mockFramework = MockFramework.MOCKITO - val forceStaticMocking = ForceStaticMocking.FORCE - - for (staticsMocking in StaticsMocking.allItems) { - for (parametrizedTestSource in ParametrizedTestSource.allItems) { - for (codegenLanguage in CodegenLanguage.allItems) { - // We should not reset values for non-final static fields in parameterized tests - val resetNonFinalFieldsAfterClinit = - parametrizedTestSource == ParametrizedTestSource.DO_NOT_PARAMETRIZE - - possibleConfiguration += TestFrameworkConfiguration( - testFramework, - mockFramework, - mockStrategy, - staticsMocking, - parametrizedTestSource, - codegenLanguage, - forceStaticMocking, - resetNonFinalFieldsAfterClinit - ) - } - } - } - } - } - - possibleConfiguration.toList() -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/algorithms/BinarySearchTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/algorithms/BinarySearchTest.kt deleted file mode 100644 index e3e36855f1..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/algorithms/BinarySearchTest.kt +++ /dev/null @@ -1,97 +0,0 @@ -package org.utbot.examples.algorithms - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.framework.plugin.api.DocCodeStmt -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.DocStatement -import org.junit.jupiter.api.Test - -class BinarySearchTest : AbstractTestCaseGeneratorTest(testClass = BinarySearch::class,) { - @Test - fun testLeftBinarySearch() { - val fullSummary = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("does not iterate "), - DocCodeStmt("while(left < right - 1)"), - DocRegularStmt(", "), - DocRegularStmt("executes conditions:\n"), - DocRegularStmt(" "), - DocCodeStmt("(found): False"), - DocRegularStmt("\n"), - DocRegularStmt("returns from: "), - DocCodeStmt("return -1;"), - DocRegularStmt("\n") - - ) - ) - ) - checkWithException( - BinarySearch::leftBinSearch, - ignoreExecutionsNumber, - { a, _, r -> a == null && r.isException() }, - { a, _, r -> a.size >= 2 && a[0] > a[1] && r.isException() }, - { a, _, r -> a.isEmpty() && r.getOrNull() == -1 }, - { a, key, r -> a.isNotEmpty() && key >= a[(a.size - 1) / 2] && key !in a && r.getOrNull() == -1 }, - { a, key, r -> a.isNotEmpty() && key in a && r.getOrNull() == a.indexOfFirst { it == key } + 1 }, - // TODO enable it JIRA:1442 - /* - summaryTextChecks = listOf( - keyContain(DocCodeStmt("(found): False")), - keyContain(DocCodeStmt("(found): True")), - keyContain(DocRegularStmt(" BinarySearch::isUnsorted once")), - keyContain(DocRegularStmt("throws NullPointerException in: isUnsorted(array)")), - keyContain(DocRegularStmt("throws IllegalArgumentException after condition: isUnsorted(array)")), - keyContain(DocCodeStmt("(array[middle] < key): True")), - keyContain(DocCodeStmt("(array[middle] == key): True")), - keyContain(DocCodeStmt("(array[middle] < key): True")), - keyMatch(fullSummary) - ), - summaryNameChecks = listOf( - keyContain("testLeftBinSearch_BinarySearchIsUnsorted"), - keyContain("testLeftBinSearch_ThrowIllegalArgumentException"), - keyContain("testLeftBinSearch_NotFound"), - keyContain("testLeftBinSearch_MiddleOfArrayLessThanKey"), - keyContain("testLeftBinSearch_Found") - ), - summaryDisplayNameChecks = listOf( - keyMatch("isUnsorted(array) -> ThrowIllegalArgumentException"), - keyMatch("isUnsorted(array) -> ThrowIllegalArgumentException"), - keyMatch("found : False -> return -1"), - keyMatch("array[middle] == key : True -> return right + 1"), - keyMatch("array[middle] < key : True -> return -1") - ) - */ - ) - } - - @Test - fun testRightBinarySearch() { - checkWithException( - BinarySearch::rightBinSearch, - ignoreExecutionsNumber, - { a, _, r -> a == null && r.isException() }, - { a, _, r -> a.isEmpty() && r.getOrNull() == -1 }, - { a, _, r -> a.size >= 2 && a[0] > a[1] && r.isException() }, - { a, key, r -> a.isNotEmpty() && key !in a && r.getOrNull() == -1 }, - { a, key, r -> a.isNotEmpty() && key in a && r.getOrNull() == a.indexOfLast { it == key } + 1 } - ) - } - - @Test - fun testDefaultBinarySearch() { - checkWithException( - BinarySearch::defaultBinarySearch, - ignoreExecutionsNumber, - { a, _, r -> a == null && r.isException() }, - { a, _, r -> a.isEmpty() && r.getOrNull() == -1 }, - { a, _, r -> a.size >= 2 && a[0] > a[1] && r.isException() }, - { a, key, r -> a.isNotEmpty() && key < a.first() && r.getOrNull() == a.binarySearch(key) }, - { a, key, r -> a.isNotEmpty() && key == a.first() && r.getOrNull() == a.binarySearch(key) }, - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/algorithms/CorrectBracketSequencesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/algorithms/CorrectBracketSequencesTest.kt deleted file mode 100644 index 0b8d288089..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/algorithms/CorrectBracketSequencesTest.kt +++ /dev/null @@ -1,152 +0,0 @@ -package org.utbot.examples.algorithms - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.algorithms.CorrectBracketSequences.isBracket -import org.utbot.examples.algorithms.CorrectBracketSequences.isOpen -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.examples.keyMatch -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.DocCodeStmt -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.junit.jupiter.api.Test - -internal class CorrectBracketSequencesTest : AbstractTestCaseGeneratorTest( - testClass = CorrectBracketSequences::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) // TODO generics in lists - ) -) { - @Test - fun testIsOpen() { - val isOpenSummary = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("returns from: "), - DocCodeStmt("return a == '(' || a == '{' || a == '[';"), - DocRegularStmt("\n") - ) - ) - ) - - checkStaticMethod( - CorrectBracketSequences::isOpen, - eq(4), - { c, r -> c == '(' && r == true }, - { c, r -> c == '{' && r == true }, - { c, r -> c == '[' && r == true }, - { c, r -> c !in "({[".toList() && r == false }, - summaryNameChecks = listOf( - keyMatch("testIsOpen_AEqualsCharOrAEqualsCharOrAEqualsChar"), - keyMatch("testIsOpen_ANotEqualsCharOrANotEqualsCharOrANotEqualsChar") - ), - summaryDisplayNameChecks = listOf( - keyMatch("return a == '(' || a == '{' || a == '[' : False -> return a == '(' || a == '{' || a == '['"), - keyMatch("return a == '(' || a == '{' || a == '[' : True -> return a == '(' || a == '{' || a == '['") - ), - summaryTextChecks = listOf( - keyMatch(isOpenSummary) - ) - ) - } - - @Test - fun testIsBracket() { - val isBracketSummary = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("returns from: "), - DocCodeStmt("return isOpen(a) || a == ')' || a == '}' || a == ']';"), - DocRegularStmt("\n") - ) - ) - ) - checkStaticMethod( - CorrectBracketSequences::isBracket, - eq(7), - { c, r -> c == '(' && r == true }, - { c, r -> c == '{' && r == true }, - { c, r -> c == '[' && r == true }, - { c, r -> c == ')' && r == true }, - { c, r -> c == '}' && r == true }, - { c, r -> c == ']' && r == true }, - { c, r -> c !in "(){}[]".toList() && r == false }, - summaryNameChecks = listOf( - keyMatch("testIsBracket_IsOpenOrANotEqualsCharOrANotEqualsCharOrANotEqualsChar"), - keyMatch("testIsBracket_IsOpenOrAEqualsCharOrAEqualsCharOrAEqualsChar") - ), - summaryDisplayNameChecks = listOf( - keyMatch("return isOpen(a) || a == ')' || a == '}' || a == ']' : False -> return isOpen(a) || a == ')' || a == '}' || a == ']'"), - keyMatch("return isOpen(a) || a == ')' || a == '}' || a == ']' : True -> return isOpen(a) || a == ')' || a == '}' || a == ']'") - ), - summaryTextChecks = listOf( - keyMatch(isBracketSummary) - ) - ) - } - - @Test - fun testIsTheSameType() { - val isTheSameTypeSummary = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("returns from: "), - DocCodeStmt("return a == '(' && b == ')' || a == '{' && b == '}' || a == '[' && b == ']';"), - DocRegularStmt("\n") - ) - ) - ) - checkStaticMethod( - CorrectBracketSequences::isTheSameType, - ignoreExecutionsNumber, - { a, b, r -> a == '(' && b == ')' && r == true }, - { a, b, r -> a == '{' && b == '}' && r == true }, - { a, b, r -> a == '[' && b == ']' && r == true }, - { a, b, r -> a == '(' && b != ')' && r == false }, - { a, b, r -> a == '{' && b != '}' && r == false }, - { a, b, r -> a == '[' && b != ']' && r == false }, - { a, b, r -> (a != '(' || b != ')') && (a != '{' || b != '}') && (a != '[' || b != ']') && r == false }, - summaryNameChecks = listOf( - keyMatch("testIsTheSameType_ANotEqualsCharAndBNotEqualsCharOrANotEqualsCharAndBNotEqualsCharOrANotEqualsCharAndBNotEqualsChar"), - keyMatch("testIsTheSameType_AEqualsCharAndBEqualsCharOrAEqualsCharAndBEqualsCharOrAEqualsCharAndBEqualsChar"), - ), - summaryDisplayNameChecks = listOf( - keyMatch("return a == '(' && b == ')' || a == '{' && b == '}' || a == '[' && b == ']' : False -> return a == '(' && b == ')' || a == '{' && b == '}' || a == '[' && b == ']'"), - keyMatch("return a == '(' && b == ')' || a == '{' && b == '}' || a == '[' && b == ']' : True -> return a == '(' && b == ')' || a == '{' && b == '}' || a == '[' && b == ']'") - ), - summaryTextChecks = listOf( - keyMatch(isTheSameTypeSummary) - ) - ) - } - - @Test - fun testIsCbs() { - val method = CorrectBracketSequences::isCbs - checkStaticMethodWithException( - method, - ignoreExecutionsNumber, - { chars, r -> chars == null && r.isException() }, - { chars, r -> chars != null && chars.isEmpty() && r.getOrNull() == true }, - { chars, r -> chars.any { it == null } && r.isException() }, - { chars, r -> !isBracket(chars.first()) && r.getOrNull() == false }, - { chars, r -> !isOpen(chars.first()) && r.getOrNull() == false }, - { chars, _ -> isOpen(chars.first()) }, - { chars, r -> chars.all { isOpen(it) } && r.getOrNull() == false }, - { chars, _ -> - val charsWithoutFirstOpenBrackets = chars.dropWhile { isOpen(it) } - val firstNotOpenBracketChar = charsWithoutFirstOpenBrackets.first() - - isBracket(firstNotOpenBracketChar) && !isOpen(firstNotOpenBracketChar) - }, - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/algorithms/GraphTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/algorithms/GraphTest.kt deleted file mode 100644 index 3a7fadcb7b..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/algorithms/GraphTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.utbot.examples.algorithms - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.junit.jupiter.api.Tag -import org.junit.jupiter.api.Test - -internal class GraphTest : AbstractTestCaseGeneratorTest(testClass = GraphExample::class) { - @Test - @Tag("slow") - fun testRunFindCycle() { - check( - GraphExample::runFindCycle, - eq(1), - ) - } - - @Test - fun testDijkstra() { - check( - GraphExample::runDijkstra, - eq(1) - ) - } - - /** - * TODO: fix Dijkstra algorithm. - */ - @Test - fun testRunDijkstraWithParameter() { - checkWithException( - GraphExample::runDijkstraWithParameter, - ignoreExecutionsNumber, - { g, r -> g == null && r.isException() }, - { g, r -> g.isEmpty() && r.isException() }, - { g, r -> g.size == 1 && r.getOrNull()?.size == 1 && r.getOrNull()?.first() == 0 }, - { g, r -> g.size > 1 && g[1] == null && r.isException() }, - { g, r -> g.isNotEmpty() && g.size != g.first().size && r.isException() }, - { g, r -> - val concreteResult = GraphExample().runDijkstraWithParameter(g) - g.isNotEmpty() && r.getOrNull().contentEquals(concreteResult) - } - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/algorithms/SortTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/algorithms/SortTest.kt deleted file mode 100644 index 7424258430..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/algorithms/SortTest.kt +++ /dev/null @@ -1,178 +0,0 @@ -package org.utbot.examples.algorithms - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.examples.keyMatch -import org.utbot.framework.plugin.api.DocCodeStmt -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.MockStrategyApi -import org.junit.jupiter.api.Test - -internal class SortTest : AbstractTestCaseGeneratorTest(testClass = Sort::class) { - @Test - fun testQuickSort() { - check( - Sort::quickSort, - ignoreExecutionsNumber, - mockStrategy = MockStrategyApi.OTHER_PACKAGES - ) - } - - @Test - fun testSwap() { - checkWithException( - Sort::swap, - ge(4), - { a, _, _, r -> a == null && r.isException() }, - { a, i, _, r -> a != null && (i < 0 || i >= a.size) && r.isException() }, - { a, i, j, r -> a != null && i in a.indices && (j < 0 || j >= a.size) && r.isException() }, - { a, i, j, _ -> a != null && i in a.indices && j in a.indices } - ) - } - - @Test - fun testArrayCopy() { - check( - Sort::arrayCopy, - eq(1), - { r -> r contentEquals intArrayOf(1, 2, 3) } - ) - } - - @Test - fun testMergeSort() { - check( - Sort::mergeSort, - eq(4), - { a, r -> a == null && r == null }, - { a, r -> a != null && r != null && a.size < 2 }, - { a, r -> - require(a is IntArray && r is IntArray) - - val sortedConstraint = a.size >= 2 && a.sorted() == r.toList() - - val maxInLeftHalf = a.slice(0 until a.size / 2).maxOrNull()!! - val maxInRightHalf = a.slice(a.size / 2 until a.size).maxOrNull()!! - - sortedConstraint && maxInLeftHalf >= maxInRightHalf - }, - { a, r -> - require(a is IntArray && r is IntArray) - - val sortedConstraint = a.size >= 2 && a.sorted() == r.toList() - - val maxInLeftHalf = a.slice(0 until a.size / 2).maxOrNull()!! - val maxInRightHalf = a.slice(a.size / 2 until a.size).maxOrNull()!! - - sortedConstraint && maxInLeftHalf < maxInRightHalf - }, - ) - } - - @Test - fun testMerge() { - checkWithException( - Sort::merge, - eq(6), - { lhs, _, r -> lhs == null && r.isException() }, - { lhs, rhs, r -> lhs != null && lhs.isEmpty() && r.getOrNull() contentEquals rhs }, - { lhs, rhs, _ -> lhs != null && lhs.isNotEmpty() && rhs == null }, - { lhs, rhs, r -> - val lhsCondition = lhs != null && lhs.isNotEmpty() - val rhsCondition = rhs != null && rhs.isEmpty() - val connection = r.getOrNull() contentEquals lhs - - lhsCondition && rhsCondition && connection - }, - { lhs, rhs, r -> - val lhsCondition = lhs != null && lhs.isNotEmpty() - val rhsCondition = rhs != null && rhs.isNotEmpty() - val connection = lhs.last() < rhs.last() && r.getOrNull()?.toList() == (lhs + rhs).sorted() - - lhsCondition && rhsCondition && connection - }, - { lhs, rhs, r -> - val lhsCondition = lhs != null && lhs.isNotEmpty() - val rhsCondition = rhs != null && rhs.isNotEmpty() - val connection = lhs.last() >= rhs.last() && r.getOrNull()?.toList() == (lhs + rhs).sorted() - - lhsCondition && rhsCondition && connection - }, - ) - } - - @Test - fun testDefaultSort() { - val defaultSortSummary1 = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("\n"), - DocRegularStmt("throws NullPointerException in: array.length < 4"), - DocRegularStmt("\n") - ) - ) - ) - - val defaultSortSummary2 = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("executes conditions:\n"), - DocRegularStmt(" "), - DocCodeStmt("(array.length < 4): True"), - DocRegularStmt("\n"), - DocRegularStmt("\n"), - DocRegularStmt("throws IllegalArgumentException after condition: array.length < 4"), - DocRegularStmt("\n") - ) - ) - ) - val defaultSortSummary3 = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("executes conditions:\n"), - DocRegularStmt(" "), - DocCodeStmt("(array.length < 4): False"), - DocRegularStmt("\n"), - DocRegularStmt("invokes:\n"), - DocRegularStmt(" Arrays::sort once"), - DocRegularStmt("\n"), - DocRegularStmt("returns from: "), - DocCodeStmt("return array;"), - DocRegularStmt("\n") - ) - ) - ) - checkWithException( - Sort::defaultSort, - eq(3), - { a, r -> a == null && r.isException() }, - { a, r -> a != null && a.size < 4 && r.isException() }, - { a, r -> - val resultArray = intArrayOf(-100, 0, 100, 200) - a != null && r.getOrNull()!!.size >= 4 && r.getOrNull() contentEquals resultArray - }, - summaryTextChecks = listOf( - keyMatch(defaultSortSummary1), - keyMatch(defaultSortSummary2), - keyMatch(defaultSortSummary3), - ), - summaryNameChecks = listOf( - keyMatch("testDefaultSort_ThrowNullPointerException"), - keyMatch("testDefaultSort_ArrayLengthLessThan4"), - keyMatch("testDefaultSort_ArrayLengthGreaterOrEqual4"), - ), - summaryDisplayNameChecks = listOf( - keyMatch("array.length < 4 -> ThrowNullPointerException"), - keyMatch("array.length < 4 -> ThrowIllegalArgumentException"), - keyMatch("array.length < 4 : False -> return array"), - ) - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/LombokAnnotationTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/LombokAnnotationTest.kt deleted file mode 100644 index f27e4cc91c..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/annotations/LombokAnnotationTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.utbot.examples.annotations - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.annotations.lombok.EnumWithAnnotations -import org.utbot.examples.annotations.lombok.EnumWithoutAnnotations -import org.utbot.examples.annotations.lombok.NotNullAnnotations -import org.utbot.examples.eq -import org.junit.jupiter.api.Test - -/** - * Tests for code with Lombok annotations - * - * We do not calculate coverage here as Lombok always make it pure - * (see, i.e. https://stackoverflow.com/questions/44584487/improve-lombok-data-code-coverage) - * and Lombok code is considered to be already tested itself. - */ -class LombokAnnotationTest : AbstractTestCaseGeneratorTest(testClass = EnumWithAnnotations::class) { - - @Test - fun testGetterWithAnnotations() { - check( - EnumWithAnnotations::getConstant, - eq(1), - coverage = DoNotCalculate, - ) - } - - @Test - fun testGetterWithoutAnnotations() { - check( - EnumWithoutAnnotations::getConstant, - eq(1), - coverage = DoNotCalculate, - ) - } - - @Test - fun testNonNullAnnotations() { - check( - NotNullAnnotations::lombokNonNull, - eq(1), - { value, r -> value == r }, - coverage = DoNotCalculate, - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArrayOfObjectsTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArrayOfObjectsTest.kt deleted file mode 100644 index c89c8b17d4..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/ArrayOfObjectsTest.kt +++ /dev/null @@ -1,116 +0,0 @@ -package org.utbot.examples.arrays - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.atLeast -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import org.junit.jupiter.api.Test - -// TODO failed Kotlin compilation SAT-1332 -internal class ArrayOfObjectsTest : AbstractTestCaseGeneratorTest( - testClass = ArrayOfObjects::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) -) { - @Test - fun testDefaultValues() { - check( - ArrayOfObjects::defaultValues, - eq(1), - { r -> r != null && r.single() == null }, - coverage = atLeast(50) - ) - } - - @Test - fun testCreateArray() { - check( - ArrayOfObjects::createArray, - eq(2), - { _, _, length, _ -> length < 3 }, - { x, y, length, r -> - require(r != null) - - val sizeConstraint = length >= 3 && r.size == length - val contentConstraint = r.mapIndexed { i, elem -> elem.x == x + i && elem.y == y + i }.all { it } - - sizeConstraint && contentConstraint - } - ) - } - - @Test - fun testCopyArray() { - checkWithException( - ArrayOfObjects::copyArray, - ge(4), - { a, r -> a == null && r.isException() }, - { a, r -> a.size < 3 && r.isException() }, - { a, r -> a.size >= 3 && null in a && r.isException() }, - { a, r -> a.size >= 3 && r.getOrThrow().all { it.x == -1 && it.y == 1 } }, - ) - } - - @Test - fun testCopyArrayMutation() { - checkParamsMutations( - ArrayOfObjects::copyArray, - ignoreExecutionsNumber, - { _, arrayAfter -> arrayAfter.all { it.x == -1 && it.y == 1 } } - ) - } - - @Test - fun testArrayWithSucc() { - check( - ArrayOfObjects::arrayWithSucc, - eq(3), - { length, _ -> length < 0 }, - { length, r -> length < 2 && r != null && r.size == length && r.all { it == null } }, - { length, r -> - require(r != null) - - val sizeConstraint = length >= 2 && r.size == length - val zeroElementConstraint = r[0] is ObjectWithPrimitivesClass && r[0].x == 2 && r[0].y == 4 - val firstElementConstraint = r[1] is ObjectWithPrimitivesClassSucc && r[1].x == 3 - - sizeConstraint && zeroElementConstraint && firstElementConstraint - }, - ) - } - - @Test - fun testObjectArray() { - check( - ArrayOfObjects::objectArray, - eq(5), - { a, _, _ -> a == null }, - { a, _, r -> a != null && a.size != 2 && r == -1 }, - { a, o, _ -> a != null && a.size == 2 && o == null }, - { a, p, r -> a != null && a.size == 2 && p != null && p.x + 5 > 20 && r == 1 }, - { a, o, r -> a != null && a.size == 2 && o != null && o.x + 5 <= 20 && r == 0 }, - ) - } - - @Test - fun testArrayOfArrays() { - check( - ArrayOfObjects::arrayOfArrays, - between(4..5), // might be two ClassCastExceptions - { a, _ -> a.any { it == null } }, - { a, _ -> a.any { it != null && it !is IntArray } }, - { a, r -> (a.all { it != null && it is IntArray && it.isEmpty() } || a.isEmpty()) && r == 0 }, - { a, r -> a.all { it is IntArray } && r == a.sumBy { (it as IntArray).sum() } }, - coverage = DoNotCalculate - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/FinalStaticFieldArrayTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/FinalStaticFieldArrayTest.kt deleted file mode 100644 index 624d27766e..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/arrays/FinalStaticFieldArrayTest.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.utbot.examples.arrays - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.ignoreExecutionsNumber -import org.junit.jupiter.api.Test - -internal class FinalStaticFieldArrayTest : AbstractTestCaseGeneratorTest(testClass = FinalStaticFieldArray::class) { - - @Test - fun testFactorial() { - checkStaticMethod( - FinalStaticFieldArray::factorial, - ignoreExecutionsNumber, - { n, r -> - (n as Int) >= 0 && n < FinalStaticFieldArray.MAX_FACTORIAL && r == FinalStaticFieldArray.factorial(n) - }, - { n, _ -> (n as Int) < 0 }, - { n, r -> (n as Int) > FinalStaticFieldArray.MAX_FACTORIAL && r == FinalStaticFieldArray.factorial(n) }, - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/casts/CastExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/casts/CastExampleTest.kt deleted file mode 100644 index 277d88e8bf..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/casts/CastExampleTest.kt +++ /dev/null @@ -1,100 +0,0 @@ -package org.utbot.examples.casts - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.isException -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import org.junit.jupiter.api.Test - -// TODO failed Kotlin compilation SAT-1332 -internal class CastExampleTest : AbstractTestCaseGeneratorTest( - testClass = CastExample::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) -) { - @Test - fun testSimpleCast() { - check( - CastExample::simpleCast, - eq(3), - { o, _ -> o != null && o !is CastClassFirstSucc }, - { o, r -> o != null && r is CastClassFirstSucc }, - { o, r -> o == null && r == null }, - ) - } - - @Test - fun testClassCastException() { - checkWithException( - CastExample::castClassException, - eq(3), - { o, r -> o == null && r.isException() }, - { o, r -> o != null && o !is CastClassFirstSucc && r.isException() }, - { o, r -> o != null && o is CastClassFirstSucc && r.isException() }, - coverage = DoNotCalculate - ) - } - - @Test - fun testCastUp() { - check( - CastExample::castUp, - eq(1) - ) - } - - @Test - fun testCastNullToDifferentTypes() { - check( - CastExample::castNullToDifferentTypes, - eq(1) - ) - } - - @Test - fun testFromObjectToPrimitive() { - check( - CastExample::fromObjectToPrimitive, - eq(3), - { obj, _ -> obj == null }, - { obj, _ -> obj != null && obj !is Int }, - { obj, r -> obj != null && obj is Int && r == obj } - ) - } - - @Test - fun testCastFromObjectToInterface() { - check( - CastExample::castFromObjectToInterface, - eq(2), - { obj, _ -> obj != null && obj !is Colorable }, - { obj, r -> obj != null && obj is Colorable && r == obj }, - coverage = DoNotCalculate - ) - } - - @Test - fun testComplicatedCast() { - check( - CastExample::complicatedCast, - eq(2), - { i, a, _ -> i == 0 && a != null && a[i] != null && a[i] !is CastClassFirstSucc }, - { i, a, r -> i == 0 && a != null && a[i] != null && a[i] is CastClassFirstSucc && r is CastClassFirstSucc }, - coverage = DoNotCalculate - ) - } - - @Test - fun testThisTypeChoice() { - check( - CastClass::castToInheritor, - eq(0), - coverage = DoNotCalculate - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/ClassWithStaticAndInnerClassesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/ClassWithStaticAndInnerClassesTest.kt deleted file mode 100644 index 3cc02c8e18..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/codegen/ClassWithStaticAndInnerClassesTest.kt +++ /dev/null @@ -1,99 +0,0 @@ -package org.utbot.examples.codegen - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.junit.jupiter.api.Test - -@Suppress("INACCESSIBLE_TYPE") -internal class ClassWithStaticAndInnerClassesTest : AbstractTestCaseGeneratorTest(testClass = ClassWithStaticAndInnerClasses::class) { - @Test - fun testUsePrivateStaticClassWithPrivateField() { - check( - ClassWithStaticAndInnerClasses::usePrivateStaticClassWithPrivateField, - eq(2), - coverage = DoNotCalculate - ) - } - - @Test - fun testUsePrivateStaticClassWithPublicField() { - check( - ClassWithStaticAndInnerClasses::usePrivateStaticClassWithPublicField, - eq(2), - coverage = DoNotCalculate - ) - } - - @Test - fun testUsePublicStaticClassWithPrivateField() { - check( - ClassWithStaticAndInnerClasses::usePublicStaticClassWithPrivateField, - eq(2), - coverage = DoNotCalculate - ) - } - - @Test - fun testUsePublicStaticClassWithPublicField() { - check( - ClassWithStaticAndInnerClasses::usePublicStaticClassWithPublicField, - eq(2), - coverage = DoNotCalculate - ) - } - - @Test - fun testUsePrivateInnerClassWithPrivateField() { - check( - ClassWithStaticAndInnerClasses::usePrivateInnerClassWithPrivateField, - eq(2), - coverage = DoNotCalculate - ) - } - - @Test - fun testUsePrivateInnerClassWithPublicField() { - check( - ClassWithStaticAndInnerClasses::usePrivateInnerClassWithPublicField, - eq(2), - coverage = DoNotCalculate - ) - } - - @Test - fun testUsePublicInnerClassWithPrivateField() { - check( - ClassWithStaticAndInnerClasses::usePublicInnerClassWithPrivateField, - eq(2), - coverage = DoNotCalculate - ) - } - - @Test - fun testUsePublicInnerClassWithPublicField() { - check( - ClassWithStaticAndInnerClasses::usePublicInnerClassWithPublicField, - eq(2), - coverage = DoNotCalculate - ) - } - - @Test - fun testUsePackagePrivateFinalStaticClassWithPackagePrivateField() { - check( - ClassWithStaticAndInnerClasses::usePackagePrivateFinalStaticClassWithPackagePrivateField, - eq(2), - coverage = DoNotCalculate - ) - } - - @Test - fun testUsePackagePrivateFinalInnerClassWithPackagePrivateField() { - check( - ClassWithStaticAndInnerClasses::usePackagePrivateFinalInnerClassWithPackagePrivateField, - eq(2), - coverage = DoNotCalculate - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/GenericListsExample.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/collections/GenericListsExample.kt deleted file mode 100644 index 755bdd27db..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/GenericListsExample.kt +++ /dev/null @@ -1,163 +0,0 @@ -package org.utbot.examples.collections - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test - -// TODO disabled tests should be fixes with SAT-1441 -internal class GenericListsExampleTest : AbstractTestCaseGeneratorTest( - testClass = GenericListsExample::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) -) { - @Test - @Disabled("Doesn't find branches without NPE") - fun testListOfListsOfT() { - check( - GenericListsExample::listOfListsOfT, - eq(-1) - ) - } - - @Test - @Disabled("Problems with memory") - fun testListOfComparable() { - check( - GenericListsExample::listOfComparable, - eq(1), - { v, r -> v != null && v.size > 1 && v[0] != null && v.all { it is Comparable<*> || it == null } && v == r }, - coverage = DoNotCalculate - ) - } - - @Test - fun testListOfT() { - check( - GenericListsExample::listOfT, - eq(1), - { v, r -> v != null && v.size >= 2 && v[0] != null && v == r }, - coverage = DoNotCalculate - ) - } - - @Test - @Disabled("Wrong number of matchers") - fun testListOfTArray() { - check( - GenericListsExample::listOfTArray, - eq(1) - ) - } - - @Test - @Disabled("JIRA:1446") - fun testListOfExtendsTArray() { - check( - GenericListsExample::listOfExtendsTArray, - eq(-1) - ) - } - - @Test - @Disabled("java.lang.ClassCastException: java.util.ArraysParallelSortHelpers\$FJShort\$Merger cannot be cast to [I") - fun testListOfPrimitiveArrayInheritors() { - check( - GenericListsExample::listOfPrimitiveArrayInheritors, - eq(-1) - ) - } - - @Test - @Disabled("JIRA:1620") - fun createWildcard() { - check( - GenericListsExample<*>::wildcard, - eq(4), - { v, r -> v == null && r?.isEmpty() == true }, - { v, r -> v != null && v.size == 1 && v[0] != null && v == r && v.all { it is Number || it == null } }, - { v, r -> v != null && (v.size != 1 || v[0] == null) && v == r && v.all { it is Number || it == null } }, - coverage = DoNotCalculate - ) - } - - @Suppress("NestedLambdaShadowedImplicitParameter") - @Test - @Disabled("unexpected empty nested list") - fun createListOfLists() { - check( - GenericListsExample<*>::listOfLists, - eq(1), - { v, r -> - val valueCondition = v != null && v[0] != null && v[0].isNotEmpty() - val typeCondition = v.all { (it is List<*> && it.all { it is Int || it == null }) || it == null } - - valueCondition && typeCondition && v == r - }, - coverage = DoNotCalculate - ) - } - - @Test - fun createWildcardWithOnlyQuestionMark() { - check( - GenericListsExample<*>::wildcardWithOnlyQuestionMark, - eq(3), - { v, r -> v == null && r?.isEmpty() == true }, - { v, r -> v.size == 1 && v == r }, - { v, r -> v.size != 1 && v == r }, - coverage = DoNotCalculate - ) - } - - - @Test - fun testGenericWithArrayOfPrimitives() { - check( - GenericListsExample<*>::genericWithArrayOfPrimitives, - eq(1), - { v, _ -> - val valueCondition = v != null && v.size >= 2 && v[0] != null && v[0].isNotEmpty() && v[0][0] != 0L - val typeCondition = v.all { it is LongArray || it == null } - - valueCondition && typeCondition - }, - coverage = DoNotCalculate - ) - } - - - @Test - fun testGenericWithObject() { - check( - GenericListsExample<*>::genericWithObject, - eq(1), - { v, r -> v != null && v.size >= 2 && v[0] != null && v[0] is Long && v == r }, - coverage = DoNotCalculate - ) - } - - - @Test - fun testGenericWithArrayOfArrays() { - check( - GenericListsExample<*>::genericWithArrayOfArrays, - eq(1), - { v, _ -> - val valueCondition = v != null && v.size >= 2 && v[0] != null && v[0].isNotEmpty() && v[0][0] != null - val typeCondition = v.all { - (it is Array<*> && it.isArrayOf>() && it.all { it.isArrayOf() || it == null}) || it == null - } - - valueCondition && typeCondition - }, - coverage = DoNotCalculate - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/ListAlgorithmsTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/collections/ListAlgorithmsTest.kt deleted file mode 100644 index 445fbf914a..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/ListAlgorithmsTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -package org.utbot.examples.collections - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import org.junit.jupiter.api.Test -import org.utbot.examples.atLeast - -// TODO failed Kotlin compilation SAT-1332 -class ListAlgorithmsTest : AbstractTestCaseGeneratorTest( - testClass = ListAlgorithms::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) -) { - - @Test - fun testMergeLists() { - check( - ListAlgorithms::mergeListsInplace, - eq(4), - { a, b, r -> b.subList(0, b.size - 1).any { a.last() < it } && r != null && r == r.sorted() }, - { a, b, r -> (a.subList(0, a.size - 1).any { b.last() <= it } || a.any { ai -> b.any { ai < it } }) && r != null && r == r.sorted() }, - { a, b, r -> a[0] < b[0] && r != null && r == r.sorted() }, - { a, b, r -> a[0] >= b[0] && r != null && r == r.sorted() }, - coverage = atLeast(94) - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/ListIteratorsTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/collections/ListIteratorsTest.kt deleted file mode 100644 index 5e320c5fda..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/ListIteratorsTest.kt +++ /dev/null @@ -1,110 +0,0 @@ -package org.utbot.examples.collections - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import kotlin.math.min -import org.junit.jupiter.api.Test - -// TODO failed Kotlin compilation (generics) SAT-1332 -internal class ListIteratorsTest : AbstractTestCaseGeneratorTest( - testClass = ListIterators::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) -) { - - @Test - fun testIterate() { - check( - ListIterators::iterate, - eq(3), - { l, _ -> l == null }, - { l, result -> l.isEmpty() && result == l }, - { l, result -> l.isNotEmpty() && result == l }, - coverage = DoNotCalculate - ) - } - - @Test - fun testIterateReversed() { - check( - ListIterators::iterateReversed, - eq(3), - { l, _ -> l == null }, - { l, result -> l.isEmpty() && result == l }, - { l, result -> l.isNotEmpty() && result == l.reversed() }, - coverage = DoNotCalculate - ) - } - - @Test - fun testIterateForEach() { - check( - ListIterators::iterateForEach, - eq(4), - { l, _ -> l == null }, - { l, result -> l.isEmpty() && result == 0 }, - { l, _ -> l.isNotEmpty() && l.any { it == null } }, - { l, result -> l.isNotEmpty() && result == l.sum() }, - coverage = DoNotCalculate - ) - } - - @Test - fun testAddElements() { - check( - ListIterators::addElements, - eq(5), - { l, _, _ -> l == null }, - { l, _, result -> l != null && l.isEmpty() && result == l }, - { l, arr, _ -> l != null && l.size > 0 && arr == null }, - { l, arr, _ -> l != null && arr != null && l.isNotEmpty() && arr.isEmpty() }, - { l, arr, _ -> l != null && arr != null && l.size > arr.size }, - coverage = DoNotCalculate - ) - } - - @Test - fun testSetElements() { - check( - ListIterators::setElements, - eq(5), - { l, _, _ -> l == null }, - { l, _, result -> l != null && l.isEmpty() && result == l }, - { l, arr, _ -> l != null && arr != null && l.size > arr.size }, - { l, arr, _ -> l != null && l.size > 0 && arr == null }, - { l, arr, result -> l != null && arr != null && l.size <= arr.size && result == arr.asList().take(l.size) }, - coverage = DoNotCalculate - ) - } - - @Test - fun testRemoveElements() { - check( - ListIterators::removeElements, - ignoreExecutionsNumber, // the exact number of the executions depends on the decisions made by PathSelector - // so we can have either six results or seven, depending on the [pathSelectorType] - // from UtSettings - { l, _, _ -> l == null }, - { l, i, _ -> l != null && i <= 0 }, - { l, i, _ -> l != null && l.isEmpty() && i > 0 }, - { l, i, _ -> l != null && i > 0 && l.subList(0, min(i, l.size)).any { it !is Int } }, - { l, i, _ -> l != null && i > 0 && l.subList(0, min(i, l.size)).any { it == null } }, - { l, i, _ -> l != null && l.isNotEmpty() && i > 0 }, - { l, i, result -> - require(l != null) - - val precondition = l.isNotEmpty() && i > 0 && l.subList(0, i).all { it is Int } - val postcondition = result == (l.subList(0, i - 1) + l.subList(min(l.size, i), l.size)) - - precondition && postcondition - }, - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/ListsTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/collections/ListsTest.kt deleted file mode 100644 index feb5b8cdc4..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/ListsTest.kt +++ /dev/null @@ -1,274 +0,0 @@ -package org.utbot.examples.collections - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.between -import org.utbot.examples.isException -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test - -// TODO failed Kotlin compilation SAT-1332 -internal class ListsTest : AbstractTestCaseGeneratorTest( - testClass = Lists::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) -) { - @Test - fun testBigListFromParameters() { - check( - Lists::bigListFromParameters, - eq(1), - { list, r -> list.size == r && list.size == 11 }, - coverage = DoNotCalculate - ) - } - - @Test - fun testGetNonEmptyCollection() { - check( - Lists::getNonEmptyCollection, - eq(3), - { collection, _ -> collection == null }, - { collection, r -> collection.isEmpty() && r == null }, - { collection, r -> collection.isNotEmpty() && collection == r }, - coverage = DoNotCalculate - ) - } - - @Test - fun createTest() { - check( - Lists::create, - eq(3), - { a, _ -> a == null }, - { a, r -> a != null && a.isEmpty() && r!!.isEmpty() }, - { a, r -> a != null && a.isNotEmpty() && r != null && r.isNotEmpty() && a.toList() == r.also { println(r) } }, - coverage = DoNotCalculate - ) - } - - @Test - fun testIterableContains() { - check( - Lists::iterableContains, - ignoreExecutionsNumber, - { iterable, _ -> iterable == null }, - { iterable, r -> 1 in iterable && r == true }, - { iterable, r -> 1 !in iterable && r == false }, - ) - } - - @Test - fun testCollectionContains() { - check( - Lists::collectionContains, - ignoreExecutionsNumber, - { collection, _ -> collection == null }, - { collection, r -> 1 in collection && r == true }, - { collection, r -> 1 !in collection && r == false }, - ) - } - - @Test - fun testGetFromAnotherListToArray() { - check( - Lists::getFromAnotherListToArray, - eq(4), - { l, _ -> l == null }, - { l, _ -> l.isEmpty() }, - { l, r -> l[0] == null && r == null }, - { l, r -> l[0] != null && r is Array<*> && r.isArrayOf() && r.size == 1 && r[0] == l[0] }, - coverage = DoNotCalculate - ) - } - - @Test - fun addElementsTest() { - check( - Lists::addElements, - eq(5), - { list, _, _ -> list == null }, - { list, a, _ -> list != null && list.size >= 2 && a == null }, - { list, _, r -> list.size < 2 && r == list }, - { list, a, r -> list.size >= 2 && a.size < 2 && r == list }, - { list, a, r -> - require(r != null) - - val sizeConstraint = list.size >= 2 && a.size >= 2 && r.size == list.size + a.size - val content = r.mapIndexed { i, it -> if (i < r.size) it == r[i] else it == a[i - r.size] }.all { it } - - sizeConstraint && content - }, - coverage = DoNotCalculate - ) - } - - @Test - fun removeElementsTest() { - checkWithException( - Lists::removeElements, - between(7..8), - { list, _, _, r -> list == null && r.isException() }, - { list, i, _, r -> list != null && i < 0 && r.isException() }, - { list, i, _, r -> list != null && i >= 0 && list.size > i && list[i] == null && r.isException() }, - { list, i, j, r -> - require(list != null && list[i] != null) - - val listConstraints = i >= 0 && list.size > i && (list.size <= j + 1 || j < 0) - val resultConstraint = r.isException() - - listConstraints && resultConstraint - }, - { list, i, j, r -> - require(list != null && list[i] != null) - - val k = j + if (i <= j) 1 else 0 - val indicesConstraint = i >= 0 && list.size > i && j >= 0 && list.size > j + 1 - val contentConstraint = list[i] != null && list[k] == null - val resultConstraint = r.isException() - - indicesConstraint && contentConstraint && resultConstraint - }, - { list, i, j, r -> - require(list != null) - - val k = j + if (i <= j) 1 else 0 - - val precondition = i >= 0 && list.size > i && j >= 0 && list.size > j + 1 && list[i] < list[k] - val postcondition = r.getOrNull() == list[i] - - precondition && postcondition - }, - { list, i, j, r -> - require(list != null) - - val k = j + if (i <= j) 1 else 0 - - val precondition = i >= 0 && list.size > i && j >= 0 && list.size > j + 1 && list[i] >= list[k] - val postcondition = r.getOrNull() == list[k] - - precondition && postcondition - }, - coverage = DoNotCalculate - ) - } - - @Test - fun createArrayWithDifferentTypeTest() { - check( - Lists::createWithDifferentType, - eq(2), - { x, r -> x % 2 != 0 && r is java.util.LinkedList && r == List(4) { it } }, - { x, r -> x % 2 == 0 && r is java.util.ArrayList && r == List(4) { it } }, - coverage = DoNotCalculate - ) - } - - @Test - fun getElementsTest() { - check( - Lists::getElements, - eq(4), - { x, _ -> x == null }, - { x, r -> x != null && x.isEmpty() && r!!.isEmpty() }, - { x, _ -> x != null && x.isNotEmpty() && x.any { it == null } }, - { x, r -> x != null && x.isNotEmpty() && x.all { it is Int } && r!!.toList() == x }, - coverage = DoNotCalculate - ) - } - - @Test - fun setElementsTest() { - check( - Lists::setElements, - eq(3), - { x, _ -> x == null }, - { x, r -> x != null && x.isEmpty() && r!!.isEmpty() }, - { x, r -> x != null && x.isNotEmpty() && r!!.containsAll(x.toList()) && r.size == x.size }, - coverage = DoNotCalculate - ) - } - - @Test - fun testClear() { - check( - Lists::clear, - eq(3), - { list, _ -> list == null }, - { list, r -> list.size >= 2 && r == emptyList() }, - { list, r -> list.size < 2 && r == emptyList() }, - coverage = DoNotCalculate - ) - } - - @Test - fun testAddAll() { - check( - Lists::addAll, - eq(3), - { list, _, _ -> list == null }, - { list, i, r -> - list != null && list.isEmpty() && r != null && r.size == 1 && r[0] == i - }, - { list, i, r -> - list != null && list.isNotEmpty() && r != null && r.size == 1 + list.size && r == listOf(i) + list - }, - coverage = DoNotCalculate - ) - } - - @Test - fun testAddAllInIndex() { - check( - Lists::addAllByIndex, - eq(4), - { list, i, _ -> list == null && i >= 0 }, - { list, i, _ -> list == null && i < 0 }, - { list, i, r -> list != null && i >= list.size && r == list }, - { list, i, r -> - list != null && i in 0..list.lastIndex && r == list.toMutableList().apply { addAll(i, listOf(0, 1)) } - }, - coverage = DoNotCalculate - ) - } - - @Test - @Disabled("TODO: add choosing proper type in list wrapper") - fun testRemoveFromList() { - checkWithException( - Lists::removeFromList, - ge(4), - { list, _, r -> list == null && r.isException() }, - { list, _, r -> list != null && list.isEmpty() && r.isException() }, - { list, i, r -> - require(list != null && list.lastOrNull() != null) - - list.isNotEmpty() && (i < 0 || i >= list.size) && r.isException() - }, - { list, i, r -> - require(list != null && list.lastOrNull() != null) - - val changedList = list.toMutableList().apply { - set(i, last()) - removeLast() - } - - val precondition = list.isNotEmpty() && i >= 0 && i < list.size - val postcondition = changedList == r.getOrNull() - - precondition && postcondition - }, - // TODO: add branches with conditions (list is LinkedList) and (list !is ArrayList && list !is LinkedList) - coverage = DoNotCalculate - ) - } - -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/MapsTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/collections/MapsTest.kt deleted file mode 100644 index a66a07f044..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/collections/MapsTest.kt +++ /dev/null @@ -1,388 +0,0 @@ -package org.utbot.examples.collections - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.AtLeast -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.examples.withPushingStateFromPathSelectorForConcrete -import org.utbot.examples.withoutMinimization -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockStrategyApi -import org.junit.jupiter.api.Test - -// TODO failed Kotlin compilation ($ in names, generics) SAT-1220 SAT-1332 -internal class MapsTest : AbstractTestCaseGeneratorTest( - testClass = Maps::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) -) { - @Test - fun createTest() { - check( - Maps::create, - eq(5), - { keys, _, _ -> keys == null }, - { keys, _, result -> keys.isEmpty() && result!!.isEmpty() }, - { keys, values, result -> keys.isNotEmpty() && values == null }, - { keys, values, result -> keys.isNotEmpty() && values.size < keys.size }, - { keys, values, result -> - keys.isNotEmpty() && values.size >= keys.size && - result!!.size == keys.size && keys.indices.all { result[keys[it]] == values[it] } - }, - coverage = DoNotCalculate - ) - } - - @Test - fun testToString() { - check( - Maps::mapToString, - eq(1), - { a, b, c, r -> r == Maps().mapToString(a, b, c) } - ) - } - - @Test - fun testFindAllChars() { - check( - Maps::countChars, - eq(3), - { s, _ -> s == null }, - { s, result -> s == "" && result!!.isEmpty() }, - { s, result -> s != "" && result == s.groupingBy { it }.eachCount() }, - coverage = DoNotCalculate - ) - } - - @Test - fun putElementsTest() { - check( - Maps::putElements, - ge(5), - { map, _, _ -> map == null }, - { map, array, _ -> map != null && map.isNotEmpty() && array == null }, - { map, _, result -> map.isEmpty() && result == map }, - { map, array, result -> map.isNotEmpty() && array.isEmpty() && result == map }, - { map, array, result -> - map.size >= 1 && array.isNotEmpty() - && result == map.toMutableMap().apply { putAll(array.map { it to it }) } - }, - coverage = DoNotCalculate - ) - } - - @Test - fun removeEntries() { - check( - Maps::removeElements, - ge(6), - { map, _, _, _ -> map == null }, - { map, i, j, res -> map != null && (i !in map || map[i] == null) && (j !in map || map[j] == null) && res == -1 }, - { map, i, j, res -> map != null && map.isNotEmpty() && i !in map && j in map && res == 4 }, - { map, i, j, res -> map != null && map.isNotEmpty() && i in map && (j !in map || j == i) && res == 3 }, - { map, i, j, res -> map != null && map.size >= 2 && i in map && j in map && i > j && res == 2 }, - { map, i, j, res -> map != null && map.size >= 2 && i in map && j in map && i < j && res == 1 }, - coverage = DoNotCalculate - ) - } - - @Test - fun createWithDifferentTypeTest() { - check( - Maps::createWithDifferentType, - eq(2), - { seed, result -> seed % 2 != 0 && result is java.util.LinkedHashMap }, - { seed, result -> seed % 2 == 0 && result !is java.util.LinkedHashMap && result is java.util.HashMap }, - coverage = DoNotCalculate - ) - } - - @Test - fun removeCustomObjectTest() { - check( - Maps::removeCustomObject, - ge(3), - { map, _, _ -> map == null }, - { map, i, result -> (map.isEmpty() || CustomClass(i) !in map) && result == null }, - { map, i, result -> map.isNotEmpty() && CustomClass(i) in map && result == map[CustomClass(i)] }, - coverage = DoNotCalculate - ) - } - - @Test - fun testComputeValue() { - check( - Maps::computeValue, - between(3..5), - { map, _, _ -> map == null }, - { map, key, result -> - val valueWasUpdated = result!![key] == key + 1 - val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } - map[key] == null && valueWasUpdated && otherValuesWerentTouched - }, - { map, key, result -> - val valueWasUpdated = result!![key] == map[key]!! + 1 - val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } - map[key] != null && valueWasUpdated && otherValuesWerentTouched - }, - coverage = DoNotCalculate - ) - } - - @Test - fun testComputeValueWithMocks() { - check( - Maps::computeValue, - between(3..5), - { map, _, _ -> map == null }, - { map, key, result -> - val valueWasUpdated = result!![key] == key + 1 - val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } - map[key] == null && valueWasUpdated && otherValuesWerentTouched - }, - { map, key, result -> - val valueWasUpdated = result!![key] == map[key]!! + 1 - val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } - map[key] != null && valueWasUpdated && otherValuesWerentTouched - }, - mockStrategy = MockStrategyApi.OTHER_PACKAGES, // checks that we do not generate mocks for lambda classes - coverage = DoNotCalculate - ) - } - - @Test - fun testComputeValueIfAbsent() { - check( - Maps::computeValueIfAbsent, - between(3..5), - { map, _, _ -> map == null }, - { map, key, result -> map[key] != null && result == map }, - { map, key, result -> - val valueWasUpdated = result!![key] == key + 1 - val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } - map[key] == null && valueWasUpdated && otherValuesWerentTouched - }, - coverage = DoNotCalculate - ) - } - - @Test - fun testComputeValueIfPresent() { - check( - Maps::computeValueIfPresent, - between(3..5), - { map, _, _ -> map == null }, - { map, key, result -> map[key] == null && result == map }, - { map, key, result -> - val valueWasUpdated = result!![key] == map[key]!! + 1 - val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } - map[key] != null && valueWasUpdated && otherValuesWerentTouched - }, - coverage = DoNotCalculate - ) - } - - @Test - fun testClearEntries() { - check( - Maps::clearEntries, - between(3..4), - { map, _ -> map == null }, - { map, result -> map.isEmpty() && result == 0 }, - { map, result -> map.isNotEmpty() && result == 1 }, - coverage = DoNotCalculate - ) - } - - @Test - fun testContainsKey() { - check( - Maps::containsKey, - between(3..5), - { map, _, _ -> map == null }, - { map, key, result -> key !in map && result == 0 }, - { map, key, result -> key in map && result == 1 }, - coverage = DoNotCalculate - ) - } - - @Test - fun testContainsValue() { - check( - Maps::containsValue, - between(3..6), - { map, _, _ -> map == null }, - { map, value, result -> value !in map.values && result == 0 }, - { map, value, result -> value in map.values && result == 1 }, - coverage = DoNotCalculate - ) - } - - @Test - fun testGetOrDefaultElement() { - check( - Maps::getOrDefaultElement, - between(4..6), - { map, _, _ -> map == null }, - { map, i, result -> i !in map && result == 1 }, - { map, i, result -> i in map && map[i] == null && result == 0 }, - { map, i, result -> i in map && map[i] != null && result == map[i] }, - coverage = DoNotCalculate - ) - } - - @Test - fun testRemoveKeyWithValue() { - check( - Maps::removeKeyWithValue, - ge(6), - { map, _, _, _ -> map == null }, - { map, key, value, result -> key !in map && value !in map.values && result == 0 }, - { map, key, value, result -> key in map && value !in map.values && result == -1 }, - { map, key, value, result -> key !in map && value in map.values && result == -2 }, - { map, key, value, result -> key in map && map[key] == value && result == 3 }, - { map, key, value, result -> key in map && value in map.values && map[key] != value && result == -3 }, - coverage = DoNotCalculate - ) - } - - @Test - fun testPutElementIfAbsent() { - withoutMinimization { // TODO: JIRA:1506 - check( - Maps::putElementIfAbsent, - ignoreExecutionsNumber, - { map, _, _, _ -> map == null }, - { map, key, _, result -> map != null && key in map && result == map }, - { map, key, value, result -> - val valueWasPut = result!![key] == value && result.size == map.size + 1 - val otherValuesWerentTouched = result.entries.containsAll(map.entries) - key !in map && valueWasPut && otherValuesWerentTouched - }, - coverage = AtLeast(90) // unreachable else branch in MUT - ) - } - } - - @Test - fun testReplaceEntry() { - check( - Maps::replaceEntry, - between(3..6), - { map, _, _, _ -> map == null }, - { map, key, _, result -> key !in map && result == map }, - { map, key, value, result -> - val valueWasReplaced = result!![key] == value - val otherValuesWerentTouched = result.entries.all { it.key == key || it in map.entries } - key in map && valueWasReplaced && otherValuesWerentTouched - }, - coverage = DoNotCalculate - ) - } - - @Test - fun testReplaceEntryWithValue() { - withPushingStateFromPathSelectorForConcrete { - check( - Maps::replaceEntryWithValue, - ge(6), - { map, _, _, _ -> map == null }, - { map, key, value, result -> key !in map && value !in map.values && result == 0 }, - { map, key, value, result -> key in map && value !in map.values && result == -1 }, - { map, key, value, result -> key !in map && value in map.values && result == -2 }, - { map, key, value, result -> key in map && map[key] == value && result == 3 }, - { map, key, value, result -> key in map && value in map.values && map[key] != value && result == -3 }, - coverage = DoNotCalculate - ) - } - } - - @Test - fun testMerge() { - withoutMinimization { // TODO: JIRA:1506 - checkWithException( - Maps::merge, - ge(5), - { map, _, _, result -> map == null && result.isException() }, - { map, _, value, result -> map != null && value == null && result.isException() }, - { map, key, value, result -> - val resultMap = result.getOrNull()!! - val entryWasPut = resultMap.entries.all { it.key == key && it.value == value || it in map.entries } - key !in map && value != null && entryWasPut - }, - { map, key, value, result -> - val resultMap = result.getOrNull()!! - val valueInMapIsNull = key in map && map[key] == null - val valueWasReplaced = resultMap[key] == value - val otherValuesWerentTouched = resultMap.entries.all { it.key == key || it in map.entries } - value != null && valueInMapIsNull && valueWasReplaced && otherValuesWerentTouched - }, - { map, key, value, result -> - val resultMap = result.getOrNull()!! - val valueInMapIsNotNull = map[key] != null - val valueWasMerged = resultMap[key] == map[key]!! + value - val otherValuesWerentTouched = resultMap.entries.all { it.key == key || it in map.entries } - value != null && valueInMapIsNotNull && valueWasMerged && otherValuesWerentTouched - }, - coverage = DoNotCalculate - ) - } - } - - @Test - fun testPutAllEntries() { - withPushingStateFromPathSelectorForConcrete { - check( - Maps::putAllEntries, - ge(5), - { map, _, _ -> map == null }, - { map, other, _ -> map != null && other == null }, - { map, other, result -> map != null && other != null && map.keys.containsAll(other.keys) && result == 0 }, - { map, other, result -> map != null && other != null && other.keys.all { it !in map.keys } && result == 1 }, - { map, other, result -> - val notNull = map != null && other != null - val mapContainsAtLeastOneKeyOfOther = other.keys.any { it in map.keys } - val mapDoesNotContainAllKeysOfOther = !map.keys.containsAll(other.keys) - notNull && mapContainsAtLeastOneKeyOfOther && mapDoesNotContainAllKeysOfOther && result == 2 - }, - coverage = DoNotCalculate - ) - } - } - - @Test - fun testReplaceAllEntries() { - check( - Maps::replaceAllEntries, - between(5..6), - { map, _ -> map == null }, - { map, result -> map.isEmpty() && result == null }, - { map, _ -> map.isNotEmpty() && map.containsValue(null) }, - { map, result -> - val precondition = map.isNotEmpty() && !map.containsValue(null) - val firstBranchInLambdaExists = map.entries.any { it.key > it.value } - val valuesWereReplaced = - result == map.mapValues { if (it.key > it.value) it.value + 1 else it.value - 1 } - precondition && firstBranchInLambdaExists && valuesWereReplaced - }, - { map, result -> - val precondition = map.isNotEmpty() && !map.containsValue(null) - val secondBranchInLambdaExists = map.entries.any { it.key <= it.value } - val valuesWereReplaced = - result == map.mapValues { if (it.key > it.value) it.value + 1 else it.value - 1 } - precondition && secondBranchInLambdaExists && valuesWereReplaced - }, - coverage = DoNotCalculate - ) - } - - -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/controlflow/ConditionsTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/controlflow/ConditionsTest.kt deleted file mode 100644 index b9a01de495..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/controlflow/ConditionsTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -package org.utbot.examples.controlflow - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.keyContain -import org.utbot.examples.keyMatch -import org.utbot.framework.plugin.api.DocCodeStmt -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.DocStatement -import org.junit.jupiter.api.Test - -internal class ConditionsTest : AbstractTestCaseGeneratorTest(testClass = Conditions::class) { - @Test - fun testSimpleCondition() { - val conditionSummary = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("executes conditions:\n"), - DocRegularStmt(" "), - DocCodeStmt("(condition): True"), - DocRegularStmt("\n"), - DocRegularStmt("returns from: "), - DocCodeStmt("return 1;"), - DocRegularStmt("\n"), - ) - ) - ) - check( - Conditions::simpleCondition, - eq(2), - { condition, r -> !condition && r == 0 }, - { condition, r -> condition && r == 1 }, - summaryTextChecks = listOf( - keyContain(DocCodeStmt("(condition): True")), - keyContain(DocCodeStmt("(condition): False")), - keyMatch(conditionSummary) - ), - summaryNameChecks = listOf( - keyMatch("testSimpleCondition_Condition"), - keyMatch("testSimpleCondition_NotCondition"), - ), - summaryDisplayNameChecks = listOf( - keyContain("condition : True"), - keyContain("condition : False"), - ) - ) - } - - @Test - fun testIfLastStatement() { - checkWithException( - Conditions::emptyBranches, - ignoreExecutionsNumber, - ) - } -} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/controlflow/CycleDependedConditionTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/controlflow/CycleDependedConditionTest.kt deleted file mode 100644 index 3bb944b466..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/controlflow/CycleDependedConditionTest.kt +++ /dev/null @@ -1,120 +0,0 @@ -package org.utbot.examples.controlflow - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.utbot.examples.keyContain -import org.utbot.examples.keyMatch -import org.utbot.framework.plugin.api.DocCodeStmt -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.DocStatement -import org.junit.jupiter.api.Test - -internal class CycleDependedConditionTest : AbstractTestCaseGeneratorTest(testClass = CycleDependedCondition::class) { - @Test - fun testCycleDependedOneCondition() { - val conditionSummary = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("does not iterate "), - DocCodeStmt("for(int i = 0; i < x; i++)"), - DocRegularStmt(", "), - DocRegularStmt("returns from: "), - DocCodeStmt("return 0;"), - DocRegularStmt("\n"), - ) - ) - ) - check( - CycleDependedCondition::oneCondition, - eq(3), - { x, r -> x <= 0 && r == 0 }, - { x, r -> x in 1..2 && r == 0 }, - { x, r -> x > 2 && r == 1 }, - summaryTextChecks = listOf( - keyContain(DocCodeStmt("(i == 2): True")), - keyContain(DocCodeStmt("(i == 2): False")), - keyMatch(conditionSummary) - ), - summaryNameChecks = listOf( - keyMatch("testOneCondition_IEquals2"), - keyMatch("testOneCondition_INotEquals2"), - keyMatch("testOneCondition_ReturnZero"), - ), - summaryDisplayNameChecks = listOf( - keyContain("i == 2 : True"), - keyContain("i == 2 : False"), - keyContain("return 0"), - ) - ) - } - - @Test - fun testCycleDependedTwoCondition() { - val conditionSummary = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("does not iterate "), - DocCodeStmt("for(int i = 0; i < x; i++)"), - DocRegularStmt(", "), - DocRegularStmt("returns from: "), - DocCodeStmt("return 0;"), - DocRegularStmt("\n"), - ) - ) - ) - check( - CycleDependedCondition::twoCondition, - eq(4), - { x, r -> x <= 0 && r == 0 }, - { x, r -> x in 1..3 && r == 0 }, - { x, r -> x == 4 && r == 1 }, - { x, r -> x >= 5 && r == 0 }, - summaryTextChecks = listOf( - keyContain(DocCodeStmt("(x == 4): False")), - keyContain(DocCodeStmt("(i > 2): True")), - keyContain(DocCodeStmt("(i > 2): False")), - keyMatch(conditionSummary) - ), - summaryNameChecks = listOf( - keyMatch("testTwoCondition_XNotEquals4"), - keyMatch("testTwoCondition_XEquals4"), - keyMatch("testTwoCondition_ILessOrEqual2"), - keyMatch("testTwoCondition_ReturnZero"), - ), - summaryDisplayNameChecks = listOf( - keyContain("x == 4 : False"), - keyContain("x == 4 : True"), - keyContain("i > 2 : False"), - keyContain("return 0"), - ) - ) - } - - - @Test - fun testCycleDependedThreeCondition() { - check( - CycleDependedCondition::threeCondition, - eq(4), - { x, r -> x <= 0 && r == 0 }, - { x, r -> x in 1..5 && r == 0 }, - { x, r -> x == 6 || x > 8 && r == 1 }, - { x, r -> x == 7 && r == 0 } - ) - } - - - @Test - fun testCycleDependedOneConditionHigherNumber() { - check( - CycleDependedCondition::oneConditionHigherNumber, - eq(3), - { x, r -> x <= 0 && r == 0 }, - { x, r -> x in 1..100 && r == 0 }, - { x, r -> x > 100 && r == 1 && r == 1 } - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/controlflow/CyclesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/controlflow/CyclesTest.kt deleted file mode 100644 index b49cde4e76..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/controlflow/CyclesTest.kt +++ /dev/null @@ -1,223 +0,0 @@ -package org.utbot.examples.controlflow - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.atLeast -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.examples.keyContain -import org.utbot.examples.keyMatch -import org.utbot.framework.plugin.api.DocCodeStmt -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.DocStatement -import org.junit.jupiter.api.Test - -internal class CyclesTest : AbstractTestCaseGeneratorTest(testClass = Cycles::class) { - @Test - fun testForCycle() { - check( - Cycles::forCycle, - eq(3), - { x, r -> x <= 0 && r == -1 }, - { x, r -> x in 1..5 && r == -1 }, - { x, r -> x > 5 && r == 1 } - ) - } - - @Test - fun testForCycleFour() { - val cycleSummary = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("does not iterate "), - DocCodeStmt("for(int i = 0; i < x; i++)"), - DocRegularStmt(", "), - DocRegularStmt("returns from: "), - DocCodeStmt("return -1;"), - DocRegularStmt("\n"), - ) - ) - ) - check( - Cycles::forCycleFour, - eq(3), - { x, r -> x <= 0 && r == -1 }, - { x, r -> x in 1..4 && r == -1 }, - { x, r -> x > 4 && r == 1 }, - summaryTextChecks = listOf( - keyContain(DocCodeStmt("(i > 4): True")), - keyContain(DocCodeStmt("(i > 4): False")), - keyMatch(cycleSummary) - ), - summaryNameChecks = listOf( - keyMatch("testForCycleFour_IGreaterThan4"), - keyMatch("testForCycleFour_ILessOrEqual4"), - keyMatch("testForCycleFour_ReturnNegative1") - ), - summaryDisplayNameChecks = listOf( - keyContain("i > 4 : True"), - keyContain("i > 4 : False"), - keyContain("return -1") - ) - ) - } - - @Test - fun testForCycleJayHorn() { - check( - Cycles::forCycleFromJayHorn, - eq(2), - { x, r -> x <= 0 && r == 0 }, - { x, r -> x > 0 && r == 2 * x } - ) - } - - @Test - fun testFiniteCycle() { - check( - Cycles::finiteCycle, - eq(2), - { x, r -> x % 519 == 0 && (r as Int) % 519 == 0 }, - { x, r -> x % 519 != 0 && (r as Int) % 519 == 0 } - ) - } - - @Test - fun testWhileCycle() { - check( - Cycles::whileCycle, - eq(2), - { x, r -> x <= 0 && r == 0 }, - { x, r -> x > 0 && r == (0 until x).sum() } - ) - } - - @Test - fun testCallInnerWhile() { - check( - Cycles::callInnerWhile, - between(1..2), - { x, r -> x >= 42 && r == Cycles().callInnerWhile(x) }, - summaryTextChecks = listOf( - keyContain(DocCodeStmt("return innerWhile(value, 42);")), - ), - summaryNameChecks = listOf( - keyMatch("testCallInnerWhile_IterateWhileLoop") - ) - ) - } - - @Test - fun testInnerLoop() { - val innerLoopSummary1 = arrayOf( - DocRegularStmt("Test "), - DocRegularStmt("calls "), - DocRegularStmt("CycleDependedCondition::twoCondition"), - DocRegularStmt(",\n there it "), - DocRegularStmt("iterates the loop "), - DocRegularStmt(""), - DocCodeStmt("for(int i = 0; i < x; i++)"), - DocRegularStmt(" "), - DocRegularStmt("once"), - DocRegularStmt(""), - DocRegularStmt(". "), - DocRegularStmt("\n Test "), -// DocRegularStmt("afterwards "), - DocRegularStmt("returns from: "), - DocCodeStmt("return 0;"), - DocRegularStmt("\n "), - DocRegularStmt("\nTest "), -// DocRegularStmt("afterwards "), - DocRegularStmt("returns from: "), - DocCodeStmt("return cycleDependedCondition.twoCondition(value);"), - DocRegularStmt("\n"), - ) - val innerLoopSummary2 = arrayOf( - DocRegularStmt("Test "), - DocRegularStmt("calls "), - DocRegularStmt("CycleDependedCondition::twoCondition"), - DocRegularStmt(",\n there it "), - DocRegularStmt("iterates the loop "), - DocRegularStmt(""), - DocCodeStmt("for(int i = 0; i < x; i++)"), - DocRegularStmt(" "), - DocRegularStmt("4 times"), - DocRegularStmt(""), - DocRegularStmt(",\n "), - DocRegularStmt(" "), - DocRegularStmt("inside this loop, the test "), - DocRegularStmt("executes conditions:\n "), - DocRegularStmt(""), - DocCodeStmt("(i > 2): True"), - DocRegularStmt(",\n "), - DocCodeStmt("(x == 4): True"), - DocRegularStmt("\n returns from: "), - DocCodeStmt("return 1;"), - DocRegularStmt("\nTest "), -// DocRegularStmt("afterwards "), - DocRegularStmt("returns from: "), - DocCodeStmt("return cycleDependedCondition.twoCondition(value);"), - DocRegularStmt("\n"), - ) - val innerLoopSummary3 = arrayOf( - DocRegularStmt("Test "), - DocRegularStmt("calls "), - DocRegularStmt("CycleDependedCondition::twoCondition"), - DocRegularStmt(",\n there it "), - DocRegularStmt("iterates the loop "), - DocCodeStmt("for(int i = 0; i < x; i++)"), - DocRegularStmt("5 times"), - DocRegularStmt("inside this loop, the test "), - DocRegularStmt("executes conditions:\n "), - DocCodeStmt("(x == 4): False"), - DocRegularStmt("\n Test "), - DocRegularStmt("returns from: "), - DocCodeStmt("return 0;"), - DocCodeStmt("return cycleDependedCondition.twoCondition(value);"), - ) - check( - Cycles::innerLoop, - ignoreExecutionsNumber, - { x, r -> x in 1..3 && r == 0 }, - { x, r -> x == 4 && r == 1 }, - { x, r -> x >= 5 && r == 0 }, - // TODO JIRA:1442 -/* summaryTextChecks = listOf( - keyContain(*innerLoopSummary1), - keyContain(*innerLoopSummary2), - keyContain(*innerLoopSummary3) - ), - summaryNameChecks = listOf( - keyMatch("testInnerLoop_ReturnZero"), - keyMatch("testInnerLoop_XEquals4"), - keyMatch("testInnerLoop_XNotEquals4") - )*/ - ) - } - - @Test - fun testDivideByZeroCheckWithCycles() { - checkWithException( - Cycles::divideByZeroCheckWithCycles, - eq(3), - { n, _, r -> n < 5 && r.isException() }, - { n, x, r -> n >= 5 && x == 0 && r.isException() }, - { n, x, r -> n >= 5 && x != 0 && r.getOrNull() == Cycles().divideByZeroCheckWithCycles(n, x) } - ) - } - - @Test - fun moveToExceptionTest() { - checkWithException( - Cycles::moveToException, - eq(3), - { x, r -> x < 400 && r.isException() }, - { x, r -> x > 400 && r.isException() }, - { x, r -> x == 400 && r.isException() }, - coverage = atLeast(85) - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/controlflow/SwitchTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/controlflow/SwitchTest.kt deleted file mode 100644 index ede219ef32..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/controlflow/SwitchTest.kt +++ /dev/null @@ -1,91 +0,0 @@ -package org.utbot.examples.controlflow - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.keyContain -import org.utbot.examples.keyMatch -import org.utbot.examples.withoutMinimization -import org.utbot.framework.plugin.api.DocCodeStmt -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.DocStatement -import java.math.RoundingMode.CEILING -import java.math.RoundingMode.DOWN -import java.math.RoundingMode.HALF_DOWN -import java.math.RoundingMode.HALF_EVEN -import java.math.RoundingMode.HALF_UP -import org.junit.jupiter.api.Test - -internal class SwitchTest : AbstractTestCaseGeneratorTest(testClass = Switch::class) { - @Test - fun testSimpleSwitch() { - val switchCaseSummary = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("activates switch case: "), - DocCodeStmt("default"), - DocRegularStmt(", "), - DocRegularStmt("returns from: "), - DocCodeStmt("return -1;"), - DocRegularStmt("\n"), - ) - ) - ) - check( - Switch::simpleSwitch, - ge(4), - { x, r -> x == 10 && r == 10 }, - { x, r -> (x == 11 || x == 12) && r == 12 }, // fall-through has it's own branch - { x, r -> x == 13 && r == 13 }, - { x, r -> x !in 10..13 && r == -1 }, // one for default is enough - summaryTextChecks = listOf( - keyContain(DocCodeStmt("return 10;")), - keyContain(DocCodeStmt("return 12;")), - keyContain(DocCodeStmt("return 12;")), - keyContain(DocCodeStmt("return 13;")), - keyMatch(switchCaseSummary) - ), - summaryNameChecks = listOf( - keyMatch("testSimpleSwitch_Return10"), - keyMatch("testSimpleSwitch_Return13"), - keyMatch("testSimpleSwitch_ReturnNegative1"), - ), - summaryDisplayNameChecks = listOf( - keyMatch("switch(x) case: 10 -> return 10"), - keyMatch("switch(x) case: 13 -> return 13"), - keyMatch("switch(x) case: Default -> return -1"), - ) - ) - } - - @Test - fun testLookupSwitch() { - check( - Switch::lookupSwitch, - ge(4), - { x, r -> x == 0 && r == 0 }, - { x, r -> (x == 10 || x == 20) && r == 20 }, // fall-through has it's own branch - { x, r -> x == 30 && r == 30 }, - { x, r -> x !in setOf(0, 10, 20, 30) && r == -1 } // one for default is enough - ) - } - - @Test - fun testEnumSwitch() { - withoutMinimization { // TODO: JIRA:1506 - check( - Switch::enumSwitch, - eq(7), - { m, r -> m == null && r == null }, // NPE - { m, r -> m == HALF_DOWN && r == 1 }, // We will minimize two of these branches - { m, r -> m == HALF_EVEN && r == 1 }, // We will minimize two of these branches - { m, r -> m == HALF_UP && r == 1 }, // We will minimize two of these branches - { m, r -> m == DOWN && r == 2 }, - { m, r -> m == CEILING && r == 3 }, - { m, r -> m !in setOf(HALF_DOWN, HALF_EVEN, HALF_UP, DOWN, CEILING) && r == -1 }, - ) - } - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt deleted file mode 100644 index 079bf1d01c..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/enums/ClassWithEnumTest.kt +++ /dev/null @@ -1,197 +0,0 @@ -package org.utbot.examples.enums - -import org.utbot.common.findField -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.enums.ClassWithEnum.StatusEnum.ERROR -import org.utbot.examples.enums.ClassWithEnum.StatusEnum.READY -import org.utbot.examples.eq -import org.utbot.examples.isException -import org.utbot.examples.withPushingStateFromPathSelectorForConcrete -import org.utbot.examples.withoutConcrete -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.util.id -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test - -class ClassWithEnumTest : AbstractTestCaseGeneratorTest(testClass = ClassWithEnum::class) { - @Test - fun testOrdinal() { - withoutConcrete { - checkAllCombinations(ClassWithEnum::useOrdinal) - } - } - - @Test - fun testGetter() { - check( - ClassWithEnum::useGetter, - eq(2), - { s, r -> s == null && r == -1 }, - { s, r -> s != null && r == 0 }, - ) - } - - @Test - fun testDifficultIfBranch() { - check( - ClassWithEnum::useEnumInDifficultIf, - eq(2), - { s, r -> s.equals("TRYIF", ignoreCase = true) && r == 1 }, - { s, r -> !s.equals("TRYIF", ignoreCase = true) && r == 2 }, - ) - } - - @Test - @Disabled("TODO JIRA:1686") - fun testNullParameter() { - check( - ClassWithEnum::nullEnumAsParameter, - eq(3), - { e, _ -> e == null }, - { e, r -> e == READY && r == 0 }, - { e, r -> e == ERROR && r == -1 }, - ) - } - - @Test - @Disabled("TODO JIRA:1686") - fun testNullField() { - checkWithException( - ClassWithEnum::nullField, - eq(3), - { e, r -> e == null && r.isException() }, - { e, r -> e == ERROR && r.isException() }, - { e, r -> e == READY && r.getOrNull()!! == 3 && READY.s.length == 3 }, - ) - } - - @Test - @Disabled("TODO JIRA:1686") - fun testChangeEnum() { - checkWithException( - ClassWithEnum::changeEnum, - eq(3), - { e, r -> e == null && r.isException() }, - { e, r -> e == READY && r.getOrNull()!! == ERROR.ordinal }, - { e, r -> e == ERROR && r.getOrNull()!! == READY.ordinal }, - ) - } - - @Test - fun testChangeMutableField() { - // TODO testing code generation for this method is disabled because we need to restore original field state - // should be enabled after solving JIRA:1648 - withEnabledTestingCodeGeneration(testCodeGeneration = false) { - checkWithException( - ClassWithEnum::changeMutableField, - eq(2), - { e, r -> e == READY && r.getOrNull()!! == 2 }, - { e, r -> (e == null || e == ERROR) && r.getOrNull()!! == -2 }, - ) - } - } - - @Test - @Disabled("TODO JIRA:1686") - fun testCheckName() { - check( - ClassWithEnum::checkName, - eq(3), - { s, _ -> s == null }, - { s, r -> s == READY.name && r == ERROR.name }, - { s, r -> s != READY.name && r == READY.name }, - ) - } - - @Test - fun testChangingStaticWithEnumInit() { - checkThisAndStaticsAfter( - ClassWithEnum::changingStaticWithEnumInit, - eq(1), - { t, staticsAfter, r -> - // for some reasons x is inaccessible - val x = t.javaClass.findField("x").get(t) as Int - - val y = staticsAfter[FieldId(ClassWithEnum.ClassWithStaticField::class.id, "y")]!!.value as Int - - val areStaticsCorrect = x == 1 && y == 11 - areStaticsCorrect && r == true - } - ) - } - - @Test - fun testEnumValues() { - checkStaticMethod( - ClassWithEnum.StatusEnum::values, - eq(1), - { r -> r.contentEquals(arrayOf(READY, ERROR)) }, - ) - } - - @Test - fun testFromCode() { - checkStaticMethod( - ClassWithEnum.StatusEnum::fromCode, - eq(3), - { code, r -> code == 10 && r == READY }, - { code, r -> code == -10 && r == ERROR }, - { code, r -> code !in setOf(10, -10) && r == null }, // IllegalArgumentException - ) - } - - @Test - fun testFromIsReady() { - checkStaticMethod( - ClassWithEnum.StatusEnum::fromIsReady, - eq(2), - { isFirst, r -> isFirst && r == READY }, - { isFirst, r -> !isFirst && r == ERROR }, - ) - } - - @Test - @Disabled("TODO JIRA:1450") - fun testPublicGetCodeMethod() { - checkWithThis( - ClassWithEnum.StatusEnum::publicGetCode, - eq(2), - { enumInstance, r -> enumInstance == READY && r == 10 }, - { enumInstance, r -> enumInstance == ERROR && r == -10 }, - coverage = DoNotCalculate - ) - } - - @Test - fun testImplementingInterfaceEnumInDifficultBranch() { - withPushingStateFromPathSelectorForConcrete { - check( - ClassWithEnum::implementingInterfaceEnumInDifficultBranch, - eq(2), - { s, r -> s.equals("SUCCESS", ignoreCase = true) && r == 0 }, - { s, r -> !s.equals("SUCCESS", ignoreCase = true) && r == 2 }, - ) - } - } - - @Test - fun testAffectSystemStaticAndUseInitEnumFromIt() { - check( - ClassWithEnum::affectSystemStaticAndInitEnumFromItAndReturnField, - eq(1), - { r -> r == true }, - coverage = DoNotCalculate - ) - } - - @Test - fun testAffectSystemStaticAndInitEnumFromItAndGetItFromEnumFun() { - check( - ClassWithEnum::affectSystemStaticAndInitEnumFromItAndGetItFromEnumFun, - eq(1), - { r -> r == true }, - coverage = DoNotCalculate - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/ExceptionClusteringExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/ExceptionClusteringExamplesTest.kt deleted file mode 100644 index 36bbe81361..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/ExceptionClusteringExamplesTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package org.utbot.examples.exceptions - -import org.utbot.examples.AbstractModelBasedTest -import org.utbot.examples.ge -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.primitiveValue -import org.utbot.framework.plugin.api.UtExecutionSuccess -import org.utbot.framework.plugin.api.UtExplicitlyThrownException -import org.utbot.framework.plugin.api.UtImplicitlyThrownException -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtTimeoutException -import org.junit.jupiter.api.Test - -internal class ExceptionClusteringExamplesTest : - AbstractModelBasedTest(testClass = ExceptionClusteringExamples::class) { - /** - * Difference is in throwing unchecked exceptions - for method under test is [UtExpectedCheckedThrow]. - */ - @Test - fun testDifferentExceptions() { - check( - ExceptionClusteringExamples::differentExceptions, - ignoreExecutionsNumber, - { i, r -> i.int() == 0 && r is UtImplicitlyThrownException && r.exception is ArithmeticException }, - { i, r -> i.int() == 1 && r is UtExplicitlyThrownException && r.exception is MyCheckedException }, - { i, r -> i.int() == 2 && r is UtExplicitlyThrownException && r.exception is IllegalArgumentException }, - { i, r -> i.int() !in 0..2 && r is UtExecutionSuccess && r.model.int() == 2 * i.int() }, - ) - } - - /** - * Difference is in throwing unchecked exceptions - for nested call it is [UtUnexpectedUncheckedThrow]. - */ - @Test - fun testDifferentExceptionsInNestedCall() { - check( - ExceptionClusteringExamples::differentExceptionsInNestedCall, - ignoreExecutionsNumber, - { i, r -> i.int() == 0 && r is UtImplicitlyThrownException && r.exception is ArithmeticException }, - { i, r -> i.int() == 1 && r is UtExplicitlyThrownException && r.exception is MyCheckedException }, - { i, r -> i.int() == 2 && r is UtExplicitlyThrownException && r.exception is IllegalArgumentException }, - { i, r -> i.int() !in 0..2 && r is UtExecutionSuccess && r.model.int() == 2 * i.int() }, - ) - } - - @Test - fun testSleepingMoreThanDefaultTimeout() { - check( - ExceptionClusteringExamples::sleepingMoreThanDefaultTimeout, - ge(1), - { _, r -> r is UtTimeoutException }, // we will minimize one of these: i <= 0 or i > 0 - ) - } -} - -private fun UtModel.int(): Int = this.primitiveValue() - diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/ExceptionExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/ExceptionExamplesTest.kt deleted file mode 100644 index 7ff4b3c412..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/ExceptionExamplesTest.kt +++ /dev/null @@ -1,122 +0,0 @@ -package org.utbot.examples.exceptions - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.atLeast -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import org.junit.jupiter.api.Test - -internal class ExceptionExamplesTest : AbstractTestCaseGeneratorTest( - testClass = ExceptionExamples::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) // TODO: fails because we construct lists with generics - ) -) { - @Test - fun testInitAnArray() { - check( - ExceptionExamples::initAnArray, - ignoreExecutionsNumber, - { n, r -> n < 0 && r == -2 }, - { n, r -> n == 0 || n == 1 && r == -3 }, - { n, r -> n > 1 && r == 2 * n + 3 }, - coverage = atLeast(80) - ) - } - - @Test - fun testNestedExceptions() { - check( - ExceptionExamples::nestedExceptions, - eq(3), - { i, r -> i < 0 && r == -100 }, - { i, r -> i > 0 && r == 100 }, - { i, r -> i == 0 && r == 0 }, - ) - } - - @Test - fun testDoNotCatchNested() { - checkWithException( - ExceptionExamples::doNotCatchNested, - eq(3), - { i, r -> i < 0 && r.isException() }, - { i, r -> i > 0 && r.isException() }, - { i, r -> i == 0 && r.getOrThrow() == 0 }, - ) - } - - @Test - fun testFinallyThrowing() { - checkWithException( - ExceptionExamples::finallyThrowing, - eq(2), - { i, r -> i <= 0 && r.isException() }, - { i, r -> i > 0 && r.isException() }, - ) - } - - @Test - fun testFinallyChanging() { - check( - ExceptionExamples::finallyChanging, - eq(2), - { i, r -> i * 2 <= 0 && r == i * 2 + 10 }, - { i, r -> i * 2 > 0 && r == i * 2 + 110 }, - coverage = atLeast(80) // differs from JaCoCo - ) - } - - @Test - fun testThrowException() { - checkWithException( - ExceptionExamples::throwException, - eq(2), - { i, r -> i <= 0 && r.getOrNull() == 101 }, - { i, r -> i > 0 && r.isException() }, - coverage = atLeast(66) // because of unexpected exception thrown - ) - } - - @Test - fun testCreateException() { - check( - ExceptionExamples::createException, - eq(1), - { r -> r is java.lang.IllegalArgumentException }, - ) - } - - /** - * Used for path generation check in [org.utbot.engine.UtBotSymbolicEngine.fullPath] - */ - @Test - fun testCatchDeepNestedThrow() { - checkWithException( - ExceptionExamples::catchDeepNestedThrow, - eq(2), - { i, r -> i < 0 && r.isException() }, - { i, r -> i >= 0 && r.getOrThrow() == i }, - coverage = atLeast(66) // because of unexpected exception thrown - ) - } - - /** - * Used for path generation check in [org.utbot.engine.UtBotSymbolicEngine.fullPath] - */ - @Test - fun testDontCatchDeepNestedThrow() { - checkWithException( - ExceptionExamples::dontCatchDeepNestedThrow, - eq(2), - { i, r -> i < 0 && r.isException() }, - { i, r -> i >= 0 && r.getOrThrow() == i }, - coverage = atLeast(66) // because of unexpected exception thrown - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/JvmCrashExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/JvmCrashExamplesTest.kt deleted file mode 100644 index adee01ba08..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/exceptions/JvmCrashExamplesTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.utbot.examples.exceptions - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.primitiveValue -import org.utbot.framework.plugin.api.UtModel -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test - -internal class JvmCrashExamplesTest : AbstractTestCaseGeneratorTest(testClass = JvmCrashExamples::class) { - @Test - @Disabled("JIRA:1527") - fun testExit() { - check( - JvmCrashExamples::exit, - eq(2) - ) - } - - @Test - fun testCrash() { - check( - JvmCrashExamples::crash, - eq(1), // we expect only one execution after minimization - coverage = DoNotCalculate - ) - } -} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/invokes/StaticInvokeExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/invokes/StaticInvokeExampleTest.kt deleted file mode 100644 index c72af5b6b4..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/invokes/StaticInvokeExampleTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.utbot.examples.invokes - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.between -import kotlin.math.max -import org.junit.jupiter.api.Test - -internal class StaticInvokeExampleTest : AbstractTestCaseGeneratorTest(testClass = StaticInvokeExample::class) { - // TODO: inline local variables when types inference bug in Kotlin fixed - @Test - fun testMaxForThree() { - val method = StaticInvokeExample::maxForThree - checkStaticMethod( - method, - between(2..3), // two executions can cover all branches - { x, y, _, _ -> x > y }, - { x, y, _, _ -> x <= y }, - { x, y, z, _ -> max(x, y.toInt()) > z }, - { x, y, z, _ -> max(x, y.toInt()) <= z }, - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt deleted file mode 100644 index ecd4a8976e..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/lambda/SimpleLambdaExamplesTest.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.utbot.examples.lambda - -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.utbot.examples.isException - -class SimpleLambdaExamplesTest : AbstractTestCaseGeneratorTest(testClass = SimpleLambdaExamples::class) { - @Test - fun testBiFunctionLambdaExample() { - checkWithException( - SimpleLambdaExamples::biFunctionLambdaExample, - eq(2), - { _, b, r -> b == 0 && r.isException() }, - { a, b, r -> b != 0 && r.getOrThrow() == a / b }, - ) - } - - @Test - @Disabled("TODO 0 executions https://github.com/UnitTestBot/UTBotJava/issues/192") - fun testChoosePredicate() { - check( - SimpleLambdaExamples::choosePredicate, - eq(2), - { b, r -> b && !r!!.test(null) && r.test(0) }, - { b, r -> !b && r!!.test(null) && !r.test(0) }, - // TODO coverage calculation fails https://github.com/UnitTestBot/UTBotJava/issues/192 - ) - } -} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/math/OverflowAsErrorTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/math/OverflowAsErrorTest.kt deleted file mode 100644 index bd389a2210..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/math/OverflowAsErrorTest.kt +++ /dev/null @@ -1,287 +0,0 @@ -package org.utbot.examples.math - -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.AtLeast -import org.utbot.examples.algorithms.Sort -import org.utbot.examples.eq -import org.utbot.examples.ignoreExecutionsNumber -import org.utbot.examples.isException -import org.utbot.examples.withSolverTimeoutInMillis -import org.utbot.examples.withTreatingOverflowAsError -import org.utbot.framework.codegen.Compilation -import org.utbot.framework.plugin.api.CodegenLanguage -import kotlin.math.floor -import kotlin.math.sqrt - -internal class OverflowAsErrorTest : AbstractTestCaseGeneratorTest( - testClass = OverflowExamples::class, - testCodeGeneration = true, - // Don't launch tests, because ArithmeticException will be expected, but it is not supposed to be actually thrown. - // ArithmeticException acts as a sign of Overflow. - listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA, Compilation), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, Compilation), - ) -) { - @Test - fun testIntOverflow() { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::intOverflow, - eq(5), - { x, _, r -> x * x * x <= 0 && x > 0 && r.isException() }, // through overflow - { x, _, r -> x * x * x <= 0 && x > 0 && r.isException() }, // through overflow (2nd '*') - { x, _, r -> x * x * x >= 0 && x >= 0 && r.getOrNull() == 0 }, - { x, y, r -> x * x * x > 0 && x > 0 && y == 10 && r.getOrNull() == 1 }, - { x, y, r -> x * x * x > 0 && x > 0 && y != 10 && r.getOrNull() == 0 }, - coverage = AtLeast(90), - ) - } - } - - @Test - fun testByteAddOverflow() { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::byteAddOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { x, y, r -> - val negOverflow = ((x + y).toByte() >= 0 && x < 0 && y < 0) - val posOverflow = ((x + y).toByte() <= 0 && x > 0 && y > 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) - } - } - - @Test - fun testByteSubOverflow() { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::byteSubOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { x, y, r -> - val negOverflow = ((x - y).toByte() >= 0 && x < 0 && y > 0) - val posOverflow = ((x - y).toByte() <= 0 && x > 0 && y < 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) - } - } - - @Test - fun testByteMulOverflow() { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::byteMulOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { _, _, r -> r.isException() }, // through overflow - ) - } - } - - @Test - fun testShortAddOverflow() { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::shortAddOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { x, y, r -> - val negOverflow = ((x + y).toShort() >= 0 && x < 0 && y < 0) - val posOverflow = ((x + y).toShort() <= 0 && x > 0 && y > 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) - } - } - - @Test - fun testShortSubOverflow() { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::shortSubOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { x, y, r -> - val negOverflow = ((x - y).toShort() >= 0 && x < 0 && y > 0) - val posOverflow = ((x - y).toShort() <= 0 && x > 0 && y < 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) - } - } - - @Test - fun testShortMulOverflow() { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::shortMulOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { _, _, r -> r.isException() }, // through overflow - ) - } - } - - @Test - fun testIntAddOverflow() { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::intAddOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { x, y, r -> - val negOverflow = ((x + y) >= 0 && x < 0 && y < 0) - val posOverflow = ((x + y) <= 0 && x > 0 && y > 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) - } - } - - @Test - fun testIntSubOverflow() { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::intSubOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { x, y, r -> - val negOverflow = ((x - y) >= 0 && x < 0 && y > 0) - val posOverflow = ((x - y) <= 0 && x > 0 && y < 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) - } - } - - @Test - fun testIntMulOverflow() { - // This test has solver timeout. - // Reason: softConstraints, containing limits for Int values, hang solver. - // With solver timeout softConstraints are dropped and hard constraints are SAT for overflow. - withSolverTimeoutInMillis(timeoutInMillis = 1000) { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::intMulOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { _, _, r -> r.isException() }, // through overflow - ) - } - } - } - - @Test - fun testLongAddOverflow() { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::longAddOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { x, y, r -> - val negOverflow = ((x + y) >= 0 && x < 0 && y < 0) - val posOverflow = ((x + y) <= 0 && x > 0 && y > 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) - } - } - - @Test - fun testLongSubOverflow() { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::longSubOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { x, y, r -> - val negOverflow = ((x - y) >= 0 && x < 0 && y > 0) - val posOverflow = ((x - y) <= 0 && x > 0 && y < 0) - (negOverflow || posOverflow) && r.isException() - }, // through overflow - ) - } - } - - @Test - @Disabled("Flaky branch count mismatch (1 instead of 2)") - fun testLongMulOverflow() { - // This test has solver timeout. - // Reason: softConstraints, containing limits for Int values, hang solver. - // With solver timeout softConstraints are dropped and hard constraints are SAT for overflow. - withSolverTimeoutInMillis(timeoutInMillis = 2000) { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::longMulOverflow, - eq(2), - { _, _, r -> !r.isException() }, - { _, _, r -> r.isException() }, // through overflow - ) - } - } - } - - @Test - fun testIncOverflow() { - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::incOverflow, - eq(2), - { _, r -> !r.isException() }, - { _, r -> r.isException() }, // through overflow - ) - } - } - - @Test - fun testIntCubeOverflow() { - val sqrtIntMax = floor(sqrt(Int.MAX_VALUE.toDouble())).toInt() - withTreatingOverflowAsError { - checkWithException( - OverflowExamples::intCubeOverflow, - eq(3), - { _, r -> !r.isException() }, - // Can't use abs(x) below, because abs(Int.MIN_VALUE) == Int.MIN_VALUE. - // (Int.MAX_VALUE shr 16) is the border of square overflow and cube overflow. - // Int.MAX_VALUE.toDouble().pow(1/3.toDouble()) - { x, r -> (x > -sqrtIntMax && x < sqrtIntMax) && r.isException() }, // through overflow - { x, r -> (x <= -sqrtIntMax || x >= sqrtIntMax) && r.isException() }, // through overflow - ) - } - } - -// Generated Kotlin code does not compile, so disabled for now - @Test - @Disabled - fun testQuickSort() { - withTreatingOverflowAsError { - checkWithException( - Sort::quickSort, - ignoreExecutionsNumber, - { _, _, _, r -> !r.isException() }, - { _, _, _, r -> r.isException() }, // through overflow - ) - } - } - - @Test - fun testIntOverflowWithoutError() { - check( - OverflowExamples::intOverflow, - eq(6), - { x, _, r -> x * x * x <= 0 && x <= 0 && r == 0 }, - { x, _, r -> x * x * x > 0 && x <= 0 && r == 0 }, // through overflow - { x, y, r -> x * x * x > 0 && x > 0 && y != 10 && r == 0 }, - { x, y, r -> x * x * x > 0 && x > 0 && y == 10 && r == 1 }, - { x, y, r -> x * x * x <= 0 && x > 0 && y != 20 && r == 0 }, // through overflow - { x, y, r -> x * x * x <= 0 && x > 0 && y == 20 && r == 2 } // through overflow - ) - } -} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/MonitorUsageTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/MonitorUsageTest.kt deleted file mode 100644 index 16a9b88d24..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/MonitorUsageTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.utbot.examples.mixed - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.atLeast -import org.utbot.examples.ignoreExecutionsNumber -import org.junit.jupiter.api.Test - -internal class MonitorUsageTest : AbstractTestCaseGeneratorTest(testClass = MonitorUsage::class) { - @Test - fun testSimpleMonitor() { - check( - MonitorUsage::simpleMonitor, - ignoreExecutionsNumber, - { x, r -> x <= 0 && r == 0 }, - { x, r -> x > 0 && x <= Int.MAX_VALUE - 1 && r == 1 }, - coverage = atLeast(81) // differs from JaCoCo - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/PrivateConstructorExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/PrivateConstructorExampleTest.kt deleted file mode 100644 index 934f605280..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/PrivateConstructorExampleTest.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.utbot.examples.mixed - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.junit.jupiter.api.Test - -internal class PrivateConstructorExampleTest : AbstractTestCaseGeneratorTest(testClass = PrivateConstructorExample::class) { - - /** - * Two branches need to be covered: - * 1. argument must be <= a - b, - * 2. argument must be > a - b - * - * a and b are fields of the class under test - */ - @Test - fun testLimitedSub() { - checkWithThis( - PrivateConstructorExample::limitedSub, - eq(2), - { caller, limit, r -> caller.a - caller.b >= limit && r == caller.a - caller.b }, - { caller, limit, r -> caller.a - caller.b < limit && r == limit }, - coverage = DoNotCalculate // TODO: Method coverage with `this` parameter isn't supported - ) - } -} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/SimpleNoConditionTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/SimpleNoConditionTest.kt deleted file mode 100644 index bac0b0f1fc..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/SimpleNoConditionTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.utbot.examples.mixed - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.junit.jupiter.api.Test - -internal class SimpleNoConditionTest : AbstractTestCaseGeneratorTest(testClass = SimpleNoCondition::class) { - - @Test - fun testNoConditionAdd() { - check( - SimpleNoCondition::basicAdd, - eq(1) - ) - } - - @Test - fun testNoConditionPow() { - check( - SimpleNoCondition::basicXorInt, - eq(1) - ) - } - - @Test - fun testNoConditionPowBoolean() { - check( - SimpleNoCondition::basicXorBoolean, - eq(1) - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/SimplifierTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/SimplifierTest.kt deleted file mode 100644 index f5143d743d..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mixed/SimplifierTest.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.utbot.examples.mixed - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.junit.jupiter.api.Test - -internal class SimplifierTest: AbstractTestCaseGeneratorTest(testClass = Simplifier::class) { - @Test - fun testSimplifyAdditionWithZero() { - check( - Simplifier::simplifyAdditionWithZero, - eq(1), - { fst, r -> r != null && r.x == fst.shortValue.toInt() }, - coverage = DoNotCalculate // because of assumes - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/CommonMocksExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mock/CommonMocksExampleTest.kt deleted file mode 100644 index b51015d797..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/CommonMocksExampleTest.kt +++ /dev/null @@ -1,59 +0,0 @@ -package org.utbot.examples.mock - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.framework.plugin.api.MockStrategyApi -import org.junit.jupiter.api.Test - -internal class CommonMocksExampleTest: AbstractTestCaseGeneratorTest(testClass = CommonMocksExample::class) { - @Test - fun testMockInterfaceWithoutImplementors() { - checkMocks( - CommonMocksExample::mockInterfaceWithoutImplementors, - eq(2), - { v, mocks, _ -> v == null && mocks.isEmpty() }, - { _, mocks, _ -> mocks.singleOrNull() != null }, - coverage = DoNotCalculate - ) - } - - // TODO JIRA:1449 - @Test - fun testDoNotMockEquals() { - checkMocks( - CommonMocksExample::doNotMockEquals, - eq(2), - { fst, _, mocks, _ -> fst == null && mocks.isEmpty() }, - { _, _, mocks, _ -> mocks.isEmpty() }, // should be changed to not null fst when 1449 will be finished - mockStrategy = MockStrategyApi.OTHER_PACKAGES, - coverage = DoNotCalculate - ) - } - - // TODO JIRA:1449 - @Test - fun testNextValue() { - checkMocks( - CommonMocksExample::nextValue, - eq(4), - // node == null -> NPE - // node.next == null -> NPE - // node == node.next - // node.next.value == node.value + 1 - mockStrategy = MockStrategyApi.OTHER_CLASSES, - coverage = DoNotCalculate - ) - } - - @Test - fun testClinitMockExample() { - check( - CommonMocksExample::clinitMockExample, - eq(1), - { r -> r == -420 }, - mockStrategy = MockStrategyApi.OTHER_CLASSES, - coverage = DoNotCalculate - ) - } -} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/InnerMockWithFieldExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mock/InnerMockWithFieldExampleTest.kt deleted file mode 100644 index 3e02c56651..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/InnerMockWithFieldExampleTest.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.utbot.examples.mock - -import org.utbot.examples.AbstractModelBasedTest -import org.utbot.examples.eq -import org.utbot.examples.primitiveValue -import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_PACKAGES -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.getOrThrow -import org.utbot.framework.plugin.api.isMockModel -import org.utbot.framework.plugin.api.isNotNull -import org.utbot.framework.plugin.api.isNull -import org.junit.jupiter.api.Test - -internal class InnerMockWithFieldExampleTest : AbstractModelBasedTest(testClass = InnerMockWithFieldExample::class) { - @Test - fun testCheckAndUpdate() { - checkStatic( - InnerMockWithFieldExample::checkAndUpdate, - eq(4), - { example, r -> example.isNull() && r.isException() }, - { example, r -> example.isNotNull() && example.stamp.isNull() && r.isException() }, - { example, r -> - val result = r.getOrThrow() - val isMockModels = example.stamp.isMockModel() && result.isMockModel() - val stampConstraint = example.stamp.initial > example.stamp.version - val postcondition = result.initial == example.stamp.initial && result.version == result.initial - - isMockModels && stampConstraint && postcondition - }, - { example, r -> - val result = r.getOrThrow() - val stamp = example.stamp - - val isMockModels = stamp.isMockModel() && result.isMockModel() - val stampConstraint = stamp.initial <= stamp.version - val postcondition = result.initial == stamp.initial && result.version == stamp.version + 1 - - isMockModels && stampConstraint && postcondition - }, - mockStrategy = OTHER_PACKAGES - ) - } - - private val UtModel.stamp: UtModel - get() = findField("stamp") - - private val UtModel.initial: Int - get() = findField("initial").primitiveValue() - - private val UtModel.version: Int - get() = findField("version").primitiveValue() -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockFinalClassTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockFinalClassTest.kt deleted file mode 100644 index c0f3286a1b..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockFinalClassTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.utbot.examples.mock - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.ge -import org.utbot.examples.mock.others.FinalClass -import org.utbot.examples.singleMock -import org.utbot.examples.value -import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_CLASSES -import org.junit.jupiter.api.Test - -internal class MockFinalClassTest : AbstractTestCaseGeneratorTest(testClass = MockReturnObjectExample::class) { - @Test - fun testFinalClass() { - checkMocks( - MockFinalClassExample::useFinalClass, - ge(2), - { mocks, r -> - val intProvider = mocks.singleMock("intProvider", FinalClass::provideInt) - intProvider.value(0) == 1 && r == 1 - }, - { mocks, r -> - val intProvider = mocks.singleMock("intProvider", FinalClass::provideInt) - intProvider.value(0) != 1 && r == 2 - }, - coverage = DoNotCalculate, - mockStrategy = OTHER_CLASSES - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockStaticFieldExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockStaticFieldExampleTest.kt deleted file mode 100644 index 9467f7da0b..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockStaticFieldExampleTest.kt +++ /dev/null @@ -1,75 +0,0 @@ -package org.utbot.examples.mock - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.mock.others.Generator -import org.utbot.examples.singleMock -import org.utbot.examples.singleMockOrNull -import org.utbot.examples.value -import org.utbot.examples.withoutConcrete -import org.utbot.framework.plugin.api.FieldMockTarget -import org.utbot.framework.plugin.api.MockInfo -import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_PACKAGES -import kotlin.reflect.KClass -import org.junit.jupiter.api.Test - -internal class MockStaticFieldExampleTest : AbstractTestCaseGeneratorTest(testClass = MockStaticFieldExample::class) { - - @Test - fun testMockStaticField() { - withoutConcrete { // TODO JIRA:1420 - checkMocks( - MockStaticFieldExample::calculate, - eq(4), // 2 NPE - // NPE, privateGenerator is null - { _, mocks, r -> - val privateGenerator = mocks.singleMockOrNull("privateGenerator", Generator::generateInt) - privateGenerator == null && r == null - }, - // NPE, publicGenerator is null - { _, mocks, r -> - val publicGenerator = mocks.singleMockOrNull("publicGenerator", Generator::generateInt) - publicGenerator == null && r == null - }, - { threshold, mocks, r -> - val mock1 = mocks.singleMock("privateGenerator", Generator::generateInt) - val mock2 = mocks.singleMock("publicGenerator", Generator::generateInt) - - val (index1, index2) = if (mock1.values.size > 1) 0 to 1 else 0 to 0 - - val value1 = mock1.value(index1) - val value2 = mock2.value(index2) - - val firstMockConstraint = mock1.mocksStaticField(MockStaticFieldExample::class) - val secondMockConstraint = mock2.mocksStaticField(MockStaticFieldExample::class) - val resultConstraint = threshold < value1 + value2 && r == threshold - - firstMockConstraint && secondMockConstraint && resultConstraint - }, - { threshold, mocks, r -> - val mock1 = mocks.singleMock("privateGenerator", Generator::generateInt) - val mock2 = mocks.singleMock("publicGenerator", Generator::generateInt) - - val (index1, index2) = if (mock1.values.size > 1) 0 to 1 else 0 to 0 - - val value1 = mock1.value(index1) - val value2 = mock2.value(index2) - - val firstMockConstraint = mock1.mocksStaticField(MockStaticFieldExample::class) - val secondMockConstraint = mock2.mocksStaticField(MockStaticFieldExample::class) - val resultConstraint = threshold >= value1 + value2 && r == value1 + value2 + 1 - - firstMockConstraint && secondMockConstraint && resultConstraint - }, - coverage = DoNotCalculate, - mockStrategy = OTHER_PACKAGES - ) - } - } - - private fun MockInfo.mocksStaticField(kClass: KClass<*>) = when (val mock = mock) { - is FieldMockTarget -> mock.ownerClassName == kClass.qualifiedName && mock.owner == null - else -> false - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockStaticMethodExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockStaticMethodExampleTest.kt deleted file mode 100644 index 203c5bd5cb..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockStaticMethodExampleTest.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.utbot.examples.mock - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.util.singleModel -import org.utbot.framework.util.singleStaticMethod -import org.utbot.framework.util.singleValue -import org.junit.jupiter.api.Test - -internal class MockStaticMethodExampleTest : AbstractTestCaseGeneratorTest(testClass = MockStaticMethodExample::class) { - @Test - fun testUseStaticMethod() { - checkMocksAndInstrumentation( - MockStaticMethodExample::useStaticMethod, - eq(2), - { _, instrumentation, r -> - val mockValue = instrumentation - .singleStaticMethod("nextRandomInt") - .singleModel() - .singleValue() as Int - - mockValue > 50 && r == 100 - }, - { _, instrumentation, r -> - val mockValue = instrumentation - .singleStaticMethod("nextRandomInt") - .singleModel() - .singleValue() as Int - - mockValue <= 50 && r == 0 - }, - coverage = DoNotCalculate, - mockStrategy = MockStrategyApi.OTHER_PACKAGES - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockWithFieldExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockWithFieldExampleTest.kt deleted file mode 100644 index 6c0fd02030..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/MockWithFieldExampleTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.utbot.examples.mock - -import org.utbot.examples.AbstractModelBasedTest -import org.utbot.examples.eq -import org.utbot.examples.primitiveValue -import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_PACKAGES -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.getOrThrow -import org.utbot.framework.plugin.api.isMockModel -import org.utbot.framework.plugin.api.isNull -import org.junit.jupiter.api.Test - -internal class MockWithFieldExampleTest : AbstractModelBasedTest(testClass = MockWithFieldExample::class) { - @Test - fun testCheckAndUpdate() { - check( - MockWithFieldExample::checkAndUpdate, - eq(3), - { stamp, r -> stamp.isNull() && r.isException() }, - { stamp, r -> - val result = r.getOrThrow() - - val mockModels = stamp.isMockModel() && result.isMockModel() - val stampValues = stamp.initial > stamp.version - val resultConstraint = result.initial == stamp.initial && result.version == result.initial - - mockModels && stampValues && resultConstraint - }, - { stamp, r -> - val result = r.getOrThrow() - - val mockModels = stamp.isMockModel() && result.isMockModel() - val stampValues = stamp.initial <= stamp.version - val resultConstraint = result.initial == stamp.initial && result.version == stamp.version + 1 - - mockModels && stampValues && resultConstraint - }, - mockStrategy = OTHER_PACKAGES - ) - } - - private val UtModel.initial: Int - get() = findField("initial").primitiveValue() - - private val UtModel.version: Int - get() = findField("version").primitiveValue() -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/model/FieldMockModelBasedTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/mock/model/FieldMockModelBasedTest.kt deleted file mode 100644 index 8e798f823e..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/mock/model/FieldMockModelBasedTest.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.utbot.examples.mock.model - -import org.utbot.examples.AbstractModelBasedTest -import org.utbot.examples.eq -import org.utbot.examples.mock.provider.impl.ProviderImpl -import org.utbot.examples.mock.service.impl.ServiceWithField -import org.utbot.examples.primitiveValue -import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_PACKAGES -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.isNotNull -import org.utbot.framework.plugin.api.isNull -import org.junit.jupiter.api.Test - -internal class FieldMockModelBasedTest : AbstractModelBasedTest(testClass = ServiceWithField::class) { - @Test - fun testMockForField_IntPrimitive() { - checkStatic( - ServiceWithField::staticCalculateBasedOnInteger, - eq(4), - { service, r -> service.isNull() && r.isException() }, - { service, r -> service.provider.isNull() && r.isException() }, - { service, r -> - service.provider.isNotNull() && - service.provider.mocksMethod(ProviderImpl::provideInteger)!!.single() - .primitiveValue() > 5 && r.primitiveValue() == 1 - }, - { service, r -> - service.provider.isNotNull() && - service.provider.mocksMethod(ProviderImpl::provideInteger)!!.single() - .primitiveValue() <= 5 && r.primitiveValue() == 0 - }, - mockStrategy = OTHER_PACKAGES - ) - } - - private val UtModel.provider: UtModel - get() = this.findField("provider") -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/models/CompositeModelMinimizationExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/models/CompositeModelMinimizationExampleTest.kt deleted file mode 100644 index 9d8123232b..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/models/CompositeModelMinimizationExampleTest.kt +++ /dev/null @@ -1,78 +0,0 @@ -package org.utbot.examples.models - -import org.utbot.examples.AbstractModelBasedTest -import org.utbot.examples.eq -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtCompositeModel -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtReferenceModel -import org.junit.Test - -internal class CompositeModelMinimizationExampleTest : AbstractModelBasedTest( - testClass = CompositeModelMinimizationExample::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) -) { - private fun UtModel.getFieldsOrNull(): Map? = when(this) { - is UtAssembleModel -> origin?.fields - is UtCompositeModel -> fields - else -> null - } - - private fun UtModel.hasInitializedFields(): Boolean = getFieldsOrNull()?.isNotEmpty() == true - private fun UtModel.isNotInitialized(): Boolean = getFieldsOrNull()?.isEmpty() == true - - @Test - fun singleNotNullArgumentInitializationRequiredTest() { - check( - CompositeModelMinimizationExample::singleNotNullArgumentInitializationRequired, - eq(2), - { o, _ -> o.hasInitializedFields() } - ) - } - - @Test - fun sameArgumentsInitializationRequiredTest() { - check( - CompositeModelMinimizationExample::sameArgumentsInitializationRequired, - eq(3), - { a, b, _ -> - a as UtReferenceModel - b as UtReferenceModel - a.id == b.id && a.hasInitializedFields() && b.hasInitializedFields() - } - ) - } - - @Test - fun distinctNotNullArgumentsSecondInitializationNotExpected() { - check( - CompositeModelMinimizationExample::distinctNotNullArgumentsSecondInitializationNotExpected, - eq(2), - { a, b, _ -> - a as UtReferenceModel - b as UtReferenceModel - a.hasInitializedFields() && b.isNotInitialized() - } - ) - } - - @Test - fun distinctNotNullArgumentsInitializationRequired() { - check( - CompositeModelMinimizationExample::distinctNotNullArgumentsInitializationRequired, - eq(2), - { a, b, _ -> - a as UtReferenceModel - b as UtReferenceModel - a.hasInitializedFields() && b.hasInitializedFields() - } - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/models/ModelsIdEqualityExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/models/ModelsIdEqualityExampleTest.kt deleted file mode 100644 index e19dc189dd..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/models/ModelsIdEqualityExampleTest.kt +++ /dev/null @@ -1,145 +0,0 @@ -package org.utbot.examples.models - -import org.utbot.examples.AbstractModelBasedTest -import org.utbot.examples.eq -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtDirectSetFieldModel -import org.utbot.framework.plugin.api.UtExecutionSuccess -import org.utbot.framework.plugin.api.UtReferenceModel -import org.junit.jupiter.api.Test - -// TODO failed Kotlin compilation SAT-1332 -internal class ModelsIdEqualityExampleTest : AbstractModelBasedTest( - testClass = ModelsIdEqualityExample::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) -) { - @Test - fun testObjectItself() { - check( - ModelsIdEqualityExample::objectItself, - eq(1), - { o, r -> (o as UtReferenceModel).id == ((r as UtExecutionSuccess).model as UtReferenceModel).id } - ) - } - - @Test - fun testRefField() { - check( - ModelsIdEqualityExample::refField, - eq(1), - { o, r -> - val resultId = ((r as UtExecutionSuccess).model as UtReferenceModel).id - val fieldId = (o as UtAssembleModel).findFieldId() - fieldId == resultId - } - ) - } - - @Test - fun testArrayField() { - check( - ModelsIdEqualityExample::arrayField, - eq(1), - { o, r -> - val resultId = ((r as UtExecutionSuccess).model as UtReferenceModel).id - val fieldId = (o as UtAssembleModel).findFieldId() - fieldId == resultId - } - ) - } - - @Test - fun testArrayItself() { - check( - ModelsIdEqualityExample::arrayItself, - eq(1), - { o, r -> (o as? UtReferenceModel)?.id == ((r as UtExecutionSuccess).model as? UtReferenceModel)?.id } - ) - } - - @Test - fun testSubArray() { - check( - ModelsIdEqualityExample::subArray, - eq(1), - { array, r -> - val resultId = ((r as UtExecutionSuccess).model as UtReferenceModel).id - val arrayId = (array as UtArrayModel).findElementId(0) - resultId == arrayId - } - ) - } - - @Test - fun testSubRefArray() { - check( - ModelsIdEqualityExample::subRefArray, - eq(1), - { array, r -> - val resultId = ((r as UtExecutionSuccess).model as UtReferenceModel).id - val arrayId = (array as UtArrayModel).findElementId(0) - resultId == arrayId - } - ) - } - - @Test - fun testWrapperExample() { - check( - ModelsIdEqualityExample::wrapperExample, - eq(1), - { o, r -> (o as? UtReferenceModel)?.id == ((r as UtExecutionSuccess).model as? UtReferenceModel)?.id } - ) - } - - @Test - fun testObjectFromArray() { - check( - ModelsIdEqualityExample::objectFromArray, - eq(1), - { array, r -> - val resultId = ((r as UtExecutionSuccess).model as UtReferenceModel).id - val objectId = (array as UtArrayModel).findElementId(0) - resultId == objectId - } - ) - } - - @Test - fun testObjectAndStatic() { - checkStaticsAfter( - ModelsIdEqualityExample::staticSetter, - eq(1), - { obj, statics, r -> - val resultId = ((r as UtExecutionSuccess).model as UtReferenceModel).id - val objectId = (obj as UtReferenceModel).id - val staticId = (statics.values.single() as UtReferenceModel).id - resultId == objectId && resultId == staticId - } - ) - - } - - private fun UtReferenceModel.findFieldId(): Int? { - this as UtAssembleModel - val fieldModel = this.allStatementsChain - .filterIsInstance() - .single() - .fieldModel - return (fieldModel as UtReferenceModel).id - } - - private fun UtArrayModel.findElementId(index: Int) = - if (index in stores.keys) { - (stores[index] as UtReferenceModel).id - } else { - (constModel as UtReferenceModel).id - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/natives/NativeExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/natives/NativeExamplesTest.kt deleted file mode 100644 index a99b6a41cc..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/natives/NativeExamplesTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.utbot.examples.natives - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.junit.jupiter.api.Test -import org.utbot.examples.withSolverTimeoutInMillis - -internal class NativeExamplesTest : AbstractTestCaseGeneratorTest(testClass = NativeExamples::class) { - - @Test - fun testFindAndPrintSum() { - // TODO related to the https://github.com/UnitTestBot/UTBotJava/issues/131 - withSolverTimeoutInMillis(5000) { - check( - NativeExamples::findAndPrintSum, - ge(1), - coverage = DoNotCalculate, - ) - } - } - - @Test - fun testFindSumWithMathRandom() { - check( - NativeExamples::findSumWithMathRandom, - eq(1), - coverage = DoNotCalculate, - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/AnonymousClassesExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/AnonymousClassesExampleTest.kt deleted file mode 100644 index 2c078d0528..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/AnonymousClassesExampleTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.utbot.examples.objects - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.isException -import org.junit.jupiter.api.Test - -class AnonymousClassesExampleTest : AbstractTestCaseGeneratorTest(testClass = AnonymousClassesExample::class) { - @Test - fun testAnonymousClassAsParam() { - checkWithException( - AnonymousClassesExample::anonymousClassAsParam, - eq(2), - { abstractAnonymousClass, r -> abstractAnonymousClass == null && r.isException() }, - { abstractAnonymousClass, r -> abstractAnonymousClass != null && r.getOrNull() == 0 }, - coverage = DoNotCalculate - ) - } - - @Test - fun testNonFinalAnonymousStatic() { - check( - AnonymousClassesExample::nonFinalAnonymousStatic, - eq(0), // we remove all anonymous classes in statics - coverage = DoNotCalculate - ) - } - - @Test - fun testAnonymousClassAsStatic() { - check( - AnonymousClassesExample::anonymousClassAsStatic, - eq(0), // we remove all anonymous classes in statics - coverage = DoNotCalculate - ) - } - - @Test - fun testAnonymousClassAsResult() { - check( - AnonymousClassesExample::anonymousClassAsResult, - eq(0), // we remove anonymous classes from the params and the result - coverage = DoNotCalculate - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ClassWithClassRefTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ClassWithClassRefTest.kt deleted file mode 100644 index d96311db6b..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ClassWithClassRefTest.kt +++ /dev/null @@ -1,37 +0,0 @@ -package org.utbot.examples.objects - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.isException -import org.utbot.examples.withoutConcrete -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.codegen.Compilation -import org.utbot.framework.plugin.api.CodegenLanguage -import org.junit.jupiter.api.Test - -// TODO Kotlin compilation SAT-1332 -// Code generation executions fail due we cannot analyze strings properly for now -internal class ClassWithClassRefTest : AbstractTestCaseGeneratorTest( - testClass = ClassWithClassRef::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA, Compilation), // TODO JIRA:1479 - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) -) { - @Test - // TODO test does not work properly JIRA:1479 - // TODO we don't fail now, but we do not generate correct value as well - fun testClassRefGetName() { - withoutConcrete { // TODO: concrete execution returns "java.lang.Object" - checkWithThisAndException( - ClassWithClassRef::classRefName, - eq(2), - { instance, r -> instance.someListClass == null && r.isException() }, - { instance, r -> instance.someListClass != null && r.getOrNull() == "" }, - coverage = DoNotCalculate // TODO: Method coverage with `this` parameter isn't supported - ) - } - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/HiddenFieldExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/HiddenFieldExampleTest.kt deleted file mode 100644 index d7466b7c2a..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/HiddenFieldExampleTest.kt +++ /dev/null @@ -1,39 +0,0 @@ -package org.utbot.examples.objects - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test - -internal class HiddenFieldExampleTest : AbstractTestCaseGeneratorTest(testClass = HiddenFieldExample::class) { - @Test - // Engine creates HiddenFieldSuccClass instead of HiddenFieldSuperClass, feels wrong field and matchers fail - fun testCheckHiddenField() { - check( - HiddenFieldExample::checkHiddenField, - eq(4), - { o, _ -> o == null }, - { o, r -> o != null && o.a != 1 && r == 2 }, - { o, r -> o != null && o.a == 1 && o.b != 2 && r == 2 }, - { o, r -> o != null && o.a == 1 && o.b == 2 && r == 1 }, - coverage = DoNotCalculate - ) - } - - @Test - @Disabled("SAT-315 Engine cannot work with hidden fields") - // Engine translates calls to super.b as calls to succ.b - fun testCheckSuccField() { - check( - HiddenFieldExample::checkSuccField, - eq(5), - { o, _ -> o == null }, - { o, r -> o.a == 1 && r == 1 }, - { o, r -> o.a != 1 && o.b == 2.0 && r == 2 }, - { o, r -> o.a != 1 && o.b != 2.0 && (o as HiddenFieldSuperClass).b == 3 && r == 3 }, - { o, r -> o.a != 1 && o.b != 2.0 && (o as HiddenFieldSuperClass).b != 3 && r == 4 }, - coverage = DoNotCalculate - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithFinalStaticTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithFinalStaticTest.kt deleted file mode 100644 index ad1e5707c7..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithFinalStaticTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.utbot.examples.objects - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.singleValue -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import org.junit.jupiter.api.Test - -class ObjectWithFinalStaticTest : AbstractTestCaseGeneratorTest( - testClass = ObjectWithFinalStatic::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) -) { - @Test - fun testParameterEqualsFinalStatic() { - checkStatics( - ObjectWithFinalStatic::parameterEqualsFinalStatic, - eq(2), - { key, _, statics, result -> key != statics.singleValue() as Int && result == -420 }, - // matcher checks equality by value, but branch is executed if objects are equal by reference - { key, i, statics, result -> key == statics.singleValue() && i == result }, - coverage = DoNotCalculate - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithThrowableConstructorTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithThrowableConstructorTest.kt deleted file mode 100644 index 9c857fad6c..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/ObjectWithThrowableConstructorTest.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.utbot.examples.objects - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import kotlin.reflect.KFunction2 -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test - -internal class ObjectWithThrowableConstructorTest : AbstractTestCaseGeneratorTest(testClass = ObjectWithThrowableConstructor::class) { - @Test - @Disabled("SAT-1500 Support verification of UtAssembleModel for possible exceptions") - fun testThrowableConstructor() { - val method: KFunction2 = ::ObjectWithThrowableConstructor - checkStaticMethod( - method, - eq(2), - // TODO: SAT-933 Add support for constructor testing - coverage = DoNotCalculate - ) - } -} diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/PrivateFieldsTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/PrivateFieldsTest.kt deleted file mode 100644 index e8ad23c0d6..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/PrivateFieldsTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package org.utbot.examples.objects - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.utbot.examples.isException -import org.junit.jupiter.api.Test - -internal class PrivateFieldsTest : AbstractTestCaseGeneratorTest(testClass = PrivateFields::class) { - @Test - fun testAccessWithGetter() { - checkWithException( - PrivateFields::accessWithGetter, - eq(3), - { x, r -> x == null && r.isException() }, - { x, r -> x.a == 1 && r.getOrNull() == true }, - { x, r -> x.a != 1 && r.getOrNull() == false }, - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/SimpleClassExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/SimpleClassExampleTest.kt deleted file mode 100644 index 82840e022f..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/SimpleClassExampleTest.kt +++ /dev/null @@ -1,106 +0,0 @@ -package org.utbot.examples.objects - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.examples.isException -import org.utbot.examples.keyContain -import org.utbot.examples.keyMatch -import org.utbot.framework.plugin.api.DocCodeStmt -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.DocStatement -import org.junit.jupiter.api.Test - -internal class SimpleClassExampleTest : AbstractTestCaseGeneratorTest(testClass = SimpleClassExample::class) { - @Test - fun simpleConditionTest() { - check( - SimpleClassExample::simpleCondition, - eq(4), - { c, _ -> c == null }, // NPE - { c, r -> c.a >= 5 && r == 3 }, - { c, r -> c.a < 5 && c.b <= 10 && r == 3 }, - { c, r -> c.a < 5 && c.b > 10 && r == 0 }, - coverage = DoNotCalculate // otherwise we overwrite original values - ) - } - - /** - * Additional bytecode instructions between IFs, because of random, makes different order of executing the branches, - * that affects their number. Changing random seed in PathSelector can explore 6th branch - * - * @see multipleFieldAccessesTest - */ - @Test - fun singleFieldAccessTest() { - check( - SimpleClassExample::singleFieldAccess, - between(5..6), // could be 6 - { c, _ -> c == null }, // NPE - { c, r -> c.a == 3 && c.b != 5 && r == 2 }, - { c, r -> c.a == 3 && c.b == 5 && r == 1 }, - { c, r -> c.a == 2 && c.b != 3 && r == 2 }, - { c, r -> c.a == 2 && c.b == 3 && r == 0 } - ) - } - - /** - * Additional bytecode instructions between IFs, because of random, makes different order of executing the branches, - * that affects their number - */ - @Test - fun multipleFieldAccessesTest() { - check( - SimpleClassExample::multipleFieldAccesses, - eq(6), - { c, _ -> c == null }, // NPE - { c, r -> c.a != 2 && c.a != 3 && r == 2 }, // this one appears - { c, r -> c.a == 3 && c.b != 5 && r == 2 }, - { c, r -> c.a == 3 && c.b == 5 && r == 1 }, - { c, r -> c.a == 2 && c.b != 3 && r == 2 }, - { c, r -> c.a == 2 && c.b == 3 && r == 0 } - ) - } - - @Test - fun immutableFieldAccessTest() { - val immutableFieldAccessSummary = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("executes conditions:\n"), - DocRegularStmt(" "), - DocCodeStmt("(c.b == 10): True"), - DocRegularStmt("\n"), - DocRegularStmt("returns from: "), - DocCodeStmt("return 0;"), - DocRegularStmt("\n"), - ) - ) - ) - checkWithException( - SimpleClassExample::immutableFieldAccess, - eq(3), - { c, r -> c == null && r.isException() }, - { c, r -> c.b == 10 && r.getOrNull() == 0 }, - { c, r -> c.b != 10 && r.getOrNull() == 1 }, - summaryTextChecks = listOf( - keyContain(DocRegularStmt("throws NullPointerException in: c.b == 10")), - keyContain(DocCodeStmt("(c.b == 10): False")), - keyMatch(immutableFieldAccessSummary) - ), - summaryNameChecks = listOf( - keyMatch("testImmutableFieldAccess_ThrowNullPointerException"), - keyMatch("testImmutableFieldAccess_CBNotEquals10"), - keyMatch("testImmutableFieldAccess_CBEquals10") - ), - summaryDisplayNameChecks = listOf( - keyContain("NullPointerException", "c.b == 10"), - keyContain("c.b == 10 : False"), - keyContain("c.b == 10 : True") - ) - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/SimpleClassMultiInstanceExampleTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/objects/SimpleClassMultiInstanceExampleTest.kt deleted file mode 100644 index 7b552ada8b..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/objects/SimpleClassMultiInstanceExampleTest.kt +++ /dev/null @@ -1,21 +0,0 @@ -package org.utbot.examples.objects - -import org.junit.Ignore -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.junit.Test - -internal class SimpleClassMultiInstanceExampleTest : AbstractTestCaseGeneratorTest(testClass = SimpleClassMultiInstanceExample::class) { - @Test - fun singleObjectChangeTest() { - check( - SimpleClassMultiInstanceExample::singleObjectChange, - eq(3), - { first, _, _ -> first == null }, // NPE - { first, _, r -> first.a < 5 && r == 3 }, - { first, _, r -> first.a >= 5 && r == first.b }, - coverage = DoNotCalculate - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/primitives/FloatExamplesTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/primitives/FloatExamplesTest.kt deleted file mode 100644 index d5c4fddc81..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/primitives/FloatExamplesTest.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.utbot.examples.primitives - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.junit.jupiter.api.Test - -internal class FloatExamplesTest : AbstractTestCaseGeneratorTest(testClass = FloatExamples::class) { - @Test - fun testFloatInfinity() { - check( - FloatExamples::floatInfinity, - eq(3), - { f, r -> f == Float.POSITIVE_INFINITY && r == 1 }, - { f, r -> f == Float.NEGATIVE_INFINITY && r == 2 }, - { f, r -> !f.isInfinite() && r == 3 }, - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/recursion/RecursionTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/recursion/RecursionTest.kt deleted file mode 100644 index 6e6e6f20e6..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/recursion/RecursionTest.kt +++ /dev/null @@ -1,135 +0,0 @@ -package org.utbot.examples.recursion - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.atLeast -import org.utbot.examples.between -import org.utbot.examples.eq -import org.utbot.examples.ge -import org.utbot.examples.isException -import org.utbot.examples.keyContain -import org.utbot.examples.keyMatch -import org.utbot.framework.plugin.api.DocCodeStmt -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.DocStatement -import kotlin.math.pow -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test - -internal class RecursionTest : AbstractTestCaseGeneratorTest(testClass = Recursion::class) { - @Test - fun testFactorial() { - val factorialSummary = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("returns from: "), - DocCodeStmt("return 1;"), - DocRegularStmt("\n"), - ) - ) - ) - - checkWithException( - Recursion::factorial, - eq(3), - { x, r -> x < 0 && r.isException() }, - { x, r -> x == 0 && r.getOrNull() == 1 }, - { x, r -> x > 0 && r.getOrNull() == (1..x).reduce { a, b -> a * b } }, - summaryTextChecks = listOf( - keyContain(DocCodeStmt("(n < 0): True")), - keyMatch(factorialSummary), - keyContain(DocCodeStmt("(n == 0): False")) - ), - summaryNameChecks = listOf( - keyMatch("testFactorial_NLessThanZero"), - keyMatch("testFactorial_Return1"), - keyMatch("testFactorial_NNotEqualsZero") - ), - summaryDisplayNameChecks = listOf( - keyMatch("-> return 1"), - keyMatch("n < 0 -> ThrowIllegalArgumentException"), - keyMatch("-> return 1") - ) - ) - } - - @Test - fun testFib() { - checkWithException( - Recursion::fib, - eq(4), - { x, r -> x < 0 && r.isException() }, - { x, r -> x == 0 && r.getOrNull() == 0 }, - { x, r -> x == 1 && r.getOrNull() == 1 }, - { x, r -> x > 1 && r.getOrNull() == Recursion().fib(x) } - ) - } - - @Test - @Disabled("Freezes the execution when snd != 0 JIRA:1293") - fun testSum() { - check( - Recursion::sum, - eq(2), - { x, y, r -> y == 0 && r == x }, - { x, y, r -> y != 0 && r == x + y } - ) - } - - @Test - fun testPow() { - checkWithException( - Recursion::pow, - eq(4), - { _, y, r -> y < 0 && r.isException() }, - { _, y, r -> y == 0 && r.getOrNull() == 1 }, - { x, y, r -> y % 2 == 1 && r.getOrNull() == x.toDouble().pow(y.toDouble()).toInt() }, - { x, y, r -> y % 2 != 1 && r.getOrNull() == x.toDouble().pow(y.toDouble()).toInt() } - ) - } - - @Test - fun infiniteRecursionTest() { - checkWithException( - Recursion::infiniteRecursion, - eq(2), - { x, r -> x > 10000 && r.isException() }, - { x, r -> x <= 10000 && r.isException() }, - coverage = atLeast(50) - ) - } - - @Test - fun vertexSumTest() { - check( - Recursion::vertexSum, - between(2..3), - { x, _ -> x <= 10 }, - { x, _ -> x > 10 } - ) - } - - @Test - fun recursionWithExceptionTest() { - checkWithException( - Recursion::recursionWithException, - ge(3), - { x, r -> x < 42 && r.isException() }, - { x, r -> x == 42 && r.isException() }, - { x, r -> x > 42 && r.isException() }, - coverage = atLeast(50) - ) - } - - @Test - fun recursionLoopTest() { - check( - Recursion::firstMethod, - eq(2), - { x, _ -> x < 4 }, - { x, _ -> x >= 4 }, - coverage = atLeast(50) - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/statics/substitution/StaticsSubstitutionTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/statics/substitution/StaticsSubstitutionTest.kt deleted file mode 100644 index b453d95408..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/statics/substitution/StaticsSubstitutionTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.utbot.examples.statics.substitution - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.withoutSubstituteStaticsWithSymbolicVariable -import org.junit.jupiter.api.Test - -class StaticsSubstitutionTest : AbstractTestCaseGeneratorTest(testClass = StaticSubstitutionExamples::class) { - - @Test - fun lessThanZeroWithSubstitution() { - check( - StaticSubstitutionExamples::lessThanZero, - eq(2), - { r -> r != 0 }, - { r -> r == 0 }, - ) - } - - @Test - fun lessThanZeroWithoutSubstitution() { - withoutSubstituteStaticsWithSymbolicVariable { - checkWithoutStaticsSubstitution( - StaticSubstitutionExamples::lessThanZero, - eq(1), - { r -> r != 0 }, - coverage = DoNotCalculate, - ) - } - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/structures/StandardStructuresTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/structures/StandardStructuresTest.kt deleted file mode 100644 index abe0ea16ae..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/structures/StandardStructuresTest.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.utbot.examples.structures - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.examples.keyContain -import org.utbot.examples.keyMatch -import org.utbot.framework.plugin.api.DocCodeStmt -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.DocStatement -import java.util.LinkedList -import java.util.TreeMap -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test - -internal class StandardStructuresTest : AbstractTestCaseGeneratorTest(testClass = StandardStructures::class) { - @Test - @Disabled("TODO down cast for object wrapper JIRA:1480") - fun testGetList() { - check( - StandardStructures::getList, - eq(4), - { l, r -> l is ArrayList && r is ArrayList }, - { l, r -> l is LinkedList && r is LinkedList }, - { l, r -> l == null && r == null }, - { l, r -> - l !is ArrayList && l !is LinkedList && l != null && r !is ArrayList && r !is LinkedList && r != null - }, - coverage = DoNotCalculate - ) - } - - @Test - @Disabled("TODO down cast for object wrapper JIRA:1480") - fun testGetMap() { - check( - StandardStructures::getMap, - eq(3), - { m, r -> m is TreeMap && r is TreeMap }, - { m, r -> m == null && r == null }, - { m, r -> m !is TreeMap && m != null && r !is TreeMap && r != null }, - coverage = DoNotCalculate - ) - } - - @Test - @Disabled("TODO use correct wrapper JIRA:1495") - fun testGetDeque() { - val dequeSummary = listOf( - DocPreTagStatement( - listOf( - DocRegularStmt("Test "), - DocRegularStmt("executes conditions:\n"), - DocRegularStmt(" "), - DocCodeStmt("(deque instanceof LinkedList): False"), - DocRegularStmt(",\n"), - DocRegularStmt(" "), - DocCodeStmt("(deque == null): True"), - DocRegularStmt("\n"), - DocRegularStmt("returns from: "), - DocCodeStmt("return null;"), - DocRegularStmt("\n") - ) - ) - ) - - check( - StandardStructures::getDeque, - eq(4), - { d, r -> d is java.util.ArrayDeque && r is java.util.ArrayDeque }, - { d, r -> d is LinkedList && r is LinkedList }, - { d, r -> d == null && r == null }, - { d, r -> - d !is ArrayDeque<*> && d !is LinkedList && d != null && r !is ArrayDeque<*> && r !is LinkedList && r != null - }, - coverage = DoNotCalculate, - summaryTextChecks = listOf( - keyContain(DocCodeStmt("(deque instanceof ArrayDeque): True")), - keyContain(DocCodeStmt("(deque instanceof LinkedList): True")), - keyContain(DocCodeStmt("(deque == null): True")), - keyMatch(dequeSummary) - ), - summaryNameChecks = listOf( - keyMatch("testGetDeque_DequeInstanceOfArrayDeque"), - keyMatch("testGetDeque_DequeInstanceOfLinkedList"), - keyMatch("testGetDeque_DequeEqualsNull"), - keyMatch("testGetDeque_DequeNotEqualsNull"), - ), - summaryDisplayNameChecks = listOf( - keyContain("deque", "instance", "ArrayDeque"), - keyContain("deque", "instance", "LinkedList"), - keyContain("deque == null", "True"), - keyContain("deque == null", "False"), - ) - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/unsafe/UnsafeWithFieldTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/unsafe/UnsafeWithFieldTest.kt deleted file mode 100644 index 1a42e853f7..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/unsafe/UnsafeWithFieldTest.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.utbot.examples.unsafe - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.eq -import org.junit.jupiter.api.Test - -internal class UnsafeWithFieldTest: AbstractTestCaseGeneratorTest(UnsafeWithField::class) { - - @Test - fun checkSetField() { - check( - UnsafeWithField::setField, - eq(1) - // TODO JIRA:1579 - // for now we might have any value as a result, so there is no need in the matcher - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/CharacterWrapperTest.kt b/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/CharacterWrapperTest.kt deleted file mode 100644 index 786052acd2..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/examples/wrappers/CharacterWrapperTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package org.utbot.examples.wrappers - -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.eq -import org.utbot.framework.codegen.CodeGeneration -import org.utbot.framework.plugin.api.CodegenLanguage -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test - -// TODO failed Kotlin compilation -internal class CharacterWrapperTest : AbstractTestCaseGeneratorTest( - testClass = CharacterWrapper::class, - testCodeGeneration = true, - languagePipelines = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, CodeGeneration) - ) -) { - @Test - fun primitiveToWrapperTest() { - check( - CharacterWrapper::primitiveToWrapper, - eq(2), - { x, r -> x.toInt() >= 100 && r!!.toInt() >= 100 }, - { x, r -> x.toInt() < 100 && r!!.toInt() == x.toInt() + 100 }, - ) - } - - @Test - fun wrapperToPrimitiveTest() { - check( - CharacterWrapper::wrapperToPrimitive, - eq(3), - { x, _ -> x == null }, - { x, r -> x.toInt() >= 100 && r!!.toInt() >= 100 }, - { x, r -> x.toInt() < 100 && r!!.toInt() == x.toInt() + 100 }, - ) - } - - @Disabled("Caching char values between -128 and 127 isn't supported JIRA:1481") - @Test - fun equalityTest() { - check( - CharacterWrapper::equality, - eq(3), - { a, b, result -> a == b && a.toInt() <= 127 && result == 1 }, - { a, b, result -> a == b && a.toInt() > 127 && result == 2 }, - { a, b, result -> a != b && result == 4 }, - coverage = DoNotCalculate - ) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/SootUtils.kt b/utbot-framework/src/test/kotlin/org/utbot/framework/SootUtils.kt deleted file mode 100644 index ced794ae2b..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/SootUtils.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.utbot.framework - -import org.utbot.common.FileUtil -import org.utbot.framework.plugin.api.runSoot -import java.nio.file.Path -import kotlin.reflect.KClass - -object SootUtils { - - /** - * Runs Soot in tests if it hasn't already been done. - */ - fun runSoot(clazz: KClass<*>) { - val buildDir = FileUtil.locateClassPath(clazz) ?: FileUtil.isolateClassFiles(clazz) - val buildDirPath = buildDir.toPath() - - if (buildDirPath != previousBuildDir) { - runSoot(buildDirPath, null) - previousBuildDir = buildDirPath - } - } - - private var previousBuildDir: Path? = null -} diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/codegen/BaseTestCodeGeneratorPipeline.kt b/utbot-framework/src/test/kotlin/org/utbot/framework/codegen/BaseTestCodeGeneratorPipeline.kt deleted file mode 100644 index 58bb70ac5f..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/codegen/BaseTestCodeGeneratorPipeline.kt +++ /dev/null @@ -1,427 +0,0 @@ -package org.utbot.framework.codegen - -import org.utbot.common.FileUtil -import org.utbot.common.bracket -import org.utbot.common.info -import org.utbot.common.packageName -import org.utbot.examples.TestFrameworkConfiguration -import org.utbot.framework.codegen.ExecutionStatus.FAILED -import org.utbot.framework.codegen.ExecutionStatus.SUCCESS -import org.utbot.framework.codegen.model.ModelBasedTestCodeGenerator -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.UtBotTestCaseGenerator -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.withUtContext -import org.utbot.framework.util.description -import kotlin.reflect.KClass -import mu.KotlinLogging -import org.junit.jupiter.api.Assertions.assertTrue - -private val logger = KotlinLogging.logger {} - -class BaseTestCodeGeneratorPipeline(private val testFrameworkConfiguration: TestFrameworkConfiguration) { - - fun runClassesCodeGenerationTests(classesStages: List) { - val pipelines = classesStages.map { - with(it) { ClassPipeline(StageContext(testClass, testCases, testCases.size), check) } - } - - if (pipelines.isEmpty()) return - - checkPipelinesResults(pipelines) - } - - private fun runPipelinesStages(classesPipelines: List): List { - val classesPipelinesNames = classesPipelines.joinToString(" ") { - val classUnderTest = it.stageContext.classUnderTest - classUnderTest.qualifiedName ?: error("${classUnderTest.simpleName} doesn't have a fqn name") - } - - logger - .info() - .bracket("Executing code generation tests for [$classesPipelinesNames]") { - CodeGeneration.filterPipelines(classesPipelines).forEach { - withUtContext(UtContext(it.stageContext.classUnderTest.java.classLoader)) { - processCodeGenerationStage(it) - } - } - - Compilation.filterPipelines(classesPipelines).let { - if (it.isNotEmpty()) processCompilationStages(it) - } - - TestExecution.filterPipelines(classesPipelines).let { - if (it.isNotEmpty()) processTestExecutionStages(it) - } - - return classesPipelines.map { - with(it.stageContext) { - CodeGenerationResult(classUnderTest, numberOfTestCases, stages) - } - } - } - } - - @Suppress("UNCHECKED_CAST") - private fun processCodeGenerationStage(classPipeline: ClassPipeline) { - with(classPipeline.stageContext) { - val information = StageExecutionInformation(CodeGeneration) - val testCases = data as List - - val codegenLanguage = testFrameworkConfiguration.codegenLanguage - - val testClass = callToCodeGenerator(testCases, classUnderTest) - - // actual number of the tests in the generated testClass - val generatedMethodsCount = testClass - .lines() - .count { - val trimmedLine = it.trimStart() - if (codegenLanguage == CodegenLanguage.JAVA) { - trimmedLine.startsWith("public void") - } else { - trimmedLine.startsWith("fun ") - } - } - // expected number of the tests in the generated testClass - val expectedNumberOfGeneratedMethods = testCases.sumOf { it.executions.size } - - // check for error in the generated file - runCatching { - val separator = System.lineSeparator() - require(ERROR_REGION_BEGINNING !in testClass) { - val lines = testClass.lines().withIndex().toList() - - val errorsRegionsBeginningIndices = lines - .filter { it.value.trimStart().startsWith(ERROR_REGION_BEGINNING) } - - val errorsRegionsEndIndices = lines - .filter { it.value.trimStart().startsWith(ERROR_REGION_END) } - - val errorRegions = errorsRegionsBeginningIndices.map { beginning -> - val endIndex = errorsRegionsEndIndices.indexOfFirst { it.index > beginning.index } - lines.subList(beginning.index + 1, errorsRegionsEndIndices[endIndex].index).map { it.value } - } - - val errorText = errorRegions.joinToString(separator, separator, separator) { errorRegion -> - val text = errorRegion.joinToString(separator = separator) - "Error region in ${classUnderTest.simpleName}: $text" - } - - logger.error(errorText) - - "Errors regions has been generated: $errorText" - } - - require(generatedMethodsCount == expectedNumberOfGeneratedMethods) { - "Something went wrong during the code generation for ${classUnderTest.simpleName}. " + - "Expected to generate $expectedNumberOfGeneratedMethods test methods, " + - "but got only $generatedMethodsCount" - } - }.onFailure { - val classes = listOf(classPipeline).retrieveClasses() - val buildDirectory = classes.createBuildDirectory() - - val testClassName = classPipeline.retrieveTestClassName("BrokenGeneratedTest") - val generatedTestFile = writeTest(testClass, testClassName, buildDirectory, codegenLanguage) - - logger.error("Broken test has been written to the file: [$generatedTestFile]") - logger.error("Failed configuration: $testFrameworkConfiguration") - - throw it - } - - classPipeline.stageContext = copy(data = testClass, stages = stages + information.completeStage()) - } - } - - @Suppress("UNCHECKED_CAST") - private fun processCompilationStages(classesPipelines: List) { - val information = StageExecutionInformation(Compilation) - val classes = classesPipelines.retrieveClasses() - val buildDirectory = classes.createBuildDirectory() - - val codegenLanguage = testFrameworkConfiguration.codegenLanguage - - val testClassesNamesToTestGeneratedTests = classesPipelines.map { classPipeline -> - val testClass = classPipeline.stageContext.data as String - val testClassName = classPipeline.retrieveTestClassName("GeneratedTest") - val generatedTestFile = writeTest(testClass, testClassName, buildDirectory, codegenLanguage) - - logger.info("Test has been written to the file: [$generatedTestFile]") - - testClassName to generatedTestFile - } - - compileTests( - "$buildDirectory", - testClassesNamesToTestGeneratedTests.map { it.second.absolutePath }, - codegenLanguage - ) - - testClassesNamesToTestGeneratedTests.zip(classesPipelines) { testClassNameToTest, classPipeline -> - classPipeline.stageContext = classPipeline.stageContext.copy( - data = CompilationResult("$buildDirectory", testClassNameToTest.first), - stages = classPipeline.stageContext.stages + information.completeStage() - ) - } - } - - /** - * For simple CUT equals to its fqn + [testSuffix] suffix, - * for nested CUT is its package + dot + its simple name + [testSuffix] suffix (to avoid outer class mention). - */ - private fun ClassPipeline.retrieveTestClassName(testSuffix: String): String = - stageContext.classUnderTest.let { "${it.java.packageName}.${it.simpleName}" } + testSuffix - - private fun List>.createBuildDirectory() = FileUtil.isolateClassFiles(*toTypedArray()).toPath() - - private fun List.retrieveClasses() = map { it.stageContext.classUnderTest } - - @Suppress("UNCHECKED_CAST") - private fun processTestExecutionStages(classesPipelines: List) { - val information = StageExecutionInformation(TestExecution) - val buildDirectory = (classesPipelines.first().stageContext.data as CompilationResult).buildDirectory - val testClassesNames = classesPipelines.map { classPipeline -> - (classPipeline.stageContext.data as CompilationResult).testClassName - } - with(testFrameworkConfiguration) { - runTests(buildDirectory, testClassesNames, testFramework, codegenLanguage) - } - classesPipelines.forEach { - it.stageContext = it.stageContext.copy( - data = Unit, - stages = it.stageContext.stages + information.completeStage() - ) - } - } - - private fun callToCodeGenerator( - testCases: List, - classUnderTest: KClass<*> - ): String { - val params = mutableMapOf, List>() - - val modelBasedTestCodeGenerator = with(testFrameworkConfiguration) { - ModelBasedTestCodeGenerator() - .apply { - init( - classUnderTest.java, - params = params, - testFramework = testFramework, - mockFramework = MockFramework.MOCKITO, - staticsMocking = staticsMocking, - forceStaticMocking = forceStaticMocking, - generateWarningsForStaticMocking = false, - codegenLanguage = codegenLanguage, - parameterizedTestSource = parametrizedTestSource, - runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour, - enableTestsTimeout = enableTestsTimeout - ) - } - } - val testClassCustomName = "${classUnderTest.java.simpleName}GeneratedTest" - - return modelBasedTestCodeGenerator.generateAsString(testCases, testClassCustomName) - } - - private fun checkPipelinesResults(classesPipelines: List) { - val results = runPipelinesStages(classesPipelines) - val classesChecks = classesPipelines.map { it.stageContext.classUnderTest to listOf(it.check) } - processResults(results, classesChecks) - } - - @Suppress("unused") - internal fun checkResults( - targetClasses: List>, - testCases: List = listOf(), - lastStage: Stage = TestExecution, - vararg checks: StageStatusCheck - ) { - val results = executeTestGenerationPipeline(targetClasses, testCases, lastStage) - processResults(results, results.map { it.classUnderTest to checks.toList() }) - } - - private fun processResults( - results: List, - classesChecks: List, List>> - ) { - val transformedResults: List, List>> = - results.zip(classesChecks) { result, classChecks -> - val stageStatusChecks = classChecks.second.mapNotNull { check -> - runCatching { check(result) } - .fold( - onSuccess = { if (it) null else check.description }, - onFailure = { it.description } - ) - } - result.classUnderTest to stageStatusChecks - } - val failedResults = transformedResults.filter { it.second.isNotEmpty() } - - assertTrue(failedResults.isEmpty()) { - val separator = "\n\t\t" - val failedResultsConcatenated = failedResults.joinToString(separator, prefix = separator) { - "${it.first.simpleName} : ${it.second.joinToString()}" - } - - "There are failed checks: $failedResultsConcatenated" - } - } - - private fun executeTestGenerationPipeline( - targetClasses: List>, - testCases: List, - lastStage: Stage = TestExecution - ): List = targetClasses.map { - val buildDir = FileUtil.isolateClassFiles(it).toPath() - val classPath = System.getProperty("java.class.path") - val dependencyPath = System.getProperty("java.class.path") - UtBotTestCaseGenerator.init(buildDir, classPath, dependencyPath, isCanceled = { false }) - - val pipelineStages = runPipelinesStages( - listOf( - ClassPipeline( - StageContext(it, testCases, testCases.size), - StageStatusCheck(lastStage = lastStage, status = SUCCESS) - ) - ) - ) - - pipelineStages.singleOrNull() ?: error("A single result's expected, but got ${pipelineStages.size} instead") - } - - - companion object { - val CodegenLanguage.defaultCodegenPipeline: BaseTestCodeGeneratorPipeline - get() = BaseTestCodeGeneratorPipeline( - TestFrameworkConfiguration( - testFramework = TestFramework.defaultItem, - codegenLanguage = this, - mockFramework = MockFramework.defaultItem, - mockStrategy = MockStrategyApi.defaultItem, - staticsMocking = StaticsMocking.defaultItem, - parametrizedTestSource = ParametrizedTestSource.defaultItem, - forceStaticMocking = ForceStaticMocking.defaultItem, - ) - ) - - private const val ERROR_REGION_BEGINNING = "///region Errors" - private const val ERROR_REGION_END = "///endregion" - } -} - -enum class ExecutionStatus { - IN_PROCESS, FAILED, SUCCESS -} - -sealed class Stage(private val name: String, val nextStage: Stage?) { - override fun toString() = name - - fun filterPipelines(classesPipelines: List): List = - classesPipelines.filter { - it.check.firstStage <= this && it.check.lastStage >= this - } - - abstract operator fun compareTo(stage: Stage): Int -} - -object CodeGeneration : Stage("Code Generation", Compilation) { - override fun compareTo(stage: Stage): Int = if (stage is CodeGeneration) 0 else -1 -} - -object Compilation : Stage("Compilation", TestExecution) { - override fun compareTo(stage: Stage): Int = - when (stage) { - is CodeGeneration -> 1 - is Compilation -> 0 - else -> -1 - } -} - -object TestExecution : Stage("Test Execution", null) { - override fun compareTo(stage: Stage): Int = if (stage is TestExecution) 0 else 1 -} - -private fun pipeline(firstStage: Stage = CodeGeneration, lastStage: Stage = TestExecution): Sequence = - generateSequence(firstStage) { if (it == lastStage) null else it.nextStage } - -data class StageExecutionInformation( - val stage: Stage, - val status: ExecutionStatus = ExecutionStatus.IN_PROCESS -) { - fun completeStage(status: ExecutionStatus = SUCCESS) = copy(status = status) -} - -data class CodeGenerationResult( - val classUnderTest: KClass<*>, - val numberOfTestCases: Int, - val stageStatisticInformation: List -) - -sealed class PipelineResultCheck( - val description: String, - private val check: (CodeGenerationResult) -> Boolean -) { - open operator fun invoke(codeGenerationResult: CodeGenerationResult) = check(codeGenerationResult) -} - -/** - * Checks that stage failed and all previous stages are successfully processed. - */ -class StageStatusCheck( - val firstStage: Stage = CodeGeneration, - val lastStage: Stage, - status: ExecutionStatus, -) : PipelineResultCheck( - description = "Expect [$lastStage] to be in [$status] status", - check = constructPipelineResultCheck(firstStage, lastStage, status) -) - -private fun constructPipelineResultCheck( - firstStage: Stage, - lastStage: Stage, - status: ExecutionStatus -): (CodeGenerationResult) -> Boolean = - { result -> - val statuses = result.stageStatisticInformation.associate { it.stage to it.status } - val failedPrevStage = pipeline(firstStage, lastStage) - .takeWhile { it != lastStage } - .firstOrNull { statuses[it] != SUCCESS } - - if (failedPrevStage != null) error("[$lastStage] is not started cause $failedPrevStage has failed") - - statuses[lastStage] == status - } - -private fun failed(stage: Stage) = StageStatusCheck(lastStage = stage, status = FAILED) -private fun succeeded(stage: Stage) = StageStatusCheck(lastStage = stage, status = SUCCESS) - -// Everything succeeded because last step succeeded -val everythingSucceeded = succeeded(TestExecution) - -data class CompilationResult(val buildDirectory: String, val testClassName: String) - -/** - * Context to run Stage. Contains class under test, data (input of current stage), number of analyzed test cases and - * stage execution information. - */ -data class StageContext( - val classUnderTest: KClass<*>, - val data: Any = Unit, - val numberOfTestCases: Int = 0, - val stages: List = emptyList(), - val status: ExecutionStatus = SUCCESS -) - -data class ClassStages( - val testClass: KClass<*>, - val check: StageStatusCheck, - val testCases: List = emptyList() -) - -data class ClassPipeline(var stageContext: StageContext, val check: StageStatusCheck) \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/codegen/DependencyUtilsTests.kt b/utbot-framework/src/test/kotlin/org/utbot/framework/codegen/DependencyUtilsTests.kt deleted file mode 100644 index 76ab9bb13b..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/codegen/DependencyUtilsTests.kt +++ /dev/null @@ -1,48 +0,0 @@ -package org.utbot.framework.codegen - -import org.utbot.framework.codegen.model.util.checkFrameworkDependencies -import java.io.File -import java.net.URLClassLoader -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments -import org.junit.jupiter.params.provider.MethodSource - -class DependencyUtilsTests { - @ParameterizedTest - @MethodSource("provideDependencyPaths") - fun testDependencyChecker(path: String?, isValid: Boolean, reason: String) { - try { - checkFrameworkDependencies(path) - assertTrue(isValid) - } catch (ex: IllegalStateException) { - val message = ex.message - assertTrue(message != null && message.contains(reason), message) - } - } - - companion object { - private val separator = File.pathSeparatorChar - private val jarUrls = (Thread.currentThread().contextClassLoader as URLClassLoader).urLs - - private val mockito = jarUrls.firstOrNull { it.path.contains("mockito-core") }?.path ?: "" - private val junit4 = jarUrls.firstOrNull { it.path.contains("junit-4") }?.path ?: "" - private val junit5 = jarUrls.firstOrNull { it.path.contains("junit-jupiter-api") }?.path ?: "" - private val testNg = jarUrls.firstOrNull { it.path.contains("testng") }?.path ?: "" - - @JvmStatic - fun provideDependencyPaths(): ArrayList { - val argList = ArrayList() - - argList.add(Arguments.arguments("$junit4$separator$mockito$separator", true, "")) - argList.add(Arguments.arguments("$junit5$separator$mockito$separator", true, "")) - argList.add(Arguments.arguments("$testNg$separator$mockito$separator", true, "")) - argList.add(Arguments.arguments("", false, "Dependency paths is empty")) - argList.add(Arguments.arguments(null, false, "Dependency paths is empty")) - argList.add(Arguments.arguments("$junit4$separator$junit5$separator$testNg$separator", false, "Mock frameworks are not found")) - argList.add(Arguments.arguments("$mockito$separator", false, "Test frameworks are not found")) - - return argList - } - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/BaseConstructorTest.kt b/utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/BaseConstructorTest.kt deleted file mode 100644 index 71c63531fa..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/BaseConstructorTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.utbot.framework.concrete.constructors - -import org.utbot.engine.ValueConstructor -import org.utbot.framework.concrete.UtModelConstructor -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.id -import java.util.IdentityHashMap -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.BeforeEach - -abstract class BaseConstructorTest { - private lateinit var cookie: AutoCloseable - - @BeforeEach - fun setup() { - cookie = UtContext.setUtContext(UtContext(ClassLoader.getSystemClassLoader())) - } - - @AfterEach - fun tearDown() { - cookie.close() - } - - protected fun computeReconstructed(value: T): T { - val model = UtModelConstructor(IdentityHashMap()).construct(value, value::class.java.id) - - Assertions.assertTrue(model is UtAssembleModel) - - @Suppress("UNCHECKED_CAST") - return ValueConstructor().construct(listOf(model)).single().value as T - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/coverage/CoverageFinder.kt b/utbot-framework/src/test/kotlin/org/utbot/framework/coverage/CoverageFinder.kt deleted file mode 100644 index c94a9c8410..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/coverage/CoverageFinder.kt +++ /dev/null @@ -1,100 +0,0 @@ -package org.utbot.framework.coverage - -import org.utbot.examples.controlflow.CycleDependedCondition -import org.utbot.framework.plugin.api.UtMethod -import java.lang.reflect.InvocationTargetException -import kotlin.reflect.KClass -import kotlin.reflect.KFunction -import kotlin.reflect.full.createInstance -import kotlin.reflect.full.memberFunctions -import org.jacoco.core.analysis.Analyzer -import org.jacoco.core.analysis.CoverageBuilder -import org.jacoco.core.data.ExecutionDataStore -import org.jacoco.core.data.SessionInfoStore -import org.jacoco.core.instr.Instrumenter -import org.jacoco.core.runtime.IRuntime -import org.jacoco.core.runtime.LoggerRuntime -import org.jacoco.core.runtime.RuntimeData - -fun main() { - println("test ints from -100 to 100") - val result = coverage(CycleDependedCondition::twoCondition, -100..100) - result.forEach { (range, coverage) -> - println("${range.prettify()} -> ${coverage.toAtLeast()} $coverage") - } -} - -private fun coverage( - method: KFunction<*>, - range: IntRange -): List> { - val coverageSeq = coverage(method, range.asSequence().map { arrayOf(it) }) - return coverageSeq.fold(mutableListOf()) { acc, (params, coverage) -> - val value = params[0] as Int - if (acc.isEmpty() || coverage > acc.last().coverage) { - acc.add(IncrementalCoverage(value..value, coverage)) - } else { - acc[acc.lastIndex] = acc.last().copy(params = acc.last().params.first..value) - } - acc - } -} - -private fun coverage( - method: KFunction<*>, - paramSeq: Sequence> -): Sequence>> { - val (_, clazz) = UtMethod.from(method) - val calculator = CoverageCalculator(clazz, method.name) - val targetClass = calculator.instrumentedClass - val targetMethod = targetClass.memberFunctions.first { it.name == method.name } - return paramSeq.map { params -> - try { - targetMethod.call(targetClass.createInstance(), *params) - } catch (_: InvocationTargetException) { - } - val coverage = calculator.calc() - IncrementalCoverage(params, coverage) - } -} - -private fun IntRange.prettify(): String = if (this.first == this.last) "${this.first}" else "$this" - -private class CoverageCalculator(val clazz: KClass<*>, private val methodName: String) { - private val targetName: String = clazz.qualifiedName!! - private val data: RuntimeData - val instrumentedClass: KClass<*> - - init { - val runtime: IRuntime = LoggerRuntime() - - val instrumented = clazz.asInputStream().use { - Instrumenter(runtime).instrument(it, targetName) - } - - data = RuntimeData() - runtime.startup(data) - - instrumentedClass = MemoryClassLoader().apply { - addDefinition(targetName, instrumented) - }.loadClass(targetName).kotlin - } - - fun calc(): Coverage { - val methodCoverage = collectCoverage().classes.first { it.name == instrumentedClass.simpleName } - .methods.first { it.name == methodName } - return methodCoverage.toMethodCoverage() - } - - private fun collectCoverage() = CoverageBuilder().apply { - val eD = ExecutionDataStore() - val sI = SessionInfoStore() - data.collect(eD, sI, false) - val analyzer = Analyzer(eD, this) - clazz.asInputStream().use { - analyzer.analyzeClass(it, targetName) - } - } -} - -data class IncrementalCoverage(val params: T, val coverage: Coverage) \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/plugin/api/MockStrategyApiTest.kt b/utbot-framework/src/test/kotlin/org/utbot/framework/plugin/api/MockStrategyApiTest.kt deleted file mode 100644 index c53f2868e8..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/plugin/api/MockStrategyApiTest.kt +++ /dev/null @@ -1,33 +0,0 @@ -package org.utbot.framework.plugin.api - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Test - -internal class MockStrategyApiTest { - - @Test - fun ensureDefaultStrategyIsOtherPackages() { - assertEquals( - MockStrategyApi.OTHER_PACKAGES, - MockStrategyApi.defaultItem, - "Expecting that ${MockStrategyApi.OTHER_PACKAGES} is the default policy for Mocks " + - "but ${MockStrategyApi.defaultItem} found" - ) - } - - @Test - fun testLabelToEnum() { - assertEquals( - MockStrategyApi.values().size, - MockStrategyApi.labels().toSet().size, - "Expecting all labels are unique" - ) - - assertFalse( - MockStrategyApi.labels().any { it.isBlank() }, - "Expecting all labels are not empty" - ) - } - -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGeneratorTest.kt b/utbot-framework/src/test/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGeneratorTest.kt deleted file mode 100644 index 61dcd67b83..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/plugin/api/UtBotTestCaseGeneratorTest.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.utbot.framework.plugin.api - -import org.utbot.engine.MockStrategy -import org.utbot.framework.plugin.api.UtBotTestCaseGenerator.apiToModel -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test - - -internal class UtBotTestCaseGeneratorTest { - - @Test - fun testApiToModel() { - assertEquals( - MockStrategyApi.values().size, MockStrategy.values().size, - "The number of strategies in the contract and engine model should match" - ) - - assertEquals(3, MockStrategyApi.values().size, "Three options only (so far)") - assertEquals(MockStrategy.NO_MOCKS, apiToModel(MockStrategyApi.NO_MOCKS)) - assertEquals(MockStrategy.OTHER_PACKAGES, apiToModel(MockStrategyApi.OTHER_PACKAGES)) - assertEquals(MockStrategy.OTHER_CLASSES, apiToModel(MockStrategyApi.OTHER_CLASSES)) - } -} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/z3/extension/Z3ExtensionForTests.kt b/utbot-framework/src/test/kotlin/org/utbot/framework/z3/extension/Z3ExtensionForTests.kt deleted file mode 100644 index fdc65fd713..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/z3/extension/Z3ExtensionForTests.kt +++ /dev/null @@ -1,60 +0,0 @@ -@file:Suppress("PackageDirectoryMismatch", "unused", "NonAsciiCharacters") - -package com.microsoft.z3 - -import kotlin.reflect.KProperty - - -operator fun ArithExpr.plus(other: ArithExpr): ArithExpr = context.mkAdd(this, other) -operator fun ArithExpr.plus(other: Int): ArithExpr = this + context.mkInt(other) - -operator fun ArithExpr.minus(other: ArithExpr): ArithExpr = context.mkSub(this, other) -operator fun ArithExpr.minus(other: Int): ArithExpr = this - context.mkInt(other) - -operator fun ArithExpr.times(other: ArithExpr): ArithExpr = context.mkMul(this, other) -operator fun ArithExpr.times(other: Int): ArithExpr = this * context.mkInt(other) - -infix fun Expr.`=`(other: Int): BoolExpr = this eq context.mkInt(other) -infix fun Expr.`=`(other: Expr): BoolExpr = this eq other -infix fun Expr.eq(other: Expr): BoolExpr = context.mkEq(this, other) -infix fun Expr.`!=`(other: Expr): BoolExpr = context.mkNot(this `=` other) -operator fun BoolExpr.not(): BoolExpr = context.mkNot(this) - -infix fun BoolExpr.`⇒`(other: BoolExpr): BoolExpr = this implies other -infix fun BoolExpr.`⇒`(other: Boolean): BoolExpr = this implies other -infix fun BoolExpr.implies(other: Boolean): BoolExpr = this implies context.mkBool(other) -infix fun BoolExpr.implies(other: BoolExpr): BoolExpr = context.mkImplies(this, other) -infix fun BoolExpr.and(other: BoolExpr): BoolExpr = context.mkAnd(this, other) -infix fun BoolExpr.or(other: BoolExpr): BoolExpr = context.mkOr(this, other) - -infix fun ArithExpr.gt(other: ArithExpr): BoolExpr = context.mkGt(this, other) -infix fun ArithExpr.gt(other: Int): BoolExpr = context.mkGt(this, context.mkInt(other)) - -infix fun ArithExpr.`=`(other: Int): BoolExpr = context.mkEq(this, context.mkInt(other)) - -operator fun ArrayExpr.get(index: IntExpr): Expr = context.mkSelect(this, index) -operator fun ArrayExpr.get(index: Int): Expr = this[context.mkInt(index)] -fun ArrayExpr.set(index: IntExpr, value: Expr): ArrayExpr = context.mkStore(this, index, value) -fun ArrayExpr.set(index: Int, value: Expr): ArrayExpr = set(context.mkInt(index), value) - -operator fun SeqExpr.plus(other: SeqExpr): SeqExpr = context.mkConcat(this, other) -operator fun SeqExpr.plus(other: String): SeqExpr = context.mkConcat(context.mkString(other)) - -infix fun SeqExpr.`=`(other: String): BoolExpr = context.mkEq(this, context.mkString(other)) - -class Const(private val ctx: Context, val produce: (Context, name: String) -> T) { - operator fun getValue(thisRef: Nothing?, property: KProperty<*>): T = produce(ctx, property.name) -} - -fun Context.declareInt() = Const(this) { ctx, name -> ctx.mkIntConst(name) } -fun Context.declareBool() = Const(this) { ctx, name -> ctx.mkBoolConst(name) } -fun Context.declareReal() = Const(this) { ctx, name -> ctx.mkRealConst(name) } -fun Context.declareString() = Const(this) { ctx, name -> ctx.mkConst(name, stringSort) as SeqExpr } -fun Context.declareList(sort: ListSort) = Const(this) { ctx, name -> ctx.mkConst(name, sort) } -fun Context.declareIntArray() = Const(this) { ctx, name -> ctx.mkArrayConst(name, intSort, intSort) } - - -operator fun FuncDecl.invoke(vararg expr: Expr): Expr = context.mkApp(this, *expr) - -fun Model.eval(expr: Expr): Expr = this.eval(expr, true) -fun Model.eval(vararg exprs: Expr): List = exprs.map { this.eval(it, true) } \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/sarif/SarifReportTest.kt b/utbot-framework/src/test/kotlin/org/utbot/sarif/SarifReportTest.kt deleted file mode 100644 index fad4ff0334..0000000000 --- a/utbot-framework/src/test/kotlin/org/utbot/sarif/SarifReportTest.kt +++ /dev/null @@ -1,237 +0,0 @@ -package org.utbot.sarif - -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtImplicitlyThrownException -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.UtTestCase -import org.junit.Test -import org.mockito.Mockito - -class SarifReportTest { - - @Test - fun testNonEmptyReport() { - val actualReport = SarifReport( - testCases = listOf(), - generatedTestsCode = "", - sourceFindingEmpty - ).createReport() - - assert(actualReport.isNotEmpty()) - } - - @Test - fun testNoUncheckedExceptions() { - val sarif = SarifReport( - testCases = listOf(testCase), - generatedTestsCode = "", - sourceFindingEmpty - ).createReport().toSarif() - - assert(sarif.runs.first().results.isEmpty()) - } - - @Test - fun testDetectAllUncheckedExceptions() { - mockUtMethodNames() - - val mockUtExecutionNPE = Mockito.mock(UtExecution::class.java, Mockito.RETURNS_DEEP_STUBS) - Mockito.`when`(mockUtExecutionNPE.result).thenReturn( - UtImplicitlyThrownException(NullPointerException(), false), - ) - Mockito.`when`(mockUtExecutionNPE.stateBefore.parameters).thenReturn(listOf()) - - val mockUtExecutionAIOBE = Mockito.mock(UtExecution::class.java, Mockito.RETURNS_DEEP_STUBS) - Mockito.`when`(mockUtExecutionAIOBE.result).thenReturn( - UtImplicitlyThrownException(ArrayIndexOutOfBoundsException(), false), - ) - Mockito.`when`(mockUtExecutionAIOBE.stateBefore.parameters).thenReturn(listOf()) - - val testCases = listOf( - UtTestCase(mockUtMethod, listOf(mockUtExecutionNPE)), - UtTestCase(mockUtMethod, listOf(mockUtExecutionAIOBE)) - ) - - val report = SarifReport( - testCases = testCases, - generatedTestsCode = "", - sourceFindingEmpty - ).createReport().toSarif() - - assert(report.runs.first().results[0].message.text.contains("NullPointerException")) - assert(report.runs.first().results[1].message.text.contains("ArrayIndexOutOfBoundsException")) - } - - @Test - fun testCorrectResultLocations() { - mockUtMethodNames() - - Mockito.`when`(mockUtExecution.result).thenReturn( - UtImplicitlyThrownException(NullPointerException(), false) - ) - Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn(listOf()) - Mockito.`when`(mockUtExecution.path.lastOrNull()?.stmt?.javaSourceStartLineNumber).thenReturn(1337) - Mockito.`when`(mockUtExecution.testMethodName).thenReturn("testMain_ThrowArithmeticException") - - val report = sarifReportMain.createReport().toSarif() - - val result = report.runs.first().results.first() - val location = result.locations.first().physicalLocation - val relatedLocation = result.relatedLocations.first().physicalLocation - - assert(location.artifactLocation.uri.contains("Main.java")) - assert(location.region.startLine == 1337) - assert(relatedLocation.artifactLocation.uri.contains("MainTest.java")) - assert(relatedLocation.region.startLine == 1) - } - - @Test - fun testCorrectMethodParameters() { - mockUtMethodNames() - - Mockito.`when`(mockUtExecution.result).thenReturn( - UtImplicitlyThrownException(NullPointerException(), false) - ) - Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn( - listOf( - UtPrimitiveModel(227), - UtPrimitiveModel(3.14), - UtPrimitiveModel(false) - ) - ) - - val report = sarifReportMain.createReport().toSarif() - - val result = report.runs.first().results.first() - assert(result.message.text.contains("227")) - assert(result.message.text.contains("3.14")) - assert(result.message.text.contains("false")) - } - - @Test - fun testCorrectCodeFlows() { - mockUtMethodNames() - - val uncheckedException = Mockito.mock(NullPointerException::class.java) - val stackTraceElement = StackTraceElement("Main", "main", "Main.java", 17) - Mockito.`when`(uncheckedException.stackTrace).thenReturn( - Array(2) { stackTraceElement } - ) - - Mockito.`when`(mockUtExecution.result).thenReturn( - UtImplicitlyThrownException(uncheckedException, false) - ) - Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn(listOf()) - - val report = sarifReportMain.createReport().toSarif() - - val result = report.runs.first().results.first().codeFlows.first().threadFlows.first().locations.map { - it.location.physicalLocation - } - for (index in 0..1) { - assert(result[index].artifactLocation.uri.contains("Main.java")) - assert(result[index].region.startLine == 17) - } - } - - @Test - fun testCodeFlowsStartsWithMethodCall() { - mockUtMethodNames() - - val uncheckedException = Mockito.mock(NullPointerException::class.java) - val stackTraceElement = StackTraceElement("Main", "main", "Main.java", 3) - Mockito.`when`(uncheckedException.stackTrace).thenReturn(arrayOf(stackTraceElement)) - - Mockito.`when`(mockUtExecution.result).thenReturn( - UtImplicitlyThrownException(uncheckedException, false) - ) - Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn(listOf()) - Mockito.`when`(mockUtExecution.testMethodName).thenReturn("testMain_ThrowArithmeticException") - - val report = sarifReportMain.createReport().toSarif() - - val codeFlowPhysicalLocations = report.runs[0].results[0].codeFlows[0].threadFlows[0].locations.map { - it.location.physicalLocation - } - assert(codeFlowPhysicalLocations[0].artifactLocation.uri.contains("MainTest.java")) - assert(codeFlowPhysicalLocations[0].region.startLine == 3) - } - - @Test - fun testCodeFlowsStartsWithPrivateMethodCall() { - mockUtMethodNames() - - val uncheckedException = Mockito.mock(NullPointerException::class.java) - val stackTraceElement = StackTraceElement("Main", "main", "Main.java", 3) - Mockito.`when`(uncheckedException.stackTrace).thenReturn(arrayOf(stackTraceElement)) - - Mockito.`when`(mockUtExecution.result).thenReturn( - UtImplicitlyThrownException(uncheckedException, false) - ) - Mockito.`when`(mockUtExecution.stateBefore.parameters).thenReturn(listOf()) - Mockito.`when`(mockUtExecution.testMethodName).thenReturn("testMain_ThrowArithmeticException") - - val report = sarifReportPrivateMain.createReport().toSarif() - - val codeFlowPhysicalLocations = report.runs[0].results[0].codeFlows[0].threadFlows[0].locations.map { - it.location.physicalLocation - } - assert(codeFlowPhysicalLocations[0].artifactLocation.uri.contains("MainTest.java")) - assert(codeFlowPhysicalLocations[0].region.startLine == 4) - } - - // internal - - private val mockUtMethod = Mockito.mock(UtMethod::class.java, Mockito.RETURNS_DEEP_STUBS) - - private val mockUtExecution = Mockito.mock(UtExecution::class.java, Mockito.RETURNS_DEEP_STUBS) - - private val testCase = UtTestCase(mockUtMethod, listOf(mockUtExecution)) - - private fun mockUtMethodNames() { - Mockito.`when`(mockUtMethod.callable.name).thenReturn("main") - Mockito.`when`(mockUtMethod.clazz.qualifiedName).thenReturn("Main") - } - - private fun String.toSarif(): Sarif = jacksonObjectMapper().readValue(this) - - // constants - - private val sourceFindingEmpty = SourceFindingStrategyDefault( - sourceClassFqn = "", - sourceFilePath = "", - testsFilePath = "", - projectRootPath = "" - ) - - private val sourceFindingMain = SourceFindingStrategyDefault( - sourceClassFqn = "Main", - sourceFilePath = "src/Main.java", - testsFilePath = "test/MainTest.java", - projectRootPath = "." - ) - - private val generatedTestsCodeMain = """ - public void testMain_ThrowArithmeticException() { - Main main = new Main(); - main.main(0); - } - """.trimIndent() - - private val generatedTestsCodePrivateMain = """ - public void testMain_ThrowArithmeticException() { - Main main = new Main(); - // ... - mainMethod.invoke(main, mainMethodArguments); - } - """.trimIndent() - - private val sarifReportMain = - SarifReport(listOf(testCase), generatedTestsCodeMain, sourceFindingMain) - - private val sarifReportPrivateMain = - SarifReport(listOf(testCase), generatedTestsCodePrivateMain, sourceFindingMain) -} \ No newline at end of file diff --git a/utbot-framework/src/test/resources/log4j2.xml b/utbot-framework/src/test/resources/log4j2.xml deleted file mode 100644 index 11a2d0701c..0000000000 --- a/utbot-framework/src/test/resources/log4j2.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/utbot-fuzzers/build.gradle b/utbot-fuzzers/build.gradle deleted file mode 100644 index fd94590950..0000000000 --- a/utbot-fuzzers/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - id "com.github.johnrengelman.shadow" version "6.1.0" -} - -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" - -dependencies { - api project(':utbot-framework-api') - implementation "com.github.UnitTestBot:soot:${soot_commit_hash}" - implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version -} - -compileJava { - options.compilerArgs = [] -} - -shadowJar { - configurations = [project.configurations.compileClasspath] - archiveClassifier.set('') - minimize() -} \ No newline at end of file diff --git a/utbot-fuzzers/readme.md b/utbot-fuzzers/readme.md deleted file mode 100644 index 14327ea259..0000000000 --- a/utbot-fuzzers/readme.md +++ /dev/null @@ -1,62 +0,0 @@ -# UTBot Fuzzer - -Fuzzer generates method input values to improve method coverage or find unexpected errors. In UTBot next strategies can be used to find those values: - -* **Default values** for objects, e.g. 0, 0.0, empty string or null values. -* **Bound values** of primitives types, e.g. Integer.MAX_VALUE, Double.POSITIVE_INFINITY, etc. -* **Method constants and their simple mutations** of primitive types. -* **Simple objects** created via its constructors. - -## Design - -Fuzzer requires model providers which are a simple functions to create a set of `UtModel` for a given `ClassId`. Fuzzer iterates through these providers and creates models, which are used for generating all possible combinations. Each combination contains values for the method. For example, if a method has `String, double, int` as parameters then fuzzer can create combination `"sometext", Double.POSITIVE_INFINITY, 0`. - -Fuzzer's entry point is: -```kotlin -// org.utbot.fuzzer.FuzzerKt -fun fuzz(method: FuzzedMethodDescription, vararg models: ModelProvider): Sequence> -``` - -Model provider should implement - -```kotlin -fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) -``` -where consumer accepts 2 values: index of a parameter for the method and model for this parameter. For every parameter should exist at least one `UtModel`. `ModelProvider.withFallback` can be used to process those classes which cannot be processed by provider. Several providers can be combined into one by using `ModelProvider.with(anotherModel: ModelProvider)`. - -Common way to generate all combinations is: - -```kotlin -ObjectModelProvider() - .with(PrimitiveModelProvider) - // ... - .with(NullModelProvider) - .withFallback { classId -> - createDefaultModelByClass(classID) - } -``` -or - -```kotlin -// org.utbot.fuzzer.FuzzerKt -fun defaultModelProviders(idGenerator: ToIntFunction) -``` - -## List of builtin Providers - -### PrimitiveModelProvider - -Creates default values and some corner case values such as Integer.MAX_VALUE, 0.0, Double.NaN, empty string, etc. - -### ConstantsModelProvider - -Uses information about concrete values from `FuzzedMethodDescription#concreteValues` to generate simple values. -At the moment, only primitive values are supported. - -### ObjectModelProvider - -Creates models of class that has public constructor with primitives as parameters only. - -### NullModelProvider - -Creates `UtNullModel` for every reference class. \ No newline at end of file diff --git a/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/Generator.java b/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/Generator.java deleted file mode 100644 index c0770653e7..0000000000 --- a/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/Generator.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.utbot.fuzzer.baseline.generator; - -import org.utbot.framework.plugin.api.UtMethod; -import org.utbot.framework.plugin.api.UtValueExecution; -import org.utbot.framework.plugin.api.UtValueTestCase; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import kotlin.jvm.JvmClassMappingKt; -import kotlin.reflect.KCallable; -import kotlin.reflect.KClass; -import kotlin.reflect.KFunction; -import kotlin.reflect.jvm.ReflectJvmMapping; - -import static java.util.Collections.emptyMap; - -public class Generator { - public static List> executions(UtMethod utMethod, Class clazz, Object caller) { - return Arrays.stream(clazz.getDeclaredMethods()) - .filter(method -> isSameMethod(utMethod, method)) - .filter(method -> !Modifier.isPrivate(method.getModifiers())) - .map(method -> new TestMethodGen(method, caller).gen()) - .findFirst().orElseGet(Collections::emptyList); - } - - private static boolean isSameMethod(UtMethod utMethod, Method method) { - KCallable utCallable = utMethod.getCallable(); - if (!(utCallable instanceof KFunction)) { - return false; - } - KFunction utKFunction = (KFunction) utCallable; - Method utJavaMethod = ReflectJvmMapping.getJavaMethod(utKFunction); - if (utJavaMethod == null) { - return false; - } - return utJavaMethod.equals(method); - } - - public static UtValueTestCase generateTests(UtMethod method) throws IllegalAccessException, InstantiationException { - KClass kClass = method.getClazz(); - Class clazz = JvmClassMappingKt.getJavaClass(kClass); - // TODO: newInstance() is deprecated, need to create an instance in another way - Object object = clazz.newInstance(); - return new UtValueTestCase<>(method, executions(method, clazz, object), null, emptyMap()); - } -} diff --git a/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/TestMethodGen.java b/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/TestMethodGen.java deleted file mode 100644 index ae06530b7e..0000000000 --- a/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/TestMethodGen.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.utbot.fuzzer.baseline.generator; - -import org.utbot.framework.plugin.api.UtConcreteValue; -import org.utbot.framework.plugin.api.UtValueExecution; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.ArrayList; -import java.util.List; - -import static org.utbot.fuzzer.baseline.BaselineFuzzerKt.failedUtExecution; -import static org.utbot.fuzzer.baseline.BaselineFuzzerKt.successfulUtExecution; - - -/** - * - */ -public class TestMethodGen { - // TODO: add selection of test cases number to the plugin - private final int TEST_CASES_NUMBER = 5; - - private final Method method; - private final Object caller; - - private final TypeVariable[] typeParameters; - private final Type[] typeValues; - - public TestMethodGen(Method method, Object caller) { - this.method = method; - this.caller = caller; - typeParameters = method.getTypeParameters(); - typeValues = TypeChooser.chooseTypeParameterValues(typeParameters); - } - - public List> gen() { - List> executions = new ArrayList<>(); - for (int i = 0; i < TEST_CASES_NUMBER; i++) { - UtValueExecution execution = generateExecution(); - if (execution != null) { - executions.add(generateExecution()); - } - } - return executions; - } - - private UtValueExecution generateExecution() { - List> params = new ArrayList<>(); - for (int i = 0; i < method.getParameters().length; i++) { - Type parType = method.getGenericParameterTypes()[i]; - ValueGen valueGen = new ValueGen(parType, typeParameters, typeValues); - Object value = valueGen.generate(); - params.add(UtConcreteValue.invoke(value)); - } - Object[] arguments = params.stream() - .map(UtConcreteValue::getValue).toArray(); - - UtValueExecution execution; - try { - Object result = method.invoke(caller, arguments); - execution = successfulUtExecution(params, result); - } catch (IllegalAccessException | InvocationTargetException e) { - // TODO: what if these exceptions were thrown by the method under test? - return null; - } catch (Exception e) { - execution = failedUtExecution(params, e); - } - - return execution; - } -} diff --git a/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/TypeChooser.java b/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/TypeChooser.java deleted file mode 100644 index d28cbd2e81..0000000000 --- a/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/TypeChooser.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.utbot.fuzzer.baseline.generator; - -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; - -/** - * - */ -public class TypeChooser { - private static Type chooseType(TypeVariable typeVariable) { - Type[] bounds = typeVariable.getBounds(); - if (bounds.length <= 0) { - return null; - } - Type bound = bounds[0]; - return bound; - } - - public static Type[] chooseTypeParameterValues(TypeVariable[] typeParameters) { - Type[] typeValues = new Type[typeParameters.length]; - for (int i = 0; i < typeParameters.length; i++) { - typeValues[i] = chooseType(typeParameters[i]); - } - return typeValues; - } -} diff --git a/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/Util.java b/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/Util.java deleted file mode 100644 index 0655329c59..0000000000 --- a/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/Util.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.utbot.fuzzer.baseline.generator; - -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; -import java.util.regex.Pattern; - -/** - * - */ -public class Util { - private static final Random rnd = new Random(); - - public static List addTabs(List src, int count) { - List res = new LinkedList(); - for (String line : src) { - String tabs = ""; - for (int i = 0; i < count; i++) { - tabs += "\t"; - } - res.add(tabs + line); - } - return res; - } - - public static int findType(TypeVariable[] typeVariables, TypeVariable findThis) { - String typeName = findThis.getName(); - for (int i = 0; i < typeVariables.length; i++) { - if (typeVariables[i].getName().equals(typeName)) { - return i; - } - } - return -1; - } - - public static String getTypeName(Type t, TypeVariable[] typeVariables, Type[] typeValues) { - String res = t.getTypeName(); - if (typeVariables != null && typeValues != null - && typeVariables.length > 0 && typeValues.length == typeVariables.length) { - for (int i = 0; i < typeVariables.length; i++) { - String pattern = "\\b" + Pattern.quote(typeVariables[i].getName()) + "\\b"; - String value = typeValues[i].getTypeName(); - res = res.replaceAll(pattern, value); - } - } - return res; - } - - public static int rndRange(int min, int max) { - return rnd.nextInt(max - min + 1) + min; - } - - public static int getArrayDepth(Type t) { - int count = 0; - while ((t instanceof GenericArrayType) - || (t instanceof Class && ((Class) t).isArray())) { - count++; - if (t instanceof GenericArrayType) { - t = ((GenericArrayType) t).getGenericComponentType(); - } else { - t = ((Class) t).getComponentType(); - } - } - return count; - } - - public static Type getArrayChildType(Type t) { - while ((t instanceof GenericArrayType) - || (t instanceof Class && ((Class) t).isArray())) { - if (t instanceof GenericArrayType) { - t = ((GenericArrayType) t).getGenericComponentType(); - } else { - t = ((Class) t).getComponentType(); - } - } - return t; - } - - public static String repeat(String str, int count) { - String res = ""; - for (int i = 0; i < count; i++) { - res += str; - } - return res; - } -} diff --git a/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/ValueGen.java b/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/ValueGen.java deleted file mode 100644 index fe7794607f..0000000000 --- a/utbot-fuzzers/src/main/java/org/utbot/fuzzer/baseline/generator/ValueGen.java +++ /dev/null @@ -1,299 +0,0 @@ -package org.utbot.fuzzer.baseline.generator; - -import java.lang.reflect.Constructor; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; -import java.util.Set; - -/** - * - */ -public class ValueGen { - private final static Random rnd = new Random(); - - private Type type; - private TypeVariable[] methodTypeParameters; - private Type[] methodTypeValues; - - private final String LIST_TYPE = "java.util.List"; - private final String ARRAY_LIST_TYPE = "java.util.ArrayList"; - private final String LINKED_LIST_TYPE = "java.util.LinkedList"; - - private final Set LIST_TYPES = new HashSet<>( - Arrays.asList( - LIST_TYPE, - ARRAY_LIST_TYPE, - LINKED_LIST_TYPE - ) - ); - - public ValueGen(Type type, TypeVariable[] methodTypeParameters, - Type[] methodTypeValues) { - this.type = type; - this.methodTypeParameters = methodTypeParameters; - this.methodTypeValues = methodTypeValues; - } - - private Object genPrimitive(Class clazz) { - String dataType = getTypeName(clazz); - switch (dataType) { - case "boolean": - case "java.lang.Boolean": - return rnd.nextInt(2) == 1; - case "byte": - case "java.lang.Byte": - return (byte) (-128 + rnd.nextInt(256)); - case "char": - case "java.lang.Character": - return (char) (rnd.nextInt(127 - 32) + 32); - case "double": - case "java.lang.Double": - return rnd.nextDouble(); - case "float": - case "java.lang.Float": - return rnd.nextFloat(); - case "int": - case "java.lang.Integer": - return rnd.nextInt(); - case "long": - case "java.lang.Long": - return rnd.nextLong(); - case "short": - case "java.lang.Short": - return (short) (-32768 + rnd.nextInt(65536)); - default: - return null; - } - } - - private String genString() { - int len = Util.rndRange(1, 4); - String str = ""; - for (int i = 0; i < len; i++) { - char rndChar = (char) (rnd.nextInt(123 - 97) + 97); - str += rndChar; - } - return str; - } - -// TODO: rewrite so that it can be used with our plugin's API - /*private String genArrayInitializer(Type t) throws IOException, ClassNotFoundException { - boolean noBraces = false; - int elementsCount = Util.rndRange(1, 3); - Type subtype; - if (t instanceof GenericArrayType) { - // e.g. t is "List[][]" or "List[]" - GenericArrayType gat = (GenericArrayType) t; - // e.g. subtype is "List[]" or "List" respectively - subtype = gat.getGenericComponentType(); - } else { - // e.g. t is "String[][]" or "int[]" - Class c = (Class) t; - // e.g. subtype is "String[]" or "int" - subtype = c.getComponentType(); - } - List values = new LinkedList(); - for (int i = 0; i < elementsCount; i++) { - values.add(genValue(subtype)); - } - String result = String.join(", ", values); - if (noBraces) { - return result; - } - return "{ " + result + " }"; - }*/ - - private boolean isListType(String typeName) { - return LIST_TYPES.contains(typeName); - } - - private List emptyList(String listTypeName) { - switch (listTypeName) { - case LINKED_LIST_TYPE: - return new LinkedList<>(); - case LIST_TYPE: - case ARRAY_LIST_TYPE: - return new ArrayList<>(); - default: - throw new RuntimeException("Unknown list type: " + listTypeName); - } - } - - // "Class, Type" - private boolean isType(ParameterizedType pt) { - Type baseType = pt.getRawType(); - if (baseType == null) { - return false; - } - String baseTypeName = getTypeName(baseType); - return baseTypeName.equals("java.lang.reflect.Type") - || baseTypeName.equals("java.lang.Class"); - } - -// TODO: rewrite so that it can be used with our plugin's API - // "Class, Type" -// private String genValueForType(ParameterizedType pt) { -// Type baseType = pt.getRawType(); -// if (baseType == null) { -// return "?"; -// } -// String baseTypeName = getTypeName(baseType); -// String className = "?"; -// if (baseTypeName.equals("java.lang.reflect.Type")) { -// className = "java.lang.Object"; -// } else if (baseTypeName.equals("java.lang.Class")) { -// className = getTypeName(pt.getActualTypeArguments()[0]); -// } -// return "(" + getTypeName(pt) + ") java.lang.Class.forName(\"" + className + "\")"; -// } - -// TODO: rewrite so that it can be used with our plugin's API -// @SneakyThrows -// private String genArrayValue(Type t) { -// if (t instanceof GenericArrayType) { -// int arrayDepth = Util.getArrayDepth(t); -// Type childType = Util.getArrayChildType(t); -// Type rawType = childType; -// if (childType instanceof ParameterizedType) { -// rawType = ((ParameterizedType)childType).getRawType(); -// } -// String castingPart = "(" + getTypeName(t) + ") "; -// String mainPart = "new " + getTypeName(rawType) + Util.repeat("[]", arrayDepth); -// return castingPart + mainPart + genArrayInitializer(t); -// } -// return "new " + getTypeName(t) + genArrayInitializer(t); -// } - - private Constructor getPublicConstructor(Class c) { - Constructor[] constructorArray = c.getConstructors(); - for (int i = 0; i < constructorArray.length; i++) { - int modifiers = constructorArray[i].getModifiers(); - if (Modifier.isPublic(modifiers)) { - return constructorArray[i]; - } - } - return null; - } - - - private Object genClassValue(Class c) { - if (c.isInterface()) { - return null; // interface cannot be instantiated - } - if (Modifier.isAbstract(c.getModifiers())) { - return null; //abstract classes cannot be instantiated - } - List arguments = new LinkedList<>(); - Constructor ctor = getPublicConstructor(c); - if (ctor == null) { - return null; //no public constructor available - } - TypeVariable[] typeParameters = ctor.getTypeParameters(); - Type[] typeValues = TypeChooser.chooseTypeParameterValues(typeParameters); - int parCnt = ctor.getParameterCount(); - for (int i = 0; i < parCnt; i++) { - Type parType = ctor.getGenericParameterTypes()[i]; - ValueGen gen = new ValueGen(parType, typeParameters, typeValues); - Object value = gen.generate(); - arguments.add(value); - } - try { - return ctor.newInstance(arguments.toArray()); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException("Could not instantiate class " + c.getName()); - } - } - - private boolean isPrimitive(Class c) { - return c.isPrimitive() || - c.getTypeName().equals("java.lang.Short") || - c.getTypeName().equals("java.lang.Boolean") || - c.getTypeName().equals("java.lang.Byte") || - c.getTypeName().equals("java.lang.Character") || - c.getTypeName().equals("java.lang.Double") || - c.getTypeName().equals("java.lang.Float") || - c.getTypeName().equals("java.lang.Integer") || - c.getTypeName().equals("java.lang.Long") || - c.getTypeName().equals("java.lang.Short"); - } - - private Object genValue(Type t) { - if (t instanceof TypeVariable) { - // e.g. T for: " void foo(List some) {}" - TypeVariable tv = (TypeVariable) t; - int typeIndex = Util.findType(methodTypeParameters, tv); - return genValue(methodTypeValues[typeIndex]); - } else if (t instanceof ParameterizedType) { - // e.g. List, Foo" - ParameterizedType pt = (ParameterizedType) t; - Type rawType = pt.getRawType(); - String rawTypeName = getTypeName(rawType); - if (isListType(rawTypeName)) { - Type childType = pt.getActualTypeArguments()[0]; - List list = emptyList(rawTypeName); - int elemCnt = Util.rndRange(1, 3); - for (int i = 0; i < elemCnt; i++) { - list.add(genValue(childType)); - } - return list; - } - if (isType(pt)) { - // TODO - return null; // genValueForType(pt); - } - Type theRawType = pt.getRawType(); - if (theRawType instanceof Class) { - Class cc = (Class) theRawType; - Constructor pc = getPublicConstructor(cc); - if (pc == null || cc.isInterface() || Modifier.isAbstract(cc.getModifiers())) { - return null; // abstract generic class/interface cannot be instantiated - } - // TODO - return null; // genClassValue(cc, true); - } - return null; // failed to instantiate complex generic class or interface - } else if (t instanceof GenericArrayType) { - // TODO - return null; // genArrayValue(t); - } else if (t instanceof Class) { - Class c = (Class) t; - if (isPrimitive(c)) { - if (getTypeName(c).equals("void")) { - throw new RuntimeException("Cannot generate value for void-type"); - } - return genPrimitive(c); - } - if (getTypeName(c).equals("java.lang.String")) { - return genString(); - } - if (c.isArray()) { - // It's important to use "t" here, because array can be "List[]", - // which is GenericArrayType - // TODO - return null; //genArrayValue(t); - } - // Usual class - return genClassValue(c); - } - return null; // unknown instance type - } - - public Object generate() { - return genValue(type); - } - - private String getTypeName(Type t) { - return Util.getTypeName(t, methodTypeParameters, methodTypeValues); - } -} diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt deleted file mode 100644 index ec26297d01..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/CartesianProduct.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.utbot.fuzzer - -import kotlin.random.Random - -/** - * Creates iterable for all values of cartesian product of `lists`. - */ -class CartesianProduct( - private val lists: List>, - private val random: Random? = null -): Iterable> { - - fun asSequence(): Sequence> = iterator().asSequence() - - override fun iterator(): Iterator> { - val combinations = Combinations(*lists.map { it.size }.toIntArray()) - val sequence = if (random != null) { - val permutation = PseudoShuffledIntProgression(combinations.size, random) - (0 until combinations.size).asSequence().map { combinations[permutation[it]] } - } else { - combinations.asSequence() - } - return sequence.map { combination -> - combination.mapIndexedTo(mutableListOf()) { element, value -> lists[element][value] } - }.iterator() - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt deleted file mode 100644 index 86e706e804..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt +++ /dev/null @@ -1,86 +0,0 @@ -package org.utbot.fuzzer - -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.ExecutableId - -/** - * Method traverser is an object, - * that helps to collect information about a method. - * - * @param name pretty name of the method - * @param returnType type of returning value - * @param parameters method parameters types - * @param concreteValues any concrete values to be processed by fuzzer - * - */ -class FuzzedMethodDescription( - val name: String, - val returnType: ClassId, - val parameters: List, - val concreteValues: Collection = emptyList() -) { - - /** - * Name that can be used to generate test names - */ - var compilableName: String? = null - - /** - * Returns parameter name by its index in the signature - */ - var parameterNameMap: (Int) -> String? = { null } - - /** - * Map class id to indices of this class in parameters list. - */ - val parametersMap: Map> by lazy { - val result = mutableMapOf>() - parameters.forEachIndexed { index, classId -> - result.computeIfAbsent(classId) { mutableListOf() }.add(index) - } - result - } - - constructor(executableId: ExecutableId, concreteValues: Collection = emptyList()) : this( - executableId.classId.simpleName + "." + executableId.name, - executableId.returnType, - executableId.parameters, - concreteValues - ) -} - -/** - * Object to pass concrete values to fuzzer - */ -data class FuzzedConcreteValue( - val classId: ClassId, - val value: Any, - val relativeOp: FuzzedOp = FuzzedOp.NONE, -) - -enum class FuzzedOp(val sign: String?) { - NONE(null), - EQ("=="), - NE("!="), - GT(">"), - GE(">="), - LT("<"), - LE("<="), - CH(null), // changed or called - ; - - fun isComparisonOp() = this == EQ || this == NE || this == GT || this == GE || this == LT || this == LE - - fun reverseOrNull() : FuzzedOp? = when(this) { - EQ -> NE - NE -> EQ - GT -> LE - LT -> GE - LE -> GT - GE -> LT - else -> null - } - - fun reverseOrElse(another: (FuzzedOp) -> FuzzedOp): FuzzedOp = - reverseOrNull() ?: another(this) -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedValue.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedValue.kt deleted file mode 100644 index 3919f111cd..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/FuzzedValue.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.utbot.fuzzer - -import org.utbot.framework.plugin.api.UtModel - -/** - * Fuzzed Value stores information about concrete UtModel, reference to [ModelProvider] - * and reasons about why this value was generated. - */ -class FuzzedValue( - val model: UtModel, - val createdBy: ModelProvider? = null -) { - - /** - * Summary is a piece of useful information that clarify why this value has a concrete value. - * - * It supports a special character `%var%` that is used as a placeholder for parameter name. - * - * For example: - * 1. `%var% = 2` for a value that have value 2 - * 2. `%var% >= 4` for a value that shouldn't be less than 4 - * 3. `foo(%var%) returns true` for values that should be passed as a function parameter - * 4. `%var% has special characters` to describe content - */ - var summary: String? = null -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt deleted file mode 100644 index b15bbf19d9..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Fuzzer.kt +++ /dev/null @@ -1,73 +0,0 @@ -package org.utbot.fuzzer - -import org.utbot.fuzzer.providers.ConstantsModelProvider -import org.utbot.fuzzer.providers.ObjectModelProvider -import org.utbot.fuzzer.providers.PrimitivesModelProvider -import org.utbot.fuzzer.providers.StringConstantModelProvider -import mu.KotlinLogging -import org.utbot.fuzzer.providers.ArrayModelProvider -import org.utbot.fuzzer.providers.CharToStringModelProvider -import org.utbot.fuzzer.providers.CollectionModelProvider -import org.utbot.fuzzer.providers.PrimitiveDefaultsModelProvider -import org.utbot.fuzzer.providers.EnumModelProvider -import org.utbot.fuzzer.providers.PrimitiveWrapperModelProvider -import java.util.concurrent.atomic.AtomicInteger -import java.util.function.IntSupplier -import kotlin.random.Random - -private val logger = KotlinLogging.logger {} - -fun fuzz(description: FuzzedMethodDescription, vararg modelProviders: ModelProvider): Sequence> { - val values = List>(description.parameters.size) { mutableListOf() } - modelProviders.forEach { fuzzingProvider -> - fuzzingProvider.generate(description) { index, model -> - values[index].add(model) - } - } - description.parameters.forEachIndexed { index, classId -> - val models = values[index] - if (models.isEmpty()) { - logger.warn { "There's no models provided classId=$classId. No suitable values are generated for ${description.name}" } - return emptySequence() - } - } - return CartesianProduct(values, Random(0L)).asSequence() -} - -/** - * Creates a model provider from a list of default providers. - */ -fun defaultModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): ModelProvider { - return ModelProvider.of( - ObjectModelProvider(idGenerator), - CollectionModelProvider(idGenerator), - ArrayModelProvider(idGenerator), - EnumModelProvider, - ConstantsModelProvider, - StringConstantModelProvider, - CharToStringModelProvider, - PrimitivesModelProvider, - PrimitiveWrapperModelProvider, - ) -} - -/** - * Creates a model provider for [ObjectModelProvider] that generates values for object constructor. - */ -fun objectModelProviders(idGenerator: IntSupplier = SimpleIdGenerator()): ModelProvider { - return ModelProvider.of( - CollectionModelProvider(idGenerator), - ArrayModelProvider(idGenerator), - EnumModelProvider, - StringConstantModelProvider, - CharToStringModelProvider, - ConstantsModelProvider, - PrimitiveDefaultsModelProvider, - PrimitiveWrapperModelProvider, - ) -} - -private class SimpleIdGenerator : IntSupplier { - private val id = AtomicInteger() - override fun getAsInt() = id.incrementAndGet() -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt deleted file mode 100644 index 45d51fd83c..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ModelProvider.kt +++ /dev/null @@ -1,135 +0,0 @@ -package org.utbot.fuzzer - -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.ClassId -import java.util.function.BiConsumer - -fun interface ModelProvider { - - /** - * Generates values for the method. - * - * @param description a fuzzed method description - * @param consumer accepts index in the parameter list and [UtModel] for this parameter. - */ - fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) - - /** - * Combines this model provider with `anotherModelProvider` into one instance. - * - * This model provider is called before `anotherModelProvider`. - */ - fun with(anotherModelProvider: ModelProvider): ModelProvider { - fun toList(m: ModelProvider) = if (m is Combined) m.providers else listOf(m) - return Combined(toList(this) + toList(anotherModelProvider)) - } - - /** - * Removes `anotherModelProvider` from current one. - */ - fun except(anotherModelProvider: ModelProvider): ModelProvider { - return except { it == anotherModelProvider } - } - - /** - * Removes `anotherModelProvider` from current one. - */ - fun except(filter: (ModelProvider) -> Boolean): ModelProvider { - return if (this is Combined) { - Combined(providers.filterNot(filter)) - } else { - Combined(if (filter(this)) emptyList() else listOf(this)) - } - } - - /** - * Creates [ModelProvider] that passes unprocessed classes to `modelProvider`. - * - * Returned model provider is called before `modelProvider` is called, therefore consumer will get values - * from returned model provider and only after it calls `modelProvider`. - * - * @param modelProvider is called and every value of [ClassId] is collected which wasn't created by this model provider. - */ - fun withFallback(modelProvider: ModelProvider) : ModelProvider { - return ModelProvider { description, consumer -> - val providedByDelegateMethodParameters = mutableMapOf>() - this@ModelProvider.generate(description) { index, model -> - providedByDelegateMethodParameters.computeIfAbsent(index) { mutableListOf() }.add(model) - } - providedByDelegateMethodParameters.forEach { (index, models) -> - models.forEach { model -> - consumer.accept(index, model) - } - } - val missingParameters = - (0 until description.parameters.size).filter { !providedByDelegateMethodParameters.containsKey(it) } - if (missingParameters.isNotEmpty()) { - val values = mutableMapOf>() - modelProvider.generate(description) { i, m -> values.computeIfAbsent(i) { mutableListOf() }.add(m) } - missingParameters.forEach { index -> - values[index]?.let { models -> - models.forEach { model -> - consumer.accept(index, model) - } - } - } - } - } - } - - /** - * Creates [ModelProvider] that passes unprocessed classes to `fallbackModelSupplier` function. - * - * This model provider is called before function is called, therefore consumer will get values - * from this model provider and only after it created by `fallbackModelSupplier`. - * - * @param fallbackModelSupplier is called for every [ClassId] which wasn't created by this model provider. - */ - fun withFallback(fallbackModelSupplier: (ClassId) -> UtModel?) : ModelProvider { - return withFallback { description, consumer -> - description.parametersMap.forEach { (classId, indices) -> - fallbackModelSupplier(classId)?.let { model -> - indices.forEach { index -> - consumer.accept(index, model.fuzzed()) - } - } - } - } - } - - companion object { - @JvmStatic - fun of(vararg providers: ModelProvider): ModelProvider { - return Combined(providers.toList()) - } - - fun BiConsumer.consumeAll(indices: List, models: Sequence) { - models.forEach { model -> - indices.forEach { index -> - accept(index, model) - } - } - } - - fun BiConsumer.consumeAll(indices: List, models: List) { - consumeAll(indices, models.asSequence()) - } - } - - /** - * Wrapper class that delegates implementation to the [providers]. - */ - private class Combined(val providers: List): ModelProvider { - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - providers.forEach { provider -> - provider.generate(description, consumer) - } - } - } - - fun UtModel.fuzzed(block: FuzzedValue.() -> Unit = {}): FuzzedValue = FuzzedValue(this, this@ModelProvider).apply(block) -} - -inline fun ModelProvider.exceptIsInstance(): ModelProvider { - return except { it is T } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ObsoleteTestCaseGenerator.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ObsoleteTestCaseGenerator.kt deleted file mode 100644 index 4076e712ac..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/ObsoleteTestCaseGenerator.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.utbot.fuzzer - -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtValueTestCase - -interface ObsoleteTestCaseGenerator { - fun generate(method: UtMethod<*>, mockStrategy: MockStrategyApi): UtValueTestCase<*> -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/baseline/BaselineFuzzer.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/baseline/BaselineFuzzer.kt deleted file mode 100644 index 5903441c8d..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/baseline/BaselineFuzzer.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.utbot.fuzzer.baseline - -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.UtConcreteValue -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtValueExecution -import org.utbot.framework.plugin.api.UtValueTestCase -import org.utbot.fuzzer.ObsoleteTestCaseGenerator -import org.utbot.fuzzer.baseline.generator.Generator -import kotlin.Result.Companion.failure -import kotlin.Result.Companion.success - -object BaselineFuzzer : ObsoleteTestCaseGenerator { - override fun generate(method: UtMethod<*>, mockStrategy: MockStrategyApi): UtValueTestCase<*> = - Generator.generateTests(method) -} - -fun successfulUtExecution(params: List>, result: Any): UtValueExecution<*> = - UtValueExecution(params, success(result)) - -fun failedUtExecution(params: List>, exception: Throwable): UtValueExecution = - UtValueExecution(params, failure(exception)) diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/MethodBasedNameSuggester.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/MethodBasedNameSuggester.kt deleted file mode 100644 index 7dc6dbaa10..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/MethodBasedNameSuggester.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.utbot.fuzzer.names - -import org.utbot.framework.plugin.api.UtExecutionResult -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedValue - -class MethodBasedNameSuggester : NameSuggester { - override fun suggest(description: FuzzedMethodDescription, values: List, result: UtExecutionResult?): Sequence { - return sequenceOf(TestSuggestedInfo("test${description.compilableName?.capitalize() ?: "Created"}ByFuzzer")) - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/ModelBasedNameSuggester.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/ModelBasedNameSuggester.kt deleted file mode 100644 index 4c254a49d9..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/ModelBasedNameSuggester.kt +++ /dev/null @@ -1,142 +0,0 @@ -package org.utbot.fuzzer.names - -import org.utbot.framework.plugin.api.UtExecutionFailure -import org.utbot.framework.plugin.api.UtExecutionResult -import org.utbot.framework.plugin.api.UtExecutionSuccess -import org.utbot.framework.plugin.api.UtExplicitlyThrownException -import org.utbot.framework.plugin.api.UtImplicitlyThrownException -import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.exceptionOrNull -import org.utbot.framework.plugin.api.util.voidClassId -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedValue - -class ModelBasedNameSuggester( - private val suggester: List = listOf( - PrimitiveModelNameSuggester, - ArrayModelNameSuggester, - ) -): NameSuggester { - - var maxNumberOfParametersWhenNameIsSuggested = 3 - set(value) { - field = maxOf(0, value) - } - - override fun suggest(description: FuzzedMethodDescription, values: List, result: UtExecutionResult?): Sequence { - if (description.parameters.size > maxNumberOfParametersWhenNameIsSuggested) { - return emptySequence() - } - - return sequenceOf(TestSuggestedInfo( - testName = createTestName(description, values, result), - displayName = createDisplayName(description, values, result) - )) - } - - /** - * Name of a test. - * - * Result example: - * - * 1. *Without any information*: `testMethod` - * 2. *With parameters only*: `testMethodNameWithCornerCasesAndEmptyString` - * 3. *With return value*: `testMethodReturnZeroWithNonEmptyString` - * 4. *When throws an exception*: `testMethodThrowsNPEWithEmptyString` - */ - private fun createTestName(description: FuzzedMethodDescription, values: List, result: UtExecutionResult?): String { - val returnString = when (result) { - is UtExecutionSuccess -> (result.model as? UtPrimitiveModel)?.value?.let { v -> - when (v) { - is Number -> prettifyNumber(v) - is Boolean -> v.toString().capitalize() - else -> null - }?.let { "Returns$it" } - } - is UtExplicitlyThrownException, is UtImplicitlyThrownException -> result.exceptionOrNull()?.let { t -> - prettifyException(t).let { "Throws$it" } - } - else -> null - } ?: "" - - val parameters = values.asSequence() - .flatMap { value -> - suggester.map { suggester -> - suggester.suggest(description, value) - } - } - .filterNot { it.isNullOrBlank() } - .groupingBy { it } - .eachCount() - .entries - .joinToString(separator = "And") { (name, count) -> - name + if (count > 1) "s" else "" - } - - return buildString { - append("test") - append(description.compilableName?.capitalize() ?: "Method") - append(returnString) - if (parameters.isNotEmpty()) { - append("With", parameters) - } - } - } - - /** - * Display name of a test. - * - * Result example: - * 1. **Full name**: `firstArg = 12, secondArg < 100.0, thirdArg = empty string -> throw IllegalArgumentException` - * 2. **Name without appropriate information**: `arg_0 = 0 and others -> return 0` - */ - private fun createDisplayName(description: FuzzedMethodDescription, values: List, result: UtExecutionResult?): String { - val summaries = values.asSequence() - .mapIndexed { index, value -> - value.summary?.replace("%var%", description.parameterNameMap(index) ?: "arg_$index") - } - .filterNotNull() - .toList() - - val parameters = summaries.joinToString(postfix = if (summaries.size < values.size) " and others" else "") - - val returnValue = when(result) { - is UtExecutionSuccess -> result.model.let { m -> - when { - m is UtPrimitiveModel && m.classId != voidClassId -> "-> return " + m.value - m is UtNullModel -> "-> return null" - else -> null - } - } - is UtExplicitlyThrownException, is UtImplicitlyThrownException -> "-> throw ${(result as UtExecutionFailure).exception::class.java.simpleName}" - else -> null - } - - return listOfNotNull(parameters, returnValue).joinToString(separator = " ") - } - - companion object { - private fun prettifyNumber(value: T): String? { - return when { - value.toDouble() == 0.0 -> "Zero" - value.toDouble() == 1.0 -> "One" - value is Double -> when { - value.isNaN() -> "Nan" - value.isInfinite() -> "Infinity" - else -> null - } - (value is Byte || value is Short || value is Int || value is Long) && value.toLong() in 0..99999 -> value.toString() - else -> null - } - } - - private fun prettifyException(throwable: Throwable): String = - throwable.javaClass.simpleName - .toCharArray() - .asSequence() - .filter { it.isUpperCase() } - .joinToString(separator = "") - } - -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/TestSuggestedInfo.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/TestSuggestedInfo.kt deleted file mode 100644 index 45f212a321..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/TestSuggestedInfo.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.utbot.fuzzer.names - -/** - * Information that can be used to generate test names. - */ -class TestSuggestedInfo( - val testName: String, - val displayName: String? = null -) \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/primitive/PrimitiveFuzzer.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/primitive/PrimitiveFuzzer.kt deleted file mode 100644 index c84ba32c2b..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/primitive/PrimitiveFuzzer.kt +++ /dev/null @@ -1,48 +0,0 @@ -package org.utbot.fuzzer.primitive - -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.UtConcreteValue -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtValueExecution -import org.utbot.framework.plugin.api.UtValueTestCase -import org.utbot.fuzzer.ObsoleteTestCaseGenerator -import kotlin.Result.Companion.success -import kotlin.reflect.KCallable -import kotlin.reflect.KClass -import kotlin.reflect.KParameter.Kind -import kotlin.reflect.KType - -object PrimitiveFuzzer : ObsoleteTestCaseGenerator { - override fun generate(method: UtMethod<*>, mockStrategy: MockStrategyApi): UtValueTestCase<*> = - UtValueTestCase(method, executions(method.callable)) -} - -private fun executions(method: KCallable) = listOf(execution(method)) - -private fun execution(method: KCallable): UtValueExecution { - val params = method.parameters.filter { it.kind == Kind.VALUE }.map { it.type.utValue() } - val returnValue = success(method.returnType.utValue().value) - return UtValueExecution(params, returnValue) -} - -// TODO: we don't cover String and nullable versions of wrappers for primitive types, for instance java.lang.Integer -private fun KType.utValue(): UtConcreteValue = - when (val kClass = this.classifier as KClass<*>) { - Byte::class -> UtConcreteValue(0.toByte()) - Short::class -> UtConcreteValue(0.toShort()) - Char::class -> UtConcreteValue(0.toChar()) - Int::class -> UtConcreteValue(0) - Long::class -> UtConcreteValue(0L) - Float::class -> UtConcreteValue(0.0f) - Double::class -> UtConcreteValue(0.0) - Boolean::class -> UtConcreteValue(false) - ByteArray::class -> UtConcreteValue(byteArrayOf()) - ShortArray::class -> UtConcreteValue(shortArrayOf()) - CharArray::class -> UtConcreteValue(charArrayOf()) - IntArray::class -> UtConcreteValue(intArrayOf()) - LongArray::class -> UtConcreteValue(longArrayOf()) - FloatArray::class -> UtConcreteValue(floatArrayOf()) - DoubleArray::class -> UtConcreteValue(doubleArrayOf()) - BooleanArray::class -> UtConcreteValue(booleanArrayOf()) - else -> UtConcreteValue(null, kClass.java) - } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/AbstractModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/AbstractModelProvider.kt deleted file mode 100644 index d7dccf977c..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/AbstractModelProvider.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.* -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider -import java.util.function.BiConsumer - -/** - * Simple model implementation. - */ -@Suppress("unused") -abstract class AbstractModelProvider: ModelProvider { - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - description.parametersMap.forEach { (classId, indices) -> - toModel(classId)?.let { defaultModel -> - indices.forEach { index -> - consumer.accept(index, defaultModel.fuzzed()) - } - } - } - } - - abstract fun toModel(classId: ClassId): UtModel? -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt deleted file mode 100644 index 92095009b2..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ArrayModelProvider.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.UtArrayModel -import org.utbot.framework.plugin.api.util.defaultValueModel -import org.utbot.framework.plugin.api.util.isArray -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider -import org.utbot.fuzzer.ModelProvider.Companion.consumeAll -import java.util.function.BiConsumer -import java.util.function.IntSupplier - -class ArrayModelProvider( - private val idGenerator: IntSupplier -) : ModelProvider { - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - description.parametersMap - .asSequence() - .filter { (classId, _) -> classId.isArray } - .forEach { (arrayClassId, indices) -> - consumer.consumeAll(indices, listOf(0, 10).map { arraySize -> - UtArrayModel( - id = idGenerator.asInt, - arrayClassId, - length = arraySize, - arrayClassId.elementClassId!!.defaultValueModel(), - mutableMapOf() - ).fuzzed { - this.summary = "%var% = ${arrayClassId.elementClassId!!.simpleName}[$arraySize]" - } - }) - } - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CharToStringModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CharToStringModelProvider.kt deleted file mode 100644 index 16e20b6adc..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CharToStringModelProvider.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.util.charClassId -import org.utbot.framework.plugin.api.util.stringClassId -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider -import java.util.function.BiConsumer - -/** - * Collects all char constants and creates string with them. - */ -object CharToStringModelProvider : ModelProvider { - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - val indices = description.parametersMap[stringClassId] ?: return - if (indices.isNotEmpty()) { - val string = description.concreteValues.asSequence() - .filter { it.classId == charClassId } - .map { it.value } - .filterIsInstance() - .joinToString(separator = "") - if (string.isNotEmpty()) { - val model = UtPrimitiveModel(string).fuzzed() - indices.forEach { - consumer.accept(it, model) - } - } - } - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt deleted file mode 100644 index 349ab90aef..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/CollectionModelProvider.kt +++ /dev/null @@ -1,106 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.ConstructorId -import org.utbot.framework.plugin.api.ExecutableId -import org.utbot.framework.plugin.api.MethodId -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtStatementModel -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider -import org.utbot.fuzzer.ModelProvider.Companion.consumeAll -import java.util.function.BiConsumer -import java.util.function.IntSupplier - -/** - * Provides different collection for concrete classes. - * - * For example, ArrayList, LinkedList, Collections.singletonList can be passed to check - * if that parameter breaks anything. For example, in case method doesn't expect - * a non-modifiable collection and tries to add values. - */ -class CollectionModelProvider( - private val idGenerator: IntSupplier -) : ModelProvider { - - private val generators = mapOf( - java.util.List::class.java to ::createListModels, - java.util.Set::class.java to ::createSetModels, - java.util.Map::class.java to ::createMapModels, - java.util.Collection::class.java to ::createCollectionModels, - java.lang.Iterable::class.java to ::createCollectionModels, - java.util.Iterator::class.java to ::createIteratorModels, - ) - - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - description.parametersMap - .asSequence() - .forEach { (classId, indices) -> - generators[classId.jClass]?.let { createModels -> - consumer.consumeAll(indices, createModels().map { it.fuzzed() }) - } - } - } - - private fun createListModels(): List { - return listOf( - java.util.List::class.java.createdBy(java.util.ArrayList::class.java.asConstructor()), - java.util.List::class.java.createdBy(java.util.LinkedList::class.java.asConstructor()), - java.util.List::class.java.createdBy(java.util.Collections::class.java.methodCall("emptyList", java.util.List::class.java)), - java.util.List::class.java.createdBy(java.util.Collections::class.java.methodCall("synchronizedList", java.util.List::class.java, params = listOf(java.util.List::class.java)), listOf( - java.util.List::class.java.createdBy(java.util.ArrayList::class.java.asConstructor()) - )), - ) - } - - private fun createSetModels(): List { - return listOf( - java.util.Set::class.java.createdBy(java.util.HashSet::class.java.asConstructor()), - java.util.Set::class.java.createdBy(java.util.TreeSet::class.java.asConstructor()), - java.util.Set::class.java.createdBy(java.util.Collections::class.java.methodCall("emptySet", java.util.Set::class.java)) - ) - } - - private fun createMapModels(): List { - return listOf( - java.util.Map::class.java.createdBy(java.util.HashMap::class.java.asConstructor()), - java.util.Map::class.java.createdBy(java.util.TreeMap::class.java.asConstructor()), - java.util.Map::class.java.createdBy(java.util.Collections::class.java.methodCall("emptyMap", java.util.Map::class.java)), - ) - } - - private fun createCollectionModels(): List { - return listOf( - java.util.Collection::class.java.createdBy(java.util.ArrayList::class.java.asConstructor()), - java.util.Collection::class.java.createdBy(java.util.HashSet::class.java.asConstructor()), - java.util.Collection::class.java.createdBy(java.util.Collections::class.java.methodCall("emptySet", java.util.Set::class.java)), - ) - } - - private fun createIteratorModels(): List { - return listOf( - java.util.Iterator::class.java.createdBy(java.util.Collections::class.java.methodCall("emptyIterator", java.util.Iterator::class.java)), - ) - } - - private fun Class<*>.asConstructor() = ConstructorId(id, emptyList()) - - private fun Class<*>.methodCall(methodName: String, returnType: Class<*>, params: List> = emptyList()) = MethodId(id, methodName, returnType.id, params.map { it.id }) - - private fun Class<*>.createdBy(init: ExecutableId, params: List = emptyList()): UtAssembleModel { - val instantiationChain = mutableListOf() - val genId = idGenerator.asInt - return UtAssembleModel( - genId, - id, - "${init.classId.name}${init.parameters}#" + genId.toString(16), - instantiationChain - ).apply { - instantiationChain += UtExecutableCallModel(null, init, params, this) - } - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt deleted file mode 100644 index 659a2281c2..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ConstantsModelProvider.kt +++ /dev/null @@ -1,53 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.util.isPrimitive -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedOp -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider -import java.util.function.BiConsumer - -/** - * Traverses through method constants and creates appropriate models for them. - */ -object ConstantsModelProvider : ModelProvider { - - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - description.concreteValues - .asSequence() - .filter { (classId, _) -> classId.isPrimitive } - .forEach { (_, value, op) -> - sequenceOf( - UtPrimitiveModel(value).fuzzed { summary = "%var% = $value" }, - modifyValue(value, op) - ) - .filterNotNull() - .forEach { m -> - description.parametersMap.getOrElse(m.model.classId) { emptyList() }.forEach { index -> - consumer.accept(index, m) - } - } - } - } - - private fun modifyValue(value: Any, op: FuzzedOp): FuzzedValue? { - if (!op.isComparisonOp()) return null - val multiplier = if (op == FuzzedOp.LT || op == FuzzedOp.GE) -1 else 1 - return when(value) { - is Boolean -> value.not() - is Byte -> value + multiplier.toByte() - is Char -> (value.toInt() + multiplier).toChar() - is Short -> value + multiplier.toShort() - is Int -> value + multiplier - is Long -> value + multiplier.toLong() - is Float -> value + multiplier.toDouble() - is Double -> value + multiplier.toDouble() - else -> null - }?.let { UtPrimitiveModel(it).fuzzed { summary = "%var% ${ - (if (op == FuzzedOp.EQ || op == FuzzedOp.LE || op == FuzzedOp.GE) { - op.reverseOrNull() ?: error("cannot find reverse operation for $op") - } else op).sign - } $value" } } - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt deleted file mode 100644 index 68af765931..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/EnumModelProvider.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.UtEnumConstantModel -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.isSubtypeOf -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider -import org.utbot.fuzzer.ModelProvider.Companion.consumeAll -import java.util.function.BiConsumer - -object EnumModelProvider : ModelProvider { - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - description.parametersMap - .asSequence() - .filter { (classId, _) -> classId.isSubtypeOf(Enum::class.java.id) } - .forEach { (classId, indices) -> - consumer.consumeAll(indices, classId.jClass.enumConstants.filterIsInstance>().map { - UtEnumConstantModel(classId, it).fuzzed { summary = "%var% = ${it.name}" } - }) - } - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt deleted file mode 100644 index 3aa9085d95..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/NullModelProvider.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.UtNullModel -import org.utbot.framework.plugin.api.util.isRefType -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider -import java.util.function.BiConsumer - -/** - * Provides [UtNullModel] for every reference class. - */ -@Suppress("unused") // disabled until fuzzer breaks test with null/nonnull annotations -object NullModelProvider : ModelProvider { - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - description.parametersMap - .asSequence() - .filter { (classId, _) -> classId.isRefType } - .forEach { (classId, indices) -> - val model = UtNullModel(classId) - indices.forEach { consumer.accept(it, model.fuzzed { this.summary = "%var% = null" }) } - } - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt deleted file mode 100644 index 52d9f0c054..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/ObjectModelProvider.kt +++ /dev/null @@ -1,132 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.ConstructorId -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.framework.plugin.api.UtStatementModel -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.isPrimitive -import org.utbot.framework.plugin.api.util.isPrimitiveWrapper -import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.stringClassId -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider -import org.utbot.fuzzer.exceptIsInstance -import org.utbot.fuzzer.fuzz -import org.utbot.fuzzer.objectModelProviders -import org.utbot.fuzzer.providers.ConstantsModelProvider.fuzzed -import java.lang.reflect.Constructor -import java.lang.reflect.Modifier -import java.util.function.BiConsumer -import java.util.function.IntSupplier - -/** - * Creates [UtAssembleModel] for objects which have public constructors with primitives types and String as parameters. - */ -class ObjectModelProvider : ModelProvider { - - var modelProvider: ModelProvider - - private val idGenerator: IntSupplier - private val recursion: Int - private val limit: Int - - constructor(idGenerator: IntSupplier) : this(idGenerator, Int.MAX_VALUE) - - constructor(idGenerator: IntSupplier, limit: Int) : this(idGenerator, limit, 1) - - private constructor(idGenerator: IntSupplier, limit: Int, recursion: Int) { - this.idGenerator = idGenerator - this.recursion = recursion - this.limit = limit - this.modelProvider = objectModelProviders(idGenerator) - } - - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - val fuzzedValues = with(description) { - parameters.asSequence() - .filterNot { it == stringClassId || it.isPrimitiveWrapper } - .flatMap { classId -> - collectConstructors(classId) { javaConstructor -> - isPublic(javaConstructor) - }.sortedWith( - primitiveParameterizedConstructorsFirstAndThenByParameterCount - ).take(limit) - } - .associateWith { constructorId -> - val modelProviderWithoutRecursion = modelProvider.exceptIsInstance() - fuzzParameters( - constructorId, - if (recursion > 0) { - ObjectModelProvider(idGenerator, limit = 1, recursion - 1).with(modelProviderWithoutRecursion) - } else { - modelProviderWithoutRecursion.withFallback(NullModelProvider) - } - ) - } - .flatMap { (constructorId, fuzzedParameters) -> - if (constructorId.parameters.isEmpty()) { - sequenceOf(assembleModel(idGenerator.asInt, constructorId, emptyList())) - } - else { - fuzzedParameters.map { params -> - assembleModel(idGenerator.asInt, constructorId, params) - } - } - } - } - - fuzzedValues.forEach { fuzzedValue -> - description.parametersMap[fuzzedValue.model.classId]?.forEach { index -> - consumer.accept(index, fuzzedValue) - } - } - } - - companion object { - private fun collectConstructors(classId: ClassId, predicate: (Constructor<*>) -> Boolean): Sequence { - return classId.jClass.declaredConstructors.asSequence() - .filter(predicate) - .map { javaConstructor -> - ConstructorId(classId, javaConstructor.parameters.map { it.type.id }) - } - } - - private fun isPublic(javaConstructor: Constructor<*>): Boolean { - return javaConstructor.modifiers and Modifier.PUBLIC != 0 - } - - private fun FuzzedMethodDescription.fuzzParameters(constructorId: ConstructorId, vararg modelProviders: ModelProvider): Sequence> { - val fuzzedMethod = FuzzedMethodDescription( - executableId = constructorId, - concreteValues = this.concreteValues - ) - return fuzz(fuzzedMethod, *modelProviders) - } - - private fun assembleModel(id: Int, constructorId: ConstructorId, params: List): FuzzedValue { - val instantiationChain = mutableListOf() - return UtAssembleModel( - id, - constructorId.classId, - "${constructorId.classId.name}${constructorId.parameters}#" + id.toString(16), - instantiationChain - ).apply { - instantiationChain += UtExecutableCallModel(null, constructorId, params.map { it.model }, this) - }.fuzzed { - summary = "%var% = ${constructorId.classId.simpleName}(${constructorId.parameters.joinToString { it.simpleName }})" - } - } - - private val primitiveParameterizedConstructorsFirstAndThenByParameterCount = - compareByDescending { constructorId -> - constructorId.parameters.all { classId -> - classId.isPrimitive || classId == stringClassId - } - }.thenComparingInt { constructorId -> - constructorId.parameters.size - } - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveDefaultsModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveDefaultsModelProvider.kt deleted file mode 100644 index b28d0c69ee..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveDefaultsModelProvider.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.util.booleanClassId -import org.utbot.framework.plugin.api.util.byteClassId -import org.utbot.framework.plugin.api.util.charClassId -import org.utbot.framework.plugin.api.util.doubleClassId -import org.utbot.framework.plugin.api.util.floatClassId -import org.utbot.framework.plugin.api.util.intClassId -import org.utbot.framework.plugin.api.util.longClassId -import org.utbot.framework.plugin.api.util.shortClassId -import org.utbot.framework.plugin.api.util.stringClassId -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider -import java.util.function.BiConsumer - -/** - * Provides default values for primitive types. - */ -object PrimitiveDefaultsModelProvider : ModelProvider { - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - description.parametersMap.forEach { (classId, parameterIndices) -> - valueOf(classId)?.let { model -> - parameterIndices.forEach { index -> - consumer.accept(index, model) - } - } - } - } - - fun valueOf(classId: ClassId): FuzzedValue? = when (classId) { - booleanClassId -> UtPrimitiveModel(false).fuzzed { summary = "%var% = false" } - byteClassId -> UtPrimitiveModel(0.toByte()).fuzzed { summary = "%var% = 0" } - charClassId -> UtPrimitiveModel('\u0000').fuzzed { summary = "%var% = \u0000" } - shortClassId -> UtPrimitiveModel(0.toShort()).fuzzed { summary = "%var% = 0" } - intClassId -> UtPrimitiveModel(0).fuzzed { summary = "%var% = 0" } - longClassId -> UtPrimitiveModel(0L).fuzzed { summary = "%var% = 0L" } - floatClassId -> UtPrimitiveModel(0.0f).fuzzed { summary = "%var% = 0f" } - doubleClassId -> UtPrimitiveModel(0.0).fuzzed { summary = "%var% = 0.0" } - stringClassId -> UtPrimitiveModel("").fuzzed { summary = "%var% = \"\"" } - else -> null - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveWrapperModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveWrapperModelProvider.kt deleted file mode 100644 index 2a568f9e04..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitiveWrapperModelProvider.kt +++ /dev/null @@ -1,64 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.util.isPrimitiveWrapper -import org.utbot.framework.plugin.api.util.primitiveByWrapper -import org.utbot.framework.plugin.api.util.stringClassId -import org.utbot.framework.plugin.api.util.voidClassId -import org.utbot.framework.plugin.api.util.wrapperByPrimitive -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider -import org.utbot.fuzzer.ModelProvider.Companion.consumeAll -import java.util.function.BiConsumer - -object PrimitiveWrapperModelProvider: ModelProvider { - - private val constantModels = ModelProvider.of( - PrimitiveDefaultsModelProvider, - ConstantsModelProvider, - StringConstantModelProvider - ) - - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - val primitiveWrapperTypesAsPrimitiveTypes = description.parametersMap - .keys - .asSequence() - .filter { - it == stringClassId || it.isPrimitiveWrapper - } - .mapNotNull { classId -> - when { - classId == stringClassId -> stringClassId - classId.isPrimitiveWrapper -> primitiveByWrapper[classId] - else -> null - } - }.toList() - - if (primitiveWrapperTypesAsPrimitiveTypes.isEmpty()) { - return - } - - val constants = mutableMapOf>() - constantModels.generate(FuzzedMethodDescription( - name = this::class.simpleName + " constant generation ", - returnType = voidClassId, - parameters = primitiveWrapperTypesAsPrimitiveTypes, - concreteValues = description.concreteValues - )) { index, value -> - val primitiveWrapper = wrapperByPrimitive[primitiveWrapperTypesAsPrimitiveTypes[index]] - if (primitiveWrapper != null) { - constants.computeIfAbsent(primitiveWrapper) { mutableListOf() }.add(value) - } - } - - description.parametersMap - .asSequence() - .filter { (classId, _) -> classId == stringClassId || classId.isPrimitiveWrapper } - .forEach { (classId, indices) -> - constants[classId]?.let { models -> - consumer.consumeAll(indices, models) - } - } - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt deleted file mode 100644 index ee206cfbf8..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/PrimitivesModelProvider.kt +++ /dev/null @@ -1,89 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.util.* -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider -import java.util.function.BiConsumer - -/** - * Produces bound values for primitive types. - */ -object PrimitivesModelProvider : ModelProvider { - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - description.parametersMap.forEach { (classId, parameterIndices) -> - val primitives: List = when (classId) { - booleanClassId -> listOf( - UtPrimitiveModel(false).fuzzed { summary = "%var% = false" }, - UtPrimitiveModel(true).fuzzed { summary = "%var% = true" } - ) - charClassId -> listOf( - UtPrimitiveModel(Char.MIN_VALUE).fuzzed { summary = "%var% = Char.MIN_VALUE" }, - UtPrimitiveModel(Char.MAX_VALUE).fuzzed { summary = "%var% = Char.MAX_VALUE" }, - ) - byteClassId -> listOf( - UtPrimitiveModel(0.toByte()).fuzzed { summary = "%var% = 0" }, - UtPrimitiveModel(1.toByte()).fuzzed { summary = "%var% > 0" }, - UtPrimitiveModel((-1).toByte()).fuzzed { summary = "%var% < 0" }, - UtPrimitiveModel(Byte.MIN_VALUE).fuzzed { summary = "%var% = Byte.MIN_VALUE" }, - UtPrimitiveModel(Byte.MAX_VALUE).fuzzed { summary = "%var% = Byte.MAX_VALUE" }, - ) - shortClassId -> listOf( - UtPrimitiveModel(0.toShort()).fuzzed { summary = "%var% = 0" }, - UtPrimitiveModel(1.toShort()).fuzzed { summary = "%var% > 0" }, - UtPrimitiveModel((-1).toShort()).fuzzed { summary = "%var% < 0" }, - UtPrimitiveModel(Short.MIN_VALUE).fuzzed { summary = "%var% = Short.MIN_VALUE" }, - UtPrimitiveModel(Short.MAX_VALUE).fuzzed { summary = "%var% = Short.MAX_VALUE" }, - ) - intClassId -> listOf( - UtPrimitiveModel(0).fuzzed { summary = "%var% = 0" }, - UtPrimitiveModel(1).fuzzed { summary = "%var% > 0" }, - UtPrimitiveModel((-1)).fuzzed { summary = "%var% < 0" }, - UtPrimitiveModel(Int.MIN_VALUE).fuzzed { summary = "%var% = Int.MIN_VALUE" }, - UtPrimitiveModel(Int.MAX_VALUE).fuzzed { summary = "%var% = Int.MAX_VALUE" }, - ) - longClassId -> listOf( - UtPrimitiveModel(0L).fuzzed { summary = "%var% = 0L" }, - UtPrimitiveModel(1L).fuzzed { summary = "%var% > 0L" }, - UtPrimitiveModel(-1L).fuzzed { summary = "%var% < 0L" }, - UtPrimitiveModel(Long.MIN_VALUE).fuzzed { summary = "%var% = Long.MIN_VALUE" }, - UtPrimitiveModel(Long.MAX_VALUE).fuzzed { summary = "%var% = Long.MAX_VALUE" }, - ) - floatClassId -> listOf( - UtPrimitiveModel(0.0f).fuzzed { summary = "%var% = 0f" }, - UtPrimitiveModel(1.1f).fuzzed { summary = "%var% > 0f" }, - UtPrimitiveModel(-1.1f).fuzzed { summary = "%var% < 0f" }, - UtPrimitiveModel(Float.MIN_VALUE).fuzzed { summary = "%var% = Float.MIN_VALUE" }, - UtPrimitiveModel(Float.MAX_VALUE).fuzzed { summary = "%var% = Float.MAX_VALUE" }, - UtPrimitiveModel(Float.NEGATIVE_INFINITY).fuzzed { summary = "%var% = Float.NEGATIVE_INFINITY" }, - UtPrimitiveModel(Float.POSITIVE_INFINITY).fuzzed { summary = "%var% = Float.POSITIVE_INFINITY" }, - UtPrimitiveModel(Float.NaN).fuzzed { summary = "%var% = Float.NaN" }, - ) - doubleClassId -> listOf( - UtPrimitiveModel(0.0).fuzzed { summary = "%var% = 0.0" }, - UtPrimitiveModel(1.1).fuzzed { summary = "%var% > 0.0" }, - UtPrimitiveModel(-1.1).fuzzed { summary = "%var% < 0.0" }, - UtPrimitiveModel(Double.MIN_VALUE).fuzzed { summary = "%var% = Double.MIN_VALUE" }, - UtPrimitiveModel(Double.MAX_VALUE).fuzzed { summary = "%var% = Double.MAX_VALUE" }, - UtPrimitiveModel(Double.NEGATIVE_INFINITY).fuzzed { summary = "%var% = Double.NEGATIVE_INFINITY" }, - UtPrimitiveModel(Double.POSITIVE_INFINITY).fuzzed { summary = "%var% = Double.POSITIVE_INFINITY" }, - UtPrimitiveModel(Double.NaN).fuzzed { summary = "%var% = Double.NaN" }, - ) - stringClassId -> listOf( - UtPrimitiveModel("").fuzzed { summary = "%var% = empty string" }, - UtPrimitiveModel(" ").fuzzed { summary = "%var% = blank string" }, - UtPrimitiveModel("string").fuzzed { summary = "%var% != empty string" }, - UtPrimitiveModel("\n\t\r").fuzzed { summary = "%var% has special characters" }, - ) - else -> listOf() - } - - primitives.forEach { model -> - parameterIndices.forEach { index -> - consumer.accept(index, model) - } - } - } - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/StringConstantModelProvider.kt b/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/StringConstantModelProvider.kt deleted file mode 100644 index 9beff9b93b..0000000000 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/providers/StringConstantModelProvider.kt +++ /dev/null @@ -1,48 +0,0 @@ -package org.utbot.fuzzer.providers - -import org.utbot.framework.plugin.api.UtPrimitiveModel -import org.utbot.framework.plugin.api.util.stringClassId -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedOp -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.ModelProvider -import java.util.function.BiConsumer -import kotlin.random.Random - -object StringConstantModelProvider : ModelProvider { - - override fun generate(description: FuzzedMethodDescription, consumer: BiConsumer) { - val random = Random(72923L) - description.concreteValues - .asSequence() - .filter { (classId, _) -> classId == stringClassId } - .forEach { (_, value, op) -> - listOf(value, mutate(random, value as? String, op)) - .asSequence() - .filterNotNull() - .map { UtPrimitiveModel(it) }.forEach { model -> - description.parametersMap.getOrElse(model.classId) { emptyList() }.forEach { index -> - consumer.accept(index, model.fuzzed { summary = "%var% = string" }) - } - } - } - } - - private fun mutate(random: Random, value: String?, op: FuzzedOp): String? { - if (value == null || value.isEmpty() || op != FuzzedOp.CH) return null - val indexOfMutation = random.nextInt(value.length) - return value.replaceRange(indexOfMutation, indexOfMutation + 1, SingleCharacterSequence(value[indexOfMutation] - random.nextInt(1, 128))) - } - - private class SingleCharacterSequence(private val character: Char) : CharSequence { - override val length: Int - get() = 1 - - override fun get(index: Int): Char = character - - override fun subSequence(startIndex: Int, endIndex: Int): CharSequence { - throw UnsupportedOperationException() - } - - } -} \ No newline at end of file diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/CombinationsTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/CombinationsTest.kt deleted file mode 100644 index 87e8a502a0..0000000000 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/CombinationsTest.kt +++ /dev/null @@ -1,108 +0,0 @@ -package org.utbot.framework.plugin.api - -import org.utbot.fuzzer.CartesianProduct -import org.utbot.fuzzer.Combinations -import org.junit.jupiter.api.Assertions.assertArrayEquals -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertThrows -import org.junit.jupiter.api.Test -import kotlin.math.pow - -class CombinationsTest { - - @Test - fun testSpecification() { - val combinations = Combinations(2, 3) - assertArrayEquals(intArrayOf(0, 0), combinations[0]) - assertArrayEquals(intArrayOf(0, 1), combinations[1]) - assertArrayEquals(intArrayOf(0, 2), combinations[2]) - assertArrayEquals(intArrayOf(1, 0), combinations[3]) - assertArrayEquals(intArrayOf(1, 1), combinations[4]) - assertArrayEquals(intArrayOf(1, 2), combinations[5]) - } - - @Test - fun testCartesianProduct() { - val result: List> = CartesianProduct( - listOf( - listOf("x", "y", "z"), - listOf(1.0, 2.0, 3.0) - ) - ).toList() - assertEquals(9, result.size) - assertEquals(listOf("x", 1.0), result[0]) - assertEquals(listOf("x", 2.0), result[1]) - assertEquals(listOf("x", 3.0), result[2]) - assertEquals(listOf("y", 1.0), result[3]) - assertEquals(listOf("y", 2.0), result[4]) - assertEquals(listOf("y", 3.0), result[5]) - assertEquals(listOf("z", 1.0), result[6]) - assertEquals(listOf("z", 2.0), result[7]) - assertEquals(listOf("z", 3.0), result[8]) - } - - @Test - fun testCombinationsIsLazy() { - val combinations = Combinations(2, 2, 2) - val taken = combinations.take(3) - assertArrayEquals(intArrayOf(0, 0, 0), taken[0]) - assertArrayEquals(intArrayOf(0, 0, 1), taken[1]) - assertArrayEquals(intArrayOf(0, 1, 0), taken[2]) - } - - @Test - fun testDecimalNumbers() { - val array = intArrayOf(10, 10, 10) - val combinations = Combinations(*array) - combinations.forEachIndexed { i, c -> - var actual = 0 - for (pos in array.indices) { - actual += c[pos] * (10.0.pow(array.size - 1.0 - pos).toInt()) - } - assertEquals(i, actual) - } - } - - @Test - fun testDecimalNumbersByString() { - Combinations(10, 10, 10).forEachIndexed { number, digits -> - assertEquals(String.format("%03d", number), digits.joinToString(separator = "") { it.toString() }) - } - } - - @Test - fun testSomeNumbers() { - val c = Combinations(3, 4, 2) - assertArrayEquals(intArrayOf(0, 3, 0), c[6]) - assertArrayEquals(intArrayOf(1, 0, 1), c[9]) - } - - @Test - fun testZeroValues() { - val c = Combinations() - assertEquals(0, c.size) - } - - @Test - fun testZeroValuesWithException() { - val c = Combinations() - assertThrows(IllegalArgumentException::class.java) { - @Suppress("UNUSED_VARIABLE") val result = c[0] - } - } - - @Test - fun testZeroAsMaxValues() { - assertThrows(java.lang.IllegalArgumentException::class.java) { - Combinations(0, 0, 0) - } - } - - @Test - fun testZeroInTheMiddle() { - assertThrows(IllegalArgumentException::class.java) { - Combinations(2, -1, 3) - } - } - -} \ No newline at end of file diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/FuzzedValueDescriptionTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/FuzzedValueDescriptionTest.kt deleted file mode 100644 index e08ae0f48f..0000000000 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/FuzzedValueDescriptionTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -package org.utbot.framework.plugin.api - -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Test -import org.utbot.framework.plugin.api.util.intClassId -import org.utbot.framework.plugin.api.util.voidClassId -import org.utbot.fuzzer.FuzzedConcreteValue -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedOp -import org.utbot.fuzzer.FuzzedValue -import org.utbot.fuzzer.providers.ConstantsModelProvider - -class FuzzedValueDescriptionTest { - - @Test - fun testConstantModelProviderTest() { - val values = mutableListOf() - val concreteValues = listOf( - FuzzedConcreteValue(intClassId, 10, FuzzedOp.EQ), - FuzzedConcreteValue(intClassId, 20, FuzzedOp.NE), - FuzzedConcreteValue(intClassId, 30, FuzzedOp.LT), - FuzzedConcreteValue(intClassId, 40, FuzzedOp.LE), - FuzzedConcreteValue(intClassId, 50, FuzzedOp.GT), - FuzzedConcreteValue(intClassId, 60, FuzzedOp.GE), - ) - val summaries = listOf( - "%var% = 10" to 10, - "%var% != 10" to 11, // 1, FuzzedOp.EQ -> %var% == 10: False - "%var% = 20" to 20, - "%var% != 20" to 21, // 2, FuzzedOp.NE -> %var% != 20: True - "%var% = 30" to 30, - "%var% < 30" to 29, // 3, FuzzedOp.LT -> %var% < 30: True - "%var% = 40" to 40, - "%var% > 40" to 41, // 4, FuzzedOp.LE -> %var% <= 40: False - "%var% = 50" to 50, - "%var% > 50" to 51, // 5, FuzzedOp.GT -> %var% > 50: True - "%var% = 60" to 60, - "%var% < 60" to 59, // 6, FuzzedOp.GE -> %var% >= 60: False - ) - val expected = concreteValues.size * 2 - ConstantsModelProvider.generate( - FuzzedMethodDescription( - name = "name", - returnType = voidClassId, - parameters = listOf(intClassId), - concreteValues = concreteValues - ) - ) { _, value -> values.add(value) } - assertEquals(expected, values.size) { - "Expected $expected values: a half is origin values and another is modified, but only ${values.size} are generated" - } - for (i in summaries.indices) { - assertEquals(summaries[i].second, (values[i].model as UtPrimitiveModel).value) { - "Constant model provider should change constant values to reverse if-statement" - } - assertEquals(summaries[i].first, values[i].summary) - } - } - -} \ No newline at end of file diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/FuzzerTestCaseGeneratorTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/FuzzerTestCaseGeneratorTest.kt deleted file mode 100644 index 2a976b18e0..0000000000 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/FuzzerTestCaseGeneratorTest.kt +++ /dev/null @@ -1,51 +0,0 @@ -package org.utbot.framework.plugin.api - -import org.utbot.fuzzer.primitive.PrimitiveFuzzer -import kotlin.reflect.KClass -import kotlin.reflect.KFunction -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.Arguments.arguments -import org.junit.jupiter.params.provider.MethodSource - -// TODO: no support for String -internal class FuzzerTestCaseGeneratorTest { - @ParameterizedTest - @MethodSource("manyMethods") - fun testManyMethods(method: KFunction<*>, returnType: KClass<*>, vararg paramTypes: KClass<*>) { - val testCase = generate(method) - testCase.executions.forEach { execution -> - assertEquals(paramTypes.toList(), execution.stateBefore.params.map { it.type }) { "$method" } - assertTrue(execution.returnValue.isSuccess) { "$method" } - val value = execution.returnValue.getOrNull() - assertNotNull(value) { "$method" } - assertEquals(returnType, value!!::class) { "$method" } - } - } - - companion object { - /** - * Arguments for generated types checks. - * - * Each line contains: - * - method - * - parameter types - * - return type - */ - @Suppress("unused") - @JvmStatic - fun manyMethods() = listOf( - args(Object::equals, Any::class, returnType = Boolean::class), - args(Object::hashCode, returnType = Int::class), - args(Math::copySign, Double::class, Double::class, returnType = Double::class) - ) - - private fun args(method: KFunction<*>, vararg paramTypes: KClass<*>, returnType: KClass<*>) = - arguments(method, returnType, paramTypes) - } -} - -private fun generate(method: KFunction<*>) = - PrimitiveFuzzer.generate(UtMethod.from(method), MockStrategyApi.NO_MOCKS) \ No newline at end of file diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt b/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt deleted file mode 100644 index 106a4a3d62..0000000000 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/ModelProviderTest.kt +++ /dev/null @@ -1,451 +0,0 @@ -package org.utbot.framework.plugin.api - -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.booleanClassId -import org.utbot.framework.plugin.api.util.byteClassId -import org.utbot.framework.plugin.api.util.charClassId -import org.utbot.framework.plugin.api.util.doubleClassId -import org.utbot.framework.plugin.api.util.floatClassId -import org.utbot.framework.plugin.api.util.id -import org.utbot.framework.plugin.api.util.intClassId -import org.utbot.framework.plugin.api.util.longClassId -import org.utbot.framework.plugin.api.util.shortClassId -import org.utbot.framework.plugin.api.util.stringClassId -import org.utbot.framework.plugin.api.util.voidClassId -import org.utbot.framework.plugin.api.util.withUtContext -import org.utbot.fuzzer.FuzzedConcreteValue -import org.utbot.fuzzer.FuzzedMethodDescription -import org.utbot.fuzzer.FuzzedOp -import org.utbot.fuzzer.ModelProvider -import org.utbot.fuzzer.providers.ConstantsModelProvider -import org.utbot.fuzzer.providers.ObjectModelProvider -import org.utbot.fuzzer.providers.PrimitivesModelProvider -import org.utbot.fuzzer.providers.StringConstantModelProvider -import org.junit.jupiter.api.Assertions.* -import org.junit.jupiter.api.Test -import org.utbot.framework.plugin.api.util.primitiveByWrapper -import org.utbot.framework.plugin.api.util.primitiveWrappers -import org.utbot.framework.plugin.api.util.voidWrapperClassId -import org.utbot.fuzzer.defaultModelProviders -import org.utbot.fuzzer.providers.EnumModelProvider -import org.utbot.fuzzer.providers.EnumModelProvider.fuzzed -import org.utbot.fuzzer.providers.PrimitiveDefaultsModelProvider -import java.util.Date - -class ModelProviderTest { - - @Test - fun `test generate primitive models for boolean`() { - val models = collect(PrimitivesModelProvider, - parameters = listOf(booleanClassId) - ) - - assertEquals(1, models.size) - assertEquals(2, models[0]!!.size) - assertTrue(models[0]!!.contains(UtPrimitiveModel(true))) - assertTrue(models[0]!!.contains(UtPrimitiveModel(false))) - } - - @Test - fun `test all known primitive types are generate at least one value`() { - val primitiveTypes = listOf( - byteClassId, - booleanClassId, - charClassId, - shortClassId, - intClassId, - longClassId, - floatClassId, - doubleClassId, - stringClassId, - ) - val models = collect(PrimitivesModelProvider, - parameters = primitiveTypes - ) - - assertEquals(primitiveTypes.size, models.size) - primitiveTypes.indices.forEach { - assertTrue(models[it]!!.isNotEmpty()) - } - } - - @Test - fun `test that empty constants don't generate any models`() { - val models = collect(ConstantsModelProvider, - parameters = listOf(intClassId), - constants = emptyList() - ) - - assertEquals(0, models.size) - } - - @Test - fun `test that one constant generate corresponding value`() { - val models = collect(ConstantsModelProvider, - parameters = listOf(intClassId), - constants = listOf( - FuzzedConcreteValue(intClassId, 123) - ) - ) - - assertEquals(1, models.size) - assertEquals(1, models[0]!!.size) - assertEquals(UtPrimitiveModel(123), models[0]!![0]) - assertEquals(intClassId, models[0]!![0].classId) - } - - @Test - fun `test that constants are mutated if comparison operation is set`() { - val models = collect(ConstantsModelProvider, - parameters = listOf(intClassId), - constants = listOf( - FuzzedConcreteValue(intClassId, 10, FuzzedOp.EQ), - FuzzedConcreteValue(intClassId, 20, FuzzedOp.NE), - FuzzedConcreteValue(intClassId, 30, FuzzedOp.LT), - FuzzedConcreteValue(intClassId, 40, FuzzedOp.LE), - FuzzedConcreteValue(intClassId, 50, FuzzedOp.GT), - FuzzedConcreteValue(intClassId, 60, FuzzedOp.GE), - ) - ) - - assertEquals(1, models.size) - val expectedValues = listOf(10, 11, 20, 21, 29, 30, 40, 41, 50, 51, 59, 60) - assertEquals(expectedValues.size, models[0]!!.size) - expectedValues.forEach { - assertTrue(models[0]!!.contains(UtPrimitiveModel(it))) - } - } - - @Test - fun `test constant empty string generates only corresponding model`() { - val models = collect(StringConstantModelProvider, - parameters = listOf(stringClassId), - constants = listOf( - FuzzedConcreteValue(stringClassId, "", FuzzedOp.CH), - ) - ) - - assertEquals(1, models.size) - assertEquals(1, models[0]!!.size) - assertEquals(UtPrimitiveModel(""), models[0]!![0]) - } - - @Test - fun `test non-empty string is not mutated if operation is not set`() { - val models = collect(StringConstantModelProvider, - parameters = listOf(stringClassId), - constants = listOf( - FuzzedConcreteValue(stringClassId, "nonemptystring", FuzzedOp.NONE), - ) - ) - - assertEquals(1, models.size) - assertEquals(1, models[0]!!.size) - assertEquals(UtPrimitiveModel("nonemptystring"), models[0]!![0]) - } - - @Test - fun `test non-empty string is mutated if modification operation is set`() { - val models = collect(StringConstantModelProvider, - parameters = listOf(stringClassId), - constants = listOf( - FuzzedConcreteValue(stringClassId, "nonemptystring", FuzzedOp.CH), - ) - ) - - assertEquals(1, models.size) - assertEquals(2, models[0]!!.size) - listOf("nonemptystring", "nonemptystr`ng").forEach { - assertTrue( models[0]!!.contains(UtPrimitiveModel(it))) { "Failed to find string $it in list ${models[0]}" } - } - } - - @Test - fun `test mutation creates the same values between different runs`() { - repeat(10) { - val models = collect(StringConstantModelProvider, - parameters = listOf(stringClassId), - constants = listOf( - FuzzedConcreteValue(stringClassId, "anotherstring", FuzzedOp.CH), - ) - ) - listOf("anotherstring", "anotherskring").forEach { - assertTrue( models[0]!!.contains(UtPrimitiveModel(it))) { "Failed to find string $it in list ${models[0]}" } - } - } - } - - @Test - @Suppress("unused", "UNUSED_PARAMETER", "RemoveEmptySecondaryConstructorBody") - fun `test default object model creation for simple constructors`() { - withUtContext(UtContext(this::class.java.classLoader)) { - class A { - constructor(a: Int) {} - constructor(a: Int, b: String) {} - constructor(a: Int, b: String, c: Boolean) - } - - val classId = A::class.java.id - val models = collect( - ObjectModelProvider { 0 }.apply { - modelProvider = ModelProvider.of(PrimitiveDefaultsModelProvider) - }, - parameters = listOf(classId) - ) - - assertEquals(1, models.size) - assertEquals(3, models[0]!!.size) - assertTrue(models[0]!!.all { it is UtAssembleModel && it.classId == classId }) - - models[0]!!.filterIsInstance().forEachIndexed { index, model -> - assertEquals(1, model.instantiationChain.size) - val stm = model.instantiationChain[0] - assertTrue(stm is UtExecutableCallModel) - stm as UtExecutableCallModel - val paramCountInConstructorAsTheyListed = index + 1 - assertEquals(paramCountInConstructorAsTheyListed, stm.params.size) - } - } - } - - @Test - fun `test no object model is created for empty constructor`() { - withUtContext(UtContext(this::class.java.classLoader)) { - class A - - val classId = A::class.java.id - val models = collect( - ObjectModelProvider { 0 }, - parameters = listOf(classId) - ) - - assertEquals(1, models.size) - assertEquals(1, models[0]!!.size) - } - } - - @Test - @Suppress("unused", "UNUSED_PARAMETER") - fun `test that constructors with not primitive parameters are ignored`() { - withUtContext(UtContext(this::class.java.classLoader)) { - class A { - constructor(a: Int, b: Int) - constructor(a: Int, b: Date) - } - - val classId = A::class.java.id - val models = collect( - ObjectModelProvider { 0 }, - parameters = listOf(classId) - ) - - assertEquals(1, models.size) - assertTrue(models[0]!!.isNotEmpty()) - val chain = (models[0]!![0] as UtAssembleModel).instantiationChain - assertEquals(1, chain.size) - assertTrue(chain[0] is UtExecutableCallModel) - (chain[0] as UtExecutableCallModel).params.forEach { - assertEquals(intClassId, it.classId) - } - } - } - - @Test - fun `test fallback model can create custom values for any parameter`() { - val firstParameterIsUserGenerated = ModelProvider { _, consumer -> - consumer.accept(0, UtPrimitiveModel(-123).fuzzed()) - }.withFallback(PrimitivesModelProvider) - - val result = collect( - firstParameterIsUserGenerated, - parameters = listOf(intClassId, intClassId) - ) - - assertEquals(2, result.size) - assertEquals(1, result[0]!!.size) - assertTrue(result[1]!!.size > 1) - assertEquals(UtPrimitiveModel(-123), result[0]!![0]) - } - - @Test - fun `test collection model can produce basic values with assembled model`() { - withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect( - defaultModelProviders { 0 }, - parameters = listOf(java.util.List::class.java.id) - ) - - assertEquals(1, result.size) - } - } - - @Test - fun `test enum model provider`() { - withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(EnumModelProvider, parameters = listOf(OneTwoThree::class.java.id)) - assertEquals(1, result.size) - assertEquals(3, result[0]!!.size) - OneTwoThree.values().forEachIndexed { index: Int, value -> - assertEquals(UtEnumConstantModel(OneTwoThree::class.java.id, value), result[0]!![index]) - } - } - } - - @Test - fun `test string value generates only primitive models`() { - withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(defaultModelProviders { 0 }, parameters = listOf(stringClassId)) - assertEquals(1, result.size) - result[0]!!.forEach { - assertInstanceOf(UtPrimitiveModel::class.java, it) - assertEquals(stringClassId, it.classId) - } - } - } - - @Test - fun `test wrapper primitives generate only primitive models`() { - withUtContext(UtContext(this::class.java.classLoader)) { - primitiveWrappers.asSequence().filterNot { it == voidWrapperClassId }.forEach { classId -> - val result = collect(defaultModelProviders { 0 }, parameters = listOf(classId)) - assertEquals(1, result.size) - result[0]!!.forEach { - assertInstanceOf(UtPrimitiveModel::class.java, it) - val expectPrimitiveBecauseItShouldBeGeneratedByDefaultProviders = primitiveByWrapper[classId] - assertEquals(expectPrimitiveBecauseItShouldBeGeneratedByDefaultProviders, it.classId) - } - } - } - } - - @Test - fun `test at least one string is created if characters exist as constants`() { - withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect( - defaultModelProviders { 0 }, - parameters = listOf(stringClassId), - constants = listOf( - FuzzedConcreteValue(charClassId, 'a'), - FuzzedConcreteValue(charClassId, 'b'), - FuzzedConcreteValue(charClassId, 'c'), - ) - ) - assertEquals(1, result.size) - assertTrue(result[0]!!.any { - it is UtPrimitiveModel && it.value == "abc" - }) - } - } - - @Test - @Suppress("unused", "UNUSED_PARAMETER", "ConvertSecondaryConstructorToPrimary") - fun `test complex object is constructed and it is not null`() { - class A { - constructor(some: Any) - } - - withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }, parameters = listOf(A::class.java.id)) - assertEquals(1, result.size) - assertEquals(1, result[0]!!.size) - assertInstanceOf(UtAssembleModel::class.java, result[0]!![0]) - assertEquals(A::class.java.id, result[0]!![0].classId) - (result[0]!![0] as UtAssembleModel).instantiationChain.forEach { - assertTrue(it is UtExecutableCallModel) - assertEquals(1, (it as UtExecutableCallModel).params.size) - val objectParamInConstructor = it.params[0] - assertInstanceOf(UtAssembleModel::class.java, objectParamInConstructor) - val innerAssembledModel = objectParamInConstructor as UtAssembleModel - assertEquals(Any::class.java.id, innerAssembledModel.classId) - assertEquals(1, innerAssembledModel.instantiationChain.size) - val objectCreation = innerAssembledModel.instantiationChain.first() as UtExecutableCallModel - assertEquals(0, objectCreation.params.size) - assertInstanceOf(ConstructorId::class.java, objectCreation.executable) - } - } - } - - @Test - @Suppress("unused", "UNUSED_PARAMETER", "ConvertSecondaryConstructorToPrimary") - fun `test recursive constructor calls and can pass null into inner if no other values exist`() { - class MyA { - constructor(some: MyA?) - } - - withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }, parameters = listOf(MyA::class.java.id)) - assertEquals(1, result.size) - assertEquals(1, result[0]!!.size) - val outerModel = result[0]!![0] as UtAssembleModel - outerModel.instantiationChain.forEach { - val constructorParameters = (it as UtExecutableCallModel).params - assertEquals(1, constructorParameters.size) - val innerModel = (constructorParameters[0] as UtAssembleModel) - assertEquals(MyA::class.java.id, innerModel.classId) - assertEquals(1, innerModel.instantiationChain.size) - val innerConstructorParameters = innerModel.instantiationChain[0] as UtExecutableCallModel - assertEquals(1, innerConstructorParameters.params.size) - assertInstanceOf(UtNullModel::class.java, innerConstructorParameters.params[0]) - } - } - } - - @Test - @Suppress("unused", "UNUSED_PARAMETER", "ConvertSecondaryConstructorToPrimary") - fun `test complex object is constructed with the simplest inner object constructor`() { - - class Inner { - constructor(some: Inner?) - - constructor(some: Inner?, other: Any) - - // this constructor should be chosen - constructor(int: Int, double: Double) - - constructor(other: Any, int: Int) - - constructor(some: Inner?, other: Double) - } - - class Outer { - constructor(inner: Inner) - } - - withUtContext(UtContext(this::class.java.classLoader)) { - val result = collect(ObjectModelProvider { 0 }, parameters = listOf(Outer::class.java.id)) - assertEquals(1, result.size) - assertEquals(1, result[0]!!.size) - val outerModel = result[0]!![0] as UtAssembleModel - outerModel.instantiationChain.forEach { - val constructorParameters = (it as UtExecutableCallModel).params - assertEquals(1, constructorParameters.size) - val innerModel = (constructorParameters[0] as UtAssembleModel) - assertEquals(Inner::class.java.id, innerModel.classId) - assertEquals(1, innerModel.instantiationChain.size) - val innerConstructorParameters = innerModel.instantiationChain[0] as UtExecutableCallModel - assertEquals(2, innerConstructorParameters.params.size) - assertTrue(innerConstructorParameters.params.all { param -> param is UtPrimitiveModel }) - assertEquals(intClassId, innerConstructorParameters.params[0].classId) - assertEquals(doubleClassId, innerConstructorParameters.params[1].classId) - } - } - } - - private fun collect( - modelProvider: ModelProvider, - name: String = "testMethod", - returnType: ClassId = voidClassId, - parameters: List, - constants: List = emptyList() - ): Map> { - return mutableMapOf>().apply { - modelProvider.generate(FuzzedMethodDescription(name, returnType, parameters, constants)) { i, m -> - computeIfAbsent(i) { mutableListOf() }.add(m.model) - } - } - } - - private enum class OneTwoThree { - ONE, TWO, THREE - } -} \ No newline at end of file diff --git a/utbot-fuzzing/build.gradle.kts b/utbot-fuzzing/build.gradle.kts new file mode 100644 index 0000000000..a5ab65bf30 --- /dev/null +++ b/utbot-fuzzing/build.gradle.kts @@ -0,0 +1,7 @@ +val kotlinLoggingVersion: String by rootProject +val rgxgenVersion: String by rootProject + +dependencies { + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) + implementation(group = "org.cornutum.regexp", name = "regexp-gen", version = "2.0.1") +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/java/org/utbot/fuzzing/demo/A.java b/utbot-fuzzing/src/main/java/org/utbot/fuzzing/demo/A.java new file mode 100644 index 0000000000..d073a0f0a6 --- /dev/null +++ b/utbot-fuzzing/src/main/java/org/utbot/fuzzing/demo/A.java @@ -0,0 +1,33 @@ +package org.utbot.fuzzing.demo; + +/** + * Example class that is used in {@link JavaFuzzingKt} + */ +@SuppressWarnings("unused") +final class A { + + public String name; + public int age; + public A copy; + + public A() { + } + + public A(String name) { + this.name = name; + } + + public A(String name, int age) { + this.name = name; + this.age = age; + } + + @Override + public String toString() { + return "A{" + + "name='" + name + '\'' + + ", age=" + age + + ", copy=" + copy + + '}'; + } +} diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt new file mode 100644 index 0000000000..a33cba2ea0 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Api.kt @@ -0,0 +1,743 @@ +@file:JvmName("FuzzingApi") +package org.utbot.fuzzing + +import kotlinx.coroutines.* +import mu.KotlinLogging +import org.utbot.fuzzing.seeds.KnownValue +import org.utbot.fuzzing.utils.MissedSeed +import org.utbot.fuzzing.utils.chooseOne +import org.utbot.fuzzing.utils.flipCoin +import org.utbot.fuzzing.utils.transformIfNotEmpty +import kotlin.random.Random + +private val logger by lazy { KotlinLogging.logger {} } + +/** + * Describes some data to start fuzzing: initial seeds and how to run target program using generated values. + * + * @see [org.utbot.fuzzing.demo.AbcFuzzingKt] + * @see [org.utbot.fuzzing.demo.JavaFuzzing] + * @see [org.utbot.fuzzing.demo.JsonFuzzingKt] + */ +interface Fuzzing, FEEDBACK : Feedback> { + + /** + * Before producing seeds, this method is called to recognize, + * whether seeds should be generated especially. + * + * [Description.clone] method must be overridden, or it throws an exception if the scope is changed. + */ + fun enrich(description: DESCRIPTION, type: TYPE, scope: Scope) {} + + /** + * Generates seeds for a concrete type. + * + * If any information except type is required, like parameter index or another, + * [description] parameter can be used. + * + * NB: Fuzzing implementation caches seeds for concrete types to improve performance because + * usually all seeds are statically defined. In case some dynamic behavior is required use + * [Feedback.control] to reset caches. + */ + fun generate(description: DESCRIPTION, type: TYPE): Sequence> + + /** + * This method is called on every value list generated by fuzzer. + * + * Fuzzing combines, randomize and mutates values using the seeds. + * Then it generates values and runs them with this method. This method should provide some feedback, + * which is the most important part for a good fuzzing result. [emptyFeedback] can be provided only for test + * or infinite loops. Consider implementing own implementation of [Feedback] to provide more correct data or + * use [BaseFeedback] to generate key based feedback. In this case, the key is used to analyze what value should be next. + * + * @param description contains user-defined information about the current run. Can be used as a state of the run. + * @param values current values to process. + */ + suspend fun handle(description: DESCRIPTION, values: List): FEEDBACK + + /** + * Starts fuzzing with new description but with copy of [Statistic]. + */ + suspend fun fork(description: DESCRIPTION, statistics: Statistic) { + fuzz(description, StatisticImpl(statistics)) + } + + /** + * Checks whether the fuzzer should stop. + */ + suspend fun isCancelled(description: DESCRIPTION, stats: Statistic): Boolean { + return false + } + + suspend fun beforeIteration(description: DESCRIPTION, statistics: Statistic) { } + suspend fun afterIteration(description: DESCRIPTION, statistics: Statistic) { } +} + +/** + * Some description of the current fuzzing run. Usually, it contains the name of the target method and its parameter list. + */ +open class Description( + parameters: List +) { + val parameters: List = parameters.toList() + + open fun clone(scope: Scope): Description { + error("Scope was changed for $this, but method clone is not specified") + } +} + +class Scope( + val parameterIndex: Int, + val recursionDepth: Int, + private val properties: MutableMap, Any?> = hashMapOf(), +) { + fun putProperty(param: ScopeProperty, value: T) { + properties[param] = value + } + + fun getProperty(param: ScopeProperty): T? { + @Suppress("UNCHECKED_CAST") + return properties[param] as? T + } + + fun isNotEmpty(): Boolean = properties.isNotEmpty() +} + +class ScopeProperty( + val description: String +) { + fun getValue(scope: Scope): T? { + return scope.getProperty(this) + } +} + +/** + * Input value that fuzzing knows how to build and use them. + */ +sealed interface Seed { + /** + * Simple value is just a concrete value that should be used as is. + * + * Any mutation can be provided if it is applicable to this value. + */ + class Simple(val value: RESULT, val mutation: (RESULT, random: Random) -> RESULT = { f, _ -> f }): Seed + + /** + * Known value is a typical value that can be manipulated by fuzzing without knowledge about object structure + * in concrete language. For example, integer can be represented as a bit vector of n-bits. + * + * The list of the known to fuzzing values are: + * + * 1. BitVectorValue represents a vector of bits. + * 2. ... + */ + class Known>(val value: V, val build: (V) -> RESULT): Seed + + /** + * Recursive value defines an object with typically has a constructor and list of modifications. + * + * This task creates a tree of object values. + */ + class Recursive( + val construct: Routine.Create, + val modify: Sequence> = emptySequence(), + val empty: Routine.Empty + ) : Seed + + /** + * Collection is a task, that has 2 main options: + * + * 1. Construction the collection + * 2. Modification of the collections that depends on some number of iterations. + */ + class Collection( + val construct: Routine.Collection, + val modify: Routine.ForEach + ) : Seed +} + +/** + * Routine is a task that is used to build a value. + * + * There are several types of a routine, which all are generally only functions. + * These functions accept some data and generate target value. + */ +sealed class Routine(val types: List) : Iterable by types { + + /** + * Creates an empty recursive object. + */ + class Create( + types: List, + val builder: (arguments: List) -> R, + ) : Routine(types) { + operator fun invoke(arguments: List): R = builder(arguments) + } + + /** + * Calls routine for a given object. + */ + class Call( + types: List, + val callable: (instance: R, arguments: List) -> Unit + ) : Routine(types) { + operator fun invoke(instance: R, arguments: List) { + callable(instance, arguments) + } + } + + /** + * Creates a collection of concrete sizes. + */ + class Collection( + val builder: (size: Int) -> R, + ) : Routine(emptyList()) { + operator fun invoke(size: Int): R = builder(size) + } + + /** + * Is called for a collection with index of iterations. + */ + class ForEach( + types: List, + val callable: (instance: R, index: Int, arguments: List) -> Unit + ) : Routine(types) { + operator fun invoke(instance: R, index: Int, arguments: List) = callable(instance, index, arguments) + } + + /** + * Empty routine that generates a concrete value. + */ + class Empty( + val builder: () -> R, + ) : Routine(emptyList()) { + operator fun invoke(): R = builder() + } +} + +/** + * Interface to force [Any.hashCode] and [Any.equals] implementation for [Feedback], + * because it is used in the map. + */ +interface AsKey { + override fun equals(other: Any?): Boolean + override fun hashCode(): Int +} + +/** + * Language feedback from a concrete execution of the target code. + */ +interface Feedback : AsKey { + /** + * Controls what fuzzing should do. + * + * @see [Control] + */ + val control: Control +} + +/** + * Base implementation of [Feedback]. + * + * NB! [VALUE] type must implement [equals] and [hashCode] due to the fact it uses as a key in map. + * If it doesn't implement those methods, [OutOfMemoryError] is possible. + */ +data class BaseFeedback( + val result: VALUE, + override val control: Control, +) : Feedback + +/** + * Controls fuzzing execution. + */ +enum class Control { + /** + * Analyze feedback and continue. + */ + CONTINUE, + + /** + * Do not process this feedback and just start the next value generation. + */ + PASS, + + /** + * Stop fuzzing. + */ + STOP, +} + +/** + * Returns empty feedback which is equals to any another empty feedback. + */ +@Suppress("UNCHECKED_CAST") +fun emptyFeedback(): Feedback = (EmptyFeedback as Feedback) + +private object EmptyFeedback : Feedback { + override val control: Control + get() = Control.CONTINUE + + override fun equals(other: Any?): Boolean { + return true + } + + override fun hashCode(): Int { + return 0 + } +} + +class NoSeedValueException internal constructor( + // this type cannot be generalized because Java forbids types for [Throwable]. + val type: Any? +) : Exception() { + override fun fillInStackTrace(): Throwable { + return this + } + + override val message: String + get() = "No seed candidates generated for type: $type" +} + +suspend fun , F : Feedback> Fuzzing.fuzz( + description: D, + random: Random = Random(0), + configuration: Configuration = Configuration() +) { + fuzz(description, StatisticImpl(random = random, configuration = configuration)) +} + +/** + * Starts fuzzing for this [Fuzzing] object. + * + * This is an entry point for every fuzzing. + */ +private suspend fun , F : Feedback> Fuzzing.fuzz( + description: D, + statistic: StatisticImpl, +) { + val random = statistic.random + val configuration = statistic.configuration + val fuzzing = this + val typeCache = hashMapOf>>() + val mutationFactory = MutationFactory() + fun fuzzOne(parameters: List): Node = fuzz( + parameters = parameters, + fuzzing = fuzzing, + description = description, + random = random, + configuration = configuration, + builder = PassRoutine("Main Routine"), + state = State(typeCache, statistic.missedTypes), + ) + + while (!fuzzing.isCancelled(description, statistic)) { + beforeIteration(description, statistic) + val values = if (statistic.isNotEmpty() && random.flipCoin(configuration.probSeedRetrievingInsteadGenerating)) { + statistic.getRandomSeed(random, configuration).let { + mutationFactory.mutate(it, random, configuration) + } + } else { + val actualParameters = description.parameters + // fuzz one value, seems to be bad, when have only a few and simple values + fuzzOne(actualParameters).let { + if (random.flipCoin(configuration.probMutationRate)) { + mutationFactory.mutate(it, random, configuration) + } else { + it + } + } + } + afterIteration(description, statistic) + + yield() + statistic.apply { + totalRuns++ + } + check(values.parameters.size == values.result.size) { "Cannot create value for ${values.parameters}" } + val valuesCache = mutableMapOf, R>() + val result = values.result.map { valuesCache.computeIfAbsent(it) { r -> create(r) } } + val feedback = fuzzing.handle(description, result) + when (feedback.control) { + Control.CONTINUE -> { + statistic.put(random, configuration, feedback, values) + } + Control.STOP -> { + break + } + Control.PASS -> {} + } + } +} + + +///region Implementation of the fuzzing and non-public functions. + +private fun , FEEDBACK : Feedback> fuzz( + parameters: List, + fuzzing: Fuzzing, + description: DESCRIPTION, + random: Random, + configuration: Configuration, + builder: Routine, + state: State, +): Node { + val typeCache = mutableMapOf>>() + val result = parameters.mapIndexed { index, type -> + val results = typeCache.computeIfAbsent(type) { mutableListOf() } + if (results.isNotEmpty() && random.flipCoin(configuration.probReuseGeneratedValueForSameType)) { + // we need to check cases when one value is passed for different arguments + results.random(random) + } else { + produce(type, fuzzing, description, random, configuration, state.copy { + parameterIndex = index + }).also { + results += it + } + } + } + // is not inlined to debug values generated for a concrete type + return Node(result, parameters, builder) +} + +private fun , FEEDBACK : Feedback> produce( + type: TYPE, + fuzzing: Fuzzing, + description: DESCRIPTION, + random: Random, + configuration: Configuration, + state: State, +): Result { + val scope = Scope(state.parameterIndex, state.recursionTreeDepth).apply { + fuzzing.enrich(description, type, this) + } + @Suppress("UNCHECKED_CAST") + val seeds = when { + scope.isNotEmpty() -> { + fuzzing.generate(description.clone(scope) as DESCRIPTION, type).toList() + } + else -> state.cache.computeIfAbsent(type) { + fuzzing.generate(description, it).toList() + } + } + if (seeds.isEmpty()) { + throw NoSeedValueException(type) + } + return seeds.random(random).let { + when (it) { + is Seed.Simple -> Result.Simple(it.value, it.mutation) + is Seed.Known -> it.asResult() + is Seed.Recursive -> reduce(it, fuzzing, description, random, configuration, state) + is Seed.Collection -> reduce(it, fuzzing, description, random, configuration, state) + } + } +} + +/** + * reduces [Seed.Collection] type. When `configuration.recursionTreeDepth` limit is reached it creates + * an empty collection and doesn't do any modification to it. + */ +private fun , FEEDBACK : Feedback> reduce( + task: Seed.Collection, + fuzzing: Fuzzing, + description: DESCRIPTION, + random: Random, + configuration: Configuration, + state: State, +): Result { + return if (state.recursionTreeDepth > configuration.recursionTreeDepth) { + Result.Empty { task.construct.builder(0) } + } else try { + val iterations = when { + state.iterations >= 0 && random.flipCoin(configuration.probCreateRectangleCollectionInsteadSawLike) -> state.iterations + random.flipCoin(configuration.probEmptyCollectionCreation) -> 0 + else -> random.nextInt(1, configuration.collectionIterations + 1) + } + Result.Collection( + construct = fuzz( + task.construct.types, + fuzzing, + description, + random, + configuration, + task.construct, + state.copy { + recursionTreeDepth++ + } + ), + modify = if (random.flipCoin(configuration.probCollectionDuplicationInsteadCreateNew)) { + val result = fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, state.copy { + recursionTreeDepth++ + this.iterations = iterations + parameterIndex = -1 + }) + List(iterations) { result } + } else { + (0 until iterations).map { + fuzz(task.modify.types, fuzzing, description, random, configuration, task.modify, state.copy { + recursionTreeDepth++ + this.iterations = iterations + }) + } + }, + iterations = iterations + ) + } catch (nsv: NoSeedValueException) { + @Suppress("UNCHECKED_CAST") + state.missedTypes[nsv.type as TYPE] = task + if (configuration.generateEmptyCollectionsForMissedTypes) { + Result.Empty { task.construct.builder(0) } + } else { + throw nsv + } + } +} + +/** + * reduces [Seed.Recursive] type. When `configuration.recursionTreeDepth` limit is reached it calls + * `Seed.Recursive#empty` routine to create an empty object. + */ +private fun , FEEDBACK : Feedback> reduce( + task: Seed.Recursive, + fuzzing: Fuzzing, + description: DESCRIPTION, + random: Random, + configuration: Configuration, + state: State, +): Result { + return if (state.recursionTreeDepth > configuration.recursionTreeDepth) { + Result.Empty { task.empty.builder() } + } else try { + Result.Recursive( + construct = fuzz( + task.construct.types, + fuzzing, + description, + random, + configuration, + task.construct, + state.copy { + recursionTreeDepth++ + iterations = -1 + parameterIndex = -1 + } + ), + modify = task.modify + .toMutableList() + .transformIfNotEmpty { + shuffle(random) + take(configuration.maxNumberOfRecursiveSeedModifications) + } + .mapTo(arrayListOf()) { routine -> + fuzz( + routine.types, + fuzzing, + description, + random, + configuration, + routine, + state.copy { + recursionTreeDepth++ + iterations = -1 + parameterIndex = -1 + } + ) + } + ) + } catch (nsv: NoSeedValueException) { + @Suppress("UNCHECKED_CAST") + state.missedTypes[nsv.type as TYPE] = task + if (configuration.generateEmptyRecursiveForMissedTypes) { + Result.Empty { task.empty.builder() } + } else { + throw nsv + } + } +} + + +/** + * Creates a real result. + * + * Fuzzing doesn't use real object because it mutates values by itself. + */ +@Suppress("UNCHECKED_CAST") +private fun create(result: Result): R = when(result) { + is Result.Simple -> result.result + is Result.Known -> (result.build as KnownValue<*>.() -> R)(result.value) + is Result.Recursive -> with(result) { + val obj: R = when (val c = construct.builder) { + is Routine.Create -> c(construct.result.map { create(it) }) + is Routine.Empty -> c() + else -> error("Undefined create method") + } + modify.forEach { func -> + when (val builder = func.builder) { + is Routine.Call -> builder(obj, func.result.map { create(it) }) + is PassRoutine -> logger.warn { "Routine pass: ${builder.description}" } + else -> error("Undefined object call method ${func.builder}") + } + } + obj + } + is Result.Collection -> with(result) { + val collection: R = when (val c = construct.builder) { + is Routine.Create -> c(construct.result.map { create(it) }) + is Routine.Empty -> c() + is Routine.Collection -> c(modify.size) + else -> error("Undefined create method") + } + modify.forEachIndexed { index, func -> + when (val builder = func.builder) { + is Routine.ForEach -> builder(collection, index, func.result.map { create(it) }) + is PassRoutine -> logger.warn { "Routine pass: ${builder.description}" } + else -> error("Undefined collection call method ${func.builder}") + } + } + collection + } + is Result.Empty -> result.build() +} + +/** + * Empty routine to start a recursion within [fuzz]. + */ +private data class PassRoutine(val description: String) : Routine(emptyList()) + +/** + * Internal state for one fuzzing run. + */ +private class State( + val cache: MutableMap>>, + val missedTypes: MissedSeed, + val recursionTreeDepth: Int = 1, + val iterations: Int = -1, + val parameterIndex: Int = -1, +) { + + fun copy(block: Builder.() -> Unit): State { + return Builder(this).apply(block).build() + } + + class Builder( + state: State + ) { + var recursionTreeDepth: Int = state.recursionTreeDepth + var cache: MutableMap>> = state.cache + var missedTypes: MissedSeed = state.missedTypes + var iterations: Int = state.iterations + var parameterIndex: Int = state.parameterIndex + + fun build(): State { + return State( + cache, + missedTypes, + recursionTreeDepth, + iterations, + parameterIndex, + ) + } + } +} + +/** + * The result of producing real values for the language. + */ +sealed interface Result { + + /** + * Simple result as is. + */ + class Simple(val result: RESULT, val mutation: (RESULT, random: Random) -> RESULT = emptyMutation()) : Result + + /** + * Known value. + */ + class Known>(val value: V, val build: (V) -> RESULT) : Result + /** + * A tree of object that has constructor and some modifications. + */ + class Recursive( + val construct: Node, + val modify: List>, + ) : Result + + /** + * A tree of collection-like structures and their modification. + */ + class Collection( + val construct: Node, + val modify: List>, + val iterations: Int, + ) : Result + + /** + * Empty result which just returns a value. + */ + class Empty( + val build: () -> RESULT + ) : Result +} + +/** + * Temporary object to storage information about partly calculated values tree. + */ +class Node( + val result: List>, + val parameters: List, + val builder: Routine, +) + +private class StatisticImpl>( + override var totalRuns: Long = 0, + override val startTime: Long = System.nanoTime(), + override var missedTypes: MissedSeed = MissedSeed(), + override val random: Random, + override val configuration: Configuration, +) : Statistic { + + constructor(source: Statistic) : this( + totalRuns = source.totalRuns, + startTime = source.startTime, + missedTypes = source.missedTypes, + random = source.random, + configuration = source.configuration.copy(), + ) + + override val elapsedTime: Long + get() = System.nanoTime() - startTime + private val seeds = linkedMapOf>() + private val count = linkedMapOf() + + fun put(random: Random, configuration: Configuration, feedback: FEEDBACK, seed: Node) { + if (random.flipCoin(configuration.probUpdateSeedInsteadOfKeepOld)) { + seeds[feedback] = seed + } else { + seeds.putIfAbsent(feedback, seed) + } + count[feedback] = count.getOrDefault(feedback, 0L) + 1L + } + + fun getRandomSeed(random: Random, configuration: Configuration): Node { + if (seeds.isEmpty()) error("Call `isNotEmpty` before getting the seed") + val entries = seeds.entries.toList() + val frequencies = DoubleArray(seeds.size).also { f -> + entries.forEachIndexed { index, (key, _) -> + f[index] = configuration.energyFunction(count.getOrDefault(key, 0L)) + } + } + val index = random.chooseOne(frequencies) + return entries[index].value + } + + fun isNotEmpty() = seeds.isNotEmpty() +} +///endregion + + +///region Utilities +@Suppress("UNCHECKED_CAST") +private fun > Seed.Known.asResult(): Result.Known { + val value: T = value as T + return Result.Known(value, build as KnownValue.() -> RESULT) +} +///endregion diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt new file mode 100644 index 0000000000..a6c19926c9 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Configuration.kt @@ -0,0 +1,98 @@ +package org.utbot.fuzzing + +import kotlin.math.pow + +/** + * Configures fuzzing behaviour. Usually, it is not required to tune anything. + */ +data class Configuration( + + /** + * Choose between already generated values and new generation of values. + */ + var probSeedRetrievingInsteadGenerating: Int = 70, + + /** + * Choose between generation and mutation. + */ + var probMutationRate: Int = 99, + + /** + * Fuzzer creates a tree of object for generating values. At some point this recursion should be stopped. + * + * To stop recursion [Seed.Recursive.empty] is called to create new values. + */ + var recursionTreeDepth: Int = 4, + + /** + * The limit of collection size to create. + */ + var collectionIterations: Int = 5, + + /** + * Energy function that is used to choose seeded value. + */ + var energyFunction: (x: Long) -> Double = { x -> 1 / x.coerceAtLeast(1L).toDouble().pow(2) }, + + /** + * Probability to prefer shuffling collection instead of mutation one value from modification + */ + var probCollectionShuffleInsteadResultMutation: Int = 75, + + /** + * Probability of creating shifted array values instead of generating new values for modification. + */ + var probCollectionDuplicationInsteadCreateNew: Int = 10, + + /** + * Probability of creating empty collections + */ + var probEmptyCollectionCreation: Int = 1, + + /** + * Probability to prefer change constructor instead of modification. + */ + var probConstructorMutationInsteadModificationMutation: Int = 30, + + /** + * Probability to a shuffle modification list of the recursive object + */ + var probShuffleAndCutRecursiveObjectModificationMutation: Int = 30, + + /** + * Probability to prefer create rectangle collections instead of creating saw-like one. + */ + var probCreateRectangleCollectionInsteadSawLike: Int = 80, + + /** + * Probability of updating old seed instead of leaving to the new one when [Feedback] has same key. + */ + var probUpdateSeedInsteadOfKeepOld: Int = 70, + + /** + * When mutating StringValue a new string will not exceed this value. + */ + var maxStringLengthWhenMutated: Int = 128, + + /** + * Probability of reusing same generated value when 2 or more parameters have the same type. + */ + var probReuseGeneratedValueForSameType: Int = 1, + + /** + * When true any [Seed.Collection] will not try + * to generate modification if a current type is already known to fail to generate values. + */ + var generateEmptyCollectionsForMissedTypes: Boolean = true, + + /** + * When true any [Seed.Recursive] will not try + * to generate a recursive object, but will use [Seed.Recursive.empty] instead. + */ + var generateEmptyRecursiveForMissedTypes: Boolean = true, + + /** + * Limits maximum number of recursive seed modifications + */ + var maxNumberOfRecursiveSeedModifications: Int = 10, +) \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt new file mode 100644 index 0000000000..9868be39b8 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Mutations.kt @@ -0,0 +1,334 @@ +@file:Suppress("ReplaceRangeStartEndInclusiveWithFirstLast") + +package org.utbot.fuzzing + +import org.utbot.fuzzing.seeds.* +import org.utbot.fuzzing.utils.chooseOne +import org.utbot.fuzzing.utils.flipCoin +import kotlin.random.Random + +class MutationFactory { + + fun mutate(node: Node, random: Random, configuration: Configuration): Node { + if (node.result.isEmpty()) return node + val indexOfMutatedResult = random.chooseOne(node.result.map(::rate).toDoubleArray()) + val recursive: NodeMutation = NodeMutation { n, r, c -> + mutate(n, r, c) + } + val mutated = when (val resultToMutate = node.result[indexOfMutatedResult]) { + is Result.Simple -> Result.Simple(resultToMutate.mutation(resultToMutate.result, random), resultToMutate.mutation) + is Result.Known -> { + val mutations = resultToMutate.value.mutations() + if (mutations.isNotEmpty()) { + resultToMutate.mutate(mutations.random(random), random, configuration) + } else { + resultToMutate + } + } + is Result.Recursive -> { + when { + resultToMutate.modify.isEmpty() || random.flipCoin(configuration.probConstructorMutationInsteadModificationMutation) -> + RecursiveMutations.Constructor() + random.flipCoin(configuration.probShuffleAndCutRecursiveObjectModificationMutation) -> + RecursiveMutations.ShuffleAndCutModifications() + else -> + RecursiveMutations.Mutate() + }.mutate(resultToMutate, recursive, random, configuration) + } + is Result.Collection -> if (resultToMutate.modify.isNotEmpty()) { + when { + random.flipCoin(100 - configuration.probCollectionShuffleInsteadResultMutation) -> + CollectionMutations.Mutate() + else -> + CollectionMutations.Shuffle() + }.mutate(resultToMutate, recursive, random, configuration) + } else { + resultToMutate + } + is Result.Empty -> resultToMutate + } + return Node(node.result.toMutableList().apply { + set(indexOfMutatedResult, mutated) + }, node.parameters, node.builder) + } + + /** + * Rates somehow the result. + * + * For example, fuzzing should not try to mutate some empty structures, like empty collections or objects. + */ + private fun rate(result: Result): Double { + if (!canMutate(result)) { + return ALMOST_ZERO + } + return when (result) { + is Result.Recursive -> if (result.construct.parameters.isEmpty() and result.modify.isEmpty()) ALMOST_ZERO else 0.5 + is Result.Collection -> if (result.iterations == 0) return ALMOST_ZERO else 0.7 + is StringValue -> 2.0 + is Result.Known -> 1.2 + is Result.Simple -> 2.0 + is Result.Empty -> ALMOST_ZERO + } + } + + private fun canMutate(node: Result): Boolean { + return when (node) { + is Result.Simple -> node.mutation === emptyMutation() + is Result.Known -> node.value.mutations().isNotEmpty() + is Result.Recursive -> node.modify.isNotEmpty() + is Result.Collection -> node.modify.isNotEmpty() && node.iterations > 0 + is Result.Empty -> false + } + } + + @Suppress("UNCHECKED_CAST") + private fun > Result.Known.mutate(mutation: Mutation, random: Random, configuration: Configuration): Result.Known { + val source: T = value as T + val mutate = mutation.mutate(source, random, configuration) + return Result.Known( + mutate, + build as (T) -> RESULT + ) + } +} + +private const val ALMOST_ZERO = 1E-7 +private val IDENTITY_MUTATION: (Any, random: Random) -> Any = { f, _ -> f } + +fun emptyMutation(): (RESULT, random: Random) -> RESULT { + @Suppress("UNCHECKED_CAST") + return IDENTITY_MUTATION as (RESULT, random: Random) -> RESULT +} + +/** + * Mutations is an object which applies some changes to the source object + * and then returns a new object (or old one without changes). + */ +fun interface Mutation { + fun mutate(source: T, random: Random, configuration: Configuration): T +} + +sealed class BitVectorMutations : Mutation { + + abstract fun rangeOfMutation(source: BitVectorValue): IntRange + + override fun mutate(source: BitVectorValue, random: Random, configuration: Configuration): BitVectorValue { + with (rangeOfMutation(source)) { + val firstBits = random.nextInt(start, endInclusive.coerceAtLeast(1)) + return BitVectorValue(source, this@BitVectorMutations).apply { this[firstBits] = !this[firstBits] } + } + } + + object SlightDifferent : BitVectorMutations() { + override fun rangeOfMutation(source: BitVectorValue) = 0 .. source.size / 4 + } + + object DifferentWithSameSign : BitVectorMutations() { + override fun rangeOfMutation(source: BitVectorValue) = source.size / 4 .. source.size + } + + object ChangeSign : BitVectorMutations() { + override fun rangeOfMutation(source: BitVectorValue) = source.size - 1 .. source.size + } +} + +sealed interface IEEE754Mutations : Mutation { + + object ChangeSign : IEEE754Mutations { + override fun mutate(source: IEEE754Value, random: Random, configuration: Configuration): IEEE754Value { + return IEEE754Value(source, this).apply { + setRaw(0, !getRaw(0)) + } + } + } + + object Mantissa : IEEE754Mutations { + override fun mutate(source: IEEE754Value, random: Random, configuration: Configuration): IEEE754Value { + val i = random.nextInt(0, source.mantissaSize) + return IEEE754Value(source, this).apply { + setRaw(1 + exponentSize + i, !getRaw(1 + exponentSize + i)) + } + } + } + + object Exponent : IEEE754Mutations { + override fun mutate(source: IEEE754Value, random: Random, configuration: Configuration): IEEE754Value { + val i = random.nextInt(0, source.exponentSize) + return IEEE754Value(source, this).apply { + setRaw(1 + i, !getRaw(1 + i)) + } + } + } +} + +sealed interface StringMutations : Mutation { + + object AddCharacter : StringMutations { + override fun mutate(source: StringValue, random: Random, configuration: Configuration): StringValue { + val value = source.value + if (value.length >= configuration.maxStringLengthWhenMutated) { + return source + } + val position = random.nextInt(value.length + 1) + val charToMutate = if (value.isNotEmpty()) { + value.random(random) + } else { + // use any meaningful character from the ascii table + random.nextInt(33, 127).toChar() + } + val newString = buildString { + append(value.substring(0, position)) + // try to change char to some that is close enough to origin char + val charTableSpread = 64 + if (random.nextBoolean()) { + append(charToMutate - random.nextInt(1, charTableSpread)) + } else { + append(charToMutate + random.nextInt(1, charTableSpread)) + } + append(value.substring(position, value.length)) + } + return StringValue(newString, lastMutation = this, mutatedFrom = source) + } + } + + object RemoveCharacter : StringMutations { + override fun mutate(source: StringValue, random: Random, configuration: Configuration): StringValue { + val value = source.value + val position = random.nextInt(value.length + 1) + if (position >= value.length) return source + val toRemove = random.nextInt(value.length) + val newString = buildString { + append(value.substring(0, toRemove)) + append(value.substring(toRemove + 1, value.length)) + } + return StringValue(newString, this) + } + } + + object ShuffleCharacters : StringMutations { + override fun mutate(source: StringValue, random: Random, configuration: Configuration): StringValue { + return StringValue( + value = String(source.value.toCharArray().apply { shuffle(random) }), + lastMutation = this + ) + } + } +} + +fun interface NodeMutation : Mutation> + +sealed interface CollectionMutations : Mutation, NodeMutation>> { + + override fun mutate( + source: Pair, NodeMutation>, + random: Random, + configuration: Configuration + ): Pair, NodeMutation> { + return mutate(source.first, source.second, random, configuration) to source.second + } + + fun mutate( + source: Result.Collection, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ) : Result.Collection + + class Shuffle : CollectionMutations { + override fun mutate( + source: Result.Collection, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ): Result.Collection { + return Result.Collection( + construct = source.construct, + modify = source.modify.toMutableList().shuffled(random), + iterations = source.iterations + ) + } + } + + class Mutate : CollectionMutations { + override fun mutate( + source: Result.Collection, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ): Result.Collection { + return Result.Collection( + construct = source.construct, + modify = source.modify.toMutableList().apply { + val i = random.nextInt(0, source.modify.size) + set(i, recursive.mutate(source.modify[i], random, configuration)) + }, + iterations = source.iterations + ) + } + } +} + +sealed interface RecursiveMutations : Mutation, NodeMutation>> { + + override fun mutate( + source: Pair, NodeMutation>, + random: Random, + configuration: Configuration + ): Pair, NodeMutation> { + return mutate(source.first, source.second, random, configuration) to source.second + } + + fun mutate( + source: Result.Recursive, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ) : Result.Recursive + + + class Constructor : RecursiveMutations { + override fun mutate( + source: Result.Recursive, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ): Result.Recursive { + return Result.Recursive( + construct = recursive.mutate(source.construct,random, configuration), + modify = source.modify + ) + } + } + + class ShuffleAndCutModifications : RecursiveMutations { + override fun mutate( + source: Result.Recursive, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ): Result.Recursive { + return Result.Recursive( + construct = source.construct, + modify = source.modify.shuffled(random).take(random.nextInt(source.modify.size + 1)) + ) + } + } + + class Mutate : RecursiveMutations { + override fun mutate( + source: Result.Recursive, + recursive: NodeMutation, + random: Random, + configuration: Configuration + ): Result.Recursive { + return Result.Recursive( + construct = source.construct, + modify = source.modify.toMutableList().apply { + val i = random.nextInt(0, source.modify.size) + set(i, recursive.mutate(source.modify[i], random, configuration)) + } + ) + } + + } +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Providers.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Providers.kt new file mode 100644 index 0000000000..0a47c42624 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Providers.kt @@ -0,0 +1,223 @@ +package org.utbot.fuzzing + +import mu.KotlinLogging +import kotlin.random.Random + +private val logger by lazy { KotlinLogging.logger {} } + +/** + * Entry point to run fuzzing. + */ +suspend fun , FEEDBACK : Feedback> runFuzzing( + provider: ValueProvider, + description: DESCRIPTION, + random: Random = Random(0), + configuration: Configuration = Configuration(), + handle: suspend (description: DESCRIPTION, values: List) -> FEEDBACK +) { + BaseFuzzing(listOf(provider), handle).fuzz(description, random, configuration) +} + +/** + * Implements base concepts that use providers to generate values for some types. + * + * @param providers is a list of "type to values" generator + * @param exec this function is called when fuzzer generates values of type R to run it with target program. + */ +class BaseFuzzing, F : Feedback>( + val providers: List>, + val exec: suspend (description: D, values: List) -> F +) : Fuzzing { + + constructor(vararg providers: ValueProvider, exec: suspend (description: D, values: List) -> F) : this(providers.toList(), exec) + + override fun enrich(description: D, type: T, scope: Scope) { + providers.asSequence().forEach { + it.enrich(description, type, scope) + } + } + + override fun generate(description: D, type: T): Sequence> { + return providers.asSequence().flatMap { provider -> + try { + if (provider.accept(type)) { + provider.generate(description, type) + } else { + emptySequence() + } + } catch (t: Throwable) { + logger.error(t) { "Error occurs in value provider: $provider" } + emptySequence() + } + } + } + + override suspend fun handle(description: D, values: List): F { + return exec(description, values) + } +} + +/** + * Value provider generates [Seed] and has other methods to combine providers. + */ +fun interface ValueProvider> { + + fun enrich(description: D, type: T, scope: Scope) {} + + /** + * Generate a sequence of [Seed] that is merged with values generated by other provider. + */ + fun generate(description: D, type: T): Sequence> + + /** + * Validates if this provider is applicable to some type. + */ + fun accept(type: T): Boolean = true + + /** + * Combines this model provider with `anotherValueProviders` into one instance. + * + * This model provider is called before `anotherValueProviders`. + */ + infix fun with(anotherValueProvider: ValueProvider): ValueProvider { + fun toList(m: ValueProvider) = if (m is Combined) m.providers else listOf(m) + return Combined(toList(this) + toList(anotherValueProvider)) + } + + /** + * Removes `anotherValueProviders): ValueProvider { + return except { it == anotherValueProvider } + } + + /** + * Removes providers matching [filter] from the current one. + */ + fun except(filter: (ValueProvider) -> Boolean): ValueProvider = + map { if (filter(it)) Combined(emptyList()) else it } + + /** + * Applies [transform] for current provider + */ + fun map(transform: (ValueProvider) -> ValueProvider): ValueProvider = + transform(this) + + /** + * Uses fallback value provider in case when 'this' one failed to generate any value. + */ + fun withFallback(fallback: ValueProvider) : ValueProvider { + return Fallback(this, fallback) + } + + /** + * Creates a new value provider that creates default value if no values are generated by this provider. + */ + fun withFallback(fallbackSupplier: (T) -> Seed) : ValueProvider { + return withFallback { _, type -> + sequenceOf(fallbackSupplier(type)) + } + } + + fun letIf(flag: Boolean, block: (ValueProvider) -> ValueProvider) : ValueProvider { + return if (flag) block(this) else this + } + + /** + * Checks if current provider has fallback and return the initial provider. + * + * If the initial provider is also fallback, then it is unwrapped too. + * + * @return unwrapped provider or this if it is not fallback + */ + fun unwrapIfFallback(): ValueProvider { + return if (this is Fallback) { provider.unwrapIfFallback() } else { this } + } + + private class Fallback>( + val provider: ValueProvider, + val fallback: ValueProvider, + ): ValueProvider { + + override fun enrich(description: D, type: T, scope: Scope) { + provider.enrich(description, type, scope) + // Enriching scope by fallback value provider in this point is not quite right, + // but it doesn't look as a problem right now. + fallback.enrich(description, type, scope) + } + + override fun generate(description: D, type: T): Sequence> { + val default = if (provider.accept(type)) provider.generate(description, type) else emptySequence() + return if (default.iterator().hasNext()) { + default + } else if (fallback.accept(type)) { + fallback.generate(description, type) + } else { + emptySequence() + } + } + + override fun map(transform: (ValueProvider) -> ValueProvider): ValueProvider = + transform(Fallback(provider.map(transform), fallback.map(transform))) + } + + /** + * Wrapper class that delegates implementation to the [providers]. + */ + private class Combined>(providers: List>): ValueProvider { + val providers: List> + + init { + // Flattening to avoid Combined inside Combined (for correct work of except, map, etc.) + this.providers = providers.flatMap { + if (it is Combined) + it.providers + else + listOf(it) + } + } + + override fun enrich(description: D, type: T, scope: Scope) { + providers.forEach { it.enrich(description, type, scope) } + } + + override fun accept(type: T): Boolean { + return providers.any { it.accept(type) } + } + + override fun generate(description: D, type: T): Sequence> = sequence { + providers.asSequence().filter { it.accept(type) }.forEach { provider -> + provider.generate(description, type).forEach { + yield(it) + } + } + } + + override fun map(transform: (ValueProvider) -> ValueProvider): ValueProvider = + transform(Combined(providers.map { it.map(transform) })) + } + + companion object { + fun > of(valueProviders: List>): ValueProvider { + return Combined(valueProviders) + } + } +} + +/** + * Simple value provider for a concrete type. + * + * @param type that is used as a filter to call this provider + * @param generate yields values for the type + */ +class TypeProvider>( + val type: T, + val generate: suspend SequenceScope>.(description: D, type: T) -> Unit +) : ValueProvider { + override fun accept(type: T) = this.type == type + override fun generate(description: D, type: T) = sequence { + if (accept(type)) { + this.generate(description, type) + } + } +} diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Statistic.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Statistic.kt new file mode 100644 index 0000000000..877937cedb --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/Statistic.kt @@ -0,0 +1,16 @@ +package org.utbot.fuzzing + +import org.utbot.fuzzing.utils.MissedSeed +import kotlin.random.Random + +/** + * User class that holds data about current fuzzing running. + */ +interface Statistic { + val startTime: Long + val totalRuns: Long + val elapsedTime: Long + val missedTypes: MissedSeed + val random: Random + val configuration: Configuration +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/AbcFuzzing.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/AbcFuzzing.kt new file mode 100644 index 0000000000..4969ccf7f4 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/AbcFuzzing.kt @@ -0,0 +1,63 @@ +package org.utbot.fuzzing.demo + +import org.utbot.fuzzing.* + +/** + * This example shows the minimal required implementation to start fuzzing any function. + * + * Assume, there's a function that returns some positive integer if one string is a substring. + * The value of this integer is maximum number of same characters, therefore, bigger is better. + * + * Lets fuzzing values for some given string to find out does the fuzzing can find the whole string or not. + */ +fun String.findMaxSubstring(s: String) : Int { + if (s.isEmpty()) return -1 + for (i in s.indices) { + if (s[i] != this[i]) return i - 1 + } + return s.length +} + +// the given string +private const val searchString = + "fun String.findMaxSubstring(s: String) : Int {\n" + + " if (s.isEmpty()) return -1\n" + + " for (i in s.indices) {\n" + + " if (s[i] != this[i]) return i - 1\n" + + " }\n" + + " return s.length\n" + + "}" + +suspend fun main() { + // Define fuzzing description to start searching. + object : Fuzzing, BaseFeedback> { + /** + * Generate method returns several samples or seeds which are used as a base for fuzzing. + * + * In this particular case only 1 value is provided which is an empty string. Also, a mutation + * is defined for any string value. This mutation adds a random character from ASCII table. + */ + override fun generate(description: Description, type: Unit) = sequenceOf>( + Seed.Simple("") { s, r -> s + Char(r.nextInt(1, 256)) } + ) + + /** + * After the fuzzing generates a new value it calls this method to execute target program and waits for feedback. + * + * This implementation just calls the target function and returns a result. After it returns an empty feedback. + * If some returned value equals to the length of the source string then feedback returns 'stop' signal. + */ + override suspend fun handle(description: Description, values: List): BaseFeedback { + check(values.size == 1) { + "Only one value must be generated because of `description.parameters.size = ${description.parameters.size}`" + } + val input = values.first() + val result = searchString.findMaxSubstring(input) + println("findMaxSubstring(\"$input\") = $result") + return BaseFeedback( + result = result, + control = if (result == searchString.length) Control.STOP else Control.CONTINUE + ) + } + }.fuzz(Description(listOf(Unit))) +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/ForkFuzzing.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/ForkFuzzing.kt new file mode 100644 index 0000000000..99be17e156 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/ForkFuzzing.kt @@ -0,0 +1,57 @@ +package org.utbot.fuzzing.demo + +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.utbot.fuzzing.* +import java.util.concurrent.atomic.AtomicLong + +private enum class Type { + ANY, CONCRETE, MORE_CONCRETE +} + +fun main(): Unit = runBlocking { + launch { + object : Fuzzing, Feedback> { + + private val runs = mutableMapOf() + + override fun generate(description: Description, type: Type): Sequence> { + return sequenceOf(Seed.Simple(type.name)) + } + + override suspend fun handle(description: Description, values: List): Feedback { + description.parameters.forEach { + runs[it]!!.incrementAndGet() + } + println(values) + return emptyFeedback() + } + + override suspend fun afterIteration( + description: Description, + stats: Statistic, + ) { + if (stats.totalRuns % 10 == 0L && description.parameters.size == 1) { + val newTypes = when (description.parameters[0]) { + Type.ANY -> listOf(Type.CONCRETE) + Type.CONCRETE -> listOf(Type.MORE_CONCRETE) + Type.MORE_CONCRETE -> listOf() + } + if (newTypes.isNotEmpty()) { + val d = Description(newTypes) + fork(d, stats) + // Description can be used as a transfer object, + // that collects information about the current running. + println("Fork ended: ${d.parameters}") + } + } + } + + override suspend fun isCancelled(description: Description, stats: Statistic): Boolean { + println("info: ${description.parameters} runs ${stats.totalRuns}") + return description.parameters.all { runs.computeIfAbsent(it) { AtomicLong(0) }.get() >= 10 } + } + }.fuzz(Description(listOf(Type.ANY))) + } +// .cancel() +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/HttpRequest.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/HttpRequest.kt new file mode 100644 index 0000000000..844336aad1 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/HttpRequest.kt @@ -0,0 +1,60 @@ +package org.utbot.fuzzing.demo + +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import org.utbot.fuzzing.* +import java.util.concurrent.TimeUnit + +fun main() = runBlocking { + withTimeout(TimeUnit.SECONDS.toMillis(10)) { + object : Fuzzing, Feedback> { + override fun generate(description: Description, type: String) = sequence> { + when (type) { + "url" -> yield(Seed.Recursive( + construct = Routine.Create( + listOf("protocol", "host", "port", "path") + ) { + val (protocol, host, port, path) = it + "$protocol://$host${if (port.isNotBlank()) ":$port" else ""}/$path" + }, + empty = Routine.Empty { error("error") } + )) + "protocol" -> { + yield(Seed.Simple("http")) + yield(Seed.Simple("https")) + yield(Seed.Simple("ftp")) + } + "host" -> { + yield(Seed.Simple("localhost")) + yield(Seed.Simple("127.0.0.1")) + } + "port" -> { + yield(Seed.Simple("8080")) + yield(Seed.Simple("")) + } + "path" -> yield(Seed.Recursive( + construct = Routine.Create(listOf("page", "id")) { + it.joinToString("/") + }, + empty = Routine.Empty { error("error") } + )) + "page" -> { + yield(Seed.Simple("owners")) + yield(Seed.Simple("users")) + yield(Seed.Simple("admins")) + } + "id" -> (0..1000).forEach { yield(Seed.Simple(it.toString())) } + else -> error("unknown type '$type'") + } + } + + override suspend fun handle( + description: Description, + values: List + ): Feedback { + println(values[0]) + return BaseFeedback(values[0], Control.PASS) + } + }.fuzz(Description(listOf("url"))) + } +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/JavaFuzzing.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/JavaFuzzing.kt new file mode 100644 index 0000000000..0d3d490dd7 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/JavaFuzzing.kt @@ -0,0 +1,94 @@ +package org.utbot.fuzzing.demo + +import org.utbot.fuzzing.* +import org.utbot.fuzzing.seeds.BitVectorValue +import org.utbot.fuzzing.seeds.Bool +import org.utbot.fuzzing.seeds.Signed +import org.utbot.fuzzing.seeds.StringValue + +/** + * This example implements some basics for Java fuzzing that supports only a few types: + * integers, strings, primitive arrays and user class [A]. + */ +object JavaFuzzing : Fuzzing, Any?, Description>, Feedback, Any?>> { + override fun generate(description: Description>, type: Class<*>) = sequence, Any?>> { + if (type == Boolean::class.javaPrimitiveType) { + yield(Seed.Known(Bool.TRUE.invoke()) { obj: BitVectorValue -> obj.toBoolean() }) + yield(Seed.Known(Bool.FALSE.invoke()) { obj: BitVectorValue -> obj.toBoolean() }) + } + if (type == String::class.java) { + yield(Seed.Known(StringValue(""), StringValue::value)) + yield(Seed.Known(StringValue("hello"), StringValue::value)) + } + for (signed in Signed.values()) { + if (type == Char::class.javaPrimitiveType) { + yield(Seed.Known(signed.invoke(8)) { obj: BitVectorValue -> obj.toCharacter() }) + } + if (type == Byte::class.javaPrimitiveType) { + yield(Seed.Known(signed.invoke(8)) { obj: BitVectorValue -> obj.toByte() }) + } + if (type == Short::class.javaPrimitiveType) { + yield(Seed.Known(signed.invoke(16)) { obj: BitVectorValue -> obj.toShort() }) + } + if (type == Int::class.javaPrimitiveType) { + yield(Seed.Known(signed.invoke(32)) { obj: BitVectorValue -> obj.toInt() }) + } + if (type == Long::class.javaPrimitiveType) { + yield(Seed.Known(signed.invoke(64)) { obj: BitVectorValue -> obj.toLong() }) + } + } + if (type == A::class.java) { + for (constructor in A::class.java.constructors) { + yield( + Seed.Recursive( + construct = Routine.Create(constructor.parameters.map { it.type }) { objects -> + constructor.newInstance(*objects.toTypedArray()) + }, + modify = type.fields.asSequence().map { field -> + Routine.Call(listOf(field.type)) { self: Any?, objects: List<*> -> + try { + field[self] = objects[0] + } catch (e: IllegalAccessException) { + throw RuntimeException(e) + } + } + }, + empty = Routine.Empty { null } + ) + ) + } + } + if (type.isArray) { + yield( + Seed.Collection( + construct = Routine.Collection { length: Int -> + java.lang.reflect.Array.newInstance(type.componentType, length) + }, + modify = Routine.ForEach(listOf(type.componentType)) { self: Any?, index: Int, objects: List<*> -> + java.lang.reflect.Array.set(self, index, objects[0]) + } + )) + } + } + + override suspend fun handle(description: Description>, values: List): Feedback, Any?> { + println(values.joinToString { + when (it) { + is BooleanArray -> it.contentToString() + is CharArray -> it.contentToString() + is ByteArray -> it.contentToString() + is ShortArray -> it.contentToString() + is IntArray -> it.contentToString() + is LongArray -> it.contentToString() + else -> it.toString() + } + }) + return emptyFeedback() + } +} + +suspend fun main() { + JavaFuzzing.fuzz( + Description(listOf(Int::class.javaPrimitiveType!!, CharArray::class.java, A::class.java)), + ) +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/JsonFuzzing.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/JsonFuzzing.kt new file mode 100644 index 0000000000..80863e4053 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/demo/JsonFuzzing.kt @@ -0,0 +1,95 @@ +package org.utbot.fuzzing.demo + +import org.utbot.fuzzing.* +import org.utbot.fuzzing.seeds.BitVectorValue +import org.utbot.fuzzing.seeds.Signed +import org.utbot.fuzzing.seeds.StringValue +import java.util.concurrent.atomic.AtomicLong +import kotlin.random.Random + +private enum class CustomType { + INT, STR, OBJ, LST +} + +private class JsonBuilder( + var before: String, + val children: MutableList = mutableListOf(), + var after: String = "", +) { + override fun toString(): String { + return buildString { + append(before) + append(children.joinToString(", ")) + append(after) + } + } +} + +/** + * This example shows how json based output can be built by fuzzer for some types. + * + * Also, some utility class such as [BaseFuzzing] and [TypeProvider] are used to start fuzzing without class inheritance. + */ +suspend fun main() { + var count = 0 + val set = mutableMapOf() + BaseFuzzing, Feedback>( + TypeProvider(CustomType.INT) { _, _ -> + for (b in Signed.values()) { + yield(Seed.Known(BitVectorValue(3, b)) { bvv -> + JsonBuilder((0 until bvv.size).joinToString(separator = "", prefix = "\"0b", postfix = "\"") { i -> + if (bvv[i]) "1" else "0" + }) + }) + } + }, + TypeProvider(CustomType.STR) { _, _ -> + listOf("Ted", "Mike", "John").forEach { n -> + yield(Seed.Known(StringValue(n)) { sv -> JsonBuilder("\"${sv.value}\"") }) + } + }, + TypeProvider(CustomType.OBJ) { _, _ -> + yield(Seed.Recursive( + construct = Routine.Create(listOf(CustomType.OBJ)) { + JsonBuilder(before = "{", after = "}") + }, + modify = sequence { + yield(Routine.Call(listOf(CustomType.INT)) { self, values -> + self.children += JsonBuilder("\"value\": ${values.joinToString(", ")}") + }) + yield(Routine.Call(listOf(CustomType.STR)) { self, values -> + self.children += JsonBuilder("\"name\": ${values.joinToString(", ")}") + }) + yield(Routine.Call(listOf(CustomType.OBJ)) { self, values -> + self.children += JsonBuilder("\"child\": ${values.joinToString(", ")}") + }) + }, + empty = Routine.Empty { + JsonBuilder("null") + } + )) + }, + TypeProvider(CustomType.LST) { _, _ -> + for (type in listOf(CustomType.INT, CustomType.STR)) { + yield(Seed.Collection( + construct = Routine.Collection { JsonBuilder(before = "[", after = "]") }, + modify = Routine.ForEach(listOf(type)) { self, _, values -> + self.children += JsonBuilder(values.joinToString(", ")) + } + )) + } + }, + ) { _, values -> + val result = values.toString() + println(result) + set.computeIfAbsent(result) { AtomicLong() }.incrementAndGet() + if (++count < 10000) emptyFeedback() else { + println("Unique ratio:" + set.size / count.toDouble()) + error("Forced from the example") + } + }.fuzz( + Description(listOf(CustomType.LST, CustomType.OBJ)), + Random(0), + Configuration(recursionTreeDepth = 2, collectionIterations = 2) + ) +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/BitVectorValue.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/BitVectorValue.kt new file mode 100644 index 0000000000..895c677a95 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/BitVectorValue.kt @@ -0,0 +1,273 @@ +package org.utbot.fuzzing.seeds + +import org.utbot.fuzzing.* +import org.utbot.fuzzing.utils.Endian +import java.math.BigInteger +import java.util.BitSet + +class BitVectorValue : KnownValue { + + /** + * Vector of value bits. + * + * Here, the base is 2 and the exponent for each member is equal to index of this member. + * Therefore, the values is stored using LE system. + */ + private val vector: BitSet + val size: Int + override val lastMutation: Mutation? + override val mutatedFrom: BitVectorValue? + + constructor(bits: Int, bound: Bound) { + vector = BitSet(bits).also { + for (i in 0 until bits) { + it[i] = bound.initializer(i, bits) + } + } + size = bits + lastMutation = null + mutatedFrom = null + } + + constructor(other: BitVectorValue, mutation: Mutation? = null) { + vector = other.vector.clone() as BitSet + size = other.size + lastMutation = mutation + mutatedFrom = other + } + + private constructor(size: Int, value: BitSet) : this(size, { i, _ -> value[i] }) + + operator fun get(index: Int): Boolean = vector[index] + + operator fun set(index: Int, value: Boolean) { + vector[index] = value + } + + /** + * Increase value by 1. + * + * @return true if integer overflow is occurred in sign values + */ + fun inc(): Boolean { + var shift = 0 + var carry = true + while (carry and (shift < size)) { + if (!vector[shift]) { + carry = false + } + vector[shift] = !vector[shift] + shift++ + } + return !carry && shift == size + } + + /** + * Decrease value by 1. + * + * @return true if integer underflow is occurred + */ + fun dec(): Boolean { + var shift = 0 + var carry = true + while (carry and (shift < size)) { + if (vector[shift]) { + carry = false + } + vector[shift] = !vector[shift] + shift++ + } + return !carry && shift == size + } + + override fun mutations() = listOf>( + BitVectorMutations.SlightDifferent, + BitVectorMutations.DifferentWithSameSign, + BitVectorMutations.ChangeSign + ) + + override fun equals(other: Any?): Boolean { + if (other !is BitVectorValue) return false + if (size != other.size) return false + for (i in 0 until size) { + if (vector[i] != other.vector[i]) return false + } + return true + } + + override fun hashCode(): Int { + return vector.hashCode() + } + + @Suppress("MemberVisibilityCanBePrivate") + fun toString(radix: Int, isUnsigned: Boolean = false): String { + return toBigInteger(isUnsigned).toString(radix) + } + + override fun toString() = toString(10) + + @Suppress("unused") + internal fun toBinaryString(endian: Endian) = buildString { + for (i in endian.range(0, size - 1)) { + append(if (this@BitVectorValue[i]) '1' else '0') + } + } + + private fun toLong(bits: Int, shift: Int = 0): Long { + assert(bits <= 64) { "Cannot convert to long vector with more than 64 bits, but $bits is requested" } + var result = 0L + for (i in shift until minOf(bits + shift, size)) { + result = result or ((if (vector[i]) 1L else 0L) shl (i - shift)) + } + return result + } + + private fun toBigInteger(isUnsigned: Boolean): BigInteger { + val size = if (isUnsigned) size + 1 else size + val array = ByteArray(size / 8 + if (size % 8 != 0) 1 else 0) { index -> + toLong(bits = 8, shift = index * 8).toByte() + } + array.reverse() + return BigInteger(array) + } + + fun toBigInteger() = toBigInteger(false) + + fun toBoolean() = vector[0] + + fun toByte() = toLong(8).toByte() + + fun toUByte() = toLong(8).toUByte() + + fun toShort() = toLong(16).toShort() + + fun toUShort() = toLong(16).toUShort() + + fun toInt() = toLong(32).toInt() + + fun toUInt() = toLong(32).toUInt() + + fun toLong() = toLong(64) + + fun toULong() = toLong(64).toULong() + + fun toCharacter() = Char(toUShort()) + + companion object { + fun fromValue(value: Any): BitVectorValue { + return when (value) { + is Char -> fromChar(value) + is Boolean -> fromBoolean(value) + is Byte -> fromByte(value) + is Short -> fromShort(value) + is Int -> fromInt(value) + is Long -> fromLong(value) + is BigInteger -> fromBigInteger(value) + else -> error("unknown type of value $value (${value::class})") + } + } + + fun fromBoolean(value: Boolean): BitVectorValue { + return BitVectorValue(1, if (value) Bool.TRUE else Bool.FALSE) + } + + fun fromByte(value: Byte): BitVectorValue { + return fromLong(8, value.toLong()) + } + + fun fromShort(value: Short): BitVectorValue { + return fromLong(16, value.toLong()) + } + + fun fromChar(value: Char): BitVectorValue { + return fromLong(16, value.code.toLong()) + } + + fun fromInt(value: Int): BitVectorValue { + return fromLong(32, value.toLong()) + } + + fun fromLong(value: Long): BitVectorValue { + return fromLong(64, value) + } + + private fun fromLong(size: Int, value: Long): BitVectorValue { + val vector = BitSet(size) + for (i in 0 until size) { + vector[i] = value and (1L shl i) != 0L + } + return BitVectorValue(size, vector) + } + + fun fromBigInteger(value: BigInteger): BitVectorValue { + val size = 128 + val bits = value.bitCount() + assert(bits <= size) { "This value $value is too big. Max value is 2^$bits." } + val vector = BitSet(size) + for (i in 0 until size) { + vector[i] = value.testBit(i) + } + return BitVectorValue(size, vector) + } + } +} + +fun interface Bound { + fun initializer(index: Int, size: Int): Boolean +} + +class DefaultBound private constructor(private val value: Long) : Bound { + + override fun initializer(index: Int, size: Int): Boolean { + return value and (1L shl index) != 0L + } + + @Suppress("unused") + companion object { + fun ofByte(value: Byte) = DefaultBound(value.toLong()) + + fun ofUByte(value: UByte) = DefaultBound(value.toLong()) + + fun ofShort(value: Short) = DefaultBound(value.toLong()) + + fun ofUShort(value: UShort) = DefaultBound(value.toLong()) + + fun ofInt(value: Int) = DefaultBound(value.toLong()) + + fun ofUInt(value: UInt) = DefaultBound(value.toLong()) + + fun ofLong(value: Long) = DefaultBound(value) + + fun ofULong(value: ULong) = DefaultBound(value.toLong()) + } +} + +enum class Signed : Bound { + ZERO { override fun initializer(index: Int, size: Int) = false }, + MIN { override fun initializer(index: Int, size: Int) = index == size - 1 }, + NEGATIVE { override fun initializer(index: Int, size: Int) = true }, + POSITIVE { override fun initializer(index: Int, size: Int) = index == 0 }, + MAX { override fun initializer(index: Int, size: Int) = index < size - 1 }, + ; + + operator fun invoke(size: Int) = BitVectorValue(size, this) + + fun test(value: BitVectorValue) = (0..value.size).all { value[it] == initializer(it, value.size) } +} + +enum class Unsigned : Bound { + ZERO { override fun initializer(index: Int, size: Int) = false }, + POSITIVE { override fun initializer(index: Int, size: Int) = index == 0 }, + MAX { override fun initializer(index: Int, size: Int) = true }, + ; + + operator fun invoke(size: Int) = BitVectorValue(size, this) +} + +enum class Bool : Bound { + FALSE { override fun initializer(index: Int, size: Int) = false }, + TRUE { override fun initializer(index: Int, size: Int) = true }, + ; + + operator fun invoke() = BitVectorValue(1, this) +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/IEEE754Value.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/IEEE754Value.kt new file mode 100644 index 0000000000..0e44449925 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/IEEE754Value.kt @@ -0,0 +1,278 @@ +package org.utbot.fuzzing.seeds + +import org.utbot.fuzzing.* +import kotlin.math.pow + +//val FLOAT_ZERO = DefaultFloatBound.ZERO(23, 8) +//val FLOAT_NAN = DefaultFloatBound.NAN(23, 8) +//val FLOAT_POSITIVE_INFINITY = DefaultFloatBound.POSITIVE_INFINITY(23, 8) +//val FLOAT_NEGATIVE_INFINITY = DefaultFloatBound.NEGATIVE_INFINITY(23, 8) +//val DOUBLE_ZERO = DefaultFloatBound.ZERO(52, 11) +//val DOUBLE_NAN = DefaultFloatBound.NAN(52, 11) +//val DOUBLE_POSITIVE_INFINITY = DefaultFloatBound.POSITIVE_INFINITY(52, 11) +//val DOUBLE_NEGATIVE_INFINITY = DefaultFloatBound.NEGATIVE_INFINITY(52, 11) + +class IEEE754Value : KnownValue { + + val isPositive: Boolean + get() = !vector[0] + + val bias: Int + get() = (2 shl (exponentSize - 2)) - 1 + + val exponent: Long + get() { + check(exponentSize <= 64) { "Exponent cannot be represented as long" } + var result = 0L + for (i in 0 until exponentSize) { + if (vector[exponentSize - i]) { + result += 1L shl i + } + } + return result - bias + } + + val mantissaSize: Int + val exponentSize: Int + override val lastMutation: Mutation? + override val mutatedFrom: IEEE754Value? + + private val vector: BitVectorValue + + constructor(mantissaSize: Int, exponentSize: Int, bound: FloatBound ) { + this.mantissaSize = mantissaSize + this.exponentSize = exponentSize + this.vector = BitVectorValue(1 + mantissaSize + exponentSize) { index, size -> + check(1 + exponentSize + mantissaSize == size) { "size exceeds" } + when { + index >= 1 + exponentSize + mantissaSize -> error("out of range") + index >= 1 + exponentSize -> bound.mantissa(index - 1 - exponentSize, mantissaSize) + index >= 1 -> bound.exponent(index - 1, exponentSize) + index == 0 -> bound.sign() + else -> error("out of range") + } + } + lastMutation = null + mutatedFrom = null + } + + constructor(value: IEEE754Value, mutation: Mutation? = null) { + this.vector = BitVectorValue(value.vector) + this.mantissaSize = value.mantissaSize + this.exponentSize = value.exponentSize + this.lastMutation = mutation + this.mutatedFrom = value + } + + fun getRaw(index: Int) = vector[index] + + fun setRaw(index: Int, value: Boolean) { + vector[index] = value + } + + fun toFloat(): Float { + DefaultFloatBound.values().forEach { + if (it.test(this)) when (it) { + DefaultFloatBound.ZERO -> return 0.0f + DefaultFloatBound.NAN -> return Float.NaN + DefaultFloatBound.POSITIVE_INFINITY -> return Float.POSITIVE_INFINITY + DefaultFloatBound.NEGATIVE_INFINITY -> return Float.NEGATIVE_INFINITY + else -> {} + } + } + var result = 0.0f + val e = exponent.toFloat() + result += 2.0f.pow(e) + for (i in 0 until mantissaSize) { + if (vector[1 + exponentSize + i]) { + result += 2.0f.pow(e - 1 - i) + } + } + return result * if (isPositive) 1.0f else -1.0f + } + + fun toDouble(): Double { + DefaultFloatBound.values().forEach { + if (it.test(this)) when (it) { + DefaultFloatBound.ZERO -> return 0.0 + DefaultFloatBound.NAN -> return Double.NaN + DefaultFloatBound.POSITIVE_INFINITY -> return Double.POSITIVE_INFINITY + DefaultFloatBound.NEGATIVE_INFINITY -> return Double.NEGATIVE_INFINITY + else -> {} + } + } + var result = 0.0 + val e = exponent.toDouble() + result += 2.0.pow(e) + for (i in 0 until mantissaSize) { + if (vector[1 + exponentSize + i]) { + result += 2.0.pow(e - 1 - i) + } + } + return result * if (isPositive) 1.0 else -1.0 + } + + fun is32Float(): Boolean { + return vector.size == 32 && mantissaSize == 23 && exponentSize == 8 + } + + fun is64Float(): Boolean { + return vector.size == 64 && mantissaSize == 52 && exponentSize == 11 + } + + override fun toString() = buildString { + for (i in 0 until vector.size) { + if (i == 1 || i == 1 + exponentSize) append(" ") + append(if (getRaw(i)) '1' else '0') + } + } + + override fun mutations() = listOf>( + IEEE754Mutations.ChangeSign, + IEEE754Mutations.Mantissa, + IEEE754Mutations.Exponent, + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as IEEE754Value + + if (mantissaSize != other.mantissaSize) return false + if (exponentSize != other.exponentSize) return false + if (vector != other.vector) return false + + return true + } + + override fun hashCode(): Int { + var result = mantissaSize + result = 31 * result + exponentSize + result = 31 * result + vector.hashCode() + return result + } + + companion object { + fun fromValue(value: Any): IEEE754Value { + return when (value) { + is Float -> fromFloat(value) + is Double -> fromDouble(value) + else -> error("unknown type of value $value (${value::class})") + } + } + + fun fromFloat(value: Float): IEEE754Value { + return IEEE754Value(23, 8, object : FloatBound { + + val rawInt = value.toRawBits() + + override fun sign(): Boolean { + return rawInt and (1 shl 31) != 0 + } + + override fun mantissa(index: Int, size: Int): Boolean { + return rawInt and (1 shl (size - 1 - index)) != 0 + } + + override fun exponent(index: Int, size: Int): Boolean { + return rawInt and (1 shl (30 - index)) != 0 + } + + }) + } + + fun fromDouble(value: Double): IEEE754Value { + return IEEE754Value(52, 11, object : FloatBound { + + val rawLong = value.toRawBits() + + override fun sign(): Boolean { + return rawLong and (1L shl 63) != 0L + } + + override fun mantissa(index: Int, size: Int): Boolean { + return rawLong and (1L shl (size - 1 - index)) != 0L + } + + override fun exponent(index: Int, size: Int): Boolean { + return rawLong and (1L shl (62 - index)) != 0L + } + + }) + } + } +} + +interface FloatBound { + fun sign(): Boolean + fun mantissa(index: Int, size: Int): Boolean + fun exponent(index: Int, size: Int): Boolean +} + +private class CopyBound(val vector: IEEE754Value) : FloatBound { + override fun sign(): Boolean = vector.getRaw(0) + override fun exponent(index: Int, size: Int): Boolean = vector.getRaw(1 + index) + override fun mantissa(index: Int, size: Int): Boolean = vector.getRaw(1 + vector.exponentSize + index) +} + +enum class DefaultFloatBound : FloatBound { + ZERO { + override fun sign() = false + override fun mantissa(index: Int, size: Int) = false + override fun exponent(index: Int, size: Int) = false + }, + NAN { + override fun sign() = false + override fun mantissa(index: Int, size: Int) = index == size - 1 + override fun exponent(index: Int, size: Int) = true + }, + POSITIVE { + override fun sign() = false + override fun mantissa(index: Int, size: Int) = false + override fun exponent(index: Int, size: Int) = index != 0 + }, + NEGATIVE { + override fun sign() = true + override fun mantissa(index: Int, size: Int) = false + override fun exponent(index: Int, size: Int) = index != 0 + }, + POSITIVE_INFINITY { + override fun sign() = false + override fun mantissa(index: Int, size: Int) = false + override fun exponent(index: Int, size: Int) = true + }, + NEGATIVE_INFINITY { + override fun sign() = true + override fun mantissa(index: Int, size: Int) = false + override fun exponent(index: Int, size: Int) = true + }, + ; + + operator fun invoke(mantissaSize: Int, exponentSize: Int): IEEE754Value { + return IEEE754Value(mantissaSize, exponentSize, this) + } + + fun test(value: IEEE754Value): Boolean { + for (i in 0 until 1 + value.exponentSize + value.mantissaSize) { + @Suppress("KotlinConstantConditions") + val res = when { + i >= 1 + value.exponentSize -> mantissa(i - 1 - value.exponentSize, value.mantissaSize) + i >= 1 -> exponent(i - 1, value.exponentSize) + i == 0 -> sign() + else -> error("bad index $i") + } + if (value.getRaw(i) != res) return false + } + return true + } +} + +//fun main() { +// println(IEEE754Value.fromDouble(8.75).toFloat()) +// println(IEEE754Value.fromFloat(28.7f).toDouble()) +// println(28.7f.toDouble()) +// DefaultFloatBound.values().forEach { +// println(IEEE754Value(3, 3, it).toFloat()) +// } +//} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/KnownValue.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/KnownValue.kt new file mode 100644 index 0000000000..19c5d45aab --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/KnownValue.kt @@ -0,0 +1,15 @@ +package org.utbot.fuzzing.seeds + +import org.utbot.fuzzing.Mutation + +interface KnownValue> { + val lastMutation: Mutation? + get() = null + + val mutatedFrom: T? + get() = null + + fun mutations(): List> { + return emptyList() + } +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/RegexValue.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/RegexValue.kt new file mode 100644 index 0000000000..eb95e34a9c --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/RegexValue.kt @@ -0,0 +1,35 @@ +package org.utbot.fuzzing.seeds + +import org.cornutum.regexpgen.js.Provider +import org.cornutum.regexpgen.random.RandomBoundsGen +import org.utbot.fuzzing.Mutation +import kotlin.random.Random +import kotlin.random.asJavaRandom + +class RegexValue( + val pattern: String, + val random: Random, + val maxLength: Int = listOf(16, 256, 2048).random(random) +) : StringValue( + valueProvider = { + val matchingExact = Provider.forEcmaScript().matchingExact(pattern) + matchingExact.generate(RandomBoundsGen(random.asJavaRandom()), 1, maxLength) + } +) { + + override fun mutations(): List> { + return super.mutations() + Mutation { source, random, _ -> + RegexValue(source.pattern, random) + } + } +} + +fun String.isSupportedPattern(): Boolean { + if (isEmpty()) return false + return try { + Provider.forEcmaScript().matchingExact(this) + true + } catch (_: Throwable) { + false + } +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/StringValue.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/StringValue.kt new file mode 100644 index 0000000000..cd054754b8 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/seeds/StringValue.kt @@ -0,0 +1,27 @@ +package org.utbot.fuzzing.seeds + +import org.utbot.fuzzing.Mutation +import org.utbot.fuzzing.StringMutations + +open class StringValue( + val valueProvider: () -> String, + override val lastMutation: Mutation? = null, + override val mutatedFrom: StringValue? = null, +) : KnownValue { + + constructor( + value: String, + lastMutation: Mutation? = null, + mutatedFrom: StringValue? = null + ) : this(valueProvider = { value }, lastMutation, mutatedFrom) + + val value by lazy { valueProvider() } + + override fun mutations(): List> { + return listOf( + StringMutations.AddCharacter, + StringMutations.RemoveCharacter, + StringMutations.ShuffleCharacters, + ) + } +} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/CartesianProduct.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/CartesianProduct.kt new file mode 100644 index 0000000000..399f514c89 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/CartesianProduct.kt @@ -0,0 +1,101 @@ +package org.utbot.fuzzing.utils + +import kotlin.jvm.Throws +import kotlin.random.Random + +/** + * Creates iterable for all values of cartesian product of `lists`. + */ +class CartesianProduct( + private val lists: List>, + private val random: Random? = null +): Iterable> { + + /** + * Estimated number of all combinations. + */ + val estimatedSize: Long + get() = Combinations(*lists.map { it.size }.toIntArray()).size + + @Throws(TooManyCombinationsException::class) + fun asSequence(): Sequence> { + val combinations = Combinations(*lists.map { it.size }.toIntArray()) + val sequence = if (random != null) { + sequence { + forEachChunk(Int.MAX_VALUE, combinations.size) { startIndex, combinationSize, _ -> + val permutation = PseudoShuffledIntProgression(combinationSize, random) + val temp = IntArray(size = lists.size) + for (it in 0 until combinationSize) { + yield(combinations[permutation[it] + startIndex, temp]) + } + } + } + } else { + combinations.asSequence() + } + return sequence.map { combination -> + combination.mapIndexedTo(ArrayList(combination.size)) { index, value -> lists[index][value] } + } + } + + override fun iterator(): Iterator> = asSequence().iterator() + + companion object { + /** + * Consumer for processing blocks of input larger block. + * + * If source example is sized to 12 and every block is sized to 5 then consumer should be called 3 times with these values: + * + * 1. start = 0, size = 5, remain = 7 + * 2. start = 5, size = 5, remain = 2 + * 3. start = 10, size = 2, remain = 0 + * + * The sum of start, size and remain should be equal to source block size. + */ + internal inline fun forEachChunk( + chunkSize: Int, + totalSize: Long, + block: (start: Long, size: Int, remain: Long) -> Unit + ) { + val iterationsCount = totalSize / chunkSize + if (totalSize % chunkSize == 0L) 0 else 1 + (0L until iterationsCount).forEach { iteration -> + val start = iteration * chunkSize + val size = minOf(chunkSize.toLong(), totalSize - start).toInt() + val remain = totalSize - size - start + block(start, size, remain) + } + } + } +} + +inline fun List>.cartesian(block: (List) -> Unit) { + cartesian().forEach(block) +} + +fun List>.cartesian(): Sequence> = sequence { + cartesian(this@cartesian, 0, IntArray(size)) +} + +private suspend fun SequenceScope>.cartesian(lists: List>, iteration: Int, array: IntArray) { + if (iteration == lists.size) { + yield(array.mapIndexed { l, v -> lists[l][v] }) + } else { + check(iteration < lists.size) + for (j in lists[iteration].indices) { + array[iteration] = j + cartesian(lists, iteration + 1, array) + } + } +} + +//private suspend fun SequenceScope>.cartesian(lists: List>, head: List) { +// if (head.size == lists.size) { +// yield(head) +// } else { +// check(head.size < lists.size) +// lists[head.size].forEach { +// val copy = head + it +// cartesian(lists, copy) +// } +// } +//} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Combinations.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/Combinations.kt similarity index 79% rename from utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Combinations.kt rename to utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/Combinations.kt index 35134d6785..33908563a8 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/Combinations.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/Combinations.kt @@ -1,4 +1,4 @@ -package org.utbot.fuzzer +package org.utbot.fuzzing.utils /** * Enumerates all possible combinations for a given list of maximum numbers of elements for every position. @@ -63,8 +63,8 @@ class Combinations(vararg elementNumbers: Int): Iterable { * * The total count of all possible combinations is therefore `count[0]`. */ - private val count: IntArray - val size: Int + private val count: LongArray + val size: Long get() = if (count.isEmpty()) 0 else count[0] init { @@ -72,9 +72,13 @@ class Combinations(vararg elementNumbers: Int): Iterable { if (badValue >= 0) { throw IllegalArgumentException("Max value must be at least 1 to build combinations, but ${elementNumbers[badValue]} is found at position $badValue (list: $elementNumbers)") } - count = IntArray(elementNumbers.size) { elementNumbers[it] } + count = LongArray(elementNumbers.size) { elementNumbers[it].toLong() } for (i in count.size - 2 downTo 0) { - count[i] = count[i] * count[i + 1] + try { + count[i] = StrictMath.multiplyExact(count[i], count[i + 1]) + } catch (e: ArithmeticException) { + throw TooManyCombinationsException("Long overflow: ${count[i]} * ${count[i + 1]}") + } } } @@ -94,7 +98,7 @@ class Combinations(vararg elementNumbers: Int): Iterable { * } * ``` */ - operator fun get(value: Int, target: IntArray = IntArray(count.size)): IntArray { + operator fun get(value: Long, target: IntArray = IntArray(count.size)): IntArray { if (value >= size) { throw java.lang.IllegalArgumentException("Only $size values allowed") } @@ -104,13 +108,20 @@ class Combinations(vararg elementNumbers: Int): Iterable { var rem = value for (i in target.indices) { target[i] = if (i < target.size - 1) { - val res = rem / count[i + 1] + val res = checkBoundsAndCast(rem / count[i + 1]) rem %= count[i + 1] res } else { - rem + checkBoundsAndCast(rem) } } return target } -} \ No newline at end of file + + private fun checkBoundsAndCast(value: Long): Int { + check(value >= 0 && value < Int.MAX_VALUE) { "Value is out of bounds: $value" } + return value.toInt() + } +} + +class TooManyCombinationsException(msg: String) : RuntimeException(msg) \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/Functions.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/Functions.kt new file mode 100644 index 0000000000..c2f9add11b --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/Functions.kt @@ -0,0 +1,96 @@ +@file:Suppress("unused") + +package org.utbot.fuzzing.utils + +import org.utbot.fuzzing.seeds.BitVectorValue + +internal fun T.toBinaryString( + size: Int, + endian: Endian = Endian.BE, + separator: String = " ", + format: (index: Int) -> Boolean = BinaryFormat.BYTE, + bit: (v: T, i: Int) -> Boolean +): String = buildString { + (endian.range(0, size - 1)).forEachIndexed { index, i -> + val b = if (bit(this@toBinaryString, i)) 1 else 0 + if (format(index)) append(separator) + append(b) + } + appendLine() +} + +internal fun UByte.toBinaryString(endian: Endian = Endian.BE, separator: String = " ", format: (index: Int) -> Boolean = BinaryFormat.BYTE): String { + return toBinaryString(8, endian, separator, format) { v, i -> v.toInt() and (1 shl i) != 0 } +} + +internal fun Byte.toBinaryString(endian: Endian = Endian.BE, separator: String = " ", format: (index: Int) -> Boolean = BinaryFormat.BYTE): String { + return toBinaryString(8, endian, separator, format) { v, i -> v.toInt() and (1 shl i) != 0 } +} + +internal fun UShort.toBinaryString(endian: Endian = Endian.BE, separator: String = " ", format: (index: Int) -> Boolean = BinaryFormat.BYTE): String { + return toBinaryString(16, endian, separator, format) { v, i -> v.toInt() and (1 shl i) != 0 } +} + +internal fun Short.toBinaryString(endian: Endian = Endian.BE, separator: String = " ", format: (index: Int) -> Boolean = BinaryFormat.BYTE): String { + return toBinaryString(16, endian, separator, format) { v, i -> v.toInt() and (1 shl i) != 0 } +} + +internal fun UInt.toBinaryString(endian: Endian = Endian.BE, separator: String = " ", format: (index: Int) -> Boolean = BinaryFormat.BYTE): String { + return toBinaryString(32, endian, separator, format) { v, i -> v and (1u shl i) != 0u } +} + +internal fun Int.toBinaryString(endian: Endian = Endian.BE, separator: String = " ", format: (index: Int) -> Boolean = BinaryFormat.BYTE): String { + return toBinaryString(32, endian, separator, format) { v, i -> v and (1 shl i) != 0 } +} + +internal fun ULong.toBinaryString(endian: Endian = Endian.BE, separator: String = " ", format: (index: Int) -> Boolean = BinaryFormat.BYTE): String { + return toBinaryString(64, endian, separator, format) { v, i -> v and (1uL shl i) != 0uL } +} + +internal fun Long.toBinaryString(endian: Endian = Endian.BE, separator: String = " ", format: (index: Int) -> Boolean = BinaryFormat.BYTE): String { + return toBinaryString(64, endian, separator, format) { v, i -> v and (1L shl i) != 0L } +} + +internal fun Float.toBinaryString(endian: Endian = Endian.BE, separator: String = " ", format: (index: Int) -> Boolean = { + when (endian) { + Endian.BE -> it == 1 || it == 9 + Endian.LE -> it == 31 || it == 23 + } +}): String { + return toRawBits().toBinaryString(endian, separator, format) +} + +internal fun Double.toBinaryString(endian: Endian = Endian.BE, separator: String = " ", format: (index: Int) -> Boolean = { + when (endian) { + Endian.BE -> it == 1 || it == 12 + Endian.LE -> it == 63 || it == 52 + } +}): String { + return toRawBits().toBinaryString(endian, separator, format) +} + +internal enum class Endian { + BE { override fun range(fromInclusive: Int, toInclusive: Int) = (toInclusive downTo fromInclusive) }, + LE { override fun range(fromInclusive: Int, toInclusive: Int) = (fromInclusive .. toInclusive) }; + abstract fun range(fromInclusive: Int, toInclusive: Int): IntProgression +} + +internal enum class BinaryFormat : (Int) -> Boolean { + HALF { override fun invoke(index: Int) = index % 4 == 0 && index != 0 }, + BYTE { override fun invoke(index: Int) = index % 8 == 0 && index != 0 }, + DOUBLE { override fun invoke(index: Int) = index % 16 == 0 && index != 0 }, +} + +internal fun MutableList.transformIfNotEmpty(transform: MutableList.() -> List): List { + return if (isNotEmpty()) transform() else this +} + +// todo move to tests +//fun main() { +// val endian = Endian.BE +// println(255.toUByte().toBinaryString(endian)) +// println(2.toBinaryString(endian)) +// println(BitVectorValue.fromInt(2).toBinaryString(endian)) +// print(8.75f.toBinaryString(endian)) +// print(8.75.toBinaryString(endian)) +//} \ No newline at end of file diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/MissedSeed.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/MissedSeed.kt new file mode 100644 index 0000000000..2618238522 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/MissedSeed.kt @@ -0,0 +1,27 @@ +package org.utbot.fuzzing.utils + +import org.utbot.fuzzing.Seed + +class MissedSeed : Iterable { + + private val values = hashMapOf>() + + operator fun set(value: T, seed: Seed) { + values[value] = seed + } + + operator fun get(value: T): Seed? { + return values[value] + } + + fun isEmpty(): Boolean { + return values.size == 0 + } + + fun isNotEmpty(): Boolean = !isEmpty() + + override fun iterator(): Iterator { + return values.keys.iterator() + } + +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/PseudoShuffledIntProgression.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/PseudoShuffledIntProgression.kt similarity index 99% rename from utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/PseudoShuffledIntProgression.kt rename to utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/PseudoShuffledIntProgression.kt index 4ed08dd7a4..5d6ebdbce1 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/PseudoShuffledIntProgression.kt +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/PseudoShuffledIntProgression.kt @@ -1,4 +1,4 @@ -package org.utbot.fuzzer +package org.utbot.fuzzing.utils import kotlin.math.sqrt import kotlin.random.Random diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/RandomExtensions.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/RandomExtensions.kt new file mode 100644 index 0000000000..74e62210bc --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/RandomExtensions.kt @@ -0,0 +1,41 @@ +package org.utbot.fuzzing.utils + +import kotlin.random.Random + +/** + * Chooses a random value using frequencies. + * + * If value has greater frequency value then it would be chosen with greater probability. + * + * @return the index of the chosen item. + */ +fun Random.chooseOne(frequencies: DoubleArray): Int { + val total = frequencies.sum() + val value = nextDouble(total) + var nextBound = 0.0 + frequencies.forEachIndexed { index, bound -> + check(bound >= 0) { "Frequency must not be negative" } + nextBound += bound + if (value < nextBound) return index + } + error("Cannot find next index") +} + +/** + * Tries a value. + * + * If a random value is less than [probability] returns true. + */ +fun Random.flipCoin(probability: Int): Boolean { + if (probability == 0) return false + if (probability == 100) return true + check(probability in 0 .. 100) { "probability must in range [0, 100] but $probability is provided" } + return nextInt(1, 101) <= probability +} + +fun Long.invertBit(bitIndex: Int): Long { + return this xor (1L shl bitIndex) +} + +fun Int.hex(): String = + toString(16) diff --git a/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/Trie.kt b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/Trie.kt new file mode 100644 index 0000000000..7bc4190f64 --- /dev/null +++ b/utbot-fuzzing/src/main/kotlin/org/utbot/fuzzing/utils/Trie.kt @@ -0,0 +1,184 @@ +package org.utbot.fuzzing.utils + +fun trieOf(vararg values: Iterable): Trie = IdentityTrie().apply { + values.forEach(this::add) +} + +fun stringTrieOf(vararg values: String): StringTrie = StringTrie().apply { + values.forEach(this::add) +} + +class StringTrie : IdentityTrie() { + fun add(string: String) = super.add(string.toCharArray().asIterable()) + fun removeCompletely(string: String) = super.removeCompletely(string.toCharArray().asIterable()) + fun remove(string: String) = super.remove(string.toCharArray().asIterable()) + operator fun get(string: String) = super.get(string.toCharArray().asIterable()) + fun collect() = asSequence().map { String(it.toCharArray()) }.toSet() +} + +open class IdentityTrie : Trie({ it }) + +/** + * Implementation of a trie for any iterable values. + */ +open class Trie( + private val keyExtractor: (T) -> K +) : Iterable> { + + private val roots = HashMap>() + private val implementations = HashMap, NodeImpl>() + + /** + * Adds value into a trie. + * + * If value already exists then do nothing except increasing internal counter of added values. + * The counter can be returned by [Node.count]. + * + * @return corresponding [Node] of the last element in the `values` + */ + fun add(values: Iterable): Node { + val root = try { values.first() } catch (e: NoSuchElementException) { error("Empty list are not allowed") } + var key = keyExtractor(root) + var node = roots.computeIfAbsent(key) { NodeImpl(root, null) } + values.asSequence().drop(1).forEach { value -> + key = keyExtractor(value) + node = node.children.computeIfAbsent(key) { NodeImpl(value, node) } + } + node.count++ + implementations[node] = node + return node + } + + /** + * Decreases node counter value or removes the value completely if `counter == 1`. + * + * Use [removeCompletely] to remove the value from the trie regardless of counter value. + * + * @return removed node if value exists. + */ + fun remove(values: Iterable): Node? { + val node = findImpl(values) ?: return null + return when { + node.count == 1 -> removeCompletely(values) + node.count > 1 -> node.apply { count-- } + else -> throw IllegalStateException("count should be 1 or greater") + } + } + + /** + * Removes value from a trie. + * + * The value is removed completely from the trie. Thus, the next code is true: + * + * ``` + * trie.remove(someValue) + * trie.get(someValue) == null + * ``` + * + * Use [remove] to decrease counter value instead of removal. + * + * @return removed node if value exists + */ + fun removeCompletely(values: Iterable): Node? { + val node = findImpl(values) ?: return null + if (node.count > 0 && node.children.isEmpty()) { + var n: NodeImpl? = node + while (n != null) { + val key = keyExtractor(n.data) + n = n.parent + if (n == null) { + val removed = roots.remove(key) + check(removed != null) + } else { + val removed = n.children.remove(key) + check(removed != null) + if (n.count != 0) { + break + } + } + } + } + return if (node.count > 0) { + node.count = 0 + implementations.remove(node) + node + } else { + null + } + } + + operator fun get(values: Iterable): Node? { + return findImpl(values) + } + + operator fun get(node: Node): List? { + return implementations[node]?.let(this::buildValue) + } + + private fun findImpl(values: Iterable): NodeImpl? { + val root = try { values.first() } catch (e: NoSuchElementException) { return null } + var key = keyExtractor(root) + var node = roots[key] ?: return null + values.asSequence().drop(1).forEach { value -> + key = keyExtractor(value) + node = node.children[key] ?: return null + } + return node.takeIf { it.count > 0 } + } + + override fun iterator(): Iterator> { + return iterator { + roots.values.forEach { node -> + traverseImpl(node) + } + } + } + + private suspend fun SequenceScope>.traverseImpl(node: NodeImpl) { + val stack = ArrayDeque>() + stack.addLast(node) + while (stack.isNotEmpty()) { + val n = stack.removeLast() + if (n.count > 0) { + yield(buildValue(n)) + } + n.children.values.forEach(stack::addLast) + } + } + + private fun buildValue(node: NodeImpl): List { + return generateSequence(node) { it.parent }.map { it.data }.toList().asReversed() + } + + interface Node { + val data: T + val count: Int + } + + /** + * Trie node + * + * @param data data to be stored + * @param parent reference to the previous element of the value + * @param count number of value insertions + * @param children list of children mapped by their key + */ + private class NodeImpl( + override val data: T, + val parent: NodeImpl?, + override var count: Int = 0, + val children: MutableMap> = HashMap(), + ) : Node + + private object EmptyNode : Node { + override val data: Any + get() = error("empty node has no data") + override val count: Int + get() = 0 + } + + companion object { + @Suppress("UNCHECKED_CAST") + fun emptyNode() = EmptyNode as Node + } +} \ No newline at end of file diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/FuzzerSmokeTest.kt b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/FuzzerSmokeTest.kt new file mode 100644 index 0000000000..260b824942 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/FuzzerSmokeTest.kt @@ -0,0 +1,311 @@ +package org.utbot.fuzzing + +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.flow +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.fail +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Timeout +import org.junit.jupiter.api.assertThrows +import org.utbot.fuzzing.seeds.BitVectorValue +import org.utbot.fuzzing.seeds.Signed +import java.util.concurrent.TimeUnit +import kotlin.reflect.KClass + +class FuzzerSmokeTest { + + @Test + fun `fuzzing runs with empty parameters`() { + runBlocking { + var count = 0 + runFuzzing, BaseFeedback>( + { _, _ -> sequenceOf() }, + Description(emptyList()) + ) { _, _ -> + count += 1 + BaseFeedback(Unit, Control.STOP) + } + Assertions.assertEquals(1, count) + } + } + + @Test + fun `fuzzing throws an exception if no values generated for some type`() { + assertThrows { + runBlocking { + var count = 0 + runFuzzing, BaseFeedback>( + provider = { _, _ -> sequenceOf() }, + description = Description(listOf(Unit)), + configuration = Configuration( + generateEmptyCollectionsForMissedTypes = false + ) + ) { _, _ -> + count += 1 + BaseFeedback(Unit, Control.STOP) + } + Assertions.assertEquals(0, count) + } + } + } + + @Test + fun `fuzzing stops on Control$STOP signal after one execution`() { + runBlocking { + var count = 0 + runFuzzing( + { _, _ -> sequenceOf(Seed.Simple(Unit)) }, + Description(listOf(Unit)) + ) { _, _ -> + count += 1 + BaseFeedback(Unit, Control.STOP) + } + Assertions.assertEquals(1, count) + } + } + + @Test + fun `fuzzing stops on Control$STOP signal after 3 execution`() { + runBlocking { + var count = 3 + var executions = 0 + runFuzzing( + { _, _ -> sequenceOf(Seed.Simple(Unit)) }, + Description(listOf(Unit)) + ) { _, _ -> + executions++ + BaseFeedback(Unit, if (--count == 0) Control.STOP else Control.CONTINUE) + } + Assertions.assertEquals(0, count) + Assertions.assertEquals(3, executions) + } + } + + @Test + fun `fuzzing runs value generation once per type by default`() { + runBlocking { + var count = 10 + var generations = 0 + runFuzzing( + { _, _ -> + generations++ + sequenceOf(Seed.Simple(Unit)) + }, + Description(listOf(Unit)) + ) { _, _ -> + BaseFeedback(Unit, if (--count == 0) Control.STOP else Control.CONTINUE) + } + Assertions.assertEquals(0, count) + Assertions.assertEquals(1, generations) + } + } + + @Test + fun `fuzzer rethrow exception from execution block`() { + class SpecialException : Exception() + runBlocking { + assertThrows { + withTimeout(1000) { + runFuzzing( + { _, _ -> sequenceOf(Seed.Simple(Unit)) }, + Description(listOf(Unit)) + ) { _, _ -> + throw SpecialException() + } + } + } + } + } + + @Test + fun `fuzzer generates recursive data with correct depth`() { + data class Node(val left: Node?, val right: Node?) + + runBlocking { + val configuration = Configuration( + recursionTreeDepth = 10 + ) + var depth = 0 + var count = 0 + runFuzzing( + ValueProvider, Node?, Description>> { _, _ -> + sequenceOf(Seed.Recursive( + construct = Routine.Create(listOf(Node::class, Node::class)) { v -> + Node(v[0], v[1]) + }, + empty = Routine.Empty { null } + )) + }, + Description(listOf(Node::class)), + configuration = configuration + ) { _, v -> + fun traverse(n: Node?, l: Int = 1) { + n ?: return + depth = maxOf(depth, l) + count++ + traverse(n.left, l + 1) + traverse(n.right, l + 1) + } + traverse(v.first()) + Assertions.assertEquals(configuration.recursionTreeDepth, depth) + Assertions.assertEquals((1 shl configuration.recursionTreeDepth) - 1, count) + BaseFeedback(this, Control.STOP) + } + } + } + + @Test + fun `fuzzer can be cancelled by timeout`() { + val start = System.currentTimeMillis() + runBlocking { + assertThrows { + withTimeout(1000) { + runFuzzing( + { _, _ -> sequenceOf(Seed.Simple(Unit)) }, + Description(listOf(Unit)) + ) { _, _ -> + if (System.currentTimeMillis() - start > 10_000) { + error("Fuzzer didn't stopped in 10 000 ms") + } + BaseFeedback(Unit, Control.CONTINUE) + } + } + } + } + } + + @Test + fun `fuzzer can be cancelled by coroutine`() { + runBlocking { + val deferred = async { + val start = System.currentTimeMillis() + runFuzzing( + { _, _ -> sequenceOf(Seed.Simple(Unit)) }, + Description(listOf(Unit)) + ) { _, _ -> + if (System.currentTimeMillis() - start > 10_000) { + error("Fuzzer didn't stopped in 10_000 ms") + } + BaseFeedback(Unit, Control.CONTINUE) + } + } + delay(1000) + deferred.cancel() + } + } + + @Test + fun `fuzzer generate same result when random is seeded`() { + data class B(var a: Int) + val provider = ValueProvider> { _, _ -> + sequenceOf( + Seed.Simple(B(0)) { p, r -> B(p.a + r.nextInt()) }, + Seed.Known(BitVectorValue(32, Signed.POSITIVE)) { B(it.toInt()) }, + Seed.Recursive( + construct = Routine.Create(listOf(Unit)) { _ -> B(2) }, + modify = sequenceOf( + Routine.Call(listOf(Unit)) { self, _ -> self.a = 3 }, + Routine.Call(listOf(Unit)) { self, _ -> self.a = 4 }, + Routine.Call(listOf(Unit)) { self, _ -> self.a = 5 }, + Routine.Call(listOf(Unit)) { self, _ -> self.a = 6 }, + ), + empty = Routine.Empty { B(7) } + ), + Seed.Collection( + construct = Routine.Collection { size -> B(size) }, + modify = Routine.ForEach(listOf(Unit)) { self, ind, v -> self.a = ind * self.a * v.first().a } + ) + ) + } + fun createValues(): MutableList { + val result = mutableListOf() + val probes = 1_000 + runBlocking { + runFuzzing(provider, Description(listOf(Unit))) { _, v -> + result.add(v.first().a) + BaseFeedback(Unit, if (result.size >= probes) Control.STOP else Control.CONTINUE) + } + } + return result + } + val firstRun = createValues() + val secondRun = createValues() + val thirdRun = createValues() + Assertions.assertEquals(firstRun, secondRun) + Assertions.assertEquals(firstRun, thirdRun) + Assertions.assertEquals(secondRun, thirdRun) + } + + @Test + fun `check flow invariant is not violated`() { + val timeConsumer: (Long) -> Unit = {} + runBlocking { + val deferred = async { + val start = System.currentTimeMillis() + flow { + runFuzzing( + { _, _ -> sequenceOf(Seed.Simple(Unit)) }, + Description(listOf(Unit)) + ) { _, _ -> + if (System.currentTimeMillis() - start > 10_000) { + error("Fuzzer didn't stopped in 10_000 ms") + } + emit(System.currentTimeMillis()) + BaseFeedback(Unit, Control.CONTINUE) + } + }.collect { + timeConsumer(it) + } + } + delay(1000) + deferred.cancel() + } + } + + @Test + fun `fuzzer can generate value without mutations`() { + runBlocking { + var seenEmpty = false + withTimeout(1000) { + runFuzzing( + { _, _ -> sequenceOf(Seed.Recursive( + construct = Routine.Create(emptyList()) { StringBuilder("") }, + modify = sequenceOf(Routine.Call(emptyList()) { s, _ -> s.append("1") }), + empty = Routine.Empty { fail("Empty is called despite construct requiring no args") } + )) }, + Description(listOf(Unit)) + ) { _, (s) -> + if (s.isEmpty()) { + seenEmpty = true + BaseFeedback(Unit, Control.STOP) + } else BaseFeedback(Unit, Control.CONTINUE) + } + } + Assertions.assertTrue(seenEmpty) { "Unmodified empty string wasn't generated" } + } + } + + @Test + @Timeout(10, unit = TimeUnit.SECONDS) // withTimeout(1000) works inconsistently + fun `fuzzer works when there are many recursive seeds`() { + class Node(val parent: Node?) + + runBlocking { + var seenAnything = false + withTimeout(1000) { + runFuzzing( + { _, _ -> List(100) {Seed.Recursive( + construct = Routine.Create(listOf(Unit)) { (parent) -> Node(parent) }, + modify = emptySequence(), + empty = Routine.Empty { null } + )}.asSequence() }, + Description(listOf(Unit)) + ) { _, _ -> + seenAnything = true + BaseFeedback(Unit, Control.STOP) + } + } + Assertions.assertTrue(seenAnything) { "Fuzzer hasn't generated any values" } + } + } +} \ No newline at end of file diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/ProvidersTest.kt b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/ProvidersTest.kt new file mode 100644 index 0000000000..a312bd0a61 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/ProvidersTest.kt @@ -0,0 +1,194 @@ +package org.utbot.fuzzing + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class ProvidersTest { + + private fun p(supplier: () -> T) : ValueProvider> { + return ValueProvider { _, _ -> sequenceOf(Seed.Simple(supplier())) } + } + + private fun p( + accept: () -> Boolean, + supplier: () -> T + ) : ValueProvider> { + return object : ValueProvider> { + override fun accept(type: Unit) = accept() + override fun generate(description: Description, type: Unit) = sequence> { + yield(Seed.Simple(supplier())) + } + } + } + + private val description = Description(listOf(Unit)) + + @Test + fun `test common provider API`() { + val provider = ValueProvider.of(listOf(p { 1 })) + (1..3).forEach { _ -> + val attempt = provider.generate(description, Unit).first() as Seed.Simple + Assertions.assertEquals(1, attempt.value) + } + } + + @Test + fun `test merging of providers`() { + var count = 0 + ValueProvider.of(listOf( + p { 1 }, p { 2 }, p { 3 } + )).generate(description, Unit) + .map { (it as Seed.Simple).value } + .forEachIndexed { index, result -> + count++ + Assertions.assertEquals(index + 1, result) + } + Assertions.assertEquals(3, count) + } + + @Test + fun `test merging of providers several times`() { + val p1 = p { 1 } + val p2 = p { 2 } + val p3 = p { 3 } + val p4 = p { 4 } + val m1 = ValueProvider.of(listOf(p1, p2)) + val m2 = p3 with p4 + val m3 = m1 with m2 + val m4 = m3 with ValueProvider.of(emptyList()) + + Assertions.assertEquals(1, p1.generate(description, Unit).count()) + Assertions.assertEquals(1, (p1.generate(description, Unit).first() as Seed.Simple).value) + + Assertions.assertEquals(1, p2.generate(description, Unit).count()) + Assertions.assertEquals(2, (p2.generate(description, Unit).first() as Seed.Simple).value) + + Assertions.assertEquals(1, p3.generate(description, Unit).count()) + Assertions.assertEquals(3, (p3.generate(description, Unit).first() as Seed.Simple).value) + + Assertions.assertEquals(1, p4.generate(description, Unit).count()) + Assertions.assertEquals(4, (p4.generate(description, Unit).first() as Seed.Simple).value) + + Assertions.assertEquals(2, m1.generate(description, Unit).count()) + Assertions.assertEquals(1, (m1.generate(description, Unit).first() as Seed.Simple).value) + Assertions.assertEquals(2, (m1.generate(description, Unit).drop(1).first() as Seed.Simple).value) + + Assertions.assertEquals(2, m2.generate(description, Unit).count()) + Assertions.assertEquals(3, (m2.generate(description, Unit).first() as Seed.Simple).value) + Assertions.assertEquals(4, (m2.generate(description, Unit).drop(1).first() as Seed.Simple).value) + + Assertions.assertEquals(4, m3.generate(description, Unit).count()) + Assertions.assertEquals(1, (m3.generate(description, Unit).first() as Seed.Simple).value) + Assertions.assertEquals(2, (m3.generate(description, Unit).drop(1).first() as Seed.Simple).value) + Assertions.assertEquals(3, (m3.generate(description, Unit).drop(2).first() as Seed.Simple).value) + Assertions.assertEquals(4, (m3.generate(description, Unit).drop(3).first() as Seed.Simple).value) + + Assertions.assertEquals(4, m4.generate(description, Unit).count()) + } + + @Test + fun `test merging of providers by with-method`() { + var count = 0 + p { 1 }.with(p { 2 }).with(p { 3 }).generate(description, Unit) + .map { (it as Seed.Simple).value } + .forEachIndexed { index, result -> + count++ + Assertions.assertEquals(index + 1, result) + } + Assertions.assertEquals(3, count) + } + + @Test + fun `test excepting from providers`() { + val provider = p { 2 } + val seq = ValueProvider.of(listOf( + p { 1 }, provider, p { 3 } + )).except { + it === provider + }.generate(description, Unit) + Assertions.assertEquals(2, seq.count()) + Assertions.assertEquals(1, (seq.first() as Seed.Simple).value) + Assertions.assertEquals(3, (seq.drop(1).first() as Seed.Simple).value) + } + + @Test + fun `test using except on provider with fallback`() { + val provider1 = p { 2 } + val provider2 = p { 3 } + val fallback = p { 4 } + val providers1 = ValueProvider.of(listOf( + provider1.withFallback(fallback), + provider2 + )) + val seq1 = providers1.generate(description, Unit).toSet() + Assertions.assertEquals(2, seq1.count()) + Assertions.assertEquals(2, (seq1.first() as Seed.Simple).value) + Assertions.assertEquals(3, (seq1.drop(1).first() as Seed.Simple).value) + + val providers2 = providers1.except(provider1) + + val seq2 = providers2.generate(description, Unit).toSet() + Assertions.assertEquals(2, seq2.count()) + Assertions.assertEquals(4, (seq2.first() as Seed.Simple).value) + Assertions.assertEquals(3, (seq2.drop(1).first() as Seed.Simple).value) + } + + @Test + fun `provider is not called when accept-method returns false`() { + val seq = ValueProvider.of(listOf( + p({ true }, { 1 }), p({ false }, { 2 }), p({ true }, { 3 }), + )).generate(description, Unit) + Assertions.assertEquals(2, seq.count()) + Assertions.assertEquals(1, (seq.first() as Seed.Simple).value) + Assertions.assertEquals(3, (seq.drop(1).first() as Seed.Simple).value) + } + + @Test + fun `provider doesnt call fallback when values is generated`() { + val seq = ValueProvider.of(listOf( + p({ true }, { 1 }), p({ false }, { 2 }), p({ true }, { 3 }), + )).withFallback { + Seed.Simple(4) + }.generate(description, Unit) + Assertions.assertEquals(2, seq.count()) + Assertions.assertEquals(1, (seq.first() as Seed.Simple).value) + Assertions.assertEquals(3, (seq.drop(1).first() as Seed.Simple).value) + } + + @Test + fun `provider calls fallback when values are not generated`() { + val seq = ValueProvider.of(listOf( + p({ false }, { 1 }), p({ false }, { 2 }), p({ false }, { 3 }), + )).withFallback { + Seed.Simple(4) + }.generate(description, Unit) + Assertions.assertEquals(1, seq.count()) + Assertions.assertEquals(4, (seq.first() as Seed.Simple).value) + } + + @Test + fun `provider generates no values when fallback cannot accept value`() { + val seq = ValueProvider.of(listOf( + p({ false }, { 1 }), p({ false }, { 2 }), p({ false }, { 3 }), + )).withFallback( + object : ValueProvider> { + override fun accept(type: Unit) = false + override fun generate(description: Description, type: Unit) = emptySequence>() + } + ).generate(description, Unit) + Assertions.assertEquals(0, seq.count()) + } + + @Test + fun `type providers check exactly the type`() { + val seq1 = TypeProvider>('A') { _, _ -> + yield(Seed.Simple(2)) + }.generate(Description(listOf('A')), 'A') + Assertions.assertEquals(1, seq1.count()) + + val seq2 = TypeProvider>('A') { _, _ -> + yield(Seed.Simple(2)) + }.generate(Description(listOf('A')), 'B') + Assertions.assertEquals(0, seq2.count()) + } +} \ No newline at end of file diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/providers/known/BitVectorValueTest.kt b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/providers/known/BitVectorValueTest.kt new file mode 100644 index 0000000000..dc447931a7 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/providers/known/BitVectorValueTest.kt @@ -0,0 +1,136 @@ +package org.utbot.fuzzing.providers.known + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.utbot.fuzzing.seeds.BitVectorValue +import org.utbot.fuzzing.seeds.DefaultBound +import org.utbot.fuzzing.seeds.Signed +import org.utbot.fuzzing.seeds.Unsigned +import kotlin.random.Random + +class BitVectorValueTest { + + @Test + fun `convert default kotlin literals to vector`() { + for (i in Byte.MIN_VALUE..Byte.MAX_VALUE) { + assertEquals(i.toByte(), BitVectorValue(Byte.SIZE_BITS, DefaultBound.ofByte(i.toByte())).toByte()) + } + + for (i in Short.MIN_VALUE..Short.MAX_VALUE) { + assertEquals(i.toShort(), BitVectorValue(Short.SIZE_BITS, DefaultBound.ofShort(i.toShort())).toShort()) + } + + val randomIntSequence = with(Random(0)) { sequence { while (true) yield(nextInt()) } } + randomIntSequence.take(100_000).forEach { + assertEquals(it, BitVectorValue(Int.SIZE_BITS, DefaultBound.ofInt(it)).toInt()) + } + + val randomLongSequence = with(Random(0)) { sequence { while (true) yield(nextLong()) } } + randomLongSequence.take(100_000).forEach { + assertEquals(it, BitVectorValue(Long.SIZE_BITS, DefaultBound.ofLong(it)).toLong()) + } + } + + @Test + fun `test default bit vectors (byte)`() { + assertEquals(0, BitVectorValue(8, Signed.ZERO).toByte()) + assertEquals(-128, BitVectorValue(8, Signed.MIN).toByte()) + assertEquals(-1, BitVectorValue(8, Signed.NEGATIVE).toByte()) + assertEquals(1, BitVectorValue(8, Signed.POSITIVE).toByte()) + assertEquals(127, BitVectorValue(8, Signed.MAX).toByte()) + } + + @Test + fun `test default bit vectors (unsigned byte)`() { + assertEquals(0u.toUByte(), BitVectorValue(8, Unsigned.ZERO).toUByte()) + assertEquals(1u.toUByte(), BitVectorValue(8, Unsigned.POSITIVE).toUByte()) + assertEquals(255u.toUByte(), BitVectorValue(8, Unsigned.MAX).toUByte()) + } + + @Test + fun `test default bit vectors (short)`() { + assertEquals(0, BitVectorValue(16, Signed.ZERO).toShort()) + assertEquals(Short.MIN_VALUE, BitVectorValue(16, Signed.MIN).toShort()) + assertEquals(-1, BitVectorValue(16, Signed.NEGATIVE).toShort()) + assertEquals(1, BitVectorValue(16, Signed.POSITIVE).toShort()) + assertEquals(Short.MAX_VALUE, BitVectorValue(16, Signed.MAX).toShort()) + } + + @Test + fun `test default bit vectors (unsigned short)`() { + assertEquals(UShort.MIN_VALUE, BitVectorValue(16, Unsigned.ZERO).toUShort()) + assertEquals(1u.toUShort(), BitVectorValue(16, Unsigned.POSITIVE).toUShort()) + assertEquals(UShort.MAX_VALUE, BitVectorValue(16, Unsigned.MAX).toUShort()) + } + + @Test + fun `test default bit vectors (int)`() { + assertEquals(0, BitVectorValue(32, Signed.ZERO).toInt()) + assertEquals(Int.MIN_VALUE, BitVectorValue(32, Signed.MIN).toInt()) + assertEquals(-1, BitVectorValue(32, Signed.NEGATIVE).toInt()) + assertEquals(1, BitVectorValue(32, Signed.POSITIVE).toInt()) + assertEquals(Int.MAX_VALUE, BitVectorValue(32, Signed.MAX).toInt()) + } + + @Test + fun `test default bit vectors (unsigned int)`() { + assertEquals(UInt.MIN_VALUE, BitVectorValue(32, Unsigned.ZERO).toUInt()) + assertEquals(1u, BitVectorValue(32, Unsigned.POSITIVE).toUInt()) + assertEquals(UInt.MAX_VALUE, BitVectorValue(32, Unsigned.MAX).toUInt()) + } + + @Test + fun `test default bit vectors (long)`() { + assertEquals(0, BitVectorValue(64, Signed.ZERO).toLong()) + assertEquals(Long.MIN_VALUE, BitVectorValue(64, Signed.MIN).toLong()) + assertEquals(-1, BitVectorValue(64, Signed.NEGATIVE).toLong()) + assertEquals(1, BitVectorValue(64, Signed.POSITIVE).toLong()) + assertEquals(Long.MAX_VALUE, BitVectorValue(64, Signed.MAX).toLong()) + } + + @Test + fun `test default bit vectors (unsigned long)`() { + assertEquals(ULong.MIN_VALUE, BitVectorValue(64, Unsigned.ZERO).toULong()) + assertEquals(1uL, BitVectorValue(64, Unsigned.POSITIVE).toULong()) + assertEquals(ULong.MAX_VALUE, BitVectorValue(64, Unsigned.MAX).toULong()) + } + + @Test + fun `convert byte from and to`() { + for (i in Byte.MIN_VALUE..Byte.MAX_VALUE) { + assertEquals(i.toByte(), BitVectorValue.fromByte(i.toByte()).toByte()) + } + } + + @Test + fun `inc and dec byte`() { + for (i in Byte.MIN_VALUE..Byte.MAX_VALUE) { + val v = BitVectorValue.fromByte(i.toByte()) + assertEquals(i.toByte() == Byte.MAX_VALUE, v.inc()) { "$v" } + assertEquals((i + 1).toByte(), v.toByte()) + } + + for (i in Byte.MAX_VALUE downTo Byte.MIN_VALUE) { + val v = BitVectorValue.fromByte(i.toByte()) + assertEquals(i.toByte() == Byte.MIN_VALUE, v.dec()) { "$v" } + assertEquals((i - 1).toByte(), v.toByte()) + } + } + + @Test + fun `inc and dec long`() { + val r = with(Random(0)) { LongArray(1024) { nextLong() } } + r[0] = Long.MIN_VALUE + r[1] = Long.MAX_VALUE + r.forEach { l -> + val v = BitVectorValue.fromLong(l) + assertEquals(l == Long.MAX_VALUE, v.inc()) { "$v" } + assertEquals(l + 1, v.toLong()) + } + r.forEach { l -> + val v = BitVectorValue.fromLong(l) + assertEquals(l == Long.MIN_VALUE, v.dec()) { "$v" } + assertEquals(l - 1, v.toLong()) + } + } +} \ No newline at end of file diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Arrays.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Arrays.java new file mode 100644 index 0000000000..02520ef345 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Arrays.java @@ -0,0 +1,110 @@ +package org.utbot.fuzzing.samples; + +import java.util.HashSet; +import java.util.Set; + +@SuppressWarnings({"unused", "ForLoopReplaceableByForEach"}) +public class Arrays { + + // should find identity matrix + public boolean isIdentityMatrix(int[][] matrix) { + if (matrix.length < 3) { + throw new IllegalArgumentException("matrix.length < 3"); + } + + for (int i = 0; i < matrix.length; i++) { + if (matrix[i].length != matrix.length) { + return false; + } + for (int j = 0; j < matrix[i].length; j++) { + if (i == j && matrix[i][j] != 1) { + return false; + } + + if (i != j && matrix[i][j] != 0) { + return false; + } + } + } + + return true; + } + + // should fail with OOME and should reveal some branches + public boolean isIdentityMatrix(int[][][] matrix) { + if (matrix.length < 3) { + throw new IllegalArgumentException("matrix.length < 3"); + } + + for (int i = 0; i < matrix.length; i++) { + if (matrix[i].length != matrix.length) { + return false; + } else { + for (int j = 0; j < matrix.length; j++) { + if (matrix[i][j].length != matrix[i].length) { + return false; + } + } + } + for (int j = 0; j < matrix[i].length; j++) { + for (int k = 0; k < matrix[i][j].length; k++) { + if (i == j && j == k && matrix[i][j][k] != 1) { + return false; + } + + if ((i != j || j != k) && matrix[i][j][k] != 0) { + return false; + } + } + } + } + + return true; + } + + public static class Point { + int x, y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + boolean equals(Point other) { + return (other.x == this.x && other.y == this.y); + } + } + + boolean checkAllSame(Integer[] a) { // Fuzzer should find parameters giving true as well as parameters giving false + if (a.length < 1) return true; + Set s = new HashSet<>(java.util.Arrays.asList(a)); + return (s.size() <= 1); + } + + public boolean checkAllSamePoints(Point[] a) { // Also works for classes + if (a.length == 4) { + return false; // Test with array of size 4 should be generated by fuzzer + } + for (int i = 1; i < a.length; i++) { + if (!a[i].equals(a[i - 1])) + return false; + } + return true; + } + + public boolean checkRowsWithAllSame2D(int[][] a, int y) { + int cntSame = 0; + for (int i = 0; i < a.length; i++) { + boolean same = true; + for (int j = 1; j < a[i].length; j++) { + if (a[i][j] != a[i][j - 1]) { + same = false; + break; + } + } + if (same) + cntSame++; + } + return (cntSame == y && y > 0 && y < a.length); + } +} diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Collections.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Collections.java new file mode 100644 index 0000000000..e26c7b6d1a --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Collections.java @@ -0,0 +1,189 @@ +package org.utbot.fuzzing.samples; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +@SuppressWarnings({"unused", "RedundantIfStatement"}) +public class Collections { + /** + * Should create unsorted list that will be sorted as a result. + */ + public static Collection sorted(Collection source) { + if (source.size() < 2) throw new IllegalArgumentException(); + return source.stream().sorted().collect(Collectors.toList()); + } + + /** + * Should create at least both answers: one that finds the key, and another returns null. + */ + public static String getKeyForValue(Map map, Number value) { + for (Map.Entry entry : map.entrySet()) { + if (java.util.Objects.equals(entry.getValue(), value)) { + return entry.getKey(); + } + } + return null; + } + + /** + * Should find a branch that returns true and size of array is greater than 1 (non-trivial). + */ + public static boolean isDiagonal(Collection> matrix) { + int cols = matrix.size(); + if (cols <= 1) { + return false; + } + int i = 0; + for (Collection col : matrix) { + if (col.size() != cols) { + return false; + } + int j = 0; + for (Double value : col) { + if (i == j && value == 0.0) return false; + if (i != j && value != 0.0) return false; + j++; + } + i++; + } + return true; + } + + /** + * Checks that different collections can be created. Part 1 + */ + public boolean allCollectionAreSameSize1( + Collection c, + List l, + Set s, + SortedSet ss, + Deque d, + Iterable i + ) { + if (c.size() != l.size()) { + return false; + } + if (l.size() != s.size()) { + return false; + } + if (s.size() != ss.size()) { + return false; + } + if (ss.size() != d.size()) { + return false; + } + if (d.size() != StreamSupport.stream(i.spliterator(), false).count()) { + return false; + } + return true; + } + + /** + * Checks that different collections can be created. Part 2 + */ + public boolean allCollectionAreSameSize2( + Iterable i, + Stack st, + NavigableSet ns, + Map m, + SortedMap sm, + NavigableMap nm + ) { + if (StreamSupport.stream(i.spliterator(), false).count() != st.size()) { + return false; + } + if (st.size() != ns.size()) { + return false; + } + if (ns.size() != m.size()) { + return false; + } + if (m.size() != sm.size()) { + return false; + } + if (sm.size() != nm.size()) { + return false; + } + return true; + } + + /** + * Should create TreeSet without any modifications as T extends Number is not Comparable + */ + public boolean testTreeSetWithoutComparable(NavigableSet set) { + if (set.size() > 3) { + return true; + } + return false; + } + + /** + * Should create TreeSet with modifications as Integer is Comparable + */ + public boolean testTreeSetWithComparable(NavigableSet set) { + if (set.size() > 3) { + return true; + } + return false; + } + + public static class ConcreteList extends LinkedList { + public boolean equals(Collection collection) { + if (collection.size() != size()) { + return false; + } + int i = 0; + for (T t : collection) { + if (!java.util.Objects.equals(get(i), t)) { + return false; + } + } + return true; + } + } + + /** + * Should create concrete class + */ + public boolean testConcreteCollectionIsCreated(ConcreteList list) { + if (list.size() > 3) { + return true; + } + return false; + } + + public static class ConcreteMap extends HashMap { } + + /** + * Should create concrete class + */ + public boolean testConcreteMapIsCreated(ConcreteMap map) { + if (map.size() > 3) { + return true; + } + return false; + } + + /** + * Should create test with no error + */ + public boolean testNoErrorWithHashMap(HashMap map) { + if (map.size() > 5) { + return true; + } + return false; + } + + /** + * Should generate iterators with recursions + */ + public static > int size(Iterator some) { + int r = 0; + while (some.hasNext()) { + some.next(); + r++; + } + return r; + } +} diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Dates.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Dates.java new file mode 100644 index 0000000000..3e0bb16aae --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Dates.java @@ -0,0 +1,21 @@ +package org.utbot.fuzzing.samples; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +@SuppressWarnings({"unused", "RedundantIfStatement"}) +public class Dates { + public boolean getTime(Date date) { + return date.getTime() == 100; + } + + public boolean getTimeFormatted(Date date) throws ParseException { + boolean after = new SimpleDateFormat("dd-MM-yyyy").parse("10-06-2012").after(date); + if (after) { + return true; + } else { + return false; + } + } +} diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Enums.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Enums.java new file mode 100644 index 0000000000..0a216f4af1 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Enums.java @@ -0,0 +1,61 @@ +package org.utbot.fuzzing.samples; + +@SuppressWarnings("unused") +public class Enums { + + public int test(int x) { + switch(x) { + case 1: + return 1; + case 2: + return 2; + case 3: + return 3; + } + return 0; + } + + public int test3(int x) { + switch(x) { + case 11: + return 1; + case 25: + return 2; + case 32: + return 3; + } + return 0; + } + + public int test4(S x) { + switch(x) { + case A: + return 1; + case B: + return 2; + case C: + return 3; + } + return 0; + } + + public enum S { + A, B, C + } + + public int test2(int x) { + int a = 0; + switch(x) { + case 1: + a = 1; + break; + case 2: + a = 2; + break; + case 3: + a = 3; + } + return a; + } + +} diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Floats.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Floats.java new file mode 100644 index 0000000000..0f0917ffe9 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Floats.java @@ -0,0 +1,44 @@ +package org.utbot.fuzzing.samples; + +@SuppressWarnings("unused") +public class Floats { + + // Should find the value between 0 and -1. + public int floatToInt(float x) { + if (x < 0) { + if ((int) x < 0) { + return 1; + } + return 2; // smth small to int zero + } + return 3; + } + + // should find all branches that return -2, -1, 0, 1, 2. + public int numberOfRootsInSquareFunction(double a, double b, double c) { + if (!Double.isFinite(a) || !Double.isFinite(b) || !Double.isFinite(c)) return -1; + if (a == 0.0 || b == 0.0 || c == 0.0) return -2; + double result = b * b - 4 * a * c; + if (result > 0) { + return 2; + } else if (result == 0) { + return 1; + } + return 0; + } + + // will never be succeeded to find the value because of floating precision + public void floatEq(float v) { + if (v == 28.7) { + throw new IllegalArgumentException(); + } + } + + // should generate double for constant float that equals to 28.700000762939453 + public void floatEq(double v) { + if (v == 28.7f) { + throw new IllegalArgumentException(); + } + } + +} diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Generics.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Generics.java new file mode 100644 index 0000000000..381d71a70d --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Generics.java @@ -0,0 +1,27 @@ +package org.utbot.fuzzing.samples; + +@SuppressWarnings("unused") +public class Generics> { + + private final T[] value; + + public Generics(T[] value) { + this.value = value; + } + + // should generate data with numbers to sum it + public double getSum() { + double sum = 0; + for (T numbers : value) { + for (Number number : numbers) { + if (number.doubleValue() > 0) { + sum += number.doubleValue(); + } + } + } + if (sum == 0.0) { + throw new IllegalStateException(); + } + return sum; + } +} diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Integers.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Integers.java new file mode 100644 index 0000000000..d65dcbdfa9 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Integers.java @@ -0,0 +1,66 @@ +package org.utbot.fuzzing.samples; + +@SuppressWarnings({"unused", "RedundantIfStatement"}) +public class Integers { + + public byte biting(byte a, byte b, boolean s) { + if (s) { + return a; + } else { + return b; + } + } + + // should cover 100% + public static void diff(int a) { + a = Math.abs(a); + while (a > 0) { + a = a % 2; + } + if (a < 0) { + throw new IllegalArgumentException(); + } + throw new RuntimeException(); + } + + // should cover 100% and better when values are close to constants, + // also should generate "this" empty object + public String extent(int a) { + if (a < -2.0) { + return "-1"; + } + if (a > 5) { + return "-2"; + } + if (a == 3) { + return "-3"; + } + if (4L < a) { + return "-4"; + } + return "0"; + } + + // should cover 100% with 3 tests + public static boolean isGreater(long a, short b, int c) { + if (b > a && a < c) { + return true; + } + return false; + } + + // should find a bad value with integer overflow + public boolean unreachable(int x) { + int y = x * x - 2 * x + 1; + if (y < 0) throw new IllegalArgumentException(); + return true; + } + + public boolean chars(char a) { + if (a >= 'a' && a <= 'z') { + return true; + } + return false; + } + +} diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Objects.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Objects.java new file mode 100644 index 0000000000..a6d640cc48 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Objects.java @@ -0,0 +1,36 @@ +package org.utbot.fuzzing.samples; + +import org.utbot.fuzzing.samples.data.Recursive; +import org.utbot.fuzzing.samples.data.UserDefinedClass; + +@SuppressWarnings({"unused", "RedundantIfStatement"}) +public class Objects { + + public int test(UserDefinedClass userDefinedClass) { + if (userDefinedClass.getX() > 5 && userDefinedClass.getY() < 10) { + return 1; + } else if (userDefinedClass.getZ() < 20) { + return 2; + } else { + return 3; + } + } + + public boolean testMe(Recursive r) { + if (r.val() > 10) { + return true; + } + return false; + } + + private int data; + + public static void foo(Objects a, Objects b) { + a.data = 1; + b.data = 2; + //noinspection ConstantValue + if (a.data == b.data) { + throw new IllegalArgumentException(); + } + } +} diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Strings.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Strings.java new file mode 100644 index 0000000000..49ef688ac5 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/Strings.java @@ -0,0 +1,67 @@ +package org.utbot.fuzzing.samples; + +import java.util.regex.Pattern; + +@SuppressWarnings({"unused", "SimplifiableConditionalExpression"}) +public class Strings { + + // should find a string that starts with "bad!" prefix + public static void test(String s) { + if (s.charAt(0) == "b".charAt(0)) { + if (s.charAt(1) == "a".charAt(0)) { + if (s.charAt(2) == "d".charAt(0)) { + if (s.charAt(3) == "!".charAt(0)) { + throw new IllegalArgumentException(); + } + } + } + } + } + + // should try to find the string with size 6 and with "!" in the end + public static void testStrRem(String str) { + if (!"world???".equals(str) && str.charAt(5) == '!' && str.length() == 6) { + throw new RuntimeException(); + } + } + + public boolean isValidUuid(String uuid) { + return isNotBlank(uuid) && uuid + .matches("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); + } + + public boolean isValidUuidShortVersion(String uuid) { + return uuid != null && uuid.matches("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); + } + + public boolean isValidDouble(String value) { + return value.matches("[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?([fFdD]?)") && value.contains("E") && value.contains("-"); + } + + static final String pattern = "\\d+"; + + public boolean isNumber(String s) { + return Pattern.matches(pattern, s) ? true : false; + } + + private static boolean isNotBlank(CharSequence cs) { + return !isBlank(cs); + } + + private static boolean isBlank(CharSequence cs) { + int strLen = length(cs); + if (strLen != 0) { + for (int i = 0; i < strLen; ++i) { + if (!Character.isWhitespace(cs.charAt(i))) { + return false; + } + } + + } + return true; + } + + private static int length(CharSequence cs) { + return cs == null ? 0 : cs.length(); + } +} diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/data/Recursive.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/data/Recursive.java new file mode 100644 index 0000000000..9f3ab45d71 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/data/Recursive.java @@ -0,0 +1,19 @@ +package org.utbot.fuzzing.samples.data; + +@SuppressWarnings("unused") +public class Recursive { + + private final int a; + + public Recursive(int a) { + this.a = a; + } + + public Recursive(Recursive r) { + this.a = r.a; + } + + public int val() { + return a; + } +} diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/data/UserDefinedClass.java b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/data/UserDefinedClass.java new file mode 100644 index 0000000000..5733a78c55 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/samples/data/UserDefinedClass.java @@ -0,0 +1,44 @@ +package org.utbot.fuzzing.samples.data; + +@SuppressWarnings("unused") +public class UserDefinedClass { + public int x; + private int y; + public int z; + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public UserDefinedClass setY(int y) { + this.y = y; + return this; + } + + public void setZ(int z) { + this.z = z; + } + + public int getZ() { + return z; + } + + public UserDefinedClass() { + } + + public UserDefinedClass(int x, int y) { + this.x = x; + this.y = y; + this.z = 0; + } + + public UserDefinedClass(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } +} diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/CombinationsTest.kt b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/CombinationsTest.kt new file mode 100644 index 0000000000..a1ae742b0c --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/CombinationsTest.kt @@ -0,0 +1,299 @@ +package org.utbot.fuzzing.utils + +import org.junit.jupiter.api.Assertions.assertArrayEquals +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import java.util.BitSet +import kotlin.math.pow +import kotlin.random.Random + +class CombinationsTest { + + @Test + fun testSpecification() { + val combinations = Combinations(2, 3) + assertArrayEquals(intArrayOf(0, 0), combinations[0]) + assertArrayEquals(intArrayOf(0, 1), combinations[1]) + assertArrayEquals(intArrayOf(0, 2), combinations[2]) + assertArrayEquals(intArrayOf(1, 0), combinations[3]) + assertArrayEquals(intArrayOf(1, 1), combinations[4]) + assertArrayEquals(intArrayOf(1, 2), combinations[5]) + } + + @Test + fun testCartesianProduct() { + val result: List> = CartesianProduct( + listOf( + listOf("x", "y", "z"), + listOf(1.0, 2.0, 3.0) + ) + ).toList() + assertEquals(9, result.size) + assertEquals(listOf("x", 1.0), result[0]) + assertEquals(listOf("x", 2.0), result[1]) + assertEquals(listOf("x", 3.0), result[2]) + assertEquals(listOf("y", 1.0), result[3]) + assertEquals(listOf("y", 2.0), result[4]) + assertEquals(listOf("y", 3.0), result[5]) + assertEquals(listOf("z", 1.0), result[6]) + assertEquals(listOf("z", 2.0), result[7]) + assertEquals(listOf("z", 3.0), result[8]) + } + + @Test + fun testCombinationsIsLazy() { + val combinations = Combinations(2, 2, 2) + val taken = combinations.take(3) + assertArrayEquals(intArrayOf(0, 0, 0), taken[0]) + assertArrayEquals(intArrayOf(0, 0, 1), taken[1]) + assertArrayEquals(intArrayOf(0, 1, 0), taken[2]) + } + + @Test + fun testDecimalNumbers() { + val array = intArrayOf(10, 10, 10) + val combinations = Combinations(*array) + combinations.forEachIndexed { i, c -> + var actual = 0L + for (pos in array.indices) { + actual += c[pos] * (10.0.pow(array.size - 1.0 - pos).toInt()) + } + assertEquals(i.toLong(), actual) + } + } + + @Test + fun testDecimalNumbersByString() { + Combinations(10, 10, 10).forEachIndexed { number, digits -> + assertEquals(String.format("%03d", number), digits.joinToString(separator = "") { it.toString() }) + } + } + + @Test + fun testSomeNumbers() { + val c = Combinations(3, 4, 2) + assertArrayEquals(intArrayOf(0, 3, 0), c[6]) + assertArrayEquals(intArrayOf(1, 0, 1), c[9]) + } + + @Test + fun testZeroValues() { + val c = Combinations() + assertEquals(0, c.size) + } + + @Test + fun testZeroValuesWithException() { + val c = Combinations() + assertThrows(IllegalArgumentException::class.java) { + @Suppress("UNUSED_VARIABLE") val result = c[0] + } + } + + @Test + fun testZeroAsMaxValues() { + assertThrows(java.lang.IllegalArgumentException::class.java) { + Combinations(0, 0, 0) + } + } + + @Test + fun testZeroInTheMiddle() { + assertThrows(IllegalArgumentException::class.java) { + Combinations(2, -1, 3) + } + } + + @ParameterizedTest(name = "testAllLongValues{arguments}") + @ValueSource(ints = [1, 100, Int.MAX_VALUE]) + fun testAllLongValues(value: Int) { + val combinations = Combinations(value, value, 2) + assertEquals(2L * value * value, combinations.size) + val array = combinations[combinations.size - 1] + assertEquals(value - 1, array[0]) + assertEquals(value - 1, array[1]) + assertEquals(1, array[2]) + } + + @Test + fun testCartesianFindsAllValues() { + val radix = 4 + val product = createIntCartesianProduct(radix, 10) + val total = product.estimatedSize + assertTrue(total < Int.MAX_VALUE) { "This test should generate less than Int.MAX_VALUE values but has $total" } + + val set = BitSet((total / 64).toInt()) + val updateSet: (List) -> Unit = { + val value = it.joinToString("").toLong(radix).toInt() + assertFalse(set[value]) + set.set(value) + } + val realCount = product.onEach(updateSet).count() + assertEquals(total, realCount.toLong()) + + for (i in 0 until total) { + assertTrue(set[i.toInt()]) { "Values is not listed for index = $i" } + } + for (i in total until set.size()) { + assertFalse(set[i.toInt()]) + } + } + + /** + * Creates all numbers from 0 to `radix^repeat`. + * + * For example: + * + * radix = 2, repeat = 2 -> {'0', '0'}, {'0', '1'}, {'1', '0'}, {'1', '1'} + * radix = 16, repeat = 1 -> {'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}, {'9'}, {'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'} + */ + private fun createIntCartesianProduct(radix: Int, repeat: Int) = + CartesianProduct( + lists = (1..repeat).map { + Array(radix) { it.toString(radix) }.toList() + }, + random = Random(0) + ).apply { + assertEquals((1L..repeat).fold(1L) { acc, _ -> acc * radix }, estimatedSize) + } + + @Test + fun testCanCreateCartesianProductWithSizeGreaterThanMaxInt() { + val product = createIntCartesianProduct(5, 15) + assertTrue(product.estimatedSize > Int.MAX_VALUE) { "This test should generate more than Int.MAX_VALUE values but has ${product.estimatedSize}" } + assertDoesNotThrow { + product.first() + } + } + + @Test + fun testIterationWithChunksIsCorrect() { + val expected = mutableListOf( + Triple(0L, 5, 7L), + Triple(5L, 5, 2L), + Triple(10L, 2, 0L), + ) + CartesianProduct.forEachChunk(5, 12) { start, chunk, remain -> + assertEquals(expected.removeFirst(), Triple(start, chunk, remain)) + } + assertTrue(expected.isEmpty()) + } + + @Test + fun testIterationWithChunksIsCorrectWhenChunkIsIntMax() { + val total = 12 + val expected = mutableListOf( + Triple(0L, total, 0L) + ) + CartesianProduct.forEachChunk(Int.MAX_VALUE, total.toLong()) { start, chunk, remain -> + assertEquals(expected.removeFirst(), Triple(start, chunk, remain)) + } + assertTrue(expected.isEmpty()) + } + + @ParameterizedTest(name = "testIterationWithChunksIsCorrectWhenChunkIs{arguments}") + @ValueSource(ints = [1, 2, 3, 4, 6, 12]) + fun testIterationWithChunksIsCorrectWhenChunk(chunkSize: Int) { + val total = 12 + assertTrue(total % chunkSize == 0) { "Test requires values that are dividers of the total = $total, but it is not true for $chunkSize" } + val expected = (0 until total step chunkSize).map { it.toLong() }.map { + Triple(it, chunkSize, total - it - chunkSize) + }.toMutableList() + CartesianProduct.forEachChunk(chunkSize, total.toLong()) { start, chunk, remain -> + assertEquals(expected.removeFirst(), Triple(start, chunk, remain)) + } + assertTrue(expected.isEmpty()) + } + + @ParameterizedTest(name = "testIterationsWithChunksThroughLongWithRemainingIs{arguments}") + @ValueSource(longs = [1L, 200L, 307, Int.MAX_VALUE - 1L, Int.MAX_VALUE.toLong()]) + fun testIterationsWithChunksThroughLongTotal(remaining: Long) { + val expected = mutableListOf( + Triple(0L, Int.MAX_VALUE, Int.MAX_VALUE + remaining), + Triple(Int.MAX_VALUE.toLong(), Int.MAX_VALUE, remaining), + Triple(Int.MAX_VALUE * 2L, remaining.toInt(), 0L), + ) + CartesianProduct.forEachChunk(Int.MAX_VALUE, Int.MAX_VALUE * 2L + remaining) { start, chunk, remain -> + assertEquals(expected.removeFirst(), Triple(start, chunk, remain)) + } + assertTrue(expected.isEmpty()) + } + + @Test + fun testCartesianProductDoesNotThrowsExceptionBeforeOverflow() { + // We assume that a standard method has no more than 7 parameters. + // In this case every parameter can accept up to 511 values without Long overflow. + // CartesianProduct throws exception + val values = Array(511) { it }.toList() + val parameters = Array(7) { values }.toList() + assertDoesNotThrow { + CartesianProduct(parameters, Random(0)).asSequence() + } + } + + @Test + fun testCartesianProductThrowsExceptionOnOverflow() { + // We assume that a standard method has no more than 7 parameters. + // In this case every parameter can accept up to 511 values without Long overflow. + // CartesianProduct throws exception + val values = Array(512) { it }.toList() + val parameters = Array(7) { values }.toList() + assertThrows(TooManyCombinationsException::class.java) { + CartesianProduct(parameters, Random(0)).asSequence() + } + } + + @ParameterizedTest(name = "testCombinationHasValue{arguments}") + @ValueSource(ints = [1, Int.MAX_VALUE]) + fun testCombinationHasValue(value: Int) { + val combinations = Combinations(value) + assertEquals(value.toLong(), combinations.size) + assertEquals(value - 1, combinations[value - 1L][0]) + } + + @Test + fun testNoFailWhenMixedValues() { + val combinations = Combinations(2, Int.MAX_VALUE) + assertEquals(2 * Int.MAX_VALUE.toLong(), combinations.size) + assertArrayEquals(intArrayOf(0, 0), combinations[0L]) + assertArrayEquals(intArrayOf(0, Int.MAX_VALUE - 1), combinations[Int.MAX_VALUE - 1L]) + assertArrayEquals(intArrayOf(1, 0), combinations[Int.MAX_VALUE.toLong()]) + assertArrayEquals(intArrayOf(1, 1), combinations[Int.MAX_VALUE + 1L]) + assertArrayEquals(intArrayOf(1, Int.MAX_VALUE - 1), combinations[Int.MAX_VALUE * 2L - 1]) + } + + @Test + fun testLazyCartesian() { + val source = listOf( + (1..10).toList(), + listOf("a", "b", "c"), + listOf(true, false) + ) + val eager = CartesianProduct(source).asSequence().toList() + val lazy = source.cartesian().toList() + assertEquals(eager.size, lazy.size) + for (i in eager.indices) { + assertEquals(eager[i], lazy[i]) + } + } + + @Test + fun testLazyCartesian2() { + val source = listOf( + (1..10).toList(), + listOf("a", "b", "c"), + listOf(true, false) + ) + val eager = CartesianProduct(source).asSequence().toList() + var index = 0 + source.cartesian { + assertEquals(eager[index++], it) + } + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/PseudoShuffledIntProgressionTest.kt b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/PseudoShuffledIntProgressionTest.kt similarity index 98% rename from utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/PseudoShuffledIntProgressionTest.kt rename to utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/PseudoShuffledIntProgressionTest.kt index 6f1f6fd64c..af48867f06 100644 --- a/utbot-fuzzers/src/test/kotlin/org/utbot/framework/plugin/api/PseudoShuffledIntProgressionTest.kt +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/PseudoShuffledIntProgressionTest.kt @@ -1,10 +1,9 @@ -package org.utbot.framework.plugin.api +package org.utbot.fuzzing.utils import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource -import org.utbot.fuzzer.PseudoShuffledIntProgression import kotlin.random.Random class PseudoShuffledIntProgressionTest { diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/RandomExtensionsTest.kt b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/RandomExtensionsTest.kt new file mode 100644 index 0000000000..ab83236b09 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/RandomExtensionsTest.kt @@ -0,0 +1,100 @@ +package org.utbot.fuzzing.utils + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import kotlin.math.abs +import kotlin.random.Random + +class RandomExtensionsTest { + + @Test + fun `default implementation always returns 0`() { + val frequencies = doubleArrayOf(1.0) + (0 until 1000).forEach { _ -> + assertEquals(0, Random.chooseOne(frequencies)) + } + } + + @ParameterizedTest(name = "seed{arguments}") + @ValueSource(ints = [0, 100, -123, 99999, 84]) + fun `with default forEach function frequencies is equal`(seed: Int) { + val random = Random(seed) + val frequencies = doubleArrayOf(10.0, 20.0, 30.0, 40.0) + val result = IntArray(frequencies.size) + assertEquals(100.0, frequencies.sum()) { "In this test every frequency value represents a percent. The sum must be equal to 100" } + val tries = 100_000 + val errors = tries / 100 // 1% + (0 until tries).forEach { _ -> + result[random.chooseOne(frequencies)]++ + } + val expected = frequencies.map { tries * it / 100} + result.forEachIndexed { index, value -> + assertTrue(abs(expected[index] - value) < errors) { + "The error should not extent $errors for $tries cases, but ${expected[index]} and $value too far" + } + } + } + + @Test + fun `inverting probabilities from the documentation`() { + val frequencies = doubleArrayOf(20.0, 80.0) + val random = Random(0) + val result = IntArray(frequencies.size) + val tries = 10_000 + val errors = tries / 100 // 1% + (0 until tries).forEach { _ -> + result[random.chooseOne(DoubleArray(frequencies.size) { 100.0 - frequencies[it] })]++ + } + result.forEachIndexed { index, value -> + val expected = frequencies[frequencies.size - 1 - index] * errors + assertTrue(abs(value - expected) < errors) { + "The error should not extent 100 for 10 000 cases, but $expected and $value too far" + } + } + } + + @Test + fun `flip the coin is fair enough`() { + val random = Random(0) + var result = 0 + val probability = 20 + val experiments = 1_000_000 + for (i in 0 until experiments) { + if (random.flipCoin(probability)) { + result++ + } + } + val error = experiments / 1000 // 0.1 % + assertTrue(abs(result - experiments * probability / 100) < error) + } + + @Test + fun `invert bit works for long`() { + var attempts = 100_000 + val random = Random(2210) + sequence { + while (true) { + yield(random.nextLong()) + } + }.forEach { value -> + if (attempts-- <= 0) { return } + for (bit in 0 until Long.SIZE_BITS) { + val newValue = value.invertBit(bit) + val oldBinary = value.toBinaryString() + val newBinary = newValue.toBinaryString() + assertEquals(oldBinary.length, newBinary.length) + for (test in Long.SIZE_BITS - 1 downTo 0) { + if (test != Long.SIZE_BITS - 1 - bit) { + assertEquals(oldBinary[test], newBinary[test]) { "$oldBinary : $newBinary for value $value" } + } else { + assertNotEquals(oldBinary[test], newBinary[test]) { "$oldBinary : $newBinary for value $value" } + } + } + } + } + } + + private fun Long.toBinaryString() = java.lang.Long.toBinaryString(this).padStart(Long.SIZE_BITS, '0') +} \ No newline at end of file diff --git a/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/TrieTest.kt b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/TrieTest.kt new file mode 100644 index 0000000000..cc3a6ba360 --- /dev/null +++ b/utbot-fuzzing/src/test/kotlin/org/utbot/fuzzing/utils/TrieTest.kt @@ -0,0 +1,128 @@ +package org.utbot.fuzzing.utils + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class TrieTest { + + @Test + fun simpleTest() { + val trie = stringTrieOf() + assertThrows(java.lang.IllegalStateException::class.java) { + trie.add(emptyList()) + } + assertEquals(1, trie.add("Tree").count) + assertEquals(2, trie.add("Tree").count) + assertEquals(1, trie.add("Trees").count) + assertEquals(1, trie.add("Treespss").count) + assertEquals(1, trie.add("Game").count) + assertEquals(1, trie.add("Gamer").count) + assertEquals(1, trie.add("Games").count) + assertEquals(2, trie["Tree"]?.count) + assertEquals(1, trie["Trees"]?.count) + assertEquals(1, trie["Gamer"]?.count) + assertNull(trie["Treesp"]) + assertNull(trie["Treessss"]) + + assertEquals(setOf("Tree", "Trees", "Treespss", "Game", "Gamer", "Games"), trie.collect()) + } + + @Test + fun testSingleElement() { + val trie = trieOf(listOf(1)) + assertEquals(1, trie.toList().size) + } + + @Test + fun testRemoval() { + val trie = stringTrieOf() + trie.add("abc") + assertEquals(1, trie.toList().size) + trie.add("abcd") + assertEquals(2, trie.toList().size) + trie.add("abcd") + assertEquals(2, trie.toList().size) + trie.add("abcde") + assertEquals(3, trie.toList().size) + + assertNotNull(trie.removeCompletely("abcd")) + assertEquals(2, trie.toList().size) + + assertNull(trie.removeCompletely("ffff")) + assertEquals(2, trie.toList().size) + + assertNotNull(trie.removeCompletely("abcde")) + assertEquals(1, trie.toList().size) + + assertNotNull(trie.removeCompletely("abc")) + assertEquals(0, trie.toList().size) + } + + @Test + fun testSearchingAfterDeletion() { + val trie = stringTrieOf("abc", "abc", "abcde") + assertEquals(2, trie.toList().size) + assertEquals(2, trie["abc"]?.count) + + val removed1 = trie.remove("abc") + assertNotNull(removed1) + + val find = trie["abc"] + assertNotNull(find) + assertEquals(1, find!!.count) + + val removed2 = trie.remove("abc") + assertNotNull(removed2) + } + + @Test + fun testTraverse() { + val trie = Trie(Data::id).apply { + add((1..10).map { Data(it.toLong(), it) }) + add((1..10).mapIndexed { index, it -> if (index == 5) Data(3L, it) else Data(it.toLong(), it) }) + } + + val paths = trie.toList() + assertEquals(2, paths.size) + assertNotEquals(paths[0], paths[1]) + } + + @Test + fun testNoDuplications() { + val trie = trieOf( + (1..10), + (1..10), + (1..10), + (1..10), + (1..10), + ) + + assertEquals(1, trie.toList().size) + assertEquals(5, trie[(1..10)]!!.count) + } + + @Test + fun testAcceptsNulls() { + val trie = trieOf( + listOf(null), + listOf(null, null), + listOf(null, null, null), + ) + + assertEquals(3, trie.toList().size) + for (i in 1 .. 3) { + assertEquals(1, trie[(1..i).map { null }]!!.count) + } + } + + @Test + fun testAddPrefixAfterWord() { + val trie = stringTrieOf() + trie.add("Hello, world!") + trie.add("Hello") + + assertEquals(setOf("Hello, world!", "Hello"), trie.collect()) + } + + data class Data(val id: Long, val number: Int) +} \ No newline at end of file diff --git a/utbot-go/build.gradle.kts b/utbot-go/build.gradle.kts new file mode 100644 index 0000000000..3a31276f7b --- /dev/null +++ b/utbot-go/build.gradle.kts @@ -0,0 +1,31 @@ +val intellijPluginVersion: String? by rootProject +val kotlinLoggingVersion: String? by rootProject +val apacheCommonsTextVersion: String? by rootProject +val jacksonVersion: String? by rootProject +val ideType: String? by rootProject +val pythonCommunityPluginVersion: String? by rootProject +val pythonUltimatePluginVersion: String? by rootProject + +tasks { + compileKotlin { + kotlinOptions { + jvmTarget = "17" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class") + allWarningsAsErrors = false + } + } + + test { + useJUnitPlatform() + } +} + +dependencies { + api(project(":utbot-fuzzing")) + api(project(":utbot-framework")) + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + + implementation("com.beust:klaxon:5.5") + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) +} \ No newline at end of file diff --git a/utbot-go/docs/DEVELOPER_GUIDE.md b/utbot-go/docs/DEVELOPER_GUIDE.md new file mode 100644 index 0000000000..86aea31c0d --- /dev/null +++ b/utbot-go/docs/DEVELOPER_GUIDE.md @@ -0,0 +1,61 @@ +# UnitTestBot Go Developer Guide + +## Overview + +Here are the main stages of UnitTestBot Go test generation pipeline. + +```mermaid +flowchart TB + A["Target selection and configuration (IntelliJ IDEA plugin or CLI)"]:::someclass --> B(Go source code analysis) + classDef someclass fill:#b810 + + B --> C(Code instrumentation) + C --> D(Generation of arguments) + D --> E(Executing functions) + E --> F(Getting results) + F --> D + D ---> G(Test file generation) +``` + +### Target selection and configuration + +A user manually selects the target source file and functions to generate the tests for. +Test generation settings are also configured manually. + +### Code instrumentation + +After each line in a function, UnitTestBot Go adds logging about this line traversal during the execution. + +### Go source code analysis + +UnitTestBot Go gains information about the `int` or `uint` value size in bits, and information about the target functions: +* signatures and type information; +* constants in the function bodies. + +As a result, UnitTestBot Go gets an internal +representation of the target functions. + +### Generation of arguments + +At this stage, UnitTestBot Go generates the input values for the target functions. + +### Executing target functions + +UnitTestBot Go executes the target functions with the generated values as arguments. + +### Getting results + +The result of target function execution is sent to the fuzzer for analysis: +coverage rate information guides the following value generation process. +Based on remaining time, +the fuzzer decides whether it should continue or stop generating input values. + +### Test code generation + +Generating test source code requires support for Go language features (for example, the necessity to cast +constants to the desired type). +Extended support for various Go constructs is a future plan for us. + +## How to test UnitTestBot Go + +_**TODO**_ diff --git a/utbot-go/docs/FUTURE_PLANS.md b/utbot-go/docs/FUTURE_PLANS.md new file mode 100644 index 0000000000..d841237f73 --- /dev/null +++ b/utbot-go/docs/FUTURE_PLANS.md @@ -0,0 +1,13 @@ +# UTBot Go: Future plans + +## Primarily + +_**TODO**_ + +## Afterwards + +_**TODO**_ + +## Maybe in the future + +_**TODO**_ diff --git a/utbot-go/docs/images/install-intellij-plugin-from-disk.png b/utbot-go/docs/images/install-intellij-plugin-from-disk.png new file mode 100644 index 0000000000..1f0ceee756 Binary files /dev/null and b/utbot-go/docs/images/install-intellij-plugin-from-disk.png differ diff --git a/utbot-go/go-samples/go.mod b/utbot-go/go-samples/go.mod new file mode 100644 index 0000000000..8f59b4476d --- /dev/null +++ b/utbot-go/go-samples/go.mod @@ -0,0 +1,13 @@ +module go-samples + +go 1.19 + +require ( + github.com/pmezard/go-difflib v1.0.0 + github.com/stretchr/testify v1.8.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/utbot-go/go-samples/go.sum b/utbot-go/go-samples/go.sum new file mode 100644 index 0000000000..2ec90f70f8 --- /dev/null +++ b/utbot-go/go-samples/go.sum @@ -0,0 +1,17 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/utbot-go/go-samples/simple/nested/nested.go b/utbot-go/go-samples/simple/nested/nested.go new file mode 100644 index 0000000000..772d6cc2a6 --- /dev/null +++ b/utbot-go/go-samples/simple/nested/nested.go @@ -0,0 +1,9 @@ +package nested + +type unexportedStruct struct { + int +} + +type ExportedStruct struct { + unexportedStruct +} diff --git a/utbot-go/go-samples/simple/samples.go b/utbot-go/go-samples/simple/samples.go new file mode 100644 index 0000000000..52e337bf63 --- /dev/null +++ b/utbot-go/go-samples/simple/samples.go @@ -0,0 +1,154 @@ +// Most of the algorithm examples are taken from https://github.com/TheAlgorithms/Go + +package simple + +import ( + "errors" + "math" +) + +// DivOrPanic divides x by y or panics if y is 0 +func DivOrPanic(x int, y int) int { + if y == 0 { + panic("div by 0") + } + return x / y +} + +// Extended simple extended gcd +func Extended(a, b int64) (int64, int64, int64) { + if a == 0 { + return b, 0, 1 + } + gcd, xPrime, yPrime := Extended(b%a, a) + return gcd, yPrime - (b/a)*xPrime, xPrime +} + +func ArraySum(array [5]int) int { + sum := 0 + for _, elem := range array { + sum += elem + } + return sum +} + +func GenerateArrayOfIntegers(num int) [10]int { + result := [10]int{} + for i := range result { + result[i] = num + } + return result +} + +type Point struct { + x, y float64 +} + +func DistanceBetweenTwoPoints(a, b Point) float64 { + return math.Sqrt(math.Pow(a.x-b.x, 2) + math.Pow(a.y-b.y, 2)) +} + +func GetCoordinatesOfMiddleBetweenTwoPoints(a, b Point) (float64, float64) { + return (a.x + b.x) / 2, (a.y + b.y) / 2 +} + +func GetCoordinateSumOfPoints(points []Point) (float64, float64) { + sumX := 0.0 + sumY := 0.0 + for _, point := range points { + sumX += point.x + sumY += point.y + } + return sumX, sumY +} + +type Circle struct { + Center Point + Radius float64 +} + +func GetAreaOfCircle(circle Circle) float64 { + return math.Pi * math.Pow(circle.Radius, 2) +} + +func IsIdentity(matrix [3][3]int) bool { + for i := 0; i < 3; i++ { + for j := 0; j < 3; j++ { + if i == j && matrix[i][j] != 1 { + return false + } + + if i != j && matrix[i][j] != 0 { + return false + } + } + } + return true +} + +var ErrNotFound = errors.New("target not found in array") + +// Binary search for target within a sorted array by repeatedly dividing the array in half and comparing the midpoint with the target. +// This function uses recursive call to itself. +// If a target is found, the index of the target is returned. Else the function return -1 and ErrNotFound. +func Binary(array []int, target int, lowIndex int, highIndex int) (int, error) { + if highIndex < lowIndex { + return -1, ErrNotFound + } + mid := lowIndex + (highIndex-lowIndex)/2 + if array[mid] > target { + return Binary(array, target, lowIndex, mid-1) + } else if array[mid] < target { + return Binary(array, target, mid+1, highIndex) + } else { + return mid, nil + } +} + +func StringSearch(str string) bool { + if len(str) != 3 { + return false + } + if str[0] == 'A' { + if str[1] == 'B' { + if str[2] == 'C' { + return true + } + } + } + return false +} + +func SumOfChanElements(c <-chan int) int { + sum := 0 + for val := range c { + sum += val + } + return sum +} + +type List struct { + tail *List + val int +} + +func LenOfList(l *List) int { + if l == nil { + return 0 + } + length := 1 + for ; l.tail != nil; l = l.tail { + length++ + } + return length +} + +func GetLastNode(n *Node) *Node { + if n == nil { + return nil + } + for ; n.next != nil; n = n.next { + + } + return n +} diff --git a/utbot-go/go-samples/simple/samples_go_ut_test.go b/utbot-go/go-samples/simple/samples_go_ut_test.go new file mode 100644 index 0000000000..1c5f5e9986 --- /dev/null +++ b/utbot-go/go-samples/simple/samples_go_ut_test.go @@ -0,0 +1,459 @@ +package simple + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestDivOrPanicByUtGoFuzzer(t *testing.T) { + x := 0 + y := 1 + + actualVal := DivOrPanic(x, y) + + expectedVal := 0 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestDivOrPanicPanicsByUtGoFuzzer(t *testing.T) { + x := 1 + y := 0 + + expectedVal := "div by 0" + + assert.PanicsWithValue(t, expectedVal, func() { + _ = DivOrPanic(x, y) + }) +} + +func TestExtendedByUtGoFuzzer1(t *testing.T) { + a := int64(1) + b := int64(3) + + actualVal0, actualVal1, actualVal2 := Extended(a, b) + + expectedVal0 := int64(1) + expectedVal1 := int64(1) + expectedVal2 := int64(0) + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal0, actualVal0) + assertMultiple.Equal(expectedVal1, actualVal1) + assertMultiple.Equal(expectedVal2, actualVal2) +} + +func TestExtendedByUtGoFuzzer2(t *testing.T) { + a := int64(0) + b := int64(0) + + actualVal0, actualVal1, actualVal2 := Extended(a, b) + + expectedVal0 := int64(0) + expectedVal1 := int64(0) + expectedVal2 := int64(1) + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal0, actualVal0) + assertMultiple.Equal(expectedVal1, actualVal1) + assertMultiple.Equal(expectedVal2, actualVal2) +} + +func TestArraySumByUtGoFuzzer(t *testing.T) { + array := [5]int{1, 1, 0, 0, 0} + + actualVal := ArraySum(array) + + expectedVal := 2 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestGenerateArrayOfIntegersByUtGoFuzzer(t *testing.T) { + num := 0 + + actualVal := GenerateArrayOfIntegers(num) + + expectedVal := [10]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestDistanceBetweenTwoPointsByUtGoFuzzer(t *testing.T) { + a := Point{x: 2.0, y: 2.0} + b := Point{x: 2.0, y: 2.0} + + actualVal := DistanceBetweenTwoPoints(a, b) + + expectedVal := 0.0 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestGetCoordinatesOfMiddleBetweenTwoPointsByUtGoFuzzer(t *testing.T) { + a := Point{x: 2.0, y: 2.0} + b := Point{x: 2.0, y: 2.0} + + actualVal0, actualVal1 := GetCoordinatesOfMiddleBetweenTwoPoints(a, b) + + expectedVal0 := 2.0 + expectedVal1 := 2.0 + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal0, actualVal0) + assertMultiple.Equal(expectedVal1, actualVal1) +} + +func TestGetCoordinateSumOfPointsByUtGoFuzzer1(t *testing.T) { + points := ([]Point)(nil) + + actualVal0, actualVal1 := GetCoordinateSumOfPoints(points) + + expectedVal0 := 0.0 + expectedVal1 := 0.0 + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal0, actualVal0) + assertMultiple.Equal(expectedVal1, actualVal1) +} + +func TestGetCoordinateSumOfPointsByUtGoFuzzer2(t *testing.T) { + points := []Point{{}} + + actualVal0, actualVal1 := GetCoordinateSumOfPoints(points) + + expectedVal0 := 0.0 + expectedVal1 := 0.0 + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal0, actualVal0) + assertMultiple.Equal(expectedVal1, actualVal1) +} + +func TestGetAreaOfCircleByUtGoFuzzer(t *testing.T) { + circle := Circle{Center: Point{x: 2.0, y: 2.0}, Radius: 2.0} + + actualVal := GetAreaOfCircle(circle) + + expectedVal := 12.566370614359172 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestIsIdentityByUtGoFuzzer1(t *testing.T) { + matrix := [3][3]int{{3, 0, 3}, {1, 0, 1}, {0, 0, 0}} + + actualVal := IsIdentity(matrix) + + assert.False(t, actualVal) +} + +func TestIsIdentityByUtGoFuzzer2(t *testing.T) { + matrix := [3][3]int{{1, 3, 0}, {0, 3, 0}, {0, 0, 3}} + + actualVal := IsIdentity(matrix) + + assert.False(t, actualVal) +} + +func TestIsIdentityByUtGoFuzzer3(t *testing.T) { + matrix := [3][3]int{{1, 0, 0}, {0, 0, 3}, {0, 3, 1}} + + actualVal := IsIdentity(matrix) + + assert.False(t, actualVal) +} + +func TestIsIdentityByUtGoFuzzer4(t *testing.T) { + matrix := [3][3]int{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}} + + actualVal := IsIdentity(matrix) + + assert.True(t, actualVal) +} + +func TestBinaryWithNonNilErrorByUtGoFuzzer1(t *testing.T) { + array := ([]int)(nil) + target := 2 + lowIndex := 2 + highIndex := 1 + + actualVal, actualErr := Binary(array, target, lowIndex, highIndex) + + expectedVal := -1 + expectedErrorMessage := "target not found in array" + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal, actualVal) + assertMultiple.ErrorContains(actualErr, expectedErrorMessage) +} + +func TestBinaryWithNonNilErrorByUtGoFuzzer2(t *testing.T) { + array := []int{1} + target := 2 + lowIndex := 0 + highIndex := 0 + + actualVal, actualErr := Binary(array, target, lowIndex, highIndex) + + expectedVal := -1 + expectedErrorMessage := "target not found in array" + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal, actualVal) + assertMultiple.ErrorContains(actualErr, expectedErrorMessage) +} + +func TestBinaryWithNonNilErrorByUtGoFuzzer3(t *testing.T) { + array := []int{1} + target := 0 + lowIndex := 0 + highIndex := 0 + + actualVal, actualErr := Binary(array, target, lowIndex, highIndex) + + expectedVal := -1 + expectedErrorMessage := "target not found in array" + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal, actualVal) + assertMultiple.ErrorContains(actualErr, expectedErrorMessage) +} + +func TestBinaryWithNonNilErrorByUtGoFuzzer4(t *testing.T) { + array := []int{1, 33554433} + target := 16385 + lowIndex := 0 + highIndex := 1 + + actualVal, actualErr := Binary(array, target, lowIndex, highIndex) + + expectedVal := -1 + expectedErrorMessage := "target not found in array" + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal, actualVal) + assertMultiple.ErrorContains(actualErr, expectedErrorMessage) +} + +func TestBinaryByUtGoFuzzer5(t *testing.T) { + array := []int{0} + target := 0 + lowIndex := 0 + highIndex := 0 + + actualVal, actualErr := Binary(array, target, lowIndex, highIndex) + + expectedVal := 0 + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal, actualVal) + assertMultiple.Nil(actualErr) +} + +func TestBinaryByUtGoFuzzer6(t *testing.T) { + array := []int{1, 65} + target := 1 + lowIndex := 0 + highIndex := 2 + + actualVal, actualErr := Binary(array, target, lowIndex, highIndex) + + expectedVal := 0 + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal, actualVal) + assertMultiple.Nil(actualErr) +} + +func TestBinaryByUtGoFuzzer7(t *testing.T) { + array := []int{0, 1, 2} + target := 1 + lowIndex := -1 + highIndex := 2 + + actualVal, actualErr := Binary(array, target, lowIndex, highIndex) + + expectedVal := 1 + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal, actualVal) + assertMultiple.Nil(actualErr) +} + +func TestBinaryByUtGoFuzzer8(t *testing.T) { + array := []int{-9151296850630803456, 0, 70} + target := 0 + lowIndex := -1 + highIndex := 5 + + actualVal, actualErr := Binary(array, target, lowIndex, highIndex) + + expectedVal := 1 + + assertMultiple := assert.New(t) + assertMultiple.Equal(expectedVal, actualVal) + assertMultiple.Nil(actualErr) +} + +func TestBinaryPanicsByUtGoFuzzer1(t *testing.T) { + array := ([]int)(nil) + target := 0 + lowIndex := 1 + highIndex := 1 + + expectedErrorMessage := "runtime error: index out of range [1] with length 0" + + assert.PanicsWithError(t, expectedErrorMessage, func() { + _, _ = Binary(array, target, lowIndex, highIndex) + }) +} + +func TestBinaryPanicsByUtGoFuzzer2(t *testing.T) { + array := []int{2} + target := 1 + lowIndex := -1 + highIndex := 1 + + expectedErrorMessage := "runtime error: index out of range [-1]" + + assert.PanicsWithError(t, expectedErrorMessage, func() { + _, _ = Binary(array, target, lowIndex, highIndex) + }) +} + +func TestBinaryPanicsByUtGoFuzzer3(t *testing.T) { + array := []int{0} + target := 2 + lowIndex := 0 + highIndex := 1 + + expectedErrorMessage := "runtime error: index out of range [1] with length 1" + + assert.PanicsWithError(t, expectedErrorMessage, func() { + _, _ = Binary(array, target, lowIndex, highIndex) + }) +} + +func TestStringSearchByUtGoFuzzer1(t *testing.T) { + str := "" + + actualVal := StringSearch(str) + + assert.False(t, actualVal) +} + +func TestStringSearchByUtGoFuzzer2(t *testing.T) { + str := "￸" + + actualVal := StringSearch(str) + + assert.False(t, actualVal) +} + +func TestStringSearchByUtGoFuzzer3(t *testing.T) { + str := "Aa." + + actualVal := StringSearch(str) + + assert.False(t, actualVal) +} + +func TestStringSearchByUtGoFuzzer4(t *testing.T) { + str := "AB~" + + actualVal := StringSearch(str) + + assert.False(t, actualVal) +} + +func TestStringSearchByUtGoFuzzer5(t *testing.T) { + str := "ABC" + + actualVal := StringSearch(str) + + assert.True(t, actualVal) +} + +func TestSumOfChanElementsByUtGoFuzzer1(t *testing.T) { + c := make(chan int, 1) + c <- 0 + close(c) + + actualVal := SumOfChanElements(c) + + expectedVal := 0 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestSumOfChanElementsByUtGoFuzzer2(t *testing.T) { + c := make(chan int, 0) + close(c) + + actualVal := SumOfChanElements(c) + + expectedVal := 0 + + assert.Equal(t, expectedVal, actualVal) +} + +// SumOfChanElements((<-chan int)(nil)) exceeded 1000 ms timeout + +func TestLenOfListByUtGoFuzzer1(t *testing.T) { + l := (*List)(nil) + + actualVal := LenOfList(l) + + expectedVal := 0 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestLenOfListByUtGoFuzzer2(t *testing.T) { + l := &List{val: 1} + + actualVal := LenOfList(l) + + expectedVal := 1 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestLenOfListByUtGoFuzzer3(t *testing.T) { + l := &List{tail: &List{}} + + actualVal := LenOfList(l) + + expectedVal := 2 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestGetLastNodeByUtGoFuzzer1(t *testing.T) { + n := (*Node)(nil) + + actualVal := GetLastNode(n) + + assert.Nil(t, actualVal) +} + +func TestGetLastNodeByUtGoFuzzer2(t *testing.T) { + n := &Node{val: 1} + + actualVal := GetLastNode(n) + + expectedVal := &Node{val: 1} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestGetLastNodeByUtGoFuzzer3(t *testing.T) { + n := &Node{next: &Node{}} + + actualVal := GetLastNode(n) + + expectedVal := &Node{} + + assert.Equal(t, expectedVal, actualVal) +} diff --git a/utbot-go/go-samples/simple/supported_types.go b/utbot-go/go-samples/simple/supported_types.go new file mode 100644 index 0000000000..ebd0142206 --- /dev/null +++ b/utbot-go/go-samples/simple/supported_types.go @@ -0,0 +1,425 @@ +package simple + +import ( + "errors" + "fmt" + "github.com/pmezard/go-difflib/difflib" + dif "github.com/pmezard/go-difflib/difflib" + "go-samples/simple/nested" + "math" +) + +func WithoutParametersAndReturnValues() { + print("Hello World") +} + +func Int(n int) int { + if n < 0 { + return -n + } + return n +} + +func Int8(n int8) int8 { + if n < 0 { + return -n + } + return n +} + +func Int16(n int16) int16 { + if n < 0 { + return -n + } + return n +} + +func Int32(n int32) int32 { + if n < 0 { + return -n + } + return n +} + +func Int64(n int64) int64 { + if n < 0 { + return -n + } + return n +} + +func Uint(n uint) uint { + return n +} + +func Uint8(n uint8) uint8 { + return n +} + +func Uint16(n uint16) uint16 { + return n +} + +func Uint32(n uint32) uint32 { + return n +} + +func Uint64(n uint64) uint64 { + return n +} + +func UintPtr(n uintptr) uintptr { + return n +} + +func Float32(n float32) float32 { + if math.IsInf(float64(n), 1) { + return n + } else if math.IsInf(float64(n), -1) { + return n + } else if math.IsNaN(float64(n)) { + return n + } + return n +} + +func Float64(n float64) float64 { + if math.IsInf(n, 1) { + return n + } else if math.IsInf(n, -1) { + return n + } else if math.IsNaN(n) { + return n + } + return n +} + +func Complex64(n complex64) complex64 { + return n +} + +func Complex128(n complex128) complex128 { + return n +} + +func Byte(n byte) byte { + return n +} + +func Rune(n rune) rune { + return n +} + +func String(n string) string { + return n +} + +func Bool(n bool) bool { + return n +} + +type Structure struct { + int + int8 + int16 + int32 + int64 + uint + uint8 + uint16 + uint32 + uint64 + uintptr + float32 + float64 + complex64 + complex128 + byte + rune + string + bool +} + +func Struct(s Structure) Structure { + return s +} + +func StructWithNan(s Structure) Structure { + s.float64 = math.NaN() + return s +} + +func ArrayOfInt(array [10]int) [10]int { + return array +} + +func ArrayOfUintPtr(array [10]uintptr) [10]uintptr { + return array +} + +func ArrayOfString(array [10]string) [10]string { + return array +} + +func ArrayOfStructs(array [10]Structure) [10]Structure { + return array +} + +func ArrayOfStructsWithNan(array [10]Structure) [10]Structure { + array[0].float64 = math.NaN() + return array +} + +func ArrayOfArrayOfUint(array [5][5]uint) [5][5]uint { + return array +} + +func ArrayOfArrayOfStructs(array [5][5]Structure) [5][5]Structure { + return array +} + +func ArrayOfSliceOfUint(array [5][]uint) [5][]uint { + return array +} + +func returnErrorOrNil(n int) error { + if n > 0 { + return errors.New("error") + } else { + return nil + } +} + +func ExternalStruct(match difflib.Match, structure Structure) Structure { + return structure +} + +func ExternalStructWithAlias(match dif.Match) difflib.Match { + return match +} + +func SliceOfInt(slice []int) []int { + if slice == nil { + return slice + } + return slice +} + +func SliceOfUintPtr(slice []uintptr) []uintptr { + if slice == nil { + return slice + } + return slice +} + +func SliceOfString(slice []string) []string { + if slice == nil { + return slice + } + return slice +} + +func SliceOfStructs(slice []Structure) []Structure { + if slice == nil { + return slice + } + return slice +} + +func SliceOfStructsWithNan(slice []Structure) []Structure { + if slice == nil { + return slice + } + slice[0].float64 = math.NaN() + return slice +} + +func SliceOfSliceOfByte(slice [][]byte) [][]byte { + if slice == nil { + return slice + } + return slice +} + +func SliceOfSliceOfStructs(slice [][]Structure) [][]Structure { + if slice == nil { + return slice + } + return slice +} + +func SliceOfArrayOfInt(slice [][5]int) [][5]int { + if slice == nil { + return slice + } + return slice +} + +func ExportedStructWithEmbeddedUnexportedStruct(exportedStruct nested.ExportedStruct) nested.ExportedStruct { + return exportedStruct +} + +type Type byte + +func NamedType(n Type) Type { + return n +} + +func ArrayOfNamedType(array [5]Type) [5]Type { + return array +} + +type T [5][5]Type + +func ArrayOfArrayOfNamedType(array [5][5]Type) T { + return array +} + +func SliceOfNamedType(slice []Type) []Type { + if slice == nil { + return slice + } + return slice +} + +type NA [5]uintptr + +func NamedArray(array NA) NA { + return array +} + +type NS []int + +func NamedSlice(slice NS) NS { + if slice == nil { + return slice + } + return slice +} + +type S struct { + t Type + T + n NA + NS +} + +func StructWithFieldsOfNamedTypes(s S) S { + return s +} + +func Map(table map[string]int) map[string]int { + return table +} + +func MapOfStructures(table map[Structure]Structure) map[Structure]Structure { + return table +} + +func MapOfSliceOfInt(table map[string][]int) map[string][]int { + return table +} + +func MapOfNamedType(table map[int]Type) map[int]Type { + return table +} + +func MapOfNamedSlice(table map[uint]NS) map[uint]NS { + return table +} + +type NM map[string]NA + +func NamedMap(n NM) NM { + return n +} + +func Channel(c chan Structure) { + if c == nil { + return + } +} + +func SendOnlyChannel(c chan<- int) { + if c == nil { + return + } +} + +func RecvOnlyChannel(c <-chan NM) { + if c == nil { + return + } +} + +func PointerToInt(n *int) *int { + if n == nil { + return n + } + return n +} + +func PointerToSlice(n *[]int) *[]int { + if n == nil { + return n + } + return n +} + +func PointerToArray(n *[3]int) *[3]int { + if n == nil { + return n + } + return n +} + +func PointerToMap(n *map[string]int) *map[string]int { + if n == nil { + return n + } + return n +} + +func PointerToStructure(n *Structure) *Structure { + if n == nil { + return n + } + return n +} + +func PointerToNamedType(n *Type) *Type { + if n == nil { + return n + } + return n +} + +type Node struct { + prev, next *Node + val int +} + +func PointerToRecursiveStruct(n *Node) *Node { + if n == nil { + return n + } + return n +} + +type I interface { + String() string +} + +func Interface(i I) { + if i != nil { + return + } + return +} + +func ExternalInterface(i fmt.Stringer) { + if i != nil { + return + } + return +} diff --git a/utbot-go/go-samples/simple/supported_types_go_ut_test.go b/utbot-go/go-samples/simple/supported_types_go_ut_test.go new file mode 100644 index 0000000000..3a5fcf35b5 --- /dev/null +++ b/utbot-go/go-samples/simple/supported_types_go_ut_test.go @@ -0,0 +1,941 @@ +package simple + +import ( + "fmt" + "github.com/pmezard/go-difflib/difflib" + "github.com/stretchr/testify/assert" + "go-samples/simple/nested" + "math" + "testing" + "time" +) + +func TestWithoutParametersAndReturnValuesByUtGoFuzzer(t *testing.T) { + assert.NotPanics(t, func() { + WithoutParametersAndReturnValues() + }) +} + +func TestIntByUtGoFuzzer1(t *testing.T) { + n := 4 + + actualVal := Int(n) + + expectedVal := 4 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestIntByUtGoFuzzer2(t *testing.T) { + n := -1 + + actualVal := Int(n) + + expectedVal := 1 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestInt8ByUtGoFuzzer1(t *testing.T) { + n := int8(1) + + actualVal := Int8(n) + + expectedVal := int8(1) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestInt8ByUtGoFuzzer2(t *testing.T) { + n := int8(-3) + + actualVal := Int8(n) + + expectedVal := int8(3) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestInt16ByUtGoFuzzer1(t *testing.T) { + n := int16(2) + + actualVal := Int16(n) + + expectedVal := int16(2) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestInt16ByUtGoFuzzer2(t *testing.T) { + n := int16(-9) + + actualVal := Int16(n) + + expectedVal := int16(9) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestInt32ByUtGoFuzzer1(t *testing.T) { + n := int32(2) + + actualVal := Int32(n) + + expectedVal := int32(2) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestInt32ByUtGoFuzzer2(t *testing.T) { + n := int32(-3) + + actualVal := Int32(n) + + expectedVal := int32(3) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestInt64ByUtGoFuzzer1(t *testing.T) { + n := int64(-1) + + actualVal := Int64(n) + + expectedVal := int64(1) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestInt64ByUtGoFuzzer2(t *testing.T) { + n := int64(1) + + actualVal := Int64(n) + + expectedVal := int64(1) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestUintByUtGoFuzzer(t *testing.T) { + n := uint(5) + + actualVal := Uint(n) + + expectedVal := uint(5) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestUint8ByUtGoFuzzer(t *testing.T) { + n := uint8(2) + + actualVal := Uint8(n) + + expectedVal := uint8(2) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestUint16ByUtGoFuzzer(t *testing.T) { + n := uint16(1) + + actualVal := Uint16(n) + + expectedVal := uint16(1) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestUint32ByUtGoFuzzer(t *testing.T) { + n := uint32(8) + + actualVal := Uint32(n) + + expectedVal := uint32(8) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestUint64ByUtGoFuzzer(t *testing.T) { + n := uint64(4) + + actualVal := Uint64(n) + + expectedVal := uint64(4) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestUintPtrByUtGoFuzzer(t *testing.T) { + n := uintptr(4) + + actualVal := UintPtr(n) + + expectedVal := uintptr(4) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestFloat32ByUtGoFuzzer1(t *testing.T) { + n := float32(0.143228) + + actualVal := Float32(n) + + expectedVal := float32(0.143228) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestFloat32ByUtGoFuzzer2(t *testing.T) { + n := float32(math.Inf(-1)) + + actualVal := Float32(n) + + assert.True(t, math.IsInf(float64(actualVal), -1)) +} + +func TestFloat32ByUtGoFuzzer3(t *testing.T) { + n := float32(math.Inf(1)) + + actualVal := Float32(n) + + assert.True(t, math.IsInf(float64(actualVal), 1)) +} + +func TestFloat64ByUtGoFuzzer1(t *testing.T) { + n := 0.61258062787271 + + actualVal := Float64(n) + + expectedVal := 0.61258062787271 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestFloat64ByUtGoFuzzer2(t *testing.T) { + n := math.Inf(1) + + actualVal := Float64(n) + + assert.True(t, math.IsInf(actualVal, 1)) +} + +func TestFloat64ByUtGoFuzzer3(t *testing.T) { + n := math.Inf(-1) + + actualVal := Float64(n) + + assert.True(t, math.IsInf(actualVal, -1)) +} + +func TestComplex64ByUtGoFuzzer(t *testing.T) { + n := complex(float32(math.Inf(1)), float32(0.415752)) + + actualVal := Complex64(n) + + expectedVal := complex(float32(math.Inf(1)), float32(0.415752)) + + assertMultiple := assert.New(t) + assertMultiple.Equal(real(expectedVal), real(actualVal)) + assertMultiple.Equal(imag(expectedVal), imag(actualVal)) +} + +func TestComplex128ByUtGoFuzzer(t *testing.T) { + n := complex(0.2112353749298962, math.Inf(1)) + + actualVal := Complex128(n) + + expectedVal := complex(0.2112353749298962, math.Inf(1)) + + assertMultiple := assert.New(t) + assertMultiple.Equal(real(expectedVal), real(actualVal)) + assertMultiple.Equal(imag(expectedVal), imag(actualVal)) +} + +func TestByteByUtGoFuzzer(t *testing.T) { + n := byte(1) + + actualVal := Byte(n) + + expectedVal := byte(1) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestRuneByUtGoFuzzer(t *testing.T) { + n := rune(1) + + actualVal := Rune(n) + + expectedVal := rune(1) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestStringByUtGoFuzzer(t *testing.T) { + n := "" + + actualVal := String(n) + + expectedVal := "" + + assert.Equal(t, expectedVal, actualVal) +} + +func TestBoolByUtGoFuzzer(t *testing.T) { + n := true + + actualVal := Bool(n) + + assert.True(t, actualVal) +} + +func TestStructByUtGoFuzzer(t *testing.T) { + s := Structure{int32: int32(1), int64: int64(1), uint8: uint8(1), uint16: uint16(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.4714877), float64: 0.501834850205735, complex64: complex(float32(0.4714877), float32(0.4714877)), complex128: complex(0.501834850205735, 0.501834850205735), byte: byte(1)} + + actualVal := Struct(s) + + expectedVal := Structure{int32: int32(1), int64: int64(1), uint8: uint8(1), uint16: uint16(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.4714877), float64: 0.501834850205735, complex64: complex(float32(0.4714877), float32(0.4714877)), complex128: complex(0.501834850205735, 0.501834850205735), byte: byte(1)} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestStructWithNanByUtGoFuzzer(t *testing.T) { + s := Structure{int8: int8(1), int16: int16(-1), int32: int32(-1), int64: int64(1), uint8: uint8(1), uint16: uint16(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.63904), float64: 0.2555253108964435, complex64: complex(float32(0.63904), float32(0.63904)), complex128: complex(0.2555253108964435, 0.2555253108964435), byte: byte(255), rune: rune(-1)} + + actualVal := StructWithNan(s) + + expectedVal := Structure{int8: int8(1), int16: int16(-1), int32: int32(-1), int64: int64(1), uint8: uint8(1), uint16: uint16(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.63904), float64: math.NaN(), complex64: complex(float32(0.63904), float32(0.63904)), complex128: complex(0.2555253108964435, 0.2555253108964435), byte: byte(255), rune: rune(-1)} + + assert.NotEqual(t, expectedVal, actualVal) +} + +func TestArrayOfIntByUtGoFuzzer(t *testing.T) { + array := [10]int{0, 1, 1, 0, 1, 1, 0, 64, 0, 1} + + actualVal := ArrayOfInt(array) + + expectedVal := [10]int{0, 1, 1, 0, 1, 1, 0, 64, 0, 1} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestArrayOfUintPtrByUtGoFuzzer(t *testing.T) { + array := [10]uintptr{1, 0, 1, 0, 1, 1, 0, 0, 1, 0} + + actualVal := ArrayOfUintPtr(array) + + expectedVal := [10]uintptr{1, 0, 1, 0, 1, 1, 0, 0, 1, 0} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestArrayOfStringByUtGoFuzzer(t *testing.T) { + array := [10]string{"", "", "", "", "", "", "", "", "", ""} + + actualVal := ArrayOfString(array) + + expectedVal := [10]string{"", "", "", "", "", "", "", "", "", ""} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestArrayOfStructsByUtGoFuzzer(t *testing.T) { + array := [10]Structure{{int8: int8(-1), int16: int16(-1), int32: int32(1), uint: uint(1), uint8: uint8(255), uint16: uint16(1), uint32: uint32(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(1), rune: rune(1), string: "hello"}, {int: -9223372036854775808, int8: int8(-1), int16: int16(1), int32: int32(1), int64: int64(-1), uint8: uint8(1), uint16: uint16(1), uint32: uint32(1), uint64: uint64(18446744073709551615), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(1), string: "hello"}, {int: -1, int8: int8(1), int16: int16(-1), int32: int32(-1), int64: int64(1), uint: uint(18446744073709551615), uint32: uint32(1), uint64: uint64(1), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(255), rune: rune(1), string: "hello", bool: true}, {int: 9223372036854775807, int8: int8(1), int16: int16(-1), int32: int32(-1), int64: int64(-9223372036854775808), uint8: uint8(1), uint16: uint16(65535), uint32: uint32(4294967295), uintptr: uintptr(1), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(1)}, {int: -9223372036854775808, int8: int8(1), int16: int16(-1), int32: int32(-1), int64: int64(-1), uint: uint(1), uint8: uint8(1), uint32: uint32(1), uintptr: uintptr(18446744073709551615), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(1), rune: rune(-2147483648)}, {int: -1, int16: int16(1), int32: int32(1), int64: int64(1), uint: uint(1), uint8: uint8(1), uint16: uint16(1), uint32: uint32(4294967295), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(255), rune: rune(1), string: "hello", bool: true}, {int: -9223372036854775808, int16: int16(1), int64: int64(1), uint: uint(1), uint8: uint8(1), uint16: uint16(1), uint32: uint32(1), uintptr: uintptr(1), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(1), rune: rune(2147483647), string: "hello"}, {int: 1, int8: int8(1), int16: int16(32767), int32: int32(2147483647), int64: int64(-9223372036854775808), uint: uint(1), uint8: uint8(255), uint16: uint16(65535), uint32: uint32(1), uint64: uint64(1), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(255), rune: rune(-1), string: "hello", bool: true}, {int8: int8(127), int64: int64(-1), uint: uint(1), uint64: uint64(1), uintptr: uintptr(18446744073709551615), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), string: "hello"}, {int: -9223372036854775808, int8: int8(-1), int16: int16(32767), int32: int32(1), int64: int64(-1), uint: uint(18446744073709551615), uint8: uint8(255), uint32: uint32(1), uintptr: uintptr(18446744073709551615), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), rune: rune(2147483647), bool: true}} + + actualVal := ArrayOfStructs(array) + + expectedVal := [10]Structure{{int8: int8(-1), int16: int16(-1), int32: int32(1), uint: uint(1), uint8: uint8(255), uint16: uint16(1), uint32: uint32(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(1), rune: rune(1), string: "hello"}, {int: -9223372036854775808, int8: int8(-1), int16: int16(1), int32: int32(1), int64: int64(-1), uint8: uint8(1), uint16: uint16(1), uint32: uint32(1), uint64: uint64(18446744073709551615), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(1), string: "hello"}, {int: -1, int8: int8(1), int16: int16(-1), int32: int32(-1), int64: int64(1), uint: uint(18446744073709551615), uint32: uint32(1), uint64: uint64(1), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(255), rune: rune(1), string: "hello", bool: true}, {int: 9223372036854775807, int8: int8(1), int16: int16(-1), int32: int32(-1), int64: int64(-9223372036854775808), uint8: uint8(1), uint16: uint16(65535), uint32: uint32(4294967295), uintptr: uintptr(1), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(1)}, {int: -9223372036854775808, int8: int8(1), int16: int16(-1), int32: int32(-1), int64: int64(-1), uint: uint(1), uint8: uint8(1), uint32: uint32(1), uintptr: uintptr(18446744073709551615), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(1), rune: rune(-2147483648)}, {int: -1, int16: int16(1), int32: int32(1), int64: int64(1), uint: uint(1), uint8: uint8(1), uint16: uint16(1), uint32: uint32(4294967295), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(255), rune: rune(1), string: "hello", bool: true}, {int: -9223372036854775808, int16: int16(1), int64: int64(1), uint: uint(1), uint8: uint8(1), uint16: uint16(1), uint32: uint32(1), uintptr: uintptr(1), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(1), rune: rune(2147483647), string: "hello"}, {int: 1, int8: int8(1), int16: int16(32767), int32: int32(2147483647), int64: int64(-9223372036854775808), uint: uint(1), uint8: uint8(255), uint16: uint16(65535), uint32: uint32(1), uint64: uint64(1), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), byte: byte(255), rune: rune(-1), string: "hello", bool: true}, {int8: int8(127), int64: int64(-1), uint: uint(1), uint64: uint64(1), uintptr: uintptr(18446744073709551615), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), string: "hello"}, {int: -9223372036854775808, int8: int8(-1), int16: int16(32767), int32: int32(1), int64: int64(-1), uint: uint(18446744073709551615), uint8: uint8(255), uint32: uint32(1), uintptr: uintptr(18446744073709551615), float32: float32(0.5316085), float64: 0.3025369185032576, complex64: complex(float32(0.5316085), float32(0.5316085)), complex128: complex(0.3025369185032576, 0.3025369185032576), rune: rune(2147483647), bool: true}} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestArrayOfStructsWithNanByUtGoFuzzer(t *testing.T) { + array := [10]Structure{{int: -9223372036854775808, int8: int8(-128), int16: int16(-1), int32: int32(-1), int64: int64(-9223372036854775808), uint: uint(18446744073709551615), uint8: uint8(1), uint32: uint32(1), uintptr: uintptr(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(255), rune: rune(-2147483648), bool: true}, {int: -1, int16: int16(-32768), int32: int32(2147483647), uint8: uint8(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754)}, {int: -1, int16: int16(-1), int64: int64(1), uint8: uint8(1), uint32: uint32(4294967295), uintptr: uintptr(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(255), rune: rune(2147483647), string: "hello", bool: true}, {int16: int16(-1), uint: uint(18446744073709551615), uint16: uint16(65535), uint32: uint32(4294967295), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(1), rune: rune(-2147483648)}, {int8: int8(1), int16: int16(-1), int32: int32(2147483647), uint: uint(18446744073709551615), uint32: uint32(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(255), rune: rune(1)}, {int32: int32(-1), int64: int64(1), uint8: uint8(1), uint16: uint16(1), uintptr: uintptr(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), rune: rune(2147483647)}, {int: -9223372036854775808, int8: int8(-128), int16: int16(32767), int64: int64(1), uint: uint(18446744073709551615), uint16: uint16(65535), uint32: uint32(1), uint64: uint64(1), uintptr: uintptr(18446744073709551615), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(255), rune: rune(-1), bool: true}, {int: 1, int8: int8(127), int16: int16(32767), int32: int32(1), int64: int64(-1), uint: uint(18446744073709551615), uint8: uint8(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(1), rune: rune(2147483647), string: "hello", bool: true}, {int: 1, int8: int8(127), int32: int32(1), uint: uint(18446744073709551615), uint8: uint8(1), uint64: uint64(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(255), rune: rune(1), string: "hello"}, {int: -9223372036854775808, int16: int16(32767), int32: int32(-2147483648), int64: int64(-1), uint: uint(1), uint16: uint16(65535), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), rune: rune(1)}} + + actualVal := ArrayOfStructsWithNan(array) + + expectedVal := [10]Structure{{int: -9223372036854775808, int8: int8(-128), int16: int16(-1), int32: int32(-1), int64: int64(-9223372036854775808), uint: uint(18446744073709551615), uint8: uint8(1), uint32: uint32(1), uintptr: uintptr(1), float32: float32(0.9086392), float64: math.NaN(), complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(255), rune: rune(-2147483648), bool: true}, {int: -1, int16: int16(-32768), int32: int32(2147483647), uint8: uint8(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754)}, {int: -1, int16: int16(-1), int64: int64(1), uint8: uint8(1), uint32: uint32(4294967295), uintptr: uintptr(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(255), rune: rune(2147483647), string: "hello", bool: true}, {int16: int16(-1), uint: uint(18446744073709551615), uint16: uint16(65535), uint32: uint32(4294967295), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(1), rune: rune(-2147483648)}, {int8: int8(1), int16: int16(-1), int32: int32(2147483647), uint: uint(18446744073709551615), uint32: uint32(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(255), rune: rune(1)}, {int32: int32(-1), int64: int64(1), uint8: uint8(1), uint16: uint16(1), uintptr: uintptr(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), rune: rune(2147483647)}, {int: -9223372036854775808, int8: int8(-128), int16: int16(32767), int64: int64(1), uint: uint(18446744073709551615), uint16: uint16(65535), uint32: uint32(1), uint64: uint64(1), uintptr: uintptr(18446744073709551615), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(255), rune: rune(-1), bool: true}, {int: 1, int8: int8(127), int16: int16(32767), int32: int32(1), int64: int64(-1), uint: uint(18446744073709551615), uint8: uint8(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(1), rune: rune(2147483647), string: "hello", bool: true}, {int: 1, int8: int8(127), int32: int32(1), uint: uint(18446744073709551615), uint8: uint8(1), uint64: uint64(1), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), byte: byte(255), rune: rune(1), string: "hello"}, {int: -9223372036854775808, int16: int16(32767), int32: int32(-2147483648), int64: int64(-1), uint: uint(1), uint16: uint16(65535), float32: float32(0.9086392), float64: 0.977378020386754, complex64: complex(float32(0.9086392), float32(0.9086392)), complex128: complex(0.977378020386754, 0.977378020386754), rune: rune(1)}} + + assert.NotEqual(t, expectedVal, actualVal) +} + +func TestArrayOfArrayOfUintByUtGoFuzzer(t *testing.T) { + array := [5][5]uint{{0, 1, 0, 1, 1}, {1, 1, 0, 0, 0}, {1, 1, 0, 0, 0}, {0, 1, 0, 0, 18446744073709551615}, {1, 35184372088832, 1, 1, 0}} + + actualVal := ArrayOfArrayOfUint(array) + + expectedVal := [5][5]uint{{0, 1, 0, 1, 1}, {1, 1, 0, 0, 0}, {1, 1, 0, 0, 0}, {0, 1, 0, 0, 18446744073709551615}, {1, 35184372088832, 1, 1, 0}} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestArrayOfArrayOfStructsByUtGoFuzzer(t *testing.T) { + array := [5][5]Structure{{{int: -9223372036854775808, int8: int8(-128), int16: int16(-32768), int32: int32(2147483647), int64: int64(1), uint64: uint64(18446744073709551615), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(1), rune: rune(1)}, {int8: int8(-1), int16: int16(-32768), int64: int64(9223372036854775807), uint8: uint8(255), uint16: uint16(65535), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(255), rune: rune(-1), bool: true}, {int: 1, int8: int8(-1), int16: int16(32767), int32: int32(2147483647), uint: uint(18446744073709551615), uint8: uint8(1), uint16: uint16(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(1), rune: rune(1), bool: true}, {int8: int8(127), int16: int16(-1), int32: int32(-1), int64: int64(-1), uint: uint(18446744073709551615), uint8: uint8(1), uint32: uint32(4294967295), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(255), rune: rune(-2147483648)}, {int: 1, int8: int8(-128), int16: int16(32767), int64: int64(-1), uint32: uint32(4294967295), uint64: uint64(1), uintptr: uintptr(18446744073709551615), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), rune: rune(-1), string: "hello"}}, {{int: -9223372036854775808, int8: int8(127), int16: int16(-1), int32: int32(1), int64: int64(1), uint: uint(18446744073709551615), uint8: uint8(255), uint16: uint16(65535), uint64: uint64(18446744073709551615), uintptr: uintptr(18446744073709551615), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), rune: rune(1), string: "hello"}, {int: -1, int8: int8(127), int16: int16(-1), int32: int32(2147483647), int64: int64(9223372036854775807), uint: uint(18446744073709551615), uint8: uint8(255), uint16: uint16(65535), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(1), rune: rune(-2147483648), bool: true}, {int: -9223372036854775808, int8: int8(-128), int16: int16(32767), int32: int32(1), int64: int64(1), uint: uint(1), uint16: uint16(65535), uint32: uint32(4294967295), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(255), rune: rune(-1), bool: true}, {int: -1, int8: int8(1), int32: int32(-2147483648), int64: int64(1), uint: uint(18446744073709551615), uint8: uint8(255), uint16: uint16(65535), uint32: uint32(1), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), string: "hello"}, {int: 1, int8: int8(-1), int16: int16(32767), int64: int64(-9223372036854775808), uint: uint(18446744073709551615), uint8: uint8(255), uint16: uint16(65535), uint32: uint32(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(1), rune: rune(2147483647), string: "hello", bool: true}}, {{int: -9223372036854775808, int8: int8(1), int16: int16(1), int64: int64(1), uint: uint(1), uint16: uint16(65535), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(255), rune: rune(1)}, {int: 1, int8: int8(127), int16: int16(-1), int32: int32(1), int64: int64(-9223372036854775808), uint: uint(1), uint8: uint8(255), uint16: uint16(1), uint32: uint32(4294967295), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), rune: rune(2147483647), string: "hello"}, {int: 1, int8: int8(-128), int32: int32(-1), int64: int64(-1), uint: uint(18446744073709551615), uint32: uint32(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(1), rune: rune(2147483647), string: "hello"}, {int: -1, int16: int16(1), int32: int32(1), uint: uint(1), uint16: uint16(65535), uint32: uint32(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0))}, {int8: int8(-1), int16: int16(32767), int32: int32(-2147483648), int64: int64(-1), uint: uint(1), uint8: uint8(255), uint16: uint16(1), uint32: uint32(4294967295), uint64: uint64(1), uintptr: uintptr(18446744073709551615), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), rune: rune(1), bool: true}}, {{int: -1, int8: int8(1), int16: int16(1), int32: int32(2147483647), int64: int64(-1), uint: uint(1), uint32: uint32(4294967295), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(1), bool: true}, {int: -9223372036854775808, int16: int16(32767), int32: int32(-1), int64: int64(9223372036854775807), uint16: uint16(65535), uint32: uint32(4294967295), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(1), rune: rune(2147483647)}, {int: 1, int8: int8(127), int16: int16(1), int32: int32(-2147483648), int64: int64(-1), uint: uint(18446744073709551615), uint8: uint8(255), uint16: uint16(1), uint64: uint64(1), uintptr: uintptr(18446744073709551615), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(1), bool: true}, {int: -9223372036854775808, int16: int16(1), int32: int32(1), uint: uint(1), uint8: uint8(1), uint16: uint16(65535), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(1), rune: rune(1), string: "hello", bool: true}, {int: -1, int8: int8(1), int16: int16(-32768), int32: int32(2147483647), int64: int64(1), uint16: uint16(65535), uint32: uint32(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(1), rune: rune(-1), string: "hello"}}, {{int: -1, int8: int8(-1), int16: int16(-1), int32: int32(-2147483648), int64: int64(9223372036854775807), uint: uint(1), uint8: uint8(255), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), rune: rune(-2147483648), string: "hello", bool: true}, {int: 1, int8: int8(-128), int16: int16(32767), int32: int32(2147483647), uint8: uint8(255), uint32: uint32(4294967295), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(1), rune: rune(1), bool: true}, {int16: int16(32767), int32: int32(2147483647), int64: int64(-1), uint32: uint32(1), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(255), rune: rune(-2147483648), string: "hello", bool: true}, {int: 1, int8: int8(-128), int16: int16(1), int32: int32(-2147483648), int64: int64(1), uint8: uint8(255), uint16: uint16(1), uint32: uint32(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), byte: byte(255), rune: rune(1)}, {int: 9223372036854775807, int8: int8(1), int16: int16(-32768), int32: int32(-2147483648), int64: int64(-9223372036854775808), uint8: uint8(1), uint32: uint32(4294967295), uint64: uint64(1), uintptr: uintptr(18446744073709551615), float32: float32(0.33522367), float64: 0.003913530617220884, complex64: complex(float32(0.0), float32(0.0)), string: "hello", bool: true}}} + + actualVal := ArrayOfArrayOfStructs(array) + + expectedVal := [5][5]Structure{{{int: -9223372036854775808, int8: int8(-128), int16: int16(-32768), int32: int32(2147483647), int64: int64(1), uint64: uint64(18446744073709551615), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(1), rune: rune(1)}, {int8: int8(-1), int16: int16(-32768), int64: int64(9223372036854775807), uint8: uint8(255), uint16: uint16(65535), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(255), rune: rune(-1), bool: true}, {int: 1, int8: int8(-1), int16: int16(32767), int32: int32(2147483647), uint: uint(18446744073709551615), uint8: uint8(1), uint16: uint16(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(1), rune: rune(1), bool: true}, {int8: int8(127), int16: int16(-1), int32: int32(-1), int64: int64(-1), uint: uint(18446744073709551615), uint8: uint8(1), uint32: uint32(4294967295), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(255), rune: rune(-2147483648)}, {int: 1, int8: int8(-128), int16: int16(32767), int64: int64(-1), uint32: uint32(4294967295), uint64: uint64(1), uintptr: uintptr(18446744073709551615), float32: float32(0.33522367), float64: 0.003913530617220884, rune: rune(-1), string: "hello"}}, {{int: -9223372036854775808, int8: int8(127), int16: int16(-1), int32: int32(1), int64: int64(1), uint: uint(18446744073709551615), uint8: uint8(255), uint16: uint16(65535), uint64: uint64(18446744073709551615), uintptr: uintptr(18446744073709551615), float32: float32(0.33522367), float64: 0.003913530617220884, rune: rune(1), string: "hello"}, {int: -1, int8: int8(127), int16: int16(-1), int32: int32(2147483647), int64: int64(9223372036854775807), uint: uint(18446744073709551615), uint8: uint8(255), uint16: uint16(65535), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(1), rune: rune(-2147483648), bool: true}, {int: -9223372036854775808, int8: int8(-128), int16: int16(32767), int32: int32(1), int64: int64(1), uint: uint(1), uint16: uint16(65535), uint32: uint32(4294967295), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(255), rune: rune(-1), bool: true}, {int: -1, int8: int8(1), int32: int32(-2147483648), int64: int64(1), uint: uint(18446744073709551615), uint8: uint8(255), uint16: uint16(65535), uint32: uint32(1), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, string: "hello"}, {int: 1, int8: int8(-1), int16: int16(32767), int64: int64(-9223372036854775808), uint: uint(18446744073709551615), uint8: uint8(255), uint16: uint16(65535), uint32: uint32(1), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(1), rune: rune(2147483647), string: "hello", bool: true}}, {{int: -9223372036854775808, int8: int8(1), int16: int16(1), int64: int64(1), uint: uint(1), uint16: uint16(65535), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(255), rune: rune(1)}, {int: 1, int8: int8(127), int16: int16(-1), int32: int32(1), int64: int64(-9223372036854775808), uint: uint(1), uint8: uint8(255), uint16: uint16(1), uint32: uint32(4294967295), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, rune: rune(2147483647), string: "hello"}, {int: 1, int8: int8(-128), int32: int32(-1), int64: int64(-1), uint: uint(18446744073709551615), uint32: uint32(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(1), rune: rune(2147483647), string: "hello"}, {int: -1, int16: int16(1), int32: int32(1), uint: uint(1), uint16: uint16(65535), uint32: uint32(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884}, {int8: int8(-1), int16: int16(32767), int32: int32(-2147483648), int64: int64(-1), uint: uint(1), uint8: uint8(255), uint16: uint16(1), uint32: uint32(4294967295), uint64: uint64(1), uintptr: uintptr(18446744073709551615), float32: float32(0.33522367), float64: 0.003913530617220884, rune: rune(1), bool: true}}, {{int: -1, int8: int8(1), int16: int16(1), int32: int32(2147483647), int64: int64(-1), uint: uint(1), uint32: uint32(4294967295), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(1), bool: true}, {int: -9223372036854775808, int16: int16(32767), int32: int32(-1), int64: int64(9223372036854775807), uint16: uint16(65535), uint32: uint32(4294967295), uint64: uint64(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(1), rune: rune(2147483647)}, {int: 1, int8: int8(127), int16: int16(1), int32: int32(-2147483648), int64: int64(-1), uint: uint(18446744073709551615), uint8: uint8(255), uint16: uint16(1), uint64: uint64(1), uintptr: uintptr(18446744073709551615), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(1), bool: true}, {int: -9223372036854775808, int16: int16(1), int32: int32(1), uint: uint(1), uint8: uint8(1), uint16: uint16(65535), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(1), rune: rune(1), string: "hello", bool: true}, {int: -1, int8: int8(1), int16: int16(-32768), int32: int32(2147483647), int64: int64(1), uint16: uint16(65535), uint32: uint32(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(1), rune: rune(-1), string: "hello"}}, {{int: -1, int8: int8(-1), int16: int16(-1), int32: int32(-2147483648), int64: int64(9223372036854775807), uint: uint(1), uint8: uint8(255), float32: float32(0.33522367), float64: 0.003913530617220884, rune: rune(-2147483648), string: "hello", bool: true}, {int: 1, int8: int8(-128), int16: int16(32767), int32: int32(2147483647), uint8: uint8(255), uint32: uint32(4294967295), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(1), rune: rune(1), bool: true}, {int16: int16(32767), int32: int32(2147483647), int64: int64(-1), uint32: uint32(1), uint64: uint64(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(255), rune: rune(-2147483648), string: "hello", bool: true}, {int: 1, int8: int8(-128), int16: int16(1), int32: int32(-2147483648), int64: int64(1), uint8: uint8(255), uint16: uint16(1), uint32: uint32(1), uintptr: uintptr(1), float32: float32(0.33522367), float64: 0.003913530617220884, byte: byte(255), rune: rune(1)}, {int: 9223372036854775807, int8: int8(1), int16: int16(-32768), int32: int32(-2147483648), int64: int64(-9223372036854775808), uint8: uint8(1), uint32: uint32(4294967295), uint64: uint64(1), uintptr: uintptr(18446744073709551615), float32: float32(0.33522367), float64: 0.003913530617220884, string: "hello", bool: true}}} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestArrayOfSliceOfUintByUtGoFuzzer(t *testing.T) { + array := [5][]uint{nil, nil, nil, nil, nil} + + actualVal := ArrayOfSliceOfUint(array) + + expectedVal := [5][]uint{nil, nil, nil, nil, nil} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestReturnErrorOrNilWithNonNilErrorByUtGoFuzzer1(t *testing.T) { + n := 4 + + actualErr := returnErrorOrNil(n) + + expectedErrorMessage := "error" + + assert.ErrorContains(t, actualErr, expectedErrorMessage) +} + +func TestReturnErrorOrNilByUtGoFuzzer2(t *testing.T) { + n := 0 + + actualErr := returnErrorOrNil(n) + + assert.Nil(t, actualErr) +} + +func TestExternalStructByUtGoFuzzer(t *testing.T) { + match := difflib.Match{B: 1, Size: -1} + structure := Structure{int8: int8(1), int16: int16(32767), int32: int32(-1), int64: int64(1), uint: uint(1), uint8: uint8(1), uint16: uint16(1), uint32: uint32(1), uintptr: uintptr(1), float32: float32(0.37985808), float64: 0.4084964347010259, complex64: complex(float32(0.37985808), float32(0.37985808)), complex128: complex(0.4084964347010259, 0.4084964347010259), byte: byte(1), rune: rune(1), string: "hello"} + + actualVal := ExternalStruct(match, structure) + + expectedVal := Structure{int8: int8(1), int16: int16(32767), int32: int32(-1), int64: int64(1), uint: uint(1), uint8: uint8(1), uint16: uint16(1), uint32: uint32(1), uintptr: uintptr(1), float32: float32(0.37985808), float64: 0.4084964347010259, complex64: complex(float32(0.37985808), float32(0.37985808)), complex128: complex(0.4084964347010259, 0.4084964347010259), byte: byte(1), rune: rune(1), string: "hello"} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestExternalStructWithAliasByUtGoFuzzer(t *testing.T) { + match := difflib.Match{A: 1, Size: 1} + + actualVal := ExternalStructWithAlias(match) + + expectedVal := difflib.Match{A: 1, Size: 1} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestSliceOfIntByUtGoFuzzer1(t *testing.T) { + slice := ([]int)(nil) + + actualVal := SliceOfInt(slice) + + assert.Nil(t, actualVal) +} + +func TestSliceOfIntByUtGoFuzzer2(t *testing.T) { + slice := []int{} + + actualVal := SliceOfInt(slice) + + expectedVal := []int{} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestSliceOfUintPtrByUtGoFuzzer1(t *testing.T) { + slice := []uintptr{} + + actualVal := SliceOfUintPtr(slice) + + expectedVal := []uintptr{} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestSliceOfUintPtrByUtGoFuzzer2(t *testing.T) { + slice := ([]uintptr)(nil) + + actualVal := SliceOfUintPtr(slice) + + assert.Nil(t, actualVal) +} + +func TestSliceOfStringByUtGoFuzzer1(t *testing.T) { + slice := ([]string)(nil) + + actualVal := SliceOfString(slice) + + assert.Nil(t, actualVal) +} + +func TestSliceOfStringByUtGoFuzzer2(t *testing.T) { + slice := []string{} + + actualVal := SliceOfString(slice) + + expectedVal := []string{} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestSliceOfStructsByUtGoFuzzer1(t *testing.T) { + slice := ([]Structure)(nil) + + actualVal := SliceOfStructs(slice) + + assert.Nil(t, actualVal) +} + +func TestSliceOfStructsByUtGoFuzzer2(t *testing.T) { + slice := []Structure{} + + actualVal := SliceOfStructs(slice) + + expectedVal := []Structure{} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestSliceOfStructsWithNanByUtGoFuzzer1(t *testing.T) { + slice := ([]Structure)(nil) + + actualVal := SliceOfStructsWithNan(slice) + + assert.Nil(t, actualVal) +} + +func TestSliceOfStructsWithNanByUtGoFuzzer2(t *testing.T) { + slice := []Structure{{int8: int8(-1), int32: int32(1), uint8: uint8(1), uint16: uint16(1), uint32: uint32(1), uint64: uint64(1), float32: float32(0.6586717), float64: 0.5295327756124881, complex64: complex(float32(0.6586717), float32(0.6586717)), complex128: complex(0.5295327756124881, 0.5295327756124881), rune: rune(-1)}} + + actualVal := SliceOfStructsWithNan(slice) + + expectedVal := []Structure{{int8: int8(-1), int32: int32(1), uint8: uint8(1), uint16: uint16(1), uint32: uint32(1), uint64: uint64(1), float32: float32(0.6586717), float64: math.NaN(), complex64: complex(float32(0.6586717), float32(0.6586717)), complex128: complex(0.5295327756124881, 0.5295327756124881), rune: rune(-1)}} + + assert.NotEqual(t, expectedVal, actualVal) +} + +func TestSliceOfStructsWithNanPanicsByUtGoFuzzer(t *testing.T) { + slice := []Structure{} + + expectedErrorMessage := "runtime error: index out of range [0] with length 0" + + assert.PanicsWithError(t, expectedErrorMessage, func() { + _ = SliceOfStructsWithNan(slice) + }) +} + +func TestSliceOfSliceOfByteByUtGoFuzzer1(t *testing.T) { + slice := [][]byte{} + + actualVal := SliceOfSliceOfByte(slice) + + expectedVal := [][]byte{} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestSliceOfSliceOfByteByUtGoFuzzer2(t *testing.T) { + slice := ([][]byte)(nil) + + actualVal := SliceOfSliceOfByte(slice) + + assert.Nil(t, actualVal) +} + +func TestSliceOfSliceOfStructsByUtGoFuzzer1(t *testing.T) { + slice := [][]Structure{} + + actualVal := SliceOfSliceOfStructs(slice) + + expectedVal := [][]Structure{} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestSliceOfSliceOfStructsByUtGoFuzzer2(t *testing.T) { + slice := ([][]Structure)(nil) + + actualVal := SliceOfSliceOfStructs(slice) + + assert.Nil(t, actualVal) +} + +func TestSliceOfArrayOfIntByUtGoFuzzer1(t *testing.T) { + slice := ([][5]int)(nil) + + actualVal := SliceOfArrayOfInt(slice) + + assert.Nil(t, actualVal) +} + +func TestSliceOfArrayOfIntByUtGoFuzzer2(t *testing.T) { + slice := [][5]int{} + + actualVal := SliceOfArrayOfInt(slice) + + expectedVal := [][5]int{} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestExportedStructWithEmbeddedUnexportedStructByUtGoFuzzer(t *testing.T) { + exportedStruct := nested.ExportedStruct{} + + actualVal := ExportedStructWithEmbeddedUnexportedStruct(exportedStruct) + + expectedVal := nested.ExportedStruct{} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestNamedTypeByUtGoFuzzer(t *testing.T) { + n := Type(1) + + actualVal := NamedType(n) + + expectedVal := Type(1) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestArrayOfNamedTypeByUtGoFuzzer(t *testing.T) { + array := [5]Type{0, 0, 0, 1, 0} + + actualVal := ArrayOfNamedType(array) + + expectedVal := [5]Type{0, 0, 0, 1, 0} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestArrayOfArrayOfNamedTypeByUtGoFuzzer(t *testing.T) { + array := [5][5]Type{{255, 1, 1, 0, 1}, {0, 0, 0, 0, 0}, {1, 0, 0, 0, 1}, {1, 1, 255, 1, 0}, {1, 1, 0, 0, 0}} + + actualVal := ArrayOfArrayOfNamedType(array) + + expectedVal := T{{255, 1, 1, 0, 1}, {0, 0, 0, 0, 0}, {1, 0, 0, 0, 1}, {1, 1, 255, 1, 0}, {1, 1, 0, 0, 0}} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestSliceOfNamedTypeByUtGoFuzzer1(t *testing.T) { + slice := ([]Type)(nil) + + actualVal := SliceOfNamedType(slice) + + assert.Nil(t, actualVal) +} + +func TestSliceOfNamedTypeByUtGoFuzzer2(t *testing.T) { + slice := []Type{} + + actualVal := SliceOfNamedType(slice) + + expectedVal := []Type{} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestNamedArrayByUtGoFuzzer(t *testing.T) { + array := NA{1, 1, 0, 1, 0} + + actualVal := NamedArray(array) + + expectedVal := NA{1, 1, 0, 1, 0} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestNamedSliceByUtGoFuzzer1(t *testing.T) { + slice := NS{} + + actualVal := NamedSlice(slice) + + expectedVal := NS{} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestNamedSliceByUtGoFuzzer2(t *testing.T) { + slice := NS(nil) + + actualVal := NamedSlice(slice) + + assert.Nil(t, actualVal) +} + +func TestStructWithFieldsOfNamedTypesByUtGoFuzzer(t *testing.T) { + s := S{n: NA{1, 0, 1, 0, 1}} + + actualVal := StructWithFieldsOfNamedTypes(s) + + expectedVal := S{n: NA{1, 0, 1, 0, 1}} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestMapByUtGoFuzzer(t *testing.T) { + table := (map[string]int)(nil) + + actualVal := Map(table) + + assert.Nil(t, actualVal) +} + +func TestMapOfStructuresByUtGoFuzzer(t *testing.T) { + table := (map[Structure]Structure)(nil) + + actualVal := MapOfStructures(table) + + assert.Nil(t, actualVal) +} + +func TestMapOfSliceOfIntByUtGoFuzzer(t *testing.T) { + table := (map[string][]int)(nil) + + actualVal := MapOfSliceOfInt(table) + + assert.Nil(t, actualVal) +} + +func TestMapOfNamedTypeByUtGoFuzzer(t *testing.T) { + table := (map[int]Type)(nil) + + actualVal := MapOfNamedType(table) + + assert.Nil(t, actualVal) +} + +func TestMapOfNamedSliceByUtGoFuzzer(t *testing.T) { + table := (map[uint]NS)(nil) + + actualVal := MapOfNamedSlice(table) + + assert.Nil(t, actualVal) +} + +func TestNamedMapByUtGoFuzzer(t *testing.T) { + n := NM(nil) + + actualVal := NamedMap(n) + + assert.Nil(t, actualVal) +} + +func TestChannelByUtGoFuzzer1(t *testing.T) { + c := make(chan Structure, 0) + close(c) + + assert.NotPanics(t, func() { + Channel(c) + }) +} + +func TestChannelByUtGoFuzzer2(t *testing.T) { + c := (chan Structure)(nil) + + assert.NotPanics(t, func() { + Channel(c) + }) +} + +func TestSendOnlyChannelByUtGoFuzzer1(t *testing.T) { + c := (chan<- int)(nil) + + assert.NotPanics(t, func() { + SendOnlyChannel(c) + }) +} + +func TestSendOnlyChannelByUtGoFuzzer2(t *testing.T) { + c := make(chan int, 0) + close(c) + + assert.NotPanics(t, func() { + SendOnlyChannel(c) + }) +} + +func TestRecvOnlyChannelByUtGoFuzzer1(t *testing.T) { + c := make(chan NM, 0) + close(c) + + assert.NotPanics(t, func() { + RecvOnlyChannel(c) + }) +} + +func TestRecvOnlyChannelByUtGoFuzzer2(t *testing.T) { + c := (<-chan NM)(nil) + + assert.NotPanics(t, func() { + RecvOnlyChannel(c) + }) +} + +func TestPointerToIntByUtGoFuzzer1(t *testing.T) { + n := (*int)(nil) + + actualVal := PointerToInt(n) + + assert.Nil(t, actualVal) +} + +func TestPointerToIntByUtGoFuzzer2(t *testing.T) { + n := new(int) + *n = 0 + + actualVal := PointerToInt(n) + + expectedVal := new(int) + *expectedVal = 0 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestPointerToSliceByUtGoFuzzer1(t *testing.T) { + n := (*[]int)(nil) + + actualVal := PointerToSlice(n) + + assert.Nil(t, actualVal) +} + +func TestPointerToSliceByUtGoFuzzer2(t *testing.T) { + n := new([]int) + + actualVal := PointerToSlice(n) + + expectedVal := new([]int) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestPointerToArrayByUtGoFuzzer1(t *testing.T) { + n := &[3]int{0, 1, 1} + + actualVal := PointerToArray(n) + + expectedVal := &[3]int{0, 1, 1} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestPointerToArrayByUtGoFuzzer2(t *testing.T) { + n := (*[3]int)(nil) + + actualVal := PointerToArray(n) + + assert.Nil(t, actualVal) +} + +func TestPointerToMapByUtGoFuzzer1(t *testing.T) { + n := (*map[string]int)(nil) + + actualVal := PointerToMap(n) + + assert.Nil(t, actualVal) +} + +func TestPointerToMapByUtGoFuzzer2(t *testing.T) { + n := new(map[string]int) + + actualVal := PointerToMap(n) + + expectedVal := new(map[string]int) + + assert.Equal(t, expectedVal, actualVal) +} + +func TestPointerToStructureByUtGoFuzzer1(t *testing.T) { + n := &Structure{int8: int8(-128), int16: int16(1), int64: int64(-1), uint: uint(1), uint8: uint8(255), float32: float32(0.5587146), float64: 0.745147167878201, complex64: complex(float32(0.5587146), float32(0.5587146)), complex128: complex(0.745147167878201, 0.745147167878201), byte: byte(1)} + + actualVal := PointerToStructure(n) + + expectedVal := &Structure{int8: int8(-128), int16: int16(1), int64: int64(-1), uint: uint(1), uint8: uint8(255), float32: float32(0.5587146), float64: 0.745147167878201, complex64: complex(float32(0.5587146), float32(0.5587146)), complex128: complex(0.745147167878201, 0.745147167878201), byte: byte(1)} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestPointerToStructureByUtGoFuzzer2(t *testing.T) { + n := (*Structure)(nil) + + actualVal := PointerToStructure(n) + + assert.Nil(t, actualVal) +} + +func TestPointerToNamedTypeByUtGoFuzzer1(t *testing.T) { + n := new(Type) + *n = 1 + + actualVal := PointerToNamedType(n) + + expectedVal := new(Type) + *expectedVal = 1 + + assert.Equal(t, expectedVal, actualVal) +} + +func TestPointerToNamedTypeByUtGoFuzzer2(t *testing.T) { + n := (*Type)(nil) + + actualVal := PointerToNamedType(n) + + assert.Nil(t, actualVal) +} + +func TestPointerToRecursiveStructByUtGoFuzzer1(t *testing.T) { + n := &Node{val: 4} + + actualVal := PointerToRecursiveStruct(n) + + expectedVal := &Node{val: 4} + + assert.Equal(t, expectedVal, actualVal) +} + +func TestPointerToRecursiveStructByUtGoFuzzer2(t *testing.T) { + n := (*Node)(nil) + + actualVal := PointerToRecursiveStruct(n) + + assert.Nil(t, actualVal) +} + +func TestInterfaceByUtGoFuzzer1(t *testing.T) { + i := I(nil) + + assert.NotPanics(t, func() { + Interface(i) + }) +} + +func TestInterfaceByUtGoFuzzer2(t *testing.T) { + i := time.Duration(1) + + assert.NotPanics(t, func() { + Interface(i) + }) +} + +func TestExternalInterfaceByUtGoFuzzer1(t *testing.T) { + i := time.Duration(1) + + assert.NotPanics(t, func() { + ExternalInterface(i) + }) +} + +func TestExternalInterfaceByUtGoFuzzer2(t *testing.T) { + i := fmt.Stringer(nil) + + assert.NotPanics(t, func() { + ExternalInterface(i) + }) +} diff --git a/utbot-go/src/main/kotlin/org/utbot/go/GoEngine.kt b/utbot-go/src/main/kotlin/org/utbot/go/GoEngine.kt new file mode 100644 index 0000000000..0f2393f466 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/GoEngine.kt @@ -0,0 +1,138 @@ +package org.utbot.go + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.launch +import mu.KotlinLogging +import org.utbot.fuzzing.BaseFeedback +import org.utbot.fuzzing.Control +import org.utbot.fuzzing.utils.Trie +import org.utbot.go.api.* +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.logic.TestsGenerationMode +import org.utbot.go.worker.GoWorker +import org.utbot.go.worker.convertRawExecutionResultToExecutionResult +import java.net.SocketException +import java.net.SocketTimeoutException +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + +val logger = KotlinLogging.logger {} + +class GoEngine( + private var workers: List, + private val functionUnderTest: GoUtFunction, + private val needToCoverLines: Set, + private val aliases: Map, + private val functionExecutionTimeoutMillis: Long, + private val mode: TestsGenerationMode, + private val timeoutExceededOrIsCanceled: () -> Boolean +) { + var numberOfFunctionExecutions: AtomicInteger = AtomicInteger(0) + + fun fuzzing(): Flow> = channelFlow { + if (!functionUnderTest.isMethod && functionUnderTest.parameters.isEmpty()) { + val lengthOfParameters = workers[0].sendFuzzedParametersValues(functionUnderTest, emptyList(), emptyMap()) + val (executionResult, coverTab) = run { + val rawExecutionResult = workers[0].receiveRawExecutionResult() + numberOfFunctionExecutions.incrementAndGet() + convertRawExecutionResultToExecutionResult( + rawExecutionResult = rawExecutionResult, + functionResultTypes = functionUnderTest.results.map { it.type }, + timeoutMillis = functionExecutionTimeoutMillis, + ) to rawExecutionResult.coverTab + } + val fuzzedFunction = GoUtFuzzedFunction(functionUnderTest, emptyList()) + val testCase = GoUtFuzzedFunctionTestCase( + fuzzedFunction, executionResult + ) + send(mapOf(CoveredLines(coverTab.keys) to ExecutionResults(testCase, lengthOfParameters))) + } else { + val needToStop = AtomicBoolean() + workers.mapIndexed { index, worker -> + launch(Dispatchers.IO) { + val testCases = mutableMapOf() + try { + fuzzingProcessRoutine(needToStop, testCases, worker, index) + } finally { + send(testCases) + } + } + } + } + } + + private suspend fun fuzzingProcessRoutine( + needToStop: AtomicBoolean, + testCases: MutableMap, + worker: GoWorker, + index: Int + ) { + var attempts = 0 + val attemptsLimit = Int.MAX_VALUE + runGoFuzzing(functionUnderTest, worker, index) { description, values -> + try { + if (needToStop.get() || timeoutExceededOrIsCanceled()) { + return@runGoFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.STOP) + } + + val lengthOfParameters = + description.worker.sendFuzzedParametersValues(functionUnderTest, values, aliases) + val (executionResult, coverTab) = run { + val rawExecutionResult = description.worker.receiveRawExecutionResult() + numberOfFunctionExecutions.incrementAndGet() + convertRawExecutionResultToExecutionResult( + rawExecutionResult = rawExecutionResult, + functionResultTypes = functionUnderTest.results.map { it.type }, + timeoutMillis = functionExecutionTimeoutMillis, + ) to rawExecutionResult.coverTab + } + + if (coverTab.isEmpty()) { + logger.error { "Coverage is empty for [${functionUnderTest.name}] with $values}" } + return@runGoFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.PASS) + } + + val coveredLines = CoveredLines(needToCoverLines.intersect(coverTab.keys)) + val fuzzedFunction = GoUtFuzzedFunction(functionUnderTest, values) + val testCase = GoUtFuzzedFunctionTestCase(fuzzedFunction, executionResult) + if (mode != TestsGenerationMode.FUZZING_MODE) { + if (testCases[coveredLines] == null) { + testCases[coveredLines] = ExecutionResults(testCase, lengthOfParameters) + } else { + testCases[coveredLines]!!.update(testCase, lengthOfParameters) + } + } + + val trieNode = description.coverage.add(coveredLines.lines.sorted()) + if (executionResult is GoUtTimeoutExceeded) { + description.worker.restartWorker() + return@runGoFuzzing BaseFeedback(result = trieNode, control = Control.PASS) + } + if (trieNode.count > 1) { + if (++attempts >= attemptsLimit) { + return@runGoFuzzing BaseFeedback( + result = Trie.emptyNode(), + control = Control.STOP + ) + } + return@runGoFuzzing BaseFeedback(result = trieNode, control = Control.CONTINUE) + } + + if (mode == TestsGenerationMode.FUZZING_MODE && executionResult !is GoUtExecutionSuccess) { + needToStop.set(true) + testCases[coveredLines] = ExecutionResults(testCase, lengthOfParameters) + return@runGoFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.STOP) + } + BaseFeedback(result = trieNode, control = Control.CONTINUE) + } catch (e: SocketTimeoutException) { + description.worker.restartWorker() + return@runGoFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.PASS) + } catch (e: SocketException) { + description.worker.restartWorker() + return@runGoFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.PASS) + } + } + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/GoLanguage.kt b/utbot-go/src/main/kotlin/org/utbot/go/GoLanguage.kt new file mode 100644 index 0000000000..99459ffb02 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/GoLanguage.kt @@ -0,0 +1,61 @@ +package org.utbot.go + +import org.utbot.fuzzing.* +import org.utbot.fuzzing.utils.IdentityTrie +import org.utbot.fuzzing.utils.Trie +import org.utbot.go.api.GoUtFunction +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel +import org.utbot.go.fuzzer.providers.* +import org.utbot.go.worker.GoWorker +import kotlin.random.Random + + +fun goDefaultValueProviders() = listOf( + GoPrimitivesValueProvider, + GoArrayValueProvider, + GoSliceValueProvider, + GoMapValueProvider, + GoChanValueProvider, + GoStructValueProvider, + GoConstantValueProvider, + GoNamedValueProvider, + GoNilValueProvider, + GoPointerValueProvider, + GoInterfaceValueProvider +) + +data class CoveredLines(val lines: Set) + +class GoDescription( + val worker: GoWorker, + val functionUnderTest: GoUtFunction, + val coverage: Trie, + val configuration: Configuration, +) : Description( + if (functionUnderTest.isMethod) { + listOf(functionUnderTest.receiver!!.type) + functionUnderTest.parameters.map { it.type }.toList() + } else { + functionUnderTest.parameters.map { it.type }.toList() + } +) + +suspend fun runGoFuzzing( + function: GoUtFunction, + worker: GoWorker, + index: Int, + providers: List> = goDefaultValueProviders(), + exec: suspend (description: GoDescription, values: List) -> BaseFeedback, GoTypeId, GoUtModel> +) { + val config = Configuration() + BaseFuzzing(providers, exec).fuzz( + description = GoDescription( + worker = worker, + functionUnderTest = function, + coverage = IdentityTrie(), + configuration = config + ), + random = Random(index), + configuration = config, + ) +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/api/GoTypesApi.kt b/utbot-go/src/main/kotlin/org/utbot/go/api/GoTypesApi.kt new file mode 100644 index 0000000000..9244f8194f --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/api/GoTypesApi.kt @@ -0,0 +1,238 @@ +package org.utbot.go.api + +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.framework.api.go.GoTypeId + +/** + * Represents real Go primitive type. + */ +class GoPrimitiveTypeId(name: String) : GoTypeId(name) { + override val canonicalName: String = when (name) { + "byte" -> "uint8" + "rune" -> "int32" + else -> name + } + + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String = name + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoPrimitiveTypeId) return false + + return canonicalName == other.canonicalName + } + + override fun hashCode(): Int = name.hashCode() +} + +/** + * Class for Go struct field. + */ +data class GoFieldId( + val declaringType: GoTypeId, + val name: String, + val isExported: Boolean +) + +/** + * Represents real Go struct type. + */ +class GoStructTypeId( + name: String, + var fields: List, +) : GoTypeId(name) { + override val canonicalName: String = fields.joinToString(separator = ";", prefix = "struct{", postfix = "}") { + "${it.name} ${it.declaringType}" + } + + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String = name + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoStructTypeId) return false + + return fields == other.fields + } + + override fun hashCode(): Int = fields.hashCode() +} + +/** + * Represents real Go array type. + */ +class GoArrayTypeId( + name: String, elementTypeId: GoTypeId, val length: Int +) : GoTypeId(name, elementTypeId = elementTypeId) { + override val canonicalName: String = "[$length]${elementTypeId}" + + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String = + "[$length]${elementTypeId!!.getRelativeName(destinationPackage, aliases)}" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoArrayTypeId) return false + + return elementTypeId == other.elementTypeId && length == other.length + } + + override fun hashCode(): Int = 31 * elementTypeId.hashCode() + length +} + +/** + * Represents real Go slice type. + */ +class GoSliceTypeId( + name: String, elementTypeId: GoTypeId, +) : GoTypeId(name, elementTypeId = elementTypeId) { + override val canonicalName: String = "[]${elementTypeId}" + + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String = + "[]${elementTypeId!!.getRelativeName(destinationPackage, aliases)}" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoSliceTypeId) return false + + return elementTypeId == other.elementTypeId + } + + override fun hashCode(): Int = elementTypeId.hashCode() +} + +/** + * Represents real Go map type. + */ +class GoMapTypeId( + name: String, val keyTypeId: GoTypeId, elementTypeId: GoTypeId, +) : GoTypeId(name, elementTypeId = elementTypeId) { + override val canonicalName: String = "map[${keyTypeId.canonicalName}]${elementTypeId.canonicalName}" + + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String { + val keyType = keyTypeId.getRelativeName(destinationPackage, aliases) + val elementType = elementTypeId!!.getRelativeName(destinationPackage, aliases) + return "map[$keyType]$elementType" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoMapTypeId) return false + + return keyTypeId == other.keyTypeId && elementTypeId == other.elementTypeId + } + + override fun hashCode(): Int = 31 * keyTypeId.hashCode() + elementTypeId.hashCode() +} + +/** + * Represents real Go chan type. + */ +class GoChanTypeId( + name: String, elementTypeId: GoTypeId, val direction: Direction, +) : GoTypeId(name, elementTypeId = elementTypeId) { + enum class Direction { + SENDONLY, RECVONLY, SENDRECV + } + + private val typeWithDirection = when (direction) { + Direction.RECVONLY -> "<-chan" + Direction.SENDONLY -> "chan<-" + Direction.SENDRECV -> "chan" + } + + override val canonicalName: String = "$typeWithDirection $elementTypeId" + + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String { + val elementType = elementTypeId!!.getRelativeName(destinationPackage, aliases) + return "$typeWithDirection $elementType" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoChanTypeId) return false + + return elementTypeId == other.elementTypeId && direction == other.direction + } + + override fun hashCode(): Int = 31 * elementTypeId.hashCode() + direction.hashCode() +} + +/** + * Represents real Go interface type. + */ +class GoInterfaceTypeId(name: String, val implementations: List) : GoTypeId(name) { + override val canonicalName: String = name + + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String = name + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoInterfaceTypeId) return false + + return name == other.name + } + + override fun hashCode(): Int = name.hashCode() +} + +/** + * Represents real Go named type. + */ +class GoNamedTypeId( + name: String, override val sourcePackage: GoPackage, implementsError: Boolean, val underlyingTypeId: GoTypeId +) : GoTypeId(name, implementsError = implementsError) { + private val packageName: String = sourcePackage.name + private val packagePath: String = sourcePackage.path + override val canonicalName: String = if (sourcePackage.isBuiltin) { + name + } else { + "$packagePath/$packageName.$name" + } + + fun exported(): Boolean = name.first().isUpperCase() + + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String { + val alias = aliases[sourcePackage] + return if (sourcePackage.isBuiltin || sourcePackage == destinationPackage || alias == ".") { + name + } else if (alias == null) { + "${packageName}.${name}" + } else { + "${alias}.${name}" + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoNamedTypeId) return false + + return sourcePackage == other.sourcePackage && name == other.name + } + + override fun hashCode(): Int { + var result = packagePath.hashCode() + result = 31 * result + packageName.hashCode() + result = 31 * result + name.hashCode() + return result + } +} + +/** + * Represents real Go pointer type. + */ +class GoPointerTypeId(name: String, elementTypeId: GoTypeId) : GoTypeId(name, elementTypeId = elementTypeId) { + override val canonicalName: String = "*$elementTypeId" + + override fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String { + val elementType = elementTypeId!!.getRelativeName(destinationPackage, aliases) + return "*$elementType" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoPointerTypeId) return false + + return elementTypeId == other.elementTypeId + } + + override fun hashCode(): Int = elementTypeId.hashCode() +} diff --git a/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtExecutionResultsApi.kt b/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtExecutionResultsApi.kt new file mode 100644 index 0000000000..4a4a07879f --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtExecutionResultsApi.kt @@ -0,0 +1,84 @@ +package org.utbot.go.api + +import org.utbot.go.framework.api.go.GoUtModel + +interface GoUtExecutionResult + +interface GoUtExecutionCompleted : GoUtExecutionResult { + val models: List +} + +data class GoUtExecutionSuccess(override val models: List) : GoUtExecutionCompleted + +data class GoUtExecutionWithNonNilError(override val models: List) : GoUtExecutionCompleted + +data class GoUtPanicFailure(val panicValue: GoUtModel, val panicValueIsErrorMessage: Boolean) : GoUtExecutionResult + +data class GoUtTimeoutExceeded(val timeoutMillis: Long) : GoUtExecutionResult + +class ExecutionResults(testCase: GoUtFuzzedFunctionTestCase, length: Int) { + private var successfulExecutionTestCaseWithLengthOfParameters: Pair? = null + private var executionWithErrorTestCaseWithLengthOfParameters: Pair? = null + private var panicFailureTestCaseWithLengthOfParameters: Pair? = null + private var timeoutExceededTestCaseWithLengthOfParameters: Pair? = null + + private fun Pair?.relax( + testCase: GoUtFuzzedFunctionTestCase, + length: Int + ): Pair = if (this == null || this.second > length) { + testCase to length + } else { + this + } + + private fun Pair?.relax( + testCaseAndLength: Pair?, + ): Pair? = if (testCaseAndLength != null) { + this.relax(testCaseAndLength.first, testCaseAndLength.second) + } else { + this + } + + init { + when (testCase.executionResult) { + is GoUtExecutionSuccess -> successfulExecutionTestCaseWithLengthOfParameters = testCase to length + is GoUtExecutionWithNonNilError -> executionWithErrorTestCaseWithLengthOfParameters = testCase to length + is GoUtPanicFailure -> panicFailureTestCaseWithLengthOfParameters = testCase to length + is GoUtTimeoutExceeded -> timeoutExceededTestCaseWithLengthOfParameters = testCase to length + } + } + + fun update(testCase: GoUtFuzzedFunctionTestCase, length: Int) = when (testCase.executionResult) { + is GoUtExecutionSuccess -> successfulExecutionTestCaseWithLengthOfParameters = + successfulExecutionTestCaseWithLengthOfParameters.relax(testCase, length) + + is GoUtExecutionWithNonNilError -> executionWithErrorTestCaseWithLengthOfParameters = + executionWithErrorTestCaseWithLengthOfParameters.relax(testCase, length) + + is GoUtPanicFailure -> panicFailureTestCaseWithLengthOfParameters = + panicFailureTestCaseWithLengthOfParameters.relax(testCase, length) + + is GoUtTimeoutExceeded -> timeoutExceededTestCaseWithLengthOfParameters = + timeoutExceededTestCaseWithLengthOfParameters.relax(testCase, length) + + else -> error("${testCase.executionResult.javaClass.name} is not supported") + } + + fun update(executionResults: ExecutionResults) { + successfulExecutionTestCaseWithLengthOfParameters = + successfulExecutionTestCaseWithLengthOfParameters.relax(executionResults.successfulExecutionTestCaseWithLengthOfParameters) + executionWithErrorTestCaseWithLengthOfParameters = + executionWithErrorTestCaseWithLengthOfParameters.relax(executionResults.executionWithErrorTestCaseWithLengthOfParameters) + panicFailureTestCaseWithLengthOfParameters = + panicFailureTestCaseWithLengthOfParameters.relax(executionResults.panicFailureTestCaseWithLengthOfParameters) + timeoutExceededTestCaseWithLengthOfParameters = + timeoutExceededTestCaseWithLengthOfParameters.relax(executionResults.timeoutExceededTestCaseWithLengthOfParameters) + } + + fun getTestCases(): List = listOfNotNull( + successfulExecutionTestCaseWithLengthOfParameters?.first, + executionWithErrorTestCaseWithLengthOfParameters?.first, + panicFailureTestCaseWithLengthOfParameters?.first, + timeoutExceededTestCaseWithLengthOfParameters?.first + ) +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtFunctionApi.kt b/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtFunctionApi.kt new file mode 100644 index 0000000000..0371178375 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtFunctionApi.kt @@ -0,0 +1,36 @@ +package org.utbot.go.api + +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel +import java.io.File +import java.nio.file.Paths + +data class GoUtFile(val absolutePath: String, val sourcePackage: GoPackage) { + val fileName: String get() = File(absolutePath).name + val fileNameWithoutExtension: String get() = File(absolutePath).nameWithoutExtension + val absoluteDirectoryPath: String get() = Paths.get(absolutePath).parent.toString() +} + +data class GoUtDeclaredVariable(val name: String, val type: GoTypeId) + +data class GoUtFunction( + val name: String, + val receiver: GoUtDeclaredVariable?, + val parameters: List, + val results: List, + val constants: Map>, + val sourceFile: GoUtFile +) { + val isMethod: Boolean = receiver != null +} + +data class GoUtFuzzedFunction(val function: GoUtFunction, val parametersValues: List) + +data class GoUtFuzzedFunctionTestCase( + val fuzzedFunction: GoUtFuzzedFunction, + val executionResult: GoUtExecutionResult, +) { + val function: GoUtFunction get() = fuzzedFunction.function + val parametersValues: List get() = fuzzedFunction.parametersValues +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtModelsApi.kt b/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtModelsApi.kt new file mode 100644 index 0000000000..cdc1fd5c42 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/api/GoUtModelsApi.kt @@ -0,0 +1,383 @@ +@file:Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") + +package org.utbot.go.api + +import org.utbot.go.api.util.* +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel + +// NEVER and DEPENDS difference is useful in code generation of assert.Equals(...). +enum class ExplicitCastMode { + REQUIRED, NEVER, DEPENDS +} + +/** + * Class for Go primitive model. + */ +open class GoUtPrimitiveModel( + val value: Any, + typeId: GoPrimitiveTypeId, + val explicitCastMode: ExplicitCastMode = if (typeId.neverRequiresExplicitCast) { + ExplicitCastMode.NEVER + } else { + ExplicitCastMode.DEPENDS + }, + private val requiredPackages: Set = emptySet(), +) : GoUtModel(typeId) { + override val typeId: GoPrimitiveTypeId + get() = super.typeId as GoPrimitiveTypeId + + override fun getRequiredPackages(destinationPackage: GoPackage): Set = requiredPackages + + override fun isComparable(): Boolean = true + + override fun toString(): String = if (typeId == goStringTypeId) "\"${value}\"" else "$value" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoUtPrimitiveModel) return false + + return typeId == other.typeId && value == other.value + } + + override fun hashCode(): Int = 31 * value.hashCode() + typeId.hashCode() +} + +/** + * Class for Go struct model. + */ +class GoUtStructModel( + val value: LinkedHashMap, + typeId: GoStructTypeId, +) : GoUtModel(typeId) { + override val typeId: GoStructTypeId + get() = super.typeId as GoStructTypeId + + override fun getRequiredPackages(destinationPackage: GoPackage): Set = + value.values.fold(emptySet()) { acc, fieldModel -> + acc + fieldModel.getRequiredPackages(destinationPackage) + } + + override fun isComparable(): Boolean = value.values.all { it.isComparable() } + + override fun toString(): String = + value.entries.joinToString(prefix = "struct{", postfix = "}") { (fieldId, model) -> + "${fieldId.name}: $model" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoUtStructModel) return false + + return typeId == other.typeId && value == other.value + } + + override fun hashCode(): Int = 31 * value.hashCode() + typeId.hashCode() +} + +/** + * Class for Go array model. + */ +class GoUtArrayModel( + val value: Array, + typeId: GoArrayTypeId, +) : GoUtModel(typeId) { + val length: Int = typeId.length + + override val typeId: GoArrayTypeId + get() = super.typeId as GoArrayTypeId + + override fun getRequiredPackages(destinationPackage: GoPackage): Set { + val elementNamedTypeId = typeId.elementTypeId as? GoNamedTypeId + val imports = elementNamedTypeId?.getRequiredPackages(destinationPackage) ?: emptySet() + return value.fold(imports) { acc, model -> + acc + model.getRequiredPackages(destinationPackage) + } + } + + override fun isComparable(): Boolean = value.all { it.isComparable() } + + fun getElements(): List = value.toList() + + override fun toString(): String = getElements().joinToString(prefix = "$typeId{", postfix = "}") { + it.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoUtArrayModel) return false + + return typeId == other.typeId && value.contentEquals(other.value) && length == other.length + } + + override fun hashCode(): Int { + var result = value.hashCode() + result = 31 * result + length + result = 31 * result + typeId.hashCode() + return result + } +} + +/** + * Class for Go slice model. + */ +class GoUtSliceModel( + val value: Array, + typeId: GoSliceTypeId, + val length: Int, +) : GoUtModel(typeId) { + override val typeId: GoSliceTypeId + get() = super.typeId as GoSliceTypeId + + override fun getRequiredPackages(destinationPackage: GoPackage): Set { + val elementNamedTypeId = typeId.elementTypeId as? GoNamedTypeId + val imports = elementNamedTypeId?.getRequiredPackages(destinationPackage) ?: emptySet() + return value.filterNotNull().fold(imports) { acc, model -> + acc + model.getRequiredPackages(destinationPackage) + } + } + + override fun isComparable(): Boolean = value.all { it?.isComparable() ?: true } + + fun getElements(): List = value.map { + it ?: typeId.elementTypeId!!.goDefaultValueModel() + } + + override fun toString(): String = getElements().joinToString(prefix = "$typeId{", postfix = "}") { + it.toString() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoUtSliceModel) return false + + return typeId == other.typeId && value.contentEquals(other.value) && length == other.length + } + + override fun hashCode(): Int { + var result = value.hashCode() + result = 31 * result + length + result = 31 * result + typeId.hashCode() + return result + } +} + +/** + * Class for Go map model. + */ +class GoUtMapModel( + val value: MutableMap, + typeId: GoMapTypeId, +) : GoUtModel(typeId) { + override val typeId: GoMapTypeId + get() = super.typeId as GoMapTypeId + + override fun getRequiredPackages(destinationPackage: GoPackage): Set { + val keyNamedTypeId = typeId.keyTypeId as? GoNamedTypeId + var imports = keyNamedTypeId?.getRequiredPackages(destinationPackage) ?: emptySet() + val elementNamedTypeId = typeId.elementTypeId as? GoNamedTypeId + imports = imports + (elementNamedTypeId?.getRequiredPackages(destinationPackage) ?: emptySet()) + return value.values.fold(imports) { acc, model -> + acc + model.getRequiredPackages(destinationPackage) + } + } + + override fun isComparable(): Boolean = + value.keys.all { it.isComparable() } && value.values.all { it.isComparable() } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoUtMapModel) return false + + return typeId == other.typeId && value == other.value + } + + override fun hashCode(): Int = 31 * value.hashCode() + typeId.hashCode() +} + +/** + * Class for Go chan model. + */ +class GoUtChanModel( + val value: Array, + typeId: GoChanTypeId +) : GoUtModel(typeId) { + override val typeId: GoChanTypeId + get() = super.typeId as GoChanTypeId + + override fun getRequiredPackages(destinationPackage: GoPackage): Set { + val elementNamedTypeId = typeId.elementTypeId as? GoNamedTypeId + val imports = elementNamedTypeId?.getRequiredPackages(destinationPackage) ?: emptySet() + return value.filterNotNull().fold(imports) { acc, model -> + acc + model.getRequiredPackages(destinationPackage) + } + } + + override fun isComparable(): Boolean = value.all { it?.isComparable() ?: true } + + fun getElements(): List = value.filterNotNull() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoUtChanModel) return false + + return typeId == other.typeId && value.contentEquals(other.value) + } + + override fun hashCode(): Int = 31 * value.hashCode() + typeId.hashCode() +} + +/** + * Class for Go model with the IEEE 754 “not-a-number” value. + */ +class GoUtFloatNaNModel( + typeId: GoPrimitiveTypeId +) : GoUtPrimitiveModel( + "math.NaN()", + typeId, + explicitCastMode = if (typeId != goFloat64TypeId) { + ExplicitCastMode.REQUIRED + } else { + ExplicitCastMode.NEVER + }, + requiredPackages = setOf(GoPackage("math", "math")), +) { + override fun isComparable(): Boolean = false + + override fun equals(other: Any?): Boolean = this === other || other is GoUtFloatNaNModel + + override fun hashCode(): Int = typeId.hashCode() + + override fun toString(): String = "NaN" +} + +/** + * Class for Go model with infinity. + */ +class GoUtFloatInfModel( + val sign: Int, typeId: GoPrimitiveTypeId +) : GoUtPrimitiveModel( + "math.Inf($sign)", + typeId, + explicitCastMode = if (typeId != goFloat64TypeId) { + ExplicitCastMode.REQUIRED + } else { + ExplicitCastMode.NEVER + }, + requiredPackages = setOf(GoPackage("math", "math")), +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoUtFloatInfModel) return false + + return sign == other.sign + } + + override fun hashCode(): Int = sign.hashCode() + + override fun toString(): String = if (sign >= 0) { + "+Inf" + } else { + "-Inf" + } +} + +/** + * Class for Go models with complex numbers. + */ +class GoUtComplexModel( + var realValue: GoUtPrimitiveModel, + var imagValue: GoUtPrimitiveModel, + typeId: GoPrimitiveTypeId, +) : GoUtPrimitiveModel( + "complex($realValue, $imagValue)", + typeId, + explicitCastMode = ExplicitCastMode.NEVER +) { + override fun getRequiredPackages(destinationPackage: GoPackage): Set = + realValue.getRequiredPackages(destinationPackage) + imagValue.getRequiredPackages(destinationPackage) + + override fun isComparable(): Boolean = realValue.isComparable() && imagValue.isComparable() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoUtComplexModel) return false + + return realValue == other.realValue && imagValue == other.imagValue + } + + override fun hashCode(): Int = 31 * realValue.hashCode() + imagValue.hashCode() +} + +/** + * Class for Go model with nil. + */ +class GoUtNilModel( + typeId: GoTypeId +) : GoUtModel(typeId) { + override fun getRequiredPackages(destinationPackage: GoPackage): Set = emptySet() + + override fun isComparable(): Boolean = true + + override fun toString() = "nil" + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoUtNilModel) return false + + return typeId == other.typeId + } + + override fun hashCode(): Int = typeId.hashCode() +} + +/** + * Class for Go named model. + */ +class GoUtNamedModel( + var value: GoUtModel, + typeId: GoNamedTypeId, +) : GoUtModel(typeId) { + override val typeId: GoNamedTypeId + get() = super.typeId as GoNamedTypeId + + override fun getRequiredPackages(destinationPackage: GoPackage): Set = + typeId.getRequiredPackages(destinationPackage) + value.getRequiredPackages(destinationPackage) + + override fun isComparable(): Boolean = value.isComparable() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoUtNamedModel) return false + + return typeId == other.typeId && value == other.value + } + + override fun hashCode(): Int = 31 * value.hashCode() + typeId.hashCode() +} + +/** + * Class for Go pointer model. + */ +class GoUtPointerModel( + var value: GoUtModel, + typeId: GoPointerTypeId +) : GoUtModel(typeId) { + override val typeId: GoPointerTypeId + get() = super.typeId as GoPointerTypeId + + override fun getRequiredPackages(destinationPackage: GoPackage): Set = + value.getRequiredPackages(destinationPackage) + + override fun isComparable(): Boolean = value.isComparable() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GoUtPointerModel) return false + + return typeId == other.typeId && value == other.value + } + + override fun hashCode(): Int = 31 * value.hashCode() + typeId.hashCode() +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoTypesApiUtil.kt b/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoTypesApiUtil.kt new file mode 100644 index 0000000000..df2eba4a1d --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoTypesApiUtil.kt @@ -0,0 +1,236 @@ +package org.utbot.go.api.util + +import org.utbot.go.api.* +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel +import kotlin.properties.Delegates +import kotlin.reflect.KClass + +var intSize by Delegates.notNull() + +val goByteTypeId = GoPrimitiveTypeId("byte") +val goBoolTypeId = GoPrimitiveTypeId("bool") + +val goComplex64TypeId = GoPrimitiveTypeId("complex64") +val goComplex128TypeId = GoPrimitiveTypeId("complex128") + +val goFloat32TypeId = GoPrimitiveTypeId("float32") +val goFloat64TypeId = GoPrimitiveTypeId("float64") + +val goInt8TypeId = GoPrimitiveTypeId("int8") +val goInt16TypeId = GoPrimitiveTypeId("int16") +val goInt32TypeId = GoPrimitiveTypeId("int32") +val goIntTypeId = GoPrimitiveTypeId("int") +val goInt64TypeId = GoPrimitiveTypeId("int64") + +val goRuneTypeId = GoPrimitiveTypeId("rune") // = int32 +val goStringTypeId = GoPrimitiveTypeId("string") + +val goUint8TypeId = GoPrimitiveTypeId("uint8") +val goUint16TypeId = GoPrimitiveTypeId("uint16") +val goUint32TypeId = GoPrimitiveTypeId("uint32") +val goUintTypeId = GoPrimitiveTypeId("uint") +val goUint64TypeId = GoPrimitiveTypeId("uint64") +val goUintPtrTypeId = GoPrimitiveTypeId("uintptr") + +val goPrimitives = setOf( + goByteTypeId, + goBoolTypeId, + goComplex128TypeId, + goComplex64TypeId, + goFloat32TypeId, + goFloat64TypeId, + goIntTypeId, + goInt16TypeId, + goInt32TypeId, + goInt64TypeId, + goInt8TypeId, + goRuneTypeId, + goStringTypeId, + goUintTypeId, + goUint16TypeId, + goUint32TypeId, + goUint64TypeId, + goUint8TypeId, + goUintPtrTypeId, +) + +val goSupportedConstantTypes = setOf( + goByteTypeId, + goFloat32TypeId, + goFloat64TypeId, + goIntTypeId, + goInt8TypeId, + goInt16TypeId, + goInt32TypeId, + goInt64TypeId, + goRuneTypeId, + goStringTypeId, + goUintTypeId, + goUint8TypeId, + goUint16TypeId, + goUint32TypeId, + goUint64TypeId, + goUintPtrTypeId, +) + +private val goTypesNeverRequireExplicitCast = setOf( + goBoolTypeId, + goComplex128TypeId, + goComplex64TypeId, + goFloat64TypeId, + goIntTypeId, + goStringTypeId, +) + +val GoPrimitiveTypeId.neverRequiresExplicitCast: Boolean + get() = this in goTypesNeverRequireExplicitCast + +/** + * This method is useful for converting the string representation of a Go value to its more accurate representation. + */ +private fun GoPrimitiveTypeId.correspondingKClass(): KClass = when (this) { + goBoolTypeId -> Boolean::class + goFloat32TypeId -> Float::class + goFloat64TypeId -> Double::class + goInt8TypeId -> Byte::class + goInt16TypeId -> Short::class + goInt32TypeId, goRuneTypeId -> Int::class + goIntTypeId -> if (intSize == 32) Int::class else Long::class + goInt64TypeId -> Long::class + goStringTypeId -> String::class + goUint8TypeId, goByteTypeId -> UByte::class + goUint16TypeId -> UShort::class + goUint32TypeId -> UInt::class + goUintTypeId -> if (intSize == 32) UInt::class else ULong::class + goUint64TypeId -> ULong::class + goUintPtrTypeId -> if (intSize == 32) UInt::class else ULong::class + else -> String::class // default way to hold GoUtPrimitiveModel's value is to use String +} + +fun rawValueOfGoPrimitiveTypeToValue(typeId: GoPrimitiveTypeId, rawValue: String): Any = + when (typeId.correspondingKClass()) { + UByte::class -> rawValue.toUByte() + Boolean::class -> rawValue.toBoolean() + Float::class -> rawValue.toFloat() + Double::class -> rawValue.toDouble() + Int::class -> rawValue.toInt() + Short::class -> rawValue.toShort() + Long::class -> rawValue.toLong() + Byte::class -> rawValue.toByte() + UInt::class -> rawValue.toUInt() + UShort::class -> rawValue.toUShort() + ULong::class -> rawValue.toULong() + else -> rawValue + } + +/** + * This method is useful for creating a GoUtModel with a default value. + */ +fun GoTypeId.goDefaultValueModel(): GoUtModel = when (this) { + is GoPrimitiveTypeId -> when (this) { + goByteTypeId -> GoUtPrimitiveModel("0".toUByte(), this) + goBoolTypeId -> GoUtPrimitiveModel(false, this) + goComplex64TypeId -> GoUtComplexModel( + goFloat32TypeId.goDefaultValueModel() as GoUtPrimitiveModel, + goFloat32TypeId.goDefaultValueModel() as GoUtPrimitiveModel, + this + ) + + goComplex128TypeId -> GoUtComplexModel( + goFloat64TypeId.goDefaultValueModel() as GoUtPrimitiveModel, + goFloat64TypeId.goDefaultValueModel() as GoUtPrimitiveModel, + this + ) + + goFloat32TypeId -> GoUtPrimitiveModel(0.0f, this) + goFloat64TypeId -> GoUtPrimitiveModel(0.0, this) + goInt8TypeId -> GoUtPrimitiveModel("0".toByte(), this) + goInt16TypeId -> GoUtPrimitiveModel("0".toShort(), this) + goInt32TypeId -> GoUtPrimitiveModel("0".toInt(), this) + goIntTypeId -> if (intSize == 32) { + GoUtPrimitiveModel("0".toInt(), this) + } else { + GoUtPrimitiveModel("0".toLong(), this) + } + + goInt64TypeId -> GoUtPrimitiveModel("0".toLong(), this) + + goRuneTypeId -> GoUtPrimitiveModel("0".toInt(), this) + goStringTypeId -> GoUtPrimitiveModel("", this) + goUint8TypeId -> GoUtPrimitiveModel("0".toUByte(), this) + goUint16TypeId -> GoUtPrimitiveModel("0".toUShort(), this) + goUint32TypeId -> GoUtPrimitiveModel("0".toUInt(), this) + goUintTypeId -> if (intSize == 32) { + GoUtPrimitiveModel("0".toUInt(), this) + } else { + GoUtPrimitiveModel("0".toULong(), this) + } + + goUint64TypeId -> GoUtPrimitiveModel("0".toULong(), this) + goUintPtrTypeId -> GoUtPrimitiveModel("0".toULong(), this) + + else -> error("Generating Go default value model for ${this.javaClass} is not supported") + } + + is GoArrayTypeId -> GoUtArrayModel( + value = (0 until this.length) + .map { this.elementTypeId!!.goDefaultValueModel() } + .toTypedArray(), + typeId = this, + ) + + is GoStructTypeId -> GoUtStructModel(linkedMapOf(), this) + is GoSliceTypeId -> GoUtNilModel(this) + is GoMapTypeId -> GoUtNilModel(this) + is GoChanTypeId -> GoUtNilModel(this) + is GoPointerTypeId -> GoUtNilModel(this) + is GoNamedTypeId -> GoUtNamedModel(this.underlyingTypeId.goDefaultValueModel(), this) + is GoInterfaceTypeId -> GoUtNilModel(this) + else -> error("Generating Go default value model for ${this.javaClass} is not supported") +} + +fun GoTypeId.getAllVisibleNamedTypes(goPackage: GoPackage, visitedTypes: MutableSet): Set { + if (visitedTypes.contains(this)) { + return emptySet() + } + visitedTypes.add(this) + return when (this) { + is GoNamedTypeId -> if (this.sourcePackage == goPackage || this.sourcePackage.isBuiltin || this.exported()) { + setOf(this) + underlyingTypeId.getAllVisibleNamedTypes(goPackage, visitedTypes) + } else { + emptySet() + } + + is GoStructTypeId -> fields.fold(emptySet()) { acc: Set, field -> + acc + (field.declaringType).getAllVisibleNamedTypes(goPackage, visitedTypes) + } + + is GoArrayTypeId, is GoSliceTypeId, is GoChanTypeId, is GoPointerTypeId -> + elementTypeId!!.getAllVisibleNamedTypes(goPackage, visitedTypes) + + is GoMapTypeId -> keyTypeId.getAllVisibleNamedTypes(goPackage, visitedTypes) + + elementTypeId!!.getAllVisibleNamedTypes(goPackage, visitedTypes) + + is GoInterfaceTypeId -> implementations.fold(emptySet()) { acc, type -> + acc + type.getAllVisibleNamedTypes(goPackage, visitedTypes) + } + + else -> emptySet() + } +} + +fun List.getAllVisibleNamedTypes(goPackage: GoPackage): Set { + val visitedTypes = mutableSetOf() + return this.fold(emptySet()) { acc, type -> + acc + type.getAllVisibleNamedTypes(goPackage, visitedTypes) + } +} + +fun GoNamedTypeId.getRequiredPackages(destinationPackage: GoPackage): Set = + if (!this.sourcePackage.isBuiltin && this.sourcePackage != destinationPackage) { + setOf(this.sourcePackage) + } else { + emptySet() + } \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoUtModelsApiUtil.kt b/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoUtModelsApiUtil.kt new file mode 100644 index 0000000000..e5a8635f27 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/api/util/GoUtModelsApiUtil.kt @@ -0,0 +1,93 @@ +package org.utbot.go.api.util + +import org.utbot.go.api.* +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.framework.api.go.GoUtModel +import org.utbot.go.worker.* + +fun GoUtModel.isNaNOrInf(): Boolean = this is GoUtFloatNaNModel || this is GoUtFloatInfModel + +fun GoUtModel.doesNotContainNaNOrInf(): Boolean { + if (this.isNaNOrInf()) return false + val asComplexModel = (this as? GoUtComplexModel) ?: return true + return !(asComplexModel.realValue.isNaNOrInf() || asComplexModel.imagValue.isNaNOrInf()) +} + +fun GoUtModel.containsNaNOrInf(): Boolean = !this.doesNotContainNaNOrInf() + +fun GoUtModel.convertToRawValue(destinationPackage: GoPackage, aliases: Map): RawValue = + when (val model = this) { + is GoUtComplexModel -> PrimitiveValue( + model.typeId.getRelativeName(destinationPackage, aliases), + "${model.realValue}@${model.imagValue}" + ) + + is GoUtFloatInfModel -> PrimitiveValue( + model.typeId.getRelativeName(destinationPackage, aliases), + this.toString() + ) + + is GoUtFloatNaNModel -> PrimitiveValue( + model.typeId.getRelativeName(destinationPackage, aliases), + this.toString() + ) + + is GoUtNamedModel -> NamedValue( + model.typeId.getRelativeName(destinationPackage, aliases), + model.value.convertToRawValue(destinationPackage, aliases) + ) + + is GoUtArrayModel -> ArrayValue( + model.typeId.getRelativeName(destinationPackage, aliases), + model.typeId.elementTypeId!!.getRelativeName(destinationPackage, aliases), + model.length, + model.getElements().map { it.convertToRawValue(destinationPackage, aliases) } + ) + + is GoUtSliceModel -> SliceValue( + model.typeId.getRelativeName(destinationPackage, aliases), + model.typeId.elementTypeId!!.getRelativeName(destinationPackage, aliases), + model.length, + model.getElements().map { it.convertToRawValue(destinationPackage, aliases) } + ) + + is GoUtMapModel -> MapValue( + model.typeId.getRelativeName(destinationPackage, aliases), + model.typeId.keyTypeId.getRelativeName(destinationPackage, aliases), + model.typeId.elementTypeId!!.getRelativeName(destinationPackage, aliases), + model.value.entries.map { + val key = it.key.convertToRawValue(destinationPackage, aliases) + val value = it.value.convertToRawValue(destinationPackage, aliases) + MapValue.KeyValue(key, value) + } + ) + + is GoUtStructModel -> StructValue( + model.typeId.getRelativeName(destinationPackage, aliases), + model.value.map { (fieldId, model) -> + StructValue.FieldValue( + fieldId.name, + model.convertToRawValue(destinationPackage, aliases), + fieldId.isExported + ) + } + ) + + is GoUtChanModel -> ChanValue( + model.typeId.getRelativeName(destinationPackage, aliases), + model.typeId.elementTypeId!!.getRelativeName(destinationPackage, aliases), + model.typeId.direction.name, + model.value.size, + model.getElements().map { it.convertToRawValue(destinationPackage, aliases) } + ) + + is GoUtPointerModel -> PointerValue( + model.typeId.getRelativeName(destinationPackage, aliases), + model.typeId.elementTypeId!!.getRelativeName(destinationPackage, aliases), + model.value.convertToRawValue(destinationPackage, aliases) + ) + + is GoUtPrimitiveModel -> PrimitiveValue(model.typeId.name, model.value.toString()) + is GoUtNilModel -> NilValue(model.typeId.getRelativeName(destinationPackage, aliases)) + else -> error("Converting ${model.javaClass} to RawValue is not supported") + } \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/framework/api/go/GoApi.kt b/utbot-go/src/main/kotlin/org/utbot/go/framework/api/go/GoApi.kt new file mode 100644 index 0000000000..2602ed62e2 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/framework/api/go/GoApi.kt @@ -0,0 +1,55 @@ +package org.utbot.go.framework.api.go + +/** + * Parent class for all Go types. + * + * To see its children check GoTypesApi.kt at org.utbot.go.api. + */ +abstract class GoTypeId( + val name: String, + val elementTypeId: GoTypeId? = null, + val implementsError: Boolean = false +) { + open val sourcePackage: GoPackage = GoPackage("", "") + abstract val canonicalName: String + + abstract fun getRelativeName(destinationPackage: GoPackage, aliases: Map): String + override fun toString(): String = canonicalName +} + +/** + * Parent class for all Go models. + * + * To see its children check GoUtModelsApi.kt at org.utbot.go.api. + */ +abstract class GoUtModel( + open val typeId: GoTypeId, +) { + abstract fun getRequiredPackages(destinationPackage: GoPackage): Set + abstract fun isComparable(): Boolean +} + +/** + * Class for Go package. + */ +data class GoPackage( + val name: String, + val path: String +) { + val isBuiltin = name == "" && path == "" +} + +/** + * Class for Go import. + */ +data class GoImport( + val goPackage: GoPackage, + val alias: String? = null +) { + override fun toString(): String { + if (alias == null) { + return "\"${goPackage.path}\"" + } + return "$alias \"${goPackage.path}\"" + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoArrayValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoArrayValueProvider.kt new file mode 100644 index 0000000000..5f68f1a868 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoArrayValueProvider.kt @@ -0,0 +1,58 @@ +package org.utbot.go.fuzzer.providers + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.go.GoDescription +import org.utbot.go.api.GoArrayTypeId +import org.utbot.go.api.GoUtArrayModel +import org.utbot.go.api.util.goDefaultValueModel +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel + +object GoArrayValueProvider : ValueProvider { + override fun accept(type: GoTypeId): Boolean = type is GoArrayTypeId + + override fun generate(description: GoDescription, type: GoTypeId): Sequence> = + sequence { + type.let { it as GoArrayTypeId }.also { arrayType -> + val elementType = arrayType.elementTypeId!! + yield( + Seed.Recursive( + construct = Routine.Create((0 until arrayType.length).map { elementType }) { values -> + GoUtArrayModel( + value = values.toTypedArray(), + typeId = arrayType, + ) + }, + modify = sequence { + val probShuffle = description.configuration.probCollectionShuffleInsteadResultMutation + val numberOfShuffles = if (probShuffle != 100) { + arrayType.length * probShuffle / (100 - probShuffle) + } else { + 1 + } + if (probShuffle != 100) { + (0 until arrayType.length).forEach { index -> + yield(Routine.Call(listOf(elementType)) { self, values -> + val model = self as GoUtArrayModel + val value = values.first() + model.value[index] = value + }) + } + } + repeat(numberOfShuffles) { + yield(Routine.Call(emptyList()) { self, _ -> + val model = self as GoUtArrayModel + model.value.shuffle() + }) + } + }, + empty = Routine.Empty { + arrayType.goDefaultValueModel() + } + ) + ) + } + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoChanValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoChanValueProvider.kt new file mode 100644 index 0000000000..2a5d211b24 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoChanValueProvider.kt @@ -0,0 +1,34 @@ +package org.utbot.go.fuzzer.providers + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.go.GoDescription +import org.utbot.go.api.GoChanTypeId +import org.utbot.go.api.GoUtChanModel +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel + +object GoChanValueProvider : ValueProvider { + override fun accept(type: GoTypeId): Boolean = type is GoChanTypeId + + override fun generate(description: GoDescription, type: GoTypeId): Sequence> = + sequence { + type.let { it as GoChanTypeId }.also { chanType -> + yield( + Seed.Collection( + construct = Routine.Collection { + GoUtChanModel( + value = arrayOfNulls(it), + typeId = chanType, + ) + }, + modify = Routine.ForEach(listOf(chanType.elementTypeId!!)) { self, i, values -> + val model = self as GoUtChanModel + model.value[i] = values.first() + } + ) + ) + } + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoConstantValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoConstantValueProvider.kt new file mode 100644 index 0000000000..7103ff0a49 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoConstantValueProvider.kt @@ -0,0 +1,148 @@ +package org.utbot.go.fuzzer.providers + +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.seeds.BitVectorValue +import org.utbot.fuzzing.seeds.IEEE754Value +import org.utbot.fuzzing.seeds.StringValue +import org.utbot.go.GoDescription +import org.utbot.go.api.GoPrimitiveTypeId +import org.utbot.go.api.GoUtPrimitiveModel +import org.utbot.go.api.util.* +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel + +object GoConstantValueProvider : ValueProvider { + override fun accept(type: GoTypeId): Boolean = type in goSupportedConstantTypes + + override fun generate(description: GoDescription, type: GoTypeId): Sequence> = sequence { + type.let { it as GoPrimitiveTypeId }.also { primitiveType -> + val constants = description.functionUnderTest.constants + val primitives: List> = (constants[primitiveType] ?: emptyList()).mapNotNull { + when (primitiveType) { + goRuneTypeId, goIntTypeId, goInt8TypeId, goInt16TypeId, goInt32TypeId, goInt64TypeId -> + when (primitiveType) { + goInt8TypeId -> Seed.Known(BitVectorValue.fromValue(it)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toByte(), + primitiveType + ) + } + + goInt16TypeId -> Seed.Known(BitVectorValue.fromValue(it)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toShort(), + primitiveType + ) + } + + goInt32TypeId, goRuneTypeId -> Seed.Known(BitVectorValue.fromValue(it)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toInt(), + primitiveType + ) + } + + goIntTypeId -> Seed.Known(BitVectorValue.fromValue(it)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + if (intSize == 32) obj.toInt() else obj.toLong(), + primitiveType + ) + } + + goInt64TypeId -> Seed.Known(BitVectorValue.fromValue(it)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toLong(), + primitiveType + ) + } + + else -> return@sequence + } + + goByteTypeId, goUintTypeId, goUintPtrTypeId, goUint8TypeId, goUint16TypeId, goUint32TypeId, goUint64TypeId -> + when (primitiveType) { + goByteTypeId, goUint8TypeId -> { + val uint8AsLong = (it as UByte).toLong() + Seed.Known(BitVectorValue.fromValue(uint8AsLong)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toUByte(), + primitiveType + ) + } + } + + goUint16TypeId -> { + val uint16AsLong = (it as UShort).toLong() + Seed.Known(BitVectorValue.fromValue(uint16AsLong)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toUShort(), + primitiveType + ) + } + } + + goUint32TypeId -> { + val uint32AsLong = (it as UInt).toLong() + Seed.Known(BitVectorValue.fromValue(uint32AsLong)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toUInt(), + primitiveType + ) + } + } + + goUintTypeId, goUintPtrTypeId -> { + val uintAsLong = if (intSize == 32) (it as UInt).toLong() else (it as ULong).toLong() + Seed.Known(BitVectorValue.fromValue(uintAsLong)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + if (intSize == 32) obj.toUInt() else obj.toULong(), + primitiveType + ) + } + } + + goUint64TypeId -> { + val uint64AsLong = (it as ULong).toLong() + Seed.Known(BitVectorValue.fromValue(uint64AsLong)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toULong(), + primitiveType + ) + } + } + + else -> return@sequence + } + + goFloat32TypeId -> Seed.Known(IEEE754Value.fromValue(it)) { obj: IEEE754Value -> + GoUtPrimitiveModel( + obj.toFloat(), + primitiveType + ) + } + + goFloat64TypeId -> Seed.Known(IEEE754Value.fromValue(it)) { obj: IEEE754Value -> + GoUtPrimitiveModel( + obj.toDouble(), + primitiveType + ) + } + + goStringTypeId -> Seed.Known(StringValue(it as String)) { obj: StringValue -> + GoUtPrimitiveModel( + obj.value, + primitiveType + ) + } + + else -> null + } + } + + primitives.forEach { seed -> + yield(seed) + } + } + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoInterfaceValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoInterfaceValueProvider.kt new file mode 100644 index 0000000000..205488c724 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoInterfaceValueProvider.kt @@ -0,0 +1,30 @@ +package org.utbot.go.fuzzer.providers + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.go.GoDescription +import org.utbot.go.api.GoInterfaceTypeId +import org.utbot.go.api.GoUtNilModel +import org.utbot.go.api.util.goDefaultValueModel +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel + +object GoInterfaceValueProvider : ValueProvider { + override fun accept(type: GoTypeId): Boolean = type is GoInterfaceTypeId + + override fun generate(description: GoDescription, type: GoTypeId): Sequence> = sequence { + type.let { it as GoInterfaceTypeId }.also { interfaceTypeId -> + interfaceTypeId.implementations.forEach { + yield(Seed.Recursive( + construct = Routine.Create(listOf(it)) { values -> + values.first() + }, + empty = Routine.Empty { + interfaceTypeId.goDefaultValueModel() + } + )) + } + } + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoMapValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoMapValueProvider.kt new file mode 100644 index 0000000000..bcf2ab1af2 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoMapValueProvider.kt @@ -0,0 +1,39 @@ +package org.utbot.go.fuzzer.providers + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.go.GoDescription +import org.utbot.go.api.GoMapTypeId +import org.utbot.go.api.GoUtMapModel +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel + +object GoMapValueProvider : ValueProvider { + override fun accept(type: GoTypeId): Boolean = type is GoMapTypeId + + override fun generate(description: GoDescription, type: GoTypeId): Sequence> = + sequence { + type.let { it as GoMapTypeId }.also { mapType -> + yield( + Seed.Collection( + construct = Routine.Collection { + GoUtMapModel( + value = mutableMapOf(), + typeId = mapType + ) + }, + modify = Routine.ForEach( + listOf( + mapType.keyTypeId, + mapType.elementTypeId!! + ) + ) { self, _, values -> + val model = self as GoUtMapModel + model.value[values[0]] = values[1] + } + ) + ) + } + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoNamedValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoNamedValueProvider.kt new file mode 100644 index 0000000000..b4455a0eec --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoNamedValueProvider.kt @@ -0,0 +1,34 @@ +package org.utbot.go.fuzzer.providers + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.go.GoDescription +import org.utbot.go.api.GoNamedTypeId +import org.utbot.go.api.GoUtNamedModel +import org.utbot.go.api.util.goDefaultValueModel +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel + +object GoNamedValueProvider : ValueProvider { + override fun accept(type: GoTypeId): Boolean = type is GoNamedTypeId + + override fun generate(description: GoDescription, type: GoTypeId): Sequence> = sequence { + type.let { it as GoNamedTypeId }.also { namedType -> + yield(Seed.Recursive(construct = Routine.Create(listOf(namedType.underlyingTypeId)) { values -> + GoUtNamedModel( + value = values.first(), + typeId = namedType, + ) + }, modify = sequence { + yield(Routine.Call(listOf(namedType.underlyingTypeId)) { self, values -> + val model = self as GoUtNamedModel + val value = values.first() + model.value = value + }) + }, empty = Routine.Empty { + namedType.goDefaultValueModel() + })) + } + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoNilValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoNilValueProvider.kt new file mode 100644 index 0000000000..cf9b9b5595 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoNilValueProvider.kt @@ -0,0 +1,21 @@ +package org.utbot.go.fuzzer.providers + +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.go.GoDescription +import org.utbot.go.api.* +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel + +object GoNilValueProvider : ValueProvider { + override fun accept(type: GoTypeId): Boolean = setOf( + GoSliceTypeId::class, + GoMapTypeId::class, + GoChanTypeId::class, + GoPointerTypeId::class, + GoInterfaceTypeId::class + ).any { type::class == it } + + override fun generate(description: GoDescription, type: GoTypeId): Sequence> = + sequenceOf(Seed.Simple(GoUtNilModel(type))) +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoPointerValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoPointerValueProvider.kt new file mode 100644 index 0000000000..17ca9812f0 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoPointerValueProvider.kt @@ -0,0 +1,31 @@ +package org.utbot.go.fuzzer.providers + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.go.GoDescription +import org.utbot.go.api.GoPointerTypeId +import org.utbot.go.api.GoUtNilModel +import org.utbot.go.api.GoUtPointerModel +import org.utbot.go.api.util.goDefaultValueModel +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel + +object GoPointerValueProvider : ValueProvider { + override fun accept(type: GoTypeId): Boolean = type is GoPointerTypeId + + override fun generate(description: GoDescription, type: GoTypeId): Sequence> = sequence { + type.let { it as GoPointerTypeId }.also { pointerType -> + yield( + Seed.Recursive( + construct = Routine.Create(listOf(pointerType.elementTypeId!!)) { values -> + GoUtPointerModel(value = values.first(), typeId = pointerType) + }, + empty = Routine.Empty { + pointerType.goDefaultValueModel() + } + ) + ) + } + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoPrimitivesValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoPrimitivesValueProvider.kt new file mode 100644 index 0000000000..b2b5eab50e --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoPrimitivesValueProvider.kt @@ -0,0 +1,214 @@ +package org.utbot.go.fuzzer.providers + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.seeds.* +import org.utbot.go.GoDescription +import org.utbot.go.api.* +import org.utbot.go.api.util.* +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel +import java.util.* +import kotlin.math.sign + +object GoPrimitivesValueProvider : ValueProvider { + private val random = Random(0) + + override fun accept(type: GoTypeId): Boolean = type in goPrimitives + + override fun generate(description: GoDescription, type: GoTypeId): Sequence> = + sequence { + type.let { it as GoPrimitiveTypeId }.also { primitiveType -> + val primitives: List> = when (primitiveType) { + goBoolTypeId -> listOf( + Seed.Known(Bool.FALSE.invoke()) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toBoolean(), + primitiveType + ) + }, + Seed.Known(Bool.TRUE.invoke()) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toBoolean(), + primitiveType + ) + } + ) + + goRuneTypeId, goIntTypeId, goInt8TypeId, goInt16TypeId, goInt32TypeId, goInt64TypeId -> Signed.values() + .map { + when (type) { + goInt8TypeId -> Seed.Known(it.invoke(8)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toByte(), + primitiveType + ) + } + + goInt16TypeId -> Seed.Known(it.invoke(16)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toShort(), + primitiveType + ) + } + + goInt32TypeId, goRuneTypeId -> Seed.Known(it.invoke(32)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toInt(), + primitiveType + ) + } + + goIntTypeId -> Seed.Known(it.invoke(intSize)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + if (intSize == 32) obj.toInt() else obj.toLong(), + primitiveType + ) + } + + goInt64TypeId -> Seed.Known(it.invoke(64)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toLong(), + primitiveType + ) + } + + else -> return@sequence + } + } + + goByteTypeId, goUintTypeId, goUintPtrTypeId, goUint8TypeId, goUint16TypeId, goUint32TypeId, goUint64TypeId -> Unsigned.values() + .map { + when (type) { + goByteTypeId, goUint8TypeId -> Seed.Known(it.invoke(8)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toUByte(), + primitiveType + ) + } + + goUint16TypeId -> Seed.Known(it.invoke(16)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toUShort(), + primitiveType + ) + } + + goUint32TypeId -> Seed.Known(it.invoke(32)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toUInt(), + primitiveType + ) + } + + goUintTypeId, goUintPtrTypeId -> Seed.Known(it.invoke(intSize)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + if (intSize == 32) obj.toUInt() else obj.toULong(), + primitiveType + ) + } + + goUint64TypeId -> Seed.Known(it.invoke(64)) { obj: BitVectorValue -> + GoUtPrimitiveModel( + obj.toULong(), + primitiveType + ) + } + + else -> return@sequence + } + } + + goFloat32TypeId -> generateFloat32Seeds(primitiveType) + + goFloat64TypeId -> generateFloat64Seeds(primitiveType) + + goComplex64TypeId -> generateComplexSeeds(primitiveType, goFloat32TypeId) + + goComplex128TypeId -> generateComplexSeeds(primitiveType, goFloat64TypeId) + + goStringTypeId -> listOf( + Seed.Known(StringValue("")) { obj: StringValue -> + GoUtPrimitiveModel( + obj.value, + primitiveType + ) + }, + Seed.Known(StringValue("hello")) { obj: StringValue -> + GoUtPrimitiveModel( + obj.value, + primitiveType + ) + }) + + else -> emptyList() + } + + primitives.forEach { seed -> + yield(seed) + } + } + } + + private fun generateFloat32Seeds(typeId: GoPrimitiveTypeId): List> { + return listOf( + Seed.Known(IEEE754Value.fromFloat(random.nextFloat())) { obj: IEEE754Value -> + val d = obj.toFloat() + if (d.isInfinite()) { + GoUtFloatInfModel(d.sign.toInt(), typeId) + } else if (d.isNaN()) { + GoUtFloatNaNModel(typeId) + } else { + GoUtPrimitiveModel(d, typeId) + } + } + ) + } + + private fun generateFloat64Seeds(typeId: GoPrimitiveTypeId): List> { + return listOf( + Seed.Known(IEEE754Value.fromDouble(random.nextDouble())) { obj: IEEE754Value -> + val d = obj.toDouble() + if (d.isInfinite()) { + GoUtFloatInfModel(d.sign.toInt(), typeId) + } else if (d.isNaN()) { + GoUtFloatNaNModel(typeId) + } else { + GoUtPrimitiveModel(d, typeId) + } + } + ) + } + + private fun generateComplexSeeds( + typeId: GoPrimitiveTypeId, + floatTypeId: GoPrimitiveTypeId + ): List> { + return listOf( + Seed.Recursive( + construct = Routine.Create(listOf(floatTypeId, floatTypeId)) { values -> + GoUtComplexModel( + realValue = values[0] as GoUtPrimitiveModel, + imagValue = values[1] as GoUtPrimitiveModel, + typeId = typeId + ) + }, + modify = sequence { + yield(Routine.Call(listOf(floatTypeId)) { self, values -> + val model = self as GoUtComplexModel + val value = values.first() as GoUtPrimitiveModel + model.realValue = value + }) + }, + empty = Routine.Empty { + GoUtComplexModel( + realValue = GoUtPrimitiveModel(0.0, floatTypeId), + imagValue = GoUtPrimitiveModel(0.0, floatTypeId), + typeId = typeId + ) + } + ) + ) + } +} diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoSliceValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoSliceValueProvider.kt new file mode 100644 index 0000000000..0aa5313fa1 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoSliceValueProvider.kt @@ -0,0 +1,35 @@ +package org.utbot.go.fuzzer.providers + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.go.GoDescription +import org.utbot.go.api.GoSliceTypeId +import org.utbot.go.api.GoUtSliceModel +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel + +object GoSliceValueProvider : ValueProvider { + override fun accept(type: GoTypeId): Boolean = type is GoSliceTypeId + + override fun generate(description: GoDescription, type: GoTypeId): Sequence> = + sequence { + type.let { it as GoSliceTypeId }.also { sliceType -> + yield( + Seed.Collection( + construct = Routine.Collection { + GoUtSliceModel( + value = arrayOfNulls(it), + typeId = sliceType, + length = it, + ) + }, + modify = Routine.ForEach(listOf(sliceType.elementTypeId!!)) { self, i, values -> + val model = self as GoUtSliceModel + model.value[i] = values.first() + } + ) + ) + } + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoStructValueProvider.kt b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoStructValueProvider.kt new file mode 100644 index 0000000000..b9d9e1369b --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/fuzzer/providers/GoStructValueProvider.kt @@ -0,0 +1,42 @@ +package org.utbot.go.fuzzer.providers + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.go.GoDescription +import org.utbot.go.api.GoStructTypeId +import org.utbot.go.api.GoUtStructModel +import org.utbot.go.api.util.goDefaultValueModel +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel + +object GoStructValueProvider : ValueProvider { + override fun accept(type: GoTypeId): Boolean = type is GoStructTypeId + + override fun generate(description: GoDescription, type: GoTypeId): Sequence> = + sequence { + type.let { it as GoStructTypeId }.also { structType -> + val fields = structType.fields + yield(Seed.Recursive( + construct = Routine.Create(fields.map { it.declaringType }) { values -> + GoUtStructModel( + value = linkedMapOf(*fields.zip(values).toTypedArray()), + typeId = structType, + ) + }, + modify = sequence { + fields.forEachIndexed { index, field -> + yield(Routine.Call(listOf(field.declaringType)) { self, values -> + val model = self as GoUtStructModel + val value = values.first() + model.value[fields[index]] = value + }) + } + }, + empty = Routine.Empty { + structType.goDefaultValueModel() + } + )) + } + } +} diff --git a/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/AnalysisResults.kt b/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/AnalysisResults.kt new file mode 100644 index 0000000000..e5262669bd --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/AnalysisResults.kt @@ -0,0 +1,272 @@ +package org.utbot.go.gocodeanalyzer + +import com.beust.klaxon.TypeAdapter +import com.beust.klaxon.TypeFor +import org.utbot.go.api.* +import org.utbot.go.api.util.goPrimitives +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.framework.api.go.GoTypeId +import kotlin.reflect.KClass + +data class AnalyzedPrimitiveType( + override val name: String +) : AnalyzedType(name) { + override fun toGoTypeId( + index: String, + analyzedTypes: MutableMap, + typesToAnalyze: Map + ): GoTypeId { + var result = analyzedTypes[index] + if (result == null) { + result = GoPrimitiveTypeId(name = name) + analyzedTypes[index] = result + } + return result + } +} + +data class AnalyzedStructType( + override val name: String, + val fields: List +) : AnalyzedType(name) { + data class AnalyzedField( + val name: String, + val type: String, + val isExported: Boolean + ) + + override fun toGoTypeId( + index: String, + analyzedTypes: MutableMap, + typesToAnalyze: Map + ): GoTypeId { + var result = analyzedTypes[index] + if (result == null) { + result = GoStructTypeId( + name = name, + fields = emptyList() + ) + analyzedTypes[index] = result + result.fields = fields.map { (name, type, isExported) -> + val fieldType = typesToAnalyze[type]!!.toGoTypeId(type, analyzedTypes, typesToAnalyze) + GoFieldId(fieldType, name, isExported) + } + } + return result + } +} + +data class AnalyzedArrayType( + override val name: String, + val elementType: String, + val length: Int +) : AnalyzedType(name) { + override fun toGoTypeId( + index: String, + analyzedTypes: MutableMap, + typesToAnalyze: Map + ): GoTypeId { + var result = analyzedTypes[index] + if (result == null) { + result = GoArrayTypeId( + name = name, + elementTypeId = typesToAnalyze[elementType]!!.toGoTypeId(elementType, analyzedTypes, typesToAnalyze), + length = length + ) + analyzedTypes[index] = result + } + return result + } +} + +data class AnalyzedSliceType( + override val name: String, + val elementType: String, +) : AnalyzedType(name) { + override fun toGoTypeId( + index: String, + analyzedTypes: MutableMap, + typesToAnalyze: Map + ): GoTypeId { + var result = analyzedTypes[index] + if (result == null) { + result = GoSliceTypeId( + name = name, + elementTypeId = typesToAnalyze[elementType]!!.toGoTypeId(elementType, analyzedTypes, typesToAnalyze), + ) + analyzedTypes[index] = result + } + return result + } +} + +data class AnalyzedMapType( + override val name: String, + val keyType: String, + val elementType: String, +) : AnalyzedType(name) { + override fun toGoTypeId( + index: String, + analyzedTypes: MutableMap, + typesToAnalyze: Map + ): GoTypeId { + var result = analyzedTypes[index] + if (result == null) { + result = GoMapTypeId( + name = name, + keyTypeId = typesToAnalyze[keyType]!!.toGoTypeId(keyType, analyzedTypes, typesToAnalyze), + elementTypeId = typesToAnalyze[elementType]!!.toGoTypeId(elementType, analyzedTypes, typesToAnalyze), + ) + analyzedTypes[index] = result + } + return result + } +} + +data class AnalyzedChanType( + override val name: String, + val elementType: String, + val direction: GoChanTypeId.Direction, +) : AnalyzedType(name) { + override fun toGoTypeId( + index: String, + analyzedTypes: MutableMap, + typesToAnalyze: Map + ): GoTypeId { + var result = analyzedTypes[index] + if (result == null) { + result = GoChanTypeId( + name = name, + elementTypeId = typesToAnalyze[elementType]!!.toGoTypeId(elementType, analyzedTypes, typesToAnalyze), + direction = direction + ) + analyzedTypes[index] = result + } + return result + } +} + +data class AnalyzedInterfaceType( + override val name: String, + val implementations: List, +) : AnalyzedType(name) { + override fun toGoTypeId( + index: String, + analyzedTypes: MutableMap, + typesToAnalyze: Map + ): GoTypeId { + var result = analyzedTypes[index] + if (result == null) { + result = GoInterfaceTypeId( + name = name, + implementations = implementations.map { type -> + typesToAnalyze[type]!!.toGoTypeId( + type, + analyzedTypes, + typesToAnalyze + ) + }) + analyzedTypes[index] = result + } + return result + } +} + +data class AnalyzedNamedType( + override val name: String, + val sourcePackage: GoPackage, + val implementsError: Boolean, + val underlyingType: String +) : AnalyzedType(name) { + override fun toGoTypeId( + index: String, + analyzedTypes: MutableMap, + typesToAnalyze: Map + ): GoTypeId { + var result = analyzedTypes[index] + if (result == null) { + result = GoNamedTypeId( + name = name, + sourcePackage = sourcePackage, + implementsError = implementsError, + underlyingTypeId = typesToAnalyze[underlyingType]!!.toGoTypeId( + underlyingType, + analyzedTypes, + typesToAnalyze + ), + ) + analyzedTypes[index] = result + } + return result + } +} + +data class AnalyzedPointerType( + override val name: String, + val elementType: String +) : AnalyzedType(name) { + override fun toGoTypeId( + index: String, + analyzedTypes: MutableMap, + typesToAnalyze: Map + ): GoTypeId { + var result = analyzedTypes[index] + if (result == null) { + result = GoPointerTypeId( + name = name, + elementTypeId = typesToAnalyze[elementType]!!.toGoTypeId(elementType, analyzedTypes, typesToAnalyze), + ) + analyzedTypes[index] = result + } + return result + } +} + +@TypeFor(field = "name", adapter = AnalyzedTypeAdapter::class) +abstract class AnalyzedType(open val name: String) { + abstract fun toGoTypeId( + index: String, + analyzedTypes: MutableMap, + typesToAnalyze: Map + ): GoTypeId +} + +class AnalyzedTypeAdapter : TypeAdapter { + override fun classFor(type: Any): KClass { + val typeName = type as String + return when { + typeName == "interface{}" -> AnalyzedInterfaceType::class + typeName == "struct{}" -> AnalyzedStructType::class + typeName == "map" -> AnalyzedMapType::class + typeName == "[]" -> AnalyzedSliceType::class + typeName == "[_]" -> AnalyzedArrayType::class + typeName == "chan" -> AnalyzedChanType::class + typeName == "*" -> AnalyzedPointerType::class + goPrimitives.map { it.name }.contains(typeName) -> AnalyzedPrimitiveType::class + else -> AnalyzedNamedType::class + } + } +} + +internal data class AnalyzedVariable(val name: String, val type: String) + +internal data class AnalyzedFunction( + val name: String, + val types: Map, + val receiver: AnalyzedVariable?, + val parameters: List, + val resultTypes: List, + val constants: Map>, +) + +internal data class AnalysisResult( + val absoluteFilePath: String, + val sourcePackage: GoPackage, + val analyzedFunctions: List, + val notSupportedFunctionNames: List, + val notFoundFunctionNames: List +) + +internal data class AnalysisResults(val results: List, val intSize: Int) + +class GoParsingSourceCodeAnalysisResultException(s: String, t: Throwable) : Exception(s, t) \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/AnalysisTargets.kt b/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/AnalysisTargets.kt new file mode 100644 index 0000000000..cb82545292 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/AnalysisTargets.kt @@ -0,0 +1,9 @@ +package org.utbot.go.gocodeanalyzer + +internal data class AnalysisTarget( + val absoluteFilePath: String, + val targetFunctionNames: List, + val targetMethodNames: List +) + +internal data class AnalysisTargets(val targets: List) \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/GoSourceCodeAnalyzer.kt b/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/GoSourceCodeAnalyzer.kt new file mode 100644 index 0000000000..e2272e2a6d --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/gocodeanalyzer/GoSourceCodeAnalyzer.kt @@ -0,0 +1,165 @@ +package org.utbot.go.gocodeanalyzer + +import com.beust.klaxon.KlaxonException +import org.utbot.common.FileUtil.extractDirectoryFromArchive +import org.utbot.common.scanForResourcesContaining +import org.utbot.go.api.GoPrimitiveTypeId +import org.utbot.go.api.GoUtDeclaredVariable +import org.utbot.go.api.GoUtFile +import org.utbot.go.api.GoUtFunction +import org.utbot.go.api.util.goSupportedConstantTypes +import org.utbot.go.api.util.intSize +import org.utbot.go.api.util.rawValueOfGoPrimitiveTypeToValue +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.util.executeCommandByNewProcessOrFail +import org.utbot.go.util.modifyEnvironment +import org.utbot.go.util.parseFromJsonOrFail +import org.utbot.go.util.writeJsonToFileOrFail +import java.io.File +import java.nio.file.Path + +object GoSourceCodeAnalyzer { + + data class GoSourceFileAnalysisResult( + val functions: List, + val notSupportedFunctionAndMethodNames: List, + val notFoundFunctionAndMethodNames: List + ) + + /** + * Takes maps from paths of Go source files to names of their selected functions and methods. + * + * Returns GoSourceCodeAnalyzerResult. + */ + fun analyzeGoSourceFilesForFunctions( + targetFunctionNamesBySourceFiles: Map>, + targetMethodNamesBySourceFiles: Map>, + goExecutableAbsolutePath: Path, + gopathAbsolutePath: Path + ): Map { + val analysisTargets = AnalysisTargets( + (targetFunctionNamesBySourceFiles.keys + targetMethodNamesBySourceFiles.keys).distinct().map { filePath -> + val targetFunctionNames = targetFunctionNamesBySourceFiles[filePath] ?: emptyList() + val targetMethodNames = targetMethodNamesBySourceFiles[filePath] ?: emptyList() + AnalysisTarget(filePath.toAbsolutePath().toString(), targetFunctionNames, targetMethodNames) + } + ) + val analysisTargetsFileName = createAnalysisTargetsFileName() + val analysisResultsFileName = createAnalysisResultsFileName() + + val goCodeAnalyzerSourceDir = extractGoCodeAnalyzerSourceDirectory() + val analysisTargetsFile = goCodeAnalyzerSourceDir.resolve(analysisTargetsFileName) + val analysisResultsFile = goCodeAnalyzerSourceDir.resolve(analysisResultsFileName) + + val goCodeAnalyzerRunCommand = listOf( + goExecutableAbsolutePath.toString(), "run" + ) + getGoCodeAnalyzerSourceFilesNames() + listOf( + "-targets", + analysisTargetsFileName, + "-results", + analysisResultsFileName, + ) + + try { + writeJsonToFileOrFail(analysisTargets, analysisTargetsFile) + val environment = modifyEnvironment(goExecutableAbsolutePath, gopathAbsolutePath) + executeCommandByNewProcessOrFail( + goCodeAnalyzerRunCommand, + goCodeAnalyzerSourceDir, + "GoSourceCodeAnalyzer for $analysisTargets", + environment + ) + val analysisResults = parseFromJsonOrFail(analysisResultsFile) + intSize = analysisResults.intSize + return analysisResults.results.map { analysisResult -> + GoUtFile(analysisResult.absoluteFilePath, analysisResult.sourcePackage) to analysisResult + }.associateBy({ (sourceFile, _) -> sourceFile }) { (sourceFile, analysisResult) -> + val functions = analysisResult.analyzedFunctions.map { analyzedFunction -> + val analyzedTypes = mutableMapOf() + analyzedFunction.types.keys.forEach { index -> + analyzedFunction.types[index]!!.toGoTypeId(index, analyzedTypes, analyzedFunction.types) + } + val receiver = analyzedFunction.receiver?.let { receiver -> + GoUtDeclaredVariable( + receiver.name, analyzedTypes[receiver.type]!! + ) + } + val parameters = analyzedFunction.parameters.map { parameter -> + GoUtDeclaredVariable( + parameter.name, analyzedTypes[parameter.type]!! + ) + } + val resultTypes = analyzedFunction.resultTypes.map { result -> + GoUtDeclaredVariable( + result.name, analyzedTypes[result.type]!!, + ) + } + val constants = mutableMapOf>() + analyzedFunction.constants.map { (type, rawValues) -> + val typeId = GoPrimitiveTypeId(type) + if (typeId !in goSupportedConstantTypes) { + error("Constants extraction: $type is a unsupported constant type") + } + val values = rawValues.map { rawValue -> + rawValueOfGoPrimitiveTypeToValue(typeId, rawValue) + } + constants.compute(typeId) { _, v -> if (v == null) values else v + values } + } + GoUtFunction( + analyzedFunction.name, + receiver, + parameters, + resultTypes, + constants, + sourceFile + ) + } + GoSourceFileAnalysisResult( + functions, + analysisResult.notSupportedFunctionNames, + analysisResult.notFoundFunctionNames + ) + } + } catch (exception: KlaxonException) { + throw GoParsingSourceCodeAnalysisResultException( + "An error occurred while parsing the result of the source code analysis.", exception + ) + } finally { + goCodeAnalyzerSourceDir.deleteRecursively() + } + } + + private fun extractGoCodeAnalyzerSourceDirectory(): File { + val sourceDirectoryName = "go_source_code_analyzer" + val classLoader = GoSourceCodeAnalyzer::class.java.classLoader + + val containingResourceFile = classLoader.scanForResourcesContaining(sourceDirectoryName).firstOrNull() ?: error( + "Can't find resource containing $sourceDirectoryName directory." + ) + if (containingResourceFile.extension != "jar") { + error("Resource for $sourceDirectoryName directory is expected to be JAR: others are not supported yet.") + } + + val archiveFilePath = containingResourceFile.toPath() + return extractDirectoryFromArchive(archiveFilePath, sourceDirectoryName)?.toFile() + ?: error("Can't find $sourceDirectoryName directory at the top level of JAR ${archiveFilePath.toAbsolutePath()}.") + } + + private fun getGoCodeAnalyzerSourceFilesNames(): List { + return listOf( + "main.go", + "analyzer_core.go", + "analysis_targets.go", + "analysis_results.go", + "constant_extractor.go" + ) + } + + private fun createAnalysisTargetsFileName(): String { + return "ut_go_analysis_targets.json" + } + + private fun createAnalysisResultsFileName(): String { + return "ut_go_analysis_results.json" + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/gocodeinstrumentation/GoPackageInstrumentation.kt b/utbot-go/src/main/kotlin/org/utbot/go/gocodeinstrumentation/GoPackageInstrumentation.kt new file mode 100644 index 0000000000..8ca0011a6d --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/gocodeinstrumentation/GoPackageInstrumentation.kt @@ -0,0 +1,86 @@ +package org.utbot.go.gocodeinstrumentation + +import org.utbot.common.FileUtil +import org.utbot.common.scanForResourcesContaining +import org.utbot.go.util.executeCommandByNewProcessOrFail +import org.utbot.go.util.modifyEnvironment +import org.utbot.go.util.parseFromJsonOrFail +import org.utbot.go.util.writeJsonToFileOrFail +import java.io.File +import java.nio.file.Path + +object GoPackageInstrumentation { + + fun instrumentGoPackage( + testedFunctions: List, + absoluteDirectoryPath: String, + goExecutableAbsolutePath: Path, + gopathAbsolutePath: Path + ): InstrumentationResult { + val instrumentationTarget = InstrumentationTarget(absoluteDirectoryPath, testedFunctions) + val instrumentationTargetFileName = createInstrumentationTargetFileName() + val instrumentationResultFileName = createInstrumentationResultFileName() + + val goPackageInstrumentationSourceDir = extractGoPackageInstrumentationDirectory() + val instrumentationTargetFile = goPackageInstrumentationSourceDir.resolve(instrumentationTargetFileName) + val instrumentationResultFile = goPackageInstrumentationSourceDir.resolve(instrumentationResultFileName) + + val goPackageInstrumentationRunCommand = listOf( + goExecutableAbsolutePath.toString(), "run" + ) + getGoPackageInstrumentationFilesNames() + listOf( + "-target", + instrumentationTargetFile.absolutePath, + "-result", + instrumentationResultFile.absolutePath, + ) + + try { + writeJsonToFileOrFail(instrumentationTarget, instrumentationTargetFile) + val environment = modifyEnvironment(goExecutableAbsolutePath, gopathAbsolutePath) + executeCommandByNewProcessOrFail( + goPackageInstrumentationRunCommand, + goPackageInstrumentationSourceDir, + "GoPackageInstrumentation for $instrumentationTarget", + environment + ) + return parseFromJsonOrFail(instrumentationResultFile) + } finally { + instrumentationTargetFile.delete() + instrumentationResultFile.delete() + goPackageInstrumentationSourceDir.deleteRecursively() + } + } + + private fun extractGoPackageInstrumentationDirectory(): File { + val sourceDirectoryName = "go_package_instrumentation" + val classLoader = GoPackageInstrumentation::class.java.classLoader + + val containingResourceFile = classLoader.scanForResourcesContaining(sourceDirectoryName).firstOrNull() ?: error( + "Can't find resource containing $sourceDirectoryName directory." + ) + if (containingResourceFile.extension != "jar") { + error("Resource for $sourceDirectoryName directory is expected to be JAR: others are not supported yet.") + } + + val archiveFilePath = containingResourceFile.toPath() + return FileUtil.extractDirectoryFromArchive(archiveFilePath, sourceDirectoryName)?.toFile() + ?: error("Can't find $sourceDirectoryName directory at the top level of JAR ${archiveFilePath.toAbsolutePath()}.") + } + + private fun getGoPackageInstrumentationFilesNames(): List { + return listOf( + "main.go", + "instrumentator.go", + "instrumentation_target.go", + "instrumentation_result.go", + ) + } + + private fun createInstrumentationTargetFileName(): String { + return "ut_go_instrumentation_target.json" + } + + private fun createInstrumentationResultFileName(): String { + return "ut_go_instrumentation_result.json" + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/gocodeinstrumentation/InstrumentationResult.kt b/utbot-go/src/main/kotlin/org/utbot/go/gocodeinstrumentation/InstrumentationResult.kt new file mode 100644 index 0000000000..0e36539af1 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/gocodeinstrumentation/InstrumentationResult.kt @@ -0,0 +1,7 @@ +package org.utbot.go.gocodeinstrumentation + +data class InstrumentationResult( + val absolutePathToInstrumentedPackage: String, + val absolutePathToInstrumentedModule: String, + val testedFunctionsToCounters: Map> +) \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/gocodeinstrumentation/InstrumentationTarget.kt b/utbot-go/src/main/kotlin/org/utbot/go/gocodeinstrumentation/InstrumentationTarget.kt new file mode 100644 index 0000000000..2319b8333b --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/gocodeinstrumentation/InstrumentationTarget.kt @@ -0,0 +1,3 @@ +package org.utbot.go.gocodeinstrumentation + +internal data class InstrumentationTarget(val absolutePackagePath: String, val testedFunctions: List) \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/imports/GoImportsResolver.kt b/utbot-go/src/main/kotlin/org/utbot/go/imports/GoImportsResolver.kt new file mode 100644 index 0000000000..1a20c3ef8c --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/imports/GoImportsResolver.kt @@ -0,0 +1,41 @@ +package org.utbot.go.imports + +import org.utbot.go.api.util.getAllVisibleNamedTypes +import org.utbot.go.framework.api.go.GoImport +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.framework.api.go.GoTypeId + +object GoImportsResolver { + + fun resolveImportsBasedOnTypes( + types: List, + sourcePackage: GoPackage, + busyImports: Set = emptySet() + ): Set = resolveImportsBasedOnRequiredPackages( + types.getAllVisibleNamedTypes(sourcePackage).map { it.sourcePackage }.toSet(), sourcePackage, busyImports + ) + + fun resolveImportsBasedOnRequiredPackages( + requiredPackages: Set, + sourcePackage: GoPackage, + busyImports: Set = emptySet() + ): Set { + val result = busyImports.associateBy { it.goPackage }.toMutableMap() + val busyAliases = busyImports.map { it.alias ?: it.goPackage.name }.toMutableSet() + requiredPackages.distinct().filter { it != sourcePackage && !it.isBuiltin && it !in result } + .forEach { goPackage -> + val alias = if (goPackage.name in busyAliases) { + var n = 1 + while (goPackage.name + n in busyAliases) { + n++ + } + goPackage.name + n + } else { + null + } + busyAliases += alias ?: goPackage.name + result[goPackage] = GoImport(goPackage, alias) + } + return result.values.toSet() + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/logic/AbstractGoUtTestsGenerationController.kt b/utbot-go/src/main/kotlin/org/utbot/go/logic/AbstractGoUtTestsGenerationController.kt new file mode 100644 index 0000000000..01adeea072 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/logic/AbstractGoUtTestsGenerationController.kt @@ -0,0 +1,133 @@ +package org.utbot.go.logic + +import org.utbot.go.api.GoUtFile +import org.utbot.go.api.GoUtFunction +import org.utbot.go.api.GoUtFuzzedFunctionTestCase +import org.utbot.go.api.util.intSize +import org.utbot.go.gocodeanalyzer.GoSourceCodeAnalyzer +import org.utbot.go.gocodeinstrumentation.GoPackageInstrumentation +import org.utbot.go.gocodeinstrumentation.InstrumentationResult +import org.utbot.go.simplecodegeneration.GoTestCasesCodeGenerator +import java.nio.file.Path + +abstract class AbstractGoUtTestsGenerationController { + + fun generateTests( + selectedFunctionNamesBySourceFiles: Map>, + selectedMethodNamesBySourceFiles: Map>, + testsGenerationConfig: GoUtTestsGenerationConfig, + isCanceled: () -> Boolean = { false } + ) { + if (!onSourceCodeAnalysisStart(selectedFunctionNamesBySourceFiles, selectedMethodNamesBySourceFiles)) return + val analysisResults = GoSourceCodeAnalyzer.analyzeGoSourceFilesForFunctions( + selectedFunctionNamesBySourceFiles, + selectedMethodNamesBySourceFiles, + testsGenerationConfig.goExecutableAbsolutePath, + testsGenerationConfig.gopathAbsolutePath + ) + + if (!onSourceCodeAnalysisFinished(analysisResults)) return + + if (!onPackageInstrumentationStart()) return + val instrumentationResults = mutableMapOf() + analysisResults.forEach { (file, analysisResult) -> + val absoluteDirectoryPath = file.absoluteDirectoryPath + if (instrumentationResults[absoluteDirectoryPath] == null) { + instrumentationResults[absoluteDirectoryPath] = GoPackageInstrumentation.instrumentGoPackage( + testedFunctions = analysisResult.functions.map { it.name }, + absoluteDirectoryPath = absoluteDirectoryPath, + goExecutableAbsolutePath = testsGenerationConfig.goExecutableAbsolutePath, + gopathAbsolutePath = testsGenerationConfig.gopathAbsolutePath + ) + } + } + if (!onPackageInstrumentationFinished()) return + + val numOfFunctions = analysisResults.values + .map { it.functions.size } + .reduce { acc, numOfFunctions -> acc + numOfFunctions } + val functionTimeoutStepMillis = testsGenerationConfig.allFunctionExecutionTimeoutMillis / numOfFunctions + var startTimeMillis = System.currentTimeMillis() + val testCasesBySourceFiles = analysisResults.mapValues { (sourceFile, analysisResult) -> + val functions = analysisResult.functions + if (!onTestCasesGenerationForGoSourceFileFunctionsStart(sourceFile, functions)) return + val (absolutePathToInstrumentedPackage, absolutePathToInstrumentedModule, needToCoverLines) = + instrumentationResults[sourceFile.absoluteDirectoryPath]!! + GoTestCasesGenerator.generateTestCasesForGoSourceFileFunctions( + sourceFile, + functions, + absolutePathToInstrumentedPackage, + absolutePathToInstrumentedModule, + needToCoverLines, + testsGenerationConfig + ) { index -> isCanceled() || System.currentTimeMillis() - (startTimeMillis + (index + 1) * functionTimeoutStepMillis) > 0 } + .also { + startTimeMillis += functionTimeoutStepMillis * functions.size + if (!onTestCasesGenerationForGoSourceFileFunctionsFinished(sourceFile, it)) return + } + } + + testCasesBySourceFiles.forEach { (sourceFile, testCases) -> + if (!onTestCasesFileCodeGenerationStart(sourceFile, testCases)) return + val generatedTestsFileCode = GoTestCasesCodeGenerator.generateTestCasesFileCode(sourceFile, testCases) + if (!onTestCasesFileCodeGenerationFinished(sourceFile, generatedTestsFileCode)) return + } + } + + protected abstract fun onSourceCodeAnalysisStart( + targetFunctionNamesBySourceFiles: Map>, + targetMethodNamesBySourceFiles: Map>, + ): Boolean + + protected abstract fun onSourceCodeAnalysisFinished( + analysisResults: Map + ): Boolean + + protected abstract fun onPackageInstrumentationStart(): Boolean + + protected abstract fun onPackageInstrumentationFinished(): Boolean + + protected abstract fun onTestCasesGenerationForGoSourceFileFunctionsStart( + sourceFile: GoUtFile, + functions: List + ): Boolean + + protected abstract fun onTestCasesGenerationForGoSourceFileFunctionsFinished( + sourceFile: GoUtFile, + testCases: List + ): Boolean + + protected abstract fun onTestCasesFileCodeGenerationStart( + sourceFile: GoUtFile, + testCases: List + ): Boolean + + protected abstract fun onTestCasesFileCodeGenerationFinished( + sourceFile: GoUtFile, + generatedTestsFileCode: String + ): Boolean + + protected fun generateMissingSelectedFunctionsListMessage( + analysisResults: Map, + ): String? { + val missingSelectedFunctions = analysisResults.filter { (_, analysisResult) -> + analysisResult.notSupportedFunctionAndMethodNames.isNotEmpty() || analysisResult.notFoundFunctionAndMethodNames.isNotEmpty() + } + if (missingSelectedFunctions.isEmpty()) { + return null + } + return missingSelectedFunctions.map { (sourceFile, analysisResult) -> + val notSupportedFunctions = analysisResult.notSupportedFunctionAndMethodNames.joinToString(separator = ", ") + val notFoundFunctions = analysisResult.notFoundFunctionAndMethodNames.joinToString(separator = ", ") + val messageSb = StringBuilder() + messageSb.append("File ${sourceFile.absolutePath}") + if (notSupportedFunctions.isNotEmpty()) { + messageSb.append("\n-- contains currently unsupported functions: $notSupportedFunctions") + } + if (notFoundFunctions.isNotEmpty()) { + messageSb.append("\n-- does not contain functions: $notFoundFunctions") + } + messageSb.toString() + }.joinToString(separator = "\n\n", prefix = "\n\n", postfix = "\n\n") + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/logic/GoTestCasesGenerator.kt b/utbot-go/src/main/kotlin/org/utbot/go/logic/GoTestCasesGenerator.kt new file mode 100644 index 0000000000..5c26dc71ad --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/logic/GoTestCasesGenerator.kt @@ -0,0 +1,159 @@ +package org.utbot.go.logic + +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.catch +import mu.KotlinLogging +import org.utbot.common.isWindows +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.go.CoveredLines +import org.utbot.go.GoEngine +import org.utbot.go.api.ExecutionResults +import org.utbot.go.api.GoUtFile +import org.utbot.go.api.GoUtFunction +import org.utbot.go.api.GoUtFuzzedFunctionTestCase +import org.utbot.go.imports.GoImportsResolver +import org.utbot.go.util.executeCommandByNewProcessOrFail +import org.utbot.go.util.modifyEnvironment +import org.utbot.go.worker.GoWorker +import org.utbot.go.worker.GoWorkerCodeGenerationHelper +import java.io.File +import java.net.ServerSocket +import java.nio.file.Path +import kotlin.system.measureTimeMillis + +val logger = KotlinLogging.logger {} + +object GoTestCasesGenerator { + + fun generateTestCasesForGoSourceFileFunctions( + sourceFile: GoUtFile, + functions: List, + absolutePathToInstrumentedPackage: String, + absolutePathToInstrumentedModule: String, + needToCoverLines: Map>, + testsGenerationConfig: GoUtTestsGenerationConfig, + timeoutExceededOrIsCanceled: (index: Int) -> Boolean = { false }, + ): List = ServerSocket(0).use { serverSocket -> + val allTestCases = mutableListOf() + var testFile: File? = null + try { + // creating files for workers + val types = functions.flatMap { it.parameters }.map { it.type } + val imports = GoImportsResolver.resolveImportsBasedOnTypes( + types, sourceFile.sourcePackage, GoWorkerCodeGenerationHelper.alwaysRequiredImports + ) + val aliases = imports.filter { it.alias != null }.associate { it.goPackage to it.alias } + GoWorkerCodeGenerationHelper.createFileToExecute( + sourceFile, + functions, + absolutePathToInstrumentedPackage, + testsGenerationConfig.eachFunctionExecutionTimeoutMillis, + serverSocket.localPort, + imports + ) + GoWorkerCodeGenerationHelper.createFileWithCoverTab( + sourceFile, absolutePathToInstrumentedPackage + ) + + // compiling the test binary + testFile = run { + if (isWindows) { + Path.of(sourceFile.absoluteDirectoryPath, "utbot_go_test.exe") + } else { + Path.of(sourceFile.absoluteDirectoryPath, "utbot_go_test") + } + }.toFile() + val buildCommand = listOf( + testsGenerationConfig.goExecutableAbsolutePath.toString(), "test", "-c", "-o", testFile.absolutePath + ) + val environment = modifyEnvironment( + testsGenerationConfig.goExecutableAbsolutePath, + testsGenerationConfig.gopathAbsolutePath + ) + logger.debug { "Compiling the test binary - started" } + val compilingTestBinaryTime = measureTimeMillis { + executeCommandByNewProcessOrFail( + buildCommand, + File(absolutePathToInstrumentedPackage), + "compiling the test binary to the $testFile file", + environment + ) + } + logger.debug { "Compiling the test binary - completed in [$compilingTestBinaryTime] (ms)" } + + // starting worker processes + logger.debug { "Creation of workers - started" } + val creationOfWorkersStartTime = System.currentTimeMillis() + val workers = runBlocking { + val testFunctionName = GoWorkerCodeGenerationHelper.workerTestFunctionName + val goPackage = sourceFile.sourcePackage + val sourceFileDir = File(sourceFile.absoluteDirectoryPath) + return@runBlocking (1..testsGenerationConfig.numberOfFuzzingProcess).map { + async(Dispatchers.IO) { + GoWorker.createWorker( + testFunctionName, + testFile.absolutePath, + goPackage, + testsGenerationConfig.goExecutableAbsolutePath, + testsGenerationConfig.gopathAbsolutePath, + sourceFileDir, + serverSocket + ) + } + }.awaitAll() + } + logger.debug { "Creation of workers - completed in [${System.currentTimeMillis() - creationOfWorkersStartTime}] (ms)" } + + // fuzzing + functions.forEachIndexed { index, function -> + if (timeoutExceededOrIsCanceled(index)) return@forEachIndexed + val coveredLinesToExecutionResults = mutableMapOf() + val engine = GoEngine( + workers = workers, + functionUnderTest = function, + needToCoverLines = needToCoverLines[function.name]!!.toSet(), + aliases = aliases, + functionExecutionTimeoutMillis = testsGenerationConfig.eachFunctionExecutionTimeoutMillis, + mode = testsGenerationConfig.mode, + ) { timeoutExceededOrIsCanceled(index) } + logger.info { "Fuzzing for function [${function.name}] - started" } + val totalFuzzingTime = runBlocking { + measureTimeMillis { + engine.fuzzing().catch { + logger.error { "Error in flow: ${it.message}" } + }.collect { + it.entries.forEach { (coveredLines, executionResults) -> + if (coveredLinesToExecutionResults[coveredLines] == null) { + coveredLinesToExecutionResults[coveredLines] = executionResults + } else { + coveredLinesToExecutionResults[coveredLines]!!.update(executionResults) + } + } + } + } + } + val numberOfExecutionsPerSecond = if (totalFuzzingTime / 1000 != 0L) { + (engine.numberOfFunctionExecutions.get() / (totalFuzzingTime / 1000)).toString() + } else { + ">${engine.numberOfFunctionExecutions}" + } + logger.debug { "Number of function executions - [${engine.numberOfFunctionExecutions}] ($numberOfExecutionsPerSecond/sec)" } + val testCases = coveredLinesToExecutionResults.values.flatMap { it.getTestCases() } + logger.info { "Fuzzing for function [${function.name}] - completed in [$totalFuzzingTime] (ms). Generated [${testCases.size}] test cases" } + allTestCases += testCases + } + runBlocking { + workers.map { launch(Dispatchers.IO) { it.close() } }.joinAll() + } + } catch (e: TimeoutException) { + logger.error { e.message } + } catch (e: RuntimeException) { + logger.error { e.message } + } finally { + // delete test file and directory with instrumented packages + testFile?.delete() + File(absolutePathToInstrumentedModule).delete() + } + return allTestCases + } +} diff --git a/utbot-go/src/main/kotlin/org/utbot/go/logic/GoUtTestsGenerationConfig.kt b/utbot-go/src/main/kotlin/org/utbot/go/logic/GoUtTestsGenerationConfig.kt new file mode 100644 index 0000000000..40527f1584 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/logic/GoUtTestsGenerationConfig.kt @@ -0,0 +1,23 @@ +package org.utbot.go.logic + +import java.nio.file.Path + +enum class TestsGenerationMode { + DEFAULT, FUZZING_MODE +} + +class GoUtTestsGenerationConfig( + val goExecutableAbsolutePath: Path, + val gopathAbsolutePath: Path, + val numberOfFuzzingProcess: Int, + val mode: TestsGenerationMode, + val eachFunctionExecutionTimeoutMillis: Long, + val allFunctionExecutionTimeoutMillis: Long +) { + + companion object Constants { + const val DEFAULT_NUMBER_OF_FUZZING_PROCESSES: Int = 8 + const val DEFAULT_ALL_EXECUTION_TIMEOUT_MILLIS: Long = 60000 + const val DEFAULT_EACH_EXECUTION_TIMEOUT_MILLIS: Long = 1000 + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoFileCodeBuilder.kt b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoFileCodeBuilder.kt new file mode 100644 index 0000000000..a48fe78110 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoFileCodeBuilder.kt @@ -0,0 +1,40 @@ +package org.utbot.go.simplecodegeneration + +import org.utbot.go.framework.api.go.GoImport +import org.utbot.go.framework.api.go.GoPackage + +class GoFileCodeBuilder( + sourcePackage: GoPackage, + imports: Set, +) { + private val packageLine: String = "package ${sourcePackage.name}" + private val importLines: String = importLines(imports) + private val topLevelElements: MutableList = mutableListOf() + + private fun importLines(imports: Set): String { + if (imports.isEmpty()) return "" + if (imports.size == 1) { + return "import ${imports.first()}" + } + + return imports.sortedWith(compareBy { it.goPackage.path }.thenBy { it.alias }) + .joinToString(separator = "", prefix = "import (\n", postfix = ")") { + "\t$it\n" + } + } + + fun buildCodeString(): String { + if (importLines.isEmpty()) { + return "$packageLine\n\n${topLevelElements.joinToString(separator = "\n\n")}" + } + return "$packageLine\n\n$importLines\n\n${topLevelElements.joinToString(separator = "\n\n")}" + } + + fun addTopLevelElements(vararg elements: String) { + topLevelElements.addAll(elements) + } + + fun addTopLevelElements(elements: Iterable) { + topLevelElements.addAll(elements) + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoTestCasesCodeGenerator.kt b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoTestCasesCodeGenerator.kt new file mode 100644 index 0000000000..cd7cbfdc90 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoTestCasesCodeGenerator.kt @@ -0,0 +1,455 @@ +package org.utbot.go.simplecodegeneration + +import org.utbot.go.api.* +import org.utbot.go.api.util.containsNaNOrInf +import org.utbot.go.api.util.goBoolTypeId +import org.utbot.go.api.util.goFloat64TypeId +import org.utbot.go.api.util.goStringTypeId +import org.utbot.go.framework.api.go.GoImport +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel +import org.utbot.go.imports.GoImportsResolver + +object GoTestCasesCodeGenerator { + + private const val prefixOfVariableNames = "arg" + + private val alwaysRequiredImports = setOf( + GoImport(GoPackage("assert", "github.com/stretchr/testify/assert")), + GoImport(GoPackage("testing", "testing")) + ) + + private data class Variable(val name: String, val type: GoTypeId, val value: GoUtModel) + + fun generateTestCasesFileCode(sourceFile: GoUtFile, testCases: List): String { + val destinationPackage = sourceFile.sourcePackage + val imports = if (testCases.isEmpty() || testCases.all { it.executionResult is GoUtTimeoutExceeded }) { + emptySet() + } else { + val requiredPackages = mutableSetOf() + testCases.forEach { testCase -> + testCase.parametersValues.forEach { + requiredPackages += it.getRequiredPackages(destinationPackage) + } + when (val executionResult = testCase.executionResult) { + is GoUtExecutionCompleted -> executionResult.models.forEach { + requiredPackages += it.getRequiredPackages(destinationPackage) + } + + is GoUtPanicFailure -> requiredPackages += executionResult.panicValue.getRequiredPackages( + destinationPackage + ) + } + } + + GoImportsResolver.resolveImportsBasedOnRequiredPackages( + requiredPackages, destinationPackage, alwaysRequiredImports + ) + } + val fileBuilder = GoFileCodeBuilder(destinationPackage, imports) + val aliases = imports.associate { (goPackage, alias) -> goPackage to alias } + val goUtModelToCodeConverter = GoUtModelToCodeConverter(destinationPackage, aliases) + + fun List.generateTestFunctions( + generateTestFunctionForTestCase: (GoUtFuzzedFunctionTestCase, Int?, GoUtModelToCodeConverter) -> String, + ) { + this.forEachIndexed { testIndex, testCase -> + val testIndexToShow = if (this.size == 1) null else testIndex + 1 + val testFunctionCode = + generateTestFunctionForTestCase(testCase, testIndexToShow, goUtModelToCodeConverter) + fileBuilder.addTopLevelElements(testFunctionCode) + } + } + + testCases.groupBy { it.function }.forEach { (_, functionTestCases) -> + functionTestCases.filter { it.executionResult is GoUtExecutionCompleted } + .generateTestFunctions(::generateTestFunctionForCompletedExecutionTestCase) + functionTestCases.filter { it.executionResult is GoUtPanicFailure } + .generateTestFunctions(::generateTestFunctionForPanicFailureTestCase) + functionTestCases.filter { it.executionResult is GoUtTimeoutExceeded } + .generateTestFunctions(::generateTestFunctionForTimeoutExceededTestCase) + } + + return fileBuilder.buildCodeString() + } + + private fun generateTestFunctionForCompletedExecutionTestCase( + testCase: GoUtFuzzedFunctionTestCase, testIndexToShow: Int?, goUtModelToCodeConverter: GoUtModelToCodeConverter + ): String { + val (fuzzedFunction, executionResult) = testCase + val function = fuzzedFunction.function + + val testFunctionNamePostfix = if (executionResult is GoUtExecutionWithNonNilError) { + "WithNonNilError" + } else { + "" + } + val testIndexToShowString = testIndexToShow ?: "" + val testFunctionSignatureDeclaration = + "func Test${function.name.replaceFirstChar(Char::titlecaseChar)}${testFunctionNamePostfix}ByUtGoFuzzer$testIndexToShowString(t *testing.T)" + + val variables: List = generateVariables(fuzzedFunction) + val variablesDeclarationAndInitialization = + generateVariablesDeclarationAndInitialization(variables, goUtModelToCodeConverter) + + if (function.results.isEmpty()) { + val actualFunctionCall = generateFuzzedFunctionCall(function, variables) + val testFunctionBody = buildString { + if (variablesDeclarationAndInitialization != "\n") { + append(variablesDeclarationAndInitialization) + } + appendLine("\tassert.NotPanics(t, func() {") + appendLine("\t\t$actualFunctionCall") + appendLine("\t})") + + } + return "$testFunctionSignatureDeclaration {\n$testFunctionBody}" + } + + val resultTypes = function.results.map { it.type } + val doResultTypesImplementError = resultTypes.map { it.implementsError } + val errorVariablesNumber = doResultTypesImplementError.count { it } + val commonVariablesNumber = resultTypes.size - errorVariablesNumber + val actualResultVariablesNames = run { + var errorVariablesIndex = 0 + var commonVariablesIndex = 0 + doResultTypesImplementError.map { implementsError -> + if (implementsError) { + "actualErr${if (errorVariablesNumber > 1) errorVariablesIndex++ else ""}" + } else { + "actualVal${if (commonVariablesNumber > 1) commonVariablesIndex++ else ""}" + } + } + } + val actualFunctionCall = generateFuzzedFunctionCallSavedToVariables( + actualResultVariablesNames, fuzzedFunction, variables + ) + + val expectedResultValues = (executionResult as GoUtExecutionCompleted).models + val expectedResultVariables = run { + var errorVariablesIndex = 0 + var commonVariablesIndex = 0 + resultTypes.zip(expectedResultValues).map { (type, value) -> + if (modelIsNilOrBoolOrFloatNanOrFloatInf(value)) { + return@map null + } + val name = if (type.implementsError) { + "expectedErrorMessage${if (errorVariablesNumber > 1) errorVariablesIndex++ else ""}" + } else { + "expectedVal${if (commonVariablesNumber > 1) commonVariablesIndex++ else ""}" + } + Variable(name, type, value) + } + } + + val expectedVariablesDeclarationAndInitialization = + generateVariablesDeclarationAndInitialization(expectedResultVariables, goUtModelToCodeConverter) + + val (assertionName, assertionTParameter) = if (expectedResultValues.size > 1 || expectedResultValues.any { it.isComplexModelAndNeedsSeparateAssertions() }) { + "assertMultiple" to "" + } else { + "assert" to "t, " + } + val allAssertionCalls = + actualResultVariablesNames.zip(expectedResultVariables.map { it?.name ?: "" }).zip(expectedResultValues) + .flatMap { (actualAndExpectedResultVariableName, expectedResultValue) -> + val (actualResultVariableName, expectedResultVariableName) = actualAndExpectedResultVariableName + + val assertionCalls = if (expectedResultValue.isComplexModelAndNeedsSeparateAssertions()) { + listOf( + generateCompletedExecutionAssertionCall( + expectedModel = expectedResultValue, + expectedResultCode = "real($expectedResultVariableName)", + actualResultCode = "real($actualResultVariableName)", + doesReturnTypeImplementError = expectedResultValue.typeId.implementsError, + assertionTParameter + ), generateCompletedExecutionAssertionCall( + expectedModel = expectedResultValue, + expectedResultCode = "imag($expectedResultVariableName)", + actualResultCode = "imag($actualResultVariableName)", + doesReturnTypeImplementError = expectedResultValue.typeId.implementsError, + assertionTParameter + ) + ) + } else { + listOf( + generateCompletedExecutionAssertionCall( + expectedModel = expectedResultValue, + expectedResultCode = expectedResultVariableName, + actualResultCode = actualResultVariableName, + doesReturnTypeImplementError = expectedResultValue.typeId.implementsError, + assertionTParameter + ) + ) + } + assertionCalls.map { "$assertionName.$it" } + } + + val testFunctionBody = buildString { + if (variablesDeclarationAndInitialization != "\n") { + append(variablesDeclarationAndInitialization) + } + append("\t$actualFunctionCall\n\n") + if (expectedVariablesDeclarationAndInitialization != "\n") { + append(expectedVariablesDeclarationAndInitialization) + } + if (expectedResultValues.size > 1 || expectedResultValues.any { it.isComplexModelAndNeedsSeparateAssertions() }) { + append("\tassertMultiple := assert.New(t)\n") + } + allAssertionCalls.forEach { + append("\t$it\n") + } + } + + return "$testFunctionSignatureDeclaration {\n$testFunctionBody}" + } + + private fun GoUtModel.isComplexModelAndNeedsSeparateAssertions(): Boolean = + this is GoUtComplexModel && this.containsNaNOrInf() + + private fun generateCompletedExecutionAssertionCall( + expectedModel: GoUtModel, + expectedResultCode: String, + actualResultCode: String, + doesReturnTypeImplementError: Boolean, + assertionTParameter: String, + ): String { + if (expectedModel is GoUtNilModel || (expectedModel is GoUtNamedModel && expectedModel.value is GoUtNilModel)) { + return "Nil($assertionTParameter$actualResultCode)" + } + if (doesReturnTypeImplementError && (expectedModel is GoUtNamedModel && expectedModel.value.typeId == goStringTypeId)) { + return "ErrorContains($assertionTParameter$actualResultCode, ${expectedResultCode})" + } + if (expectedModel is GoUtPrimitiveModel && expectedModel.typeId == goBoolTypeId) { + return if (expectedModel.value == true) { + "True($assertionTParameter$actualResultCode)" + } else { + "False($assertionTParameter$actualResultCode)" + } + } + if (expectedModel is GoUtFloatNaNModel) { + val castedActualResultCode = generateCastIfNeed(goFloat64TypeId, expectedModel.typeId, actualResultCode) + return "True(${assertionTParameter}math.IsNaN($castedActualResultCode))" + } + if (expectedModel is GoUtFloatInfModel) { + val castedActualResultCode = generateCastIfNeed(goFloat64TypeId, expectedModel.typeId, actualResultCode) + return "True(${assertionTParameter}math.IsInf($castedActualResultCode, ${expectedModel.sign}))" + } + val prefix = if (!expectedModel.isComparable()) "Not" else "" + return "${prefix}Equal($assertionTParameter$expectedResultCode, $actualResultCode)" + } + + private fun generateTestFunctionForPanicFailureTestCase( + testCase: GoUtFuzzedFunctionTestCase, testIndexToShow: Int?, goUtModelToCodeConverter: GoUtModelToCodeConverter + ): String { + val (fuzzedFunction, executionResult) = testCase + val function = fuzzedFunction.function + + val testIndexToShowString = testIndexToShow ?: "" + val testFunctionSignatureDeclaration = + "func Test${function.name.replaceFirstChar(Char::titlecaseChar)}PanicsByUtGoFuzzer$testIndexToShowString(t *testing.T)" + + val variables: List = generateVariables(fuzzedFunction) + val variablesDeclaration = generateVariablesDeclarationAndInitialization(variables, goUtModelToCodeConverter) + + val actualFunctionCall = generateFuzzedFunctionCall(function, variables) + val actualFunctionCallLambda = buildString { + appendLine("func() {") + if (function.results.isNotEmpty()) { + appendLine("\t\t${function.results.joinToString { "_" }} = $actualFunctionCall") + } else { + appendLine("\t\t$actualFunctionCall") + } + append("\t}") + } + val testFunctionBodySb = StringBuilder(variablesDeclaration) + val (expectedPanicValue, isErrorMessage) = (executionResult as GoUtPanicFailure) + val panicValueIsComparable = expectedPanicValue.isComparable() + if (isErrorMessage) { + val errorMessageVariable = Variable("expectedErrorMessage", expectedPanicValue.typeId, expectedPanicValue) + + val errorMessageToGoCode = goUtModelToCodeConverter.toGoCode(errorMessageVariable.value) + testFunctionBodySb.append("\t${errorMessageVariable.name} := ${errorMessageToGoCode}\n\n") + testFunctionBodySb.append("\tassert.PanicsWithError(t, ${errorMessageVariable.name}, $actualFunctionCallLambda)\n") + } else if (panicValueIsComparable || expectedPanicValue is GoUtNilModel) { + val panicValueVariable = Variable("expectedVal", expectedPanicValue.typeId, expectedPanicValue) + + val panicValueToGoCode = goUtModelToCodeConverter.toGoCode(panicValueVariable.value) + testFunctionBodySb.append("\t${panicValueVariable.name} := $panicValueToGoCode\n\n") + testFunctionBodySb.append("\tassert.PanicsWithValue(t, ${panicValueVariable.name}, $actualFunctionCallLambda)\n") + } else { + testFunctionBodySb.append("\tassert.Panics(t, $actualFunctionCallLambda)\n") + } + + val testFunctionBody = testFunctionBodySb.toString() + return "$testFunctionSignatureDeclaration {\n$testFunctionBody}" + } + + private fun generateTestFunctionForTimeoutExceededTestCase( + testCase: GoUtFuzzedFunctionTestCase, + @Suppress("UNUSED_PARAMETER") testIndexToShow: Int?, + goUtModelToCodeConverter: GoUtModelToCodeConverter + ): String { + val (fuzzedFunction, executionResult) = testCase + val functionName = fuzzedFunction.function.name + val fuzzedParametersToString = fuzzedFunction.parametersValues.joinToString { + goUtModelToCodeConverter.toGoCode(it) + } + val actualFunctionCall = "$functionName($fuzzedParametersToString)" + val exceededTimeoutMillis = (executionResult as GoUtTimeoutExceeded).timeoutMillis + return "// $actualFunctionCall exceeded $exceededTimeoutMillis ms timeout" + } + + private fun generateVariables(fuzzedFunction: GoUtFuzzedFunction): List { + val parameters = fuzzedFunction.function.let { function -> + if (function.isMethod) { + listOf(function.receiver!!) + function.parameters + } else { + function.parameters + } + } + val parameterNames = parameters.map { it.name } + val parameterValues = fuzzedFunction.parametersValues + val parametersTypes = parameters.map { it.type } + + val busyVariableNames = parameterNames.filter { it != "" && it != "_" }.toMutableSet() + val variablesNumber = parameterNames.filter { it == "" || it == "_" }.size + var variablesIndex = 1 + + return parameterNames.zip(parametersTypes).zip(parameterValues) + .map { (nameAndType, value) -> + val (name, type) = nameAndType + val variableName = if (name == "" || name == "_") { + if (variablesNumber == 1 && prefixOfVariableNames !in busyVariableNames) { + prefixOfVariableNames + } else { + while ("$prefixOfVariableNames$variablesIndex" in busyVariableNames) { + variablesIndex++ + } + "$prefixOfVariableNames$variablesIndex" + } + } else { + name + } + busyVariableNames.add(variableName) + Variable(variableName, type, value) + } + } + + private fun generateChannelInitialization( + nameOfVariable: String, + model: GoUtChanModel, + goUtModelToCodeConverter: GoUtModelToCodeConverter + ): String = model.getElements().joinToString(separator = "") { + "\t$nameOfVariable <- ${goUtModelToCodeConverter.toGoCode(it)}\n" + } + "\tclose($nameOfVariable)\n" + + private fun generatePointerToPrimitiveInitialization( + nameOfVariable: String, + model: GoUtPrimitiveModel, + goUtModelToCodeConverter: GoUtModelToCodeConverter + ): String = "\t*$nameOfVariable = ${goUtModelToCodeConverter.toGoCodeWithoutTypeName(model)}\n" + + private fun generateVariableDeclaration( + variable: Variable, + goUtModelToCodeConverter: GoUtModelToCodeConverter + ): String { + val (name, type, value) = variable + return if (type.implementsError && (value is GoUtNamedModel && value.value.typeId == goStringTypeId)) { + "\t$name := ${goUtModelToCodeConverter.toGoCode(value.value)}\n" + } else { + "\t$name := ${goUtModelToCodeConverter.toGoCode(value)}\n" + } + } + + private fun generateVariableInitialization( + variable: Variable, + goUtModelToCodeConverter: GoUtModelToCodeConverter + ): String { + val (name, _, value) = variable + return when (value) { + is GoUtChanModel -> generateChannelInitialization(name, value, goUtModelToCodeConverter) + + is GoUtNamedModel -> if (value.value is GoUtChanModel) { + generateChannelInitialization(name, value.value as GoUtChanModel, goUtModelToCodeConverter) + } else { + "" + } + + is GoUtPointerModel -> if (value.value is GoUtPrimitiveModel) { + generatePointerToPrimitiveInitialization( + name, + value.value as GoUtPrimitiveModel, + goUtModelToCodeConverter + ) + } else if (value.value is GoUtNamedModel && (value.value as GoUtNamedModel).value is GoUtPrimitiveModel) { + generatePointerToPrimitiveInitialization( + name, + (value.value as GoUtNamedModel).value as GoUtPrimitiveModel, + goUtModelToCodeConverter + ) + } else { + "" + } + + else -> "" + } + } + + private fun generateVariablesDeclarationAndInitialization( + variables: List, goUtModelToCodeConverter: GoUtModelToCodeConverter + ): String { + val vars = variables.filterNotNull() + return if (vars.isNotEmpty()) { + vars.joinToString(separator = "", postfix = "\n") { variable -> + val declaration = generateVariableDeclaration(variable, goUtModelToCodeConverter) + val initialization = generateVariableInitialization(variable, goUtModelToCodeConverter) + declaration + initialization + } + } else { + "" + } + } + + private fun generateFuzzedFunctionCall(function: GoUtFunction, variables: List): String { + return if (function.isMethod) { + val fuzzedParametersToString = variables.drop(1).joinToString(prefix = "(", postfix = ")") { + it.name + } + "${variables[0].name}.${function.name}$fuzzedParametersToString" + } else { + val fuzzedParametersToString = variables.joinToString(prefix = "(", postfix = ")") { + it.name + } + "${function.name}$fuzzedParametersToString" + } + } + + private fun generateVariablesDeclarationTo(variablesNames: List, expression: String): String { + val variables = variablesNames.joinToString() + return "$variables := $expression" + } + + private fun generateFuzzedFunctionCallSavedToVariables( + variablesNames: List, fuzzedFunction: GoUtFuzzedFunction, variables: List + ): String = generateVariablesDeclarationTo( + variablesNames, expression = generateFuzzedFunctionCall(fuzzedFunction.function, variables) + ) + + private fun modelIsNilOrBoolOrFloatNanOrFloatInf(value: GoUtModel): Boolean = value is GoUtNilModel + || (value is GoUtNamedModel && value.value is GoUtNilModel) + || value.typeId == goBoolTypeId + || value is GoUtFloatNaNModel + || value is GoUtFloatInfModel + + private fun generateCastIfNeed( + toTypeId: GoPrimitiveTypeId, expressionType: GoPrimitiveTypeId, expression: String + ): String { + return if (expressionType != toTypeId) { + "${toTypeId.name}($expression)" + } else { + expression + } + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoUtModelToCodeConverter.kt b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoUtModelToCodeConverter.kt new file mode 100644 index 0000000000..233cb67458 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/simplecodegeneration/GoUtModelToCodeConverter.kt @@ -0,0 +1,151 @@ +package org.utbot.go.simplecodegeneration + +import org.utbot.go.api.* +import org.utbot.go.api.util.goDefaultValueModel +import org.utbot.go.api.util.goStringTypeId +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel + +class GoUtModelToCodeConverter( + private val destinationPackage: GoPackage, + private val aliases: Map +) { + + fun toGoCode(model: GoUtModel): String = when (model) { + is GoUtNilModel -> nilModelToGoCode(model) + + is GoUtPrimitiveModel -> when (model.explicitCastMode) { + ExplicitCastMode.REQUIRED, ExplicitCastMode.DEPENDS -> primitiveModelToCastedValueGoCode(model) + ExplicitCastMode.NEVER -> primitiveModelToValueGoCode(model) + } + + is GoUtArrayModel -> arrayModelToGoCode(model) + + is GoUtSliceModel -> sliceModelToGoCode(model) + + is GoUtMapModel -> mapModelToGoCode(model) + + is GoUtChanModel -> chanModelToGoCode(model) + + is GoUtNamedModel -> namedModelToGoCode(model) + + is GoUtPointerModel -> pointerModelToGoCode(model) + + else -> error("Converting a ${model.javaClass} to Go code isn't supported") + } + + fun toGoCodeWithoutTypeName(model: GoUtModel): String = when (model) { + is GoUtNilModel -> "nil" + + is GoUtPrimitiveModel -> when (model.explicitCastMode) { + ExplicitCastMode.REQUIRED -> primitiveModelToCastedValueGoCode(model) + ExplicitCastMode.DEPENDS, ExplicitCastMode.NEVER -> primitiveModelToValueGoCode(model) + } + + is GoUtStructModel -> structModelToGoCodeWithoutStructName(model) + + is GoUtArrayModel -> arrayModelToGoCodeWithoutTypeName(model) + + is GoUtSliceModel -> sliceModelToGoCodeWithoutTypeName(model) + + is GoUtMapModel -> mapModelToGoCodeWithoutTypeName(model) + + is GoUtNamedModel -> toGoCodeWithoutTypeName(model.value) + + is GoUtPointerModel -> pointerModelToGoCode(model) + + else -> error("Converting a ${model.javaClass} to Go code isn't supported") + } + + private fun nilModelToGoCode(model: GoUtNilModel): String { + val typeName = model.typeId.getRelativeName(destinationPackage, aliases) + return "($typeName)(nil)" + } + + private fun primitiveModelToValueGoCode(model: GoUtPrimitiveModel): String = when (model) { + is GoUtComplexModel -> complexModeToValueGoCode(model) + else -> if (model.typeId == goStringTypeId) "\"${model.value}\"" else "${model.value}" + } + + private fun primitiveModelToCastedValueGoCode(model: GoUtPrimitiveModel): String { + val typeName = model.typeId.getRelativeName(destinationPackage, aliases) + return "$typeName(${primitiveModelToValueGoCode(model)})" + } + + private fun complexModeToValueGoCode(model: GoUtComplexModel) = + "complex(${toGoCode(model.realValue)}, ${toGoCode(model.imagValue)})" + + private fun structModelToGoCodeWithoutStructName(model: GoUtStructModel): String = + model.value.entries.filter { (fieldId, model) -> model != fieldId.declaringType.goDefaultValueModel() } + .joinToString(prefix = "{", postfix = "}") { (fieldId, model) -> + "${fieldId.name}: ${toGoCode(model)}" + } + + private fun arrayModelToGoCode(model: GoUtArrayModel): String { + val typeName = model.typeId.getRelativeName(destinationPackage, aliases) + return typeName + arrayModelToGoCodeWithoutTypeName(model) + } + + private fun arrayModelToGoCodeWithoutTypeName(model: GoUtArrayModel): String = + model.getElements().joinToString(prefix = "{", postfix = "}") { + toGoCodeWithoutTypeName(it) + } + + private fun sliceModelToGoCode(model: GoUtSliceModel): String { + val typeName = model.typeId.getRelativeName(destinationPackage, aliases) + return typeName + sliceModelToGoCodeWithoutTypeName(model) + } + + private fun sliceModelToGoCodeWithoutTypeName(model: GoUtSliceModel): String = + model.getElements().joinToString(prefix = "{", postfix = "}") { + toGoCodeWithoutTypeName(it) + } + + private fun mapModelToGoCode(model: GoUtMapModel): String { + val typeName = model.typeId.getRelativeName(destinationPackage, aliases) + return typeName + mapModelToGoCodeWithoutTypeName(model) + } + + private fun mapModelToGoCodeWithoutTypeName(model: GoUtMapModel): String = + model.value.entries.joinToString(prefix = "{", postfix = "}") { + "${toGoCode(it.key)}: ${toGoCodeWithoutTypeName(it.value)}" + } + + private fun chanModelToGoCode(model: GoUtChanModel): String { + val elemTypeName = model.typeId.elementTypeId!!.getRelativeName(destinationPackage, aliases) + return "make(chan $elemTypeName, ${model.value.size})" + } + + private fun namedModelToGoCode(model: GoUtNamedModel): String { + if (model.value is GoUtNamedModel) { + return toGoCode(model.value) + } + val typeName = model.typeId.getRelativeName(destinationPackage, aliases) + return if (model.value is GoUtPrimitiveModel || model.value is GoUtNilModel) { + "$typeName(${toGoCodeWithoutTypeName(model.value)})" + } else { + "$typeName${toGoCodeWithoutTypeName(model.value)}" + } + } + + private fun pointerToZeroValueOfType(typeId: GoTypeId): String { + val typeName = typeId.getRelativeName(destinationPackage, aliases) + return "new($typeName)" + } + + private fun pointerModelToGoCode(model: GoUtPointerModel): String = when (val value = model.value) { + is GoUtNilModel -> pointerToZeroValueOfType(value.typeId) + is GoUtPrimitiveModel -> pointerToZeroValueOfType(value.typeId) + + is GoUtNamedModel -> { + if (value.value is GoUtPrimitiveModel) { + pointerToZeroValueOfType(value.typeId) + } else { + "&${toGoCode(value)}" + } + } + + else -> "&${toGoCode(value)}" + } +} diff --git a/utbot-go/src/main/kotlin/org/utbot/go/util/JsonUtil.kt b/utbot-go/src/main/kotlin/org/utbot/go/util/JsonUtil.kt new file mode 100644 index 0000000000..47cf0927da --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/util/JsonUtil.kt @@ -0,0 +1,26 @@ +package org.utbot.go.util + +import com.beust.klaxon.Klaxon +import java.io.File + +fun convertObjectToJsonString(targetObject: T): String = Klaxon().toJsonString(targetObject) + +fun writeJsonToFileOrFail(targetObject: T, jsonFile: File) { + val targetObjectAsJson = convertObjectToJsonString(targetObject) + jsonFile.writeText(targetObjectAsJson) +} + +inline fun parseFromJsonOrFail(jsonFile: File): T { + val result = Klaxon().parse(jsonFile) + if (result == null) { + val rawResults = try { + jsonFile.readText() + } catch (exception: Exception) { + null + } + throw RuntimeException( + "Failed to deserialize results: $rawResults" + ) + } + return result +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/util/ProcessExecutionUtil.kt b/utbot-go/src/main/kotlin/org/utbot/go/util/ProcessExecutionUtil.kt new file mode 100644 index 0000000000..4f07143c40 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/util/ProcessExecutionUtil.kt @@ -0,0 +1,61 @@ +package org.utbot.go.util + +import java.io.File +import java.io.InputStreamReader +import java.nio.file.Path + +fun modifyEnvironment(goExecutableAbsolutePath: Path, gopathAbsolutePath: Path): MutableMap { + val environment = System.getenv().toMutableMap().apply { + this["Path"] = + listOfNotNull(goExecutableAbsolutePath.parent, this["Path"]).joinToString(separator = File.pathSeparator) + this["GOROOT"] = goExecutableAbsolutePath.parent.parent.toString() + this["GOPATH"] = gopathAbsolutePath.toString() + } + return environment +} + +fun executeCommandByNewProcessOrFail( + command: List, + workingDirectory: File, + executionTargetName: String, + environment: Map = System.getenv(), + helpMessage: String? = null +) { + val helpMessageLine = if (helpMessage == null) "" else "\n\nHELP: $helpMessage" + val executedProcess = runCatching { + val process = executeCommandByNewProcessOrFailWithoutWaiting(command, workingDirectory, environment) + process.waitFor() + process + }.getOrElse { + throw RuntimeException( + StringBuilder() + .append("Execution of $executionTargetName in child process failed with throwable: ") + .append("$it").append(helpMessageLine) + .toString() + ) + } + val exitCode = executedProcess.exitValue() + if (exitCode != 0) { + val processOutput = InputStreamReader(executedProcess.inputStream).readText() + throw RuntimeException( + StringBuilder() + .append("Execution of $executionTargetName in child process failed with non-zero exit code = $exitCode: ") + .append("\n$processOutput").append(helpMessageLine) + .toString() + ) + } +} + +fun executeCommandByNewProcessOrFailWithoutWaiting( + command: List, + workingDirectory: File, + environment: Map = System.getenv() +): Process { + val processBuilder = ProcessBuilder(command) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectErrorStream(true) + .directory(workingDirectory) + processBuilder.environment().clear() + processBuilder.environment().putAll(environment) + return processBuilder.start() +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/worker/GoCodeTemplates.kt b/utbot-go/src/main/kotlin/org/utbot/go/worker/GoCodeTemplates.kt new file mode 100644 index 0000000000..ce227be1bc --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/worker/GoCodeTemplates.kt @@ -0,0 +1,1431 @@ +package org.utbot.go.worker + +import org.utbot.go.api.GoInterfaceTypeId +import org.utbot.go.api.GoNamedTypeId +import org.utbot.go.api.util.goDefaultValueModel +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.simplecodegeneration.GoUtModelToCodeConverter + +object GoCodeTemplates { + + private val errorMessages = """ + const ( + ErrParsingValue = "failed to parse %s value: %s" + ErrInvalidTypeName = "invalid type name: %s" + ErrStringToReflectTypeFailure = "failed to convert '%s' to reflect.Type: %s" + ErrRawValueToReflectValueFailure = "failed to convert RawValue to reflect.Value: %s" + ErrReflectValueToRawValueFailure = "failed to convert reflect.Value to RawValue: %s" + ) + """.trimIndent() + + private val typeOfExecutionResult = """ + type __TypeOfExecutionResult__ int + + const ( + Completed = iota + PanicFailure + TimeoutExceeded + ) + """.trimIndent() + + private val executionResultStruct = """ + type __ExecutionResult__ struct { + Type __TypeOfExecutionResult__ + ResultValues []reflect.Value + } + """.trimIndent() + + private val testInputStruct = """ + type __TestInput__ struct { + FunctionName string `json:"functionName"` + Arguments []map[string]interface{} `json:"arguments"` + } + """.trimIndent() + + private val rawValueInterface = """ + type __RawValue__ interface { + __toReflectValue__() (reflect.Value, error) + } + """.trimIndent() + + private val primitiveValueStruct = """ + type __PrimitiveValue__ struct { + Type string `json:"type"` + Value string `json:"value"` + } + """.trimIndent() + + + private val parseFloatFunction = """ + func __parseFloat__(s string, bitSize int) (float64, error) { + if s == "NaN" { + return math.NaN(), nil + } + + if s == "+Inf" { + return math.Inf(1), nil + } + + if s == "-Inf" { + return math.Inf(-1), nil + } + + value, err := strconv.ParseFloat(s, bitSize) + if err != nil { + return value, fmt.Errorf("failed to parse float: %s", s) + } + + return value, nil + } + """.trimIndent() + + private val primitiveValueToReflectValueMethod = """ + func (v __PrimitiveValue__) __toReflectValue__() (reflect.Value, error) { + + const complexPartsDelimiter = "@" + + switch v.Type { + case "bool": + value, err := strconv.ParseBool(v.Value) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(value), nil + case "int": + value, err := strconv.Atoi(v.Value) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(value), nil + case "int8": + value, err := strconv.ParseInt(v.Value, 10, 8) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(int8(value)), nil + case "int16": + value, err := strconv.ParseInt(v.Value, 10, 16) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(int16(value)), nil + case "int32": + value, err := strconv.ParseInt(v.Value, 10, 32) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(int32(value)), nil + case "rune": + value, err := strconv.ParseInt(v.Value, 10, 32) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(rune(value)), nil + case "int64": + value, err := strconv.ParseInt(v.Value, 10, 64) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(value), nil + case "byte": + value, err := strconv.ParseUint(v.Value, 10, 8) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(byte(value)), nil + case "uint": + value, err := strconv.ParseUint(v.Value, 10, strconv.IntSize) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(uint(value)), nil + case "uint8": + value, err := strconv.ParseUint(v.Value, 10, 8) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(uint8(value)), nil + case "uint16": + value, err := strconv.ParseUint(v.Value, 10, 16) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(uint16(value)), nil + case "uint32": + value, err := strconv.ParseUint(v.Value, 10, 32) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(uint32(value)), nil + case "uint64": + value, err := strconv.ParseUint(v.Value, 10, 64) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(value), nil + case "float32": + value, err := __parseFloat__(v.Value, 32) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(float32(value)), nil + case "float64": + value, err := __parseFloat__(v.Value, 64) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(value), nil + case "complex64": + splitValue := strings.Split(v.Value, complexPartsDelimiter) + if len(splitValue) != 2 { + return reflect.Value{}, fmt.Errorf("not correct complex64 value: %s", v.Value) + } + realPart, err := __parseFloat__(splitValue[0], 32) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + imaginaryPart, err := __parseFloat__(splitValue[1], 32) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(complex(float32(realPart), float32(imaginaryPart))), nil + case "complex128": + splitValue := strings.Split(v.Value, complexPartsDelimiter) + if len(splitValue) != 2 { + return reflect.Value{}, fmt.Errorf("not correct complex128 value: %s", v.Value) + } + + realPart, err := strconv.ParseFloat(splitValue[0], 64) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + imaginaryPart, err := strconv.ParseFloat(splitValue[1], 64) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(complex(realPart, imaginaryPart)), nil + case "string": + return reflect.ValueOf(v.Value), nil + case "uintptr": + value, err := strconv.ParseUint(v.Value, 10, strconv.IntSize) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrParsingValue, v.Type, err) + } + + return reflect.ValueOf(uintptr(value)), nil + } + return reflect.Value{}, fmt.Errorf("unsupported primitive type: '%s'", v.Type) + } + """.trimIndent() + + private val fieldValueStruct = """ + type __FieldValue__ struct { + Name string `json:"name"` + Value __RawValue__ `json:"value"` + IsExported bool `json:"isExported"` + } + """.trimIndent() + + private val structValueStruct = """ + type __StructValue__ struct { + Type string `json:"type"` + Value []__FieldValue__ `json:"value"` + } + """.trimIndent() + + private val structValueToReflectValueMethod = """ + func (v __StructValue__) __toReflectValue__() (reflect.Value, error) { + structType, err := __convertStringToReflectType__(v.Type) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrStringToReflectTypeFailure, v.Type, err) + } + + structPtr := reflect.New(structType) + + for _, f := range v.Value { + field := structPtr.Elem().FieldByName(f.Name) + + reflectValue, err := f.Value.__toReflectValue__() + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrRawValueToReflectValueFailure, err) + } + + if field.Type().Kind() == reflect.Uintptr { + reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().SetUint(reflectValue.Uint()) + } else { + reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Set(reflectValue) + } + } + + return structPtr.Elem(), nil + } + """.trimIndent() + + private val arrayValueStruct = """ + type __ArrayValue__ struct { + Type string `json:"type"` + ElementType string `json:"elementType"` + Length int `json:"length"` + Value []__RawValue__ `json:"value"` + } + """.trimIndent() + + private val arrayValueToReflectValueMethod = """ + func (v __ArrayValue__) __toReflectValue__() (reflect.Value, error) { + elementType, err := __convertStringToReflectType__(v.ElementType) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrStringToReflectTypeFailure, v.ElementType, err) + } + + arrayType := reflect.ArrayOf(v.Length, elementType) + arrayPtr := reflect.New(arrayType) + + for i := 0; i < v.Length; i++ { + element := arrayPtr.Elem().Index(i) + + reflectValue, err := v.Value[i].__toReflectValue__() + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrRawValueToReflectValueFailure, err) + } + + element.Set(reflectValue) + } + + return arrayPtr.Elem(), nil + } + """.trimIndent() + + private val sliceValueStruct = """ + type __SliceValue__ struct { + Type string `json:"type"` + ElementType string `json:"elementType"` + Length int `json:"length"` + Value []__RawValue__ `json:"value"` + } + """.trimIndent() + + private val sliceValueToReflectValueMethod = """ + func (v __SliceValue__) __toReflectValue__() (reflect.Value, error) { + elementType, err := __convertStringToReflectType__(v.ElementType) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrStringToReflectTypeFailure, v.ElementType, err) + } + + sliceType := reflect.SliceOf(elementType) + slice := reflect.MakeSlice(sliceType, v.Length, v.Length) + slicePtr := reflect.New(slice.Type()) + slicePtr.Elem().Set(slice) + + for i := 0; i < len(v.Value); i++ { + element := slicePtr.Elem().Index(i) + + reflectValue, err := v.Value[i].__toReflectValue__() + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrRawValueToReflectValueFailure, err) + } + + element.Set(reflectValue) + } + + return slicePtr.Elem(), nil + } + """.trimIndent() + + private val keyValueStruct = """ + type __KeyValue__ struct { + Key __RawValue__ `json:"key"` + Value __RawValue__ `json:"value"` + } + """.trimIndent() + + private val mapValueStruct = """ + type __MapValue__ struct { + Type string `json:"type"` + KeyType string `json:"keyType"` + ElementType string `json:"elementType"` + Value []__KeyValue__ `json:"value"` + } + """.trimIndent() + + private val mapValueToReflectValueMethod = """ + func (v __MapValue__) __toReflectValue__() (reflect.Value, error) { + keyType, err := __convertStringToReflectType__(v.KeyType) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrStringToReflectTypeFailure, v.KeyType, err) + } + + elementType, err := __convertStringToReflectType__(v.ElementType) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrStringToReflectTypeFailure, v.ElementType, err) + } + + mapType := reflect.MapOf(keyType, elementType) + m := reflect.MakeMap(mapType) + for _, keyValue := range v.Value { + keyReflectValue, err := keyValue.Key.__toReflectValue__() + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrRawValueToReflectValueFailure, err) + } + + valueReflectValue, err := keyValue.Value.__toReflectValue__() + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrRawValueToReflectValueFailure, err) + } + + m.SetMapIndex(keyReflectValue, valueReflectValue) + } + return m, nil + } + """.trimIndent() + + private val chanValueStruct = """ + type __ChanValue__ struct { + Type string `json:"type"` + ElementType string `json:"elementType"` + Direction string `json:"direction"` + Length int `json:"length"` + Value []__RawValue__ `json:"value"` + } + """.trimIndent() + + private val chanValueToReflectValueMethod = """ + func (v __ChanValue__) __toReflectValue__() (reflect.Value, error) { + elementType, err := __convertStringToReflectType__(v.ElementType) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrStringToReflectTypeFailure, v.ElementType, err) + } + + dir := reflect.BothDir + + chanType := reflect.ChanOf(dir, elementType) + channel := reflect.MakeChan(chanType, v.Length) + + for i := range v.Value { + reflectValue, err := v.Value[i].__toReflectValue__() + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrRawValueToReflectValueFailure, err) + } + + channel.Send(reflectValue) + } + if v.Direction != "SENDONLY" { + channel.Close() + } + + return channel, nil + } + """.trimIndent() + + private val nilValueStruct = """ + type __NilValue__ struct { + Type string `json:"type"` + } + """.trimIndent() + + private val nilValueToReflectValueMethod = """ + func (v __NilValue__) __toReflectValue__() (reflect.Value, error) { + typ, err := __convertStringToReflectType__(v.Type) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrStringToReflectTypeFailure, v.Type, err) + } + + return reflect.Zero(typ), nil + } + """.trimIndent() + + private val namedValueStruct = """ + type __NamedValue__ struct { + Type string `json:"type"` + Value __RawValue__ `json:"value"` + } + """.trimIndent() + + private val namedValueToReflectValueMethod = """ + func (v __NamedValue__) __toReflectValue__() (reflect.Value, error) { + typ, err := __convertStringToReflectType__(v.Type) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrStringToReflectTypeFailure, v.Type, err) + } + + if n, ok := v.Value.(__NilValue__); ok && n.Type == "interface{}" { + return reflect.Zero(typ), nil + } + + value, err := v.Value.__toReflectValue__() + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrRawValueToReflectValueFailure, err) + } + + return value.Convert(typ), nil + } + """.trimIndent() + + private val pointerValueStruct = """ + type __PointerValue__ struct { + Type string `json:"type"` + ElementType string `json:"elementType"` + Value __RawValue__ `json:"value"` + } + """.trimIndent() + + private val pointerValueToReflectValueMethod = """ + func (v __PointerValue__) __toReflectValue__() (reflect.Value, error) { + elementType, err := __convertStringToReflectType__(v.ElementType) + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrStringToReflectTypeFailure, v.Type, err) + } + + value, err := v.Value.__toReflectValue__() + if err != nil { + return reflect.Value{}, fmt.Errorf(ErrRawValueToReflectValueFailure, err) + } + + pointer := reflect.New(elementType) + pointer.Elem().Set(value) + + return pointer, nil + } + """.trimIndent() + + private fun convertStringToReflectTypeFunction( + namedTypes: Set, destinationPackage: GoPackage, aliases: Map + ): String { + val converter = GoUtModelToCodeConverter(destinationPackage, aliases) + return """ + func __convertStringToReflectType__(typeName string) (reflect.Type, error) { + var result reflect.Type + + switch { + case strings.HasPrefix(typeName, "map["): + index := strings.IndexRune(typeName, ']') + if index == -1 { + return nil, fmt.Errorf(ErrInvalidTypeName, typeName) + } + + keyTypeStr := typeName[4:index] + keyType, err := __convertStringToReflectType__(keyTypeStr) + if err != nil { + return nil, fmt.Errorf(ErrStringToReflectTypeFailure, keyTypeStr, err) + } + + elementTypeStr := typeName[index+1:] + elementType, err := __convertStringToReflectType__(elementTypeStr) + if err != nil { + return nil, fmt.Errorf(ErrStringToReflectTypeFailure, elementTypeStr, err) + } + + result = reflect.MapOf(keyType, elementType) + case strings.HasPrefix(typeName, "[]"): + index := strings.IndexRune(typeName, ']') + if index == -1 { + return nil, fmt.Errorf(ErrInvalidTypeName, typeName) + } + + res, err := __convertStringToReflectType__(typeName[index+1:]) + if err != nil { + return nil, fmt.Errorf(ErrInvalidTypeName, typeName) + } + + result = reflect.SliceOf(res) + case strings.HasPrefix(typeName, "["): + index := strings.IndexRune(typeName, ']') + if index == -1 { + return nil, fmt.Errorf(ErrInvalidTypeName, typeName) + } + + lengthStr := typeName[1:index] + length, err := strconv.Atoi(lengthStr) + if err != nil { + return nil, err + } + + res, err := __convertStringToReflectType__(typeName[index+1:]) + if err != nil { + return nil, fmt.Errorf(ErrStringToReflectTypeFailure, typeName[index+1:], err) + } + + result = reflect.ArrayOf(length, res) + case strings.HasPrefix(typeName, "<-chan") || strings.HasPrefix(typeName, "chan"): + dir := reflect.BothDir + index := 5 + if strings.HasPrefix(typeName, "<-chan") { + dir = reflect.RecvDir + index = 7 + } else if strings.HasPrefix(typeName, "chan<-") { + dir = reflect.SendDir + index = 7 + } + + elemType, err := __convertStringToReflectType__(typeName[index:]) + if err != nil { + return nil, fmt.Errorf(ErrStringToReflectTypeFailure, typeName[index:], err) + } + + result = reflect.ChanOf(dir, elemType) + case strings.HasPrefix(typeName, "*"): + elemType, err := __convertStringToReflectType__(typeName[1:]) + if err != nil { + return nil, fmt.Errorf(ErrStringToReflectTypeFailure, typeName[1:], err) + } + + result = reflect.PointerTo(elemType) + default: + switch typeName { + case "bool": + result = reflect.TypeOf(true) + case "int": + result = reflect.TypeOf(0) + case "int8": + result = reflect.TypeOf(int8(0)) + case "int16": + result = reflect.TypeOf(int16(0)) + case "int32": + result = reflect.TypeOf(int32(0)) + case "rune": + result = reflect.TypeOf(rune(0)) + case "int64": + result = reflect.TypeOf(int64(0)) + case "byte": + result = reflect.TypeOf(byte(0)) + case "uint": + result = reflect.TypeOf(uint(0)) + case "uint8": + result = reflect.TypeOf(uint8(0)) + case "uint16": + result = reflect.TypeOf(uint16(0)) + case "uint32": + result = reflect.TypeOf(uint32(0)) + case "uint64": + result = reflect.TypeOf(uint64(0)) + case "float32": + result = reflect.TypeOf(float32(0)) + case "float64": + result = reflect.TypeOf(float64(0)) + case "complex64": + result = reflect.TypeOf(complex(float32(0), float32(0))) + case "complex128": + result = reflect.TypeOf(complex(float64(0), float64(0))) + case "string": + result = reflect.TypeOf("") + case "uintptr": + result = reflect.TypeOf(uintptr(0)) + case "interface{}": + result = reflect.TypeOf((*interface{})(nil)).Elem() + ${ + namedTypes.joinToString(separator = "\n") { + val relativeName = it.getRelativeName(destinationPackage, aliases) + if (it.underlyingTypeId is GoInterfaceTypeId) { + "case \"${relativeName}\": result = reflect.TypeOf((*$relativeName)(nil)).Elem()" + } else { + "case \"${relativeName}\": result = reflect.TypeOf(${converter.toGoCode(it.goDefaultValueModel())})" + } + } + } + default: + return nil, fmt.Errorf("unsupported type: %s", typeName) + } + } + return result, nil + } + """.trimIndent() + } + + private val panicMessageStruct = """ + type __RawPanicMessage__ struct { + RawResultValue __RawValue__ `json:"rawResultValue"` + ImplementsError bool `json:"implementsError"` + } + """.trimIndent() + + private val rawExecutionResultStruct = """ + type __RawExecutionResult__ struct { + TimeoutExceeded bool `json:"timeoutExceeded"` + RawResultValues []__RawValue__ `json:"rawResultValues"` + PanicMessage *__RawPanicMessage__ `json:"panicMessage"` + CoverTab map[int]int `json:"coverTab"` + } + """.trimIndent() + + private val convertReflectValueOfDefinedTypeToRawValueFunction = """ + func __convertReflectValueOfDefinedTypeToRawValue__(v reflect.Value) (__RawValue__, error) { + value, err := __convertReflectValueOfPredeclaredOrNotDefinedTypeToRawValue__(v) + if err != nil { + return nil, fmt.Errorf(ErrReflectValueToRawValueFailure, err) + } + + return __NamedValue__{ + Type: v.Type().Name(), + Value: value, + }, nil + } + """.trimIndent() + + private val convertFloat64ValueToStringFunction = """ + func __convertFloat64ValueToString__(value float64) string { + const outputNaN = "NaN" + const outputPosInf = "+Inf" + const outputNegInf = "-Inf" + switch { + case math.IsNaN(value): + return fmt.Sprint(outputNaN) + case math.IsInf(value, 1): + return fmt.Sprint(outputPosInf) + case math.IsInf(value, -1): + return fmt.Sprint(outputNegInf) + default: + return fmt.Sprintf("%v", value) + } + } + """.trimIndent() + + private val convertReflectValueOfPredeclaredOrNotDefinedTypeToRawValueFunction = """ + func __convertReflectValueOfPredeclaredOrNotDefinedTypeToRawValue__(v reflect.Value) (__RawValue__, error) { + const outputComplexPartsDelimiter = "@" + + switch v.Kind() { + case reflect.Bool: + return __PrimitiveValue__{ + Type: v.Kind().String(), + Value: fmt.Sprintf("%#v", v.Bool()), + }, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return __PrimitiveValue__{ + Type: v.Kind().String(), + Value: fmt.Sprintf("%#v", v.Int()), + }, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return __PrimitiveValue__{ + Type: v.Kind().String(), + Value: fmt.Sprintf("%v", v.Uint()), + }, nil + case reflect.Float32, reflect.Float64: + return __PrimitiveValue__{ + Type: v.Kind().String(), + Value: __convertFloat64ValueToString__(v.Float()), + }, nil + case reflect.Complex64, reflect.Complex128: + value := v.Complex() + realPartString := __convertFloat64ValueToString__(real(value)) + imagPartString := __convertFloat64ValueToString__(imag(value)) + return __PrimitiveValue__{ + Type: v.Kind().String(), + Value: fmt.Sprintf("%v%v%v", realPartString, outputComplexPartsDelimiter, imagPartString), + }, nil + case reflect.String: + return __PrimitiveValue__{ + Type: reflect.String.String(), + Value: fmt.Sprintf("%v", v.String()), + }, nil + case reflect.Struct: + fields := reflect.VisibleFields(v.Type()) + resultValues := make([]__FieldValue__, 0, v.NumField()) + for _, field := range fields { + if len(field.Index) != 1 { + continue + } + + res, err := __convertReflectValueToRawValue__(v.FieldByName(field.Name)) + if err != nil { + return nil, fmt.Errorf(ErrReflectValueToRawValueFailure, err) + } + + resultValues = append(resultValues, __FieldValue__{ + Name: field.Name, + Value: res, + IsExported: field.IsExported(), + }) + } + return __StructValue__{ + Type: "struct{}", + Value: resultValues, + }, nil + case reflect.Array: + elem := v.Type().Elem() + elementType := elem.String() + arrayElementValues := make([]__RawValue__, 0, v.Len()) + for i := 0; i < v.Len(); i++ { + arrayElementValue, err := __convertReflectValueToRawValue__(v.Index(i)) + if err != nil { + return nil, fmt.Errorf(ErrReflectValueToRawValueFailure, err) + } + + arrayElementValues = append(arrayElementValues, arrayElementValue) + } + length := len(arrayElementValues) + return __ArrayValue__{ + Type: fmt.Sprintf("[%d]%s", length, elementType), + ElementType: elementType, + Length: length, + Value: arrayElementValues, + }, nil + case reflect.Slice: + if v.IsNil() { + return __NilValue__{Type: "nil"}, nil + } + elem := v.Type().Elem() + elementType := elem.String() + typeName := fmt.Sprintf("[]%s", elementType) + sliceElementValues := make([]__RawValue__, 0, v.Len()) + for i := 0; i < v.Len(); i++ { + sliceElementValue, err := __convertReflectValueToRawValue__(v.Index(i)) + if err != nil { + return nil, fmt.Errorf(ErrReflectValueToRawValueFailure, err) + } + + sliceElementValues = append(sliceElementValues, sliceElementValue) + } + length := len(sliceElementValues) + return __SliceValue__{ + Type: typeName, + ElementType: elementType, + Length: length, + Value: sliceElementValues, + }, nil + case reflect.Map: + if v.IsNil() { + return __NilValue__{Type: "nil"}, nil + } + key := v.Type().Key() + keyType := key.String() + elem := v.Type().Elem() + elementType := elem.String() + typeName := fmt.Sprintf("map[%s]%s", keyType, elementType) + mapValues := make([]__KeyValue__, 0, v.Len()) + for iter := v.MapRange(); iter.Next(); { + key, err := __convertReflectValueToRawValue__(iter.Key()) + if err != nil { + return nil, fmt.Errorf(ErrReflectValueToRawValueFailure, err) + } + + value, err := __convertReflectValueToRawValue__(iter.Value()) + if err != nil { + return nil, fmt.Errorf(ErrReflectValueToRawValueFailure, err) + } + + mapValues = append(mapValues, __KeyValue__{ + Key: key, + Value: value, + }) + } + return __MapValue__{ + Type: typeName, + KeyType: keyType, + ElementType: elementType, + Value: mapValues, + }, nil + case reflect.Chan: + if v.IsNil() { + return __NilValue__{Type: "nil"}, nil + } + typeName := v.Type().String() + elementType := v.Type().Elem().String() + dir := "SENDRECV" + if v.Type().ChanDir() == reflect.SendDir { + dir = "SENDONLY" + } else if v.Type().ChanDir() == reflect.RecvDir { + dir = "RECVONLY" + } + length := v.Len() + + chanElementValues := make([]__RawValue__, 0, v.Len()) + if dir != "SENDONLY" { + for v.Len() > 0 { + val, _ := v.Recv() + rawValue, err := __convertReflectValueToRawValue__(val) + if err != nil { + return nil, fmt.Errorf(ErrReflectValueToRawValueFailure, err) + } + + chanElementValues = append(chanElementValues, rawValue) + } + } + + return __ChanValue__{ + Type: typeName, + ElementType: elementType, + Direction: dir, + Length: length, + Value: chanElementValues, + }, nil + case reflect.Interface: + if v.Interface() == nil { + return __NilValue__{Type: "nil"}, nil + } + if e, ok := v.Interface().(error); ok { + value, err := __convertReflectValueOfPredeclaredOrNotDefinedTypeToRawValue__(reflect.ValueOf(e.Error())) + if err != nil { + return nil, fmt.Errorf(ErrReflectValueToRawValueFailure, err) + } + return __NamedValue__{ + Type: "error", + Value: value, + }, nil + } + return nil, fmt.Errorf("unsupported result type: %s", v.Type().String()) + case reflect.Pointer: + if v.IsNil() { + return __NilValue__{Type: "nil"}, nil + } + typeName := v.Type().String() + elementType := v.Type().Elem().String() + + value, err := __convertReflectValueToRawValue__(v.Elem()) + if err != nil { + return nil, fmt.Errorf(ErrReflectValueToRawValueFailure, err) + } + + return __PointerValue__{ + Type: typeName, + ElementType: elementType, + Value: value, + }, nil + default: + return nil, fmt.Errorf("unsupported result type: %s", v.Type().String()) + } + } + """.trimIndent() + + private val convertReflectValueToRawValueFunction = """ + func __convertReflectValueToRawValue__(v reflect.Value) (__RawValue__, error) { + if v.Type().PkgPath() != "" { + return __convertReflectValueOfDefinedTypeToRawValue__(v) + } + return __convertReflectValueOfPredeclaredOrNotDefinedTypeToRawValue__(v) + } + """.trimIndent() + + private val executeFunctionFunction = """ + func __executeFunction__( + function reflect.Value, arguments []reflect.Value, timeout time.Duration, + ) __RawExecutionResult__ { + ctxWithTimeout, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + __CoverTab__ = make([]int, __CoverSize__) + + executionResult := make(chan __ExecutionResult__, 1) + go func() { + panicked := true + defer func() { + panicMessage := recover() + if panicked { + executionResult <- __ExecutionResult__{ + Type: PanicFailure, + ResultValues: []reflect.Value{reflect.ValueOf(panicMessage)}, + } + } + }() + + executionResult <- __ExecutionResult__{ + Type: Completed, + ResultValues: function.Call(arguments), + } + panicked = false + }() + + var result __ExecutionResult__ + select { + case result = <-executionResult: + case <-ctxWithTimeout.Done(): + result = __ExecutionResult__{Type: TimeoutExceeded} + } + + return __wrapExecutionResult__(result) + } + """.trimIndent() + + private val wrapExecutionResultFunction = """ + func __wrapExecutionResult__(executionResult __ExecutionResult__) __RawExecutionResult__ { + var result __RawExecutionResult__ + switch executionResult.Type { + case Completed: + resultValues, err := __wrapResultValues__(executionResult.ResultValues) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Failed to wrap result values: %s", err) + os.Exit(1) + } + + result = __RawExecutionResult__{RawResultValues: resultValues} + case PanicFailure: + panicMessage := executionResult.ResultValues[0].Interface() + panicAsError, implementsError := panicMessage.(error) + var ( + resultValue __RawValue__ + err error + ) + if implementsError { + resultValue, err = __convertReflectValueToRawValue__(reflect.ValueOf(panicAsError.Error())) + } else { + resultValue, err = __convertReflectValueToRawValue__(reflect.ValueOf(panicMessage)) + } + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, ErrReflectValueToRawValueFailure, err) + os.Exit(1) + } + + result = __RawExecutionResult__{ + RawResultValues: []__RawValue__{}, + PanicMessage: &__RawPanicMessage__{ + RawResultValue: resultValue, + ImplementsError: implementsError, + }, + } + case TimeoutExceeded: + result = __RawExecutionResult__{ + TimeoutExceeded: true, + RawResultValues: []__RawValue__{}, + } + } + + coverTab := make(map[int]int, 256) + for i, v := range __CoverTab__ { + if v != 0 { + coverTab[i] = v + } + } + result.CoverTab = coverTab + + return result + } + """.trimIndent() + + private val wrapResultValuesForWorkerFunction = """ + func __wrapResultValues__(values []reflect.Value) ([]__RawValue__, error) { + rawValues := make([]__RawValue__, 0, len(values)) + for _, value := range values { + resultValue, err := __convertReflectValueToRawValue__(value) + if err != nil { + return nil, fmt.Errorf(ErrReflectValueToRawValueFailure, err) + } + + rawValues = append(rawValues, resultValue) + } + return rawValues, nil + } + """.trimIndent() + + private val convertRawValuesToReflectValuesFunction = """ + func __convertRawValuesToReflectValues__(values []__RawValue__) ([]reflect.Value, error) { + parameters := make([]reflect.Value, 0, len(values)) + + for _, value := range values { + reflectValue, err := value.__toReflectValue__() + if err != nil { + return nil, fmt.Errorf("failed to convert RawValue %s to reflect.Value: %s", value, err) + } + + parameters = append(parameters, reflectValue) + } + + return parameters, nil + } + """.trimIndent() + + private val parseTestInputFunction = """ + func __parseTestInput__(decoder *json.Decoder) (funcName string, rawValues []__RawValue__, err error) { + var testInput __TestInput__ + err = decoder.Decode(&testInput) + if err != nil { + return + } + + funcName = testInput.FunctionName + rawValues = make([]__RawValue__, 0, 10) + for _, arg := range testInput.Arguments { + var rawValue __RawValue__ + + rawValue, err = __parseRawValue__(arg, "") + if err != nil { + return "", nil, fmt.Errorf("failed to parse argument %s of function %s: %s", arg, funcName, err) + } + + rawValues = append(rawValues, rawValue) + } + + return + } + """.trimIndent() + + private val parseRawValueFunction = """ + func __parseRawValue__(rawValue map[string]interface{}, name string) (__RawValue__, error) { + typeName, ok := rawValue["type"] + if !ok { + return nil, fmt.Errorf("every RawValue must contain field 'type'") + } + typeNameStr, ok := typeName.(string) + if !ok { + return nil, fmt.Errorf("field 'type' must be string") + } + + v, ok := rawValue["value"] + if !ok { + return __NilValue__{Type: typeNameStr}, nil + } + + switch { + case typeNameStr == "struct{}": + if name == "" { + return nil, fmt.Errorf("anonymous structs is not supported") + } + + value, ok := v.([]interface{}) + if !ok { + return nil, fmt.Errorf("StructValue field 'value' must be array") + } + + values := make([]__FieldValue__, 0, len(value)) + for _, v := range value { + nextValue, err := __parseFieldValue__(v.(map[string]interface{})) + if err != nil { + return nil, fmt.Errorf("failed to parse field %s of struct: %s", v, err) + } + + values = append(values, nextValue) + } + + return __StructValue__{ + Type: name, + Value: values, + }, nil + case strings.HasPrefix(typeNameStr, "map["): + keyType, ok := rawValue["keyType"] + if !ok { + return nil, fmt.Errorf("MapValue must contain field 'keyType'") + } + keyTypeStr, ok := keyType.(string) + if !ok { + return nil, fmt.Errorf("MapValue field 'keyType' must be string") + } + + elementType, ok := rawValue["elementType"] + if !ok { + return nil, fmt.Errorf("MapValue must contain field 'elementType'") + } + elementTypeStr, ok := elementType.(string) + if !ok { + return nil, fmt.Errorf("MapValue field 'elementType' must be string") + } + + value, ok := v.([]interface{}) + if !ok { + return nil, fmt.Errorf("MapValue field 'value' must be array") + } + + values := make([]__KeyValue__, 0, len(value)) + for _, v := range value { + nextValue, err := __parseKeyValue__(v.(map[string]interface{})) + if err != nil { + return nil, fmt.Errorf("failed to parse KeyValue %s of map: %s", v, err) + } + + values = append(values, nextValue) + } + + return __MapValue__{ + Type: typeNameStr, + KeyType: keyTypeStr, + ElementType: elementTypeStr, + Value: values, + }, nil + case strings.HasPrefix(typeNameStr, "[]"): + elementType, ok := rawValue["elementType"] + if !ok { + return nil, fmt.Errorf("SliceValue must contain field 'elementType'") + } + elementTypeStr, ok := elementType.(string) + if !ok { + return nil, fmt.Errorf("SliceValue field 'elementType' must be string") + } + + if _, ok := rawValue["length"]; !ok { + return nil, fmt.Errorf("SliceValue must contain field 'length'") + } + length, ok := rawValue["length"].(float64) + if !ok { + return nil, fmt.Errorf("SliceValue field 'length' must be float64") + } + + value, ok := v.([]interface{}) + if !ok || len(value) != int(length) { + return nil, fmt.Errorf("SliceValue field 'value' must be array of length %d", int(length)) + } + + values := make([]__RawValue__, 0, len(value)) + for i, v := range value { + nextValue, err := __parseRawValue__(v.(map[string]interface{}), "") + if err != nil { + return nil, fmt.Errorf("failed to parse %d slice element: %s", i, err) + } + + values = append(values, nextValue) + } + + return __SliceValue__{ + Type: typeNameStr, + ElementType: elementTypeStr, + Length: int(length), + Value: values, + }, nil + case strings.HasPrefix(typeNameStr, "["): + elementType, ok := rawValue["elementType"] + if !ok { + return nil, fmt.Errorf("ArrayValue must contain field 'elementType'") + } + elementTypeStr, ok := elementType.(string) + if !ok { + return nil, fmt.Errorf("ArrayValue field 'elementType' must be string") + } + + if _, ok := rawValue["length"]; !ok { + return nil, fmt.Errorf("ArrayValue must contain field 'length'") + } + length, ok := rawValue["length"].(float64) + if !ok { + return nil, fmt.Errorf("ArrayValue field 'length' must be float64") + } + + value, ok := v.([]interface{}) + if !ok || len(value) != int(length) { + return nil, fmt.Errorf("ArrayValue field 'value' must be array of length %d", int(length)) + } + + values := make([]__RawValue__, 0, len(value)) + for i, v := range value { + nextValue, err := __parseRawValue__(v.(map[string]interface{}), "") + if err != nil { + return nil, fmt.Errorf("failed to parse %d array element: %s", i, err) + } + + values = append(values, nextValue) + } + + return __ArrayValue__{ + Type: typeNameStr, + ElementType: elementTypeStr, + Length: int(length), + Value: values, + }, nil + case strings.HasPrefix(typeNameStr, "<-chan") || strings.HasPrefix(typeNameStr, "chan"): + elementType, ok := rawValue["elementType"] + if !ok { + return nil, fmt.Errorf("ChanValue must contain field 'elementType'") + } + elementTypeStr, ok := elementType.(string) + if !ok { + return nil, fmt.Errorf("ChanValue field 'elementType' must be string") + } + + dir, ok := rawValue["direction"] + if !ok { + return nil, fmt.Errorf("ChanValue must contain field 'direction'") + } + direction, ok := dir.(string) + if !ok { + return nil, fmt.Errorf("ChanValue field 'direction' must be string") + } + + if _, ok := rawValue["length"]; !ok { + return nil, fmt.Errorf("ChanValue must contain field 'length'") + } + length, ok := rawValue["length"].(float64) + if !ok { + return nil, fmt.Errorf("ChanValue field 'length' must be float64") + } + + value, ok := v.([]interface{}) + if !ok || len(value) != int(length) { + return nil, fmt.Errorf("ChanValue field 'value' must be array of length %d", int(length)) + } + + values := make([]__RawValue__, 0, len(value)) + for i, v := range value { + nextValue, err := __parseRawValue__(v.(map[string]interface{}), "") + if err != nil { + return nil, fmt.Errorf("failed to parse %d chan element: %s", i, err) + } + + values = append(values, nextValue) + } + + return __ChanValue__{ + Type: typeNameStr, + ElementType: elementTypeStr, + Direction: direction, + Length: int(length), + Value: values, + }, nil + case strings.HasPrefix(typeNameStr, "*"): + elementType, ok := rawValue["elementType"] + if !ok { + return nil, fmt.Errorf("PointerValue must contain field 'elementType'") + } + elementTypeStr, ok := elementType.(string) + if !ok { + return nil, fmt.Errorf("PointerValue field 'elementType' must be string") + } + + value, err := __parseRawValue__(v.(map[string]interface{}), "") + if err != nil { + return nil, fmt.Errorf("failed to parse of PointerValue with type %s: %s", typeNameStr, err) + } + + return __PointerValue__{ + Type: typeNameStr, + ElementType: elementTypeStr, + Value: value, + }, nil + default: + switch typeNameStr { + case "bool", "rune", "int", "int8", "int16", "int32", "int64", "byte", "uint", "uint8", "uint16", "uint32", "uint64", "float32", "float64", "complex64", "complex128", "string", "uintptr": + value, ok := v.(string) + if !ok { + return nil, fmt.Errorf("PrimitiveValue field 'value' must be string") + } + + return __PrimitiveValue__{ + Type: typeNameStr, + Value: value, + }, nil + default: // named type + value, err := __parseRawValue__(v.(map[string]interface{}), typeNameStr) + if err != nil { + return nil, fmt.Errorf("failed to parse of NamedValue with type %s: %s", typeNameStr, err) + } + + return __NamedValue__{ + Type: typeNameStr, + Value: value, + }, nil + } + } + } + """.trimIndent() + + private val parseFieldValueFunction = """ + func __parseFieldValue__(p map[string]interface{}) (__FieldValue__, error) { + name, ok := p["name"] + if !ok { + return __FieldValue__{}, fmt.Errorf("FieldValue must contain field 'name'") + } + nameStr, ok := name.(string) + if !ok { + return __FieldValue__{}, fmt.Errorf("FieldValue 'name' must be string") + } + + if _, ok := p["value"]; !ok { + return __FieldValue__{}, fmt.Errorf("FieldValue must contain field 'value'") + } + value, err := __parseRawValue__(p["value"].(map[string]interface{}), "") + if err != nil { + return __FieldValue__{}, err + } + + isExported, ok := p["isExported"] + if !ok { + return __FieldValue__{}, fmt.Errorf("FieldValue must contain field 'isExported'") + } + isExportedBool, ok := isExported.(bool) + if !ok { + return __FieldValue__{}, fmt.Errorf("FieldValue 'isExported' must be bool") + } + + return __FieldValue__{ + Name: nameStr, + Value: value, + IsExported: isExportedBool, + }, nil + } + """.trimIndent() + + private val parseKeyValueFunction = """ + func __parseKeyValue__(p map[string]interface{}) (__KeyValue__, error) { + if _, ok := p["key"]; !ok { + return __KeyValue__{}, fmt.Errorf("KeyValue must contain field 'key'") + } + key, err := __parseRawValue__(p["key"].(map[string]interface{}), "") + if err != nil { + return __KeyValue__{}, err + } + + if _, ok := p["value"]; !ok { + return __KeyValue__{}, fmt.Errorf("KeyValue must contain field 'value'") + } + value, err := __parseRawValue__(p["value"].(map[string]interface{}), "") + if err != nil { + return __KeyValue__{}, err + } + + return __KeyValue__{ + Key: key, + Value: value, + }, nil + } + """.trimIndent() + + fun getTopLevelHelperStructsAndFunctionsForWorker( + namedTypes: Set, + destinationPackage: GoPackage, + aliases: Map, + ) = listOf( + errorMessages, + typeOfExecutionResult, + executionResultStruct, + testInputStruct, + rawValueInterface, + primitiveValueStruct, + parseFloatFunction, + primitiveValueToReflectValueMethod, + fieldValueStruct, + structValueStruct, + structValueToReflectValueMethod, + keyValueStruct, + mapValueStruct, + mapValueToReflectValueMethod, + arrayValueStruct, + arrayValueToReflectValueMethod, + sliceValueStruct, + sliceValueToReflectValueMethod, + chanValueStruct, + chanValueToReflectValueMethod, + nilValueStruct, + nilValueToReflectValueMethod, + namedValueStruct, + namedValueToReflectValueMethod, + pointerValueStruct, + pointerValueToReflectValueMethod, + convertStringToReflectTypeFunction(namedTypes, destinationPackage, aliases), + panicMessageStruct, + rawExecutionResultStruct, + convertReflectValueOfDefinedTypeToRawValueFunction, + convertFloat64ValueToStringFunction, + convertReflectValueOfPredeclaredOrNotDefinedTypeToRawValueFunction, + convertReflectValueToRawValueFunction, + executeFunctionFunction, + wrapExecutionResultFunction, + wrapResultValuesForWorkerFunction, + convertRawValuesToReflectValuesFunction, + parseTestInputFunction, + parseRawValueFunction, + parseFieldValueFunction, + parseKeyValueFunction + ) +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorker.kt b/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorker.kt new file mode 100644 index 0000000000..e29b250f2c --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorker.kt @@ -0,0 +1,169 @@ +package org.utbot.go.worker + +import com.beust.klaxon.Klaxon +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.go.api.GoUtFunction +import org.utbot.go.api.util.convertToRawValue +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.framework.api.go.GoUtModel +import org.utbot.go.util.convertObjectToJsonString +import org.utbot.go.util.executeCommandByNewProcessOrFailWithoutWaiting +import org.utbot.go.util.modifyEnvironment +import java.io.* +import java.net.ServerSocket +import java.net.Socket +import java.net.SocketTimeoutException +import java.nio.charset.StandardCharsets +import java.nio.file.Path +import java.util.concurrent.TimeUnit + +class GoWorker private constructor( + private var process: Process, + private var socket: Socket, + private val testFunctionName: String, + private val testFilePath: String, + private val goPackage: GoPackage, + private val goExecutableAbsolutePath: Path, + private val gopathAbsolutePath: Path, + private val workingDirectory: File, + private val serverSocket: ServerSocket, + private val readTimeoutMillis: Long, + private val connectionTimeoutMillis: Long, + private val endOfWorkerExecutionTimeoutMillis: Long +) : Closeable { + private var input: DataInputStream = DataInputStream(socket.getInputStream()) + private var output: DataOutputStream = DataOutputStream(socket.getOutputStream()) + + data class TestInput( + val functionName: String, val arguments: List + ) + + fun sendFuzzedParametersValues( + function: GoUtFunction, arguments: List, aliases: Map + ): Int { + val rawValues = arguments.map { it.convertToRawValue(goPackage, aliases) } + val testCase = TestInput(function.name, rawValues) + val json = convertObjectToJsonString(testCase) + output.write(json.encodeToByteArray()) + output.flush() + return json.length + } + + fun receiveRawExecutionResult(): RawExecutionResult { + socket.soTimeout = readTimeoutMillis.toInt() + val length = input.readInt() + val buffer = ByteArray(length) + input.read(buffer) + return Klaxon().parse(buffer.toString(StandardCharsets.UTF_8)) + ?: error("Error with parsing json as raw execution result") + } + + fun restartWorker() { + socket.close() + input.close() + output.close() + + process.destroy() + process = startWorkerProcess( + testFunctionName, testFilePath, goExecutableAbsolutePath, gopathAbsolutePath, workingDirectory + ) + + socket = connectingToWorker( + serverSocket, process, connectionTimeoutMillis, endOfWorkerExecutionTimeoutMillis + ) + input = DataInputStream(socket.getInputStream()) + output = DataOutputStream(socket.getOutputStream()) + } + + override fun close() { + socket.close() + input.close() + output.close() + + val processHasExited = process.waitFor(endOfWorkerExecutionTimeoutMillis, TimeUnit.MILLISECONDS) + if (!processHasExited) { + process.destroy() + val processOutput = InputStreamReader(process.inputStream).readText() + throw TimeoutException(buildString { + appendLine("Timeout exceeded: Worker didn't finish. Process output: ") + appendLine(processOutput) + }) + } + val exitCode = process.exitValue() + if (exitCode != 0) { + val processOutput = InputStreamReader(process.inputStream).readText() + throw RuntimeException(buildString { + appendLine("Execution of functions in child process failed with non-zero exit code = $exitCode: ") + appendLine(processOutput) + }) + } + } + + companion object { + private fun startWorkerProcess( + testFunctionName: String, + testFileName: String, + goExecutableAbsolutePath: Path, + gopathAbsolutePath: Path, + workingDirectory: File, + ): Process { + val environment = modifyEnvironment(goExecutableAbsolutePath, gopathAbsolutePath) + val command = listOf(testFileName, "--test.run", testFunctionName) + return executeCommandByNewProcessOrFailWithoutWaiting(command, workingDirectory, environment) + } + + private fun connectingToWorker( + serverSocket: ServerSocket, + process: Process, + connectionTimeoutMillis: Long, + endOfWorkerExecutionTimeout: Long, + ): Socket { + val workerSocket = try { + serverSocket.soTimeout = connectionTimeoutMillis.toInt() + serverSocket.accept() + } catch (e: SocketTimeoutException) { + val processHasExited = process.waitFor(endOfWorkerExecutionTimeout, TimeUnit.MILLISECONDS) + if (processHasExited) { + throw GoWorkerFailedException("An error occurred while starting the worker.") + } else { + process.destroy() + } + throw TimeoutException("Timeout exceeded: Worker not connected") + } + return workerSocket + } + + fun createWorker( + testFunctionName: String, + testFilePath: String, + goPackage: GoPackage, + goExecutableAbsolutePath: Path, + gopathAbsolutePath: Path, + workingDirectory: File, + serverSocket: ServerSocket, + connectionTimeoutMillis: Long = 10000, + endOfWorkerExecutionTimeout: Long = 5000, + ): GoWorker { + val workerProcess = startWorkerProcess( + testFunctionName, testFilePath, goExecutableAbsolutePath, gopathAbsolutePath, workingDirectory + ) + val workerSocket = connectingToWorker( + serverSocket, workerProcess, connectionTimeoutMillis, endOfWorkerExecutionTimeout + ) + return GoWorker( + process = workerProcess, + socket = workerSocket, + testFunctionName = testFunctionName, + testFilePath = testFilePath, + goPackage = goPackage, + goExecutableAbsolutePath = goExecutableAbsolutePath, + gopathAbsolutePath = gopathAbsolutePath, + workingDirectory = workingDirectory, + serverSocket = serverSocket, + readTimeoutMillis = 2 * endOfWorkerExecutionTimeout, + connectionTimeoutMillis = connectionTimeoutMillis, + endOfWorkerExecutionTimeoutMillis = endOfWorkerExecutionTimeout + ) + } + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorkerCodeGenerationHelper.kt b/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorkerCodeGenerationHelper.kt new file mode 100644 index 0000000000..ae87fd1344 --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/worker/GoWorkerCodeGenerationHelper.kt @@ -0,0 +1,205 @@ +package org.utbot.go.worker + +import org.utbot.go.api.GoUtFile +import org.utbot.go.api.GoUtFunction +import org.utbot.go.api.util.getAllVisibleNamedTypes +import org.utbot.go.framework.api.go.GoImport +import org.utbot.go.framework.api.go.GoPackage +import org.utbot.go.simplecodegeneration.GoFileCodeBuilder +import java.io.File + +internal object GoWorkerCodeGenerationHelper { + + const val workerTestFunctionName = "TestGoFileFuzzedFunctionsByUtGoWorker" + + val alwaysRequiredImports = setOf( + GoPackage("io", "io"), + GoPackage("os", "os"), + GoPackage("context", "context"), + GoPackage("binary", "encoding/binary"), + GoPackage("json", "encoding/json"), + GoPackage("fmt", "fmt"), + GoPackage("math", "math"), + GoPackage("net", "net"), + GoPackage("reflect", "reflect"), + GoPackage("strconv", "strconv"), + GoPackage("strings", "strings"), + GoPackage("testing", "testing"), + GoPackage("time", "time"), + GoPackage("unsafe", "unsafe") + ).map { GoImport(it) }.toSet() + + fun createFileToExecute( + sourceFile: GoUtFile, + functions: List, + absoluteInstrumentedPackagePath: String, + eachExecutionTimeoutMillis: Long, + port: Int, + imports: Set + ): File { + val fileToExecuteName = createFileToExecuteName() + val sourceFileDir = File(absoluteInstrumentedPackagePath) + val fileToExecute = sourceFileDir.resolve(fileToExecuteName) + + val fileToExecuteGoCode = + generateWorkerTestFileGoCode( + sourceFile, + functions, + eachExecutionTimeoutMillis, + port, + imports + ) + fileToExecute.writeText(fileToExecuteGoCode) + return fileToExecute + } + + fun createFileWithCoverTab( + sourceFile: GoUtFile, + absoluteInstrumentedPackagePath: String, + ): File { + val fileWithCoverTabName = createFileWithCoverTabName() + val sourceFileDir = File(absoluteInstrumentedPackagePath) + val fileWithCoverTab = sourceFileDir.resolve(fileWithCoverTabName) + + val fileWithCoverTabGoCode = generateFileWithCoverTabGoCode(sourceFile.sourcePackage) + fileWithCoverTab.writeText(fileWithCoverTabGoCode) + return fileWithCoverTab + } + + private fun createFileToExecuteName(): String { + return "utbot_go_worker_test.go" + } + + private fun createFileWithCoverTabName(): String { + return "utbot_go_cover.go" + } + + private fun generateWorkerTestFileGoCode( + sourceFile: GoUtFile, + functions: List, + eachExecutionTimeoutMillis: Long, + port: Int, + imports: Set + ): String { + val destinationPackage = sourceFile.sourcePackage + val fileCodeBuilder = GoFileCodeBuilder(destinationPackage, imports) + + val types = functions.flatMap { + it.parameters + if (it.isMethod) listOf(it.receiver!!) else emptyList() + }.map { it.type } + val aliases = imports.associate { it.goPackage to it.alias } + val namedTypes = types.getAllVisibleNamedTypes(destinationPackage) + + val workerTestFunctionCode = generateWorkerTestFunctionCode( + functions, destinationPackage, aliases, eachExecutionTimeoutMillis, port + ) + fileCodeBuilder.addTopLevelElements( + GoCodeTemplates.getTopLevelHelperStructsAndFunctionsForWorker( + namedTypes, + destinationPackage, + aliases, + ) + workerTestFunctionCode + ) + + return fileCodeBuilder.buildCodeString() + } + + private fun generateFileWithCoverTabGoCode(goPackage: GoPackage): String = """ + package ${goPackage.name} + + const __CoverSize__ = 64 << 10 + + var __CoverTab__ []int + """.trimIndent() + + private fun generateWorkerTestFunctionCode( + functions: List, + destinationPackage: GoPackage, + aliases: Map, + eachExecutionTimeoutMillis: Long, + port: Int + ): String { + val functionNameToFunctionCall = functions.map { function -> + function.name to if (function.isMethod) { + "(${function.receiver!!.type.getRelativeName(destinationPackage, aliases)}).${function.name}" + } else { + function.name + } + } + return """ + func $workerTestFunctionName(t *testing.T) { + con, err := net.Dial("tcp", ":$port") + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Connection to server failed: %s", err) + os.Exit(1) + } + + defer func() { + err = con.Close() + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Closing connection failed: %s", err) + os.Exit(1) + } + }() + + jsonDecoder := json.NewDecoder(con) + for { + var ( + funcName string + rawValues []__RawValue__ + ) + funcName, rawValues, err = __parseTestInput__(jsonDecoder) + if err == io.EOF { + break + } + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Failed to parse test input: %s", err) + os.Exit(1) + } + + var arguments []reflect.Value + arguments, err = __convertRawValuesToReflectValues__(rawValues) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Failed to convert slice of RawValue to slice of reflect.Value: %s", err) + os.Exit(1) + } + + var function reflect.Value + switch funcName { + ${ + functionNameToFunctionCall.joinToString(separator = "\n") { (functionName, functionCall) -> + "case \"${functionName}\": function = reflect.ValueOf($functionCall)" + } + } + default: + _, _ = fmt.Fprintf(os.Stderr, "Function %s not found", funcName) + os.Exit(1) + } + + executionResult := __executeFunction__(function, arguments, $eachExecutionTimeoutMillis*time.Millisecond) + + var jsonBytes []byte + jsonBytes, err = json.Marshal(executionResult) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Failed to serialize execution result to json: %s", err) + os.Exit(1) + } + + bs := make([]byte, 4) + binary.BigEndian.PutUint32(bs, uint32(len(jsonBytes))) + _, err = con.Write(bs) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Failed to send length of execution result: %s", err) + os.Exit(1) + } + + _, err = con.Write(jsonBytes) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Failed to send execution result: %s", err) + os.Exit(1) + } + } + } + """.trimIndent() + } +} \ No newline at end of file diff --git a/utbot-go/src/main/kotlin/org/utbot/go/worker/RawExecutionResults.kt b/utbot-go/src/main/kotlin/org/utbot/go/worker/RawExecutionResults.kt new file mode 100644 index 0000000000..3fb23a76bd --- /dev/null +++ b/utbot-go/src/main/kotlin/org/utbot/go/worker/RawExecutionResults.kt @@ -0,0 +1,260 @@ +package org.utbot.go.worker + +import com.beust.klaxon.TypeAdapter +import com.beust.klaxon.TypeFor +import org.utbot.go.api.* +import org.utbot.go.api.util.* +import org.utbot.go.framework.api.go.GoTypeId +import org.utbot.go.framework.api.go.GoUtModel +import kotlin.reflect.KClass + +data class PrimitiveValue( + override val type: String, + val value: String, +) : RawValue(type) + +data class NamedValue( + override val type: String, + val value: RawValue, +) : RawValue(type) + +data class StructValue( + override val type: String, + val value: List +) : RawValue(type) { + data class FieldValue( + val name: String, + val value: RawValue, + val isExported: Boolean + ) +} + +data class ArrayValue( + override val type: String, + val elementType: String, + val length: Int, + val value: List +) : RawValue(type) + +data class SliceValue( + override val type: String, + val elementType: String, + val length: Int, + val value: List +) : RawValue(type) + +data class MapValue( + override val type: String, + val keyType: String, + val elementType: String, + val value: List +) : RawValue(type) { + data class KeyValue( + val key: RawValue, + val value: RawValue + ) +} + +data class ChanValue( + override val type: String, + val elementType: String, + val direction: String, + val length: Int, + val value: List +) : RawValue(type) + +data class NilValue(override val type: String) : RawValue(type) + +data class InterfaceValue(override val type: String) : RawValue(type) + +data class PointerValue(override val type: String, val elementType: String, val value: RawValue) : RawValue(type) + +@TypeFor(field = "type", adapter = RawValueAdapter::class) +abstract class RawValue(open val type: String) + +class RawValueAdapter : TypeAdapter { + override fun classFor(type: Any): KClass { + val typeName = type as String + return when { + typeName == "nil" -> NilValue::class + typeName == "interface{}" -> InterfaceValue::class + typeName == "struct{}" -> StructValue::class + typeName.startsWith("map[") -> MapValue::class + typeName.startsWith("[]") -> SliceValue::class + typeName.startsWith("[") -> ArrayValue::class + typeName.startsWith("<-chan") || typeName.startsWith("chan") -> ChanValue::class + typeName.startsWith("*") -> PointerValue::class + goPrimitives.map { it.name }.contains(typeName) -> PrimitiveValue::class + else -> NamedValue::class + } + } +} + +data class RawPanicMessage( + val rawResultValue: RawValue, val implementsError: Boolean +) + +data class RawExecutionResult( + val timeoutExceeded: Boolean, + val rawResultValues: List, + val panicMessage: RawPanicMessage?, + val coverTab: Map +) + +private object RawValuesCodes { + const val NAN_VALUE = "NaN" + const val POS_INF_VALUE = "+Inf" + const val NEG_INF_VALUE = "-Inf" + const val COMPLEX_PARTS_DELIMITER = "@" +} + +class GoWorkerFailedException(s: String) : Exception(s) + +fun convertRawExecutionResultToExecutionResult( + rawExecutionResult: RawExecutionResult, functionResultTypes: List, timeoutMillis: Long +): GoUtExecutionResult { + if (rawExecutionResult.timeoutExceeded) { + return GoUtTimeoutExceeded(timeoutMillis) + } + if (rawExecutionResult.panicMessage != null) { + val (rawResultValue, implementsError) = rawExecutionResult.panicMessage + val panicValue = if (goPrimitives.map { it.name }.contains(rawResultValue.type)) { + createGoUtPrimitiveModelFromRawValue( + rawResultValue as PrimitiveValue, GoPrimitiveTypeId(rawResultValue.type) + ) + } else { + error("Only primitive panic value is currently supported") + } + return GoUtPanicFailure(panicValue, implementsError) + } + if (rawExecutionResult.rawResultValues.size != functionResultTypes.size) { + error("Function completed execution must have as many result raw values as result types.") + } + var executedWithNonNilErrorString = false + val resultValues = rawExecutionResult.rawResultValues.zip(functionResultTypes).map { (rawResultValue, resultType) -> + val model = createGoUtModelFromRawValue(rawResultValue, resultType) + if (resultType.implementsError && (model is GoUtNamedModel && model.value.typeId == goStringTypeId)) { + executedWithNonNilErrorString = true + } + return@map model + } + return if (executedWithNonNilErrorString) { + GoUtExecutionWithNonNilError(resultValues) + } else { + GoUtExecutionSuccess(resultValues) + } +} + +private fun createGoUtModelFromRawValue( + rawValue: RawValue, typeId: GoTypeId, +): GoUtModel = if (rawValue is NilValue) { + GoUtNilModel(typeId) +} else { + when (typeId) { + is GoNamedTypeId -> createGoUtNamedModelFromRawValue(rawValue as NamedValue, typeId) + // Only for error interface + is GoInterfaceTypeId -> GoUtPrimitiveModel((rawValue as PrimitiveValue).value, goStringTypeId) + + is GoStructTypeId -> createGoUtStructModelFromRawValue(rawValue as StructValue, typeId) + + is GoArrayTypeId -> createGoUtArrayModelFromRawValue(rawValue as ArrayValue, typeId) + + is GoSliceTypeId -> createGoUtSliceModelFromRawValue(rawValue as SliceValue, typeId) + + is GoMapTypeId -> createGoUtMapModelFromRawValue(rawValue as MapValue, typeId) + + is GoPrimitiveTypeId -> createGoUtPrimitiveModelFromRawValue(rawValue as PrimitiveValue, typeId) + + is GoPointerTypeId -> createGoUtPointerModelFromRawValue(rawValue as PointerValue, typeId) + + is GoChanTypeId -> createGoUtChanModelFromRawValue(rawValue as ChanValue, typeId) + + else -> error("Creating a model from raw value of [${typeId.javaClass}] type is not supported") + } +} + +private fun createGoUtPrimitiveModelFromRawValue( + resultValue: PrimitiveValue, typeId: GoPrimitiveTypeId, +): GoUtPrimitiveModel { + val rawValue = resultValue.value + if (typeId == goFloat64TypeId || typeId == goFloat32TypeId) { + return convertRawFloatValueToGoUtPrimitiveModel(rawValue, typeId) + } + if (typeId == goComplex128TypeId || typeId == goComplex64TypeId) { + val correspondingFloatType = if (typeId == goComplex128TypeId) goFloat64TypeId else goFloat32TypeId + val (realPartModel, imagPartModel) = rawValue.split(RawValuesCodes.COMPLEX_PARTS_DELIMITER).map { + convertRawFloatValueToGoUtPrimitiveModel(it, correspondingFloatType, typeId == goComplex64TypeId) + } + return GoUtComplexModel(realPartModel, imagPartModel, typeId) + } + val value = rawValueOfGoPrimitiveTypeToValue(typeId, rawValue) + return GoUtPrimitiveModel(value, typeId) +} + +private fun convertRawFloatValueToGoUtPrimitiveModel( + rawValue: String, typeId: GoPrimitiveTypeId, explicitCastRequired: Boolean = false +): GoUtPrimitiveModel { + return when (rawValue) { + RawValuesCodes.NAN_VALUE -> GoUtFloatNaNModel(typeId) + RawValuesCodes.POS_INF_VALUE -> GoUtFloatInfModel(1, typeId) + RawValuesCodes.NEG_INF_VALUE -> GoUtFloatInfModel(-1, typeId) + else -> { + val typedValue = if (typeId == goFloat64TypeId) rawValue.toDouble() else rawValue.toFloat() + if (explicitCastRequired) { + GoUtPrimitiveModel(typedValue, typeId, explicitCastMode = ExplicitCastMode.REQUIRED) + } else { + GoUtPrimitiveModel(typedValue, typeId) + } + } + } +} + +private fun createGoUtStructModelFromRawValue(resultValue: StructValue, resultTypeId: GoStructTypeId): GoUtStructModel { + val value = linkedMapOf(*resultValue.value.zip(resultTypeId.fields).map { (value, fieldId) -> + fieldId to createGoUtModelFromRawValue(value.value, fieldId.declaringType) + }.toTypedArray()) + return GoUtStructModel(value, resultTypeId) +} + +private fun createGoUtArrayModelFromRawValue(resultValue: ArrayValue, resultTypeId: GoArrayTypeId): GoUtArrayModel { + val value = resultValue.value.map { + createGoUtModelFromRawValue(it, resultTypeId.elementTypeId!!) + }.toTypedArray() + return GoUtArrayModel(value, resultTypeId) +} + +private fun createGoUtSliceModelFromRawValue(resultValue: SliceValue, resultTypeId: GoSliceTypeId): GoUtSliceModel { + val value = resultValue.value.map { + createGoUtModelFromRawValue(it, resultTypeId.elementTypeId!!) + }.toTypedArray() + return GoUtSliceModel(value, resultTypeId, resultValue.length) +} + +private fun createGoUtMapModelFromRawValue(resultValue: MapValue, resultTypeId: GoMapTypeId): GoUtMapModel { + val value = resultValue.value.associate { + val key = createGoUtModelFromRawValue(it.key, resultTypeId.keyTypeId) + val value = createGoUtModelFromRawValue(it.value, resultTypeId.elementTypeId!!) + key to value + }.toMutableMap() + return GoUtMapModel(value, resultTypeId) +} + +private fun createGoUtNamedModelFromRawValue(resultValue: NamedValue, resultTypeId: GoNamedTypeId): GoUtNamedModel { + val value = createGoUtModelFromRawValue(resultValue.value, resultTypeId.underlyingTypeId) + return GoUtNamedModel(value, resultTypeId) +} + +private fun createGoUtPointerModelFromRawValue( + resultValue: PointerValue, + resultTypeId: GoPointerTypeId +): GoUtPointerModel { + val value = createGoUtModelFromRawValue(resultValue.value, resultTypeId.elementTypeId!!) + return GoUtPointerModel(value, resultTypeId) +} + +private fun createGoUtChanModelFromRawValue(resultValue: ChanValue, resultTypeId: GoChanTypeId): GoUtChanModel { + val value = resultValue.value.map { + createGoUtModelFromRawValue(it, resultTypeId.elementTypeId!!) + }.toTypedArray() + return GoUtChanModel(value, resultTypeId) +} \ No newline at end of file diff --git a/utbot-go/src/main/resources/go_package_instrumentation/go.mod b/utbot-go/src/main/resources/go_package_instrumentation/go.mod new file mode 100644 index 0000000000..0c811c6551 --- /dev/null +++ b/utbot-go/src/main/resources/go_package_instrumentation/go.mod @@ -0,0 +1,10 @@ +module go_package_modifier + +go 1.18 + +require golang.org/x/tools v0.8.0 + +require ( + golang.org/x/mod v0.10.0 // indirect + golang.org/x/sys v0.7.0 // indirect +) diff --git a/utbot-go/src/main/resources/go_package_instrumentation/go.sum b/utbot-go/src/main/resources/go_package_instrumentation/go.sum new file mode 100644 index 0000000000..d84f5f1091 --- /dev/null +++ b/utbot-go/src/main/resources/go_package_instrumentation/go.sum @@ -0,0 +1,7 @@ +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= diff --git a/utbot-go/src/main/resources/go_package_instrumentation/instrumentation_result.go b/utbot-go/src/main/resources/go_package_instrumentation/instrumentation_result.go new file mode 100644 index 0000000000..955303b49a --- /dev/null +++ b/utbot-go/src/main/resources/go_package_instrumentation/instrumentation_result.go @@ -0,0 +1,7 @@ +package main + +type InstrumentationResult struct { + AbsolutePathToInstrumentedPackage string `json:"absolutePathToInstrumentedPackage"` + AbsolutePathToInstrumentedModule string `json:"absolutePathToInstrumentedModule"` + TestedFunctionsToCounters map[string][]string `json:"testedFunctionsToCounters"` +} diff --git a/utbot-go/src/main/resources/go_package_instrumentation/instrumentation_target.go b/utbot-go/src/main/resources/go_package_instrumentation/instrumentation_target.go new file mode 100644 index 0000000000..bc98cece3a --- /dev/null +++ b/utbot-go/src/main/resources/go_package_instrumentation/instrumentation_target.go @@ -0,0 +1,6 @@ +package main + +type InstrumentationTarget struct { + AbsolutePackagePath string `json:"absolutePackagePath"` + TestedFunctions []string `json:"testedFunctions"` +} diff --git a/utbot-go/src/main/resources/go_package_instrumentation/instrumentator.go b/utbot-go/src/main/resources/go_package_instrumentation/instrumentator.go new file mode 100644 index 0000000000..1ec4dbbc09 --- /dev/null +++ b/utbot-go/src/main/resources/go_package_instrumentation/instrumentator.go @@ -0,0 +1,164 @@ +package main + +import ( + "crypto/sha1" + "go/ast" + "go/token" + "strconv" +) + +type Instrumentator struct { + lineCounter int + currFunction string + testedFunctions map[string]bool + functionToCounters map[string][]string +} + +func NewInstrumentator(testedFunctions map[string]bool) Instrumentator { + return Instrumentator{ + testedFunctions: testedFunctions, + functionToCounters: make(map[string][]string), + } +} + +func (f *Instrumentator) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.FuncDecl: + n.Doc = nil + f.currFunction = n.Name.Name + case *ast.BlockStmt: + if n == nil { + n = &ast.BlockStmt{} + } + n.List = f.addCounter(n.List) + return nil + case *ast.IfStmt: + if n.Body == nil { + return nil + } + ast.Walk(f, n.Body) + if n.Else == nil { + n.Else = &ast.BlockStmt{} + } + switch stmt := n.Else.(type) { + case *ast.IfStmt: + n.Else = &ast.BlockStmt{List: []ast.Stmt{stmt}} + } + ast.Walk(f, n.Else) + return nil + case *ast.ForStmt: + if n.Body == nil { + return nil + } + ast.Walk(f, n.Body) + return nil + case *ast.RangeStmt: + if n.Body == nil { + return nil + } + ast.Walk(f, n.Body) + return nil + case *ast.SwitchStmt: + hasDefault := false + if n.Body == nil { + n.Body = &ast.BlockStmt{} + } + for _, stmt := range n.Body.List { + if cas, ok := stmt.(*ast.CaseClause); ok && cas.List == nil { + hasDefault = true + break + } + } + if !hasDefault { + n.Body.List = append(n.Body.List, &ast.CaseClause{}) + } + for _, stmt := range n.Body.List { + ast.Walk(f, stmt) + } + return nil + case *ast.TypeSwitchStmt: + hasDefault := false + if n.Body == nil { + n.Body = &ast.BlockStmt{} + } + for _, stmt := range n.Body.List { + if cas, ok := stmt.(*ast.CaseClause); ok && cas.List == nil { + hasDefault = true + break + } + } + if !hasDefault { + n.Body.List = append(n.Body.List, &ast.CaseClause{}) + } + for _, stmt := range n.Body.List { + ast.Walk(f, stmt) + } + return nil + case *ast.SelectStmt: + if n.Body == nil { + return nil + } + for _, stmt := range n.Body.List { + ast.Walk(f, stmt) + } + return nil + case *ast.CaseClause: + for _, expr := range n.List { + ast.Walk(f, expr) + } + n.Body = f.addCounter(n.Body) + return nil + case *ast.CommClause: + ast.Walk(f, n.Comm) + n.Body = f.addCounter(n.Body) + return nil + } + return f +} + +func (f *Instrumentator) addCounter(stmts []ast.Stmt) []ast.Stmt { + if len(stmts) == 0 { + return []ast.Stmt{f.newCounter()} + } + + var newList []ast.Stmt + for _, stmt := range stmts { + newList = append(newList, f.newCounter()) + ast.Walk(f, stmt) + newList = append(newList, stmt) + if _, ok := stmt.(*ast.ReturnStmt); ok { + break + } + } + return newList +} + +func (f *Instrumentator) getNextCounter() int { + f.lineCounter++ + id := f.lineCounter + buf := []byte{byte(id), byte(id >> 8), byte(id >> 16), byte(id >> 24)} + hash := sha1.Sum(buf) + return int(uint16(hash[0]) | uint16(hash[1])<<8) +} + +func (f *Instrumentator) newCounter() ast.Stmt { + cnt := strconv.Itoa(f.getNextCounter()) + + funcName := f.currFunction + if ok := f.testedFunctions[funcName]; ok { + f.functionToCounters[funcName] = append(f.functionToCounters[funcName], cnt) + } + + idx := &ast.BasicLit{ + Kind: token.INT, + Value: cnt, + } + counter := &ast.IndexExpr{ + X: ast.NewIdent("__CoverTab__"), + Index: idx, + } + return &ast.IncDecStmt{ + X: counter, + Tok: token.INC, + } +} diff --git a/utbot-go/src/main/resources/go_package_instrumentation/main.go b/utbot-go/src/main/resources/go_package_instrumentation/main.go new file mode 100644 index 0000000000..e59fea31e4 --- /dev/null +++ b/utbot-go/src/main/resources/go_package_instrumentation/main.go @@ -0,0 +1,178 @@ +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "go/ast" + "go/printer" + "golang.org/x/tools/go/packages" + "io" + "io/fs" + "os" + "path/filepath" + "strings" +) + +func failf(str string, args ...any) { + _, _ = fmt.Fprintf(os.Stderr, str+"\n", args...) + os.Exit(1) +} + +func instrument(astFile *ast.File, modifier *Instrumentator) { + ast.Walk(modifier, astFile) +} + +func copyFile(src, dst string) { + r, err := os.Open(src) + if err != nil { + failf("copyFile: could not read %v", src, err) + } + w, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + failf("copyFile: could not write %v: %v", dst, err) + } + if _, err := io.Copy(w, r); err != nil { + failf("copyFile: copying failed: %v", err) + } + if err := r.Close(); err != nil { + failf("copyFile: closing %v failed: %v", src, err) + } + if err := w.Close(); err != nil { + failf("copyFile: closing %v failed: %v", dst, err) + } +} + +func main() { + var targetFilePath, resultFilePath string + flag.StringVar(&targetFilePath, "target", "", "path to JSON file to read instrumentation target from") + flag.StringVar(&resultFilePath, "result", "", "path to JSON file to write instrumentation result to") + flag.Parse() + + // read and deserialize targets + targetBytes, readErr := os.ReadFile(targetFilePath) + if readErr != nil { + failf("failed to read file %s: %s", targetFilePath, readErr) + } + var instrumentationTarget InstrumentationTarget + fromJsonErr := json.Unmarshal(targetBytes, &instrumentationTarget) + if fromJsonErr != nil { + failf("failed to parse instrumentation target: %s", fromJsonErr) + } + + // parse package + pkgPath := instrumentationTarget.AbsolutePackagePath + cfg := packages.Config{ + Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | + packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo | + packages.NeedDeps | packages.NeedModule | packages.NeedEmbedFiles | packages.NeedEmbedPatterns | packages.NeedExportFile, + Dir: pkgPath, + } + cfg.Env = os.Environ() + pkgs, err := packages.Load(&cfg, pkgPath) + if err != nil { + failf("failed to parse package: %s", err) + } + if len(pkgs) != 1 { + failf("cannot build multiple packages: %s", err) + } + if packages.PrintErrors(pkgs) > 0 { + failf("typechecking of %s failed", pkgPath) + } + + targetPackage := pkgs[0] + module := targetPackage.Module + + workdir, err := os.MkdirTemp("", "utbot-go*") + if err != nil { + failf("failed to create temporary directory: %s", err) + } + + testedFunctions := make(map[string]bool, len(instrumentationTarget.TestedFunctions)) + for _, f := range instrumentationTarget.TestedFunctions { + testedFunctions[f] = true + } + modifier := NewInstrumentator(testedFunctions) + absolutePathToInstrumentedPackage := "" + visit := func(pkg *packages.Package) { + if pkg.Module == nil || pkg.Module.Path != module.Path { + return + } + for i, fullName := range pkg.CompiledGoFiles { + fname := strings.Replace(fullName, module.Dir, "", 1) + outpath := filepath.Join(workdir, fname) + if !strings.HasSuffix(fullName, ".go") { + continue + } + astFile := pkg.Syntax[i] + if pkg.PkgPath == targetPackage.PkgPath { + instrument(astFile, &modifier) + absolutePathToInstrumentedPackage = filepath.Dir(outpath) + } + buf := new(bytes.Buffer) + c := printer.Config{ + Mode: printer.TabIndent, + Tabwidth: 4, + Indent: 0, + } + err = c.Fprint(buf, pkg.Fset, astFile) + if err != nil { + failf("failed to fprint: %s", err) + } + err = os.MkdirAll(filepath.Dir(outpath), 0666) + if err != nil { + failf("failed to create directories: %s", err) + } + _, err = os.Create(outpath) + if err != nil { + failf("failed to create file: %s", err) + } + err = os.WriteFile(outpath, buf.Bytes(), 0666) + if err != nil { + failf("failed to write to file: %s", err) + } + } + } + packages.Visit(pkgs, nil, visit) + + err = filepath.Walk(module.Dir, func(path string, info fs.FileInfo, err error) error { + if info.IsDir() && info.Name() == ".git" { + return filepath.SkipDir + } + if info.IsDir() { + dname := strings.Replace(path, module.Dir, "", 1) + dirPath := filepath.Join(workdir, dname) + err = os.MkdirAll(dirPath, 0666) + return err + } + if !strings.HasSuffix(path, ".go") { + fname := strings.Replace(path, module.Dir, "", 1) + outpath := filepath.Join(workdir, fname) + err = os.Symlink(path, outpath) + if err != nil { + copyFile(path, outpath) + } + return nil + } + return nil + }) + if err != nil { + failf("failed to walk the module directory: %s", err) + } + + // serialize and write results + instrumentationResult := InstrumentationResult{ + AbsolutePathToInstrumentedPackage: absolutePathToInstrumentedPackage, + AbsolutePathToInstrumentedModule: workdir, + TestedFunctionsToCounters: modifier.functionToCounters, + } + jsonBytes, toJsonErr := json.MarshalIndent(instrumentationResult, "", " ") + if toJsonErr != nil { + failf("failed to serialize instrumentation result: %s", toJsonErr) + } + writeErr := os.WriteFile(resultFilePath, jsonBytes, os.ModePerm) + if writeErr != nil { + failf("failed to write instrumentation result: %s", writeErr) + } +} diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/analysis_results.go b/utbot-go/src/main/resources/go_source_code_analyzer/analysis_results.go new file mode 100644 index 0000000000..7bfdeb455a --- /dev/null +++ b/utbot-go/src/main/resources/go_source_code_analyzer/analysis_results.go @@ -0,0 +1,131 @@ +package main + +import "go/token" + +type Package struct { + Name string `json:"name"` + Path string `json:"path"` +} + +type AnalyzedType interface { + GetName() string +} + +type AnalyzedNamedType struct { + Name string `json:"name"` + SourcePackage Package `json:"sourcePackage"` + ImplementsError bool `json:"implementsError"` + UnderlyingType string `json:"underlyingType"` +} + +func (t AnalyzedNamedType) GetName() string { + return t.Name +} + +type AnalyzedInterfaceType struct { + Name string `json:"name"` + Implementations []string `json:"implementations"` +} + +func (t AnalyzedInterfaceType) GetName() string { + return t.Name +} + +type AnalyzedPrimitiveType struct { + Name string `json:"name"` +} + +func (t AnalyzedPrimitiveType) GetName() string { + return t.Name +} + +type AnalyzedField struct { + Name string `json:"name"` + Type string `json:"type"` + IsExported bool `json:"isExported"` +} + +type AnalyzedStructType struct { + Name string `json:"name"` + Fields []AnalyzedField `json:"fields"` +} + +func (t AnalyzedStructType) GetName() string { + return t.Name +} + +type AnalyzedArrayType struct { + Name string `json:"name"` + ElementType string `json:"elementType"` + Length int64 `json:"length"` +} + +func (t AnalyzedArrayType) GetName() string { + return t.Name +} + +type AnalyzedSliceType struct { + Name string `json:"name"` + ElementType string `json:"elementType"` +} + +func (t AnalyzedSliceType) GetName() string { + return t.Name +} + +type AnalyzedMapType struct { + Name string `json:"name"` + KeyType string `json:"keyType"` + ElementType string `json:"elementType"` +} + +func (t AnalyzedMapType) GetName() string { + return t.Name +} + +type AnalyzedChanType struct { + Name string `json:"name"` + ElementType string `json:"elementType"` + Direction string `json:"direction"` +} + +func (t AnalyzedChanType) GetName() string { + return t.Name +} + +type AnalyzedPointerType struct { + Name string `json:"name"` + ElementType string `json:"elementType"` +} + +func (t AnalyzedPointerType) GetName() string { + return t.Name +} + +type AnalyzedVariable struct { + Name string `json:"name"` + Type string `json:"type"` +} + +type AnalyzedFunction struct { + Name string `json:"name"` + Types map[string]AnalyzedType `json:"types"` + Receiver *AnalyzedVariable `json:"receiver"` + Parameters []AnalyzedVariable `json:"parameters"` + ResultTypes []AnalyzedVariable `json:"resultTypes"` + Constants map[string][]string `json:"constants"` + position token.Pos +} + +type AnalysisResult struct { + AbsoluteFilePath string `json:"absoluteFilePath"` + SourcePackage Package `json:"sourcePackage"` + AnalyzedFunctions []AnalyzedFunction `json:"analyzedFunctions"` + NotSupportedFunctionNames []string `json:"notSupportedFunctionNames"` + NotFoundFunctionNames []string `json:"notFoundFunctionNames"` +} + +type AnalysisResults struct { + Results []AnalysisResult `json:"results"` + IntSize int `json:"intSize"` +} diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/analysis_targets.go b/utbot-go/src/main/resources/go_source_code_analyzer/analysis_targets.go new file mode 100644 index 0000000000..e71600523d --- /dev/null +++ b/utbot-go/src/main/resources/go_source_code_analyzer/analysis_targets.go @@ -0,0 +1,11 @@ +package main + +type AnalysisTarget struct { + AbsoluteFilePath string `json:"absoluteFilePath"` + TargetFunctionNames []string `json:"targetFunctionNames"` + TargetMethodNames []string `json:"targetMethodNames"` +} + +type AnalysisTargets struct { + Targets []AnalysisTarget `json:"targets"` +} diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/analyzer_core.go b/utbot-go/src/main/resources/go_source_code_analyzer/analyzer_core.go new file mode 100644 index 0000000000..10475e3d3e --- /dev/null +++ b/utbot-go/src/main/resources/go_source_code_analyzer/analyzer_core.go @@ -0,0 +1,460 @@ +package main + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "sort" + "strconv" + "sync" +) + +var errorInterface = func() *types.Interface { + variable := types.NewVar(token.NoPos, nil, "", types.Typ[types.String]) + results := types.NewTuple(variable) + signature := types.NewSignatureType(nil, nil, nil, nil, results, false) + method := types.NewFunc(token.NoPos, nil, "Error", signature) + return types.NewInterfaceType([]*types.Func{method}, nil) +}() + +func implementsError(typ types.Type) bool { + return types.Implements(typ, errorInterface) +} + +func ChanDirToString(dir types.ChanDir) (string, error) { + switch dir { + case types.SendOnly: + return "SENDONLY", nil + case types.RecvOnly: + return "RECVONLY", nil + case types.SendRecv: + return "SENDRECV", nil + } + return "", fmt.Errorf("unsupported channel direction: %d", dir) +} + +func toAnalyzedType( + typ types.Type, + analyzedTypes map[string]AnalyzedType, + typeToIndex map[string]string, + sourcePackage Package, + currentPackage Package, + info *types.Info, +) string { + if index, ok := typeToIndex[typ.String()]; ok { + return index + } + + var result AnalyzedType + indexOfResult := strconv.Itoa(len(typeToIndex)) + typeToIndex[typ.String()] = indexOfResult + + switch t := typ.(type) { + case *types.Pointer: + indexOfElemType := toAnalyzedType(t.Elem(), analyzedTypes, typeToIndex, sourcePackage, currentPackage, info) + + result = AnalyzedPointerType{ + Name: "*", + ElementType: indexOfElemType, + } + case *types.Named: + name := t.Obj().Name() + + var pkg Package + if p := t.Obj().Pkg(); p != nil { + pkg.Name = p.Name() + pkg.Path = p.Path() + } + + isError := implementsError(t) + + indexOfUnderlyingType := toAnalyzedType(t.Underlying(), analyzedTypes, typeToIndex, sourcePackage, pkg, info) + + result = AnalyzedNamedType{ + Name: name, + SourcePackage: pkg, + ImplementsError: isError, + UnderlyingType: indexOfUnderlyingType, + } + case *types.Basic: + name := t.Name() + result = AnalyzedPrimitiveType{Name: name} + case *types.Struct: + println(t.String()) + fields := make([]AnalyzedField, 0, t.NumFields()) + for i := 0; i < t.NumFields(); i++ { + field := t.Field(i) + if currentPackage != sourcePackage && !field.Exported() { + continue + } + + fieldType := toAnalyzedType(field.Type(), analyzedTypes, typeToIndex, sourcePackage, currentPackage, info) + + fields = append(fields, AnalyzedField{field.Name(), fieldType, field.Exported()}) + } + + result = AnalyzedStructType{ + Name: "struct{}", + Fields: fields, + } + case *types.Array: + println(t.String()) + indexOfArrayElemType := toAnalyzedType(t.Elem(), analyzedTypes, typeToIndex, sourcePackage, currentPackage, info) + + length := t.Len() + + result = AnalyzedArrayType{ + Name: "[_]", + ElementType: indexOfArrayElemType, + Length: length, + } + case *types.Slice: + println(t.String()) + indexOfSliceElemType := toAnalyzedType(t.Elem(), analyzedTypes, typeToIndex, sourcePackage, currentPackage, info) + + result = AnalyzedSliceType{ + Name: "[]", + ElementType: indexOfSliceElemType, + } + case *types.Map: + println(t.String()) + indexOfKeyType := toAnalyzedType(t.Key(), analyzedTypes, typeToIndex, sourcePackage, currentPackage, info) + indexOfElemType := toAnalyzedType(t.Elem(), analyzedTypes, typeToIndex, sourcePackage, currentPackage, info) + + result = AnalyzedMapType{ + Name: "map", + KeyType: indexOfKeyType, + ElementType: indexOfElemType, + } + case *types.Chan: + println(t.String()) + indexOfElemType := toAnalyzedType(t.Elem(), analyzedTypes, typeToIndex, sourcePackage, currentPackage, info) + + chanDir, err := ChanDirToString(t.Dir()) + checkError(err) + + result = AnalyzedChanType{ + Name: "chan", + ElementType: indexOfElemType, + Direction: chanDir, + } + case *types.Interface: + implementations := make([]string, 0) + used := make(map[string]bool) + for _, typeAndValue := range info.Types { + switch typeAndValue.Type.(type) { + case *types.Struct, *types.Signature, *types.Tuple, *types.TypeParam, *types.Union: + continue + case *types.Basic: + b := typeAndValue.Type.(*types.Basic) + switch b.Kind() { + case types.UntypedBool, types.UntypedInt, types.UntypedRune, types.UntypedFloat, types.UntypedComplex, types.UntypedString, types.UntypedNil: + continue + } + } + if !types.IsInterface(typeAndValue.Type) && types.Implements(typeAndValue.Type, t) { + analyzedType := toAnalyzedType(typeAndValue.Type, analyzedTypes, typeToIndex, sourcePackage, currentPackage, info) + if used[analyzedType] { + continue + } + used[analyzedType] = true + implementations = append(implementations, analyzedType) + } + } + result = AnalyzedInterfaceType{ + Name: "interface{}", + Implementations: implementations, + } + default: + err := fmt.Errorf("unsupported type: %s", typ.Underlying()) + checkError(err) + } + analyzedTypes[indexOfResult] = result + return indexOfResult +} + +type ResultOfChecking int + +const ( + SupportedType = iota + Unknown + UnsupportedType +) + +func checkTypeIsSupported( + typ types.Type, + visited map[string]ResultOfChecking, + isResultType bool, + sourcePackage Package, + currentPackage Package, + depth int, +) ResultOfChecking { + if res, ok := visited[typ.String()]; ok { + return res + } + var result ResultOfChecking = Unknown + visited[typ.String()] = result + switch t := typ.(type) { + case *types.Pointer: + // no support for pointer to pointer and pointer to channel, + // support for pointer to primitive only if depth is 0 + switch t.Elem().Underlying().(type) { + case *types.Basic: + if depth == 0 { + result = SupportedType + } else { + result = UnsupportedType + } + case *types.Chan: + if depth == 0 && !isResultType { + result = SupportedType + } else { + result = UnsupportedType + } + case *types.Pointer: + result = UnsupportedType + default: + result = checkTypeIsSupported(t.Elem(), visited, isResultType, sourcePackage, currentPackage, depth+1) + } + case *types.Named: + var pkg Package + if p := t.Obj().Pkg(); p != nil { + pkg.Name = p.Name() + pkg.Path = p.Path() + } + result = checkTypeIsSupported(t.Underlying(), visited, isResultType, sourcePackage, pkg, depth+1) + case *types.Basic: + result = SupportedType + case *types.Struct: + for i := 0; i < t.NumFields(); i++ { + field := t.Field(i) + if currentPackage != sourcePackage && !field.Exported() { + continue + } + + if checkTypeIsSupported(field.Type(), visited, isResultType, sourcePackage, currentPackage, depth+1) == UnsupportedType { + visited[t.String()] = UnsupportedType + return UnsupportedType + } + } + result = SupportedType + case *types.Array: + result = checkTypeIsSupported(t.Elem(), visited, isResultType, sourcePackage, currentPackage, depth+1) + case *types.Slice: + result = checkTypeIsSupported(t.Elem(), visited, isResultType, sourcePackage, currentPackage, depth+1) + case *types.Map: + if checkTypeIsSupported(t.Key(), visited, isResultType, sourcePackage, currentPackage, depth+1) == UnsupportedType || + checkTypeIsSupported(t.Elem(), visited, isResultType, sourcePackage, currentPackage, depth+1) == UnsupportedType { + result = UnsupportedType + } else { + result = SupportedType + } + case *types.Chan: + if !isResultType && depth == 0 { + result = checkTypeIsSupported(t.Elem(), visited, isResultType, sourcePackage, currentPackage, depth+1) + } + case *types.Interface: + if isResultType { + if implementsError(t) && depth == 0 { + result = SupportedType + } else { + result = UnsupportedType + } + } else { + result = SupportedType + } + } + + if result == Unknown { + result = UnsupportedType + } + visited[typ.String()] = result + return visited[typ.String()] +} + +func checkIsSupported(signature *types.Signature, sourcePackage Package) bool { + if signature.TypeParams() != nil { // has type params + return false + } + if signature.Variadic() { // is variadic + return false + } + visited := make(map[string]ResultOfChecking, signature.Results().Len()+signature.Params().Len()) + if signature.Recv() != nil { + receiverType := signature.Recv().Type() + checkTypeIsSupported(receiverType, visited, false, sourcePackage, sourcePackage, 0) + if visited[receiverType.String()] == UnsupportedType { + return false + } + } + if results := signature.Results(); results != nil { + for i := 0; i < results.Len(); i++ { + resultType := results.At(i).Type().Underlying() + if _, ok := visited[resultType.String()]; ok { + continue + } + checkTypeIsSupported(resultType, visited, true, sourcePackage, sourcePackage, 0) + if visited[resultType.String()] == UnsupportedType { + return false + } + } + } + if parameters := signature.Params(); parameters != nil { + for i := 0; i < parameters.Len(); i++ { + paramType := parameters.At(i).Type().Underlying() + if _, ok := visited[paramType.String()]; ok { + continue + } + checkTypeIsSupported(paramType, visited, false, sourcePackage, sourcePackage, 0) + if visited[paramType.String()] == UnsupportedType { + return false + } + } + } + return true +} + +func extractConstants(info *types.Info, funcDecl *ast.FuncDecl) map[string][]string { + constantExtractor := ConstantExtractor{info: info, constants: map[string][]string{}} + ast.Walk(&constantExtractor, funcDecl) + return constantExtractor.constants +} + +func collectTargetAnalyzedFunctions( + info *types.Info, + targetFunctionNames []string, + targetMethodNames []string, + sourcePackage Package, +) ( + analyzedFunctions []AnalyzedFunction, + notSupportedFunctionNames []string, + notFoundFunctionNames []string, +) { + analyzedFunctions = []AnalyzedFunction{} + notSupportedFunctionNames = []string{} + notFoundFunctionNames = []string{} + + foundTargetFunctionNamesMap := map[string]bool{} + for _, functionName := range targetFunctionNames { + foundTargetFunctionNamesMap[functionName] = false + } + + foundTargetMethodNamesMap := map[string]bool{} + for _, functionName := range targetMethodNames { + foundTargetMethodNamesMap[functionName] = false + } + + var wg sync.WaitGroup + var mutex sync.Mutex + + for ident, obj := range info.Defs { + switch typedObj := obj.(type) { + case *types.Func: + wg.Add(1) + go func(ident *ast.Ident, typeObj *types.Func) { + defer wg.Done() + + analyzedFunction := AnalyzedFunction{ + Name: typedObj.Name(), + Parameters: []AnalyzedVariable{}, + ResultTypes: []AnalyzedVariable{}, + Constants: map[string][]string{}, + position: typedObj.Pos(), + } + + signature := typedObj.Type().(*types.Signature) + if signature.Recv() != nil { + mutex.Lock() + if isFound, ok := foundTargetMethodNamesMap[analyzedFunction.Name]; !ok || isFound { + mutex.Unlock() + return + } else { + foundTargetMethodNamesMap[analyzedFunction.Name] = true + mutex.Unlock() + } + } else { + mutex.Lock() + if isFound, ok := foundTargetFunctionNamesMap[analyzedFunction.Name]; !ok || isFound { + mutex.Unlock() + return + } else { + foundTargetFunctionNamesMap[analyzedFunction.Name] = true + mutex.Unlock() + } + } + + if !checkIsSupported(signature, sourcePackage) { + mutex.Lock() + notSupportedFunctionNames = append(notSupportedFunctionNames, analyzedFunction.Name) + mutex.Unlock() + return + } + analyzedTypes := make(map[string]AnalyzedType, signature.Params().Len()+signature.Results().Len()) + typeToIndex := make(map[string]string, signature.Params().Len()+signature.Results().Len()) + if receiver := signature.Recv(); receiver != nil { + receiverType := toAnalyzedType(receiver.Type(), analyzedTypes, typeToIndex, sourcePackage, sourcePackage, info) + analyzedFunction.Receiver = &AnalyzedVariable{ + Name: receiver.Name(), + Type: receiverType, + } + } + if parameters := signature.Params(); parameters != nil { + for i := 0; i < parameters.Len(); i++ { + parameter := parameters.At(i) + parameterType := toAnalyzedType(parameter.Type(), analyzedTypes, typeToIndex, sourcePackage, sourcePackage, info) + analyzedFunction.Parameters = append( + analyzedFunction.Parameters, + AnalyzedVariable{ + Name: parameter.Name(), + Type: parameterType, + }, + ) + } + } + if results := signature.Results(); results != nil { + for i := 0; i < results.Len(); i++ { + result := results.At(i) + resultType := toAnalyzedType(result.Type(), analyzedTypes, typeToIndex, sourcePackage, sourcePackage, info) + analyzedFunction.ResultTypes = append( + analyzedFunction.ResultTypes, + AnalyzedVariable{ + Name: result.Name(), + Type: resultType, + }, + ) + } + } + + if ident.Obj != nil { + funcDecl := ident.Obj.Decl.(*ast.FuncDecl) + analyzedFunction.Constants = extractConstants(info, funcDecl) + } + analyzedFunction.Types = analyzedTypes + + mutex.Lock() + analyzedFunctions = append(analyzedFunctions, analyzedFunction) + mutex.Unlock() + }(ident, typedObj) + } + } + + wg.Wait() + + for functionName, isFound := range foundTargetFunctionNamesMap { + if !isFound { + notFoundFunctionNames = append(notFoundFunctionNames, functionName) + } + } + for methodName, isFound := range foundTargetMethodNamesMap { + if !isFound { + notFoundFunctionNames = append(notFoundFunctionNames, methodName) + } + } + sort.Slice(analyzedFunctions, func(i, j int) bool { + return analyzedFunctions[i].position < analyzedFunctions[j].position + }) + sort.Strings(notSupportedFunctionNames) + sort.Strings(notFoundFunctionNames) + return +} diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/constant_extractor.go b/utbot-go/src/main/resources/go_source_code_analyzer/constant_extractor.go new file mode 100644 index 0000000000..b8f12389d5 --- /dev/null +++ b/utbot-go/src/main/resources/go_source_code_analyzer/constant_extractor.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "go/ast" + "go/constant" + "go/types" +) + +type ConstantExtractor struct { + info *types.Info + constants map[string][]string +} + +func (e *ConstantExtractor) Visit(node ast.Node) ast.Visitor { + if _, ok := node.(*ast.BasicLit); !ok { + return e + } + + expr, ok := node.(ast.Expr) + if !ok { + return e + } + + typeAndValue, ok := e.info.Types[expr] + if !ok { + return e + } + + var t = typeAndValue.Type + basicType, ok := t.(*types.Basic) + if !ok { + return e + } + + switch basicType.Kind() { + case types.Int, types.Int8, types.Int16, types.Int32, types.Int64: + e.constants[basicType.Name()] = append(e.constants[basicType.Name()], typeAndValue.Value.String()) + case types.Uint, types.Uint8, types.Uint16, types.Uint32, types.Uint64, types.Uintptr: + e.constants[basicType.Name()] = append(e.constants[basicType.Name()], typeAndValue.Value.String()) + case types.Float32: + if f32, ok := constant.Float32Val(typeAndValue.Value); ok { + e.constants[basicType.Name()] = append(e.constants[basicType.Name()], fmt.Sprintf("%v", f32)) + } + case types.Float64: + if f64, ok := constant.Float64Val(typeAndValue.Value); ok { + e.constants[basicType.Name()] = append(e.constants[basicType.Name()], fmt.Sprintf("%v", f64)) + } + case types.String: + e.constants[basicType.Name()] = append(e.constants[basicType.Name()], constant.StringVal(typeAndValue.Value)) + } + + return e +} diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/go.mod b/utbot-go/src/main/resources/go_source_code_analyzer/go.mod new file mode 100644 index 0000000000..cd5711b49d --- /dev/null +++ b/utbot-go/src/main/resources/go_source_code_analyzer/go.mod @@ -0,0 +1,10 @@ +module go_source_code_analyzer + +go 1.18 + +require golang.org/x/tools v0.4.0 + +require ( + golang.org/x/mod v0.7.0 // indirect + golang.org/x/sys v0.3.0 // indirect +) diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/go.sum b/utbot-go/src/main/resources/go_source_code_analyzer/go.sum new file mode 100644 index 0000000000..a20f737720 --- /dev/null +++ b/utbot-go/src/main/resources/go_source_code_analyzer/go.sum @@ -0,0 +1,7 @@ +golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= diff --git a/utbot-go/src/main/resources/go_source_code_analyzer/main.go b/utbot-go/src/main/resources/go_source_code_analyzer/main.go new file mode 100644 index 0000000000..efc8e166ed --- /dev/null +++ b/utbot-go/src/main/resources/go_source_code_analyzer/main.go @@ -0,0 +1,131 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "os" + "path/filepath" + "strconv" + "sync" + + "golang.org/x/tools/go/packages" +) + +func checkError(err error) { + if err != nil { + log.Fatal(err.Error()) + } +} + +func analyzeTarget(target AnalysisTarget) (*AnalysisResult, error) { + if len(target.TargetFunctionNames) == 0 && len(target.TargetMethodNames) == 0 { + return nil, fmt.Errorf("target must contain target functions or methods") + } + + pkgPath := filepath.Dir(target.AbsoluteFilePath) + + dir, _ := filepath.Split(target.AbsoluteFilePath) + cfg := packages.Config{ + Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedDeps | + packages.NeedImports | packages.NeedSyntax | packages.NeedFiles | packages.NeedCompiledGoFiles, + Dir: dir, + } + cfg.Env = os.Environ() + pkgs, err := packages.Load(&cfg, pkgPath) + checkError(err) + if len(pkgs) != 1 { + return nil, fmt.Errorf("cannot build multiple packages: %s", err) + } + if packages.PrintErrors(pkgs) > 0 { + return nil, fmt.Errorf("typechecking of %s failed", pkgPath) + } + + targetPackage := pkgs[0] + if len(targetPackage.CompiledGoFiles) != len(targetPackage.Syntax) { + return nil, fmt.Errorf("parsing returned nil for some files") + } + index := 0 + for ; index < len(targetPackage.CompiledGoFiles); index++ { + p1, err := filepath.Abs(targetPackage.CompiledGoFiles[index]) + checkError(err) + p2, err := filepath.Abs(target.AbsoluteFilePath) + checkError(err) + + if p1 == p2 { + break + } + } + if index == len(targetPackage.CompiledGoFiles) { + return nil, fmt.Errorf("target file not found in compiled go files") + } + + // collect required info about selected functions + analyzedFunctions, notSupportedFunctionsNames, notFoundFunctionsNames := + collectTargetAnalyzedFunctions( + targetPackage.TypesInfo, + target.TargetFunctionNames, + target.TargetMethodNames, + Package{ + Name: targetPackage.Name, + Path: targetPackage.PkgPath, + }, + ) + + return &AnalysisResult{ + AbsoluteFilePath: target.AbsoluteFilePath, + SourcePackage: Package{ + Name: targetPackage.Name, + Path: targetPackage.PkgPath, + }, + AnalyzedFunctions: analyzedFunctions, + NotSupportedFunctionNames: notSupportedFunctionsNames, + NotFoundFunctionNames: notFoundFunctionsNames, + }, nil +} + +func main() { + var targetsFilePath, resultsFilePath string + flag.StringVar(&targetsFilePath, "targets", "", "path to JSON file to read analysis targets from") + flag.StringVar(&resultsFilePath, "results", "", "path to JSON file to write analysis results to") + flag.Parse() + + // read and deserialize targets + targetsBytes, readErr := os.ReadFile(targetsFilePath) + checkError(readErr) + + var analysisTargets AnalysisTargets + fromJsonErr := json.Unmarshal(targetsBytes, &analysisTargets) + checkError(fromJsonErr) + + // parse each requested Go source file + var wg sync.WaitGroup + + results := make([]AnalysisResult, len(analysisTargets.Targets)) + for i, target := range analysisTargets.Targets { + wg.Add(1) + go func(i int, target AnalysisTarget) { + defer wg.Done() + + result, err := analyzeTarget(target) + checkError(err) + + results[i] = *result + }(i, target) + } + + wg.Wait() + + analysisResults := AnalysisResults{ + Results: results, + IntSize: strconv.IntSize, + } + + // serialize and write results + jsonBytes, toJsonErr := json.MarshalIndent(analysisResults, "", " ") + checkError(toJsonErr) + + writeErr := os.WriteFile(resultsFilePath, jsonBytes, os.ModePerm) + checkError(writeErr) +} diff --git a/utbot-gradle/build.gradle b/utbot-gradle/build.gradle index b115420101..28693ce90b 100644 --- a/utbot-gradle/build.gradle +++ b/utbot-gradle/build.gradle @@ -1,11 +1,9 @@ plugins { id 'java-gradle-plugin' id 'com.gradle.plugin-publish' version '0.18.0' - id 'com.github.johnrengelman.shadow' version '6.1.0' + id 'com.github.johnrengelman.shadow' version '7.1.2' } -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" - configurations { fetchInstrumentationJar } @@ -15,24 +13,23 @@ dependencies { shadow localGroovy() implementation project(":utbot-framework") - fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration: 'instrumentationArchive') + implementation "io.github.microutils:kotlin-logging:$kotlinLoggingVersion" - implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version + testImplementation "org.mockito:mockito-core:$mockitoVersion" + testImplementation "org.mockito:mockito-inline:$mockitoVersion" + + fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration: 'instrumentationArchive') } // needed to prevent inclusion of gradle-api into shadow JAR -configurations.compile.dependencies.remove dependencies.gradleApi() +configurations.api.dependencies.remove dependencies.gradleApi() configurations.all { exclude group: "org.apache.logging.log4j", module: "log4j-slf4j-impl" } -jar { - manifest { - // 'Fat JAR' is needed in org.utbot.framework.codegen.model.util.DependencyUtilsKt.checkDependencyIsFatJar - attributes 'JAR-Type': 'Fat JAR' - attributes 'Class-Path': configurations.compile.collect { it.getName() }.join(' ') - } +configurations { + customCompile.extendsFrom api // then customCompile.setCanBeResolved == true } /** @@ -40,7 +37,7 @@ jar { * But we need it to be packed. Workaround: double-nest the jar. */ task shadowBugWorkaround(type: Jar) { - destinationDir file('build/shadow-bug-workaround') + destinationDirectory = layout.buildDirectory.dir('build/shadow-bug-workaround') from(configurations.fetchInstrumentationJar) { into "lib" } @@ -48,6 +45,11 @@ task shadowBugWorkaround(type: Jar) { // Documentation: https://imperceptiblethoughts.com/shadow/ shadowJar { + manifest { + // 'Fat JAR' is needed in org.utbot.framework.codegen.model.util.DependencyUtilsKt.checkDependencyIsFatJar + attributes 'JAR-Type': 'Fat JAR' + attributes 'Class-Path': project.configurations.customCompile.collect { it.getName() }.join(' ') + } archiveClassifier.set('') minimize() from shadowBugWorkaround @@ -64,7 +66,7 @@ publishing { pom.withXml { // removing a dependency to `utbot-framework` from the list of dependencies asNode().dependencies.dependency.each { dependency -> - if (dependency.artifactId[0].value()[0] == 'utbot-framework') { + if (dependency.artifactId[0].value() == 'utbot-framework') { assert dependency.parent().remove(dependency) } } @@ -87,7 +89,7 @@ pluginBundle { gradlePlugin { plugins { sarifReportPlugin { - version = '1.0.0-alpha-9' // last published version + version = '1.0.0-alpha' // last published version id = 'org.utbot.gradle.plugin' displayName = 'UnitTestBot gradle plugin' description = 'The gradle plugin for generating tests and creating SARIF reports based on UnitTestBot' diff --git a/utbot-gradle/docs/utbot-gradle.md b/utbot-gradle/docs/utbot-gradle.md index 819bb6f71a..4891d32c72 100644 --- a/utbot-gradle/docs/utbot-gradle.md +++ b/utbot-gradle/docs/utbot-gradle.md @@ -40,6 +40,8 @@ __Groovy:__ generatedTestsRelativeRoot = 'build/generated/test' sarifReportsRelativeRoot = 'build/generated/sarif' markGeneratedTestsDirectoryAsTestSourcesRoot = true + testPrivateMethods = false + projectType = 'purejvm' testFramework = 'junit5' mockFramework = 'mockito' generationTimeout = 60000L @@ -58,6 +60,8 @@ __Kotlin DSL:__ generatedTestsRelativeRoot.set("build/generated/test") sarifReportsRelativeRoot.set("build/generated/sarif") markGeneratedTestsDirectoryAsTestSourcesRoot.set(true) + testPrivateMethods.set(false) + projectType.set("purejvm") testFramework.set("junit5") mockFramework.set("mockito") generationTimeout.set(60000L) @@ -69,6 +73,27 @@ __Kotlin DSL:__ } ``` +Also, you can configure the task using `-P=` syntax. + +For example, +```Kotlin +generateTestsAndSarifReport + -PtargetClasses='[com.abc.Main, com.qwerty.Util]' + -PprojectRoot='C:/.../SomeDirectory' + -PgeneratedTestsRelativeRoot='build/generated/test' + -PsarifReportsRelativeRoot='build/generated/sarif' + -PtestPrivateMethods='false' + -PtestProjectType=purejvm + -PtestFramework=junit5 + -PmockFramework=mockito + -PgenerationTimeout=60000 + -PcodegenLanguage=java + -PmockStrategy='other-packages' + -PstaticsMocking='mock-statics' + -PforceStaticMocking=force + -PclassesToMockAlways='[org.slf4j.Logger, java.util.Random]' +``` + **Note:** All configuration fields have default values, so there is no need to configure the plugin if you don't want to. **Description of fields:** @@ -93,6 +118,18 @@ __Kotlin DSL:__ - Mark the directory with generated tests as `test sources root` or not. - By default, `true` is used. +- `testPrivateMethods`– + - Generate tests for private methods or not. + - By default, `false` is used. + +- `projectType` – + - The type of project being analyzed. + - Can be one of: + - `'purejvm'` _(by default)_ + - `'spring'` + - `'python'` + - `'javascript'` + - `testFramework` – - The name of the test framework to be used. - Can be one of: @@ -208,4 +245,4 @@ Please note that the maximum archive size for publishing on the Gradle Plugin Po ### Requirements -UTBot gradle plugin requires Gradle 6.8+ +UTBot gradle plugin requires Gradle 7.4.2+ diff --git a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/GenerateTestsAndSarifReportTask.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/GenerateTestsAndSarifReportTask.kt index 4d0259ed5f..1e5081a6ba 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/GenerateTestsAndSarifReportTask.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/GenerateTestsAndSarifReportTask.kt @@ -1,117 +1,137 @@ -package org.utbot.gradle.plugin - -import mu.KLogger -import org.gradle.api.DefaultTask -import org.gradle.api.tasks.TaskAction -import org.utbot.common.bracket -import org.utbot.common.debug -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.withUtContext -import org.utbot.framework.plugin.sarif.GenerateTestsAndSarifReportFacade -import org.utbot.gradle.plugin.extension.SarifGradleExtensionProvider -import org.utbot.gradle.plugin.wrappers.GradleProjectWrapper -import org.utbot.gradle.plugin.wrappers.SourceFindingStrategyGradle -import org.utbot.gradle.plugin.wrappers.SourceSetWrapper -import org.utbot.framework.plugin.sarif.TargetClassWrapper -import javax.inject.Inject - -/** - * The main class containing the entry point [generateTestsAndSarifReport]. - * - * [Documentation](https://docs.gradle.org/current/userguide/custom_tasks.html) - */ -open class GenerateTestsAndSarifReportTask @Inject constructor( - private val sarifProperties: SarifGradleExtensionProvider -) : DefaultTask() { - - init { - group = "utbot" - description = "Generate a SARIF report" - } - - /** - * Entry point: called when the user starts this gradle task. - */ - @TaskAction - fun generateTestsAndSarifReport() { - val rootGradleProject = try { - GradleProjectWrapper(project, sarifProperties) - } catch (t: Throwable) { - logger.error(t) { "Unexpected error while configuring the gradle task" } - return - } - try { - generateForProjectRecursively(rootGradleProject) - GenerateTestsAndSarifReportFacade.mergeReports( - sarifReports = rootGradleProject.collectReportsRecursively(), - mergedSarifReportFile = rootGradleProject.sarifReportFile - ) - } catch (t: Throwable) { - logger.error(t) { "Unexpected error while generating SARIF report" } - return - } - } - - // internal - - // overwriting the getLogger() function from the DefaultTask - private val logger: KLogger = org.utbot.gradle.plugin.logger - - /** - * Generates tests and a SARIF report for classes in the [gradleProject] and in all its child projects. - */ - private fun generateForProjectRecursively(gradleProject: GradleProjectWrapper) { - gradleProject.sourceSets.forEach { sourceSet -> - generateForSourceSet(sourceSet) - } - gradleProject.childProjects.forEach { childProject -> - generateForProjectRecursively(childProject) - } - } - - /** - * Generates tests and a SARIF report for classes in the [sourceSet]. - */ - private fun generateForSourceSet(sourceSet: SourceSetWrapper) { - logger.debug().bracket("Generating tests for the '${sourceSet.sourceSet.name}' source set") { - withUtContext(UtContext(sourceSet.classLoader)) { - sourceSet.targetClasses.forEach { targetClass -> - generateForClass(sourceSet, targetClass) - } - } - } - } - - /** - * Generates tests and a SARIF report for the class [targetClass]. - */ - private fun generateForClass(sourceSet: SourceSetWrapper, targetClass: TargetClassWrapper) { - logger.debug().bracket("Generating tests for the $targetClass") { - val sourceFindingStrategy = - SourceFindingStrategyGradle(sourceSet, targetClass.testsCodeFile.path) - val generateTestsAndSarifReportFacade = - GenerateTestsAndSarifReportFacade(sarifProperties, sourceFindingStrategy) - generateTestsAndSarifReportFacade.generateForClass( - targetClass, sourceSet.workingDirectory, sourceSet.runtimeClasspath - ) - } - } - - /** - * Returns SARIF reports created for this [GradleProjectWrapper] and for all its child projects. - */ - private fun GradleProjectWrapper.collectReportsRecursively(): List = - this.sourceSets.flatMap { sourceSetWrapper -> - sourceSetWrapper.collectReports() - } + this.childProjects.flatMap { childProject -> - childProject.collectReportsRecursively() - } - - /** - * Returns SARIF reports created for this [SourceSetWrapper]. - */ - private fun SourceSetWrapper.collectReports(): List = - this.targetClasses.map { targetClass -> - targetClass.sarifReportFile.readText() - } -} +package org.utbot.gradle.plugin + +import mu.KLogger +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction +import org.utbot.common.measureTime +import org.utbot.common.debug +import org.utbot.framework.plugin.api.TestCaseGenerator +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.framework.plugin.sarif.GenerateTestsAndSarifReportFacade +import org.utbot.gradle.plugin.extension.SarifGradleExtensionProvider +import org.utbot.gradle.plugin.wrappers.GradleProjectWrapper +import org.utbot.gradle.plugin.wrappers.SourceFindingStrategyGradle +import org.utbot.gradle.plugin.wrappers.SourceSetWrapper +import org.utbot.framework.plugin.sarif.TargetClassWrapper +import org.utbot.framework.plugin.services.JdkInfoDefaultProvider +import java.io.File +import java.net.URLClassLoader +import javax.inject.Inject + +/** + * The main class containing the entry point [generateTestsAndSarifReport]. + * + * [Documentation](https://docs.gradle.org/current/userguide/custom_tasks.html) + */ +open class GenerateTestsAndSarifReportTask @Inject constructor( + private val sarifProperties: SarifGradleExtensionProvider +) : DefaultTask() { + + init { + group = "utbot" + description = "Generate a SARIF report" + } + + /** + * Entry point: called when the user starts this gradle task. + */ + @TaskAction + fun generateTestsAndSarifReport() { + // the user specifies the parameters using "-Pname=value" + sarifProperties.taskParameters = project.gradle.startParameter.projectProperties + val rootGradleProject = try { + GradleProjectWrapper(project, sarifProperties) + } catch (t: Throwable) { + logger.error(t) { "Unexpected error while configuring the gradle task" } + return + } + try { + + generateForProjectRecursively(rootGradleProject) + GenerateTestsAndSarifReportFacade.mergeReports( + sarifReports = rootGradleProject.collectReportsRecursively(), + mergedSarifReportFile = rootGradleProject.sarifReportFile + ) + } catch (t: Throwable) { + logger.error(t) { "Unexpected error while generating SARIF report" } + return + } + } + + // internal + + // overwriting the getLogger() function from the DefaultTask + private val logger: KLogger = org.utbot.gradle.plugin.logger + + private val dependencyPaths by lazy { + val thisClassLoader = this::class.java.classLoader as? URLClassLoader + ?: return@lazy System.getProperty("java.class.path") + thisClassLoader.urLs.joinToString(File.pathSeparator) { it.path } + } + + /** + * Generates tests and a SARIF report for classes in the [gradleProject] and in all its child projects. + */ + private fun generateForProjectRecursively(gradleProject: GradleProjectWrapper) { + gradleProject.sourceSets.forEach { sourceSet -> + generateForSourceSet(sourceSet) + } + gradleProject.childProjects.forEach { childProject -> + generateForProjectRecursively(childProject) + } + } + + /** + * Generates tests and a SARIF report for classes in the [sourceSet]. + */ + private fun generateForSourceSet(sourceSet: SourceSetWrapper) { + logger.debug().measureTime({ "Generating tests for the '${sourceSet.sourceSet.name}' source set" }) { + withUtContext(UtContext(sourceSet.classLoader)) { + val testCaseGenerator = + TestCaseGenerator( + listOf(sourceSet.workingDirectory), + sourceSet.runtimeClasspath, + dependencyPaths, + JdkInfoDefaultProvider().info + ) + sourceSet.targetClasses.forEach { targetClass -> + generateForClass(sourceSet, targetClass, testCaseGenerator) + } + } + } + } + + /** + * Generates tests and a SARIF report for the class [targetClass]. + */ + private fun generateForClass( + sourceSet: SourceSetWrapper, + targetClass: TargetClassWrapper, + testCaseGenerator: TestCaseGenerator, + ) { + logger.debug().measureTime({ "Generating tests for the $targetClass" }) { + val sourceFindingStrategy = SourceFindingStrategyGradle(sourceSet, targetClass.testsCodeFile.path) + GenerateTestsAndSarifReportFacade(sarifProperties, sourceFindingStrategy, testCaseGenerator) + .generateForClass(targetClass, sourceSet.workingDirectory) + } + } + + /** + * Returns SARIF reports created for this [GradleProjectWrapper] and for all its child projects. + */ + private fun GradleProjectWrapper.collectReportsRecursively(): List = + this.sourceSets.flatMap { sourceSetWrapper -> + sourceSetWrapper.collectReports() + } + this.childProjects.flatMap { childProject -> + childProject.collectReportsRecursively() + } + + /** + * Returns SARIF reports created for this [SourceSetWrapper]. + */ + private fun SourceSetWrapper.collectReports(): List = + this.targetClasses.map { targetClass -> + targetClass.sarifReportFile.readText() + } +} diff --git a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtension.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtension.kt index 1ebbc1dfda..5fda696742 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtension.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtension.kt @@ -43,6 +43,18 @@ abstract class SarifGradleExtension { @get:Input abstract val markGeneratedTestsDirectoryAsTestSourcesRoot: Property + /** + * Generate tests for private methods or not. + */ + @get:Input + abstract val testPrivateMethods: Property + + /** + * Can be one of: 'purejvm', 'spring', 'python', 'javascript`. + */ + @get:Input + abstract val projectType: Property + /** * Can be one of: 'junit4', 'junit5', 'testng'. */ diff --git a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProvider.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProvider.kt index 67c8e18d89..de56527fb6 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProvider.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProvider.kt @@ -2,9 +2,10 @@ package org.utbot.gradle.plugin.extension import org.gradle.api.Project import org.utbot.common.PathUtil.toPath -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.StaticsMocking -import org.utbot.framework.codegen.TestFramework +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.TestFramework import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.MockFramework @@ -15,70 +16,116 @@ import java.io.File /** * Provides all [SarifGradleExtension] fields in a convenient form: * Defines default values and a transform function for these fields. + * Takes the fields from the [taskParameters] if they are available there, + * otherwise takes them from the [extension]. */ class SarifGradleExtensionProvider( private val project: Project, - private val extension: SarifGradleExtension + private val extension: SarifGradleExtension, + var taskParameters: Map = mapOf() ) : SarifExtensionProvider { override val targetClasses: List - get() = extension.targetClasses - .getOrElse(listOf()) + get() = taskParameters["targetClasses"]?.transformKeywordAll()?.parseToList() + ?: extension.targetClasses.orNull + ?: listOf() override val projectRoot: File - get() = extension.projectRoot.orNull + get() = (taskParameters["projectRoot"] ?: extension.projectRoot.orNull) ?.toPath()?.toFile() ?: project.projectDir override val generatedTestsRelativeRoot: String - get() = extension.generatedTestsRelativeRoot.orNull + get() = taskParameters["generatedTestsRelativeRoot"] + ?: extension.generatedTestsRelativeRoot.orNull ?: "build/generated/test" override val sarifReportsRelativeRoot: String - get() = extension.sarifReportsRelativeRoot.orNull + get() = taskParameters["sarifReportsRelativeRoot"] + ?: extension.sarifReportsRelativeRoot.orNull ?: "build/generated/sarif" + // We don't get this field from `taskParameters` because marking the directory + // as a test source root is possible while the gradle project is reloading, + // but `taskParameters` become available only when the user runs the gradle task + // `generateTestsAndSarifReport` (that is, after a reloading). override val markGeneratedTestsDirectoryAsTestSourcesRoot: Boolean get() = extension.markGeneratedTestsDirectoryAsTestSourcesRoot.orNull ?: true + override val testPrivateMethods: Boolean + get() = taskParameters["testPrivateMethods"]?.let { it == "true"} + ?: extension.testPrivateMethods.orNull + ?: false + + override val projectType: ProjectType + get() = (taskParameters["projectType"] ?: extension.projectType.orNull) + ?.let(::projectTypeParse) + ?: ProjectType.PureJvm + override val testFramework: TestFramework - get() = extension.testFramework - .map(::testFrameworkParse) - .getOrElse(TestFramework.defaultItem) + get() = (taskParameters["testFramework"] ?: extension.testFramework.orNull) + ?.let(::testFrameworkParse) + ?: TestFramework.defaultItem override val mockFramework: MockFramework - get() = extension.mockFramework - .map(::mockFrameworkParse) - .getOrElse(MockFramework.defaultItem) + get() = (taskParameters["mockFramework"] ?: extension.mockFramework.orNull) + ?.let(::mockFrameworkParse) + ?: MockFramework.defaultItem override val generationTimeout: Long - get() = extension.generationTimeout - .map(::generationTimeoutParse) - .getOrElse(60 * 1000L) // 60 seconds + get() = (taskParameters["generationTimeout"]?.toLongOrNull() ?: extension.generationTimeout.orNull) + ?.let(::generationTimeoutParse) + ?: (60 * 1000L) // 60 seconds override val codegenLanguage: CodegenLanguage - get() = extension.codegenLanguage - .map(::codegenLanguageParse) - .getOrElse(CodegenLanguage.defaultItem) + get() = (taskParameters["codegenLanguage"] ?: extension.codegenLanguage.orNull) + ?.let(::codegenLanguageParse) + ?: CodegenLanguage.defaultItem override val mockStrategy: MockStrategyApi - get() = extension.mockStrategy - .map(::mockStrategyParse) - .getOrElse(MockStrategyApi.defaultItem) + get() = (taskParameters["mockStrategy"] ?: extension.mockStrategy.orNull) + ?.let(::mockStrategyParse) + ?: MockStrategyApi.defaultItem override val staticsMocking: StaticsMocking - get() = extension.staticsMocking - .map(::staticsMockingParse) - .getOrElse(StaticsMocking.defaultItem) + get() = (taskParameters["staticsMocking"] ?: extension.staticsMocking.orNull) + ?.let(::staticsMockingParse) + ?: StaticsMocking.defaultItem override val forceStaticMocking: ForceStaticMocking - get() = extension.forceStaticMocking - .map(::forceStaticMockingParse) - .getOrElse(ForceStaticMocking.defaultItem) + get() = (taskParameters["forceStaticMocking"] ?: extension.forceStaticMocking.orNull) + ?.let(::forceStaticMockingParse) + ?: ForceStaticMocking.defaultItem override val classesToMockAlways: Set get() = classesToMockAlwaysParse( - extension.classesToMockAlways.getOrElse(listOf()) + specifiedClasses = taskParameters["classesToMockAlways"]?.parseToList() + ?: extension.classesToMockAlways.orNull + ?: listOf() ) + + /** + * SARIF report file containing static analysis information about all [targetClasses]. + */ + val mergedSarifReportFileName: String? + get() = taskParameters["mergedSarifReportFileName"] + + // internal + + /** + * Keyword "all" is the same as "[]" for [targetClasses], but more user-friendly. + */ + private fun String.transformKeywordAll(): String = + if (this == "all") "[]" else this + + /** + * Example: "[A, B, C]" -> ["A", "B", "C"]. + */ + private fun String.parseToList() = + this.removePrefix("[") + .removeSuffix("]") + .split(",") + .map { it.trim() } + .filter { it != "" } } diff --git a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/GradleProjectWrapper.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/GradleProjectWrapper.kt index 2a7c7748f1..ad40f4dcd0 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/GradleProjectWrapper.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/GradleProjectWrapper.kt @@ -54,12 +54,12 @@ class GradleProjectWrapper( } /** - * SARIF report file containing results from all others reports from the [project]. + * SARIF report file containing results from all other reports from the [project]. */ val sarifReportFile: File by lazy { Paths.get( generatedSarifDirectory.path, - "${project.name}Report.sarif" + sarifProperties.mergedSarifReportFileName ?: "${project.name}Report.sarif" ).toFile().apply { createNewFileWithParentDirectories() } diff --git a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceFindingStrategyGradle.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceFindingStrategyGradle.kt index e45a190a17..7f7fc5f093 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceFindingStrategyGradle.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceFindingStrategyGradle.kt @@ -3,6 +3,7 @@ package org.utbot.gradle.plugin.wrappers import org.utbot.common.PathUtil import org.utbot.common.PathUtil.toPath import org.utbot.sarif.SourceFindingStrategy +import java.io.File /** * The search strategy based on the information available to the Gradle. @@ -37,6 +38,13 @@ class SourceFindingStrategyGradle( } ?: defaultPath } + /** + * Finds the source file containing the class [classFqn]. + * Returns null if the file does not exist. + */ + override fun getSourceFile(classFqn: String, extension: String?): File? = + sourceSet.findSourceCodeFile(classFqn) + // internal private val projectRootPath = sourceSet.parentProject.sarifProperties.projectRoot.absolutePath diff --git a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapper.kt b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapper.kt index 47dff001b8..2b306a514c 100644 --- a/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapper.kt +++ b/utbot-gradle/src/main/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapper.kt @@ -90,7 +90,8 @@ class SourceSetWrapper( classUnderTest, sourceCodeFile, createTestsCodeFile(classFqn), - createSarifReportFile(classFqn) + createSarifReportFile(classFqn), + parentProject.sarifProperties.testPrivateMethods ) } diff --git a/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProviderTest.kt b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProviderTest.kt index 0bc54f1c3e..9432b519fe 100644 --- a/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProviderTest.kt +++ b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/extension/SarifGradleExtensionProviderTest.kt @@ -8,7 +8,16 @@ import org.junit.jupiter.api.assertThrows import org.mockito.Mockito import org.utbot.common.PathUtil.toPath import org.utbot.engine.Mocker -import org.utbot.framework.codegen.* +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.Junit4 +import org.utbot.framework.codegen.domain.Junit5 +import org.utbot.framework.codegen.domain.MockitoStaticMocking +import org.utbot.framework.codegen.domain.NoStaticMocking +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.codegen.domain.ProjectType.* +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.codegen.domain.TestNg import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage import org.utbot.framework.plugin.api.MockFramework @@ -23,19 +32,46 @@ class SarifGradleExtensionProviderTest { inner class TargetClassesTest { @Test fun `should be an empty list by default`() { - setTargetClasses(null) + setTargetClassesInExtension(null) assertEquals(listOf(), extensionProvider.targetClasses) } @Test fun `should be provided from the extension`() { val targetClasses = listOf("com.abc.Main") - setTargetClasses(targetClasses) + setTargetClassesInExtension(targetClasses) assertEquals(targetClasses, extensionProvider.targetClasses) } - private fun setTargetClasses(value: List?) = + @Test + fun `should be provided from the task parameters`() { + val targetClasses = listOf("com.abc.Main") + setTargetClassesInTaskParameters(targetClasses) + assertEquals(targetClasses, extensionProvider.targetClasses) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + val targetClasses = listOf("com.abc.Main") + val anotherTargetClasses = listOf("com.abc.Another") + setTargetClassesInTaskParameters(targetClasses) + setTargetClassesInExtension(anotherTargetClasses) + assertEquals(targetClasses, extensionProvider.targetClasses) + } + + @Test + fun `should be resolved from the keyword 'all'`() { + extensionProvider.taskParameters = mapOf("targetClasses" to "all") + assertEquals(listOf(), extensionProvider.targetClasses) + } + + private fun setTargetClassesInExtension(value: List?) { Mockito.`when`(extensionMock.targetClasses).thenReturn(createListProperty(value)) + } + + private fun setTargetClassesInTaskParameters(value: List) { + extensionProvider.taskParameters = mapOf("targetClasses" to value.joinToString(",", "[", "]")) + } } @Nested @@ -43,19 +79,40 @@ class SarifGradleExtensionProviderTest { inner class ProjectRootTest { @Test fun `should be projectDir by default`() { - setProjectRoot(null) + setProjectRootInExtension(null) assertEquals(project.projectDir, extensionProvider.projectRoot) } @Test fun `should be provided from the extension`() { val projectRoot = "some/dir/" - setProjectRoot(projectRoot) + setProjectRootInExtension(projectRoot) + assertEquals(File(projectRoot), extensionProvider.projectRoot) + } + + @Test + fun `should be provided from the task parameters`() { + val projectRoot = "some/directory/" + setProjectRootInTaskParameters(projectRoot) + assertEquals(File(projectRoot), extensionProvider.projectRoot) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + val projectRoot = "some/dir/" + val anotherProjectRoot = "another/dir/" + setProjectRootInTaskParameters(projectRoot) + setProjectRootInExtension(anotherProjectRoot) assertEquals(File(projectRoot), extensionProvider.projectRoot) } - private fun setProjectRoot(value: String?) = + private fun setProjectRootInExtension(value: String?) { Mockito.`when`(extensionMock.projectRoot).thenReturn(createStringProperty(value)) + } + + private fun setProjectRootInTaskParameters(value: String) { + extensionProvider.taskParameters = mapOf("projectRoot" to value) + } } @Nested @@ -64,19 +121,40 @@ class SarifGradleExtensionProviderTest { @Test fun `should be build generated test by default`() { val testsRootExpected = "build/generated/test" - setGeneratedTestsRelativeRoot(null) + setGeneratedTestsRelativeRootInExtension(null) assertEquals(testsRootExpected.toPath(), extensionProvider.generatedTestsRelativeRoot.toPath()) } @Test fun `should be provided from the extension`() { val testsRoot = "some/dir/" - setGeneratedTestsRelativeRoot(testsRoot) + setGeneratedTestsRelativeRootInExtension(testsRoot) + assertEquals(testsRoot.toPath(), extensionProvider.generatedTestsRelativeRoot.toPath()) + } + + @Test + fun `should be provided from the task parameters`() { + val testsRoot = "some/directory/" + setGeneratedTestsRelativeRootInTaskParameters(testsRoot) + assertEquals(testsRoot.toPath(), extensionProvider.generatedTestsRelativeRoot.toPath()) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + val testsRoot = "some/dir/" + val anotherTestsRoot = "another/dir/" + setGeneratedTestsRelativeRootInTaskParameters(testsRoot) + setGeneratedTestsRelativeRootInExtension(anotherTestsRoot) assertEquals(testsRoot.toPath(), extensionProvider.generatedTestsRelativeRoot.toPath()) } - private fun setGeneratedTestsRelativeRoot(value: String?) = + private fun setGeneratedTestsRelativeRootInExtension(value: String?) { Mockito.`when`(extensionMock.generatedTestsRelativeRoot).thenReturn(createStringProperty(value)) + } + + private fun setGeneratedTestsRelativeRootInTaskParameters(value: String) { + extensionProvider.taskParameters = mapOf("generatedTestsRelativeRoot" to value) + } } @Nested @@ -84,7 +162,7 @@ class SarifGradleExtensionProviderTest { inner class SarifReportsRelativeRootTest { @Test fun `should be build generated sarif by default`() { - setSarifReportsRelativeRoot(null) + setSarifReportsRelativeRootInExtension(null) val sarifRoot = "build/generated/sarif" assertEquals(sarifRoot.toPath(), extensionProvider.sarifReportsRelativeRoot.toPath()) } @@ -92,12 +170,33 @@ class SarifGradleExtensionProviderTest { @Test fun `should be provided from the extension`() { val sarifRoot = "some/dir/" - setSarifReportsRelativeRoot(sarifRoot) + setSarifReportsRelativeRootInExtension(sarifRoot) + assertEquals(sarifRoot.toPath(), extensionProvider.sarifReportsRelativeRoot.toPath()) + } + + @Test + fun `should be provided from the task parameters`() { + val sarifRoot = "some/directory/" + setSarifReportsRelativeRootInTaskParameters(sarifRoot) + assertEquals(sarifRoot.toPath(), extensionProvider.sarifReportsRelativeRoot.toPath()) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + val sarifRoot = "some/dir/" + val anotherSarifRoot = "another/dir/" + setSarifReportsRelativeRootInTaskParameters(sarifRoot) + setSarifReportsRelativeRootInExtension(anotherSarifRoot) assertEquals(sarifRoot.toPath(), extensionProvider.sarifReportsRelativeRoot.toPath()) } - private fun setSarifReportsRelativeRoot(value: String?) = + private fun setSarifReportsRelativeRootInExtension(value: String?) { Mockito.`when`(extensionMock.sarifReportsRelativeRoot).thenReturn(createStringProperty(value)) + } + + private fun setSarifReportsRelativeRootInTaskParameters(value: String) { + extensionProvider.taskParameters = mapOf("sarifReportsRelativeRoot" to value) + } } @Nested @@ -115,8 +214,110 @@ class SarifGradleExtensionProviderTest { assertEquals(false, extensionProvider.markGeneratedTestsDirectoryAsTestSourcesRoot) } - private fun setMark(value: Boolean?) = - Mockito.`when`(extensionMock.markGeneratedTestsDirectoryAsTestSourcesRoot).thenReturn(createBooleanProperty(value)) + private fun setMark(value: Boolean?) { + Mockito.`when`(extensionMock.markGeneratedTestsDirectoryAsTestSourcesRoot) + .thenReturn(createBooleanProperty(value)) + } + } + + @Nested + @DisplayName("testPrivateMethods") + inner class TestPrivateMethodsTest { + @Test + fun `should be false by default`() { + setTestPrivateMethodsInExtension(null) + assertEquals(false, extensionProvider.testPrivateMethods) + } + + @Test + fun `should be provided from the extension`() { + setTestPrivateMethodsInExtension(true) + assertEquals(true, extensionProvider.testPrivateMethods) + } + + @Test + fun `should be provided from the task parameters`() { + setTestPrivateMethodsInTaskParameters(true) + assertEquals(true, extensionProvider.testPrivateMethods) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + setTestPrivateMethodsInTaskParameters(false) + setTestPrivateMethodsInExtension(true) + assertEquals(false, extensionProvider.testPrivateMethods) + } + + private fun setTestPrivateMethodsInExtension(value: Boolean?) { + Mockito.`when`(extensionMock.testPrivateMethods).thenReturn(createBooleanProperty(value)) + } + + private fun setTestPrivateMethodsInTaskParameters(value: Boolean) { + extensionProvider.taskParameters = mapOf("testPrivateMethods" to "$value") + } + } + + @Nested + @DisplayName("projectType") + inner class ProjectTypeTest { + @Test + fun `should be ProjectType defaultItem by default`() { + setProjectTypeInExtension(null) + assertEquals(PureJvm, extensionProvider.projectType) + } + + @Test + fun `should be equal to PureJvm`() { + setProjectTypeInExtension("purejvm") + assertEquals(PureJvm, extensionProvider.projectType) + } + + @Test + fun `should be equal to Spring`() { + setProjectTypeInExtension("spring") + assertEquals(Spring, extensionProvider.projectType) + } + + @Test + fun `should be equal to Python`() { + setProjectTypeInExtension("python") + assertEquals(Python, extensionProvider.projectType) + } + + @Test + fun `should be equal to JavaScript`() { + setProjectTypeInExtension("javascript") + assertEquals(JavaScript, extensionProvider.projectType) + } + + @Test + fun `should fail on unknown project type`() { + setProjectTypeInExtension("unknown") + assertThrows { + extensionProvider.projectType + } + } + + @Test + fun `should be provided from the task parameters`() { + setProjectTypeInTaskParameters("spring") + assertEquals(Spring, extensionProvider.projectType) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + setProjectTypeInTaskParameters("python") + setProjectTypeInExtension("javascript") + assertEquals(Python, extensionProvider.projectType) + } + + private fun setProjectTypeInExtension(value: String?) { + Mockito.`when`(extensionMock.projectType).thenReturn(createStringProperty(value)) + } + + private fun setProjectTypeInTaskParameters(value: String) { + extensionProvider.taskParameters = mapOf("projectType" to value) + } } @Nested @@ -124,38 +325,56 @@ class SarifGradleExtensionProviderTest { inner class TestFrameworkTest { @Test fun `should be TestFramework defaultItem by default`() { - setTestFramework(null) + setTestFrameworkInExtension(null) assertEquals(TestFramework.defaultItem, extensionProvider.testFramework) } @Test fun `should be equal to Junit4`() { - setTestFramework("junit4") + setTestFrameworkInExtension("junit4") assertEquals(Junit4, extensionProvider.testFramework) } @Test fun `should be equal to Junit5`() { - setTestFramework("junit5") + setTestFrameworkInExtension("junit5") assertEquals(Junit5, extensionProvider.testFramework) } @Test fun `should be equal to TestNg`() { - setTestFramework("testng") + setTestFrameworkInExtension("testng") assertEquals(TestNg, extensionProvider.testFramework) } @Test fun `should fail on unknown test framework`() { - setTestFramework("unknown") + setTestFrameworkInExtension("unknown") assertThrows { extensionProvider.testFramework } } - private fun setTestFramework(value: String?) = + @Test + fun `should be provided from the task parameters`() { + setTestFrameworkInTaskParameters("junit4") + assertEquals(Junit4, extensionProvider.testFramework) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + setTestFrameworkInTaskParameters("testng") + setTestFrameworkInExtension("junit5") + assertEquals(TestNg, extensionProvider.testFramework) + } + + private fun setTestFrameworkInExtension(value: String?) { Mockito.`when`(extensionMock.testFramework).thenReturn(createStringProperty(value)) + } + + private fun setTestFrameworkInTaskParameters(value: String) { + extensionProvider.taskParameters = mapOf("testFramework" to value) + } } @Nested @@ -163,26 +382,46 @@ class SarifGradleExtensionProviderTest { inner class MockFrameworkTest { @Test fun `should be MockFramework defaultItem by default`() { - setMockFramework(null) + setMockFrameworkInExtension(null) assertEquals(MockFramework.defaultItem, extensionProvider.mockFramework) } @Test fun `should be equal to MOCKITO`() { - setMockFramework("mockito") + setMockFrameworkInExtension("mockito") assertEquals(MockFramework.MOCKITO, extensionProvider.mockFramework) } @Test fun `should fail on unknown mock framework`() { - setMockFramework("unknown") + setMockFrameworkInExtension("unknown") assertThrows { extensionProvider.mockFramework } } - private fun setMockFramework(value: String?) = + @Test + fun `should be provided from the task parameters`() { + setMockFrameworkInTaskParameters("mockito") + assertEquals(MockFramework.MOCKITO, extensionProvider.mockFramework) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + setMockFrameworkInTaskParameters("unknown") + setMockFrameworkInExtension("mockito") + assertThrows { + extensionProvider.mockFramework + } + } + + private fun setMockFrameworkInExtension(value: String?) { Mockito.`when`(extensionMock.mockFramework).thenReturn(createStringProperty(value)) + } + + private fun setMockFrameworkInTaskParameters(value: String) { + extensionProvider.taskParameters = mapOf("mockFramework" to value) + } } @Nested @@ -190,26 +429,44 @@ class SarifGradleExtensionProviderTest { inner class GenerationTimeoutTest { @Test fun `should be 60 seconds by default`() { - setGenerationTimeout(null) + setGenerationTimeoutInExtension(null) assertEquals(60 * 1000L, extensionProvider.generationTimeout) } @Test fun `should be provided from the extension`() { - setGenerationTimeout(100L) + setGenerationTimeoutInExtension(100L) + assertEquals(100L, extensionProvider.generationTimeout) + } + + @Test + fun `should be provided from the task parameters`() { + setGenerationTimeoutInTaskParameters("100") assertEquals(100L, extensionProvider.generationTimeout) } + @Test + fun `should be provided from the task parameters, not from the extension`() { + setGenerationTimeoutInTaskParameters("999") + setGenerationTimeoutInExtension(100L) + assertEquals(999L, extensionProvider.generationTimeout) + } + @Test fun `should fail on negative timeout`() { - setGenerationTimeout(-1) + setGenerationTimeoutInExtension(-1) assertThrows { extensionProvider.generationTimeout } } - private fun setGenerationTimeout(value: Long?) = + private fun setGenerationTimeoutInExtension(value: Long?) { Mockito.`when`(extensionMock.generationTimeout).thenReturn(createLongProperty(value)) + } + + private fun setGenerationTimeoutInTaskParameters(value: String) { + extensionProvider.taskParameters = mapOf("generationTimeout" to value) + } } @Nested @@ -217,32 +474,50 @@ class SarifGradleExtensionProviderTest { inner class CodegenLanguageTest { @Test fun `should be CodegenLanguage defaultItem by default`() { - setCodegenLanguage(null) + setCodegenLanguageInExtension(null) assertEquals(CodegenLanguage.defaultItem, extensionProvider.codegenLanguage) } @Test fun `should be equal to JAVA`() { - setCodegenLanguage("java") + setCodegenLanguageInExtension("java") assertEquals(CodegenLanguage.JAVA, extensionProvider.codegenLanguage) } @Test fun `should be equal to KOTLIN`() { - setCodegenLanguage("kotlin") + setCodegenLanguageInExtension("kotlin") assertEquals(CodegenLanguage.KOTLIN, extensionProvider.codegenLanguage) } @Test fun `should fail on unknown codegen language`() { - setCodegenLanguage("unknown") + setCodegenLanguageInExtension("unknown") assertThrows { extensionProvider.codegenLanguage } } - private fun setCodegenLanguage(value: String?) = + @Test + fun `should be provided from the task parameters`() { + setCodegenLanguageInTaskParameters("kotlin") + assertEquals(CodegenLanguage.KOTLIN, extensionProvider.codegenLanguage) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + setCodegenLanguageInTaskParameters("java") + setCodegenLanguageInExtension("kotlin") + assertEquals(CodegenLanguage.JAVA, extensionProvider.codegenLanguage) + } + + private fun setCodegenLanguageInExtension(value: String?) { Mockito.`when`(extensionMock.codegenLanguage).thenReturn(createStringProperty(value)) + } + + private fun setCodegenLanguageInTaskParameters(value: String) { + extensionProvider.taskParameters = mapOf("codegenLanguage" to value) + } } @Nested @@ -250,38 +525,56 @@ class SarifGradleExtensionProviderTest { inner class MockStrategyTest { @Test fun `should be MockStrategyApi defaultItem by default`() { - setMockStrategy(null) + setMockStrategyInExtension(null) assertEquals(MockStrategyApi.defaultItem, extensionProvider.mockStrategy) } @Test fun `should be equal to NO_MOCKS`() { - setMockStrategy("no-mocks") + setMockStrategyInExtension("no-mocks") assertEquals(MockStrategyApi.NO_MOCKS, extensionProvider.mockStrategy) } @Test fun `should be equal to OTHER_PACKAGES`() { - setMockStrategy("other-packages") + setMockStrategyInExtension("other-packages") assertEquals(MockStrategyApi.OTHER_PACKAGES, extensionProvider.mockStrategy) } @Test fun `should be equal to OTHER_CLASSES`() { - setMockStrategy("other-classes") + setMockStrategyInExtension("other-classes") assertEquals(MockStrategyApi.OTHER_CLASSES, extensionProvider.mockStrategy) } @Test fun `should fail on unknown mock strategy`() { - setMockStrategy("unknown") + setMockStrategyInExtension("unknown") assertThrows { extensionProvider.mockStrategy } } - private fun setMockStrategy(value: String?) = + @Test + fun `should be provided from the task parameters`() { + setMockStrategyInTaskParameters("no-mocks") + assertEquals(MockStrategyApi.NO_MOCKS, extensionProvider.mockStrategy) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + setMockStrategyInTaskParameters("other-packages") + setMockStrategyInExtension("other-classes") + assertEquals(MockStrategyApi.OTHER_PACKAGES, extensionProvider.mockStrategy) + } + + private fun setMockStrategyInExtension(value: String?) { Mockito.`when`(extensionMock.mockStrategy).thenReturn(createStringProperty(value)) + } + + private fun setMockStrategyInTaskParameters(value: String) { + extensionProvider.taskParameters = mapOf("mockStrategy" to value) + } } @Nested @@ -289,32 +582,50 @@ class SarifGradleExtensionProviderTest { inner class StaticsMockingTest { @Test fun `should be StaticsMocking defaultItem by default`() { - setStaticsMocking(null) + setStaticsMockingInExtension(null) assertEquals(StaticsMocking.defaultItem, extensionProvider.staticsMocking) } @Test fun `should be equal to NoStaticMocking`() { - setStaticsMocking("do-not-mock-statics") + setStaticsMockingInExtension("do-not-mock-statics") assertEquals(NoStaticMocking, extensionProvider.staticsMocking) } @Test fun `should be equal to`() { - setStaticsMocking("mock-statics") + setStaticsMockingInExtension("mock-statics") assertEquals(MockitoStaticMocking, extensionProvider.staticsMocking) } @Test fun `should fail on unknown statics mocking`() { - setStaticsMocking("unknown") + setStaticsMockingInExtension("unknown") assertThrows { extensionProvider.staticsMocking } } - private fun setStaticsMocking(value: String?) = + @Test + fun `should be provided from the task parameters`() { + setStaticsMockingInTaskParameters("do-not-mock-statics") + assertEquals(NoStaticMocking, extensionProvider.staticsMocking) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + setStaticsMockingInTaskParameters("mock-statics") + setStaticsMockingInExtension("do-not-mock-statics") + assertEquals(MockitoStaticMocking, extensionProvider.staticsMocking) + } + + private fun setStaticsMockingInExtension(value: String?) { Mockito.`when`(extensionMock.staticsMocking).thenReturn(createStringProperty(value)) + } + + private fun setStaticsMockingInTaskParameters(value: String) { + extensionProvider.taskParameters = mapOf("staticsMocking" to value) + } } @Nested @@ -322,32 +633,50 @@ class SarifGradleExtensionProviderTest { inner class ForceStaticMockingTest { @Test fun `should be ForceStaticMocking defaultItem by default`() { - setForceStaticMocking(null) + setForceStaticMockingInExtension(null) assertEquals(ForceStaticMocking.defaultItem, extensionProvider.forceStaticMocking) } @Test fun `should be equal to FORCE`() { - setForceStaticMocking("force") + setForceStaticMockingInExtension("force") assertEquals(ForceStaticMocking.FORCE, extensionProvider.forceStaticMocking) } @Test fun `should be equal to DO_NOT_FORCE`() { - setForceStaticMocking("do-not-force") + setForceStaticMockingInExtension("do-not-force") assertEquals(ForceStaticMocking.DO_NOT_FORCE, extensionProvider.forceStaticMocking) } @Test fun `should fail on unknown force static mocking`() { - setForceStaticMocking("unknown") + setForceStaticMockingInExtension("unknown") assertThrows { extensionProvider.forceStaticMocking } } - private fun setForceStaticMocking(value: String?) = + @Test + fun `should be provided from the task parameters`() { + setForceStaticMockingInTaskParameters("do-not-force") + assertEquals(ForceStaticMocking.DO_NOT_FORCE, extensionProvider.forceStaticMocking) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + setForceStaticMockingInTaskParameters("force") + setForceStaticMockingInExtension("do-not-force") + assertEquals(ForceStaticMocking.FORCE, extensionProvider.forceStaticMocking) + } + + private fun setForceStaticMockingInExtension(value: String?) { Mockito.`when`(extensionMock.forceStaticMocking).thenReturn(createStringProperty(value)) + } + + private fun setForceStaticMockingInTaskParameters(value: String) { + extensionProvider.taskParameters = mapOf("forceStaticMocking" to value) + } } @Nested @@ -359,7 +688,7 @@ class SarifGradleExtensionProviderTest { @Test fun `should be defaultSuperClassesToMockAlwaysNames by default`() { - setClassesToMockAlways(null) + setClassesToMockAlwaysInExtension(null) assertEquals(defaultClasses, extensionProvider.classesToMockAlways) } @@ -367,12 +696,34 @@ class SarifGradleExtensionProviderTest { fun `should be provided from the extension`() { val classes = listOf("com.abc.Main") val expectedClasses = classes.map(::ClassId).toSet() + defaultClasses - setClassesToMockAlways(classes) + setClassesToMockAlwaysInExtension(classes) + assertEquals(expectedClasses, extensionProvider.classesToMockAlways) + } + + @Test + fun `should be provided from the task parameters`() { + val classes = listOf("com.abc.Main") + val expectedClasses = classes.map(::ClassId).toSet() + defaultClasses + setClassesToMockAlwaysInTaskParameters(classes) + assertEquals(expectedClasses, extensionProvider.classesToMockAlways) + } + + @Test + fun `should be provided from the task parameters, not from the extension`() { + val classes = listOf("com.abc.Main") + val anotherClasses = listOf("com.abc.Another") + val expectedClasses = classes.map(::ClassId).toSet() + defaultClasses + setClassesToMockAlwaysInTaskParameters(classes) + setClassesToMockAlwaysInExtension(anotherClasses) assertEquals(expectedClasses, extensionProvider.classesToMockAlways) } - private fun setClassesToMockAlways(value: List?) = + private fun setClassesToMockAlwaysInExtension(value: List?) = Mockito.`when`(extensionMock.classesToMockAlways).thenReturn(createListProperty(value)) + + private fun setClassesToMockAlwaysInTaskParameters(value: List) { + extensionProvider.taskParameters = mapOf("classesToMockAlways" to value.joinToString(",", "[", "]")) + } } // internal diff --git a/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapperTest.kt b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapperTest.kt index 5a98a7354c..e315c3021c 100644 --- a/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapperTest.kt +++ b/utbot-gradle/src/test/kotlin/org/utbot/gradle/plugin/wrappers/SourceSetWrapperTest.kt @@ -30,6 +30,7 @@ class SourceSetWrapperTest { Mockito.`when`(sarifPropertiesMock.codegenLanguage).thenReturn(CodegenLanguage.JAVA) Mockito.`when`(sarifPropertiesMock.generatedTestsRelativeRoot).thenReturn("test") Mockito.`when`(sarifPropertiesMock.sarifReportsRelativeRoot).thenReturn("sarif") + Mockito.`when`(sarifPropertiesMock.testPrivateMethods).thenReturn(true) val gradleProject = GradleProjectWrapper(project, sarifPropertiesMock) val sourceSetWrapper = SourceSetWrapper(project.mainSourceSet, gradleProject) @@ -47,6 +48,7 @@ class SourceSetWrapperTest { Mockito.`when`(sarifPropertiesMock.codegenLanguage).thenReturn(CodegenLanguage.JAVA) Mockito.`when`(sarifPropertiesMock.generatedTestsRelativeRoot).thenReturn("test") Mockito.`when`(sarifPropertiesMock.sarifReportsRelativeRoot).thenReturn("sarif") + Mockito.`when`(sarifPropertiesMock.testPrivateMethods).thenReturn(true) val gradleProject = GradleProjectWrapper(project, sarifPropertiesMock) val sourceSetWrapper = SourceSetWrapper(project.mainSourceSet, gradleProject) @@ -66,6 +68,7 @@ class SourceSetWrapperTest { Mockito.`when`(sarifPropertiesMock.generatedTestsRelativeRoot).thenReturn("test") Mockito.`when`(sarifPropertiesMock.sarifReportsRelativeRoot).thenReturn("sarif") + Mockito.`when`(sarifPropertiesMock.testPrivateMethods).thenReturn(true) val gradleProject = GradleProjectWrapper(project, sarifPropertiesMock) val sourceSetWrapper = SourceSetWrapper(project.mainSourceSet, gradleProject) @@ -79,6 +82,7 @@ class SourceSetWrapperTest { Mockito.`when`(sarifPropertiesMock.generatedTestsRelativeRoot).thenReturn("test") Mockito.`when`(sarifPropertiesMock.sarifReportsRelativeRoot).thenReturn("sarif") + Mockito.`when`(sarifPropertiesMock.testPrivateMethods).thenReturn(true) val gradleProject = GradleProjectWrapper(project, sarifPropertiesMock) val sourceSetWrapper = SourceSetWrapper(project.mainSourceSet, gradleProject) diff --git a/utbot-instrumentation-tests/build.gradle b/utbot-instrumentation-tests/build.gradle index b26f3d9c72..99cc4d567e 100644 --- a/utbot-instrumentation-tests/build.gradle +++ b/utbot-instrumentation-tests/build.gradle @@ -1,21 +1,38 @@ -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" +compileKotlin { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } +} + +compileTestKotlin { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } +} +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} configurations { fetchInstrumentationJar } dependencies { - fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration:'instrumentationArchive') + fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration: 'instrumentationArchive') + implementation project(':utbot-framework-api') - testImplementation project(':utbot-instrumentation') + testImplementation configurations.fetchInstrumentationJar testImplementation project(':utbot-sample') - testImplementation group: 'org.jacoco', name: 'org.jacoco.report', version: jacoco_version + testImplementation group: 'org.jacoco', name: 'org.jacoco.report', version: jacocoVersion + implementation group: 'com.jetbrains.rd', name: 'rd-framework', version: rdVersion + implementation group: 'com.jetbrains.rd', name: 'rd-core', version: rdVersion } processResources { - // We will extract this jar in `ChildProcessRunner` class. + // We will extract this jar in `InstrumentedProcess` class. from(configurations.fetchInstrumentationJar) { - into "instrumentation-lib" + into "lib" } } \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/java/org/utbot/examples/samples/ExampleClass.java b/utbot-instrumentation-tests/src/test/java/org/utbot/examples/samples/ExampleClass.java new file mode 100644 index 0000000000..e0e1dcc1ed --- /dev/null +++ b/utbot-instrumentation-tests/src/test/java/org/utbot/examples/samples/ExampleClass.java @@ -0,0 +1,72 @@ +package org.utbot.examples.samples; + +@SuppressWarnings("All") +public class ExampleClass { + int x1 = 1; + + boolean[] arr = new boolean[5]; + + boolean[] arr2 = new boolean[10]; + + public void bar(int x) { + if (x > 1) { + x1++; + x1++; + } else { + x1--; + x1--; + } + } + + public void kek2(int x) { + arr[x] = true; + } + + public int foo(int x) { + x1 = x ^ 2; + + boolean was = false; + + for (int i = 0; i < x; i++) { + was = true; + int x2 = 0; + if (i > 5) { + was = false; + x2 = 1; + } + if (was && x2 == 0) { + was = true; + } + } + + // empty lines + return was ? x1 : x1 + 1; + } + + public void dependsOnField() { + x1 = x1 ^ 1; + if ((x1 & 1) == 1) { + x1 += 4; + } else { + x1 += 2; + } + } + + public int dependsOnFieldReturn() { + x1 = x1 ^ 1; + if ((x1 & 1) == 1) { + x1 += 4; + } else { + x1 += 2; + } + return x1; + } + + public void emptyMethod() { + } + + @SuppressWarnings("unused") + public int use() { + return arr2.length; + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/java/org/utbot/examples/samples/transformation/StringMethodsCalls.java b/utbot-instrumentation-tests/src/test/java/org/utbot/examples/samples/transformation/StringMethodsCalls.java new file mode 100644 index 0000000000..d886d02cfa --- /dev/null +++ b/utbot-instrumentation-tests/src/test/java/org/utbot/examples/samples/transformation/StringMethodsCalls.java @@ -0,0 +1,45 @@ +package org.utbot.examples.samples.transformation; + +public class StringMethodsCalls { + public static boolean equalsWithEmptyString(String strToCompare) { + if (strToCompare.equals("")) { + return true; + } + return false; + } + + public static boolean equalsWithNotEmptyString(String strToCompare) { + if (strToCompare.equals("abc")) { + return true; + } + return false; + } + + public static boolean startsWithWithEmptyString(String strToCompare) { + if (strToCompare.startsWith("")) { + return true; + } + return false; + } + + public static boolean startsWithWithNotEmptyString(String strToCompare) { + if (strToCompare.startsWith("abc")) { + return true; + } + return false; + } + + public static boolean endsWithWithEmptyString(String strToCompare) { + if (strToCompare.endsWith("")) { + return true; + } + return false; + } + + public static boolean endsWithWithNotEmptyString(String strToCompare) { + if (strToCompare.endsWith("abc")) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/java/org/utbot/test/util/UtPair.java b/utbot-instrumentation-tests/src/test/java/org/utbot/test/util/UtPair.java new file mode 100644 index 0000000000..08a71a31e9 --- /dev/null +++ b/utbot-instrumentation-tests/src/test/java/org/utbot/test/util/UtPair.java @@ -0,0 +1,43 @@ +package org.utbot.test.util; + +import java.io.Serializable; + +@SuppressWarnings("All") +public class UtPair implements Serializable { + + private K key; + + public K getKey() { return key; } + + private V value; + + public V getValue() { return value; } + + public UtPair(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public String toString() { + return key + "=" + value; + } + + @Override + public int hashCode() { + return key.hashCode() * 13 + (value == null ? 0 : value.hashCode()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof UtPair) { + UtPair pair = (UtPair) o; + if (key != null ? !key.equals(pair.key) : pair.key != null) return false; + if (value != null ? !value.equals(pair.value) : pair.value != null) return false; + return true; + } + return false; + } +} + diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestBytecodeTransformation.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestBytecodeTransformation.kt new file mode 100644 index 0000000000..997a55c58b --- /dev/null +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestBytecodeTransformation.kt @@ -0,0 +1,130 @@ +package org.utbot.examples + +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.utbot.examples.samples.transformation.StringMethodsCalls +import org.utbot.instrumentation.execute +import org.utbot.instrumentation.instrumentation.transformation.BytecodeTransformation +import org.utbot.instrumentation.withInstrumentation + +class TestBytecodeTransformation { + lateinit var utContext: AutoCloseable + + @Test + fun testStringEqualsWithEmptyStringCall() { + withInstrumentation( + BytecodeTransformation.Factory, + StringMethodsCalls::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StringMethodsCalls::equalsWithEmptyString, arrayOf("")) + assertTrue(res1.getOrNull() as Boolean) + + val res2 = executor.execute(StringMethodsCalls::equalsWithEmptyString, arrayOf("abc")) + assertFalse(res2.getOrNull() as Boolean) + + val res3 = executor.execute(StringMethodsCalls::equalsWithEmptyString, arrayOf(null)) + assertTrue(res3.exceptionOrNull() is NullPointerException) + } + } + + @Test + fun testStringEqualsWithNotEmptyStringCall() { + withInstrumentation( + BytecodeTransformation.Factory, + StringMethodsCalls::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StringMethodsCalls::equalsWithNotEmptyString, arrayOf("")) + assertFalse(res1.getOrNull() as Boolean) + + val res2 = executor.execute(StringMethodsCalls::equalsWithNotEmptyString, arrayOf("abc")) + assertTrue(res2.getOrNull() as Boolean) + + val res3 = executor.execute(StringMethodsCalls::equalsWithNotEmptyString, arrayOf("abcd")) + assertFalse(res3.getOrNull() as Boolean) + + val res4 = executor.execute(StringMethodsCalls::equalsWithNotEmptyString, arrayOf(null)) + assertTrue(res4.exceptionOrNull() is NullPointerException) + } + } + + @Test + fun testStringStartsWithWithEmptyStringCall() { + withInstrumentation( + BytecodeTransformation.Factory, + StringMethodsCalls::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StringMethodsCalls::startsWithWithEmptyString, arrayOf("")) + assertTrue(res1.getOrNull() as Boolean) + + val res2 = executor.execute(StringMethodsCalls::startsWithWithEmptyString, arrayOf("abc")) + assertTrue(res2.getOrNull() as Boolean) + + val res3 = executor.execute(StringMethodsCalls::startsWithWithEmptyString, arrayOf(null)) + assertTrue(res3.exceptionOrNull() is NullPointerException) + } + } + + @Test + fun testStringStartsWithWithNotEmptyStringCall() { + withInstrumentation( + BytecodeTransformation.Factory, + StringMethodsCalls::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StringMethodsCalls::startsWithWithNotEmptyString, arrayOf("")) + assertFalse(res1.getOrNull() as Boolean) + + val res2 = executor.execute(StringMethodsCalls::startsWithWithNotEmptyString, arrayOf("abc")) + assertTrue(res2.getOrNull() as Boolean) + + val res3 = executor.execute(StringMethodsCalls::startsWithWithNotEmptyString, arrayOf("abcd")) + assertTrue(res3.getOrNull() as Boolean) + + val res4 = executor.execute(StringMethodsCalls::startsWithWithNotEmptyString, arrayOf("aabc")) + assertFalse(res4.getOrNull() as Boolean) + + val res5 = executor.execute(StringMethodsCalls::startsWithWithNotEmptyString, arrayOf(null)) + assertTrue(res5.exceptionOrNull() is NullPointerException) + } + } + + @Test + fun testStringEndsWithWithEmptyString() { + withInstrumentation( + BytecodeTransformation.Factory, + StringMethodsCalls::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StringMethodsCalls::endsWithWithEmptyString, arrayOf("")) + assertTrue(res1.getOrNull() as Boolean) + + val res2 = executor.execute(StringMethodsCalls::endsWithWithEmptyString, arrayOf("abc")) + assertTrue(res2.getOrNull() as Boolean) + + val res3 = executor.execute(StringMethodsCalls::endsWithWithEmptyString, arrayOf(null)) + assertTrue(res3.exceptionOrNull() is NullPointerException) + } + } + + @Test + fun testStringEndsWithWithNotEmptyString() { + withInstrumentation( + BytecodeTransformation.Factory, + StringMethodsCalls::class.java.protectionDomain.codeSource.location.path + ) { executor -> + val res1 = executor.execute(StringMethodsCalls::endsWithWithNotEmptyString, arrayOf("")) + assertFalse(res1.getOrNull() as Boolean) + + val res2 = executor.execute(StringMethodsCalls::endsWithWithNotEmptyString, arrayOf("abc")) + assertTrue(res2.getOrNull() as Boolean) + + val res3 = executor.execute(StringMethodsCalls::endsWithWithNotEmptyString, arrayOf("aabc")) + assertTrue(res3.getOrNull() as Boolean) + + val res4 = executor.execute(StringMethodsCalls::endsWithWithNotEmptyString, arrayOf("abcd")) + assertFalse(res4.getOrNull() as Boolean) + + val res5 = executor.execute(StringMethodsCalls::endsWithWithNotEmptyString, arrayOf(null)) + assertTrue(res5.exceptionOrNull() is NullPointerException) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestConstructors.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestConstructors.kt index 93801a9621..fea5acdb17 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestConstructors.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestConstructors.kt @@ -28,7 +28,7 @@ class TestConstructors { @Test fun testDefaultConstructor() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, CLASSPATH ).use { executor -> val constructors = ClassWithMultipleConstructors::class.constructors @@ -43,7 +43,7 @@ class TestConstructors { @Test fun testIntConstructors() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, CLASSPATH ).use { executor -> val constructors = ClassWithMultipleConstructors::class.constructors @@ -65,7 +65,7 @@ class TestConstructors { @Test fun testStringConstructors() { withInstrumentation( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, CLASSPATH ) { executor -> val constructors = ClassWithMultipleConstructors::class.constructors @@ -86,7 +86,7 @@ class TestConstructors { @Test fun testCoverageConstructor() { withInstrumentation( - CoverageInstrumentation, + CoverageInstrumentation.Factory, CLASSPATH ) { executor -> val constructors = ClassWithMultipleConstructors::class.constructors @@ -106,7 +106,7 @@ class TestConstructors { @Test fun testExecutionTraceConstructor() { withInstrumentation( - ExecutionTraceInstrumentation(), + ExecutionTraceInstrumentation.Factory, CLASSPATH ) { executor -> val constructors = ClassWithMultipleConstructors::class.constructors diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestCoverageInstrumentation.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestCoverageInstrumentation.kt index b99cacb5b4..565ca75f12 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestCoverageInstrumentation.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestCoverageInstrumentation.kt @@ -1,5 +1,6 @@ package org.utbot.examples +import com.jetbrains.rd.util.reactive.RdFault import org.utbot.examples.samples.ExampleClass import org.utbot.examples.statics.substitution.StaticSubstitution import org.utbot.examples.statics.substitution.StaticSubstitutionExamples @@ -9,7 +10,7 @@ import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.execute import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation import org.utbot.instrumentation.instrumentation.coverage.collectCoverage -import org.utbot.instrumentation.util.ChildProcessError +import org.utbot.instrumentation.util.InstrumentedProcessError import org.utbot.instrumentation.util.StaticEnvironment import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertInstanceOf @@ -23,7 +24,7 @@ class TestCoverageInstrumentation { @Test fun testCatchTargetException() { ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() @@ -32,7 +33,7 @@ class TestCoverageInstrumentation { val coverageInfo = it.collectCoverage(ExampleClass::class.java) assertEquals(5, coverageInfo.visitedInstrs.size) - assertEquals(50..55, coverageInfo.methodToInstrRange[ExampleClass::kek2.signature]) + assertEquals(43..48, coverageInfo.methodToInstrRange[ExampleClass::kek2.signature]) assertTrue(res.exceptionOrNull() is ArrayIndexOutOfBoundsException) } } @@ -40,7 +41,7 @@ class TestCoverageInstrumentation { @Test fun testIfBranches() { ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() @@ -48,25 +49,25 @@ class TestCoverageInstrumentation { it.execute(ExampleClass::bar, arrayOf(testObject, 2)) val coverageInfo1 = it.collectCoverage(ExampleClass::class.java) - assertEquals(21, coverageInfo1.visitedInstrs.size) - assertEquals(13..49, coverageInfo1.methodToInstrRange[ExampleClass::bar.signature]) + assertEquals(17, coverageInfo1.visitedInstrs.size) + assertEquals(14..42, coverageInfo1.methodToInstrRange[ExampleClass::bar.signature]) it.execute(ExampleClass::bar, arrayOf(testObject, 0)) val coverageInfo2 = it.collectCoverage(ExampleClass::class.java) - assertEquals(20, coverageInfo2.visitedInstrs.size) - assertEquals(13..49, coverageInfo2.methodToInstrRange[ExampleClass::bar.signature]) + assertEquals(16, coverageInfo2.visitedInstrs.size) + assertEquals(14..42, coverageInfo2.methodToInstrRange[ExampleClass::bar.signature]) } } @Test fun testWrongArgumentsException() { ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() - val exc = assertThrows { + val exc = assertThrows { it.execute( ExampleClass::bar, arrayOf(testObject, 1, 2, 3) @@ -74,9 +75,10 @@ class TestCoverageInstrumentation { } assertInstanceOf( - IllegalArgumentException::class.java, + RdFault::class.java, exc.cause!! ) + assertTrue((exc.cause as RdFault).reasonTypeFqn == "IllegalArgumentException") } } @@ -84,11 +86,11 @@ class TestCoverageInstrumentation { @Test fun testMultipleRunsInsideCoverage() { ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() - val exc = assertThrows { + val exc = assertThrows { it.execute( ExampleClass::bar, arrayOf(testObject, 1, 2, 3) @@ -96,21 +98,22 @@ class TestCoverageInstrumentation { } assertInstanceOf( - IllegalArgumentException::class.java, + RdFault::class.java, exc.cause!! ) + assertTrue((exc.cause as RdFault).reasonTypeFqn == "IllegalArgumentException") it.execute(ExampleClass::bar, arrayOf(testObject, 2)) val coverageInfo1 = it.collectCoverage(ExampleClass::class.java) - assertEquals(21, coverageInfo1.visitedInstrs.size) - assertEquals(13..49, coverageInfo1.methodToInstrRange[ExampleClass::bar.signature]) + assertEquals(17, coverageInfo1.visitedInstrs.size) + assertEquals(14..42, coverageInfo1.methodToInstrRange[ExampleClass::bar.signature]) it.execute(ExampleClass::bar, arrayOf(testObject, 0)) val coverageInfo2 = it.collectCoverage(ExampleClass::class.java) - assertEquals(20, coverageInfo2.visitedInstrs.size) - assertEquals(13..49, coverageInfo2.methodToInstrRange[ExampleClass::bar.signature]) + assertEquals(16, coverageInfo2.visitedInstrs.size) + assertEquals(14..42, coverageInfo2.methodToInstrRange[ExampleClass::bar.signature]) } } @@ -118,7 +121,7 @@ class TestCoverageInstrumentation { @Test fun testSameResult() { ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() @@ -127,20 +130,20 @@ class TestCoverageInstrumentation { val coverageInfo1 = it.collectCoverage(ExampleClass::class.java) assertEquals(19, coverageInfo1.visitedInstrs.size) - assertEquals(99..124, coverageInfo1.methodToInstrRange[ExampleClass::dependsOnField.signature]) + assertEquals(90..115, coverageInfo1.methodToInstrRange[ExampleClass::dependsOnField.signature]) it.execute(ExampleClass::dependsOnField, arrayOf(testObject)) val coverageInfo2 = it.collectCoverage(ExampleClass::class.java) assertEquals(19, coverageInfo2.visitedInstrs.size) - assertEquals(99..124, coverageInfo2.methodToInstrRange[ExampleClass::dependsOnField.signature]) + assertEquals(90..115, coverageInfo2.methodToInstrRange[ExampleClass::dependsOnField.signature]) } } @Test fun testResult() { ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() @@ -149,15 +152,15 @@ class TestCoverageInstrumentation { val coverageInfo = it.collectCoverage(ExampleClass::class.java) assertEquals(1, res.getOrNull()) - assertEquals(35, coverageInfo.visitedInstrs.size) - assertEquals(56..98, coverageInfo.methodToInstrRange[ExampleClass::foo.signature]) + assertEquals(33, coverageInfo.visitedInstrs.size) + assertEquals(49..89, coverageInfo.methodToInstrRange[ExampleClass::foo.signature]) } } @Test fun testEmptyMethod() { ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() @@ -173,7 +176,7 @@ class TestCoverageInstrumentation { @Test fun testTernaryOperator() { ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, StaticSubstitutionExamples::class.java.protectionDomain.codeSource.location.path ).use { val testObject = StaticSubstitutionExamples() diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestGetSourceFileName.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestGetSourceFileName.kt index bb256f024a..866d243bc5 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestGetSourceFileName.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestGetSourceFileName.kt @@ -27,19 +27,19 @@ class TestGetSourceFileName { @Test fun testThis() { - assertEquals("TestGetSourceFileName.kt", Instrumenter.computeSourceFileName(TestGetSourceFileName::class.java)) + assertEquals("TestGetSourceFileName.kt", Instrumenter.adapter.computeSourceFileName(TestGetSourceFileName::class.java)) } @Test - fun testKotlinExample() { - assertEquals("ExampleClass.kt", Instrumenter.computeSourceFileName(ExampleClass::class.java)) + fun testJavaExample1() { + assertEquals("ExampleClass.java", Instrumenter.adapter.computeSourceFileName(ExampleClass::class.java)) } @Test - fun testJavaExample() { + fun testJavaExample2() { assertEquals( "ClassWithInnerClasses.java", - Instrumenter.computeSourceFileName(ClassWithInnerClasses::class.java) + Instrumenter.adapter.computeSourceFileName(ClassWithInnerClasses::class.java) ) } @@ -47,7 +47,7 @@ class TestGetSourceFileName { fun testInnerClass() { assertEquals( "ClassWithInnerClasses.java", - Instrumenter.computeSourceFileName(ClassWithInnerClasses.InnerStaticClass::class.java) + Instrumenter.adapter.computeSourceFileName(ClassWithInnerClasses.InnerStaticClass::class.java) ) } @@ -55,12 +55,12 @@ class TestGetSourceFileName { fun testSameNameButDifferentPackages() { assertEquals( true, - Instrumenter.computeSourceFileByClass(org.utbot.examples.samples.root.MyClass::class.java)?.toPath() + Instrumenter.adapter.computeSourceFileByClass(org.utbot.examples.samples.root.MyClass::class.java)?.toPath() ?.endsWith(Paths.get("root", "MyClass.java")) ) assertEquals( true, - Instrumenter.computeSourceFileByClass(org.utbot.examples.samples.root.child.MyClass::class.java) + Instrumenter.adapter.computeSourceFileByClass(org.utbot.examples.samples.root.child.MyClass::class.java) ?.toPath()?.endsWith(Paths.get("root", "child", "MyClass.java")) ) } @@ -69,7 +69,7 @@ class TestGetSourceFileName { fun testEmptyPackage() { assertEquals( true, - Instrumenter.computeSourceFileByClass(ClassWithoutPackage::class.java)?.toPath() + Instrumenter.adapter.computeSourceFileByClass(ClassWithoutPackage::class.java)?.toPath() ?.endsWith("java/ClassWithoutPackage.java") ) } @@ -78,7 +78,7 @@ class TestGetSourceFileName { fun testPackageDoesNotMatchDir() { assertEquals( true, - Instrumenter.computeSourceFileByClass(ClassWithWrongPackage::class.java)?.toPath() + Instrumenter.adapter.computeSourceFileByClass(ClassWithWrongPackage::class.java)?.toPath() ?.endsWith("org/utbot/examples/samples/ClassWithWrongPackage.kt") ) } @@ -87,7 +87,7 @@ class TestGetSourceFileName { fun testSearchDir() { assertEquals( null, - Instrumenter.computeSourceFileByClass( + Instrumenter.adapter.computeSourceFileByClass( org.utbot.examples.samples.root.MyClass::class.java, Paths.get("src/test/kotlin") )?.name @@ -95,7 +95,7 @@ class TestGetSourceFileName { assertEquals( "MyClass.java", - Instrumenter.computeSourceFileByClass( + Instrumenter.adapter.computeSourceFileByClass( org.utbot.examples.samples.root.MyClass::class.java, Paths.get("src/test") )?.name diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeInstrumentation.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeInstrumentation.kt index 9b0b06669e..a585b2f45f 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeInstrumentation.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeInstrumentation.kt @@ -1,12 +1,13 @@ package org.utbot.examples +import com.jetbrains.rd.util.reactive.RdFault import org.utbot.examples.samples.ClassWithSameMethodNames import org.utbot.examples.samples.ExampleClass import org.utbot.examples.samples.staticenvironment.StaticExampleClass import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.execute import org.utbot.instrumentation.instrumentation.InvokeInstrumentation -import org.utbot.instrumentation.util.ChildProcessError +import org.utbot.instrumentation.util.InstrumentedProcessError import kotlin.reflect.full.declaredMembers import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertInstanceOf @@ -20,7 +21,7 @@ class TestInvokeInstrumentation { @Test fun testCatchTargetException() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { @@ -35,27 +36,28 @@ class TestInvokeInstrumentation { @Test fun testWrongArgumentsException() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() - val exc = assertThrows { + val exc = assertThrows { it.execute( ExampleClass::bar, arrayOf(testObject, 1, 2, 3) ) } assertInstanceOf( - IllegalArgumentException::class.java, + RdFault::class.java, exc.cause!! ) + assertTrue((exc.cause as RdFault).reasonTypeFqn == "IllegalArgumentException") } } @Test fun testSameResult() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() @@ -71,7 +73,7 @@ class TestInvokeInstrumentation { @Test fun testEmptyMethod() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() @@ -85,7 +87,7 @@ class TestInvokeInstrumentation { @Test fun testStaticMethodCall() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, StaticExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val res1 = it.execute(StaticExampleClass::inc, arrayOf()) @@ -103,7 +105,7 @@ class TestInvokeInstrumentation { @Test fun testNullableMethod() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, StaticExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val res1 = it.execute( @@ -134,7 +136,7 @@ class TestInvokeInstrumentation { @Test fun testDifferentSignaturesButSameMethodNames() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, ClassWithSameMethodNames::class.java.protectionDomain.codeSource.location.path ).use { val clazz = ClassWithSameMethodNames::class diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeWithStaticsInstrumentation.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeWithStaticsInstrumentation.kt index b1865499e0..c06143e38f 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeWithStaticsInstrumentation.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestInvokeWithStaticsInstrumentation.kt @@ -35,7 +35,7 @@ class TestInvokeWithStaticsInstrumentation { @Test fun testIfBranches() { ConcreteExecutor( - InvokeWithStaticsInstrumentation(), + InvokeWithStaticsInstrumentation.Factory, CLASSPATH ).use { val res = it.execute(StaticExampleClass::inc, arrayOf(), null) @@ -52,7 +52,7 @@ class TestInvokeWithStaticsInstrumentation { @Test fun testHiddenClass1() { ConcreteExecutor( - InvokeWithStaticsInstrumentation(), + InvokeWithStaticsInstrumentation.Factory, CLASSPATH ).use { val res = it.execute(TestedClass::slomayInts, arrayOf(), null) @@ -71,7 +71,7 @@ class TestInvokeWithStaticsInstrumentation { @Test fun testHiddenClassRepeatCall() { ConcreteExecutor( - InvokeWithStaticsInstrumentation(), + InvokeWithStaticsInstrumentation.Factory, CLASSPATH ).use { val se = StaticEnvironment( @@ -89,7 +89,7 @@ class TestInvokeWithStaticsInstrumentation { @Test fun testReferenceEquality() { ConcreteExecutor( - InvokeWithStaticsInstrumentation(), + InvokeWithStaticsInstrumentation.Factory, CLASSPATH ).use { diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestIsolated.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestIsolated.kt index c86695d4e0..96b3047f6e 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestIsolated.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestIsolated.kt @@ -1,10 +1,11 @@ package org.utbot.examples +import com.jetbrains.rd.util.reactive.RdFault import org.utbot.examples.samples.ExampleClass import org.utbot.examples.samples.staticenvironment.StaticExampleClass import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.instrumentation.InvokeInstrumentation -import org.utbot.instrumentation.util.ChildProcessError +import org.utbot.instrumentation.util.InstrumentedProcessError import org.utbot.instrumentation.util.Isolated import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertInstanceOf @@ -20,7 +21,7 @@ class TestIsolated { fun testCatchTargetException() { val javaClass = ExampleClass::class.java ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, javaClass.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() @@ -36,7 +37,7 @@ class TestIsolated { @Test fun testWrongArgumentsException() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() @@ -47,21 +48,22 @@ class TestIsolated { } - val exc = assertThrows { + val exc = assertThrows { isolatedFunction(testObject, 1, 2, 3) } assertInstanceOf( - IllegalArgumentException::class.java, + RdFault::class.java, exc.cause!! ) + assertTrue((exc.cause as RdFault).reasonTypeFqn == "IllegalArgumentException") } } @Test fun testSameResult() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() @@ -79,7 +81,7 @@ class TestIsolated { @Test fun testEmptyMethod() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, ExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val testObject = ExampleClass() @@ -95,7 +97,7 @@ class TestIsolated { @Test fun testStaticMethodCall() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, StaticExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val isolatedFunctionInc = Isolated(StaticExampleClass::inc, it) @@ -114,7 +116,7 @@ class TestIsolated { @Test fun testNullableMethod() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, StaticExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val isolatedFunction = Isolated(StaticExampleClass::canBeNull, it) diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestStaticMethods.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestStaticMethods.kt index 6ad3bca179..0a75292636 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestStaticMethods.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestStaticMethods.kt @@ -17,7 +17,7 @@ class TestStaticMethods { @Test fun testStaticMethodCall() { ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, StaticExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val res1 = it.execute(StaticExampleClass::inc, arrayOf()) @@ -44,7 +44,7 @@ class TestStaticMethods { @Test fun testNullableMethod() { ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, StaticExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val res1 = it.execute( @@ -75,7 +75,7 @@ class TestStaticMethods { @Test fun testNullableMethodWithoutAnnotations() { ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, StaticExampleClass::class.java.protectionDomain.codeSource.location.path ).use { val res1 = it.execute( diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestWithInstrumentation.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestWithInstrumentation.kt index f49c010b81..ae00e0f7a8 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestWithInstrumentation.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/TestWithInstrumentation.kt @@ -20,7 +20,7 @@ class TestWithInstrumentation { @Test fun testStaticMethodCall() { withInstrumentation( - CoverageInstrumentation, + CoverageInstrumentation.Factory, StaticExampleClass::class.java.protectionDomain.codeSource.location.path ) { executor -> val res1 = executor.execute(StaticExampleClass::inc, arrayOf()) @@ -47,7 +47,7 @@ class TestWithInstrumentation { @Test fun testDifferentSignaturesButSameMethodNames() { withInstrumentation( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, ClassWithSameMethodNames::class.java.protectionDomain.codeSource.location.path ) { executor -> val clazz = ClassWithSameMethodNames::class @@ -70,7 +70,7 @@ class TestWithInstrumentation { @Test fun testInnerClasses() { withInstrumentation( - CoverageInstrumentation, + CoverageInstrumentation.Factory, ClassWithInnerClasses::class.java.protectionDomain.codeSource.location.path ) { executor -> val innerClazz = ClassWithInnerClasses.InnerClass::class.java diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Benchmark.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Benchmark.kt index 41ec9da126..d4e9ba2028 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Benchmark.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Benchmark.kt @@ -12,7 +12,7 @@ import org.junit.jupiter.api.Assertions.assertEquals fun getBasicCoverageTime(count: Int): Double { var time: Long ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, Repeater::class.java.protectionDomain.codeSource.location.path ).use { executor -> val dc0 = Repeater(", ") @@ -51,7 +51,7 @@ fun getNativeCallTime(count: Int): Double { fun getJustResultTime(count: Int): Double { var time: Long ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, Repeater::class.java.protectionDomain.codeSource.location.path ).use { val dc0 = Repeater(", ") diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/BenchmarkFibonacci.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/BenchmarkFibonacci.kt index 4c370553de..07d788e4ce 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/BenchmarkFibonacci.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/BenchmarkFibonacci.kt @@ -13,7 +13,7 @@ import kotlin.system.measureNanoTime fun getBasicCoverageTime_fib(count: Int): Double { var time: Long ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, Fibonacci::class.java.protectionDomain.codeSource.location.path ).use { val fib = Isolated(Fibonacci::calc, it) @@ -47,7 +47,7 @@ fun getNativeCallTime_fib(count: Int): Double { fun getJustResultTime_fib(count: Int): Double { var time: Long ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, Fibonacci::class.java.protectionDomain.codeSource.location.path ).use { val fib = Isolated(Fibonacci::calc, it) diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Classes.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Classes.kt index a4e0656e12..4449491635 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Classes.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/Classes.kt @@ -1,6 +1,6 @@ package org.utbot.examples.benchmark -import javafx.util.Pair +import org.utbot.test.util.UtPair class Repeater(var sep: String) { /* public DifferentClass0() { @@ -22,7 +22,7 @@ class Repeater(var sep: String) { class Unzipper { var dc0 = Repeater("-") - fun unzip(chars: Array>): String { + fun unzip(chars: Array>): String { val sb = java.lang.StringBuilder() for (pr in chars) { sb.append(dc0.repeat(pr.value.toString(), pr.key!!)) diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/TestBenchmarkClasses.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/TestBenchmarkClasses.kt index f1692a0334..f189907c5d 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/TestBenchmarkClasses.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/benchmark/TestBenchmarkClasses.kt @@ -6,17 +6,19 @@ import org.utbot.instrumentation.execute import org.utbot.instrumentation.instrumentation.InvokeInstrumentation import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation import java.math.BigInteger -import javafx.util.Pair import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test +import org.utbot.test.util.UtPair class TestBenchmarkClasses { lateinit var utContext: AutoCloseable @Test + @Disabled("Ask Sergey to check") fun testRepeater() { ConcreteExecutor( - CoverageInstrumentation, + CoverageInstrumentation.Factory, Repeater::class.java.protectionDomain.codeSource.location.path ).use { val dc0 = Repeater(", ") @@ -25,7 +27,7 @@ class TestBenchmarkClasses { val dc1 = Unzipper() - val arr = arrayOf(Pair(1, 'h'), Pair(1, 'e'), Pair(2, 'l'), Pair(1, 'o')) + val arr = arrayOf(UtPair(1, 'h'), UtPair(1, 'e'), UtPair(2, 'l'), UtPair(1, 'o')) val res1 = it.execute(Unzipper::unzip, arrayOf(dc1, arr)) assertEquals("h-e-ll-o-", res1.getOrNull()) } @@ -34,7 +36,7 @@ class TestBenchmarkClasses { @Test fun testFibonacci() { ConcreteExecutor( - InvokeInstrumentation(), + InvokeInstrumentation.Factory, Fibonacci::class.java.protectionDomain.codeSource.location.path ).use { val res = diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestMixedExTrace.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestMixedExTrace.kt index 560a314633..c5ecefeded 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestMixedExTrace.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestMixedExTrace.kt @@ -25,7 +25,7 @@ class TestMixedExTrace { @Test fun testMixedDoesNotThrow() { ConcreteExecutor( - ExecutionTraceInstrumentation(), + ExecutionTraceInstrumentation.Factory, CLASSPATH ).use { val A = Isolated(ClassMixedWithNotInstrumented_Instr::a, it) diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestSimpleExTrace.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestSimpleExTrace.kt index e7dab32d5f..f8b7224de0 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestSimpleExTrace.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestSimpleExTrace.kt @@ -34,7 +34,7 @@ class TestSimpleExTrace { @Test fun testClassSimple() { ConcreteExecutor( - ExecutionTraceInstrumentation(), + ExecutionTraceInstrumentation.Factory, CLASSPATH ).use { val alwaysThrows = Isolated(ClassSimple::alwaysThrows, it) @@ -89,7 +89,7 @@ class TestSimpleExTrace { @Test fun testClasSimpleCatch() { ConcreteExecutor( - ExecutionTraceInstrumentation(), + ExecutionTraceInstrumentation.Factory, CLASSPATH ).use { val A = Isolated(ClassSimpleCatch::A, it) @@ -159,7 +159,7 @@ class TestSimpleExTrace { @Test fun testClassSimpleRecursive() { ConcreteExecutor( - ExecutionTraceInstrumentation(), + ExecutionTraceInstrumentation.Factory, CLASSPATH ).use { val A = Isolated(ClassSimpleRecursive::A, it) @@ -228,7 +228,7 @@ class TestSimpleExTrace { @Test fun testClassBinaryRecursionWithTrickyThrow() { ConcreteExecutor( - ExecutionTraceInstrumentation(), + ExecutionTraceInstrumentation.Factory, CLASSPATH ).use { val A = Isolated(ClassBinaryRecursionWithTrickyThrow::A, it) @@ -347,7 +347,7 @@ class TestSimpleExTrace { @Test fun testClassBinaryRecursionWithThrow() { ConcreteExecutor( - ExecutionTraceInstrumentation(), + ExecutionTraceInstrumentation.Factory, CLASSPATH ).use { val A = Isolated(ClassBinaryRecursionWithThrow::A, it) @@ -436,7 +436,7 @@ class TestSimpleExTrace { @Test fun testClassSimpleNPE() { ConcreteExecutor( - ExecutionTraceInstrumentation(), + ExecutionTraceInstrumentation.Factory, CLASSPATH ).use { val A = Isolated(ClassSimpleNPE::A, it) diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestStaticsUsage.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestStaticsUsage.kt index a7ec3e5780..22ade07a8c 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestStaticsUsage.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/et/TestStaticsUsage.kt @@ -3,7 +3,7 @@ package org.utbot.examples.et import org.utbot.examples.objects.ObjectWithStaticFieldsClass import org.utbot.examples.objects.ObjectWithStaticFieldsExample import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.field +import org.utbot.framework.plugin.api.util.jField import org.utbot.instrumentation.execute import org.utbot.instrumentation.instrumentation.et.ExecutionTraceInstrumentation import org.utbot.instrumentation.withInstrumentation @@ -31,7 +31,7 @@ class StaticsUsageDetectionTest { @Test fun testStaticsUsageOneUsage() { withInstrumentation( - ExecutionTraceInstrumentation(), + ExecutionTraceInstrumentation.Factory, ObjectWithStaticFieldsExample::class.java.protectionDomain.codeSource.location.path ) { val instance = ObjectWithStaticFieldsExample() @@ -39,14 +39,14 @@ class StaticsUsageDetectionTest { classInstance.x = 200 classInstance.y = 200 val result = it.execute(ObjectWithStaticFieldsExample::setStaticField, arrayOf(instance, classInstance)) - assertEquals(ObjectWithStaticFieldsClass::staticValue.javaField, result.usedStatics.single().field) + assertEquals(ObjectWithStaticFieldsClass::staticValue.javaField, result.usedStatics.single().jField) } } @Test fun testStaticsUsageZeroUsages() { withInstrumentation( - ExecutionTraceInstrumentation(), + ExecutionTraceInstrumentation.Factory, ObjectWithStaticFieldsExample::class.java.protectionDomain.codeSource.location.path ) { val instance = ObjectWithStaticFieldsExample() diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/jacoco/TestSameAsJaCoCo.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/jacoco/TestSameAsJaCoCo.kt index de8051a775..a289e1d5fa 100644 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/jacoco/TestSameAsJaCoCo.kt +++ b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/jacoco/TestSameAsJaCoCo.kt @@ -123,7 +123,7 @@ private fun methodCoverageWithJaCoCo(kClass: KClass<*>, method: KCallable<*>, ex private fun methodCoverage(kClass: KClass<*>, method: KCallable<*>, executions: List): Pair { return withInstrumentation( - CoverageInstrumentation, + CoverageInstrumentation.Factory, kClass.java.protectionDomain.codeSource.location.path ) { executor -> for (execution in executions) { diff --git a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/samples/ExampleClass.kt b/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/samples/ExampleClass.kt deleted file mode 100644 index 6f84b7c62c..0000000000 --- a/utbot-instrumentation-tests/src/test/kotlin/org/utbot/examples/samples/ExampleClass.kt +++ /dev/null @@ -1,67 +0,0 @@ -package org.utbot.examples.samples - -class ExampleClass { - var x1 = 1 - val arr = BooleanArray(5) - val arr2 = BooleanArray(10) - - fun bar(x: Int) { - if (x > 1) { - x1++ - x1++ - } else { - x1-- - x1-- - } - } - - fun kek2(x: Int) { - arr[x] = true - } - - fun foo(x: Int): Int { - x1 = x xor 2 - - var was = false - - for (i in 0 until x) { - was = true - var x2 = 0 - if (i > 5) { - was = false - x2 = 1 - } - if (was && x2 == 0) { - was = true - } - } - - // empty lines - return if (was) x1 else x1 + 1 - } - - fun dependsOnField() { - x1 = x1 xor 1 - if (x1 and 1 == 1) { - x1 += 4 - } else { - x1 += 2 - } - } - - fun dependsOnFieldReturn(): Int { - x1 = x1 xor 1 - if (x1 and 1 == 1) { - x1 += 4 - } else { - x1 += 2 - } - return x1 - } - - fun emptyMethod() { - } - - @Suppress("unused") - fun use() = arr2.size -} \ No newline at end of file diff --git a/utbot-instrumentation/build.gradle b/utbot-instrumentation/build.gradle deleted file mode 100644 index a1ee9dfc96..0000000000 --- a/utbot-instrumentation/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" - -dependencies { - implementation project(':utbot-framework-api') - - implementation group: 'org.ow2.asm', name: 'asm', version: asm_version - implementation group: 'org.ow2.asm', name: 'asm-commons', version: asm_version - implementation group: 'com.esotericsoftware', name: 'kryo', version: kryo_version - // this is necessary for serialization of some collections - implementation group: 'de.javakaffee', name: 'kryo-serializers', version: kryo_serializers_version - implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version - - // TODO: this is necessary for inline classes mocking in UtExecutionInstrumentation - implementation group: 'org.mockito', name: 'mockito-core', version: '4.2.0' - implementation group: 'org.mockito', name: 'mockito-inline', version: '4.2.0' -} - -jar { - manifest { - attributes ( - 'Main-Class': 'org.utbot.instrumentation.process.ChildProcessKt', - 'Premain-Class': 'org.utbot.instrumentation.agent.Agent', - ) - } - from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } } -} - -configurations { - instrumentationArchive -} - -artifacts { - instrumentationArchive jar -} diff --git a/utbot-instrumentation/build.gradle.kts b/utbot-instrumentation/build.gradle.kts new file mode 100644 index 0000000000..ca8ea1f469 --- /dev/null +++ b/utbot-instrumentation/build.gradle.kts @@ -0,0 +1,99 @@ +import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer + +val asmVersion: String by rootProject +val kryoVersion: String by rootProject +val kryoSerializersVersion: String by rootProject +val kotlinLoggingVersion: String by rootProject +val rdVersion: String by rootProject +val mockitoVersion: String by rootProject +val mockitoInlineVersion: String by rootProject + +plugins { + id("com.github.johnrengelman.shadow") version "7.1.2" + id("java") + application +} + +tasks.compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} + +tasks.compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +application { + mainClass.set("org.utbot.instrumentation.process.InstrumentedProcessMainKt") +} + +val fetchSpringCommonsJar: Configuration by configurations.creating { + isCanBeResolved = true + isCanBeConsumed = false +} + +dependencies { + implementation(project(":utbot-framework-api")) + implementation(project(":utbot-rd")) + + implementation("org.ow2.asm:asm:$asmVersion") + implementation("org.ow2.asm:asm-commons:$asmVersion") + implementation("io.github.microutils:kotlin-logging:$kotlinLoggingVersion") + + implementation("com.jetbrains.rd:rd-framework:$rdVersion") + implementation("com.jetbrains.rd:rd-core:$rdVersion") + implementation("net.java.dev.jna:jna-platform:5.5.0") + + // TODO: this is necessary for inline classes mocking in UtExecutionInstrumentation + implementation("org.mockito:mockito-core:$mockitoVersion") + implementation("org.mockito:mockito-inline:$mockitoInlineVersion") + + implementation(project(":utbot-spring-commons-api")) + fetchSpringCommonsJar(project(":utbot-spring-commons", configuration = "springCommonsJar")) +} + +/** + * Shadow plugin unpacks the nested `utbot-spring-commons-shadow.jar`. + * But we need it to be packed. Workaround: double-nest the jar. + */ +val shadowJarUnpackWorkaround by tasks.register("shadowBugWorkaround") { + destinationDirectory.set(layout.buildDirectory.dir("build/shadow-bug-workaround")) + from(fetchSpringCommonsJar) { + into("lib") + } +} + +tasks.shadowJar { + dependsOn(shadowJarUnpackWorkaround) + + from(shadowJarUnpackWorkaround) { + into("lib") + } + + manifest { + attributes( + "Main-Class" to "org.utbot.instrumentation.process.InstrumentedProcessMainKt", + "Premain-Class" to "org.utbot.instrumentation.agent.Agent", + ) + } + + transform(Log4j2PluginsCacheFileTransformer::class.java) + archiveFileName.set("utbot-instrumentation-shadow.jar") +} + +val instrumentationArchive: Configuration by configurations.creating { + isCanBeResolved = false + isCanBeConsumed = true +} + +artifacts { + add(instrumentationArchive.name, tasks.shadowJar) +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt index 3fb26e322a..5f80edec97 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt @@ -1,47 +1,44 @@ package org.utbot.instrumentation -import org.utbot.common.bracket -import org.utbot.common.catch -import org.utbot.common.currentThreadInfo -import org.utbot.common.debug -import org.utbot.common.pid -import org.utbot.common.trace -import org.utbot.framework.plugin.api.ConcreteExecutionFailureException -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.signature -import org.utbot.instrumentation.instrumentation.Instrumentation -import org.utbot.instrumentation.process.ChildProcessRunner -import org.utbot.instrumentation.util.ChildProcessError -import org.utbot.instrumentation.util.KryoHelper -import org.utbot.instrumentation.util.Protocol -import org.utbot.instrumentation.util.UnexpectedCommand +import com.jetbrains.rd.util.lifetime.LifetimeDefinition +import com.jetbrains.rd.util.lifetime.isAlive +import com.jetbrains.rd.util.lifetime.throwIfNotAlive import java.io.Closeable -import java.io.InputStream -import java.util.concurrent.TimeUnit -import kotlin.concurrent.thread +import java.util.concurrent.atomic.AtomicLong import kotlin.reflect.KCallable import kotlin.reflect.KFunction import kotlin.reflect.KProperty import kotlin.reflect.jvm.javaConstructor import kotlin.reflect.jvm.javaGetter import kotlin.reflect.jvm.javaMethod -import kotlin.streams.toList import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import mu.KotlinLogging +import org.utbot.framework.plugin.api.InstrumentedProcessDeathException +import org.utbot.common.logException +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.ConcreteContextLoadingResult +import org.utbot.framework.plugin.api.SpringRepositoryId +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.signature +import org.utbot.instrumentation.instrumentation.Instrumentation +import org.utbot.instrumentation.process.generated.ComputeStaticFieldParams +import org.utbot.instrumentation.process.generated.GetSpringRepositoriesParams +import org.utbot.instrumentation.process.generated.InvokeMethodCommandParams +import org.utbot.instrumentation.rd.InstrumentedProcess +import org.utbot.instrumentation.util.InstrumentedProcessError +import org.utbot.rd.generated.synchronizationModel +import org.utbot.rd.loggers.overrideDefaultRdLoggerFactoryWithKLogger private val logger = KotlinLogging.logger {} /** - * Creates [ConcreteExecutor], which delegates `execute` calls to the child process, and applies the given [block] to it. + * Creates [ConcreteExecutor], which delegates `execute` calls to the instrumented process, and applies the given [block] to it. * - * The child process will search for the classes in [pathsToUserClasses] and will use [instrumentation] for instrumenting. + * The instrumented process will search for the classes in [pathsToUserClasses] and will use [instrumentation] for instrumenting. * * Specific instrumentation can add functionality to [ConcreteExecutor] via Kotlin extension functions. * @@ -51,11 +48,10 @@ private val logger = KotlinLogging.logger {} * @see [org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation]. */ inline fun > withInstrumentation( - instrumentation: T, + instrumentationFactory: Instrumentation.Factory, pathsToUserClasses: String, - pathsToDependencyClasses: String = ConcreteExecutor.defaultPathsToDependencyClasses, block: (ConcreteExecutor) -> TBlockResult -) = ConcreteExecutor(instrumentation, pathsToUserClasses, pathsToDependencyClasses).use { +) = ConcreteExecutor(instrumentationFactory, pathsToUserClasses).use { block(it) } @@ -63,21 +59,20 @@ class ConcreteExecutorPool(val maxCount: Int = Settings.defaultConcreteExecutorP private val executors = ArrayDeque>(maxCount) /** - * Tries to find the concrete executor for the supplied [instrumentation] and [pathsToDependencyClasses]. If it + * Tries to find the concrete executor for the supplied [instrumentationFactory] and [pathsToDependencyClasses]. If it * doesn't exist, then creates a new one. */ fun > get( - instrumentation: TInstrumentation, + instrumentationFactory: Instrumentation.Factory, pathsToUserClasses: String, - pathsToDependencyClasses: String ): ConcreteExecutor { executors.removeIf { !it.alive } @Suppress("UNCHECKED_CAST") return executors.firstOrNull { - it.pathsToUserClasses == pathsToUserClasses && it.instrumentation == instrumentation + it.pathsToUserClasses == pathsToUserClasses && it.instrumentationFactory == instrumentationFactory } as? ConcreteExecutor - ?: ConcreteExecutor.createNew(instrumentation, pathsToUserClasses, pathsToDependencyClasses).apply { + ?: ConcreteExecutor.createNew(instrumentationFactory, pathsToUserClasses).apply { executors.addFirst(this) if (executors.size > maxCount) { executors.removeLast().close() @@ -89,312 +84,232 @@ class ConcreteExecutorPool(val maxCount: Int = Settings.defaultConcreteExecutorP executors.forEach { it.close() } executors.clear() } + + fun forceTerminateProcesses() { + executors.forEach { + it.forceTerminateProcess() + } + executors.clear() + } + } /** - * Concrete executor class. Takes [pathsToUserClasses] where the child process will search for the classes. Paths should + * Concrete executor class. Takes [pathsToUserClasses] where the instrumented process will search for the classes. Paths should * be separated with [java.io.File.pathSeparatorChar]. * * If [instrumentation] depends on other classes, they should be passed in [pathsToDependencyClasses]. * - * Also takes [instrumentation] object which will be used in the child process for the instrumentation. + * Also takes [instrumentationFactory] object which will be used in the instrumented process to create an [Instrumentation]. * - * @param TIResult the return type of [Instrumentation.invoke] function for the given [instrumentation]. + * @param TIResult the return type of [Instrumentation.invoke] function for the given [Instrumentation]. */ -class ConcreteExecutor> private constructor( - internal val instrumentation: TInstrumentation, - internal val pathsToUserClasses: String, - internal val pathsToDependencyClasses: String +class ConcreteExecutor> private constructor( + internal val instrumentationFactory: Instrumentation.Factory, + internal val pathsToUserClasses: String ) : Closeable, Executor { + private val ldef: LifetimeDefinition = LifetimeDefinition() companion object { - const val ERROR_CMD_ID = 0L - var nextCommandId = 1L - val defaultPool = ConcreteExecutorPool() - var defaultPathsToDependencyClasses = "" + private val sendTimestamp = AtomicLong() + private val receiveTimeStamp = AtomicLong() + val lastSendTimeMs: Long + get() = sendTimestamp.get() + val lastReceiveTimeMs: Long + get() = receiveTimeStamp.get() + val defaultPool = ConcreteExecutorPool() - var lastSendTimeMs = 0L - var lastReceiveTimeMs = 0L + init { + overrideDefaultRdLoggerFactoryWithKLogger(logger) + } /** * Delegates creation of the concrete executor to [defaultPool], which first searches for existing executor * and in case of failure, creates a new one. */ operator fun > invoke( - instrumentation: TInstrumentation, + instrumentationFactory: Instrumentation.Factory, pathsToUserClasses: String, - pathsToDependencyClasses: String = defaultPathsToDependencyClasses - ) = defaultPool.get(instrumentation, pathsToUserClasses, pathsToDependencyClasses) + ) = defaultPool.get(instrumentationFactory, pathsToUserClasses) internal fun > createNew( - instrumentation: TInstrumentation, - pathsToUserClasses: String, - pathsToDependencyClasses: String - ) = ConcreteExecutor(instrumentation, pathsToUserClasses, pathsToDependencyClasses) + instrumentationFactory: Instrumentation.Factory, + pathsToUserClasses: String + ) = ConcreteExecutor(instrumentationFactory, pathsToUserClasses) } - private val childProcessRunner = ChildProcessRunner() - var classLoader: ClassLoader? = UtContext.currentContext()?.classLoader //property that signals to executors pool whether it can reuse this executor or not - var alive = true - private set + val alive: Boolean + get() = ldef.isAlive + + private val corMutex = Mutex() + private var processInstance: InstrumentedProcess? = null + + // this function is intended to be called under corMutex + private suspend fun regenerate(): InstrumentedProcess { + ldef.throwIfNotAlive() + + var proc: InstrumentedProcess? = processInstance + + if (proc == null || !proc.lifetime.isAlive) { + proc = InstrumentedProcess( + ldef, + instrumentationFactory, + pathsToUserClasses, + classLoader + ) + processInstance = proc + } + + return proc + } /** - * Executes [kCallable] in the child process with the supplied [arguments] and [parameters], e.g. static environment. + * Main entry point for communicating with instrumented process. + * Use this function every time you want to access protocol model. + * This method prepares instrumented process for execution and ensures it is alive before giving it block * - * @return the processed result of the method call. + * @param exclusively if true - executes block under mutex. + * This guarantees that no one can access protocol model - no other calls made before block completes */ - override suspend fun executeAsync( - kCallable: KCallable<*>, - arguments: Array, - parameters: Any? - ): TIResult { - restartIfNeeded() - - val (clazz, signature) = when (kCallable) { - is KFunction<*> -> kCallable.javaMethod?.run { declaringClass to signature } - ?: kCallable.javaConstructor?.run { declaringClass to signature } - ?: error("Not a constructor or a method") - is KProperty<*> -> kCallable.javaGetter?.run { declaringClass to signature } - ?: error("Not a getter") - else -> error("Unknown KCallable: $kCallable") - } // actually executableId implements the same logic, but it requires UtContext + suspend fun withProcess(exclusively: Boolean = false, block: suspend InstrumentedProcess.() -> T): T { + fun throwConcreteIfDead(e: Throwable, proc: InstrumentedProcess?) { + if (proc?.lifetime?.isAlive != true) { + throw InstrumentedProcessDeathException(e) + } + } - val invokeMethodCommand = Protocol.InvokeMethodCommand( - clazz.name, - signature, - arguments.asList(), - parameters - ) + sendTimestamp.set(System.currentTimeMillis()) - val cmdId = sendCommand(invokeMethodCommand) - logger.trace("executeAsync, request: $cmdId , $invokeMethodCommand") + var proc: InstrumentedProcess? = null try { - val res = awaitCommand, TIResult>(cmdId) { - @Suppress("UNCHECKED_CAST") - it.result as TIResult + if (exclusively) { + corMutex.withLock { + proc = regenerate() + return proc!!.block() + } + } + else { + return corMutex.withLock { regenerate().apply { proc = this } }.block() } - logger.trace { "executeAsync, response: $cmdId, $res" } - return res - } catch (e: Throwable) { - logger.trace { "executeAsync, response(ERROR): $cmdId, $e" } - throw e } - } - - /** - * Send command and return its sequential_id - */ - private fun sendCommand(cmd: Protocol.Command): Long { - lastSendTimeMs = System.currentTimeMillis() - - val kryoHelper = state?.kryoHelper ?: error("State is null") - - val res = nextCommandId++ - logger.trace().bracket("Writing $res, $cmd to channel") { - kryoHelper.writeCommand(res, cmd) + catch (e: CancellationException) { + // cancellation can be from 2 causes + // 1. process died, its lifetime terminated, so operation was cancelled + // this clearly indicates instrumented process death -> ConcreteExecutionFailureException + throwConcreteIfDead(e, proc) + // 2. it can be ordinary timeout from coroutine. then just rethrow + throw e + } + catch(e: Throwable) { + // after exception process can either + // 1. be dead because of this exception + throwConcreteIfDead(e, proc) + // 2. might be deliberately thrown and process still can operate + throw InstrumentedProcessError(e) + } + finally { + receiveTimeStamp.set(System.currentTimeMillis()) } - return res } - fun warmup() { - restartIfNeeded() - sendCommand(Protocol.WarmupCommand()) + suspend fun executeAsync( + className: String, + signature: String, + arguments: Array, + parameters: Any? + ): TIResult = logger.logException("executeAsync, response(ERROR)") { + withProcess { + val argumentsByteArray = kryoHelper.writeObject(arguments.asList()) + val parametersByteArray = kryoHelper.writeObject(parameters) + val params = InvokeMethodCommandParams(className, signature, argumentsByteArray, parametersByteArray) + + val result = instrumentedProcessModel.invokeMethodCommand.startSuspending(lifetime, params).result + kryoHelper.readObject(result) + } } /** - * Restarts the child process if it is not active. + * Executes [kCallable] in the instrumented process with the supplied [arguments] and [parameters], e.g. static environment. + * + * @return the processed result of the method call. */ - class ConnectorState( - val receiverThread: Thread, - val process: Process, - val kryoHelper: KryoHelper, - val receiveChannel: Channel - ) { - var disposed = false - } - - data class CommandResult( - val commandId: Long, - val command: Protocol.Command, - val processStdout: InputStream - ) - - var state: ConnectorState? = null - - private fun restartIfNeeded() { - val oldState = state - if (oldState != null && oldState.process.isAlive && !oldState.disposed) return - - logger.debug() - .bracket("restartIfNeeded; instrumentation: '${instrumentation.javaClass.simpleName}',classpath: '$pathsToUserClasses'") { - //stop old thread - oldState?.terminateResources() - - try { - val process = childProcessRunner.start() - val kryoHelper = KryoHelper( - process.inputStream ?: error("Can't get the standard output of the subprocess"), - process.outputStream ?: error("Can't get the standard input of the subprocess") - ) - classLoader?.let { kryoHelper.setKryoClassLoader(it) } - - val readCommandsChannel = Channel(capacity = Channel.UNLIMITED) - val receiverThread = - thread(name = "ConcreteExecutor-${process.pid}-receiver", isDaemon = true, start = false) { - val s = state!! - while (true) { - val cmd = try { - val (commandId, command) = kryoHelper.readLong() to kryoHelper.readCommand() - logger.trace { "receiver: readFromStream: $commandId : $command" } - CommandResult(commandId, command, process.inputStream) - } catch (e: Throwable) { - if (s.disposed) { - break - } - - s.disposed = true - CommandResult( - ERROR_CMD_ID, - Protocol.ExceptionInKryoCommand(e), - process.inputStream - ) - } finally { - lastReceiveTimeMs = System.currentTimeMillis() - } - - try { - readCommandsChannel.offer(cmd) - } catch (e: CancellationException) { - s.disposed = true - - logger.info(e) { "Receiving is canceled in thread ${currentThreadInfo()} while sending command: $cmd" } - break - } catch (e: Throwable) { - s.disposed = true - - logger.error(e) { "Unexpected error while sending to channel in ${currentThreadInfo()}, cmd=$cmd" } - break - } - } - } - - state = ConnectorState(receiverThread, process, kryoHelper, readCommandsChannel) - receiverThread.start() - - // send classpath - // we don't expect ProcessReadyCommand here - sendCommand( - Protocol.AddPathsCommand( - pathsToUserClasses, - pathsToDependencyClasses - ) - ) - - // send instrumentation - // we don't expect ProcessReadyCommand here - sendCommand(Protocol.SetInstrumentationCommand(instrumentation)) - - } catch (e: Throwable) { - state?.terminateResources() - throw e - } - } + override suspend fun executeAsync( + kCallable: KCallable<*>, + arguments: Array, + parameters: Any? + ): TIResult { + val (className, signature) = when (kCallable) { + is KFunction<*> -> kCallable.javaMethod?.run { declaringClass.name to signature } + ?: kCallable.javaConstructor?.run { declaringClass.name to signature } + ?: error("Not a constructor or a method") + is KProperty<*> -> kCallable.javaGetter?.run { declaringClass.name to signature } + ?: error("Not a getter") + else -> error("Unknown KCallable: $kCallable") + } // actually executableId implements the same logic, but it requires UtContext + return executeAsync(className, signature, arguments, parameters) } - /** - * Sends [requestCmd] to the ChildProcess. - * If [action] is not null, waits for the response command, performs [action] on it and returns the result. - * This function is helpful for creating extensions for specific instrumentations. - * @see [org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation]. - */ - fun request(requestCmd: T, action: ((Protocol.Command) -> R)): R = runBlocking { - awaitCommand(sendCommand(requestCmd), action) + override fun close() { + forceTerminateProcess() } - /** - * Read next command of type [T] or throw exception - */ - private suspend inline fun awaitCommand( - awaitingCmdId: Long, - action: (T) -> R - ): R { - val s = state ?: error("State is not initialized") - - if (!currentCoroutineContext().isActive) { - logger.warn { "Current coroutine is canceled" } - } - - while (true) { - val (receivedId, cmd, processStdout) = s.receiveChannel.receive() - - if (receivedId == awaitingCmdId || receivedId == ERROR_CMD_ID) { - return when (cmd) { - is T -> action(cmd) - is Protocol.ExceptionInChildProcess -> throw ChildProcessError(cmd.exception) - is Protocol.ExceptionInKryoCommand -> { - // we assume that exception in Kryo means child process death - // and we do not need to check is it alive - throw ConcreteExecutionFailureException( - cmd.exception, - childProcessRunner.errorLogFile, - processStdout.bufferedReader().lines().toList() - ) - } - else -> throw UnexpectedCommand(cmd) + fun forceTerminateProcess() { + runBlocking { + corMutex.withLock { + if (alive) { + try { + processInstance?.run { + protocol.synchronizationModel.stopProcess.fire(Unit) + } + } catch (_: Exception) {} + processInstance = null } - } else if (receivedId > awaitingCmdId) { - logger.error { "BAD: Awaiting id: $awaitingCmdId, received: $receivedId" } - throw UnexpectedCommand(cmd) + ldef.terminate() } } } - // this fun sometimes work improperly - process dies after delay - @Suppress("unused") - private suspend fun checkProcessIsDeadWithTimeout(): Boolean = - if (state?.process?.isAlive == false) { - true - } else { - delay(50) - state?.process?.isAlive == false - } - - private fun ConnectorState.terminateResources() { - if (disposed) - return - - disposed = true - logger.debug { "Terminating resources in ConcreteExecutor.connectorState" } +} - if (!process.isAlive) { - return - } - logger.catch { kryoHelper.writeCommand(nextCommandId++, Protocol.StopProcessCommand()) } - logger.catch { kryoHelper.close() } +fun ConcreteExecutor<*,*>.warmup() = runBlocking { + withProcess { + instrumentedProcessModel.warmup.start(lifetime, Unit) + } +} - logger.catch { receiveChannel.close() } +fun ConcreteExecutor<*, *>.getRelevantSpringRepositories(classId: ClassId): Set = runBlocking { + withProcess { + val classId = kryoHelper.writeObject(classId) + val params = GetSpringRepositoriesParams(classId) + val result = instrumentedProcessModel.getRelevantSpringRepositories.startSuspending(lifetime, params) - logger.catch { process.waitUntilExitWithTimeout() } + kryoHelper.readObject(result.springRepositoryIds) } +} - override fun close() { - state?.terminateResources() - alive = false +fun ConcreteExecutor<*, *>.tryLoadingSpringContext(): ConcreteContextLoadingResult = runBlocking { + withProcess { + val result = instrumentedProcessModel.tryLoadingSpringContext.startSuspending(lifetime, Unit) + kryoHelper.readObject(result.springContextLoadingResult) } } -private fun Process.waitUntilExitWithTimeout() { - try { - if (!waitFor(100, TimeUnit.MICROSECONDS)) { - destroyForcibly() - } - } catch (e: Throwable) { - logger.error(e) { "Error during termination of child process" } +/** + * Extension function for the [ConcreteExecutor], which allows to collect static field value of [fieldId]. + */ +fun ConcreteExecutor<*, *>.computeStaticField(fieldId: FieldId): Result = runBlocking { + withProcess { + val fieldIdSerialized = kryoHelper.writeObject(fieldId) + val params = ComputeStaticFieldParams(fieldIdSerialized) + + val result = instrumentedProcessModel.computeStaticField.startSuspending(lifetime, params) + + kryoHelper.readObject(result.result) } -} \ No newline at end of file +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/Executor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/Executor.kt index 10e1ff2274..80213cb248 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/Executor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/Executor.kt @@ -1,6 +1,7 @@ package org.utbot.instrumentation import org.utbot.framework.plugin.api.ExecutableId +import java.lang.reflect.Method import kotlin.reflect.KCallable import kotlinx.coroutines.runBlocking @@ -9,7 +10,7 @@ import kotlinx.coroutines.runBlocking * * @param TResult the type of an execution result. */ -interface Executor { +interface Executor { /** * Main method to override. * Returns the result of the execution of the [ExecutableId] with [arguments] and [parameters]. @@ -28,6 +29,3 @@ fun Executor.execute( arguments: Array, parameters: Any? = null ) = runBlocking { executeAsync(kCallable, arguments, parameters) } - - - diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/Settings.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/Settings.kt index 9af98023ca..045074a26f 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/Settings.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/Settings.kt @@ -3,7 +3,7 @@ package org.utbot.instrumentation import org.objectweb.asm.Opcodes object Settings { - const val ASM_API = Opcodes.ASM5 + const val ASM_API = Opcodes.ASM7 /** * Constants used in bytecode instrumentation. @@ -14,21 +14,5 @@ object Settings { const val TRACE_ARRAY_SIZE: Int = 1 shl 20 - // TODO: maybe add this guide to confluence? - /** - * If true, runs the child process with the ability to attach a debugger. - * - * To debug the child process, set the breakpoint in the childProcessRunner.start() line - * and in the child process's main function and run the main process. - * Then run the remote JVM debug configuration in IDEA. - * If you see the message in console about successful connection, then - * the debugger is attached successfully. - * Now you can put the breakpoints in the child process and debug - * both processes simultaneously. - * - * @see [org.utbot.instrumentation.process.ChildProcessRunner.cmds] - */ - const val runChildProcessWithDebug = false - var defaultConcreteExecutorPoolSize = 10 } \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/agent/DynamicClassTransformer.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/agent/DynamicClassTransformer.kt index 6c8cea9f41..e9ba0651ff 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/agent/DynamicClassTransformer.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/agent/DynamicClassTransformer.kt @@ -1,9 +1,18 @@ package org.utbot.instrumentation.agent +import com.jetbrains.rd.util.error +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info import org.utbot.common.asPathToFile import org.utbot.framework.plugin.api.util.UtContext import java.lang.instrument.ClassFileTransformer +import java.nio.file.Paths import java.security.ProtectionDomain +import kotlin.io.path.absolutePathString +import kotlin.properties.Delegates + + +private val logger = getLogger() /** * Transformer, which will transform only classes with certain names. @@ -11,6 +20,7 @@ import java.security.ProtectionDomain class DynamicClassTransformer : ClassFileTransformer { lateinit var transformer: ClassFileTransformer + var useBytecodeTransformation by Delegates.notNull() private val pathsToUserClasses = mutableSetOf() fun addUserPaths(paths: Iterable) { @@ -25,18 +35,21 @@ class DynamicClassTransformer : ClassFileTransformer { classfileBuffer: ByteArray ): ByteArray? { try { - UtContext.currentContext()?.stopWatch?.stop() - val pathToClassfile = protectionDomain.codeSource?.location?.path?.asPathToFile() + // since we got here we have loaded a new class, meaning program is not stuck and some "meaningful" + // non-repeating actions are performed, so we assume that we should not time out for then next 65 ms + UtContext.currentContext()?.stopWatch?.stop(compensationMillis = 65) + val pathToClassfile = protectionDomain.codeSource?.location?.toURI()?.let(Paths::get)?.absolutePathString() return if (pathToClassfile in pathsToUserClasses || packsToAlwaysTransform.any(className::startsWith) ) { - System.err.println("Transforming: $className") - transformer.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer) + transformer.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer)?.also { + logger.info { "Transformed: $className" } + } } else { null } } catch (e: Throwable) { - System.err.println("Error while transforming: ${e.stackTraceToString()}") + logger.error { "Error while transforming: ${e.stackTraceToString()}" } throw e } finally { UtContext.currentContext()?.stopWatch?.start() @@ -44,12 +57,9 @@ class DynamicClassTransformer : ClassFileTransformer { } companion object { - private val packsToAlwaysTransform = listOf( "org/slf4j", "org/utbot/instrumentation/warmup" ) - } - } \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/Instrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/Instrumentation.kt index 73ef995390..92042d1cc7 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/Instrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/Instrumentation.kt @@ -1,7 +1,10 @@ package org.utbot.instrumentation.instrumentation -import org.utbot.instrumentation.util.Protocol import java.lang.instrument.ClassFileTransformer +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.process.kryo.KryoHelper +import org.utbot.instrumentation.process.generated.InstrumentedProcessModel +import org.utbot.rd.IdleWatchdog /** * Abstract class for the instrumentation. @@ -25,18 +28,14 @@ interface Instrumentation : ClassFileTransformer parameters: Any? = null ): TInvocationInstrumentation - /** - * This function will be called from the child process loop every time it receives [Protocol.InstrumentationCommand] from the main process. - * - * @return Handles [cmd] and returns command which should be sent back to the [org.utbot.instrumentation.ConcreteExecutor]. - * If returns `null`, nothing will be sent. - */ - fun handle(cmd: T): Protocol.Command? { - return null - } + fun getStaticField(fieldId: FieldId): Result<*> - /** - * Will be called in the very beginning in the child process. - */ - fun init(pathsToUserClasses: Set) {} + fun InstrumentedProcessModel.setupAdditionalRdResponses(kryoHelper: KryoHelper, watchdog: IdleWatchdog) {} + + interface Factory> { + val additionalRuntimeClasspath: Set get() = emptySet() + val forceDisableSandbox: Boolean get() = false + + fun create(): TInstrumentation + } } \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/InvokeInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/InvokeInstrumentation.kt index 229bd8452f..96a1ef9109 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/InvokeInstrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/InvokeInstrumentation.kt @@ -1,12 +1,16 @@ package org.utbot.instrumentation.instrumentation -import org.utbot.common.withAccessibility import org.utbot.framework.plugin.api.util.signature +import org.utbot.instrumentation.process.runSandbox import java.lang.reflect.Constructor import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method import java.lang.reflect.Modifier import java.security.ProtectionDomain +import org.utbot.common.withAccessibility +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.jField typealias ArgumentList = List @@ -52,7 +56,7 @@ class InvokeInstrumentation : Instrumentation> { methodOrConstructor.run { val result = when (this) { is Method -> - withAccessibility { + runSandbox { runCatching { invoke(thisObject, *realArgs.toTypedArray()).let { if (returnType != Void.TYPE) it else Unit @@ -61,7 +65,7 @@ class InvokeInstrumentation : Instrumentation> { } is Constructor<*> -> - withAccessibility { + runSandbox { runCatching { newInstance(*realArgs.toTypedArray()) } @@ -89,6 +93,19 @@ class InvokeInstrumentation : Instrumentation> { } } + /** + * Get field by reflection and return raw value. + */ + override fun getStaticField(fieldId: FieldId): Result = + if (!fieldId.isStatic) { + Result.failure(IllegalArgumentException("Field must be static!")) + } else { + val field = fieldId.jField + val value = field.withAccessibility { + field.get(null) + } + Result.success(value) + } /** * Does not change bytecode. @@ -100,4 +117,8 @@ class InvokeInstrumentation : Instrumentation> { protectionDomain: ProtectionDomain, classfileBuffer: ByteArray ) = null + + object Factory : Instrumentation.Factory, InvokeInstrumentation> { + override fun create(): InvokeInstrumentation = InvokeInstrumentation() + } } \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/InvokeWithStaticsInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/InvokeWithStaticsInstrumentation.kt index 0f5944c3b7..9572944aeb 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/InvokeWithStaticsInstrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/InvokeWithStaticsInstrumentation.kt @@ -1,11 +1,12 @@ package org.utbot.instrumentation.instrumentation -import org.utbot.common.withRemovedFinalModifier -import org.utbot.framework.plugin.api.util.field +import org.utbot.common.withAccessibility +import org.utbot.framework.plugin.api.util.jField import org.utbot.instrumentation.util.StaticEnvironment import java.lang.reflect.Field import java.lang.reflect.Modifier import java.security.ProtectionDomain +import org.utbot.framework.plugin.api.FieldId /** * This instrumentation allows supplying [StaticEnvironment] and saving static fields. This makes call pure. @@ -43,11 +44,14 @@ class InvokeWithStaticsInstrumentation : Instrumentation> { return invokeResult } + override fun getStaticField(fieldId: FieldId): Result<*> = + invokeInstrumentation.getStaticField(fieldId) + private fun setStaticFields(staticEnvironment: StaticEnvironment?) { staticEnvironment?.run { listOfFields.forEach { (fieldId, value) -> - fieldId.field.run { - withRemovedFinalModifier { + fieldId.jField.run { + withAccessibility { set(null, value) } } @@ -75,16 +79,20 @@ class InvokeWithStaticsInstrumentation : Instrumentation> { init { val staticFields = clazz.declaredFields .filter { checkField(it) } // TODO: think on this - .associate { it.name to it.withRemovedFinalModifier { it.get(null) } } + .associate { it.name to it.withAccessibility { it.get(null) } } savedFields = staticFields } fun restore() { clazz.declaredFields .filter { checkField(it) } - .forEach { it.withRemovedFinalModifier { it.set(null, savedFields[it.name]) } } + .forEach { it.withAccessibility { it.set(null, savedFields[it.name]) } } } } + + object Factory : Instrumentation.Factory, InvokeWithStaticsInstrumentation> { + override fun create(): InvokeWithStaticsInstrumentation = InvokeWithStaticsInstrumentation() + } } private fun checkField(field: Field) = Modifier.isStatic(field.modifiers) diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/coverage/CoverageInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/coverage/CoverageInstrumentation.kt index 5af671fba5..ab3238a97b 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/coverage/CoverageInstrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/coverage/CoverageInstrumentation.kt @@ -1,6 +1,7 @@ package org.utbot.instrumentation.instrumentation.coverage -import org.utbot.common.withRemovedFinalModifier +import kotlinx.coroutines.runBlocking +import org.utbot.common.withAccessibility import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.Settings import org.utbot.instrumentation.instrumentation.ArgumentList @@ -8,12 +9,10 @@ import org.utbot.instrumentation.instrumentation.Instrumentation import org.utbot.instrumentation.instrumentation.InvokeWithStaticsInstrumentation import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter import org.utbot.instrumentation.util.CastProbesArrayException -import org.utbot.instrumentation.util.ChildProcessError -import org.utbot.instrumentation.util.InstrumentationException import org.utbot.instrumentation.util.NoProbesArrayException -import org.utbot.instrumentation.util.Protocol -import org.utbot.instrumentation.util.UnexpectedCommand import java.security.ProtectionDomain +import org.utbot.framework.plugin.api.FieldId +import org.utbot.instrumentation.process.generated.CollectCoverageParams data class CoverageInfo( val methodToInstrRange: Map, @@ -23,7 +22,7 @@ data class CoverageInfo( /** * This instrumentation allows collecting coverage after several calls. */ -object CoverageInstrumentation : Instrumentation> { +class CoverageInstrumentation : Instrumentation> { private val invokeWithStatics = InvokeWithStaticsInstrumentation() /** @@ -43,21 +42,23 @@ object CoverageInstrumentation : Instrumentation> { val visitedLinesField = clazz.fields.firstOrNull { it.name == probesFieldName } ?: throw NoProbesArrayException(clazz, Settings.PROBES_ARRAY_NAME) - return visitedLinesField.withRemovedFinalModifier { + return visitedLinesField.withAccessibility { invokeWithStatics.invoke(clazz, methodSignature, arguments, parameters) } } + override fun getStaticField(fieldId: FieldId): Result<*> = + invokeWithStatics.getStaticField(fieldId) /** * Collects coverage from the given [clazz] via reflection. */ - private fun collectCoverageInfo(clazz: Class): CoverageInfo { + fun collectCoverageInfo(clazz: Class): CoverageInfo { val probesFieldName: String = Settings.PROBES_ARRAY_NAME val visitedLinesField = clazz.fields.firstOrNull { it.name == probesFieldName } ?: throw NoProbesArrayException(clazz, Settings.PROBES_ARRAY_NAME) - return visitedLinesField.withRemovedFinalModifier { + return visitedLinesField.withAccessibility { val visitedLines = visitedLinesField.get(null) as? BooleanArray ?: throw CastProbesArrayException() @@ -94,42 +95,18 @@ object CoverageInstrumentation : Instrumentation> { return instrumenter.classByteCode } - /** - * Collects coverage for the class wrapped in [cmd] if [cmd] is [CollectCoverageCommand]. - * - * @return [CoverageInfoCommand] with wrapped [CoverageInfo] if [cmd] is [CollectCoverageCommand] and `null` otherwise. - */ - override fun handle(cmd: T): Protocol.Command? = when (cmd) { - is CollectCoverageCommand<*> -> try { - CoverageInfoCommand(collectCoverageInfo(cmd.clazz)) - } catch (e: InstrumentationException) { - Protocol.ExceptionInChildProcess(e) - } - else -> null + object Factory : Instrumentation.Factory, CoverageInstrumentation> { + override fun create(): CoverageInstrumentation = CoverageInstrumentation() } } -/** - * This command is sent to the child process from the [ConcreteExecutor] if user wants to collect coverage for the - * [clazz]. - */ -data class CollectCoverageCommand(val clazz: Class) : Protocol.InstrumentationCommand() - -/** - * This command is sent back to the [ConcreteExecutor] with the [coverageInfo]. - */ -data class CoverageInfoCommand(val coverageInfo: CoverageInfo) : Protocol.InstrumentationCommand() - /** * Extension function for the [ConcreteExecutor], which allows to collect the coverage of the given [clazz]. */ -fun ConcreteExecutor, CoverageInstrumentation>.collectCoverage(clazz: Class<*>): CoverageInfo { - val collectCoverageCommand = CollectCoverageCommand(clazz) - return this.request(collectCoverageCommand) { - when (it) { - is CoverageInfoCommand -> it.coverageInfo - is Protocol.ExceptionInChildProcess -> throw ChildProcessError(it.exception) - else -> throw UnexpectedCommand(it) - } - }!! +fun ConcreteExecutor, CoverageInstrumentation>.collectCoverage(clazz: Class<*>): CoverageInfo = runBlocking { + withProcess { + val clazzByteArray = kryoHelper.writeObject(clazz) + + kryoHelper.readObject(instrumentedProcessModel.collectCoverage.startSuspending(lifetime, CollectCoverageParams(clazzByteArray)).coverageInfo) + } } \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/ExecutionTraceInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/ExecutionTraceInstrumentation.kt index f53831d2f2..f62eff9b47 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/ExecutionTraceInstrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/ExecutionTraceInstrumentation.kt @@ -5,6 +5,7 @@ import org.utbot.instrumentation.instrumentation.Instrumentation import org.utbot.instrumentation.instrumentation.InvokeWithStaticsInstrumentation import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter import java.security.ProtectionDomain +import org.utbot.framework.plugin.api.FieldId /** * This instrumentation allows to get execution trace during each call. @@ -33,6 +34,10 @@ class ExecutionTraceInstrumentation : Instrumentation { return trace } + + override fun getStaticField(fieldId: FieldId): Result<*> = + invokeWithStatics.getStaticField(fieldId) + /** * Transforms bytecode such way that it becomes possible to get an execution trace during a call. * @@ -52,4 +57,8 @@ class ExecutionTraceInstrumentation : Instrumentation { classByteCode } } + + object Factory : Instrumentation.Factory { + override fun create(): ExecutionTraceInstrumentation = ExecutionTraceInstrumentation() + } } \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/TraceHandler.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/TraceHandler.kt index b0bcf099a4..6676be0da4 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/TraceHandler.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/et/TraceHandler.kt @@ -1,5 +1,7 @@ package org.utbot.instrumentation.instrumentation.et +import com.jetbrains.rd.util.error +import com.jetbrains.rd.util.getLogger import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.FieldId import org.utbot.instrumentation.Settings @@ -61,6 +63,7 @@ class ProcessingStorage { private val idToClassMethod = mutableMapOf() private val instructionsData = mutableMapOf() + private val classToInstructionsCount = mutableMapOf() fun addClass(className: String): Int { val id = classToId.getOrPut(className) { classToId.size } @@ -86,9 +89,16 @@ class ProcessingStorage { } fun addInstruction(id: Long, instructionData: InstructionData) { - instructionsData.putIfAbsent(id, instructionData) + instructionsData.computeIfAbsent(id) { + val (className, _) = computeClassNameAndLocalId(id) + classToInstructionsCount.merge(className, 1, Long::plus) + instructionData + } } + fun getInstructionsCount(className: String): Long? = + classToInstructionsCount[className] + fun getInstruction(id: Long): InstructionData { return instructionsData.getValue(id) } @@ -98,11 +108,14 @@ class ProcessingStorage { } } +private val logger = getLogger() /** * Storage to which instrumented classes will write execution data. */ object RuntimeTraceStorage { + internal var alreadyLoggedIncreaseStackSizeTip = false + /** * Contains ids of instructions in the order of execution. */ @@ -143,7 +156,11 @@ object RuntimeTraceStorage { this.`$__trace__`[current] = id this.`$__counter__` = current + 1 } else { - System.err.println("Stack overflow (increase stack size Settings.TRACE_ARRAY_SIZE)") + val loggedTip = alreadyLoggedIncreaseStackSizeTip + if (!loggedTip) { + alreadyLoggedIncreaseStackSizeTip = true + logger.error { "Stack overflow (increase stack size Settings.TRACE_ARRAY_SIZE)" } + } } } } @@ -177,7 +194,7 @@ class TraceInstructionBytecodeInserter { } class TraceHandler { - private val processingStorage = ProcessingStorage() + val processingStorage = ProcessingStorage() private val inserter = TraceInstructionBytecodeInserter() private var instructionsList: List? = null @@ -281,5 +298,6 @@ class TraceHandler { fun resetTrace() { instructionsList = null RuntimeTraceStorage.`$__counter__` = 0 + RuntimeTraceStorage.alreadyLoggedIncreaseStackSizeTip = false } } \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/RemovingConstructFailsUtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/RemovingConstructFailsUtExecutionInstrumentation.kt new file mode 100644 index 0000000000..803f0b6e8b --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/RemovingConstructFailsUtExecutionInstrumentation.kt @@ -0,0 +1,155 @@ +package org.utbot.instrumentation.instrumentation.execution + +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MissingState +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtConcreteExecutionProcessedFailure +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtStatementCallModel +import org.utbot.framework.plugin.api.isNull +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.process.kryo.KryoHelper +import org.utbot.instrumentation.instrumentation.ArgumentList +import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext +import org.utbot.instrumentation.instrumentation.execution.phases.ExecutionPhaseStop +import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController +import org.utbot.instrumentation.instrumentation.execution.phases.ValueConstructionPhase +import org.utbot.instrumentation.process.generated.InstrumentedProcessModel +import org.utbot.rd.IdleWatchdog +import java.security.ProtectionDomain + +/** + * [UtExecutionInstrumentation] that on [invoke] tries to run [invoke] of the [delegateInstrumentation] + * a few times, each time removing failing [UtStatementCallModel]s, until either max number of reruns + * is reached or [invoke] of the [delegateInstrumentation] no longer fails with [UtConcreteExecutionProcessedFailure]. + * + * @see [UtStatementCallModel.thrownConcreteException] + */ +class RemovingConstructFailsUtExecutionInstrumentation( + instrumentationContext: InstrumentationContext, + delegateInstrumentationFactory: UtExecutionInstrumentation.Factory<*> +) : UtExecutionInstrumentation { + companion object { + private const val MAX_RETRIES = 5 + private val logger = getLogger() + } + + private val delegateInstrumentation = delegateInstrumentationFactory.create(object : InstrumentationContext by instrumentationContext { + override fun handleLastCaughtConstructionException(exception: Throwable) { + throw ExecutionPhaseStop( + phase = ValueConstructionPhase::class.java.simpleName, + result = PreliminaryUtConcreteExecutionResult( + stateAfter = MissingState, + result = UtConcreteExecutionProcessedFailure(exception), + coverage = Coverage() + ) + ) + } + }) + private var runsCompleted = 0 + private var nextRunIndexToLog = 1 // we log `attemptsDistribution` every run that has index that is a power of 10 + private val attemptsDistribution = mutableMapOf() + + override fun invoke( + clazz: Class<*>, + methodSignature: String, + arguments: ArgumentList, + parameters: Any?, + phasesWrapper: PhasesController.(invokeBasePhases: () -> PreliminaryUtConcreteExecutionResult) -> PreliminaryUtConcreteExecutionResult + ): UtConcreteExecutionResult { + @Suppress("NAME_SHADOWING") + var parameters = parameters as UtConcreteExecutionData + var attempt = 0 + var res: UtConcreteExecutionResult + try { + do { + res = delegateInstrumentation.invoke(clazz, methodSignature, arguments, parameters, phasesWrapper) + + if (res.result !is UtConcreteExecutionProcessedFailure) + return res + + parameters = parameters.mapModels(UtModelDeepMapper { model -> + shallowlyRemoveFailingCalls(model) + }) + + // if `thisInstance` is present and became `isNull`, then we should stop trying to + // correct this execution and return `UtConcreteExecutionProcessedFailure` + if (parameters.stateBefore.thisInstance?.isNull() == true) + return res + + } while (attempt++ < MAX_RETRIES) + + return res + } finally { + runsCompleted++ + attemptsDistribution[attempt] = (attemptsDistribution[attempt] ?: 0) + 1 + if (runsCompleted == nextRunIndexToLog) { + nextRunIndexToLog *= 10 + logger.info { "Run: $runsCompleted, attemptsDistribution: $attemptsDistribution" } + } + } + } + + private fun shallowlyRemoveFailingCalls(model: UtModel): UtModel = when { + model !is UtAssembleModel -> model + model.instantiationCall.thrownConcreteException != null -> model.classId.defaultValueModel() + else -> UtAssembleModel( + id = model.id, + classId = model.classId, + modelName = model.modelName, + instantiationCall = model.instantiationCall, + origin = model.origin, + modificationsChainProvider = { + model.modificationsChain.filter { + (it as? UtStatementCallModel)?.thrownConcreteException == null && + (it.instance as? UtAssembleModel)?.instantiationCall?.thrownConcreteException == null + } + } + ) + } + + override fun getStaticField(fieldId: FieldId): Result<*> = delegateInstrumentation.getStaticField(fieldId) + + override fun transform( + loader: ClassLoader?, + className: String, + classBeingRedefined: Class<*>?, + protectionDomain: ProtectionDomain, + classfileBuffer: ByteArray + ): ByteArray? = delegateInstrumentation.transform( + loader, className, classBeingRedefined, protectionDomain, classfileBuffer + ) + + override fun InstrumentedProcessModel.setupAdditionalRdResponses(kryoHelper: KryoHelper, watchdog: IdleWatchdog) = + delegateInstrumentation.run { setupAdditionalRdResponses(kryoHelper, watchdog) } + + class Factory( + private val delegateInstrumentationFactory: UtExecutionInstrumentation.Factory<*> + ) : UtExecutionInstrumentation.Factory { + override val additionalRuntimeClasspath: Set + get() = delegateInstrumentationFactory.additionalRuntimeClasspath + + override val forceDisableSandbox: Boolean + get() = delegateInstrumentationFactory.forceDisableSandbox + + override fun create(instrumentationContext: InstrumentationContext): UtExecutionInstrumentation = + RemovingConstructFailsUtExecutionInstrumentation(instrumentationContext, delegateInstrumentationFactory) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Factory + + return delegateInstrumentationFactory == other.delegateInstrumentationFactory + } + + override fun hashCode(): Int { + return delegateInstrumentationFactory.hashCode() + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt new file mode 100644 index 0000000000..bfbdabb159 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/SimpleUtExecutionInstrumentation.kt @@ -0,0 +1,209 @@ +package org.utbot.instrumentation.instrumentation.execution + +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.executable +import org.utbot.framework.plugin.api.util.signature +import org.utbot.framework.plugin.api.util.singleExecutableId +import org.utbot.instrumentation.agent.Agent +import org.utbot.instrumentation.instrumentation.ArgumentList +import org.utbot.instrumentation.instrumentation.InvokeInstrumentation +import org.utbot.instrumentation.instrumentation.et.TraceHandler +import org.utbot.instrumentation.instrumentation.execution.constructors.ConstructOnlyUserClassesOrCachedObjectsStrategy +import org.utbot.instrumentation.instrumentation.execution.constructors.StateBeforeAwareIdGenerator +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor +import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext +import org.utbot.instrumentation.instrumentation.execution.context.SimpleInstrumentationContext +import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicClassVisitor +import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicDetector +import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController +import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter +import org.utbot.instrumentation.instrumentation.mock.MockClassVisitor +import org.utbot.instrumentation.instrumentation.transformation.BytecodeTransformer +import java.security.ProtectionDomain +import kotlin.reflect.jvm.javaMethod + +class SimpleUtExecutionInstrumentation( + private val pathsToUserClasses: Set, + private val instrumentationContext: InstrumentationContext = SimpleInstrumentationContext() +) : UtExecutionInstrumentation { + private val delegateInstrumentation = InvokeInstrumentation() + + private val traceHandler = TraceHandler() + private val ndDetector = NonDeterministicDetector() + + /** + * Ignores [arguments], because concrete arguments will be constructed + * from models passed via [parameters]. + * + * Ignores [clazz] and [methodSignature] if they can be constructed + * from [parameters] (see [EnvironmentModels.executableToCall]). + * + * Argument [parameters] must be of type [UtConcreteExecutionData]. + */ + override fun invoke( + clazz: Class<*>, + methodSignature: String, + arguments: ArgumentList, + parameters: Any?, + phasesWrapper: PhasesController.(invokeBasePhases: () -> PreliminaryUtConcreteExecutionResult) -> PreliminaryUtConcreteExecutionResult + ): UtConcreteExecutionResult { + if (parameters !is UtConcreteExecutionData) { + throw IllegalArgumentException("Argument parameters must be of type UtConcreteExecutionData, but was: ${parameters?.javaClass}") + } + val (stateBefore, instrumentations, timeout) = parameters // smart cast to UtConcreteExecutionData + + lateinit var detectedMockingCandidates: Set + + return PhasesController( + instrumentationContext, + traceHandler, + delegateInstrumentation, + timeout, + idGenerator = StateBeforeAwareIdGenerator.fromUtConcreteExecutionData(parameters) + ).computeConcreteExecutionResult { + detectedMockingCandidates = valueConstructionPhase.detectedMockingCandidates + + phasesWrapper { + try { + // some preparation actions for concrete execution + val constructedData = applyPreprocessing(parameters) + + val (params, statics, cache) = constructedData + + // invocation + val concreteResult = executePhaseInTimeout(invocationPhase) { + val executableToCall = stateBefore.executableToCall?.executable + invoke( + clazz = executableToCall?.declaringClass ?: clazz, + methodSignature = executableToCall?.signature ?: methodSignature, + params = params.map { it.value } + ) + } + + // statistics collection + val (coverage, ndResults) = executePhaseInTimeout(statisticsCollectionPhase) { + getCoverage(clazz) to getNonDeterministicResults() + } + + // model construction + val (executionResult, stateAfter, newInstrumentation) = executePhaseInTimeout(modelConstructionPhase) { + configureConstructor { + this.cache = cache + strategy = ConstructOnlyUserClassesOrCachedObjectsStrategy( + pathsToUserClasses, + cache + ) + } + + val ndStatics = constructStaticInstrumentation(ndResults.statics) + val ndNews = constructNewInstrumentation(ndResults.news, ndResults.calls) + val newInstrumentation = mergeInstrumentations(instrumentations, ndStatics, ndNews) + + val returnType = clazz.singleExecutableId(methodSignature).returnType + val executionResult = convertToExecutionResult(concreteResult, returnType) + + val stateAfterParametersWithThis = constructParameters(params) + val stateAfterStatics = constructStatics(stateBefore, statics) + val (stateAfterThis, stateAfterParameters) = if (stateBefore.thisInstance == null) { + null to stateAfterParametersWithThis + } else { + stateAfterParametersWithThis.first() to stateAfterParametersWithThis.drop(1) + } + val stateAfter = EnvironmentModels( + thisInstance = stateAfterThis, + parameters = stateAfterParameters, + statics = stateAfterStatics, + executableToCall = stateBefore.executableToCall + ) + + Triple(executionResult, stateAfter, newInstrumentation) + } + + PreliminaryUtConcreteExecutionResult( + stateAfter, + executionResult, + coverage, + newInstrumentation + ) + } finally { + // restoring data after concrete execution + applyPostprocessing() + } + } + }.toCompleteUtConcreteExecutionResult( + stateBefore = stateBefore, + detectedMockingCandidates = detectedMockingCandidates, + ) + } + + override fun getStaticField(fieldId: FieldId): Result = + delegateInstrumentation.getStaticField(fieldId).map { value -> + UtModelConstructor.createOnlyUserClassesConstructor( + pathsToUserClasses = pathsToUserClasses, + utModelWithCompositeOriginConstructorFinder = instrumentationContext::findUtModelWithCompositeOriginConstructor + ).construct(value, fieldId.type) + } + + override fun transform( + loader: ClassLoader?, + className: String, + classBeingRedefined: Class<*>?, + protectionDomain: ProtectionDomain, + classfileBuffer: ByteArray + ): ByteArray { + val instrumenter = Instrumenter(classfileBuffer, loader) + + if (Agent.dynamicClassTransformer.useBytecodeTransformation) { + instrumenter.visitClass { writer -> + BytecodeTransformer(writer) + } + } + + traceHandler.registerClass(className) + instrumenter.visitInstructions(traceHandler.computeInstructionVisitor(className)) + + instrumenter.visitClass { writer -> + NonDeterministicClassVisitor(writer, ndDetector) + } + + val mockClassVisitor = instrumenter.visitClass { writer -> + MockClassVisitor( + writer, + InstrumentationContext.MockGetter::getMock.javaMethod!!, + InstrumentationContext.MockGetter::checkCallSite.javaMethod!!, + InstrumentationContext.MockGetter::hasMock.javaMethod!! + ) + } + + mockClassVisitor.signatureToId.forEach { (method, id) -> + instrumentationContext.methodSignatureToId += method to id + } + + return instrumenter.classByteCode + } + + class Factory( + private val pathsToUserClasses: Set + ) : UtExecutionInstrumentation.Factory { + override fun create(): UtExecutionInstrumentation = SimpleUtExecutionInstrumentation(pathsToUserClasses) + + override fun create(instrumentationContext: InstrumentationContext): UtExecutionInstrumentation = + SimpleUtExecutionInstrumentation(pathsToUserClasses, instrumentationContext) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Factory + + return pathsToUserClasses == other.pathsToUserClasses + } + + override fun hashCode(): Int { + return pathsToUserClasses.hashCode() + } + } +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt new file mode 100644 index 0000000000..3986bef965 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt @@ -0,0 +1,100 @@ +package org.utbot.instrumentation.instrumentation.execution + +import org.utbot.framework.UtSettings +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.mapper.UtModelMapper +import org.utbot.framework.plugin.api.mapper.mapModels +import org.utbot.instrumentation.instrumentation.ArgumentList +import org.utbot.instrumentation.instrumentation.Instrumentation +import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext +import org.utbot.instrumentation.instrumentation.execution.context.SimpleInstrumentationContext +import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController + +/** + * Consists of the data needed to execute the method concretely. Also includes method arguments stored in models. + * + * @property [stateBefore] is necessary for construction of parameters of a concrete call. + * @property [instrumentation] is necessary for mocking static methods and new instances. + * @property [timeout] is timeout for specific concrete execution (in milliseconds). + * @property [isRerun] reruns can be used to obtain more reproducible results (e.g. on clean Spring application context), + * rerun can take more time due to context reinitialisation. + * + * By default is initialized from [UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis] + */ +data class UtConcreteExecutionData( + val stateBefore: EnvironmentModels, + val instrumentation: List, + val timeout: Long, + val isRerun: Boolean, +) + +fun UtConcreteExecutionData.mapModels(mapper: UtModelMapper) = copy( + stateBefore = stateBefore.mapModels(mapper), + instrumentation = instrumentation.map { it.mapModels(mapper) } +) + +/** + * [UtConcreteExecutionResult] that has not yet been populated with extra data, e.g.: + * - updated [UtConcreteExecutionResult.stateBefore] + * - [UtConcreteExecutionResult.detectedMockingCandidates] + */ +data class PreliminaryUtConcreteExecutionResult( + val stateAfter: EnvironmentModels, + val result: UtExecutionResult, + val coverage: Coverage, + val newInstrumentation: List? = null, +) { + fun toCompleteUtConcreteExecutionResult( + stateBefore: EnvironmentModels, + detectedMockingCandidates: Set + ) = UtConcreteExecutionResult( + stateBefore = stateBefore, + stateAfter = stateAfter, + result = result, + coverage = coverage, + newInstrumentation = newInstrumentation, + detectedMockingCandidates = detectedMockingCandidates, + ) +} + +data class UtConcreteExecutionResult( + val stateBefore: EnvironmentModels, + val stateAfter: EnvironmentModels, + val result: UtExecutionResult, + val coverage: Coverage, + val newInstrumentation: List? = null, + val detectedMockingCandidates: Set, +) { + override fun toString(): String = buildString { + appendLine("UtConcreteExecutionResult(") + appendLine("stateBefore=$stateBefore") + appendLine("stateAfter=$stateAfter") + appendLine("result=$result") + appendLine("coverage=$coverage)") + } +} + +interface UtExecutionInstrumentation : Instrumentation { + override fun invoke( + clazz: Class<*>, + methodSignature: String, + arguments: ArgumentList, + parameters: Any? + ): UtConcreteExecutionResult = invoke( + clazz, methodSignature, arguments, parameters, phasesWrapper = { invokeBasePhases -> invokeBasePhases() } + ) + + fun invoke( + clazz: Class<*>, + methodSignature: String, + arguments: ArgumentList, + parameters: Any?, + phasesWrapper: PhasesController.(invokeBasePhases: () -> PreliminaryUtConcreteExecutionResult) -> PreliminaryUtConcreteExecutionResult + ): UtConcreteExecutionResult + + interface Factory : Instrumentation.Factory { + override fun create(): TInstrumentation = create(SimpleInstrumentationContext()) + + fun create(instrumentationContext: InstrumentationContext): TInstrumentation + } +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/InstrumentationContextAwareValueConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/InstrumentationContextAwareValueConstructor.kt new file mode 100644 index 0000000000..51c398ca36 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/InstrumentationContextAwareValueConstructor.kt @@ -0,0 +1,644 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.mockito.Mockito +import org.mockito.stubbing.Answer +import org.objectweb.asm.Type +import org.utbot.common.Reflection +import org.utbot.common.invokeCatching +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.DirectFieldAccessId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtClassRefModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtConcreteValue +import org.utbot.framework.plugin.api.UtCustomModel +import org.utbot.framework.plugin.api.UtDirectGetFieldModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtStatementCallModel +import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation +import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.framework.plugin.api.isNull +import org.utbot.framework.plugin.api.util.anyInstance +import org.utbot.framework.plugin.api.util.classClassId +import org.utbot.framework.plugin.api.util.constructor +import org.utbot.framework.plugin.api.util.constructor.CapturedArgument +import org.utbot.framework.plugin.api.util.constructor.constructLambda +import org.utbot.framework.plugin.api.util.constructor.constructStaticLambda +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.jField +import org.utbot.framework.plugin.api.util.method +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext +import org.utbot.instrumentation.instrumentation.execution.mock.InstanceMockController +import org.utbot.instrumentation.instrumentation.execution.mock.MethodMockController +import org.utbot.instrumentation.instrumentation.execution.mock.MockController +import org.utbot.instrumentation.process.runSandbox +import java.lang.reflect.Modifier +import java.lang.reflect.TypeVariable +import java.security.AccessController +import java.security.PrivilegedAction +import java.util.* +import kotlin.reflect.KClass + +/** + * Constructs values (including mocks) from models. + * + * Uses model->constructed object reference-equality cache. + * + * This class is based on `ValueConstructor.kt`. The main difference is the ability to create mocked objects, mock + * static methods, and [construct context dependent values][InstrumentationContext.constructContextDependentValue]. + * + * Note that `clearState` was deleted! + */ +// TODO: JIRA:1379 -- Refactor ValueConstructor and InstrumentationContextAwareValueConstructor +class InstrumentationContextAwareValueConstructor( + private val instrumentationContext: InstrumentationContext, + private val idGenerator: StateBeforeAwareIdGenerator, +) { + companion object { + private const val MAX_DYNAMIC_MOCK_DEPTH = 5 + } + + private val classLoader: ClassLoader + get() = utContext.classLoader + + val objectToModelCache: IdentityHashMap + get() { + val objectToModel = IdentityHashMap() + constructedObjects.forEach { (model, obj) -> + objectToModel[obj] = model + } + return objectToModel + } + + val detectedMockingCandidates: MutableSet = mutableSetOf() + + var lastCaughtException: Throwable? = null + private set + + // TODO: JIRA:1379 -- replace UtReferenceModel with Int + private val constructedObjects = HashMap() + + /** + * Controllers contain info about mocked methods and have to be closed to restore initial state. + */ + private val controllers = mutableListOf() + + fun constructMethodParameters(models: List): List> = + models.mapIndexed { _, model -> construct(model) } + + fun constructStatics(staticsBefore: Map): Map> = + staticsBefore.mapValues { (_, model) -> construct(model) } + + /** + * Main construction method. + * + * Takes care of nulls. Does not use cache, instead construct(Object/Array/List/FromAssembleModel) method + * uses cache directly. + * + * Takes mock creation context (possible mock target) to create mock if required. + */ + private fun construct(model: UtModel): UtConcreteValue<*> = + when (model) { + is UtNullModel -> UtConcreteValue(null, model.classId.jClass) + is UtPrimitiveModel -> UtConcreteValue(model.value, model.classId.jClass) + is UtEnumConstantModel -> UtConcreteValue(constructEnum(model)) + is UtClassRefModel -> UtConcreteValue(model.value.jClass) + is UtCompositeModel -> UtConcreteValue(constructObject(model), model.classId.jClass) + is UtArrayModel -> UtConcreteValue(constructArray(model)) + is UtAssembleModel -> UtConcreteValue(constructFromAssembleModel(model), model.classId.jClass) + is UtLambdaModel -> UtConcreteValue(constructFromLambdaModel(model)) + is UtVoidModel -> UtConcreteValue(Unit) + else -> { + instrumentationContext.constructContextDependentValue(model) ?: + if (model is UtCustomModel) + construct(model.origin ?: error("Can't construct value for custom model without origin [$model]")) + else + // PythonModel, JsUtModel may be here + throw UnsupportedOperationException("UtModel $model cannot construct UtConcreteValue") + } + } + + /** + * Use this method if you need to use [construct], while being in a sandbox. + * + * Permission elevation is required, because [construct] heavily uses reflection and Mockito. + */ + private fun constructPrivileged(model: UtModel): UtConcreteValue<*> = + AccessController.doPrivileged(PrivilegedAction { construct(model) }) + + /** + * Constructs an Enum<*> instance by model, uses reference-equality cache. + */ + private fun constructEnum(model: UtEnumConstantModel): Any { + constructedObjects[model]?.let { return it } + constructedObjects[model] = model.value + return model.value + } + + /** + * Constructs object by model, uses reference-equality cache. + * + * Returns null for mock cause cannot instantiate it. + */ + private fun constructObject(model: UtCompositeModel): Any { + constructedObjects[model]?.let { return it } + + val javaClass = javaClass(model.classId) + + val classInstance = if (!model.isMock) { + val notMockInstance = javaClass.anyInstance + + constructedObjects[model] = notMockInstance + notMockInstance + } else { + val concreteValues = model.mocks.mapValues { mutableListOf() } + val mockInstance = generateMockitoMock(javaClass, concreteValues, model) + + constructedObjects[model] = mockInstance + + concreteValues.forEach { (executableId, valuesList) -> + val mockModels = model.mocks.getValue(executableId) + // If model is unit, then null should be returned (this model has to be already constructed). + val constructedValues = mockModels.map { model -> construct(model).value.takeIf { it != Unit } } + valuesList.addAll(constructedValues) + } + + if (model.canHaveRedundantOrMissingMocks) { + // we clear `mocks` to avoid redundant mocks, + // actually useful mocks should be later added back as they are used + model.mocks.clear() + } + + mockInstance + } + + model.fields.forEach { (fieldId, fieldModel) -> + val declaredField = fieldId.jField + val accessible = declaredField.isAccessible + declaredField.isAccessible = true + + check(Reflection.isModifiersAccessible()) + + val value = construct(fieldModel).value + val instance = if (Modifier.isStatic(declaredField.modifiers)) null else classInstance + declaredField.set(instance, value) + declaredField.isAccessible = accessible + } + + return classInstance + } + + private fun generateMockitoAnswer(concreteValues: Map>): Answer<*> { + val pointers = concreteValues.mapValues { (_, _) -> 0 }.toMutableMap() + return Answer { invocation -> + with(invocation.method) { + pointers[executableId].let { pointer -> + concreteValues[executableId].let { values -> + if (pointer != null && values != null && pointer < values.size) { + pointers[executableId] = pointer + 1 + values[pointer] + } else { + invocation.callRealMethod() + } + } + } + } + } + } + + private fun generateMockitoAnswerHandlingRedundantAndMissingMocks( + concreteValues: Map>, + mockModel: UtCompositeModel + ): Answer<*> { + class MockedExecutable( + val executableId: ExecutableId, + val answerValues: List, + val answerModels: List, + ) { + private var pointer: Int = 0 + + fun nextAnswer(): Any? { + val answerValue = answerValues[pointer] + val answerModel = answerModels[pointer] + pointer = (pointer + 1) % answerValues.size + + // Record mock answers into `mockModel.mocks` as these answers are used. + // Avoid recording multiple answers if same answer is reused over and over again. + if (answerValues.size > 1 || executableId !in mockModel.mocks) { + (mockModel.mocks.getOrPut(executableId) { mutableListOf() } as MutableList).add(answerModel) + } + + return answerValue + } + } + + val mockedExecutables = concreteValues.mapValues { (executableId, values) -> + MockedExecutable( + executableId = executableId, + answerValues = values, + answerModels = mockModel.mocks.getValue(executableId), + ) + }.toMutableMap() + + return Answer { invocation -> + with(invocation.method) { + mockedExecutables.getOrPut(executableId) { + detectedMockingCandidates.add(executableId) + var answerModel = generateNewAnswerModel(executableId, dynamicMockModelToDepth[mockModel] ?: 0) + val answerValue = runCatching { constructPrivileged(answerModel) }.getOrElse { + // fallback is used, so we still get some value (null) for types + // that can't be mocked, e.g. arrays and sealed interfaces + answerModel = executableId.returnType.defaultValueModel() + constructPrivileged(answerModel) + } + + MockedExecutable( + executableId = executableId, + // `Unit` is replaced with `null`, because in Java `void` methods actually return `null` + answerValues = listOf(answerValue.value.takeUnless { it == Unit }), + answerModels = listOf(answerModel) + ) + }.nextAnswer() + } + } + } + + private val dynamicMockModelToDepth = mutableMapOf() + + private fun generateNewAnswerModel(methodId: MethodId, depth: Int) = + methodId.returnType.defaultValueModel().takeUnless { it.isNull() } ?: when { + // use `null` to avoid false positive `ClassCastException` + methodId.method.genericReturnType is TypeVariable<*> -> UtNullModel(methodId.returnType) + + // mockito can't mock `String` and `Class` + methodId.returnType == stringClassId -> UtNullModel(stringClassId) + methodId.returnType == classClassId -> UtClassRefModel( + id = idGenerator.createId(), + classId = classClassId, + value = classClassId, + ) + depth > MAX_DYNAMIC_MOCK_DEPTH -> UtNullModel(methodId.classId) + + else -> UtCompositeModel( + id = idGenerator.createId(), + // TODO mockito can't mock sealed interfaces, + // we have to mock their implementations or use null + classId = methodId.returnType, + isMock = true, + canHaveRedundantOrMissingMocks = true, + ).also { dynamicMockModelToDepth[it] = depth + 1 } + } + + private fun generateMockitoMock( + clazz: Class<*>, + concreteValues: Map>, + mockModel: UtCompositeModel + ): Any { + val answer = + if (mockModel.canHaveRedundantOrMissingMocks) { + generateMockitoAnswerHandlingRedundantAndMissingMocks(concreteValues, mockModel) + } else { + generateMockitoAnswer(concreteValues) + } + + return Mockito.mock(clazz, answer) + } + + private fun computeConcreteValuesForMethods( + methodToValues: Map>, + ): Map> = methodToValues.mapValues { (_, models) -> + models.map { mockAndGet(it) } + } + + /** + * Mocks methods on [instance] with supplied [methodToValues]. + * + * Also add new controllers to [controllers]. Each controller corresponds to one method. If it is a static method, then the controller + * must be closed. If it is a non-static method and you don't change the mocks behaviour on the passed instance, + * then the controller doesn't have to be closed + * + * @param [instance] must be non-`null` for non-static methods. + * @param [methodToValues] return values for methods. + */ + private fun mockMethods( + instance: Any?, + methodToValues: Map>, + ) { + controllers += computeConcreteValuesForMethods(methodToValues).map { (method, values) -> + if (method !is MethodId) { + throw IllegalArgumentException("Expected MethodId, but got: $method") + } + MethodMockController( + method.classId.jClass, + method.method, + instance, + values, + instrumentationContext + ) + } + + } + + /** + * Mocks static methods according to instrumentations. + */ + fun mockStaticMethods( + instrumentations: List, + ) { + val methodToValues = instrumentations.associate { it.methodId as ExecutableId to it.values } + mockMethods(null, methodToValues) + } + + /** + * Mocks new instances according to instrumentations + */ + fun mockNewInstances( + instrumentations: List, + ) { + controllers += instrumentations.map { mock -> + InstanceMockController( + mock.classId, + mock.instances.map { mockAndGet(it) }, + mock.callSites.map { Type.getType(it.jClass).internalName }.toSet() + ) + } + } + + /** + * Constructs array by model. + * + * Supports arrays of primitive, arrays of arrays and arrays of objects. + * + * Note: does not check isNull, but if isNull set returns empty array because for null array length set to 0. + */ + private fun constructArray(model: UtArrayModel): Any { + constructedObjects[model]?.let { return it } + + with(model) { + val elementClassId = classId.elementClassId ?: error( + "Provided incorrect UtArrayModel without elementClassId. ClassId: ${model.classId}, model: $model" + ) + return when (elementClassId.jvmName) { + "B" -> ByteArray(length) { primitive(constModel) }.apply { + stores.forEach { (index, model) -> this[index] = primitive(model) } + }.also { constructedObjects[model] = it } + "S" -> ShortArray(length) { primitive(constModel) }.apply { + stores.forEach { (index, model) -> this[index] = primitive(model) } + }.also { constructedObjects[model] = it } + "C" -> CharArray(length) { primitive(constModel) }.apply { + stores.forEach { (index, model) -> this[index] = primitive(model) } + }.also { constructedObjects[model] = it } + "I" -> IntArray(length) { primitive(constModel) }.apply { + stores.forEach { (index, model) -> this[index] = primitive(model) } + }.also { constructedObjects[model] = it } + "J" -> LongArray(length) { primitive(constModel) }.apply { + stores.forEach { (index, model) -> this[index] = primitive(model) } + }.also { constructedObjects[model] = it } + "F" -> FloatArray(length) { primitive(constModel) }.apply { + stores.forEach { (index, model) -> this[index] = primitive(model) } + }.also { constructedObjects[model] = it } + "D" -> DoubleArray(length) { primitive(constModel) }.apply { + stores.forEach { (index, model) -> this[index] = primitive(model) } + }.also { constructedObjects[model] = it } + "Z" -> BooleanArray(length) { primitive(constModel) }.apply { + stores.forEach { (index, model) -> this[index] = primitive(model) } + }.also { constructedObjects[model] = it } + else -> { + val javaClass = javaClass(elementClassId) + val instance = java.lang.reflect.Array.newInstance(javaClass, length) as Array<*> + constructedObjects[model] = instance + for (i in instance.indices) { + val elementModel = stores[i] ?: constModel + val value = construct(elementModel).value + try { + java.lang.reflect.Array.set(instance, i, value) + } catch (iae:IllegalArgumentException) { + throw IllegalArgumentException( + iae.message + " array: ${instance.javaClass.name}; value: ${value?.javaClass?.name}" , iae + ) + } + } + instance + } + } + } + } + + /** + * Constructs object with [UtAssembleModel]. + */ + private fun constructFromAssembleModel(assembleModel: UtAssembleModel): Any { + constructedObjects[assembleModel]?.let { return it } + + val instantiationExecutableCall = assembleModel.instantiationCall + val result = updateWithStatementCallModel(instantiationExecutableCall).getOrThrow() + + // Executions that get `null` in a complicated way (e.g. like this: `new Pair(null, null).getFirst()`) + // are only produced by fuzzer and are considered undesirable because fuzzer can also produce simpler + // executions that just use `null` literal. + // + // So for such executions we throw `IllegalStateException` that indicates that construction of arguments + // has failed and causes execution to terminate with `UtConcreteExecutionProcessedFailure` execution result. + // + // That is also helpful, because it allows to safely use the constructed value where primitive type is expected, + // otherwise some models generated by `FieldValueProvider` can lead to false-positive NPEs. + checkNotNull(result) { + "Tracked instance can't be null for call ${instantiationExecutableCall.statement} in model $assembleModel" + } + constructedObjects[assembleModel] = result + + assembleModel.modificationsChain.forEach { statementModel -> + when (statementModel) { + is UtStatementCallModel -> updateWithStatementCallModel(statementModel) + is UtDirectSetFieldModel -> updateWithDirectSetFieldModel(statementModel) + } + } + + return constructedObjects[assembleModel] ?: error("Can't assemble model: $assembleModel") + } + + private fun constructFromLambdaModel(lambdaModel: UtLambdaModel): Any { + constructedObjects[lambdaModel]?.let { return it } + // A class representing a functional interface. + val samType: Class<*> = lambdaModel.samType.jClass + // A class where the lambda is declared. + val declaringClass: Class<*> = lambdaModel.declaringClass.jClass + // A name of the synthetic method that represents a lambda. + val lambdaName = lambdaModel.lambdaName + + val lambda = if (lambdaModel.lambdaMethodId.isStatic) { + val capturedArguments = lambdaModel.capturedValues + .map { model -> CapturedArgument(type = model.classId.jClass, value = value(model)) } + .toTypedArray() + constructStaticLambda(samType, declaringClass, lambdaName, *capturedArguments) + } else { + val capturedReceiverModel = lambdaModel.capturedValues.firstOrNull() + ?: error("Non-static lambda must capture `this` instance, so there must be at least one captured value") + + // Values that the given lambda has captured. + val capturedReceiver = value(capturedReceiverModel) + val capturedArguments = lambdaModel.capturedValues.subList(1, lambdaModel.capturedValues.size) + .map { model -> CapturedArgument(type = model.classId.jClass, value = value(model)) } + .toTypedArray() + constructLambda(samType, declaringClass, lambdaName, capturedReceiver, *capturedArguments) + } + constructedObjects[lambdaModel] = lambda + return lambda + } + + /** + * Updates instance state with [callModel] invocation. + * + * @return the result of [callModel] invocation + */ + private fun updateWithStatementCallModel(callModel: UtStatementCallModel): Result<*> = + runCatching { + when (callModel) { + is UtExecutableCallModel -> { + val executable = callModel.executable + val instanceValue = callModel.instance?.let { value(it) } + val params = callModel.params.map { value(it) } + + when (executable) { + is MethodId -> executable.call(params, instanceValue) + is ConstructorId -> executable.call(params) + } + } + + is UtDirectGetFieldModel -> { + val fieldAccess = callModel.fieldAccess + val instanceValue = value(callModel.instance) + + fieldAccess.get(instanceValue) + } + } + } + .also { result -> + result + .exceptionOrNull() + ?.let { + lastCaughtException = it + callModel.thrownConcreteException = it.javaClass.id + } + } + + + /** + * Updates instance with [UtDirectSetFieldModel] execution. + */ + private fun updateWithDirectSetFieldModel(directSetterModel: UtDirectSetFieldModel) { + val instanceModel = directSetterModel.instance + val instance = value(instanceModel) + + val fieldModel = directSetterModel.fieldModel + + val field = directSetterModel.fieldId.jField + val isAccessible = field.isAccessible + + try { + //set field accessible to support protected or package-private direct setters + field.isAccessible = true + + //construct and set the value + val fieldValue = construct(fieldModel).value + field.set(instance, fieldValue) + } finally { + //restore accessibility property of the field + field.isAccessible = isAccessible + } + } + + /** + * Constructs value from [UtModel]. + */ + private fun value(model: UtModel) = construct(model).value + + private fun mockAndGet(model: UtModel): Any? { + return construct(model).value + } + + private fun MethodId.call(args: List, instance: Any?): Any? = + method.runSandbox(bypassesSandbox) { + invokeCatching(obj = instance, args = args).getOrThrow() + } + + private fun ConstructorId.call(args: List): Any? = + constructor.runSandbox(bypassesSandbox) { + newInstance(*args.toTypedArray()) + } + + private fun DirectFieldAccessId.get(instance: Any?): Any? { + val field = fieldId.jField + return field.runSandbox { + field.get(instance) + } + } + + /** + * Fetches primitive value from NutsModel to create array of primitives. + */ + private inline fun primitive(model: UtModel): T = (model as UtPrimitiveModel).value as T + + private fun javaClass(id: ClassId) = kClass(id).java + + private fun kClass(id: ClassId) = + if (id.elementClassId != null) { + arrayClassOf(id.elementClassId!!) + } else { + when (id.jvmName) { + "B" -> Byte::class + "S" -> Short::class + "C" -> Char::class + "I" -> Int::class + "J" -> Long::class + "F" -> Float::class + "D" -> Double::class + "Z" -> Boolean::class + else -> classLoader.loadClass(id.name).kotlin + } + } + + private fun arrayClassOf(elementClassId: ClassId): KClass<*> = + if (elementClassId.elementClassId != null) { + val elementClass = arrayClassOf(elementClassId.elementClassId!!) + java.lang.reflect.Array.newInstance(elementClass.java, 0)::class + } else { + when (elementClassId.jvmName) { + "B" -> ByteArray::class + "S" -> ShortArray::class + "C" -> CharArray::class + "I" -> IntArray::class + "J" -> LongArray::class + "F" -> FloatArray::class + "D" -> DoubleArray::class + "Z" -> BooleanArray::class + else -> { + val elementClass = classLoader.loadClass(elementClassId.name) + java.lang.reflect.Array.newInstance(elementClass, 0)::class + } + } + } + + fun resetMockedMethods() { + controllers.forEach { it.close() } + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/IterableConstructors.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/IterableConstructors.kt new file mode 100644 index 0000000000..45149c94a8 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/IterableConstructors.kt @@ -0,0 +1,71 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.collectionClassId +import org.utbot.framework.plugin.api.util.mapClassId +import org.utbot.framework.plugin.api.util.objectClassId + +internal class CollectionConstructor : UtAssembleModelConstructorBase() { + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, + value: Any + ): List { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + value as java.util.Collection<*> + + // If [value] constructed incorrectly (some inner transient fields are null, etc.) this may fail. + // This value will be constructed as UtCompositeModel. + val models = value.map { internalConstructor.construct(it, valueToClassId(it)) } + + val addMethodId = MethodId(collectionClassId, "add", booleanClassId, listOf(objectClassId)) + + return models.map { UtExecutableCallModel(this, addMethodId, listOf(it)) } + } + + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, + value: Any, + classId: ClassId + ): UtExecutableCallModel = + UtExecutableCallModel( + instance = null, + ConstructorId(classId, emptyList()), + emptyList() + ) +} + +internal class MapConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, + value: Any, + classId: ClassId + ): UtExecutableCallModel = + UtExecutableCallModel( + instance = null, + ConstructorId(classId, emptyList()), + emptyList() + ) + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, + value: Any + ): List { + value as java.util.AbstractMap<*, *> + + val keyToValueModels = value.map { (key, value) -> + internalConstructor.run { construct(key, valueToClassId(key)) to construct(value, valueToClassId(value)) } + } + + val putMethodId = MethodId(mapClassId, "put", objectClassId, listOf(objectClassId, objectClassId)) + + return keyToValueModels.map { (key, value) -> + UtExecutableCallModel(this, putMethodId, listOf(key, value)) + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/JavaStdLibCustomModelConstructors.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/JavaStdLibCustomModelConstructors.kt new file mode 100644 index 0000000000..72419f9c49 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/JavaStdLibCustomModelConstructors.kt @@ -0,0 +1,79 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.primitiveWrappers +import org.utbot.framework.plugin.api.util.voidWrapperClassId + +val javaStdLibModelWithCompositeOriginConstructors: Map, () -> UtModelWithCompositeOriginConstructor> = + mutableMapOf, () -> UtAssembleModelConstructorBase>( + /** + * Optionals + */ + java.util.OptionalInt::class.java to { OptionalIntConstructor() }, + java.util.OptionalLong::class.java to { OptionalLongConstructor() }, + java.util.OptionalDouble::class.java to { OptionalDoubleConstructor() }, + java.util.Optional::class.java to { OptionalConstructor() }, + + /** + * Lists + */ + java.util.LinkedList::class.java to { CollectionConstructor() }, + java.util.ArrayList::class.java to { CollectionConstructor() }, + java.util.AbstractList::class.java to { CollectionConstructor() }, + java.util.List::class.java to { CollectionConstructor() }, + java.util.concurrent.CopyOnWriteArrayList::class.java to { CollectionConstructor() }, + + /** + * Queues, deques + */ + java.util.PriorityQueue::class.java to { CollectionConstructor() }, + java.util.ArrayDeque::class.java to { CollectionConstructor() }, + java.util.concurrent.LinkedBlockingQueue::class.java to { CollectionConstructor() }, + java.util.concurrent.LinkedBlockingDeque::class.java to { CollectionConstructor() }, + java.util.concurrent.ConcurrentLinkedQueue::class.java to { CollectionConstructor() }, + java.util.concurrent.ConcurrentLinkedDeque::class.java to { CollectionConstructor() }, + java.util.Queue::class.java to { CollectionConstructor() }, + java.util.Deque::class.java to { CollectionConstructor() }, + + /** + * Sets + */ + java.util.HashSet::class.java to { CollectionConstructor() }, + java.util.TreeSet::class.java to { CollectionConstructor() }, + java.util.LinkedHashSet::class.java to { CollectionConstructor() }, + java.util.AbstractSet::class.java to { CollectionConstructor() }, + java.util.Set::class.java to { CollectionConstructor() }, + + /** + * Maps + */ + java.util.HashMap::class.java to { MapConstructor() }, + java.util.TreeMap::class.java to { MapConstructor() }, + java.util.LinkedHashMap::class.java to { MapConstructor() }, + java.util.AbstractMap::class.java to { MapConstructor() }, + java.util.concurrent.ConcurrentMap::class.java to { MapConstructor() }, + java.util.concurrent.ConcurrentHashMap::class.java to { MapConstructor() }, + java.util.IdentityHashMap::class.java to { MapConstructor() }, + java.util.WeakHashMap::class.java to { MapConstructor() }, + + /** + * Hashtables + */ + java.util.Hashtable::class.java to { MapConstructor() }, + + /** + * String wrapper + */ + java.lang.String::class.java.let { it to { PrimitiveWrapperConstructor() } }, + + /** + * TODO: JIRA:1405 -- Add assemble constructors for another standard classes as well. + */ + ).apply { + /** + * Primitive wrappers + */ + this += primitiveWrappers + .filter { it != voidWrapperClassId } + .associate { it.jClass to { PrimitiveWrapperConstructor() } } + } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/OptionalConstructors.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/OptionalConstructors.kt similarity index 80% rename from utbot-framework/src/main/kotlin/org/utbot/framework/concrete/OptionalConstructors.kt rename to utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/OptionalConstructors.kt index d8e15a20b2..76b9e87eda 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/OptionalConstructors.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/OptionalConstructors.kt @@ -1,21 +1,20 @@ -package org.utbot.framework.concrete +package org.utbot.instrumentation.instrumentation.execution.constructors +import java.util.Optional +import java.util.OptionalDouble +import java.util.OptionalInt +import java.util.OptionalLong +import kotlin.reflect.KFunction1 import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.MethodId import org.utbot.framework.plugin.api.UtAssembleModel import org.utbot.framework.plugin.api.UtExecutableCallModel -import org.utbot.framework.plugin.api.UtStatementModel import org.utbot.framework.plugin.api.util.doubleClassId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.intClassId import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.longClassId import org.utbot.framework.plugin.api.util.objectClassId -import java.util.Optional -import java.util.OptionalDouble -import java.util.OptionalInt -import java.util.OptionalLong -import kotlin.reflect.KFunction1 internal sealed class OptionalConstructorBase : UtAssembleModelConstructorBase() { @@ -24,36 +23,37 @@ internal sealed class OptionalConstructorBase : UtAssembleModelConstructorBase() abstract val isPresent: KFunction1<*, Boolean> abstract val getter: KFunction1<*, Any> - - private val emptyMethodId by lazy { MethodId(classId, "empty", classId, emptyList()) } - private val ofMethodId by lazy { MethodId(classId, "of", classId, listOf(elementClassId)) } - - final override fun UtAssembleModel.modifyChains( + final override fun provideInstantiationCall( internalConstructor: UtModelConstructorInterface, - instantiationChain: MutableList, - modificationChain: MutableList, - valueToConstructFrom: Any - ) { - require(classId.jClass.isInstance(valueToConstructFrom)) { - "Can't cast $valueToConstructFrom to ${classId.jClass} in $this assemble constructor." + value: Any, + classId: ClassId + ): UtExecutableCallModel { + require(classId.jClass.isInstance(value)) { + "Can't cast $value to ${classId.jClass} in $this assemble constructor." } - modificationChain += if (!isPresent.call(valueToConstructFrom)) { + return if (!isPresent.call(value)) { UtExecutableCallModel( instance = null, emptyMethodId, - emptyList(), - this + emptyList() ) } else { UtExecutableCallModel( instance = null, ofMethodId, - listOf(internalConstructor.construct(getter.call(valueToConstructFrom), elementClassId)), - this + listOf(internalConstructor.construct(getter.call(value), elementClassId)) ) } } + + private val emptyMethodId by lazy { MethodId(classId, "empty", classId, emptyList()) } + private val ofMethodId by lazy { MethodId(classId, "of", classId, listOf(elementClassId)) } + + final override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, + value: Any + ): List = emptyList() } internal class OptionalConstructor : OptionalConstructorBase() { diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/PrimitiveWrapperConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/PrimitiveWrapperConstructor.kt new file mode 100644 index 0000000000..ca73e24295 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/PrimitiveWrapperConstructor.kt @@ -0,0 +1,40 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.util.constructorId +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.primitiveByWrapper +import org.utbot.framework.plugin.api.util.stringClassId + +internal class PrimitiveWrapperConstructor : UtAssembleModelConstructorBase() { + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, + value: Any, + classId: ClassId + ): UtExecutableCallModel { + checkClassCast(classId.jClass, value::class.java) + + return UtExecutableCallModel( + instance = null, + constructorId(classId, classId.unbox()), + listOf(UtPrimitiveModel(value)) + ) + + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, + value: Any + ): List = emptyList() +} + + +private fun ClassId.unbox() = if (this == stringClassId) { + stringClassId +} else { + primitiveByWrapper.getOrElse(this) { error("Unknown primitive wrapper: $this") } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/StateBeforeAwareIdGenerator.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/StateBeforeAwareIdGenerator.kt new file mode 100644 index 0000000000..4192283aec --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/StateBeforeAwareIdGenerator.kt @@ -0,0 +1,26 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper.Companion.collectAllModels +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData +import org.utbot.instrumentation.instrumentation.execution.mapModels + +class StateBeforeAwareIdGenerator(allPreExistingModels: Collection) { + private val seenIds = allPreExistingModels + .filterIsInstance() + .mapNotNull { it.id } + .toMutableSet() + + private var nextId = 0 + + fun createId(): Int { + while (nextId in seenIds) nextId++ + return nextId++ + } + + companion object { + fun fromUtConcreteExecutionData(data: UtConcreteExecutionData): StateBeforeAwareIdGenerator = + StateBeforeAwareIdGenerator(collectAllModels { collector -> data.mapModels(collector) }) + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/StreamConstructors.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/StreamConstructors.kt new file mode 100644 index 0000000000..eeb578433b --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/StreamConstructors.kt @@ -0,0 +1,120 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.plugin.api.util.doubleArrayClassId +import org.utbot.framework.plugin.api.util.doubleStreamClassId +import org.utbot.framework.plugin.api.util.intArrayClassId +import org.utbot.framework.plugin.api.util.intStreamClassId +import org.utbot.framework.plugin.api.util.isPrimitiveWrapper +import org.utbot.framework.plugin.api.util.longArrayClassId +import org.utbot.framework.plugin.api.util.longStreamClassId +import org.utbot.framework.plugin.api.util.methodId +import org.utbot.framework.plugin.api.util.objectArrayClassId +import org.utbot.framework.plugin.api.util.streamClassId + +/** + * Max number of elements in any concrete stream. + */ +private const val STREAM_ELEMENTS_LIMIT: Int = 1_000_000 + +internal abstract class AbstractStreamConstructor( + private val streamClassId: ClassId, + private val elementsClassId: ClassId, +) : UtAssembleModelConstructorBase() { + private val singleElementClassId: ClassId = elementsClassId.elementClassId + ?: error("Stream $streamClassId elements have to be an array but $elementsClassId found") + + private val elementDefaultValueModel: UtModel = singleElementClassId.defaultValueModel() + + override fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, + value: Any, + classId: ClassId, + ): UtExecutableCallModel { + value as java.util.stream.BaseStream<*, *> + + val valueAsArray = value + .iterator() + .asSequence() + .take(STREAM_ELEMENTS_LIMIT) + .toList() + .toTypedArray() + + if (valueAsArray.isEmpty()) { + return UtExecutableCallModel( + instance = null, + executable = emptyMethodId, + params = emptyList() + ) + } + + // If [valueAsArray] constructed incorrectly (some inner transient fields are null, etc.) this may fail. + // This value will be constructed as UtCompositeModel. + val arrayModel = (internalConstructor.construct(valueAsArray, valueToClassId(valueAsArray)) as UtArrayModel) + .copy(classId = elementsClassId, constModel = elementDefaultValueModel) + .apply { stores.replaceAll { _, m -> m.wrapperModelToPrimitiveModel() } } + + return UtExecutableCallModel( + instance = null, + executable = ofMethodId, + params = listOf(arrayModel) + ) + } + + override fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, + value: Any + ): List = emptyList() + + private val emptyMethodId: MethodId = methodId( + classId = this.streamClassId, + name = "empty", + returnType = this.streamClassId, + arguments = emptyArray() + ) + + private val ofMethodId: MethodId = methodId( + classId = this.streamClassId, + name = "of", + returnType = this.streamClassId, + arguments = arrayOf(elementsClassId) // vararg + ) + + /** + * Transforms [this] to [UtPrimitiveModel] if it is an [UtAssembleModel] for the corresponding wrapper + * (primitive int and wrapper Integer, etc.), and throws an error otherwise. + */ + private fun UtModel.wrapperModelToPrimitiveModel(): UtModel { + if (!classId.isPrimitiveWrapper) { + // We do not need to transform classes other than primitive wrappers + return this + } + + require(this !is UtNullModel) { + "Unexpected null value in wrapper for primitive stream ${this@AbstractStreamConstructor}" + } + + require(this is UtAssembleModel) { + "Unexpected not wrapper assemble model $this for value in wrapper " + + "for primitive stream ${this@AbstractStreamConstructor.streamClassId}" + } + + return (instantiationCall.params.firstOrNull() as? UtPrimitiveModel) + ?: error("No primitive value parameter for wrapper constructor $instantiationCall in model $this " + + "in wrapper for primitive stream ${this@AbstractStreamConstructor.streamClassId}") + } +} + +internal class BaseStreamConstructor : AbstractStreamConstructor(streamClassId, objectArrayClassId) +internal class IntStreamConstructor : AbstractStreamConstructor(intStreamClassId, intArrayClassId) +internal class LongStreamConstructor : AbstractStreamConstructor(longStreamClassId, longArrayClassId) +internal class DoubleStreamConstructor : AbstractStreamConstructor(doubleStreamClassId, doubleArrayClassId) diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtAssembleModelConstructors.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtAssembleModelConstructors.kt new file mode 100644 index 0000000000..f6acd29bba --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtAssembleModelConstructors.kt @@ -0,0 +1,53 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import java.util.stream.BaseStream +import java.util.stream.DoubleStream +import java.util.stream.IntStream +import java.util.stream.LongStream +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtStatementModel + +internal fun findStreamConstructor(stream: BaseStream<*, *>): UtAssembleModelConstructorBase = + when (stream) { + is IntStream -> IntStreamConstructor() + is LongStream -> LongStreamConstructor() + is DoubleStream -> DoubleStreamConstructor() + else -> BaseStreamConstructor() + } + +internal abstract class UtAssembleModelConstructorBase : UtModelWithCompositeOriginConstructor { + override fun constructModelWithCompositeOrigin( + internalConstructor: UtModelConstructorInterface, + value: Any, + valueClassId: ClassId, + id: Int?, + saveToCache: (UtModel) -> Unit + ): UtAssembleModel { + val baseName = valueClassId.simpleName.decapitalize() + val instantiationCall = provideInstantiationCall(internalConstructor, value, valueClassId) + return UtAssembleModel(id, valueClassId, nextModelName(baseName), instantiationCall) { + saveToCache(this) + provideModificationChain(internalConstructor, value) + } + } + + protected abstract fun provideInstantiationCall( + internalConstructor: UtModelConstructorInterface, + value: Any, + classId: ClassId + ): UtExecutableCallModel + + protected abstract fun UtAssembleModel.provideModificationChain( + internalConstructor: UtModelConstructorInterface, + value: Any + ): List +} + +internal fun UtAssembleModelConstructorBase.checkClassCast(expected: Class<*>, actual: Class<*>) { + require(expected.isAssignableFrom(actual)) { + "Can't cast $actual to $expected in $this assemble constructor." + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt new file mode 100644 index 0000000000..14f4cb16bf --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelConstructor.kt @@ -0,0 +1,450 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.common.asPathToFile +import org.utbot.common.withAccessibility +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.* +import org.utbot.framework.plugin.api.visible.UtStreamConsumingException +import java.lang.reflect.Modifier +import java.lang.reflect.Proxy +import java.util.* +import java.util.stream.BaseStream + +/** + * Represents common interface for model constructors. + */ +interface UtModelConstructorInterface { + /** + * Constructs a UtModel from a concrete [value] with a specific [classId]. + */ + fun construct(value: Any?, classId: ClassId): UtModel +} + +/** + * Constructs models from concrete values. + * + * Uses reflection to traverse fields recursively ignoring static final fields. Also uses object->constructed model + * reference-equality cache. + * + * @param objectToModelCache cache used for the model construction with respect to stateBefore. For each object, it first + * @param compositeModelStrategy decides whether we should construct a composite model for a certain value or not. + * @param maxDepth determines max depth for composite and assemble model nesting + * searches in [objectToModelCache] for [UtReferenceModel.id]. + */ +class UtModelConstructor( + private val objectToModelCache: IdentityHashMap, + private val idGenerator: StateBeforeAwareIdGenerator, + private val utModelWithCompositeOriginConstructorFinder: (ClassId) -> UtModelWithCompositeOriginConstructor?, + private val compositeModelStrategy: UtCompositeModelStrategy = AlwaysConstructStrategy, + private val maxDepth: Long = DEFAULT_MAX_DEPTH +) : UtModelConstructorInterface { + private val constructedObjects = IdentityHashMap() + + companion object { + const val DEFAULT_MAX_DEPTH = 7L + + fun createOnlyUserClassesConstructor( + pathsToUserClasses: Set, + utModelWithCompositeOriginConstructorFinder: (ClassId) -> UtModelWithCompositeOriginConstructor? + ): UtModelConstructor { + val cache = IdentityHashMap() + val strategy = ConstructOnlyUserClassesOrCachedObjectsStrategy( + pathsToUserClasses, cache + ) + return UtModelConstructor( + objectToModelCache = cache, + idGenerator = StateBeforeAwareIdGenerator(allPreExistingModels = emptySet()), + utModelWithCompositeOriginConstructorFinder = utModelWithCompositeOriginConstructorFinder, + compositeModelStrategy = strategy + ) + } + } + + private fun computeUnusedIdAndUpdate(): Int = idGenerator.createId() + + private fun handleId(value: Any): Int { + return objectToModelCache[value]?.let { (it as? UtReferenceModel)?.id } ?: computeUnusedIdAndUpdate() + } + + private val proxyLambdaSubstring = "$\$Lambda$" + + private fun isProxyLambda(value: Any?): Boolean { + if (value == null) { + return false + } + return proxyLambdaSubstring in value::class.java.name + } + + private fun constructFakeLambda(value: Any, classId: ClassId): UtLambdaModel { + val baseClassName = value::class.java.name.substringBefore(proxyLambdaSubstring) + val baseClass = utContext.classLoader.loadClass(baseClassName).id + return UtLambdaModel.createFake(handleId(value), classId, baseClass) + } + + private fun isProxy(value: Any?): Boolean = + value != null && Proxy.isProxyClass(value::class.java) + + /** + * Using `UtAssembleModel` for dynamic proxies helps to avoid exceptions like + * `java.lang.ClassNotFoundException: jdk.proxy3.$Proxy184` during code generation. + */ + private fun constructProxy(value: Any, classId: ClassId): UtAssembleModel { + val newProxyInstanceExecutableId = java.lang.reflect.Proxy::newProxyInstance.executableId + + // we don't want to construct deep models for invocationHandlers, since they can be quite large + val argsRemainingDepth = 0L + + val classLoader = UtAssembleModel( + id = computeUnusedIdAndUpdate(), + classId = newProxyInstanceExecutableId.parameters[0], + modelName = "systemClassLoader", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = ClassLoader::getSystemClassLoader.executableId, + params = emptyList() + ) + ) + val interfaces = construct( + value::class.java.interfaces, + newProxyInstanceExecutableId.parameters[1], + remainingDepth = argsRemainingDepth + ) + val invocationHandler = construct( + Proxy.getInvocationHandler(value), + newProxyInstanceExecutableId.parameters[2], + remainingDepth = argsRemainingDepth + ) + + return UtAssembleModel( + id = handleId(value), + classId = classId, + modelName = "dynamicProxy", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = newProxyInstanceExecutableId, + params = listOf(classLoader, interfaces, invocationHandler) + ) + ) + } + + /** + * Constructs a UtModel from a concrete [value] with a specific [classId]. The result can be a [UtAssembleModel] + * as well. + * + * Handles cache on stateBefore values. + */ + override fun construct(value: Any?, classId: ClassId): UtModel = + construct(value, classId, maxDepth) + + private fun construct(value: Any?, classId: ClassId, remainingDepth: Long): UtModel { + objectToModelCache[value]?.let { model -> + if (model is UtLambdaModel) { + return model + } + } + if (isProxyLambda(value)) { + return constructFakeLambda(value!!, classId) + } + if (isProxy(value)) { + return constructProxy(value!!, classId) + } + return when (value) { + null -> UtNullModel(classId) + is Unit -> UtVoidModel + is Byte, + is Short, + is Char, + is Int, + is Long, + is Float, + is Double, + is Boolean -> { + if (classId.isPrimitive) UtPrimitiveModel(value) + else constructFromAny(value, classId, remainingDepth) + } + + is ByteArray -> constructFromByteArray(value, remainingDepth) + is ShortArray -> constructFromShortArray(value, remainingDepth) + is CharArray -> constructFromCharArray(value, remainingDepth) + is IntArray -> constructFromIntArray(value, remainingDepth) + is LongArray -> constructFromLongArray(value, remainingDepth) + is FloatArray -> constructFromFloatArray(value, remainingDepth) + is DoubleArray -> constructFromDoubleArray(value, remainingDepth) + is BooleanArray -> constructFromBooleanArray(value, remainingDepth) + is Array<*> -> constructFromArray(value, remainingDepth) + is Enum<*> -> constructFromEnum(value) + is Class<*> -> constructFromClass(value) + is BaseStream<*, *> -> constructFromStream(value) + else -> constructFromAny(value, classId, remainingDepth) + } + } + + fun constructMock(instance: Any, classId: ClassId, mocks: Map>): UtModel = + constructedObjects.getOrElse(instance) { + val utModel = UtCompositeModel( + handleId(instance), + classId, + isMock = true, + mocks = mocks.mapValuesTo(mutableMapOf()) { (method, values) -> + values.map { construct(it, method.returnType) } + } + ) + constructedObjects[instance] = utModel + utModel + } + + // Q: Is there a way to get rid of duplicated code? + + private fun constructFromDoubleArray(array: DoubleArray, remainingDepth: Long): UtModel = + constructedObjects.getOrElse(array) { + val stores = mutableMapOf() + val utModel = + UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0.toDouble()), stores) + constructedObjects[array] = utModel + array.forEachIndexed { idx, value -> + stores[idx] = construct(value, doubleClassId, remainingDepth - 1) + } + utModel + } + + private fun constructFromFloatArray(array: FloatArray, remainingDepth: Long): UtModel = + constructedObjects.getOrElse(array) { + val stores = mutableMapOf() + val utModel = + UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0.toFloat()), stores) + constructedObjects[array] = utModel + array.forEachIndexed { idx, value -> + stores[idx] = construct(value, floatClassId, remainingDepth - 1) + } + utModel + } + + private fun constructFromLongArray(array: LongArray, remainingDepth: Long): UtModel = + constructedObjects.getOrElse(array) { + val stores = mutableMapOf() + val utModel = + UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0.toLong()), stores) + constructedObjects[array] = utModel + array.forEachIndexed { idx, value -> + stores[idx] = construct(value, longClassId, remainingDepth - 1) + } + utModel + } + + private fun constructFromIntArray(array: IntArray, remainingDepth: Long): UtModel = + constructedObjects.getOrElse(array) { + val stores = mutableMapOf() + val utModel = UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0), stores) + constructedObjects[array] = utModel + array.forEachIndexed { idx, value -> + stores[idx] = construct(value, intClassId, remainingDepth - 1) + } + utModel + } + + private fun constructFromCharArray(array: CharArray, remainingDepth: Long): UtModel = + constructedObjects.getOrElse(array) { + val stores = mutableMapOf() + val utModel = + UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0.toChar()), stores) + constructedObjects[array] = utModel + array.forEachIndexed { idx, value -> + stores[idx] = construct(value, charClassId, remainingDepth - 1) + } + utModel + } + + private fun constructFromShortArray(array: ShortArray, remainingDepth: Long): UtModel = + constructedObjects.getOrElse(array) { + val stores = mutableMapOf() + val utModel = + UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0.toShort()), stores) + constructedObjects[array] = utModel + array.forEachIndexed { idx, value -> + stores[idx] = construct(value, shortClassId, remainingDepth - 1) + } + utModel + } + + private fun constructFromByteArray(array: ByteArray, remainingDepth: Long): UtModel = + constructedObjects.getOrElse(array) { + val stores = mutableMapOf() + val utModel = + UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(0.toByte()), stores) + constructedObjects[array] = utModel + array.forEachIndexed { idx, value -> + stores[idx] = construct(value, byteClassId, remainingDepth - 1) + } + utModel + } + + private fun constructFromBooleanArray(array: BooleanArray, remainingDepth: Long): UtModel = + constructedObjects.getOrElse(array) { + val stores = mutableMapOf() + val utModel = + UtArrayModel(handleId(array), array::class.java.id, array.size, UtPrimitiveModel(false), stores) + constructedObjects[array] = utModel + array.forEachIndexed { idx, value -> + stores[idx] = construct(value, booleanClassId, remainingDepth - 1) + } + utModel + } + + private fun constructFromArray(array: Array<*>, remainingDepth: Long): UtModel = + constructedObjects.getOrElse(array) { + val stores = mutableMapOf() + val utModel = + UtArrayModel(handleId(array), array::class.java.id, array.size, UtNullModel(objectClassId), stores) + constructedObjects[array] = utModel + array.forEachIndexed { idx, value -> + stores[idx] = construct(value, objectClassId, remainingDepth - 1) + } + utModel + } + + private fun constructFromEnum(enum: Enum<*>): UtModel = + constructedObjects.getOrElse(enum) { + val utModel = UtEnumConstantModel(handleId(enum), enum::class.java.id, enum) + constructedObjects[enum] = utModel + utModel + } + + private fun constructFromClass(clazz: Class<*>): UtModel = + constructedObjects.getOrElse(clazz) { + val utModel = UtClassRefModel(handleId(clazz), clazz::class.java.id, clazz.id) + constructedObjects[clazz] = utModel + utModel + } + + private fun constructFromStream(stream: BaseStream<*, *>): UtModel = + constructedObjects.getOrElse(stream) { + val streamConstructor = findStreamConstructor(stream) + + try { + streamConstructor.constructModelWithCompositeOrigin(this, stream, valueToClassId(stream), handleId(stream)) { + constructedObjects[stream] = it + } + } catch (e: Exception) { + // An exception occurs during consuming of the stream - + // remove the constructed object and throw this exception as a result + constructedObjects.remove(stream) + throw UtStreamConsumingException(e) + } + } + + /** + * First tries to construct UtAssembleModel. If failure, constructs UtCompositeModel. + */ + private fun constructFromAny(value: Any, classId: ClassId, remainingDepth: Long): UtModel = + constructedObjects.getOrElse(value) { + tryConstructCustomModel(value, remainingDepth) + ?: findEqualValueOfWellKnownType(value) + ?.takeIf { (_, replacementClassId) -> replacementClassId isSubtypeOf classId } + ?.let { (replacement, replacementClassId) -> + // right now replacements only work with `UtAssembleModel` + (tryConstructCustomModel(replacement, remainingDepth) as? UtAssembleModel) + ?.copy(classId = replacementClassId) + } + ?: constructCompositeModel(value, remainingDepth) + } + + private fun findEqualValueOfWellKnownType(value: Any): Pair? = runCatching { + when (value) { + is List<*> -> ArrayList(value) to listClassId + is Set<*> -> LinkedHashSet(value) to setClassId + is Map<*, *> -> LinkedHashMap(value) to mapClassId + else -> null + } + }.getOrNull() + + /** + * Constructs custom UtModel but does it only for predefined list of classes. + * + * Uses runtime class of [value]. + */ + private fun tryConstructCustomModel(value: Any, remainingDepth: Long): UtModel? = + utModelWithCompositeOriginConstructorFinder(value::class.java.id)?.let { modelConstructor -> + try { + modelConstructor.constructModelWithCompositeOrigin( + internalConstructor = this.withMaxDepth(remainingDepth - 1), + value = value, + valueClassId = valueToClassId(value), + id = handleId(value), + ) { + constructedObjects[value] = it + } + } catch (e: Exception) { // If UtAssembleModel constructor failed, we need to remove model and return null + constructedObjects.remove(value) + null + } + } + + /** + * Constructs UtCompositeModel. + * + * Uses runtime javaClass to collect ALL fields, except final static fields, and builds this model recursively. + */ + private fun constructCompositeModel(value: Any, remainingDepth: Long): UtCompositeModel { + // value can be mock only if it was previously constructed from UtCompositeModel + val isMock = objectToModelCache[value]?.isMockModel() ?: false + + val javaClazz = if (isMock) objectToModelCache.getValue(value).classId.jClass else value::class.java + if (remainingDepth <= 0 || !compositeModelStrategy.shouldConstruct(value, javaClazz)) { + return UtCompositeModel( + handleId(value), + javaClazz.id, + isMock, + fields = mutableMapOf() // we don't want to construct any further fields. + ) + } + + val fields = mutableMapOf() + val utModel = UtCompositeModel(handleId(value), javaClazz.id, isMock, fields) + constructedObjects[value] = utModel + generateSequence(javaClazz) { it.superclass }.forEach { clazz -> + val allFields = clazz.declaredFields + allFields + .asSequence() + .filter { !(Modifier.isFinal(it.modifiers) && Modifier.isStatic(it.modifiers)) } // TODO: what about static final fields? + .filterNot { it.fieldId.isInaccessibleViaReflection } + .forEach { it.withAccessibility { fields[it.fieldId] = construct(it.get(value), it.type.id, remainingDepth - 1) } } + } + return utModel + } + + private fun withMaxDepth(newMaxDepth: Long) = object : UtModelConstructorInterface { + override fun construct(value: Any?, classId: ClassId): UtModel = + construct(value, classId, newMaxDepth) + } +} + +/** + * Decides, should we construct a UtCompositeModel from a value or not. + */ +interface UtCompositeModelStrategy { + fun shouldConstruct(value: Any, clazz: Class<*>): Boolean +} + +internal object AlwaysConstructStrategy : UtCompositeModelStrategy { + override fun shouldConstruct(value: Any, clazz: Class<*>): Boolean = true +} + +/** + * This class constructs only user classes or values which are already in [objectToModelCache]. + * + * [objectToModelCache] is a cache which we build in the time of creating concrete values from [UtModel]s. + */ +internal class ConstructOnlyUserClassesOrCachedObjectsStrategy( + private val userDependencyPaths: Set, + private val objectToModelCache: IdentityHashMap +) : UtCompositeModelStrategy { + /** + * Check whether [clazz] is a user class or [value] is in cache. + */ + override fun shouldConstruct(value: Any, clazz: Class<*>): Boolean = + isUserClass(clazz) || value in objectToModelCache + + private fun isUserClass(clazz: Class<*>): Boolean = + clazz.protectionDomain.codeSource?.let { it.location.path.asPathToFile() in userDependencyPaths } ?: false + +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelWithCompositeOriginConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelWithCompositeOriginConstructor.kt new file mode 100644 index 0000000000..034f7a2306 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/UtModelWithCompositeOriginConstructor.kt @@ -0,0 +1,30 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtModelWithCompositeOrigin + +/** + * Responsible for constructing [UtModelWithCompositeOrigin]s of some specific type, that are more human-readable + * when rendered by the code generation compared to [UtCompositeModel]s. + */ +interface UtModelWithCompositeOriginConstructor { + + /** + * @param internalConstructor constructor to use for constructing child models + * (e.g. when [value] is a list, [internalConstructor] is used for constructing list elements) + * @param value object to construct model for + * @param valueClassId [ClassId] to use for constructed model + * @param saveToCache function that should be called on the returned model right after constructing it, + * but before adding any modifications, so [internalConstructor] doesn't have to reconstruct it for every modification + * and recursive values (e.g. list containing itself) are constructed correctly + */ + fun constructModelWithCompositeOrigin( + internalConstructor: UtModelConstructorInterface, + value: Any, + valueClassId: ClassId, + id: Int?, + saveToCache: (UtModel) -> Unit + ): UtModelWithCompositeOrigin +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/Utils.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/Utils.kt new file mode 100644 index 0000000000..e79ba0aefd --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/Utils.kt @@ -0,0 +1,11 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.objectClassId +import java.util.concurrent.atomic.AtomicInteger + +internal fun valueToClassId(value: Any?) = value?.let { it::class.java.id } ?: objectClassId + +val concreteModelId = AtomicInteger() + +fun nextModelName(base: String): String = "${base}_concrete_${concreteModelId.incrementAndGet()}" \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/InstrumentationContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/InstrumentationContext.kt new file mode 100644 index 0000000000..7c95db4dcb --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/InstrumentationContext.kt @@ -0,0 +1,114 @@ +package org.utbot.instrumentation.instrumentation.execution.context + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtConcreteExecutionProcessedFailure +import org.utbot.framework.plugin.api.UtConcreteValue +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtStatementCallModel +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelWithCompositeOriginConstructor +import org.utbot.instrumentation.instrumentation.execution.phases.ExecutionPhase +import org.utbot.instrumentation.instrumentation.execution.phases.ValueConstructionPhase +import java.lang.reflect.Method +import java.util.IdentityHashMap +import org.utbot.instrumentation.instrumentation.mock.computeKeyForMethod + +/** + * Some information, which is fully computed after classes instrumentation. + * + * This information will be used later in `invoke` function to construct values and models. + */ +interface InstrumentationContext { + /** + * Contains unique id for each method, which is required for this method mocking. + */ + val methodSignatureToId: MutableMap + + /** + * Constructs value that is dependent on the context provided by supported frameworks used in project (e.g. Spring). + * Returns `null` if no context dependent value can be constructed for specified [model]. + * + * NOTE! Doesn't attempt to construct context independent values, + * constructing such values is a responsibility of the user of this method. + */ + fun constructContextDependentValue(model: UtModel): UtConcreteValue<*>? + + /** + * Finds [UtModelWithCompositeOriginConstructor] that should be used to + * construct models for instances of specified [class][classId]. + */ + fun findUtModelWithCompositeOriginConstructor(classId: ClassId): UtModelWithCompositeOriginConstructor? + + /** + * Called when [timedOutedPhase] times out. + * This method is executed in the same thread that [timedOutedPhase] was run in. + * Implementor is expected to only perform some clean up operations (e.g. rollback transactions in Spring). + */ + fun onPhaseTimeout(timedOutedPhase: ExecutionPhase) + + /** + * At the very end of the [ValueConstructionPhase], instrumentation context gets to decide what to do + * with last caught [UtStatementCallModel.thrownConcreteException] (it can be caught if the call + * is non-essential for value construction, i.e. it's in [UtAssembleModel.modificationsChain]). + * + * A reasonable implementation may: + * - ignore the [exception] + * - cause phase to terminate with [UtConcreteExecutionProcessedFailure] + */ + fun handleLastCaughtConstructionException(exception: Throwable) + + object MockGetter { + data class MockContainer(private val values: List<*>) { + private var ptr: Int = 0 + fun hasNext(): Boolean = ptr < values.size + fun nextValue(): Any? = values[ptr++] + } + + /** + * Instance -> method -> list of values in the return order + */ + private val mocks = IdentityHashMap>() + private val callSites = HashMap>() + + /** + * Returns possibility of taking mock object of method with supplied [methodSignature] on an [obj] object. + */ + @JvmStatic + fun hasMock(obj: Any?, methodSignature: String): Boolean = + mocks[obj]?.get(methodSignature)?.hasNext() ?: false + + /** + * Returns the next value for mocked method with supplied [methodSignature] on an [obj] object. + * + * This function has only to be called from the instrumented bytecode everytime + * we need a next value for a mocked method. + */ + @JvmStatic + fun getMock(obj: Any?, methodSignature: String): Any? = + mocks[obj]?.get(methodSignature).let { container -> + container ?: error("Can't get mock container for method [$obj\$$methodSignature]") + container.nextValue() + } + + /** + * Returns current callSites for mocking new instance of [instanceType] contains [callSite] or not + */ + @JvmStatic + fun checkCallSite(instanceType: String, callSite: String): Boolean { + return callSites.getOrDefault(instanceType, emptySet()).contains(callSite) + } + + fun updateCallSites(instanceType: String, instanceCallSites: Set) { + callSites[instanceType] = instanceCallSites + } + + fun updateMocks(obj: Any?, methodSignature: String, values: List<*>) { + val methodMocks = mocks.getOrPut(obj) { mutableMapOf() } + methodMocks[methodSignature] = MockContainer(values) + } + + fun updateMocks(obj: Any?, method: Method, values: List<*>) { + updateMocks(obj, computeKeyForMethod(method), values) + } + } +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/SimpleInstrumentationContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/SimpleInstrumentationContext.kt new file mode 100644 index 0000000000..f0d91da558 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/context/SimpleInstrumentationContext.kt @@ -0,0 +1,29 @@ +package org.utbot.instrumentation.instrumentation.execution.context + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtConcreteValue +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelWithCompositeOriginConstructor +import org.utbot.instrumentation.instrumentation.execution.constructors.javaStdLibModelWithCompositeOriginConstructors +import org.utbot.instrumentation.instrumentation.execution.phases.ExecutionPhase + +/** + * Simple instrumentation context, that is used for pure JVM projects without + * any frameworks with special support from UTBot (like Spring) + */ +class SimpleInstrumentationContext : InstrumentationContext { + override val methodSignatureToId = mutableMapOf() + + /** + * There are no context dependent values for pure JVM projects + */ + override fun constructContextDependentValue(model: UtModel): UtConcreteValue<*>? = null + + override fun findUtModelWithCompositeOriginConstructor(classId: ClassId): UtModelWithCompositeOriginConstructor? = + javaStdLibModelWithCompositeOriginConstructors[classId.jClass]?.invoke() + + override fun onPhaseTimeout(timedOutedPhase: ExecutionPhase) = Unit + + override fun handleLastCaughtConstructionException(exception: Throwable) = Unit +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/InstanceMockController.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/mock/InstanceMockController.kt similarity index 79% rename from utbot-framework/src/main/kotlin/org/utbot/framework/concrete/InstanceMockController.kt rename to utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/mock/InstanceMockController.kt index c7f3e214bd..e2952ab112 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/InstanceMockController.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/mock/InstanceMockController.kt @@ -1,8 +1,9 @@ -package org.utbot.framework.concrete +package org.utbot.instrumentation.instrumentation.execution.mock import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.util.jClass import org.objectweb.asm.Type +import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext class InstanceMockController( clazz: ClassId, diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MethodMockController.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/mock/MethodMockController.kt similarity index 75% rename from utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MethodMockController.kt rename to utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/mock/MethodMockController.kt index 48e1a2d564..73af206f5c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/concrete/MethodMockController.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/mock/MethodMockController.kt @@ -1,11 +1,12 @@ -package org.utbot.framework.concrete +package org.utbot.instrumentation.instrumentation.execution.mock -import org.utbot.common.withRemovedFinalModifier -import org.utbot.framework.plugin.api.util.signature -import org.utbot.instrumentation.instrumentation.mock.MockConfig import java.lang.reflect.Field import java.lang.reflect.Method import java.lang.reflect.Modifier +import org.utbot.common.withAccessibility +import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext +import org.utbot.instrumentation.instrumentation.mock.MockConfig +import org.utbot.instrumentation.instrumentation.mock.computeKeyForMethod /** @@ -31,12 +32,13 @@ class MethodMockController( error("$method is an instance method, but instance is null!") } - val id = instrumentationContext.methodSignatureToId[method.signature] + val computedSignature = computeKeyForMethod(method) + val id = instrumentationContext.methodSignatureToId[computedSignature] isMockField = clazz.declaredFields.firstOrNull { it.name == MockConfig.IS_MOCK_FIELD + id } ?: error("No field ${MockConfig.IS_MOCK_FIELD + id} in $clazz") - isMockField.withRemovedFinalModifier { + isMockField.withAccessibility { isMockField.set(instance, true) } @@ -46,7 +48,7 @@ class MethodMockController( } override fun close() { - isMockField.withRemovedFinalModifier { + isMockField.withAccessibility { isMockField.set(instance, false) } } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/mock/MockController.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/mock/MockController.kt new file mode 100644 index 0000000000..a5595c3edf --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/mock/MockController.kt @@ -0,0 +1,5 @@ +package org.utbot.instrumentation.instrumentation.execution.mock + +import java.io.Closeable + +interface MockController : Closeable \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/ndd/NonDeterministicBytecodeInserter.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/ndd/NonDeterministicBytecodeInserter.kt new file mode 100644 index 0000000000..39607565a0 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/ndd/NonDeterministicBytecodeInserter.kt @@ -0,0 +1,85 @@ +package org.utbot.instrumentation.instrumentation.execution.ndd + +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type + +class NonDeterministicBytecodeInserter { + private val internalName = Type.getInternalName(NonDeterministicResultStorage::class.java) + + private fun String.getUnifiedParamsTypes(): List { + val list = mutableListOf() + var readObject = false + for (c in this) { + if (c == '(') { + continue + } + if (c == ')') { + break + } + if (readObject) { + if (c == ';') { + readObject = false + list.add("Ljava/lang/Object;") + } + } else if (c == 'L') { + readObject = true + } else { + list.add(c.toString()) + } + } + + return list + } + + private fun String.unifyTypeDescriptor(): String = + if (startsWith('L')) { + "Ljava/lang/Object;" + } else { + this + } + + private fun String.getReturnType(): String = + substringAfter(')') + + private fun getStoreDescriptor(descriptor: String): String = buildString { + append('(') + append(descriptor.getReturnType().unifyTypeDescriptor()) + append("Ljava/lang/String;)V") + } + + private fun MethodVisitor.invoke(name: String, descriptor: String) { + visitMethodInsn(Opcodes.INVOKESTATIC, internalName, name, descriptor, false) + } + + fun insertAfterNDMethod(mv: MethodVisitor, owner: String, name: String, descriptor: String, isStatic: Boolean) { + mv.visitInsn(Opcodes.DUP) + mv.visitLdcInsn(NonDeterministicResultStorage.makeSignature(owner, name, descriptor)) + mv.invoke(if (isStatic) "storeStatic" else "storeCall", getStoreDescriptor(descriptor)) + } + + fun insertBeforeNDMethod(mv: MethodVisitor, descriptor: String, isStatic: Boolean) { + if (isStatic) { + return + } + + val params = descriptor.getUnifiedParamsTypes() + + params.asReversed().forEach { + mv.invoke("putParameter${it[0]}", "($it)V") + } + + mv.visitInsn(Opcodes.DUP) + mv.invoke("saveInstance", "(Ljava/lang/Object;)V") + + params.forEach { + mv.invoke("peakParameter${it[0]}", "()$it") + } + } + + fun insertAfterNDInstanceConstructor(mv: MethodVisitor, callSite: String) { + mv.visitInsn(Opcodes.DUP) + mv.visitLdcInsn(callSite) + mv.invoke("registerInstance", "(Ljava/lang/Object;Ljava/lang/String;)V") + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/ndd/NonDeterministicClassVisitor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/ndd/NonDeterministicClassVisitor.kt new file mode 100644 index 0000000000..83ac459594 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/ndd/NonDeterministicClassVisitor.kt @@ -0,0 +1,68 @@ +package org.utbot.instrumentation.instrumentation.execution.ndd + +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import org.utbot.instrumentation.Settings + +class NonDeterministicClassVisitor( + classVisitor: ClassVisitor, + private val detector: NonDeterministicDetector +) : ClassVisitor(Settings.ASM_API, classVisitor) { + + private lateinit var currentClass: String + + override fun visit( + version: Int, + access: Int, + name: String, + signature: String?, + superName: String?, + interfaces: Array? + ) { + currentClass = name + super.visit(version, access, name, signature, superName, interfaces) + } + + override fun visitMethod( + access: Int, + name: String, + descriptor: String, + signature: String?, + exceptions: Array? + ): MethodVisitor { + val mv = cv.visitMethod(access, name, descriptor, signature, exceptions) + return object : MethodVisitor(Settings.ASM_API, mv) { + override fun visitMethodInsn( + opcodeAndSource: Int, + owner: String, + name: String, + descriptor: String, + isInterface: Boolean + ) { + if (name == "") { + mv.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface) + if (detector.isNonDeterministicClass(owner)) { + detector.inserter.insertAfterNDInstanceConstructor(mv, currentClass) + } + return + } + + val (isND, isStatic) = if (opcodeAndSource == Opcodes.INVOKESTATIC) { + detector.isNonDeterministicStaticFunction(owner, name, descriptor) to true + } else { + detector.isNonDeterministicClass(owner) to false + } + + if (isND) { + detector.inserter.insertBeforeNDMethod(mv, descriptor, isStatic) + mv.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface) + detector.inserter.insertAfterNDMethod(mv, owner, name, descriptor, isStatic) + } else { + mv.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface) + } + + } + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/ndd/NonDeterministicDetector.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/ndd/NonDeterministicDetector.kt new file mode 100644 index 0000000000..d6ef35c2be --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/ndd/NonDeterministicDetector.kt @@ -0,0 +1,21 @@ +package org.utbot.instrumentation.instrumentation.execution.ndd + +class NonDeterministicDetector { + private val nonDeterministicStaticMethods: HashSet = HashSet() + + private val nonDeterministicClasses: HashSet = buildList { + add("java/util/Random") + add("kotlin/random/Random") + }.toHashSet() + + val inserter = NonDeterministicBytecodeInserter() + + fun isNonDeterministicStaticFunction(owner: String, name: String, descriptor: String): Boolean { + return nonDeterministicStaticMethods.contains("$owner $name$descriptor") + } + + fun isNonDeterministicClass(clazz: String): Boolean { + return nonDeterministicClasses.contains(clazz) + } + +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/ndd/NonDeterministicResultStorage.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/ndd/NonDeterministicResultStorage.kt new file mode 100644 index 0000000000..da612f63cc --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/ndd/NonDeterministicResultStorage.kt @@ -0,0 +1,239 @@ +@file:Suppress("UNUSED") + +package org.utbot.instrumentation.instrumentation.execution.ndd + +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.utContext +import java.util.* + + +object NonDeterministicResultStorage { + + data class NDMethodResult(val signature: String, val result: Any?) + data class NDInstanceInfo(val instanceNumber: Int, val callSite: String) + + private var currentInstance: Any? = null + private val parameters: MutableList = mutableListOf() + + val staticStorage: MutableList = mutableListOf() + val callStorage: IdentityHashMap> = IdentityHashMap() + val ndInstances: IdentityHashMap = IdentityHashMap() + private var nextInstanceNumber = 1 + + fun clear() { + staticStorage.clear() + callStorage.clear() + ndInstances.clear() + nextInstanceNumber = 1 + } + + fun makeSignature(owner: String, name: String, descriptor: String): String { + return "$owner $name$descriptor" + } + + fun signatureToMethod(signature: String): MethodId? { + val sign = signature.split(' ') + val clazz = utContext.classLoader.loadClass( + sign[0].replace('/', '.') + ).id + return clazz.allMethods.find { it.signature == sign[1] } + } + + @JvmStatic + fun registerInstance(instance: Any, callSite: String) { + ndInstances[instance] = NDInstanceInfo(nextInstanceNumber++, callSite) + } + + @JvmStatic + fun saveInstance(instance: Any) { + currentInstance = instance + } + + // putParameter[type](type) + // peakParameter[type](): type + + @JvmStatic + fun putParameterZ(value: Boolean) { + parameters.add(value) + } + + @JvmStatic + fun peakParameterZ(): Boolean { + return parameters.removeLast() as Boolean + } + + @JvmStatic + fun putParameterB(value: Byte) { + parameters.add(value) + } + + @JvmStatic + fun peakParameterB(): Byte { + return parameters.removeLast() as Byte + } + + @JvmStatic + fun putParameterC(value: Char) { + parameters.add(value) + } + + @JvmStatic + fun peakParameterC(): Char { + return parameters.removeLast() as Char + } + + @JvmStatic + fun putParameterS(value: Short) { + parameters.add(value) + } + + @JvmStatic + fun peakParameterS(): Short { + return parameters.removeLast() as Short + } + + @JvmStatic + fun putParameterI(value: Int) { + parameters.add(value) + } + + @JvmStatic + fun peakParameterI(): Int { + return parameters.removeLast() as Int + } + + @JvmStatic + fun putParameterJ(value: Long) { + parameters.add(value) + } + + @JvmStatic + fun peakParameterJ(): Long { + return parameters.removeLast() as Long + } + + @JvmStatic + fun putParameterF(value: Float) { + parameters.add(value) + } + + @JvmStatic + fun peakParameterF(): Float { + return parameters.removeLast() as Float + } + + @JvmStatic + fun putParameterD(value: Double) { + parameters.add(value) + } + + @JvmStatic + fun peakParameterD(): Double { + return parameters.removeLast() as Double + } + + @JvmStatic + fun putParameterL(value: Any?) { + parameters.add(value) + } + + @JvmStatic + fun peakParameterL(): Any? { + return parameters.removeLast() + } + + // storeStatic(type, sign) + + @JvmStatic + fun storeStatic(result: Boolean, signature: String) { + staticStorage.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeStatic(result: Byte, signature: String) { + staticStorage.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeStatic(result: Char, signature: String) { + staticStorage.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeStatic(result: Short, signature: String) { + staticStorage.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeStatic(result: Int, signature: String) { + staticStorage.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeStatic(result: Long, signature: String) { + staticStorage.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeStatic(result: Float, signature: String) { + staticStorage.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeStatic(result: Double, signature: String) { + staticStorage.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeStatic(result: Any?, signature: String) { + staticStorage.add(NDMethodResult(signature, result)) + } + + // storeCall(type, sign) + + @JvmStatic + fun storeCall(result: Boolean, signature: String) { + callStorage.getOrPut(currentInstance) { mutableListOf() }.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeCall(result: Byte, signature: String) { + callStorage.getOrPut(currentInstance) { mutableListOf() }.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeCall(result: Char, signature: String) { + callStorage.getOrPut(currentInstance) { mutableListOf() }.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeCall(result: Short, signature: String) { + callStorage.getOrPut(currentInstance) { mutableListOf() }.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeCall(result: Int, signature: String) { + callStorage.getOrPut(currentInstance) { mutableListOf() }.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeCall(result: Long, signature: String) { + callStorage.getOrPut(currentInstance) { mutableListOf() }.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeCall(result: Float, signature: String) { + callStorage.getOrPut(currentInstance) { mutableListOf() }.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeCall(result: Double, signature: String) { + callStorage.getOrPut(currentInstance) { mutableListOf() }.add(NDMethodResult(signature, result)) + } + + @JvmStatic + fun storeCall(result: Any?, signature: String) { + callStorage.getOrPut(currentInstance) { mutableListOf() }.add(NDMethodResult(signature, result)) + } +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ExecutionPhase.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ExecutionPhase.kt new file mode 100644 index 0000000000..b9d846b0cf --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ExecutionPhase.kt @@ -0,0 +1,36 @@ +package org.utbot.instrumentation.instrumentation.execution.phases + +import com.jetbrains.rd.util.getLogger +import org.utbot.common.measureTime +import org.utbot.instrumentation.instrumentation.execution.PreliminaryUtConcreteExecutionResult +import org.utbot.rd.loggers.debug + +private val logger = getLogger() + +abstract class ExecutionPhaseException(override val message: String) : Exception() + +// Execution will be stopped, exception will be thrown in engine process +class ExecutionPhaseError(phase: String, override val cause: Throwable) : ExecutionPhaseException(phase) + +// Execution will be stopped, but considered successful, result will be returned +class ExecutionPhaseStop(phase: String, val result: PreliminaryUtConcreteExecutionResult) : ExecutionPhaseException(phase) + +interface ExecutionPhase { + fun wrapError(e: Throwable): ExecutionPhaseException +} + +fun T.start(block: T.() -> R): R = + try { + logger.debug().measureTime({ this.javaClass.simpleName } ) { + this.block() + } + } catch (e: ExecutionPhaseStop) { + throw e + } catch (e: Throwable) { + throw this.wrapError(e) + } + +abstract class ExecutionPhaseFailingOnAnyException : ExecutionPhase { + override fun wrapError(e: Throwable): ExecutionPhaseException = + ExecutionPhaseError(this::class.java.simpleName, e) +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/InvocationPhase.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/InvocationPhase.kt new file mode 100644 index 0000000000..0338d7ad50 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/InvocationPhase.kt @@ -0,0 +1,40 @@ +package org.utbot.instrumentation.instrumentation.execution.phases + +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.MissingState +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.framework.plugin.api.UtTimeoutException +import org.utbot.instrumentation.instrumentation.Instrumentation +import org.utbot.instrumentation.instrumentation.execution.PreliminaryUtConcreteExecutionResult + + +/** + * This phase is about invoking user's code using [delegateInstrumentation]. + */ +class InvocationPhase( + private val delegateInstrumentation: Instrumentation> +) : ExecutionPhase { + + override fun wrapError(e: Throwable): ExecutionPhaseException { + val message = this.javaClass.simpleName + return when (e) { + is TimeoutException -> ExecutionPhaseStop( + message, + PreliminaryUtConcreteExecutionResult( + stateAfter = MissingState, + result = UtTimeoutException(e), + coverage = Coverage() + ) + ) + + else -> ExecutionPhaseError(message, e) + } + } + + + fun invoke( + clazz: Class<*>, + methodSignature: String, + params: List, + ): Result<*> = delegateInstrumentation.invoke(clazz, methodSignature, params) +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionPhase.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionPhase.kt new file mode 100644 index 0000000000..0620fd67dc --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionPhase.kt @@ -0,0 +1,170 @@ +package org.utbot.instrumentation.instrumentation.execution.phases + +import org.utbot.common.withAccessibility +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jField +import org.utbot.framework.plugin.api.visible.UtStreamConsumingException +import org.utbot.instrumentation.instrumentation.et.ExplicitThrowInstruction +import org.utbot.instrumentation.instrumentation.et.TraceHandler +import org.utbot.instrumentation.instrumentation.execution.PreliminaryUtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.constructors.StateBeforeAwareIdGenerator +import org.utbot.instrumentation.instrumentation.execution.constructors.UtCompositeModelStrategy +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelWithCompositeOriginConstructor +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor +import java.security.AccessControlException +import java.util.* + +/** + * This phase of model construction from concrete values. + */ +class ModelConstructionPhase( + private val traceHandler: TraceHandler, + private val utModelWithCompositeOriginConstructorFinder: (ClassId) -> UtModelWithCompositeOriginConstructor?, + private val idGenerator: StateBeforeAwareIdGenerator, +) : ExecutionPhase { + + override fun wrapError(e: Throwable): ExecutionPhaseException { + val message = this.javaClass.simpleName + return when (e) { + is TimeoutException -> ExecutionPhaseStop( + message, + PreliminaryUtConcreteExecutionResult( + stateAfter = MissingState, + result = UtTimeoutException(e), + coverage = Coverage() + ) + ) + + else -> ExecutionPhaseError(message, e) + } + } + + private val constructorConfiguration = ConstructorConfiguration() + private lateinit var constructor: UtModelConstructor + + class ConstructorConfiguration { + lateinit var cache: IdentityHashMap + lateinit var strategy: UtCompositeModelStrategy + var maxDepth: Long = UtModelConstructor.DEFAULT_MAX_DEPTH + } + + fun preconfigureConstructor(block: ConstructorConfiguration.() -> Unit) { + constructorConfiguration.block() + } + + fun configureConstructor(block: ConstructorConfiguration.() -> Unit) { + constructorConfiguration.run { + block() + constructor = UtModelConstructor( + objectToModelCache = cache, + utModelWithCompositeOriginConstructorFinder = utModelWithCompositeOriginConstructorFinder, + compositeModelStrategy = strategy, + idGenerator = idGenerator, + maxDepth = maxDepth, + ) + } + } + + fun mergeInstrumentations( + oldInstrumentations: List, + statics: List, + news: List + ): List = mutableListOf().apply { + val method2Static = statics.associateBy { it.methodId } + val class2New = news.associateBy { it.classId } + + addAll(oldInstrumentations.filterNot { + when (it) { + is UtStaticMethodInstrumentation -> method2Static.contains(it.methodId) + is UtNewInstanceInstrumentation -> class2New.contains(it.classId) + } + }) + addAll(statics) + addAll(news) + } + + fun constructStaticInstrumentation(statics: Map>): List = + statics.map { (method, values) -> + UtStaticMethodInstrumentation(method, values.map { constructor.construct(it, method.returnType) }) + } + + fun constructNewInstrumentation( + news: Map, Set>>, + calls: IdentityHashMap>>, + ): List = news.map { (classId, info) -> + val models = info.first.map { instance -> + constructor.constructMock(instance, classId, calls[instance] ?: emptyMap()) + } + + UtNewInstanceInstrumentation(classId, models, info.second) + } + + fun constructParameters(params: List>): List = + params.map { + constructor.construct(it.value, it.clazz.id) + } + + fun constructStatics( + stateBefore: EnvironmentModels, + staticFields: Map> + ): Map = + staticFields.keys.associateWith { fieldId -> + fieldId.jField.run { + val computedValue = withAccessibility { get(null) } + val knownModel = stateBefore.statics[fieldId] + val knownValue = staticFields[fieldId]?.value + if (knownModel != null && knownValue != null && knownValue == computedValue) { + knownModel + } else { + constructor.construct(computedValue, fieldId.type) + } + } + } + + fun convertToExecutionResult(concreteResult: Result<*>, returnClassId: ClassId): UtExecutionResult { + val result = concreteResult.fold({ + try { + val model = constructor.construct(it, returnClassId) + UtExecutionSuccess(model) + } catch (e: Exception) { + processExceptionDuringModelConstruction(e) + } + }) { + sortOutException(it) + } + return result + } + + private fun sortOutException(exception: Throwable): UtExecutionFailure { + if (exception is TimeoutException) { + return UtTimeoutException(exception) + } + if (exception is AccessControlException || + exception is ExceptionInInitializerError && exception.exception is AccessControlException + ) { + return UtSandboxFailure(exception) + } + // there also can be other cases, when we need to wrap internal exception... I suggest adding them on demand + + val instrs = traceHandler.computeInstructionList() + val isNested = if (instrs.isEmpty()) { + false + } else { + instrs.first().callId != instrs.last().callId + } + return if (instrs.isNotEmpty() && instrs.last().instructionData is ExplicitThrowInstruction) { + UtExplicitlyThrownException(exception, isNested) + } else { + UtImplicitlyThrownException(exception, isNested) + } + + } + + private fun processExceptionDuringModelConstruction(e: Exception): UtExecutionResult = + when (e) { + is UtStreamConsumingException -> UtStreamConsumingFailure(e) + else -> throw e + } + +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt new file mode 100644 index 0000000000..4138d4882e --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt @@ -0,0 +1,132 @@ +package org.utbot.instrumentation.instrumentation.execution.phases + +import com.jetbrains.rd.util.getLogger +import org.utbot.common.StopWatch +import org.utbot.common.ThreadBasedExecutor +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.MissingState +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.framework.plugin.api.UtSandboxFailure +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.instrumentation.instrumentation.Instrumentation +import org.utbot.instrumentation.instrumentation.et.TraceHandler +import org.utbot.instrumentation.instrumentation.execution.PreliminaryUtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData +import org.utbot.instrumentation.instrumentation.execution.constructors.StateBeforeAwareIdGenerator +import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext +import java.security.AccessControlException + +class PhasesController( + private val instrumentationContext: InstrumentationContext, + traceHandler: TraceHandler, + delegateInstrumentation: Instrumentation>, + private val timeout: Long, + idGenerator: StateBeforeAwareIdGenerator, +) { + private var currentlyElapsed = 0L + val valueConstructionPhase = ValueConstructionPhase( + instrumentationContext, + idGenerator, + ) + + val preparationPhase = PreparationPhase(traceHandler) + + val invocationPhase = InvocationPhase(delegateInstrumentation) + + val statisticsCollectionPhase = StatisticsCollectionPhase(traceHandler) + + val modelConstructionPhase = ModelConstructionPhase( + traceHandler = traceHandler, + utModelWithCompositeOriginConstructorFinder = instrumentationContext::findUtModelWithCompositeOriginConstructor, + idGenerator = idGenerator, + ) + + val postprocessingPhase = PostprocessingPhase() + + inline fun computeConcreteExecutionResult(block: PhasesController.() -> PreliminaryUtConcreteExecutionResult): PreliminaryUtConcreteExecutionResult { + try { + return this.block() + } catch (e: ExecutionPhaseStop) { + return e.result + } catch (e: ExecutionPhaseError) { + if (e.cause.cause is AccessControlException) { + return PreliminaryUtConcreteExecutionResult( + stateAfter = MissingState, + result = UtSandboxFailure(e.cause.cause!!), + coverage = Coverage() + ) + } + + throw e + } + } + + companion object { + private val logger = getLogger() + } + + fun executePhaseInTimeout(phase: R, block: R.() -> T): T = phase.start { + val stopWatch = StopWatch() + val context = UtContext(utContext.classLoader, stopWatch) + val timeoutForCurrentPhase = timeout - currentlyElapsed + val executor = ThreadBasedExecutor.threadLocal + val result = executor.invokeWithTimeout(timeout - currentlyElapsed, stopWatch) { + withUtContext(context) { + try { + phase.block() + } finally { + executor.runCleanUpIfTimedOut { + instrumentationContext.onPhaseTimeout(phase) + } + } + } + } ?: throw TimeoutException("Timeout $timeoutForCurrentPhase ms for phase ${phase.javaClass.simpleName} elapsed, controller timeout - $timeout") + + val blockElapsed = stopWatch.get() + currentlyElapsed += blockElapsed + + return@start result.getOrThrow() as T + } + + fun executePhaseWithoutTimeout(phase: R, block: R.() -> T): T = phase.start { + return@start ThreadBasedExecutor.threadLocal.invokeWithoutTimeout { + phase.block() + }.getOrThrow() as T + } + + fun applyPreprocessing(parameters: UtConcreteExecutionData): ConstructedData { + + val constructedData = executePhaseInTimeout(valueConstructionPhase) { + val params = constructParameters(parameters.stateBefore) + val statics = constructStatics(parameters.stateBefore) + + // here static methods and instances are mocked + mock(parameters.instrumentation) + + lastCaughtException?.let { instrumentationContext.handleLastCaughtConstructionException(it) } + + ConstructedData(params, statics, getCache()) + } + + // invariants: + // 1. phase must always complete if started as static reset relies on it + // 2. phase must be fast as there are no incremental changes + postprocessingPhase.setStaticFields(preparationPhase.start { + val result = setStaticFields(constructedData.statics) + resetTrace() + resetND() + result + }) + + return constructedData + } + + fun applyPostprocessing() { + postprocessingPhase.start { + resetStaticFields() + valueConstructionPhase.resetMockMethods() + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PostprocessingPhase.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PostprocessingPhase.kt new file mode 100644 index 0000000000..b94b25fe1d --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PostprocessingPhase.kt @@ -0,0 +1,31 @@ +package org.utbot.instrumentation.instrumentation.execution.phases + +import org.utbot.common.withAccessibility +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.util.jField + + +/** + * The responsibility of this phase is resetting environment to the initial state. + */ +class PostprocessingPhase : ExecutionPhase { + + private var savedStaticsInstance: Map? = null + + fun setStaticFields(savedStatics: Map) { + savedStaticsInstance = savedStatics + } + + override fun wrapError(e: Throwable): ExecutionPhaseException = ExecutionPhaseError(this.javaClass.simpleName, e) + + fun resetStaticFields() { + savedStaticsInstance?.forEach { (fieldId, value) -> + fieldId.jField.run { + withAccessibility { + set(null, value) + } + } + } + } + +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PreparationPhase.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PreparationPhase.kt new file mode 100644 index 0000000000..a733192fd5 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PreparationPhase.kt @@ -0,0 +1,42 @@ +package org.utbot.instrumentation.instrumentation.execution.phases + +import org.utbot.common.withAccessibility +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtConcreteValue +import org.utbot.framework.plugin.api.util.jField +import org.utbot.instrumentation.instrumentation.et.TraceHandler +import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicResultStorage + + +/** + * The responsibility of this phase is environment preparation before execution. + */ +class PreparationPhase( + private val traceHandler: TraceHandler +) : ExecutionPhase { + + override fun wrapError(e: Throwable): ExecutionPhaseException = + ExecutionPhaseError(this.javaClass.simpleName, e) + + fun setStaticFields(staticFieldsValues: Map>): Map { + val savedStaticFields = mutableMapOf() + staticFieldsValues.forEach { (fieldId, value) -> + fieldId.jField.run { + withAccessibility { + savedStaticFields[fieldId] = get(null) + set(null, value.value) + } + } + } + return savedStaticFields + } + + fun resetTrace() { + traceHandler.resetTrace() + } + + fun resetND() { + NonDeterministicResultStorage.clear() + } + +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/StatisticsCollectionPhase.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/StatisticsCollectionPhase.kt new file mode 100644 index 0000000000..1d75d32663 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/StatisticsCollectionPhase.kt @@ -0,0 +1,87 @@ +package org.utbot.instrumentation.instrumentation.execution.phases + +import org.objectweb.asm.Type +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.instrumentation.instrumentation.et.EtInstruction +import org.utbot.instrumentation.instrumentation.et.TraceHandler +import org.utbot.instrumentation.instrumentation.execution.PreliminaryUtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.ndd.NonDeterministicResultStorage +import java.util.* + +/** + * This phase is about collection statistics such as coverage. + */ +class StatisticsCollectionPhase( + private val traceHandler: TraceHandler +) : ExecutionPhase { + + override fun wrapError(e: Throwable): ExecutionPhaseException { + val message = this.javaClass.simpleName + return when (e) { + is TimeoutException -> ExecutionPhaseStop( + message, + PreliminaryUtConcreteExecutionResult( + stateAfter = MissingState, + result = UtTimeoutException(e), + coverage = Coverage() + ) + ) + + else -> ExecutionPhaseError(message, e) + } + } + + data class NDResults( + val statics: Map>, + val news: Map, Set>>, + val calls: IdentityHashMap>> + ) + + fun getNonDeterministicResults(): NDResults { + val storage = NonDeterministicResultStorage + + val statics = storage.staticStorage + .groupBy { storage.signatureToMethod(it.signature)!! } + .mapValues { (_, values) -> values.map { it.result } } + + val news = storage.ndInstances.entries + .groupBy { it.key.javaClass.id } + .mapValues { (_, entries) -> + val values = entries.sortedBy { it.value.instanceNumber }.map { it.key } + val callSites = entries.map { + utContext.classLoader.loadClass(it.value.callSite.replace('/', '.')).id + }.toSet() + values to callSites + } + + val calls = storage.callStorage + .mapValuesTo(IdentityHashMap()) { (_, methodResults) -> + methodResults + .groupBy { storage.signatureToMethod(it.signature)!! } + .mapValues { (_, values) -> values.map { it.result } } + } + + return NDResults(statics, news, calls) + } + + fun getCoverage(clazz: Class<*>): Coverage { + return traceHandler + .computeInstructionList() + .toApiCoverage( + traceHandler.processingStorage.getInstructionsCount( + Type.getInternalName(clazz) + ) + ) + } + + /** + * Transforms a list of internal [EtInstruction]s to a list of api [Instruction]s. + */ + private fun List.toApiCoverage(instructionsCount: Long? = null): Coverage = + Coverage( + map { Instruction(it.className, it.methodSignature, it.line, it.id) }, + instructionsCount + ) +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionPhase.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionPhase.kt new file mode 100644 index 0000000000..aa71548fd4 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionPhase.kt @@ -0,0 +1,80 @@ +package org.utbot.instrumentation.instrumentation.execution.phases + +import org.utbot.framework.plugin.api.* +import java.util.IdentityHashMap +import org.utbot.instrumentation.instrumentation.execution.constructors.InstrumentationContextAwareValueConstructor +import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext +import org.utbot.framework.plugin.api.util.isInaccessibleViaReflection +import org.utbot.instrumentation.instrumentation.execution.PreliminaryUtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.constructors.StateBeforeAwareIdGenerator + +typealias ConstructedParameters = List> +typealias ConstructedStatics = Map> +typealias ConstructedCache = IdentityHashMap + +data class ConstructedData( + val params: ConstructedParameters, + val statics: ConstructedStatics, + val cache: ConstructedCache, +) + +/** + * This phase of values instantiation from given models. + */ +class ValueConstructionPhase( + instrumentationContext: InstrumentationContext, + idGenerator: StateBeforeAwareIdGenerator, +) : ExecutionPhase { + + override fun wrapError(e: Throwable): ExecutionPhaseException = ExecutionPhaseStop( + phase = this.javaClass.simpleName, + result = PreliminaryUtConcreteExecutionResult( + stateAfter = MissingState, + result = when(e) { + is TimeoutException -> UtTimeoutException(e) + else -> UtConcreteExecutionProcessedFailure(e) + }, + coverage = Coverage() + ) + ) + + private val constructor = InstrumentationContextAwareValueConstructor( + instrumentationContext, + idGenerator, + ) + val detectedMockingCandidates: Set get() = constructor.detectedMockingCandidates + val lastCaughtException: Throwable? get() = constructor.lastCaughtException + + fun getCache(): ConstructedCache { + return constructor.objectToModelCache + } + + fun constructParameters(state: EnvironmentModels): ConstructedParameters { + val parametersModels = listOfNotNull(state.thisInstance) + state.parameters + return constructor.constructMethodParameters(parametersModels) + } + + fun constructStatics(state: EnvironmentModels): ConstructedStatics = + constructor.constructStatics( + state.statics.filterKeys { !it.isInaccessibleViaReflection } + ) + + fun mock(instrumentations: List) { + mockStaticMethods(instrumentations) + mockNewInstances(instrumentations) + } + + private fun mockStaticMethods(instrumentations: List) { + val staticMethodsInstrumentation = instrumentations.filterIsInstance() + constructor.mockStaticMethods(staticMethodsInstrumentation) + } + + private fun mockNewInstances(instrumentations: List) { + val newInstanceInstrumentation = instrumentations.filterIsInstance() + constructor.mockNewInstances(newInstanceInstrumentation) + } + + fun resetMockMethods() { + constructor.resetMockedMethods() + } +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/instrumenter/Instrumenter.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/instrumenter/Instrumenter.kt index e29bdceaa1..dfb403e0ac 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/instrumenter/Instrumenter.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/instrumenter/Instrumenter.kt @@ -1,28 +1,14 @@ package org.utbot.instrumentation.instrumentation.instrumenter -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.instrumentation.Settings -import org.utbot.instrumentation.instrumentation.instrumenter.visitors.MethodToProbesVisitor -import org.utbot.instrumentation.instrumentation.instrumenter.visitors.util.AddFieldAdapter -import org.utbot.instrumentation.instrumentation.instrumenter.visitors.util.AddStaticFieldAdapter -import org.utbot.instrumentation.instrumentation.instrumenter.visitors.util.IInstructionVisitor -import org.utbot.instrumentation.instrumentation.instrumenter.visitors.util.InstanceFieldInitializer -import org.utbot.instrumentation.instrumentation.instrumenter.visitors.util.InstructionVisitorAdapter -import org.utbot.instrumentation.instrumentation.instrumenter.visitors.util.StaticFieldInitializer -import java.io.File -import java.io.IOException -import java.io.InputStream -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.reflect.KFunction -import kotlin.reflect.jvm.javaMethod import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.ClassWriter import org.objectweb.asm.Opcodes -import org.objectweb.asm.Type -import org.objectweb.asm.tree.ClassNode +import org.utbot.instrumentation.instrumentation.instrumenter.visitors.MethodToProbesVisitor +import org.utbot.instrumentation.instrumentation.instrumenter.visitors.util.* +import org.utbot.instrumentation.process.HandlerClassesLoader +import java.io.IOException +import java.io.InputStream // TODO: handle with flags EXPAND_FRAMES, etc. @@ -38,7 +24,7 @@ class Instrumenter(classByteCode: ByteArray, val classLoader: ClassLoader? = nul var classByteCode: ByteArray = classByteCode.clone() private set - constructor(clazz: Class<*>) : this(computeClassBytecode(clazz)) + constructor(clazz: Class<*>) : this(adapter.computeClassBytecode(clazz)) fun visitClass(classVisitorBuilder: ClassVisitorBuilder): T { val reader = ClassReader(classByteCode) @@ -76,68 +62,7 @@ class Instrumenter(classByteCode: ByteArray, val classLoader: ClassLoader? = nul } companion object { - private fun computeClassBytecode(clazz: Class<*>): ByteArray { - val reader = - ClassReader(clazz.classLoader.getResourceAsStream(Type.getInternalName(clazz) + ".class")) - val writer = ClassWriter(reader, 0) - reader.accept(writer, 0) - return writer.toByteArray() - } - - private fun findByteClass(className: String): ClassReader? { - val path = className.replace(".", File.separator) + ".class" - return try { - val classReader = UtContext.currentContext()?.classLoader?.getResourceAsStream(path) - ?.readBytes() - ?.let { ClassReader(it) } - ?: ClassReader(className) - classReader - } catch (e: IOException) { - //TODO: SAT-1222 - null - } - } - - // TODO: move the following methods to another file - private fun computeSourceFileName(className: String): String? { - val classReader = findByteClass(className) - val sourceFileAdapter = ClassNode(Settings.ASM_API) - classReader?.accept(sourceFileAdapter, 0) - return sourceFileAdapter.sourceFile - } - - fun computeSourceFileName(clazz: Class<*>): String? { - return computeSourceFileName(clazz.name) - } - - fun computeSourceFileByMethod(method: KFunction<*>, directoryToSearchRecursively: Path = Paths.get("")): File? = - method.javaMethod?.declaringClass?.let { - computeSourceFileByClass(it, directoryToSearchRecursively) - } - - fun computeSourceFileByClass( - className: String, - packageName: String?, - directoryToSearchRecursively: Path = Paths.get("") - ): File? { - val sourceFileName = computeSourceFileName(className) ?: return null - val files = - Files.walk(directoryToSearchRecursively).filter { it.toFile().isFile && it.endsWith(sourceFileName) } - var fileWithoutPackage: File? = null - val pathWithPackage = packageName?.let { Paths.get(it, sourceFileName) } - for (f in files) { - if (pathWithPackage == null || f.endsWith(pathWithPackage)) { - return f.toFile() - } - fileWithoutPackage = f.toFile() - } - return fileWithoutPackage - } - - fun computeSourceFileByClass(clazz: Class<*>, directoryToSearchRecursively: Path = Paths.get("")): File? { - val packageName = clazz.`package`?.name?.replace('.', File.separatorChar) - return computeSourceFileByClass(clazz.name, packageName, directoryToSearchRecursively) - } + var adapter = InstrumenterAdapter() } } @@ -155,7 +80,7 @@ private class TunedClassWriter( flags: Int ) : ClassWriter(reader, flags) { override fun getClassLoader(): ClassLoader { - return UtContext.currentContext()?.classLoader ?: this::class.java.classLoader + return HandlerClassesLoader } override fun getCommonSuperClass(type1: String, type2: String): String { try { @@ -278,8 +203,9 @@ private class TunedClassWriter( */ @Throws(IOException::class) private fun typeInfo(type: String): ClassReader { - val `is`: InputStream = classLoader.getResourceAsStream("$type.class") - ?: error("Can't find resource for class: $type.class") + val `is`: InputStream = requireNotNull(classLoader.getResourceAsStream("$type.class")) { + "Can't find resource for class: $type.class" + } return `is`.use { ClassReader(it) } } } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/instrumenter/InstrumenterAdapter.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/instrumenter/InstrumenterAdapter.kt new file mode 100644 index 0000000000..1a8ff6cba3 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/instrumenter/InstrumenterAdapter.kt @@ -0,0 +1,76 @@ +package org.utbot.instrumentation.instrumentation.instrumenter + +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.Type +import org.objectweb.asm.tree.ClassNode +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.instrumentation.Settings +import java.io.File +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.reflect.KFunction +import kotlin.reflect.jvm.javaMethod + + +open class InstrumenterAdapter { + fun computeClassBytecode(clazz: Class<*>): ByteArray { + val reader = ClassReader(clazz.classLoader.getResourceAsStream(Type.getInternalName(clazz) + ".class")) + val writer = ClassWriter(reader, 0) + reader.accept(writer, 0) + return writer.toByteArray() + } + + private fun findByteClass(className: String): ClassReader? { + val path = className.replace(".", File.separator) + ".class" + return try { + val classReader = UtContext.currentContext()?.classLoader?.getResourceAsStream(path)?.readBytes() + ?.let { ClassReader(it) } ?: ClassReader(className) + classReader + } catch (e: IOException) { + //TODO: SAT-1222 + null + } + } + + // TODO: move the following methods to another file + private fun computeSourceFileName(className: String): String? { + val classReader = findByteClass(className) + val sourceFileAdapter = ClassNode(Settings.ASM_API) + classReader?.accept(sourceFileAdapter, 0) + return sourceFileAdapter.sourceFile + } + + fun computeSourceFileName(clazz: Class<*>): String? { + return computeSourceFileName(clazz.name) + } + + fun computeSourceFileByMethod(method: KFunction<*>, directoryToSearchRecursively: Path = Paths.get("")): File? = + method.javaMethod?.declaringClass?.let { + computeSourceFileByClass(it, directoryToSearchRecursively) + } + + fun computeSourceFileByNameAndPackage( + className: String, packageName: String?, directoryToSearchRecursively: Path + ): File? { + val sourceFileName = computeSourceFileName(className) ?: return null + val files = + Files.walk(directoryToSearchRecursively).filter { it.toFile().isFile && it.endsWith(sourceFileName) } + var fileWithoutPackage: File? = null + val pathWithPackage = packageName?.let { Paths.get(it, sourceFileName) } + for (f in files) { + if (pathWithPackage == null || f.endsWith(pathWithPackage)) { + return f.toFile() + } + fileWithoutPackage = f.toFile() + } + return fileWithoutPackage + } + + open fun computeSourceFileByClass(clazz: Class<*>, directoryToSearchRecursively: Path = Paths.get("")): File? { + val packageName = clazz.`package`?.name?.replace('.', File.separatorChar) + return computeSourceFileByNameAndPackage(clazz.name, packageName, directoryToSearchRecursively) + } +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/mock/MockClassVisitor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/mock/MockClassVisitor.kt index 37dceb7efe..72f4e560a3 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/mock/MockClassVisitor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/mock/MockClassVisitor.kt @@ -10,11 +10,21 @@ import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.commons.AdviceAdapter import org.objectweb.asm.commons.Method.getMethod +import org.utbot.framework.plugin.api.util.signature object MockConfig { const val IS_MOCK_FIELD = "\$__is_mock_" } +/** + * Computes key for method that is used for mocking. + */ +fun computeKeyForMethod(internalType: String, methodSignature: String) = + "$internalType@$methodSignature" + +fun computeKeyForMethod(method: Method) = + computeKeyForMethod(Type.getInternalName(method.declaringClass), method.signature) + class MockClassVisitor( classVisitor: ClassVisitor, mockGetter: Method, @@ -55,8 +65,8 @@ class MockClassVisitor( exceptions: Array? ): MethodVisitor { val isNotSynthetic = access.and(Opcodes.ACC_SYNTHETIC) == 0 - // we do not want to mock or or synthetic methods - return if (name != "" && name != "" && isNotSynthetic) { + // we do not want to mock or synthetic methods + return if (name != "" && isNotSynthetic) { visitStaticMethod(access, name, descriptor, signature, exceptions) } else { cv.visitMethod(access, name, descriptor, signature, exceptions) @@ -73,7 +83,7 @@ class MockClassVisitor( val isStatic = access and Opcodes.ACC_STATIC != 0 val isVoidMethod = Type.getReturnType(descriptor) == Type.VOID_TYPE - val computedSignature = name + descriptor + val computedSignature = computeKeyForMethod(internalClassName, "$name$descriptor") val id = signatureToId.size signatureToId[computedSignature] = id diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/SpringInstrumentationContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/SpringInstrumentationContext.kt new file mode 100644 index 0000000000..2e23eb37ff --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/SpringInstrumentationContext.kt @@ -0,0 +1,61 @@ +package org.utbot.instrumentation.instrumentation.spring + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.SpringSettings.* +import org.utbot.framework.plugin.api.SpringConfiguration.* +import org.utbot.framework.plugin.api.UtConcreteValue +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtSpringContextModel +import org.utbot.framework.plugin.api.UtSpringEntityManagerModel +import org.utbot.framework.plugin.api.util.SpringModelUtils.resultActionsClassId +import org.utbot.framework.plugin.api.util.isSubtypeOf +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelWithCompositeOriginConstructor +import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext +import org.utbot.instrumentation.instrumentation.execution.phases.ExecutionPhase +import org.utbot.spring.api.SpringApi +import org.utbot.spring.api.provider.SpringApiProviderFacade +import org.utbot.spring.api.provider.InstantiationSettings + +class SpringInstrumentationContext( + private val springSettings: PresentSpringSettings, + private val delegateInstrumentationContext: InstrumentationContext, +) : InstrumentationContext by delegateInstrumentationContext { + // TODO: recreate context/app every time whenever we change method under test + val springApiProviderResult: SpringApiProviderFacade.ProviderResult by lazy { + val classLoader = utContext.classLoader + Thread.currentThread().contextClassLoader = classLoader + + val instantiationSettings = InstantiationSettings( + configurationClasses = arrayOf( + // TODO: for now we prohibit generating integration tests with XML configuration supplied, + // so we expect JavaConfigurations only. + // After fix rewrite the following. + classLoader.loadClass( + (springSettings.configuration as? JavaBasedConfiguration)?.configBinaryName + ?: error("JavaConfiguration was expected, but ${springSettings.configuration.javaClass.name} was provided.") + ) + ), + profiles = springSettings.profiles.toTypedArray(), + ) + + SpringApiProviderFacade + .getInstance(classLoader) + .provideMostSpecificAvailableApi(instantiationSettings) + } + + val springApi get() = springApiProviderResult.result.getOrThrow() + + override fun constructContextDependentValue(model: UtModel): UtConcreteValue<*>? = when (model) { + is UtSpringContextModel -> UtConcreteValue(springApi.getOrLoadSpringApplicationContext()) + is UtSpringEntityManagerModel -> UtConcreteValue(springApi.getEntityManager()) + else -> delegateInstrumentationContext.constructContextDependentValue(model) + } + + override fun findUtModelWithCompositeOriginConstructor(classId: ClassId): UtModelWithCompositeOriginConstructor? = + if (classId.isSubtypeOf(resultActionsClassId)) UtMockMvcResultActionsModelConstructor() + else delegateInstrumentationContext.findUtModelWithCompositeOriginConstructor(classId) + + override fun onPhaseTimeout(timedOutedPhase: ExecutionPhase) = + springApi.afterTestMethod() +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/SpringUtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/SpringUtExecutionInstrumentation.kt new file mode 100644 index 0000000000..ea61498c73 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/SpringUtExecutionInstrumentation.kt @@ -0,0 +1,240 @@ +package org.utbot.instrumentation.instrumentation.spring + +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import org.utbot.common.JarUtils +import org.utbot.common.hasOnClasspath +import org.utbot.framework.plugin.api.BeanDefinitionData +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.ConcreteContextLoadingResult +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.MissingState +import org.utbot.framework.plugin.api.SpringRepositoryId +import org.utbot.framework.plugin.api.SpringSettings.* +import org.utbot.framework.plugin.api.UtConcreteExecutionProcessedFailure +import org.utbot.framework.plugin.api.isNull +import org.utbot.framework.plugin.api.util.SpringModelUtils.mockMvcPerformMethodId +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.process.kryo.KryoHelper +import org.utbot.instrumentation.instrumentation.ArgumentList +import org.utbot.instrumentation.instrumentation.execution.PreliminaryUtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation +import org.utbot.instrumentation.instrumentation.execution.context.InstrumentationContext +import org.utbot.instrumentation.instrumentation.execution.phases.ExecutionPhaseFailingOnAnyException +import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController +import org.utbot.instrumentation.process.generated.GetSpringRepositoriesResult +import org.utbot.instrumentation.process.generated.InstrumentedProcessModel +import org.utbot.instrumentation.process.generated.TryLoadingSpringContextResult +import org.utbot.rd.IdleWatchdog +import org.utbot.spring.api.SpringApi +import java.io.File +import java.net.URL +import java.net.URLClassLoader +import java.security.ProtectionDomain + +/** + * UtExecutionInstrumentation wrapper that is aware of Spring configuration and profiles and initialises Spring context + */ +class SpringUtExecutionInstrumentation( + instrumentationContext: InstrumentationContext, + delegateInstrumentationFactory: UtExecutionInstrumentation.Factory<*>, + springSettings: PresentSpringSettings, + private val beanDefinitions: List, + buildDirs: Array, +) : UtExecutionInstrumentation { + private val instrumentationContext = SpringInstrumentationContext(springSettings, instrumentationContext) + private val delegateInstrumentation = delegateInstrumentationFactory.create(this.instrumentationContext) + private val userSourcesClassLoader = URLClassLoader(buildDirs, null) + + private val relatedBeansCache = mutableMapOf, Set>() + private val nonRepositoryRelatedBeansCache = mutableMapOf, Set>() + private val repositoryDescriptionsCache = mutableMapOf, Set>() + + private val springApi: SpringApi get() = instrumentationContext.springApi + + private object SpringBeforeTestMethodPhase : ExecutionPhaseFailingOnAnyException() + private object SpringAfterTestMethodPhase : ExecutionPhaseFailingOnAnyException() + + companion object { + private val logger = getLogger() + private const val SPRING_COMMONS_JAR_FILENAME = "utbot-spring-commons-shadow.jar" + + val springCommonsJar: File by lazy { + JarUtils.extractJarFileFromResources( + jarFileName = SPRING_COMMONS_JAR_FILENAME, + jarResourcePath = "lib/$SPRING_COMMONS_JAR_FILENAME", + targetDirectoryName = "spring-commons" + ) + } + } + + private fun tryLoadingSpringContext(): ConcreteContextLoadingResult { + val apiProviderResult = instrumentationContext.springApiProviderResult + return ConcreteContextLoadingResult( + contextLoaded = apiProviderResult.result.isSuccess, + exceptions = apiProviderResult.exceptions + ) + } + + override fun invoke( + clazz: Class<*>, + methodSignature: String, + arguments: ArgumentList, + parameters: Any?, + phasesWrapper: PhasesController.(invokeBasePhases: () -> PreliminaryUtConcreteExecutionResult) -> PreliminaryUtConcreteExecutionResult + ): UtConcreteExecutionResult = synchronized(this) { + if (parameters !is UtConcreteExecutionData) { + throw IllegalArgumentException("Argument parameters must be of type UtConcreteExecutionData, but was: ${parameters?.javaClass}") + } + + if (parameters.isRerun) + springApi.resetContext() + + // `RemovingConstructFailsUtExecutionInstrumentation` may detect that we fail to + // construct `RequestBuilder` and use `requestBuilder = null`, leading to a nonsensical + // test `mockMvc.perform((RequestBuilder) null)`, which we should discard + if (parameters.stateBefore.executableToCall == mockMvcPerformMethodId && parameters.stateBefore.parameters.single().isNull()) + return UtConcreteExecutionResult( + stateBefore = parameters.stateBefore, + stateAfter = MissingState, + result = UtConcreteExecutionProcessedFailure(IllegalStateException("requestBuilder can't be null")), + coverage = Coverage(), + detectedMockingCandidates = emptySet() + ) + + return try { + delegateInstrumentation.invoke(clazz, methodSignature, arguments, parameters) { invokeBasePhases -> + if (!parameters.isRerun) + modelConstructionPhase.preconfigureConstructor { maxDepth = 0 } + + phasesWrapper { + // NB! beforeTestMethod() and afterTestMethod() are intentionally called inside phases, + // so they are executed in one thread with method under test + // NB! beforeTestMethod() and afterTestMethod() are executed without timeout, because: + // - if the invokeBasePhases() times out, we still want to execute afterTestMethod() + // - first call to beforeTestMethod() can take significant amount of time due to class loading & transformation + executePhaseWithoutTimeout(SpringBeforeTestMethodPhase) { springApi.beforeTestMethod() } + try { + invokeBasePhases() + } finally { + executePhaseWithoutTimeout(SpringAfterTestMethodPhase) { springApi.afterTestMethod() } + } + } + } + } finally { + getNonRepositoryRelevantBeans(clazz).forEach { beanName -> springApi.resetBean(beanName) } + } + } + + override fun getStaticField(fieldId: FieldId): Result<*> = delegateInstrumentation.getStaticField(fieldId) + + private fun getRelevantBeans(clazz: Class<*>): Set = relatedBeansCache.getOrPut(clazz) { + beanDefinitions + .filter { it.beanTypeName == clazz.name } + // forces `getBean()` to load Spring classes, + // otherwise execution of method under test may fail with timeout + .onEach { springApi.getBean(it.beanName) } + .flatMap { springApi.getDependenciesForBean(it.beanName, userSourcesClassLoader) } + .toSet() + .also { logger.info { "Detected relevant beans for class ${clazz.name}: $it" } } + } + + private fun getNonRepositoryRelevantBeans(clazz: Class<*>): Set = nonRepositoryRelatedBeansCache.getOrPut(clazz) { + getRelevantBeans(clazz).subtract(getRepositoryDescriptions(clazz).map { it.repositoryBeanName }.toSet()) + } + + private fun getRepositoryDescriptions(clazz: Class<*>): Set = repositoryDescriptionsCache.getOrPut(clazz) { + val relevantBeanNames = getRelevantBeans(clazz) + val repositoryDescriptions = springApi.resolveRepositories(relevantBeanNames.toSet(), userSourcesClassLoader) + return repositoryDescriptions.map { repositoryDescription -> + SpringRepositoryId( + repositoryDescription.beanName, + ClassId(repositoryDescription.repositoryName), + ClassId(repositoryDescription.entityName), + ) + }.toSet() + } + + override fun transform( + loader: ClassLoader?, + className: String, + classBeingRedefined: Class<*>?, + protectionDomain: ProtectionDomain, + classfileBuffer: ByteArray + ): ByteArray? { + // always transform `MockMvc` to avoid empty coverage when testing controllers + val isMockMvc = className == "org/springframework/test/web/servlet/MockMvc" + + // we do not transform Spring classes as it takes too much time + + // maybe we should still transform classes related to data validation + // (e.g. from packages "javax/persistence" and "jakarta/persistence"), + // since traces from such classes can be particularly useful for feedback to fuzzer + return if (isMockMvc || userSourcesClassLoader.hasOnClasspath(className.replace("/", "."))) { + delegateInstrumentation.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer) + } else { + null + } + } + + override fun InstrumentedProcessModel.setupAdditionalRdResponses(kryoHelper: KryoHelper, watchdog: IdleWatchdog) { + watchdog.measureTimeForActiveCall(getRelevantSpringRepositories, "Getting Spring repositories") { params -> + val classId: ClassId = kryoHelper.readObject(params.classId) + val repositoryDescriptions = getRepositoryDescriptions(classId.jClass) + GetSpringRepositoriesResult(kryoHelper.writeObject(repositoryDescriptions)) + } + watchdog.measureTimeForActiveCall(tryLoadingSpringContext, "Trying to load Spring application context") { params -> + val contextLoadingResult = tryLoadingSpringContext() + TryLoadingSpringContextResult(kryoHelper.writeObject(contextLoadingResult)) + } + delegateInstrumentation.run { setupAdditionalRdResponses(kryoHelper, watchdog) } + } + + class Factory( + private val delegateInstrumentationFactory: UtExecutionInstrumentation.Factory<*>, + private val springSettings: PresentSpringSettings, + private val beanDefinitions: List, + private val buildDirs: Array, + ) : UtExecutionInstrumentation.Factory { + override val additionalRuntimeClasspath: Set + get() = super.additionalRuntimeClasspath + springCommonsJar.path + + // TODO may be we can use some alternative sandbox that has more permissions + // (at the very least we need `ReflectPermission("suppressAccessChecks")` + // to let Jackson work with private fields when `@RequestBody` is used) + override val forceDisableSandbox: Boolean + get() = true + + override fun create(instrumentationContext: InstrumentationContext): SpringUtExecutionInstrumentation = + SpringUtExecutionInstrumentation( + instrumentationContext, + delegateInstrumentationFactory, + springSettings, + beanDefinitions, + buildDirs + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Factory + + if (delegateInstrumentationFactory != other.delegateInstrumentationFactory) return false + if (springSettings != other.springSettings) return false + if (beanDefinitions != other.beanDefinitions) return false + return buildDirs.contentEquals(other.buildDirs) + } + + override fun hashCode(): Int { + var result = delegateInstrumentationFactory.hashCode() + result = 31 * result + springSettings.hashCode() + result = 31 * result + beanDefinitions.hashCode() + result = 31 * result + buildDirs.contentHashCode() + return result + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/UtMockMvcResultActionsModelConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/UtMockMvcResultActionsModelConstructor.kt new file mode 100644 index 0000000000..05f307a373 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/spring/UtMockMvcResultActionsModelConstructor.kt @@ -0,0 +1,43 @@ +package org.utbot.instrumentation.instrumentation.spring + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtSpringMockMvcResultActionsModel +import org.utbot.framework.plugin.api.util.SpringModelUtils.modelAndViewGetModelMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.modelAndViewGetViewNameMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.mvcResultGetModelAndViewMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.mvcResultGetResponseMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.responseGetContentAsStringMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.responseGetErrorMessageMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.responseGetStatusMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.resultActionsAndReturnMethodId +import org.utbot.framework.plugin.api.util.mapClassId +import org.utbot.framework.plugin.api.util.method +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelWithCompositeOriginConstructor +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructorInterface + +class UtMockMvcResultActionsModelConstructor : UtModelWithCompositeOriginConstructor { + override fun constructModelWithCompositeOrigin( + internalConstructor: UtModelConstructorInterface, + value: Any, + valueClassId: ClassId, + id: Int?, + saveToCache: (UtModel) -> Unit + ): UtSpringMockMvcResultActionsModel { + val mvcResult = resultActionsAndReturnMethodId.method.invoke(value) + val response = mvcResultGetResponseMethodId.method.invoke(mvcResult) + val modelAndView = mvcResultGetModelAndViewMethodId.method.invoke(mvcResult) + + return UtSpringMockMvcResultActionsModel( + id = id, + origin = null, // replace with actual origin if needed + status = responseGetStatusMethodId.method.invoke(response) as Int, + errorMessage = responseGetErrorMessageMethodId.method.invoke(response) as String?, + contentAsString = responseGetContentAsStringMethodId.method.invoke(response) as String, + viewName = modelAndView?.let { modelAndViewGetViewNameMethodId.method.invoke(modelAndView) } as String?, + model = modelAndView?.let { modelAndViewGetModelMethodId.method.invoke(modelAndView) }?.let { + internalConstructor.construct((it as Map<*, *>).toMap(), mapClassId) + } + ).also(saveToCache) + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformation.kt new file mode 100644 index 0000000000..92d38616ae --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformation.kt @@ -0,0 +1,44 @@ +package org.utbot.instrumentation.instrumentation.transformation + +import org.utbot.framework.plugin.api.FieldId +import org.utbot.instrumentation.instrumentation.ArgumentList +import org.utbot.instrumentation.instrumentation.Instrumentation +import org.utbot.instrumentation.instrumentation.InvokeInstrumentation +import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter +import java.security.ProtectionDomain + +/** + * This instrumentation transforms bytecode and delegates invoking a given function to [InvokeInstrumentation]. + */ +class BytecodeTransformation : Instrumentation> { + private val invokeInstrumentation = InvokeInstrumentation() + + override fun invoke( + clazz: Class<*>, + methodSignature: String, + arguments: ArgumentList, + parameters: Any? + ): Result<*> = invokeInstrumentation.invoke(clazz, methodSignature, arguments, parameters) + + override fun getStaticField(fieldId: FieldId): Result<*> = invokeInstrumentation.getStaticField(fieldId) + + override fun transform( + loader: ClassLoader?, + className: String?, + classBeingRedefined: Class<*>?, + protectionDomain: ProtectionDomain?, + classfileBuffer: ByteArray + ): ByteArray { + val instrumenter = Instrumenter(classfileBuffer, loader) + + instrumenter.visitClass { writer -> + BytecodeTransformer(writer) + } + + return instrumenter.classByteCode + } + + object Factory : Instrumentation.Factory, BytecodeTransformation> { + override fun create(): BytecodeTransformation = BytecodeTransformation() + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformer.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformer.kt new file mode 100644 index 0000000000..6cdf1295df --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/BytecodeTransformer.kt @@ -0,0 +1,23 @@ +package org.utbot.instrumentation.instrumentation.transformation + +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import org.utbot.instrumentation.Settings +import org.utbot.instrumentation.instrumentation.transformation.adapters.StringMethodsAdapter + +/** + * Main class for the transformation. + * Bytecode transformations will be combined in this class. + */ +class BytecodeTransformer(classVisitor: ClassVisitor) : ClassVisitor(Settings.ASM_API, classVisitor) { + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array? + ): MethodVisitor { + val methodVisitor = cv.visitMethod(access, name, descriptor, signature, exceptions) + return StringMethodsAdapter(api, access, descriptor, methodVisitor) + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/PatternMethodAdapter.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/PatternMethodAdapter.kt new file mode 100644 index 0000000000..d35bf0d5a2 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/PatternMethodAdapter.kt @@ -0,0 +1,117 @@ +package org.utbot.instrumentation.instrumentation.transformation.adapters + +import org.objectweb.asm.Handle +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.commons.LocalVariablesSorter + + +/** + * Abstract class for the pattern method adapters. + * + * The idea of stateful bytecode transformation was described in the [ASM library user guide](https://asm.ow2.io/asm4-guide.pdf) in chapter 3.2.5. + */ +abstract class PatternMethodAdapter( + api: Int, + access: Int, + descriptor: String?, + methodVisitor: MethodVisitor +) : LocalVariablesSorter(api, access, descriptor, methodVisitor) { + protected abstract fun resetState() + + override fun visitFrame(type: Int, numLocal: Int, local: Array?, numStack: Int, stack: Array?) { + resetState() + mv.visitFrame(type, numLocal, local, numStack, stack) + } + + override fun visitInsn(opcode: Int) { + resetState() + mv.visitInsn(opcode) + } + + override fun visitIntInsn(opcode: Int, operand: Int) { + resetState() + mv.visitIntInsn(opcode, operand) + } + + override fun visitVarInsn(opcode: Int, localVariable: Int) { + resetState() + mv.visitVarInsn(opcode, localVariable) + } + + override fun visitTypeInsn(opcode: Int, type: String?) { + resetState() + mv.visitTypeInsn(opcode, type) + } + + override fun visitFieldInsn(opcode: Int, owner: String?, name: String?, descriptor: String?) { + resetState() + mv.visitFieldInsn(opcode, owner, name, descriptor) + } + + override fun visitMethodInsn( + opcode: Int, + owner: String?, + name: String?, + descriptor: String?, + isInterface: Boolean + ) { + resetState() + mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + } + + override fun visitInvokeDynamicInsn( + name: String?, + descriptor: String?, + bootstrapMethodHandle: Handle?, + vararg bootstrapMethodArguments: Any? + ) { + resetState() + mv.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, *bootstrapMethodArguments) + } + + override fun visitJumpInsn(opcode: Int, label: Label?) { + resetState() + mv.visitJumpInsn(opcode, label) + } + + override fun visitLabel(label: Label?) { + resetState() + mv.visitLabel(label) + } + + override fun visitLdcInsn(value: Any?) { + resetState() + mv.visitLdcInsn(value) + } + + override fun visitIincInsn(`var`: Int, increment: Int) { + resetState() + mv.visitIincInsn(`var`, increment) + } + + override fun visitTableSwitchInsn(min: Int, max: Int, dflt: Label?, vararg labels: Label?) { + resetState() + mv.visitTableSwitchInsn(min, max, dflt, *labels) + } + + override fun visitLookupSwitchInsn(dflt: Label?, keys: IntArray?, labels: Array?) { + resetState() + mv.visitLookupSwitchInsn(dflt, keys, labels) + } + + override fun visitMultiANewArrayInsn(descriptor: String?, numDimensions: Int) { + resetState() + mv.visitMultiANewArrayInsn(descriptor, numDimensions) + } + + override fun visitTryCatchBlock(start: Label?, end: Label?, handler: Label?, type: String?) { + resetState() + mv.visitTryCatchBlock(start, end, handler, type) + } + + override fun visitMaxs(maxStack: Int, maxLocals: Int) { + resetState() + mv.visitMaxs(maxStack, maxLocals) + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringMethodsAdapter.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringMethodsAdapter.kt new file mode 100644 index 0000000000..b73e9d552f --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/transformation/adapters/StringMethodsAdapter.kt @@ -0,0 +1,191 @@ +package org.utbot.instrumentation.instrumentation.transformation.adapters + +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import kotlin.properties.Delegates + +/** + * Class for transforming an if statement with call of the [String.equals], [String.startsWith] or [String.endsWith] method + * with a constant string into a sequence of comparisons of each char of the string with each char of the constant string. + */ +class StringMethodsAdapter( + api: Int, + access: Int, + descriptor: String?, + methodVisitor: MethodVisitor +) : PatternMethodAdapter(api, access, descriptor, methodVisitor) { + private enum class State { + SEEN_NOTHING, + SEEN_ALOAD, + SEEN_LDC_STRING_CONST, + SEEN_INVOKEVIRTUAL_STRING_EQUALS, + SEEN_INVOKEVIRTUAL_STRING_STARTSWITH, + SEEN_INVOKEVIRTUAL_STRING_ENDSWITH + } + + private var state: State = State.SEEN_NOTHING + + private var indexOfLocalVariable by Delegates.notNull() + private lateinit var constString: String + + override fun resetState() { + when (state) { + State.SEEN_ALOAD -> mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + State.SEEN_LDC_STRING_CONST -> { + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitLdcInsn(constString) + } + + State.SEEN_INVOKEVIRTUAL_STRING_EQUALS -> { + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitLdcInsn(constString) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false) + } + + State.SEEN_INVOKEVIRTUAL_STRING_STARTSWITH -> { + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitLdcInsn(constString) + mv.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/String", + "startsWith", + "(Ljava/lang/String;)Z", + false + ) + } + + State.SEEN_INVOKEVIRTUAL_STRING_ENDSWITH -> { + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitLdcInsn(constString) + mv.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/String", + "endsWith", + "(Ljava/lang/String;)Z", + false + ) + } + + else -> {} + } + state = State.SEEN_NOTHING + } + + override fun visitVarInsn(opcode: Int, localVariable: Int) { + if (state == State.SEEN_NOTHING && opcode == Opcodes.ALOAD) { + state = State.SEEN_ALOAD + indexOfLocalVariable = localVariable + return + } + resetState() + if (opcode == Opcodes.ALOAD) { + state = State.SEEN_ALOAD + indexOfLocalVariable = localVariable + return + } + mv.visitVarInsn(opcode, localVariable) + } + + override fun visitMethodInsn( + opcode: Int, + owner: String?, + name: String?, + descriptor: String?, + isInterface: Boolean + ) { + if (state == State.SEEN_LDC_STRING_CONST && !isInterface && opcode == Opcodes.INVOKEVIRTUAL && owner == "java/lang/String") { + when { + name == "equals" && descriptor == "(Ljava/lang/Object;)Z" -> { + state = State.SEEN_INVOKEVIRTUAL_STRING_EQUALS + return + } + + name == "startsWith" && descriptor == "(Ljava/lang/String;)Z" -> { + state = State.SEEN_INVOKEVIRTUAL_STRING_STARTSWITH + return + } + + name == "endsWith" && descriptor == "(Ljava/lang/String;)Z" -> { + state = State.SEEN_INVOKEVIRTUAL_STRING_ENDSWITH + return + } + } + } + resetState() + mv.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + } + + override fun visitLdcInsn(value: Any?) { + if (state == State.SEEN_ALOAD && value is String) { + state = State.SEEN_LDC_STRING_CONST + constString = value + return + } + resetState() + mv.visitLdcInsn(value) + } + + override fun visitJumpInsn(opcode: Int, label: Label?) { + if (setOf( + State.SEEN_INVOKEVIRTUAL_STRING_EQUALS, + State.SEEN_INVOKEVIRTUAL_STRING_STARTSWITH, + State.SEEN_INVOKEVIRTUAL_STRING_ENDSWITH + ).any { state == it } && opcode == Opcodes.IFEQ + ) { + // code transformation + // if (str.length() == constString.length()) for equals method + // if (str.length() >= constString.length()) for startsWith and endsWith methods + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false) + mv.visitIntInsn(Opcodes.BIPUSH, constString.length) + if (state == State.SEEN_INVOKEVIRTUAL_STRING_EQUALS) { + mv.visitJumpInsn(Opcodes.IF_ICMPNE, label) + } else { + mv.visitJumpInsn(Opcodes.IF_ICMPLT, label) + } + + if (constString.isEmpty()) { + state = State.SEEN_NOTHING + return + } + + if (state == State.SEEN_INVOKEVIRTUAL_STRING_ENDSWITH) { + // int length = str.length() + mv.visitLabel(Label()) + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "length", "()I", false) + val length = newLocal(Type.INT_TYPE) + mv.visitVarInsn(Opcodes.ISTORE, length) + + // reverse constant string to compare chars from the end + constString.reversed().forEachIndexed { index, c -> + // if (str.charAt(length - (index + 1)) == c) + mv.visitLabel(Label()) + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitVarInsn(Opcodes.ILOAD, length) + mv.visitIntInsn(Opcodes.BIPUSH, index + 1) + mv.visitInsn(Opcodes.ISUB) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C", false) + mv.visitIntInsn(Opcodes.BIPUSH, c.code) + mv.visitJumpInsn(Opcodes.IF_ICMPNE, label) + } + } else { + constString.forEachIndexed { index, c -> + // if (str.charAt(index) == c) + mv.visitLabel(Label()) + mv.visitVarInsn(Opcodes.ALOAD, indexOfLocalVariable) + mv.visitIntInsn(Opcodes.BIPUSH, index) + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C", false) + mv.visitIntInsn(Opcodes.BIPUSH, c.code) + mv.visitJumpInsn(Opcodes.IF_ICMPNE, label) + } + } + state = State.SEEN_NOTHING + return + } + resetState() + mv.visitJumpInsn(opcode, label) + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/ChildProcess.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/ChildProcess.kt deleted file mode 100644 index 77e1d9a674..0000000000 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/ChildProcess.kt +++ /dev/null @@ -1,190 +0,0 @@ -package org.utbot.instrumentation.process - -import org.utbot.common.scanForClasses -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.instrumentation.agent.Agent -import org.utbot.instrumentation.instrumentation.Instrumentation -import org.utbot.instrumentation.util.KryoHelper -import org.utbot.instrumentation.util.Protocol -import org.utbot.instrumentation.util.UnexpectedCommand -import java.io.File -import java.io.OutputStream -import java.io.PrintStream -import java.net.URLClassLoader -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter -import kotlin.system.exitProcess -import kotlin.system.measureTimeMillis - -/** - * We use this ClassLoader to separate user's classes and our dependency classes. - * Our classes won't be instrumented. - */ -internal object HandlerClassesLoader : URLClassLoader(emptyArray()) { - fun addUrls(urls: Iterable) { - urls.forEach { super.addURL(File(it).toURI().toURL()) } - } - - /** - * System classloader can find org.slf4j thus when we want to mock something from org.slf4j - * we also want this class will be loaded by [HandlerClassesLoader] - */ - override fun loadClass(name: String, resolve: Boolean): Class<*> { - if (name.startsWith("org.slf4j")) { - return (findLoadedClass(name) ?: findClass(name)).apply { - if (resolve) resolveClass(this) - } - } - return super.loadClass(name, resolve) - } -} - -// Logging -private val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS") -private fun log(any: Any?) { - System.err.println(LocalDateTime.now().format(dateFormatter) + " | $any") -} - -private val kryoHelper: KryoHelper = KryoHelper(System.`in`, System.`out`) - -/** - * It should be compiled into separate jar file (child_process.jar) and be run with an agent (agent.jar) option. - */ -fun main() { - // We don't want user code to litter the standard output, so we redirect it. - val tmpStream = PrintStream(object : OutputStream() { - override fun write(b: Int) {} - }) - System.setOut(tmpStream) - - val classPaths = readClasspath() - val pathsToUserClasses = classPaths.pathsToUserClasses.split(File.pathSeparatorChar).toSet() - val pathsToDependencyClasses = classPaths.pathsToDependencyClasses.split(File.pathSeparatorChar).toSet() - HandlerClassesLoader.addUrls(pathsToUserClasses) - HandlerClassesLoader.addUrls(pathsToDependencyClasses) - kryoHelper.setKryoClassLoader(HandlerClassesLoader) // Now kryo will use our classloader when it encounters unregistered class. - - log("User classes:" + pathsToUserClasses.joinToString()) - - kryoHelper.use { - UtContext.setUtContext(UtContext(HandlerClassesLoader)).use { - getInstrumentation()?.let { instrumentation -> - Agent.dynamicClassTransformer.transformer = instrumentation // classTransformer is set - Agent.dynamicClassTransformer.addUserPaths(pathsToUserClasses) - instrumentation.init(pathsToUserClasses) - - try { - loop(instrumentation) - } catch (e: Throwable) { - log("Terminating process because exception occured: ${e.stackTraceToString()}") - exitProcess(1) - } - } - } - } -} - -private fun send(cmdId: Long, cmd: Protocol.Command) { - try { - kryoHelper.writeCommand(cmdId, cmd) - log("Send << $cmdId") - } catch (e: Exception) { - log("Failed to serialize << $cmdId with exception: ${e.stackTraceToString()}") - log("Writing it to kryo...") - kryoHelper.writeCommand(cmdId, Protocol.ExceptionInChildProcess(e)) - log("Successfuly wrote.") - } -} - -private fun read(cmdId: Long): Protocol.Command { - try { - val cmd = kryoHelper.readCommand() - log("Received :> $cmdId") - return cmd - } catch (e: Exception) { - log("Failed to read :> $cmdId with exception: ${e.stackTraceToString()}") - throw e - } -} - -/** - * Main loop. Processes incoming commands. - */ -private fun loop(instrumentation: Instrumentation<*>) { - while (true) { - val cmdId = kryoHelper.readLong() - val cmd = try { - read(cmdId) - } catch (e: Exception) { - send(cmdId, Protocol.ExceptionInChildProcess(e)) - continue - } - - when (cmd) { - is Protocol.WarmupCommand -> { - val time = measureTimeMillis { - HandlerClassesLoader.scanForClasses("").toList() // here we transform classes - } - System.err.println("warmup finished in $time ms") - } - is Protocol.InvokeMethodCommand -> { - val resultCmd = try { - val clazz = HandlerClassesLoader.loadClass(cmd.className) - val res = instrumentation.invoke( - clazz, - cmd.signature, - cmd.arguments, - cmd.parameters - ) - Protocol.InvocationResultCommand(res) - } catch (e: Throwable) { - System.err.println(e.stackTraceToString()) - Protocol.ExceptionInChildProcess(e) - } - send(cmdId, resultCmd) - } - is Protocol.StopProcessCommand -> { - break - } - is Protocol.InstrumentationCommand -> { - val result = instrumentation.handle(cmd) - result?.let { - send(cmdId, it) - } - } - else -> { - send(cmdId, Protocol.ExceptionInChildProcess(UnexpectedCommand(cmd))) - } - } - } -} - -/** - * Retrieves the actual instrumentation. It is passed from the main process during - * [org.utbot.instrumentation.ConcreteExecutor] instantiation. - */ -private fun getInstrumentation(): Instrumentation<*>? { - val cmdId = kryoHelper.readLong() - return when (val cmd = kryoHelper.readCommand()) { - is Protocol.SetInstrumentationCommand<*> -> { - cmd.instrumentation - } - is Protocol.StopProcessCommand -> null - else -> { - send(cmdId, Protocol.ExceptionInChildProcess(UnexpectedCommand(cmd))) - null - } - } -} - -private fun readClasspath(): Protocol.AddPathsCommand { - val cmdId = kryoHelper.readLong() - return kryoHelper.readCommand().let { cmd -> - if (cmd is Protocol.AddPathsCommand) { - cmd - } else { - send(cmdId, Protocol.ExceptionInChildProcess(UnexpectedCommand(cmd))) - error("No classpath!") - } - } -} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/ChildProcessRunner.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/ChildProcessRunner.kt deleted file mode 100644 index 9b46a8d5cf..0000000000 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/ChildProcessRunner.kt +++ /dev/null @@ -1,110 +0,0 @@ -package org.utbot.instrumentation.process - -import mu.KotlinLogging -import org.utbot.common.bracket -import org.utbot.common.debug -import org.utbot.common.firstOrNullResourceIS -import org.utbot.common.getCurrentProcessId -import org.utbot.common.packageName -import org.utbot.common.pid -import org.utbot.common.scanForResourcesContaining -import org.utbot.common.utBotTempDirectory -import org.utbot.framework.JdkPathService -import org.utbot.framework.UtSettings -import org.utbot.instrumentation.Settings -import org.utbot.instrumentation.agent.DynamicClassTransformer -import java.io.File -import java.nio.file.Paths - -private val logger = KotlinLogging.logger {} -private var processSeqN = 0 - -class ChildProcessRunner { - private val cmds: List by lazy { - val debugCmd = if (Settings.runChildProcessWithDebug) { - listOf(DEBUG_RUN_CMD) - } else { - emptyList() - } - - listOf(Paths.get(JdkPathService.jdkPath.toString(),"bin", "java").toString()) + - debugCmd + listOf("-javaagent:$jarFile", "-ea", "-jar", "$jarFile") - } - - var errorLogFile: File = NULL_FILE - - fun start(): Process { - logger.debug { "Starting child process: ${cmds.joinToString(" ")}" } - processSeqN++ - - if (UtSettings.logConcreteExecutionErrors) { - UT_BOT_TEMP_DIR.mkdirs() - errorLogFile = File(UT_BOT_TEMP_DIR, "${hashCode()}-${processSeqN}.log") - } - - val processBuilder = ProcessBuilder(cmds).redirectError(errorLogFile) - return processBuilder.start().also { - logger.debug { "Process started with PID=${it.pid}" } - - if (UtSettings.logConcreteExecutionErrors) { - logger.debug { "Child process error log: ${errorLogFile.absolutePath}" } - } - } - } - - companion object { - private const val UTBOT_INSTRUMENTATION = "utbot-instrumentation" - private const val ERRORS_FILE_PREFIX = "utbot-childprocess-errors" - private const val INSTRUMENTATION_LIB = "lib" - - private const val DEBUG_RUN_CMD = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=5005" - - private val UT_BOT_TEMP_DIR: File = File(utBotTempDirectory.toFile(), ERRORS_FILE_PREFIX) - - private val NULL_FILE_PATH: String = if (System.getProperty("os.name").startsWith("Windows")) { - "NUL" - } else { - "/dev/null" - } - - private val NULL_FILE = File(NULL_FILE_PATH) - - /** - * * Firstly, searches for utbot-instrumentation jar in the classpath. - * - * * In case of failure, searches for utbot-instrumentation jar in the resources and extracts it to the - * temporary directory. This jar file must be placed to the resources by `processResources` gradle task - * in the gradle configuration of the project which depends on utbot-instrumentation module. - */ - private val jarFile: File by lazy { - logger.debug().bracket("Finding $UTBOT_INSTRUMENTATION jar") { - run { - logger.debug("Trying to find jar in the resources.") - val tempDir = utBotTempDirectory.toFile() - val unzippedJarName = "$UTBOT_INSTRUMENTATION-${getCurrentProcessId()}.jar" - val instrumentationJarFile = File(tempDir, unzippedJarName) - - ChildProcessRunner::class.java.classLoader - .firstOrNullResourceIS(INSTRUMENTATION_LIB) { resourcePath -> - resourcePath.contains(UTBOT_INSTRUMENTATION) && resourcePath.endsWith(".jar") - } - ?.use { input -> - instrumentationJarFile.writeBytes(input.readBytes()) - } - ?: return@run null - instrumentationJarFile - } ?: run { - logger.debug("Failed to find jar in the resources. Trying to find it in the classpath.") - ChildProcessRunner::class.java.classLoader - .scanForResourcesContaining(DynamicClassTransformer::class.java.packageName) - .firstOrNull { - it.absolutePath.contains(UTBOT_INSTRUMENTATION) && it.extension == "jar" - } - } ?: error(""" - Can't find file: $UTBOT_INSTRUMENTATION-.jar. - Make sure you added $UTBOT_INSTRUMENTATION-.jar to the resources folder from gradle. - """.trimIndent()) - } - } - } -} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt new file mode 100644 index 0000000000..12b2f996b3 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt @@ -0,0 +1,161 @@ +package org.utbot.instrumentation.process + +import com.jetbrains.rd.util.* +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.reactive.adviseOnce +import kotlinx.coroutines.* +import org.mockito.Mockito +import org.utbot.common.* +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.process.kryo.KryoHelper +import org.utbot.instrumentation.agent.Agent +import org.utbot.instrumentation.instrumentation.Instrumentation +import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation +import org.utbot.instrumentation.process.generated.CollectCoverageResult +import org.utbot.instrumentation.process.generated.InstrumentedProcessModel +import org.utbot.instrumentation.process.generated.InvokeMethodCommandResult +import org.utbot.instrumentation.process.generated.instrumentedProcessModel +import org.utbot.rd.IdleWatchdog +import org.utbot.rd.ClientProtocolBuilder +import org.utbot.rd.RdSettingsContainerFactory +import org.utbot.rd.generated.loggerModel +import org.utbot.rd.generated.settingsModel +import org.utbot.rd.loggers.UtRdRemoteLoggerFactory +import java.io.File +import java.net.URLClassLoader +import java.security.AllPermission +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds + +/** + * We use this ClassLoader to separate user's classes and our dependency classes. + * Our classes won't be instrumented. + */ +internal object HandlerClassesLoader : URLClassLoader(emptyArray()) { + fun addUrls(urls: Iterable) { + urls.forEach { super.addURL(File(it).toURI().toURL()) } + } + + /** + * System classloader can find org.slf4j thus when we want to mock something from org.slf4j + * we also want this class will be loaded by [HandlerClassesLoader] + */ + override fun loadClass(name: String, resolve: Boolean): Class<*> { + if (name.startsWith("org.slf4j")) { + return (findLoadedClass(name) ?: findClass(name)).apply { + if (resolve) resolveClass(this) + } + } + return super.loadClass(name, resolve) + } +} + +/** + * Command-line option to disable the sandbox + */ +const val DISABLE_SANDBOX_OPTION = "--disable-sandbox" +private val logger = getLogger() +private val messageFromMainTimeout: Duration = 120.seconds + +interface DummyForMockitoWarmup { + fun method1() +} + +/** + * Mockito initialization take ~0.5-1 sec, which forces first `invoke` request to timeout + * it is crucial in tests as we start process just for 1-2 such requests + */ +fun warmupMockito() { + try { + Mockito.mock(DummyForMockitoWarmup::class.java) + } catch (e: Throwable) { + logger.warn { "Exception during mockito warmup: ${e.stackTraceToString()}" } + } +} + +@Suppress("unused") +object InstrumentedProcessMain + +/** + * It should be compiled into separate jar file (instrumented_process.jar) and be run with an agent (agent.jar) option. + */ +fun main(args: Array) = runBlocking { + if (!args.contains(DISABLE_SANDBOX_OPTION)) { + permissions { + // Enable all permissions for instrumentation. + // SecurityKt.sandbox() is used to restrict these permissions. + +AllPermission() + } + } + + try { + ClientProtocolBuilder().withProtocolTimeout(messageFromMainTimeout).start(args) { + loggerModel.initRemoteLogging.adviseOnce(lifetime) { + Logger.set(Lifetime.Eternal, UtRdRemoteLoggerFactory(loggerModel)) + this.protocol.scheduler.queue { warmupMockito() } + } + val kryoHelper = KryoHelper(lifetime) + logger.info { "setup started" } + AbstractSettings.setupFactory(RdSettingsContainerFactory(protocol.settingsModel)) + instrumentedProcessModel.setup(kryoHelper, it) + logger.info { "setup ended" } + } + } catch (e: Throwable) { + logger.error { "Terminating process because exception occurred: ${e.stackTraceToString()}" } + } +} + +private lateinit var pathsToUserClasses: Set +private lateinit var instrumentation: Instrumentation<*> + +private var warmupDone = false + +private fun InstrumentedProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatchdog) { + watchdog.measureTimeForActiveCall(warmup, "Classloader warmup request") { + if (!warmupDone) { + HandlerClassesLoader.scanForClasses("").toList() // here we transform classes + warmupDone = true + } else { + logger.info { "warmup already happened" } + } + } + watchdog.measureTimeForActiveCall(invokeMethodCommand, "Invoke method request") { params -> + val clazz = HandlerClassesLoader.loadClass(params.classname) + val res = kotlin.runCatching { + instrumentation.invoke( + clazz, + params.signature, + kryoHelper.readObject(params.arguments), + kryoHelper.readObject(params.parameters) + ) + } + res.fold({ + InvokeMethodCommandResult(kryoHelper.writeObject(it)) + }) { + throw it + } + } + watchdog.measureTimeForActiveCall(setInstrumentation, "Instrumentation setup") { params -> + logger.debug { "setInstrumentation request" } + val instrumentationFactory = kryoHelper.readObject>(params.instrumentation) + HandlerClassesLoader.addUrls(instrumentationFactory.additionalRuntimeClasspath) + instrumentation = instrumentationFactory.create() + logger.debug { "instrumentation - ${instrumentation.javaClass.name} " } + Agent.dynamicClassTransformer.useBytecodeTransformation = params.useBytecodeTransformation + Agent.dynamicClassTransformer.transformer = instrumentation + Agent.dynamicClassTransformer.addUserPaths(pathsToUserClasses) + instrumentation.run { setupAdditionalRdResponses(kryoHelper, watchdog) } + } + watchdog.measureTimeForActiveCall(addPaths, "User and dependency classpath setup") { params -> + pathsToUserClasses = params.pathsToUserClasses.split(File.pathSeparatorChar).toSet() + HandlerClassesLoader.addUrls(pathsToUserClasses) + kryoHelper.setKryoClassLoader(HandlerClassesLoader) // Now kryo will use our classloader when it encounters unregistered class. + UtContext.setUtContext(UtContext(HandlerClassesLoader)) + } + watchdog.measureTimeForActiveCall(collectCoverage, "Coverage") { params -> + val anyClass: Class<*> = kryoHelper.readObject(params.clazz) + logger.debug { "class - ${anyClass.name}" } + val result = (instrumentation as CoverageInstrumentation).collectCoverageInfo(anyClass) + CollectCoverageResult(kryoHelper.writeObject(result)) + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/Security.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/Security.kt new file mode 100644 index 0000000000..e506c50541 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/Security.kt @@ -0,0 +1,160 @@ +@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE") + +package org.utbot.instrumentation.process + +import org.utbot.common.withAccessibility +import sun.security.provider.PolicyFile +import java.lang.reflect.AccessibleObject +import java.net.URI +import java.nio.file.Files +import java.nio.file.Paths +import java.security.AccessControlContext +import java.security.AccessController +import java.security.CodeSource +import java.security.Permission +import java.security.PermissionCollection +import java.security.Permissions +import java.security.Policy +import java.security.PrivilegedAction +import java.security.PrivilegedActionException +import java.security.ProtectionDomain +import java.security.cert.Certificate + +internal fun permissions(block: SimplePolicy.() -> Unit) { + val policy = Policy.getPolicy() + if (policy !is SimplePolicy) { + Policy.setPolicy(SimplePolicy(block)) + System.setSecurityManager(SecurityManager()) + } else { + policy.block() + } +} + +/** + * Make this [AccessibleObject] accessible and run a block inside sandbox. + * + * If [bypassSandbox] is `true` then block is run without sandbox. + */ +fun O.runSandbox(bypassSandbox: Boolean = false, block: O.() -> R): R = withAccessibility { + if (bypassSandbox) block() + else sandbox { block() } +} + +/** + * Run [block] in sandbox mode. + * + * When running in sandbox by default only necessary to instrumentation permissions are enabled. + * Other options are not enabled by default and rises [java.security.AccessControlException]. + * + * To add new permissions create and/or edit file "{user.home}/.utbot/sandbox.policy". + * + * For example to enable property reading (`System.getProperty("user.home")`): + * + * ``` + * grant { + * permission java.util.PropertyPermission "user.home", "read"; + * }; + * ``` + * Read more [about policy file and syntax](https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html#Examples) + */ +fun sandbox(block: () -> T): T { + val policyPath = Paths.get(System.getProperty("user.home"), ".utbot", "sandbox.policy") + return sandbox(policyPath.toUri()) { block() } +} + +fun sandbox(file: URI, block: () -> T): T { + val path = Paths.get(file) + val perms = mutableListOf( + RuntimePermission("accessDeclaredMembers"), + RuntimePermission("getStackWalkerWithClassReference"), + RuntimePermission("getClassLoader"), + ) + val allCodeSource = CodeSource(null, emptyArray()) + if (Files.exists(path)) { + val policyFile = PolicyFile(file.toURL()) + val collection = policyFile.getPermissions(allCodeSource) + perms += collection.elements().toList() + } + return sandbox(perms, allCodeSource) { block() } +} + +fun sandbox(permission: List, cs: CodeSource, block: () -> T): T { + val perms = permission.fold(Permissions()) { acc, p -> acc.add(p); acc } + return sandbox(perms, cs) { block() } +} + +fun sandbox(perms: PermissionCollection, cs: CodeSource, block: () -> T): T { + val acc = AccessControlContext(arrayOf(ProtectionDomain(cs, perms))) + return try { + AccessController.doPrivileged(PrivilegedAction { block() }, acc) + } catch (e: PrivilegedActionException) { + throw e.exception + } +} + +/** + * This policy can add grant or denial rules for permissions. + * + * To add a grant permission use like this in any place: + * + * ``` + * permissions { + * + java.security.PropertyPolicy("user.home", "read,write") + * } + * ``` + * + * After first call [SecurityManager] is set with this policy + * + * To deny a permission: + * + * ``` + * permissions { + * - java.security.PropertyPolicy("user.home", "read,write") + * } + * ``` + * + * To delete all concrete permissions (if it was added before): + * + * ``` + * permissions { + * ! java.security.PropertyPolicy("user.home", "read,write") + * } + * ``` + * + * The last permission has priority. Enable all property read for "user.*", but forbid to read only "user.home": + * + * ``` + * permissions { + * + java.security.PropertyPolicy("user.*", "read,write") + * - java.security.PropertyPolicy("user.home", "read,write") + * } + * ``` + */ +internal class SimplePolicy(init: SimplePolicy.() -> Unit = {}) : Policy() { + sealed class Access(val permission: Permission) { + class Allow(permission: Permission) : Access(permission) + class Deny(permission: Permission) : Access(permission) + } + private var permissions = mutableListOf() + + init { apply(init) } + + operator fun Permission.unaryPlus() = permissions.add(Access.Allow(this)) + + operator fun Permission.unaryMinus() = permissions.add(Access.Deny(this)) + + operator fun Permission.not() = permissions.removeAll { it.permission == this } + + override fun getPermissions(codesource: CodeSource) = UNSUPPORTED_EMPTY_COLLECTION!! + override fun getPermissions(domain: ProtectionDomain) = UNSUPPORTED_EMPTY_COLLECTION!! + override fun implies(domain: ProtectionDomain, permission: Permission): Boolean { + // 0 means no info, < 0 is denied and > 0 is allowed + val result = permissions.lastOrNull { it.permission.implies(permission) }?.let { + when (it) { + is Access.Allow -> 1 + is Access.Deny -> -1 + } + } ?: 0 + return result > 0 + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt new file mode 100644 index 0000000000..d752643d3f --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/InstrumentedProcess.kt @@ -0,0 +1,141 @@ +package org.utbot.instrumentation.rd + +import com.jetbrains.rd.util.lifetime.Lifetime +import mu.KotlinLogging +import org.utbot.common.JarUtils +import org.utbot.common.debug +import org.utbot.common.getPid +import org.utbot.common.measureTime +import org.utbot.framework.UtSettings +import org.utbot.framework.plugin.services.WorkingDirService +import org.utbot.framework.process.AbstractRDProcessCompanion +import org.utbot.framework.process.kryo.KryoHelper +import org.utbot.instrumentation.instrumentation.Instrumentation +import org.utbot.instrumentation.process.DISABLE_SANDBOX_OPTION +import org.utbot.instrumentation.process.generated.AddPathsParams +import org.utbot.instrumentation.process.generated.InstrumentedProcessModel +import org.utbot.instrumentation.process.generated.SetInstrumentationParams +import org.utbot.instrumentation.process.generated.instrumentedProcessModel +import org.utbot.rd.ProcessWithRdServer +import org.utbot.rd.exceptions.InstantProcessDeathException +import org.utbot.rd.generated.LoggerModel +import org.utbot.rd.generated.loggerModel +import org.utbot.rd.loggers.setup +import org.utbot.rd.onSchedulerBlocking +import org.utbot.rd.startUtProcessWithRdServer +import org.utbot.rd.terminateOnException +import java.io.File + +private val logger = KotlinLogging.logger { } + +private const val UTBOT_INSTRUMENTATION_JAR_FILENAME = "utbot-instrumentation-shadow.jar" + +private val instrumentationJarFile: File = + logger.debug().measureTime({ "Finding $UTBOT_INSTRUMENTATION_JAR_FILENAME jar" } ) { + try { + JarUtils.extractJarFileFromResources( + jarFileName = UTBOT_INSTRUMENTATION_JAR_FILENAME, + jarResourcePath = "lib/$UTBOT_INSTRUMENTATION_JAR_FILENAME", + targetDirectoryName = "utbot-instrumentation" + ) + } catch (e: Exception) { + throw IllegalStateException( + """ + Can't find file: $UTBOT_INSTRUMENTATION_JAR_FILENAME. + Make sure you added $UTBOT_INSTRUMENTATION_JAR_FILENAME to the resources folder from gradle. + """.trimIndent(), + e + ) + } + } + +class InstrumentedProcessInstantDeathException : + InstantProcessDeathException(UtSettings.instrumentedProcessDebugPort, UtSettings.runInstrumentedProcessWithDebug) + +/** + * Main goals of this class: + * 1. prepare started instrumented process for execution - initializing rd, sending paths and instrumentation + * 2. expose bound model + */ +class InstrumentedProcess private constructor( + private val classLoader: ClassLoader?, + private val rdProcess: ProcessWithRdServer +) : ProcessWithRdServer by rdProcess { + val kryoHelper = KryoHelper(lifetime.createNested()).apply { + classLoader?.let { setKryoClassLoader(it) } + } + val instrumentedProcessModel: InstrumentedProcessModel = onSchedulerBlocking { protocol.instrumentedProcessModel } + val loggerModel: LoggerModel = onSchedulerBlocking { protocol.loggerModel } + + companion object : AbstractRDProcessCompanion( + debugPort = UtSettings.instrumentedProcessDebugPort, + runWithDebug = UtSettings.runInstrumentedProcessWithDebug, + suspendExecutionInDebugMode = UtSettings.suspendInstrumentedProcessExecutionInDebugMode, + processSpecificCommandLineArgs = { + buildList { + add("-javaagent:${instrumentationJarFile.path}") + add("-ea") + add("-jar") + add(instrumentationJarFile.path) + if (!UtSettings.useSandbox) + add(DISABLE_SANDBOX_OPTION) + } + }) { + + suspend operator fun > invoke( + parent: Lifetime, + instrumentationFactory: Instrumentation.Factory, + pathsToUserClasses: String, + classLoader: ClassLoader? + ): InstrumentedProcess = parent.createNested().terminateOnException { lifetime -> + val rdProcess: ProcessWithRdServer = startUtProcessWithRdServer( + lifetime = lifetime + ) { port -> + val cmd = obtainProcessCommandLine(port) + listOfNotNull( + DISABLE_SANDBOX_OPTION.takeIf { instrumentationFactory.forceDisableSandbox } + ) + logger.debug { "Starting instrumented process: $cmd" } + val directory = WorkingDirService.provide().toFile() + val processBuilder = ProcessBuilder(cmd) + .directory(directory) + val process = processBuilder.start() + logger.info { + "------------------------------------------------------------------\n" + + "--------Instrumented process started with PID=${process.getPid}--------\n" + + "------------------------------------------------------------------" + } + if (!process.isAlive) { + throw InstrumentedProcessInstantDeathException() + } + process + }.awaitProcessReady() + + logger.trace("rd process started") + + val proc = InstrumentedProcess(classLoader, rdProcess) + proc.loggerModel.setup(logger, proc.lifetime) + + proc.lifetime.onTermination { + logger.trace { "process is terminating" } + } + + logger.trace("sending add paths") + proc.instrumentedProcessModel.addPaths.startSuspending( + proc.lifetime, AddPathsParams( + pathsToUserClasses, + ) + ) + + logger.trace("sending instrumentation") + proc.instrumentedProcessModel.setInstrumentation.startSuspending( + proc.lifetime, SetInstrumentationParams( + proc.kryoHelper.writeObject(instrumentationFactory), + UtSettings.useBytecodeTransformation + ) + ) + logger.trace("start commands sent") + + return proc + } + } +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt new file mode 100644 index 0000000000..2fe14c35d9 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessModel.Generated.kt @@ -0,0 +1,840 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.instrumentation.process.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [InstrumentedProcessModel.kt:8] + */ +class InstrumentedProcessModel private constructor( + private val _addPaths: RdCall, + private val _warmup: RdCall, + private val _setInstrumentation: RdCall, + private val _invokeMethodCommand: RdCall, + private val _collectCoverage: RdCall, + private val _computeStaticField: RdCall, + private val _getRelevantSpringRepositories: RdCall, + private val _tryLoadingSpringContext: RdCall +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + serializers.register(AddPathsParams) + serializers.register(SetInstrumentationParams) + serializers.register(InvokeMethodCommandParams) + serializers.register(InvokeMethodCommandResult) + serializers.register(CollectCoverageParams) + serializers.register(CollectCoverageResult) + serializers.register(ComputeStaticFieldParams) + serializers.register(ComputeStaticFieldResult) + serializers.register(GetSpringRepositoriesParams) + serializers.register(GetSpringRepositoriesResult) + serializers.register(TryLoadingSpringContextResult) + } + + + @JvmStatic + @JvmName("internalCreateModel") + @Deprecated("Use create instead", ReplaceWith("create(lifetime, protocol)")) + internal fun createModel(lifetime: Lifetime, protocol: IProtocol): InstrumentedProcessModel { + @Suppress("DEPRECATION") + return create(lifetime, protocol) + } + + @JvmStatic + @Deprecated("Use protocol.instrumentedProcessModel or revise the extension scope instead", ReplaceWith("protocol.instrumentedProcessModel")) + fun create(lifetime: Lifetime, protocol: IProtocol): InstrumentedProcessModel { + InstrumentedProcessRoot.register(protocol.serializers) + + return InstrumentedProcessModel() + } + + + const val serializationHash = 8567129171874407469L + + } + override val serializersOwner: ISerializersOwner get() = InstrumentedProcessModel + override val serializationHash: Long get() = InstrumentedProcessModel.serializationHash + + //fields + + /** + * The main process tells where the instrumented process should search for the classes + */ + val addPaths: RdCall get() = _addPaths + + /** + * Load classes from classpath and instrument them + */ + val warmup: RdCall get() = _warmup + + /** + * The main process sends [instrumentation] to the instrumented process + */ + val setInstrumentation: RdCall get() = _setInstrumentation + + /** + * The main process requests the instrumented process to execute a method with the given [signature], + which declaring class's name is [className]. + @property parameters are the parameters needed for an execution, e.g. static environment + */ + val invokeMethodCommand: RdCall get() = _invokeMethodCommand + + /** + * This command is sent to the instrumented process from the [ConcreteExecutor] if user wants to collect coverage for the + [clazz] + */ + val collectCoverage: RdCall get() = _collectCoverage + + /** + * This command is sent to the instrumented process from the [ConcreteExecutor] if user wants to get value of static field + [fieldId] + */ + val computeStaticField: RdCall get() = _computeStaticField + + /** + * Gets a list of [SpringRepositoryId]s that class specified by the [ClassId] (possibly indirectly) depends on (requires Spring instrumentation) + */ + val getRelevantSpringRepositories: RdCall get() = _getRelevantSpringRepositories + + /** + * This command is sent to the instrumented process from the [ConcreteExecutor] + if the user wants to determine whether or not Spring application context can load + */ + val tryLoadingSpringContext: RdCall get() = _tryLoadingSpringContext + //methods + //initializer + init { + _addPaths.async = true + _warmup.async = true + _setInstrumentation.async = true + _invokeMethodCommand.async = true + _collectCoverage.async = true + _computeStaticField.async = true + _getRelevantSpringRepositories.async = true + _tryLoadingSpringContext.async = true + } + + init { + bindableChildren.add("addPaths" to _addPaths) + bindableChildren.add("warmup" to _warmup) + bindableChildren.add("setInstrumentation" to _setInstrumentation) + bindableChildren.add("invokeMethodCommand" to _invokeMethodCommand) + bindableChildren.add("collectCoverage" to _collectCoverage) + bindableChildren.add("computeStaticField" to _computeStaticField) + bindableChildren.add("getRelevantSpringRepositories" to _getRelevantSpringRepositories) + bindableChildren.add("tryLoadingSpringContext" to _tryLoadingSpringContext) + } + + //secondary constructor + private constructor( + ) : this( + RdCall(AddPathsParams, FrameworkMarshallers.Void), + RdCall(FrameworkMarshallers.Void, FrameworkMarshallers.Void), + RdCall(SetInstrumentationParams, FrameworkMarshallers.Void), + RdCall(InvokeMethodCommandParams, InvokeMethodCommandResult), + RdCall(CollectCoverageParams, CollectCoverageResult), + RdCall(ComputeStaticFieldParams, ComputeStaticFieldResult), + RdCall(GetSpringRepositoriesParams, GetSpringRepositoriesResult), + RdCall(FrameworkMarshallers.Void, TryLoadingSpringContextResult) + ) + + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("InstrumentedProcessModel (") + printer.indent { + print("addPaths = "); _addPaths.print(printer); println() + print("warmup = "); _warmup.print(printer); println() + print("setInstrumentation = "); _setInstrumentation.print(printer); println() + print("invokeMethodCommand = "); _invokeMethodCommand.print(printer); println() + print("collectCoverage = "); _collectCoverage.print(printer); println() + print("computeStaticField = "); _computeStaticField.print(printer); println() + print("getRelevantSpringRepositories = "); _getRelevantSpringRepositories.print(printer); println() + print("tryLoadingSpringContext = "); _tryLoadingSpringContext.print(printer); println() + } + printer.print(")") + } + //deepClone + override fun deepClone(): InstrumentedProcessModel { + return InstrumentedProcessModel( + _addPaths.deepClonePolymorphic(), + _warmup.deepClonePolymorphic(), + _setInstrumentation.deepClonePolymorphic(), + _invokeMethodCommand.deepClonePolymorphic(), + _collectCoverage.deepClonePolymorphic(), + _computeStaticField.deepClonePolymorphic(), + _getRelevantSpringRepositories.deepClonePolymorphic(), + _tryLoadingSpringContext.deepClonePolymorphic() + ) + } + //contexts +} +val IProtocol.instrumentedProcessModel get() = getOrCreateExtension(InstrumentedProcessModel::class) { @Suppress("DEPRECATION") InstrumentedProcessModel.create(lifetime, this) } + + + +/** + * #### Generated from [InstrumentedProcessModel.kt:9] + */ +data class AddPathsParams ( + val pathsToUserClasses: String +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = AddPathsParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): AddPathsParams { + val pathsToUserClasses = buffer.readString() + return AddPathsParams(pathsToUserClasses) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: AddPathsParams) { + buffer.writeString(value.pathsToUserClasses) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as AddPathsParams + + if (pathsToUserClasses != other.pathsToUserClasses) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + pathsToUserClasses.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("AddPathsParams (") + printer.indent { + print("pathsToUserClasses = "); pathsToUserClasses.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [InstrumentedProcessModel.kt:29] + */ +data class CollectCoverageParams ( + val clazz: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = CollectCoverageParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): CollectCoverageParams { + val clazz = buffer.readByteArray() + return CollectCoverageParams(clazz) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: CollectCoverageParams) { + buffer.writeByteArray(value.clazz) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as CollectCoverageParams + + if (!(clazz contentEquals other.clazz)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + clazz.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("CollectCoverageParams (") + printer.indent { + print("clazz = "); clazz.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [InstrumentedProcessModel.kt:33] + */ +data class CollectCoverageResult ( + val coverageInfo: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = CollectCoverageResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): CollectCoverageResult { + val coverageInfo = buffer.readByteArray() + return CollectCoverageResult(coverageInfo) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: CollectCoverageResult) { + buffer.writeByteArray(value.coverageInfo) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as CollectCoverageResult + + if (!(coverageInfo contentEquals other.coverageInfo)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + coverageInfo.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("CollectCoverageResult (") + printer.indent { + print("coverageInfo = "); coverageInfo.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [InstrumentedProcessModel.kt:37] + */ +data class ComputeStaticFieldParams ( + val fieldId: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = ComputeStaticFieldParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): ComputeStaticFieldParams { + val fieldId = buffer.readByteArray() + return ComputeStaticFieldParams(fieldId) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: ComputeStaticFieldParams) { + buffer.writeByteArray(value.fieldId) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as ComputeStaticFieldParams + + if (!(fieldId contentEquals other.fieldId)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + fieldId.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("ComputeStaticFieldParams (") + printer.indent { + print("fieldId = "); fieldId.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [InstrumentedProcessModel.kt:41] + */ +data class ComputeStaticFieldResult ( + val result: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = ComputeStaticFieldResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): ComputeStaticFieldResult { + val result = buffer.readByteArray() + return ComputeStaticFieldResult(result) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: ComputeStaticFieldResult) { + buffer.writeByteArray(value.result) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as ComputeStaticFieldResult + + if (!(result contentEquals other.result)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + result.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("ComputeStaticFieldResult (") + printer.indent { + print("result = "); result.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [InstrumentedProcessModel.kt:45] + */ +data class GetSpringRepositoriesParams ( + val classId: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = GetSpringRepositoriesParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): GetSpringRepositoriesParams { + val classId = buffer.readByteArray() + return GetSpringRepositoriesParams(classId) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GetSpringRepositoriesParams) { + buffer.writeByteArray(value.classId) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as GetSpringRepositoriesParams + + if (!(classId contentEquals other.classId)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + classId.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("GetSpringRepositoriesParams (") + printer.indent { + print("classId = "); classId.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [InstrumentedProcessModel.kt:49] + */ +data class GetSpringRepositoriesResult ( + val springRepositoryIds: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = GetSpringRepositoriesResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): GetSpringRepositoriesResult { + val springRepositoryIds = buffer.readByteArray() + return GetSpringRepositoriesResult(springRepositoryIds) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GetSpringRepositoriesResult) { + buffer.writeByteArray(value.springRepositoryIds) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as GetSpringRepositoriesResult + + if (!(springRepositoryIds contentEquals other.springRepositoryIds)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + springRepositoryIds.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("GetSpringRepositoriesResult (") + printer.indent { + print("springRepositoryIds = "); springRepositoryIds.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [InstrumentedProcessModel.kt:18] + */ +data class InvokeMethodCommandParams ( + val classname: String, + val signature: String, + val arguments: ByteArray, + val parameters: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = InvokeMethodCommandParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): InvokeMethodCommandParams { + val classname = buffer.readString() + val signature = buffer.readString() + val arguments = buffer.readByteArray() + val parameters = buffer.readByteArray() + return InvokeMethodCommandParams(classname, signature, arguments, parameters) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: InvokeMethodCommandParams) { + buffer.writeString(value.classname) + buffer.writeString(value.signature) + buffer.writeByteArray(value.arguments) + buffer.writeByteArray(value.parameters) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as InvokeMethodCommandParams + + if (classname != other.classname) return false + if (signature != other.signature) return false + if (!(arguments contentEquals other.arguments)) return false + if (!(parameters contentEquals other.parameters)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + classname.hashCode() + __r = __r*31 + signature.hashCode() + __r = __r*31 + arguments.contentHashCode() + __r = __r*31 + parameters.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("InvokeMethodCommandParams (") + printer.indent { + print("classname = "); classname.print(printer); println() + print("signature = "); signature.print(printer); println() + print("arguments = "); arguments.print(printer); println() + print("parameters = "); parameters.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [InstrumentedProcessModel.kt:25] + */ +data class InvokeMethodCommandResult ( + val result: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = InvokeMethodCommandResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): InvokeMethodCommandResult { + val result = buffer.readByteArray() + return InvokeMethodCommandResult(result) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: InvokeMethodCommandResult) { + buffer.writeByteArray(value.result) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as InvokeMethodCommandResult + + if (!(result contentEquals other.result)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + result.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("InvokeMethodCommandResult (") + printer.indent { + print("result = "); result.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [InstrumentedProcessModel.kt:13] + */ +data class SetInstrumentationParams ( + val instrumentation: ByteArray, + val useBytecodeTransformation: Boolean +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = SetInstrumentationParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SetInstrumentationParams { + val instrumentation = buffer.readByteArray() + val useBytecodeTransformation = buffer.readBool() + return SetInstrumentationParams(instrumentation, useBytecodeTransformation) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SetInstrumentationParams) { + buffer.writeByteArray(value.instrumentation) + buffer.writeBool(value.useBytecodeTransformation) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as SetInstrumentationParams + + if (!(instrumentation contentEquals other.instrumentation)) return false + if (useBytecodeTransformation != other.useBytecodeTransformation) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + instrumentation.contentHashCode() + __r = __r*31 + useBytecodeTransformation.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SetInstrumentationParams (") + printer.indent { + print("instrumentation = "); instrumentation.print(printer); println() + print("useBytecodeTransformation = "); useBytecodeTransformation.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [InstrumentedProcessModel.kt:53] + */ +data class TryLoadingSpringContextResult ( + val springContextLoadingResult: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = TryLoadingSpringContextResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): TryLoadingSpringContextResult { + val springContextLoadingResult = buffer.readByteArray() + return TryLoadingSpringContextResult(springContextLoadingResult) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: TryLoadingSpringContextResult) { + buffer.writeByteArray(value.springContextLoadingResult) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as TryLoadingSpringContextResult + + if (!(springContextLoadingResult contentEquals other.springContextLoadingResult)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + springContextLoadingResult.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("TryLoadingSpringContextResult (") + printer.indent { + print("springContextLoadingResult = "); springContextLoadingResult.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessRoot.Generated.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessRoot.Generated.kt new file mode 100644 index 0000000000..3ef312e0c8 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/rd/generated/InstrumentedProcessRoot.Generated.kt @@ -0,0 +1,59 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.instrumentation.process.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [InstrumentedProcessModel.kt:6] + */ +class InstrumentedProcessRoot private constructor( +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + InstrumentedProcessRoot.register(serializers) + InstrumentedProcessModel.register(serializers) + } + + + + + + const val serializationHash = -7874463878801458679L + + } + override val serializersOwner: ISerializersOwner get() = InstrumentedProcessRoot + override val serializationHash: Long get() = InstrumentedProcessRoot.serializationHash + + //fields + //methods + //initializer + //secondary constructor + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("InstrumentedProcessRoot (") + printer.print(")") + } + //deepClone + override fun deepClone(): InstrumentedProcessRoot { + return InstrumentedProcessRoot( + ) + } + //contexts +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/AccessibleTypesAnalyzer.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/AccessibleTypesAnalyzer.kt new file mode 100644 index 0000000000..5c4fd07e5f --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/AccessibleTypesAnalyzer.kt @@ -0,0 +1,37 @@ +package org.utbot.instrumentation.util + +import org.utbot.common.withAccessibility +import java.util.IdentityHashMap + +class AccessibleTypesAnalyzer { + fun collectAccessibleTypes(bean: Any): Set> { + val accessibleObjects = collectFromObject(bean, analyzedObjects = IdentityHashMap()) + + return accessibleObjects + .map { it::class.java } + .toSet() + } + + private fun collectFromObject(obj: Any, analyzedObjects: IdentityHashMap): Set { + if (analyzedObjects.contains(obj)) { + return emptySet() + } + + analyzedObjects[obj] = Unit + + val clazz = obj::class.java + val objects = mutableSetOf() + + var current: Class<*> = clazz + while (current.superclass != null) { + objects.addAll(current.declaredFields.mapNotNull { + it.withAccessibility { get(obj) } + }.flatMap { + listOf(it) + collectFromObject(it, analyzedObjects) + }) + current = current.superclass + } + + return objects + } +} diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/InstrumentationException.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/InstrumentationException.kt index 259155fbdf..8a617f0168 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/InstrumentationException.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/InstrumentationException.kt @@ -1,31 +1,30 @@ -package org.utbot.instrumentation.util - -// TODO: refactor this - -/** - * Base class for instrumentation exceptions. - */ -open class InstrumentationException(msg: String?, cause: Throwable? = null) : Exception(msg, cause) - -class NoProbesArrayException(clazz: Class<*>, arrayName: String) : - InstrumentationException( - "No probes array found\n\t" + - "Clazz: $clazz\n\t" + - "Probes array name: $arrayName\n\t" + - "All fields: ${clazz.fields.joinToString { it.name }}" - ) - -class CastProbesArrayException : - InstrumentationException("Can't cast probes array to Boolean array") - -class ReadingFromKryoException(e: Throwable) : - InstrumentationException("Reading from Kryo exception |> ${e.stackTraceToString()}", e) - -class WritingToKryoException(e: Throwable) : - InstrumentationException("Writing to Kryo exception |> ${e.stackTraceToString()}", e) - -class ChildProcessError(e: Throwable) : - InstrumentationException("Error in the child process |> ${e.stackTraceToString()}", e) - -class UnexpectedCommand(cmd: Protocol.Command) : - InstrumentationException("Got unexpected command: $cmd") +package org.utbot.instrumentation.util + +import org.utbot.framework.plugin.api.InstrumentedProcessDeathException + +// TODO: refactor this + +/** + * Base class for instrumentation exceptions. + */ +open class InstrumentationException(msg: String?, cause: Throwable? = null) : Exception(msg, cause) + +class NoProbesArrayException(clazz: Class<*>, arrayName: String) : + InstrumentationException( + "No probes array found\n\t" + + "Clazz: $clazz\n\t" + + "Probes array name: $arrayName\n\t" + + "All fields: ${clazz.fields.joinToString { it.name }}" + ) + +class CastProbesArrayException : + InstrumentationException("Can't cast probes array to Boolean array") + +/** + * this exception is thrown only in main process. + * currently it means that {e: Throwable} happened in instrumented process, + * but instrumented process still can operate and not dead. + * on instrumented process death - [InstrumentedProcessDeathException] is thrown +*/ +class InstrumentedProcessError(e: Throwable) : + InstrumentationException("Error in the instrumented process |> ${e.stackTraceToString()}", e) \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/KryoHelper.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/KryoHelper.kt deleted file mode 100644 index 4c12005351..0000000000 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/KryoHelper.kt +++ /dev/null @@ -1,151 +0,0 @@ -package org.utbot.instrumentation.util - -import com.esotericsoftware.kryo.Kryo -import com.esotericsoftware.kryo.Serializer -import com.esotericsoftware.kryo.SerializerFactory -import com.esotericsoftware.kryo.io.Input -import com.esotericsoftware.kryo.io.Output -import com.esotericsoftware.kryo.serializers.JavaSerializer -import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy -import org.utbot.framework.plugin.api.TimeoutException -import de.javakaffee.kryoserializers.GregorianCalendarSerializer -import de.javakaffee.kryoserializers.JdkProxySerializer -import de.javakaffee.kryoserializers.SynchronizedCollectionsSerializer -import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer -import java.io.ByteArrayOutputStream -import java.io.Closeable -import java.io.InputStream -import java.io.OutputStream -import java.lang.reflect.InvocationHandler -import java.util.GregorianCalendar -import org.objenesis.instantiator.ObjectInstantiator -import org.objenesis.strategy.StdInstantiatorStrategy - -/** - * Helpful class for working with the kryo. - */ -class KryoHelper internal constructor( - inputStream: InputStream, - private val outputStream: OutputStream -) : Closeable { - private val temporaryBuffer = ByteArrayOutputStream() - - private val kryoOutput = Output(temporaryBuffer) - private val kryoInput = Input(inputStream) - - private val sendKryo: Kryo = TunedKryo() - private val receiveKryo: Kryo = TunedKryo() - - fun setKryoClassLoader(classLoader: ClassLoader) { - sendKryo.classLoader = classLoader - receiveKryo.classLoader = classLoader - } - - fun readLong(): Long { - return receiveKryo.readObject(kryoInput, Long::class.java) - } - - /** - * Kryo tries to write the [cmd] to the [temporaryBuffer]. - * If no exception occurs, the output is flushed to the [outputStream]. - * - * If an exception occurs, rethrows it wrapped in [WritingToKryoException]. - */ - fun writeCommand(id: Long, cmd: T) { - try { - sendKryo.writeObject(kryoOutput, id) - sendKryo.writeClassAndObject(kryoOutput, cmd) - kryoOutput.flush() - - temporaryBuffer.writeTo(outputStream) - outputStream.flush() - } catch (e: Exception) { - throw WritingToKryoException(e) - } finally { - kryoOutput.reset() - temporaryBuffer.reset() - } - } - - /** - * Kryo tries to read a command. - * - * If an exception occurs, rethrows it wrapped in [ReadingFromKryoException]. - * - * @return successfully read command. - */ - fun readCommand(): Protocol.Command = - try { - receiveKryo.readClassAndObject(kryoInput) as Protocol.Command - } catch (e: Exception) { - throw ReadingFromKryoException(e) - } - - override fun close() { - kryoInput.close() - kryoOutput.close() - outputStream.close() - } -} - -// This kryo is used to initialize collections properly. -internal class TunedKryo : Kryo() { - init { - this.references = true - this.isRegistrationRequired = false - - this.instantiatorStrategy = object : StdInstantiatorStrategy() { - // workaround for Collections as they cannot be correctly deserialized without calling constructor - val default = DefaultInstantiatorStrategy() - val classesBadlyDeserialized = listOf( - java.util.Queue::class.java, - java.util.HashSet::class.java - ) - - override fun newInstantiatorOf(type: Class): ObjectInstantiator { - return if (classesBadlyDeserialized.any { it.isAssignableFrom(type) }) { - @Suppress("UNCHECKED_CAST") - default.newInstantiatorOf(type) as ObjectInstantiator - } else { - super.newInstantiatorOf(type) - } - } - } - - register(GregorianCalendar::class.java, GregorianCalendarSerializer()) - register(InvocationHandler::class.java, JdkProxySerializer()) - register(TimeoutException::class.java, TimeoutExceptionSerializer()) - UnmodifiableCollectionsSerializer.registerSerializers(this) - SynchronizedCollectionsSerializer.registerSerializers(this) - - // TODO: JIRA:1492 - addDefaultSerializer(java.lang.Throwable::class.java, JavaSerializer()) - - val factory = object : SerializerFactory.FieldSerializerFactory() {} - factory.config.ignoreSyntheticFields = true - factory.config.serializeTransient = false - factory.config.fieldsCanBeNull = true - this.setDefaultSerializer(factory) - - // Registration of the classes of our protocol commands. - Protocol::class.nestedClasses.forEach { - register(it.java) - } - } - - /** - * Specific serializer for [TimeoutException] - [JavaSerializer] is not applicable - * because [TimeoutException] is not in class loader. - * - * This serializer is very simple - it just writes [TimeoutException.message] - * because we do not need other components. - */ - private class TimeoutExceptionSerializer : Serializer() { - override fun write(kryo: Kryo, output: Output, value: TimeoutException) { - output.writeString(value.message) - } - - override fun read(kryo: Kryo?, input: Input, type: Class?): TimeoutException = - TimeoutException(input.readString()) - } -} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/Protocol.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/Protocol.kt deleted file mode 100644 index fd58ff3342..0000000000 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/Protocol.kt +++ /dev/null @@ -1,89 +0,0 @@ -package org.utbot.instrumentation.util - -import org.utbot.instrumentation.instrumentation.ArgumentList -import org.utbot.instrumentation.instrumentation.Instrumentation - -/** - * This object represents base commands for interprocess communication. - */ -object Protocol { - /** - * Abstract class for all commands. - */ - abstract class Command - - - /** - * The child process sends this command to the main process to indicate readiness. - */ - class ProcessReadyCommand : Command() - - /** - * The main process tells where the child process should search for the classes. - */ - data class AddPathsCommand( - val pathsToUserClasses: String, - val pathsToDependencyClasses: String - ) : Command() - - - /** - * The main process sends [instrumentation] to the child process. - */ - data class SetInstrumentationCommand( - val instrumentation: Instrumentation - ) : Command() - - /** - * The main process requests the child process to execute a method with the given [signature], - * which declaring class's name is [className]. - * - * @property parameters are the parameters needed for an execution, e.g. static environment. - */ - data class InvokeMethodCommand( - val className: String, - val signature: String, - val arguments: ArgumentList, - val parameters: Any?, - ) : Command() - - /** - * The child process returns the result of the invocation to the main process. - */ - data class InvocationResultCommand( - val result: T - ) : Command() - - /** - * Warmup - load classes from classpath and instrument them - */ - class WarmupCommand() : Command() - - /** - * The child process sends this command if unexpected exception was thrown. - * - * @property exception unexpected exception. - */ - data class ExceptionInChildProcess( - val exception: Throwable - ) : Command() - - data class ExceptionInKryoCommand(val exception: Throwable) : Command() - - /** - * This command tells the child process to stop. - */ - class StopProcessCommand : Command() - - /** - * [org.utbot.instrumentation.ConcreteExecutor] can send other commands depending on specific instrumentation. - * This commands will be handled in [Instrumentation.handle] function. - * - * Only inheritors of this abstract class will be passed in [Instrumentation.handle] function. - */ - abstract class InstrumentationCommand : Protocol.Command() - - -} - - diff --git a/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/examples/mock/MockHelper.kt b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/examples/mock/MockHelper.kt index b6f1472771..b97879559b 100644 --- a/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/examples/mock/MockHelper.kt +++ b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/examples/mock/MockHelper.kt @@ -1,14 +1,14 @@ package org.utbot.instrumentation.examples.mock -import org.utbot.common.withRemovedFinalModifier -import org.utbot.framework.plugin.api.util.signature -import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter -import org.utbot.instrumentation.instrumentation.mock.MockClassVisitor -import org.utbot.instrumentation.instrumentation.mock.MockConfig import java.lang.reflect.Method import java.util.IdentityHashMap import kotlin.reflect.jvm.javaMethod import org.objectweb.asm.Type +import org.utbot.common.withAccessibility +import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter +import org.utbot.instrumentation.instrumentation.mock.MockClassVisitor +import org.utbot.instrumentation.instrumentation.mock.MockConfig +import org.utbot.instrumentation.instrumentation.mock.computeKeyForMethod /** * Helper for generating tests with methods mocks. @@ -52,13 +52,13 @@ class MockHelper( error("Can't mock function returning void!") } - val sign = method.signature - val methodId = mockClassVisitor.signatureToId[sign] + val computedSignature = computeKeyForMethod(method) + val methodId = mockClassVisitor.signatureToId[computedSignature] val isMockField = instrumentedClazz.getDeclaredField(MockConfig.IS_MOCK_FIELD + methodId) MockGetter.updateMocks(instance, method, mockedValues) - return isMockField.withRemovedFinalModifier { + return isMockField.withAccessibility { isMockField.set(instance, true) val res = block(instance) isMockField.set(instance, false) @@ -129,7 +129,7 @@ class MockHelper( } fun updateMocks(obj: Any?, method: Method, values: List<*>) { - updateMocks(obj, method.signature, values) + updateMocks(obj, computeKeyForMethod(method), values) } } } \ No newline at end of file diff --git a/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/examples/mock/TestConstructorMock.kt b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/examples/mock/TestConstructorMock.kt index bb39ea4a41..22c3f3e255 100644 --- a/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/examples/mock/TestConstructorMock.kt +++ b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/examples/mock/TestConstructorMock.kt @@ -1,6 +1,6 @@ package org.utbot.instrumentation.examples.mock -import org.utbot.common.withRemovedFinalModifier +import org.utbot.common.withAccessibility import org.utbot.instrumentation.samples.mock.ClassForMockConstructor import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertNotEquals @@ -11,11 +11,11 @@ import org.junit.jupiter.api.Test class TestConstructorMock { private fun checkFields(instance: Any, x: Int, s: String?) { val xField = instance::class.java.getDeclaredField("x") - xField.withRemovedFinalModifier { + xField.withAccessibility { assertEquals(x, xField.getInt(instance)) } val sField = instance::class.java.getDeclaredField("s") - sField.withRemovedFinalModifier { + sField.withAccessibility { assertEquals(s, sField.get(instance)) } } diff --git a/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BaseConstructorTest.kt b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BaseConstructorTest.kt new file mode 100644 index 0000000000..3410ae8cab --- /dev/null +++ b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/BaseConstructorTest.kt @@ -0,0 +1,42 @@ +package org.utbot.instrumentation.instrumentation.execution.constructors + +import org.utbot.framework.plugin.api.util.constructor.ValueConstructor +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.id +import java.util.IdentityHashMap +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.jClass + +abstract class BaseConstructorTest { + private lateinit var cookie: AutoCloseable + + @BeforeEach + fun setup() { + cookie = UtContext.setUtContext(UtContext(ClassLoader.getSystemClassLoader())) + } + + @AfterEach + fun tearDown() { + cookie.close() + } + + protected fun computeReconstructed(value: T): T { + val model = UtModelConstructor( + objectToModelCache = IdentityHashMap(), + idGenerator = StateBeforeAwareIdGenerator(allPreExistingModels = emptySet()), + utModelWithCompositeOriginConstructorFinder = ::findUtCustomModelConstructor + ).construct(value, value::class.java.id) + + Assertions.assertTrue(model is UtAssembleModel) + + @Suppress("UNCHECKED_CAST") + return ValueConstructor().construct(listOf(model)).single().value as T + } + + protected open fun findUtCustomModelConstructor(classId: ClassId): UtModelWithCompositeOriginConstructor? = + javaStdLibModelWithCompositeOriginConstructors[classId.jClass]?.invoke() +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/ListConstructorTest.kt b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/ListConstructorTest.kt similarity index 93% rename from utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/ListConstructorTest.kt rename to utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/ListConstructorTest.kt index e3ef59049e..2b19fe60cf 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/ListConstructorTest.kt +++ b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/ListConstructorTest.kt @@ -1,4 +1,4 @@ -package org.utbot.framework.concrete.constructors +package org.utbot.instrumentation.instrumentation.execution.constructors import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/MapConstructorTest.kt b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MapConstructorTest.kt similarity index 92% rename from utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/MapConstructorTest.kt rename to utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MapConstructorTest.kt index e756e02942..919bdb5a71 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/MapConstructorTest.kt +++ b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MapConstructorTest.kt @@ -1,4 +1,4 @@ -package org.utbot.framework.concrete.constructors +package org.utbot.instrumentation.instrumentation.execution.constructors import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/OptionalConstructorTest.kt b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/OptionalConstructorTest.kt similarity index 77% rename from utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/OptionalConstructorTest.kt rename to utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/OptionalConstructorTest.kt index c4d600b5f5..b2af99a68c 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/OptionalConstructorTest.kt +++ b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/OptionalConstructorTest.kt @@ -1,19 +1,10 @@ -package org.utbot.framework.concrete.constructors - -import org.utbot.engine.ValueConstructor -import org.utbot.framework.concrete.UtModelConstructor -import org.utbot.framework.plugin.api.UtAssembleModel -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.id -import java.util.IdentityHashMap +package org.utbot.instrumentation.instrumentation.execution.constructors + import java.util.Optional import java.util.OptionalDouble import java.util.OptionalInt import java.util.OptionalLong -import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test class OptionalConstructorTest : BaseConstructorTest() { diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/SetConstructorTest.kt b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/SetConstructorTest.kt similarity index 91% rename from utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/SetConstructorTest.kt rename to utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/SetConstructorTest.kt index 56cfe25ff9..1081f21542 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/concrete/constructors/SetConstructorTest.kt +++ b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/SetConstructorTest.kt @@ -1,4 +1,4 @@ -package org.utbot.framework.concrete.constructors +package org.utbot.instrumentation.instrumentation.execution.constructors import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test diff --git a/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/security/SecurityTest.kt b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/security/SecurityTest.kt new file mode 100644 index 0000000000..517b26118c --- /dev/null +++ b/utbot-instrumentation/src/test/kotlin/org/utbot/instrumentation/security/SecurityTest.kt @@ -0,0 +1,59 @@ +package org.utbot.instrumentation.security + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.utbot.instrumentation.process.permissions +import org.utbot.instrumentation.process.sandbox +import java.lang.NullPointerException +import java.security.AccessControlException +import java.security.AllPermission +import java.security.BasicPermission +import java.security.CodeSource +import java.security.Permission +import java.security.cert.Certificate +import java.util.PropertyPermission + +class SecurityTest { + + @BeforeEach + fun init() { + permissions { + +AllPermission() + } + } + + @Test + fun `basic security works`() { + sandbox { + assertThrows { + System.getProperty("any") + } + } + } + + @Test + fun `basic permission works`() { + sandbox(listOf(PropertyPermission("java.version", "read")), CodeSource(null, emptyArray())) { + val result = System.getProperty("java.version") + assertNotNull(result) + assertThrows { + System.setProperty("any_random_value_key", "random") + } + } + } + + @Test + fun `null is ok`() { + val empty = object : BasicPermission("*") {} + val field = Permission::class.java.getDeclaredField("name") + field.isAccessible = true + field.set(empty, null) + val collection = empty.newPermissionCollection() + assertThrows { + collection.implies(empty) + } + } + +} \ No newline at end of file diff --git a/utbot-intellij-go/build.gradle.kts b/utbot-intellij-go/build.gradle.kts new file mode 100644 index 0000000000..fff30caba1 --- /dev/null +++ b/utbot-intellij-go/build.gradle.kts @@ -0,0 +1,125 @@ +val intellijPluginVersion: String? by rootProject +val kotlinLoggingVersion: String? by rootProject +val apacheCommonsTextVersion: String? by rootProject +val jacksonVersion: String? by rootProject +val kotlinPluginVersion: String by rootProject + +// === IDE settings === +val projectType: String by rootProject +val communityEdition: String by rootProject +val ultimateEdition: String by rootProject + +val ideType: String by rootProject +val androidStudioPath: String? by rootProject + +val ideaVersion: String? by rootProject +val pycharmVersion: String? by rootProject +val golandVersion: String? by rootProject + +val javaIde: String? by rootProject +val pythonIde: String? by rootProject +val jsIde: String? by rootProject +val goIde: String? by rootProject + +val ideVersion = when(ideType) { + "PC", "PY" -> pycharmVersion + "GO" -> golandVersion + else -> ideaVersion +} + +val pythonCommunityPluginVersion: String? by rootProject +val pythonUltimatePluginVersion: String? by rootProject +val goPluginVersion: String? by rootProject + +// https://plugins.jetbrains.com/docs/intellij/android-studio.html#configuring-the-plugin-pluginxml-file +val ideTypeOrAndroidStudio = if (androidStudioPath == null) ideType else "IC" + +project.tasks.asMap["runIde"]?.enabled = false +// === IDE settings === + +plugins { + id("org.jetbrains.intellij") version "1.13.1" +} + +tasks { + compileKotlin { + kotlinOptions { + jvmTarget = "17" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class") + allWarningsAsErrors = false + } + } + + java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + test { + useJUnitPlatform() + } +} + +dependencies { + testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") + implementation(project(":utbot-ui-commons")) + + //Family + implementation(project(":utbot-go")) +} + +intellij { + + val androidPlugins = listOf("org.jetbrains.android") + + val jvmPlugins = mutableListOf( + "java" + ) + + val kotlinPlugins = listOf( + "org.jetbrains.kotlin" + ) + + androidStudioPath?.let { jvmPlugins += androidPlugins } + + val pythonCommunityPlugins = listOf( + "PythonCore:${pythonCommunityPluginVersion}" + ) + + val pythonUltimatePlugins = listOf( + "Pythonid:${pythonUltimatePluginVersion}" + ) + + val jsPlugins = listOf( + "JavaScript" + ) + + val goPlugins = listOf( + "org.jetbrains.plugins.go:${goPluginVersion}" + ) + + val mavenUtilsPlugins = listOf( + "org.jetbrains.idea.maven" + ) + + val basePluginSet = jvmPlugins + kotlinPlugins + mavenUtilsPlugins + androidPlugins + + plugins.set( + when (projectType) { + communityEdition -> basePluginSet + pythonCommunityPlugins + ultimateEdition -> when (ideType) { + "IC" -> basePluginSet + pythonCommunityPlugins + "IU" -> basePluginSet + pythonUltimatePlugins + jsPlugins + goPlugins + "PC" -> pythonCommunityPlugins + "PY" -> pythonUltimatePlugins + jsPlugins + "GO" -> goPlugins + else -> basePluginSet + } + else -> basePluginSet + } + ) + + version.set(ideVersion) + type.set(ideType) +} \ No newline at end of file diff --git a/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/generator/GoUtTestsCodeFileWriter.kt b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/generator/GoUtTestsCodeFileWriter.kt new file mode 100644 index 0000000000..19b81b6421 --- /dev/null +++ b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/generator/GoUtTestsCodeFileWriter.kt @@ -0,0 +1,61 @@ +package org.utbot.intellij.plugin.go.generator + +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.fileEditor.OpenFileDescriptor +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiFileFactory +import com.intellij.psi.PsiManager +import com.intellij.util.IncorrectOperationException +import org.utbot.go.api.GoUtFile +import org.utbot.intellij.plugin.go.language.GoLanguageAssistant +import org.utbot.intellij.plugin.go.models.GenerateGoTestsModel +import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import java.nio.file.Paths + +// This class is highly inspired by CodeGenerationController. +object GoUtTestsCodeFileWriter { + + fun createTestsFileWithGeneratedCode( + model: GenerateGoTestsModel, + sourceFile: GoUtFile, + generatedTestsFileCode: String + ) { + val testsFileName = createTestsFileName(sourceFile) + try { + runWriteAction { + val sourcePsiFile = findPsiFile(model, sourceFile) + val sourceFileDir = sourcePsiFile.containingDirectory + + val testsFileNameWithExtension = "$testsFileName.go" + val testPsiFile = PsiFileFactory.getInstance(model.project) + .createFileFromText( + testsFileNameWithExtension, GoLanguageAssistant.language, generatedTestsFileCode + ) + sourceFileDir.findFile(testsFileNameWithExtension)?.delete() + sourceFileDir.add(testPsiFile) + + val testFile = sourceFileDir.findFile(testsFileNameWithExtension)!! + OpenFileDescriptor(model.project, testFile.virtualFile).navigate(true) + } + } catch (e: IncorrectOperationException) { + showCreatingFileError(model.project, testsFileName) + } + } + + private fun findPsiFile(model: GenerateGoTestsModel, sourceFile: GoUtFile): PsiFile { + val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(Paths.get(sourceFile.absolutePath))!! + return PsiManager.getInstance(model.project).findFile(virtualFile)!! + } + + private fun createTestsFileName(sourceFile: GoUtFile) = sourceFile.fileNameWithoutExtension + "_go_ut_test" + + private fun showCreatingFileError(project: Project, testFileName: String) { + showErrorDialogLater( + project, + message = "Cannot Create File '$testFileName'", + title = "Failed to Create File" + ) + } +} \ No newline at end of file diff --git a/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/generator/GoUtTestsDialogProcessor.kt b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/generator/GoUtTestsDialogProcessor.kt new file mode 100644 index 0000000000..b5c1fcdd9d --- /dev/null +++ b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/generator/GoUtTestsDialogProcessor.kt @@ -0,0 +1,138 @@ +package org.utbot.intellij.plugin.go.generator + +import com.goide.project.DefaultGoRootsProvider +import com.goide.psi.GoFunctionOrMethodDeclaration +import com.goide.sdk.GoSdk +import com.goide.sdk.GoSdkService +import com.goide.sdk.GoSdkVersion +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.module.Module +import com.intellij.openapi.options.ShowSettingsUtil +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages +import org.utbot.go.gocodeanalyzer.GoParsingSourceCodeAnalysisResultException +import org.utbot.go.logic.GoUtTestsGenerationConfig +import org.utbot.go.worker.GoWorkerFailedException +import org.utbot.intellij.plugin.go.models.GenerateGoTestsModel +import org.utbot.intellij.plugin.go.ui.GenerateGoTestsDialogWindow +import org.utbot.intellij.plugin.go.ui.utils.resolveGoExecutablePath +import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import java.nio.file.Paths + +object GoUtTestsDialogProcessor { + + private const val helpMessage: String = + "Please try running \"go mod tidy\" in one of the project directories or fix any errors in the code." + + fun createDialogAndGenerateTests( + project: Project, + module: Module, + targetFunctions: Set, + focusedTargetFunctions: Set, + ) { + createDialog(project, module, targetFunctions, focusedTargetFunctions)?.let { + if (it.showAndGet()) createTests(it.model) + } + } + + private fun createDialog( + project: Project, + module: Module, + targetFunctions: Set, + focusedTargetFunctions: Set, + ): GenerateGoTestsDialogWindow? { + val goSdk = GoSdkService.getInstance(project).getSdk(module) + if (goSdk == GoSdk.NULL) { + val result = Messages.showOkCancelDialog( + project, + "GOROOT is not defined. Select it?", + "Unsupported Go SDK", + "Select", + "Cancel", + Messages.getErrorIcon() + ) + if (result == Messages.OK) { + ShowSettingsUtil.getInstance().showSettingsDialog(project, "GOROOT") + } + return null + } else if (!goSdk.isValid || GoSdkVersion.fromText(goSdk.version).isLessThan(GoSdkVersion.GO_1_18)) { + val result = Messages.showOkCancelDialog( + project, + "Go SDK isn't valid or version less than 1.18. Select another SDK?", + "Unsupported Go SDK", + "Select", + "Cancel", + Messages.getErrorIcon() + ) + if (result == Messages.OK) { + ShowSettingsUtil.getInstance().showSettingsDialog(project, "GOROOT") + } + return null + } + + val goPath = DefaultGoRootsProvider().getGoPathRoots(project, module).first().path + return GenerateGoTestsDialogWindow( + GenerateGoTestsModel( + project, + goExecutableAbsolutePath = Paths.get(goSdk.resolveGoExecutablePath()!!).toAbsolutePath(), + gopathAbsolutePath = Paths.get(goPath).toAbsolutePath(), + targetFunctions, + focusedTargetFunctions, + ) + ) + } + + private fun buildErrorMessage(exception: Exception): String = + if (exception.message == null) { + helpMessage + } else { + buildString { + appendLine(exception.message) + appendLine(helpMessage) + } + } + + private fun createTests(model: GenerateGoTestsModel) { + ProgressManager.getInstance().run(object : Task.Backgroundable(model.project, "Generate Go tests") { + override fun run(indicator: ProgressIndicator) { + // readAction is required to read PSI-tree or else "Read access" exception occurs. + val (selectedFunctionNamesBySourceFiles, selectedMethodNamesBySourceFiles) = runReadAction { + model.selectedFunctions.groupBy({ Paths.get(it.containingFile.virtualFile.path) }) { it.name!! } to + model.selectedMethods.groupBy({ Paths.get(it.containingFile.virtualFile.path) }) { it.name!! } + } + val testsGenerationConfig = GoUtTestsGenerationConfig( + model.goExecutableAbsolutePath, + model.gopathAbsolutePath, + model.numberOfFuzzingProcess, + model.mode, + model.eachFunctionExecutionTimeoutMillis, + model.allFunctionExecutionTimeoutMillis + ) + + try { + IntellijGoUtTestsGenerationController(model, indicator).generateTests( + selectedFunctionNamesBySourceFiles, + selectedMethodNamesBySourceFiles, + testsGenerationConfig + ) { indicator.isCanceled } + } catch (e: GoParsingSourceCodeAnalysisResultException) { + val errorMessage = buildErrorMessage(e) + showErrorDialogLater( + model.project, + errorMessage, + title = "Unit tests generation is cancelled" + ) + } catch (e: GoWorkerFailedException) { + showErrorDialogLater( + model.project, + helpMessage, + title = "Unit tests generation is cancelled" + ) + } + } + }) + } +} \ No newline at end of file diff --git a/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/generator/IntellijGoUtTestsGenerationController.kt b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/generator/IntellijGoUtTestsGenerationController.kt new file mode 100644 index 0000000000..b5a89b4c17 --- /dev/null +++ b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/generator/IntellijGoUtTestsGenerationController.kt @@ -0,0 +1,153 @@ +package org.utbot.intellij.plugin.go.generator + +import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.progress.ProgressIndicator +import org.utbot.go.api.GoUtFile +import org.utbot.go.api.GoUtFunction +import org.utbot.go.api.GoUtFuzzedFunctionTestCase +import org.utbot.go.gocodeanalyzer.GoSourceCodeAnalyzer +import org.utbot.go.logic.AbstractGoUtTestsGenerationController +import org.utbot.intellij.plugin.go.models.GenerateGoTestsModel +import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import org.utbot.intellij.plugin.ui.utils.showWarningDialogLater +import java.nio.file.Path + +class IntellijGoUtTestsGenerationController( + private val model: GenerateGoTestsModel, + private val indicator: ProgressIndicator +) : AbstractGoUtTestsGenerationController() { + + private object ProgressIndicatorConstants { + const val START_FRACTION = 0.05 // is needed to prevent infinite indicator that appears for 0.0 + const val SOURCE_CODE_ANALYSIS_FRACTION = 0.25 + const val TEST_CASES_CODE_GENERATION_FRACTION = 0.1 + const val TEST_CASES_GENERATION_FRACTION = + 1.0 - SOURCE_CODE_ANALYSIS_FRACTION - TEST_CASES_CODE_GENERATION_FRACTION + } + + private lateinit var testCasesGenerationCounter: ProcessedFilesCounter + private lateinit var testCasesCodeGenerationCounter: ProcessedFilesCounter + + private data class ProcessedFilesCounter( + private val toProcessTotalFilesNumber: Int, + private var processedFiles: Int = 0 + ) { + val processedFilesRatio: Double get() = processedFiles.toDouble() / toProcessTotalFilesNumber + fun addProcessedFile() { + processedFiles++ + } + } + + override fun onSourceCodeAnalysisStart( + targetFunctionNamesBySourceFiles: Map>, + targetMethodNamesBySourceFiles: Map> + ): Boolean { + indicator.isIndeterminate = false + indicator.text = "Analyze source files" + indicator.fraction = ProgressIndicatorConstants.START_FRACTION + return true + } + + override fun onSourceCodeAnalysisFinished( + analysisResults: Map + ): Boolean { + indicator.fraction = indicator.fraction.coerceAtLeast( + ProgressIndicatorConstants.START_FRACTION + ProgressIndicatorConstants.SOURCE_CODE_ANALYSIS_FRACTION + ) + if (!handleMissingSelectedFunctions(analysisResults)) return false + + val filesToProcessTotalNumber = + analysisResults.count { (_, analysisResult) -> analysisResult.functions.isNotEmpty() } + testCasesGenerationCounter = ProcessedFilesCounter(filesToProcessTotalNumber) + testCasesCodeGenerationCounter = ProcessedFilesCounter(filesToProcessTotalNumber) + return true + } + + override fun onPackageInstrumentationStart(): Boolean { + return true + } + + override fun onPackageInstrumentationFinished(): Boolean { + return true + } + + override fun onTestCasesGenerationForGoSourceFileFunctionsStart( + sourceFile: GoUtFile, + functions: List + ): Boolean { + indicator.text = "Generate test cases for ${sourceFile.fileName}" + indicator.fraction = indicator.fraction.coerceAtLeast( + ProgressIndicatorConstants.START_FRACTION + + ProgressIndicatorConstants.SOURCE_CODE_ANALYSIS_FRACTION + + ProgressIndicatorConstants.TEST_CASES_GENERATION_FRACTION + * testCasesGenerationCounter.processedFilesRatio + ) + indicator.checkCanceled() // allow user to cancel possibly slow unit test generation + return true + } + + override fun onTestCasesGenerationForGoSourceFileFunctionsFinished( + sourceFile: GoUtFile, + testCases: List + ): Boolean { + testCasesGenerationCounter.addProcessedFile() + return true + } + + override fun onTestCasesFileCodeGenerationStart( + sourceFile: GoUtFile, + testCases: List + ): Boolean { + indicator.text = "Generate tests code for ${sourceFile.fileName}" + indicator.fraction = indicator.fraction.coerceAtLeast( + ProgressIndicatorConstants.START_FRACTION + + ProgressIndicatorConstants.SOURCE_CODE_ANALYSIS_FRACTION + + ProgressIndicatorConstants.TEST_CASES_GENERATION_FRACTION + + ProgressIndicatorConstants.TEST_CASES_CODE_GENERATION_FRACTION + * testCasesCodeGenerationCounter.processedFilesRatio + ) + return true + } + + override fun onTestCasesFileCodeGenerationFinished( + sourceFile: GoUtFile, + generatedTestsFileCode: String + ): Boolean { + invokeLater { + GoUtTestsCodeFileWriter.createTestsFileWithGeneratedCode(model, sourceFile, generatedTestsFileCode) + } + testCasesCodeGenerationCounter.addProcessedFile() + return true + } + + private fun handleMissingSelectedFunctions( + analysisResults: Map + ): Boolean { + val missingSelectedFunctionsListMessage = generateMissingSelectedFunctionsListMessage(analysisResults) + val okSelectedFunctionsArePresent = + analysisResults.any { (_, analysisResult) -> analysisResult.functions.isNotEmpty() } + + if (missingSelectedFunctionsListMessage == null) { + return okSelectedFunctionsArePresent + } + + val errorMessageSb = StringBuilder() + .append("Some selected functions were skipped during source code analysis.") + .append(missingSelectedFunctionsListMessage) + if (okSelectedFunctionsArePresent) { + showWarningDialogLater( + model.project, + errorMessageSb.append("Unit test generation for other selected functions will be performed as usual.") + .toString(), + title = "Skipped some functions for unit tests generation" + ) + } else { + showErrorDialogLater( + model.project, + errorMessageSb.append("Unit test generation is cancelled: no other selected functions.").toString(), + title = "Unit tests generation is cancelled" + ) + } + return okSelectedFunctionsArePresent + } +} \ No newline at end of file diff --git a/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/language/GoLanguageAssistant.kt b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/language/GoLanguageAssistant.kt new file mode 100644 index 0000000000..f88c243ab8 --- /dev/null +++ b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/language/GoLanguageAssistant.kt @@ -0,0 +1,104 @@ +package org.utbot.intellij.plugin.go.language + +import com.goide.psi.* +import com.intellij.lang.Language +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiTreeUtil +import org.utbot.intellij.plugin.language.agnostic.LanguageAssistant +import org.utbot.intellij.plugin.go.generator.GoUtTestsDialogProcessor + +@Suppress("unused") // is used in org.utbot.intellij.plugin.language.agnostic.LanguageAssistant via reflection +object GoLanguageAssistant : LanguageAssistant() { + + private const val goId = "go" + val language: Language = Language.findLanguageByID(goId) ?: error("Go language wasn't found") + + private data class PsiTargets( + val targetFunctions: Set, + val focusedTargetFunctions: Set, + ) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val file = e.getData(CommonDataKeys.PSI_FILE) as? GoFile ?: return + val module = ModuleUtilCore.findModuleForFile(file) ?: return + val (targetFunctions, focusedTargetFunctions) = getPsiTargets(e) ?: return + GoUtTestsDialogProcessor.createDialogAndGenerateTests( + project, + module, + targetFunctions, + focusedTargetFunctions + ) + } + + override fun update(e: AnActionEvent) { + e.presentation.isEnabled = getPsiTargets(e) != null + } + + private fun getPsiTargets(e: AnActionEvent): PsiTargets? { + e.project ?: return null + + val editor = e.getData(CommonDataKeys.EDITOR) + val file = e.getData(CommonDataKeys.PSI_FILE) as? GoFile ?: return null + val element = if (editor != null) { + findPsiElement(file, editor) ?: return null + } else { + e.getData(CommonDataKeys.PSI_ELEMENT) ?: return null + } + + val targetFunctions = extractTargetFunctionsOrMethods(file) + val containingFunctionOrMethod = getContainingFunctionOrMethod(element) + val containingStruct = getContainingStruct(element) + val focusedTargetFunctions = if (containingFunctionOrMethod != null) { + setOf(containingFunctionOrMethod) + } else { + if (containingStruct != null) { + targetFunctions.filterIsInstance() + .filter { containingStruct == getMethodReceiverStruct(it) }.toSet() + } else { + emptySet() + } + } + + return PsiTargets(targetFunctions, focusedTargetFunctions) + } + + private fun extractTargetFunctionsOrMethods(file: GoFile): Set { + return file.functions.toSet() union file.methods.toSet() + } + + private fun getContainingFunctionOrMethod(element: PsiElement): GoFunctionOrMethodDeclaration? { + if (element is GoFunctionOrMethodDeclaration) + return element + + val parent = element.parent ?: return null + return getContainingFunctionOrMethod(parent) + } + + private fun getContainingStruct(element: PsiElement): GoStructType? = + PsiTreeUtil.getParentOfType(element, GoStructType::class.java, false) + + private fun getMethodReceiverStruct(method: GoMethodDeclaration): GoStructType? { + val receiverType = method.receiverType?.contextlessUnderlyingType ?: return null + if (receiverType is GoPointerType) { + return receiverType.type?.contextlessUnderlyingType as? GoStructType + } + return receiverType as? GoStructType + } + + // This method is cloned from GenerateTestsActions.kt. + private fun findPsiElement(file: PsiFile, editor: Editor): PsiElement? { + val offset = editor.caretModel.offset + var element = file.findElementAt(offset) + if (element == null && offset == file.textLength) { + element = file.findElementAt(offset - 1) + } + + return element + } +} \ No newline at end of file diff --git a/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/models/GenerateGoTestsModel.kt b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/models/GenerateGoTestsModel.kt new file mode 100644 index 0000000000..0b62e84649 --- /dev/null +++ b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/models/GenerateGoTestsModel.kt @@ -0,0 +1,33 @@ +package org.utbot.intellij.plugin.go.models + +import com.goide.psi.GoFunctionDeclaration +import com.goide.psi.GoFunctionOrMethodDeclaration +import com.goide.psi.GoMethodDeclaration +import com.intellij.openapi.project.Project +import org.utbot.go.logic.GoUtTestsGenerationConfig +import org.utbot.go.logic.TestsGenerationMode +import java.nio.file.Path + +/** + * Contains information about Go tests generation task required for intellij plugin logic. + * + * targetFunctions: all possible functions to generate tests for; + * focusedTargetFunctions: such target functions that user is focused on while plugin execution; + * selectedFunctions: finally selected functions to generate tests for; + * goExecutableAbsolutePath: self-explanatory; + * eachFunctionExecutionTimeoutMillis: timeout in milliseconds for each fuzzed function execution. + */ +data class GenerateGoTestsModel( + val project: Project, + val goExecutableAbsolutePath: Path, + val gopathAbsolutePath: Path, + val targetFunctions: Set, + val focusedTargetFunctions: Set, +) { + lateinit var selectedFunctions: Set + lateinit var selectedMethods: Set + var numberOfFuzzingProcess: Int = GoUtTestsGenerationConfig.DEFAULT_NUMBER_OF_FUZZING_PROCESSES + var mode: TestsGenerationMode = TestsGenerationMode.DEFAULT + var eachFunctionExecutionTimeoutMillis: Long = GoUtTestsGenerationConfig.DEFAULT_EACH_EXECUTION_TIMEOUT_MILLIS + var allFunctionExecutionTimeoutMillis: Long = GoUtTestsGenerationConfig.DEFAULT_ALL_EXECUTION_TIMEOUT_MILLIS +} \ No newline at end of file diff --git a/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/ui/GenerateGoTestsDialogWindow.kt b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/ui/GenerateGoTestsDialogWindow.kt new file mode 100644 index 0000000000..b24febe97e --- /dev/null +++ b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/ui/GenerateGoTestsDialogWindow.kt @@ -0,0 +1,147 @@ +package org.utbot.intellij.plugin.go.ui + +import com.goide.psi.GoFunctionDeclaration +import com.goide.psi.GoFunctionOrMethodDeclaration +import com.goide.psi.GoMethodDeclaration +import com.goide.refactor.ui.GoDeclarationInfo +import com.intellij.openapi.components.service +import com.intellij.openapi.ui.DialogPanel +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.ValidationInfo +import com.intellij.ui.JBIntSpinner +import com.intellij.ui.components.JBLabel +import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.dsl.builder.panel +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.UIUtil +import org.utbot.go.logic.GoUtTestsGenerationConfig +import org.utbot.go.logic.TestsGenerationMode +import org.utbot.intellij.plugin.go.models.GenerateGoTestsModel +import org.utbot.intellij.plugin.settings.Settings +import java.text.ParseException +import java.util.concurrent.TimeUnit +import javax.swing.JCheckBox +import javax.swing.JComponent + +private const val MINIMUM_ALL_EXECUTION_TIMEOUT_SECONDS = 1 +private const val ALL_EXECUTION_TIMEOUT_SECONDS_SPINNER_STEP = 10 + +private const val MINIMUM_NUMBER_OF_FUZZING_PROCESSES = 1 +private const val NUMBER_OF_FUZZING_PROCESSES_STEP = 1 + +// This class is highly inspired by GenerateTestsDialogWindow. +class GenerateGoTestsDialogWindow(val model: GenerateGoTestsModel) : DialogWrapper(model.project) { + + private val targetInfos = model.targetFunctions.toInfos() + private val targetFunctionsTable = GoFunctionsSelectionTable(targetInfos).apply { + val height = this.rowHeight * (targetInfos.size.coerceAtMost(12) + 1) + this.preferredScrollableViewportSize = JBUI.size(-1, height) + } + private val numberOfFuzzingProcessesSpinner: JBIntSpinner = JBIntSpinner( + GoUtTestsGenerationConfig.DEFAULT_NUMBER_OF_FUZZING_PROCESSES, + MINIMUM_NUMBER_OF_FUZZING_PROCESSES, + Int.MAX_VALUE, + NUMBER_OF_FUZZING_PROCESSES_STEP + ) + private val fuzzingMode = JCheckBox("Fuzzing mode") + + private val allFunctionExecutionTimeoutSecondsSpinner = + JBIntSpinner( + TimeUnit.MILLISECONDS.toSeconds(GoUtTestsGenerationConfig.DEFAULT_ALL_EXECUTION_TIMEOUT_MILLIS).toInt(), + MINIMUM_ALL_EXECUTION_TIMEOUT_SECONDS, + Int.MAX_VALUE, + ALL_EXECUTION_TIMEOUT_SECONDS_SPINNER_STEP + ) + + private lateinit var panel: DialogPanel + + init { + title = "Generate Tests with UnitTestBot" + isResizable = false + init() + } + + override fun createCenterPanel(): JComponent { + panel = panel { + row("Timeout for all functions:") { + cell(allFunctionExecutionTimeoutSecondsSpinner) + cell(JBLabel("seconds")) + } + row("Number of fuzzing processes:") { + cell(numberOfFuzzingProcessesSpinner) + } + row { + cell(fuzzingMode) + contextHelp("Stop test generation when a panic or error occurs (only one test will be generated for one of these cases)") + } + row("Generate test methods for:") {} + row { + scrollCell(targetFunctionsTable).align(Align.FILL) + } + } + updateFunctionsOrMethodsTable() + return panel + } + + override fun doOKAction() { + model.selectedFunctions = + targetFunctionsTable.selectedMemberInfos.fromInfos().filterIsInstance().toSet() + model.selectedMethods = + targetFunctionsTable.selectedMemberInfos.fromInfos().filterIsInstance().toSet() + try { + numberOfFuzzingProcessesSpinner.commitEdit() + allFunctionExecutionTimeoutSecondsSpinner.commitEdit() + } catch (_: ParseException) { + } + model.numberOfFuzzingProcess = numberOfFuzzingProcessesSpinner.number + model.mode = if (fuzzingMode.isSelected) { + TestsGenerationMode.FUZZING_MODE + } else { + TestsGenerationMode.DEFAULT + } + val settings = model.project.service() + with(settings) { + model.eachFunctionExecutionTimeoutMillis = hangingTestsTimeout.timeoutMs + } + model.allFunctionExecutionTimeoutMillis = + TimeUnit.SECONDS.toMillis(allFunctionExecutionTimeoutSecondsSpinner.number.toLong()) + super.doOKAction() + } + + private fun updateFunctionsOrMethodsTable() { + val focusedTargetFunctionsNames = model.focusedTargetFunctions.map { it.name }.toSet() + val selectedInfos = targetInfos.filter { + it.declaration.name in focusedTargetFunctionsNames + } + if (selectedInfos.isEmpty()) { + checkInfos(targetInfos) + } else { + checkInfos(selectedInfos) + } + targetFunctionsTable.setMemberInfos(targetInfos) + } + + private fun checkInfos(infos: Collection) { + infos.forEach { it.isChecked = true } + } + + private fun Collection.toInfos(): Set = + this.map { GoDeclarationInfo(it) }.toSet() + + private fun Collection.fromInfos(): Set = + this.map { it.declaration as GoFunctionOrMethodDeclaration }.toSet() + + @Suppress("DuplicatedCode") // This method is highly inspired by GenerateTestsDialogWindow.doValidate(). + override fun doValidate(): ValidationInfo? { + targetFunctionsTable.tableHeader?.background = UIUtil.getTableBackground() + targetFunctionsTable.background = UIUtil.getTableBackground() + if (targetFunctionsTable.selectedMemberInfos.isEmpty()) { + targetFunctionsTable.tableHeader?.background = JBUI.CurrentTheme.Validator.errorBackgroundColor() + targetFunctionsTable.background = JBUI.CurrentTheme.Validator.errorBackgroundColor() + return ValidationInfo( + "Tick any methods to generate tests for", targetFunctionsTable.componentPopupMenu + ) + } + return null + } +} \ No newline at end of file diff --git a/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/ui/GoFunctionsSelectionTable.kt b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/ui/GoFunctionsSelectionTable.kt new file mode 100644 index 0000000000..ee6203c875 --- /dev/null +++ b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/ui/GoFunctionsSelectionTable.kt @@ -0,0 +1,32 @@ +package org.utbot.intellij.plugin.go.ui + +import com.goide.psi.GoNamedElement +import com.goide.refactor.ui.GoDeclarationInfo +import com.intellij.refactoring.ui.AbstractMemberSelectionTable +import com.intellij.ui.RowIcon +import com.intellij.util.PlatformIcons +import javax.swing.Icon + +class GoFunctionsSelectionTable(infos: Set) : + AbstractMemberSelectionTable(infos, null, null) { + + override fun getAbstractColumnValue(info: GoDeclarationInfo): Boolean { + return info.isToAbstract + } + + override fun isAbstractColumnEditable(rowIndex: Int): Boolean { + return myMemberInfoModel.isAbstractEnabled(myMemberInfos[rowIndex] as GoDeclarationInfo) + } + + override fun getOverrideIcon(memberInfo: GoDeclarationInfo): Icon? = null + + override fun setVisibilityIcon(memberInfo: GoDeclarationInfo, icon_: com.intellij.ui.icons.RowIcon?) { + val icon = icon_ as RowIcon + val iconToSet = if (memberInfo.declaration.isPublic) { + PlatformIcons.PUBLIC_ICON + } else { + PlatformIcons.PRIVATE_ICON + } + icon.setIcon(iconToSet, VISIBILITY_ICON_POSITION) + } +} \ No newline at end of file diff --git a/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/ui/utils/GoSdkUtils.kt b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/ui/utils/GoSdkUtils.kt new file mode 100644 index 0000000000..fd385cc82c --- /dev/null +++ b/utbot-intellij-go/src/main/kotlin/org/utbot/intellij/plugin/go/ui/utils/GoSdkUtils.kt @@ -0,0 +1,9 @@ +package org.utbot.intellij.plugin.go.ui.utils + +import com.goide.sdk.GoSdk +import java.nio.file.Paths + +fun GoSdk.resolveGoExecutablePath(): String? { + val canonicalGoSdkPath = this.executable?.canonicalPath ?: return null + return Paths.get(canonicalGoSdkPath).toAbsolutePath().toString() +} \ No newline at end of file diff --git a/utbot-intellij-js/build.gradle.kts b/utbot-intellij-js/build.gradle.kts new file mode 100644 index 0000000000..916c540eed --- /dev/null +++ b/utbot-intellij-js/build.gradle.kts @@ -0,0 +1,125 @@ +val intellijPluginVersion: String? by rootProject +val kotlinLoggingVersion: String? by rootProject +val apacheCommonsTextVersion: String? by rootProject +val jacksonVersion: String? by rootProject + +// === IDE settings === +val projectType: String by rootProject +val communityEdition: String by rootProject +val ultimateEdition: String by rootProject + +val ideType: String by rootProject +val androidStudioPath: String? by rootProject + +val ideaVersion: String? by rootProject +val pycharmVersion: String? by rootProject +val golandVersion: String? by rootProject + +val javaIde: String? by rootProject +val pythonIde: String? by rootProject +val jsIde: String? by rootProject +val goIde: String? by rootProject + +val ideVersion = when(ideType) { + "PC", "PY" -> pycharmVersion + "GO" -> golandVersion + else -> ideaVersion +} + +val pythonCommunityPluginVersion: String? by rootProject +val pythonUltimatePluginVersion: String? by rootProject +val goPluginVersion: String? by rootProject + +// https://plugins.jetbrains.com/docs/intellij/android-studio.html#configuring-the-plugin-pluginxml-file +val ideTypeOrAndroidStudio = if (androidStudioPath == null) ideType else "IC" + +project.tasks.asMap["runIde"]?.enabled = false +// === IDE settings === + +plugins { + id("org.jetbrains.intellij") version "1.13.1" +} + +tasks { + compileKotlin { + kotlinOptions { + jvmTarget = "17" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class") + allWarningsAsErrors = false + } + } + + java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + test { + useJUnitPlatform() + } +} + +dependencies { + testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") + implementation(project(":utbot-ui-commons")) + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) + + //Family + implementation(project(":utbot-js")) +} + +intellij { + + val androidPlugins = listOf("org.jetbrains.android") + + val jvmPlugins = mutableListOf( + "java" + ) + + val kotlinPlugins = listOf( + "org.jetbrains.kotlin" + ) + + androidStudioPath?.let { jvmPlugins += androidPlugins } + + val pythonCommunityPlugins = listOf( + "PythonCore:${pythonCommunityPluginVersion}" + ) + + val pythonUltimatePlugins = listOf( + "Pythonid:${pythonUltimatePluginVersion}" + ) + + val jsPlugins = listOf( + "JavaScript" + ) + + val goPlugins = listOf( + "org.jetbrains.plugins.go:${goPluginVersion}" + ) + + val mavenUtilsPlugins = listOf( + "org.jetbrains.idea.maven" + ) + + val basePluginSet = jvmPlugins + kotlinPlugins + mavenUtilsPlugins + androidPlugins + + plugins.set( + when (projectType) { + communityEdition -> basePluginSet + pythonCommunityPlugins + ultimateEdition -> when (ideType) { + "IC" -> basePluginSet + pythonCommunityPlugins + "IU" -> basePluginSet + pythonUltimatePlugins + jsPlugins + goPlugins + "PC" -> pythonCommunityPlugins + "PY" -> pythonUltimatePlugins + jsPlugins + "GO" -> goPlugins + else -> basePluginSet + } + else -> basePluginSet + } + ) + + version.set(ideVersion) + type.set(ideType) +} \ No newline at end of file diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/CoverageModeButtons.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/CoverageModeButtons.kt new file mode 100644 index 0000000000..c2f8c2f1e6 --- /dev/null +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/CoverageModeButtons.kt @@ -0,0 +1,33 @@ +package org.utbot.intellij.plugin.js + +import javax.swing.ButtonGroup +import javax.swing.JRadioButton +import service.coverage.CoverageMode + +object CoverageModeButtons { + + var mode = CoverageMode.FAST + + val fastButton = JRadioButton("Fast") + val baseButton = JRadioButton("Basic") + + + init { + val buttonGroup = ButtonGroup() + fastButton.isSelected = true + val baseButtonModel = baseButton.model + baseButtonModel.addChangeListener { + if (baseButtonModel.isPressed) { + mode = CoverageMode.BASIC + } + } + val fastButtonModel = fastButton.model + fastButtonModel.addChangeListener { + if (baseButtonModel.isPressed) { + mode = CoverageMode.FAST + } + } + buttonGroup.add(fastButton) + buttonGroup.add(baseButton) + } +} diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/JsDialogProcessor.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/JsDialogProcessor.kt new file mode 100644 index 0000000000..164c2b0929 --- /dev/null +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/JsDialogProcessor.kt @@ -0,0 +1,312 @@ +package org.utbot.intellij.plugin.js + +import api.JsTestGenerator +import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreterManager +import com.intellij.lang.ecmascript6.psi.ES6Class +import com.intellij.lang.javascript.psi.JSFile +import com.intellij.lang.javascript.refactoring.util.JSMemberInfo +import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.editor.Document +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.fileEditor.OpenFileDescriptor +import com.intellij.openapi.module.Module +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiFileFactory +import com.intellij.psi.impl.file.PsiDirectoryFactory +import com.intellij.util.concurrency.AppExecutorUtil +import mu.KotlinLogging +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.intellij.plugin.js.language.JsLanguageAssistant +import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import settings.JsDynamicSettings +import settings.JsExportsSettings.endComment +import settings.JsExportsSettings.startComment +import settings.JsTestGenerationSettings.dummyClassName +import settings.PackageDataService +import settings.jsPackagesList +import utils.JsCmdExec +import utils.OsProvider +import java.io.File +import java.io.IOException + +private val logger = KotlinLogging.logger {} + +object JsDialogProcessor { + + fun createDialogAndGenerateTests( + project: Project, + srcModule: Module, + fileMethods: Set, + focusedMethod: JSMemberInfo?, + containingFilePath: String, + editor: Editor?, + file: JSFile + ) { + val model = + createJsTestModel(project, srcModule, fileMethods, focusedMethod, containingFilePath, file) ?: return + (object : Task.Backgroundable( + project, + "Check the requirements" + ) { + override fun run(indicator: ProgressIndicator) { + invokeLater { + if (!PackageDataService( + model.containingFilePath, model.project.basePath!!, model.pathToNPM + ).checkAndInstallRequirements(project) + ) return@invokeLater + createDialog(model)?.let { dialogWindow -> + if (!dialogWindow.showAndGet()) return@invokeLater + // Since Tern.js accesses containing file, sync with file system required before test generation. + editor?.let { + runWriteAction { + with(FileDocumentManager.getInstance()) { + saveDocument(editor.document) + } + } + } + createTests( + dialogWindow.model, + containingFilePath, + editor, + dialogWindow.model.file.getContent() + ) + } + } + } + }).queue() + } + + private fun findNodeAndNPM(): Pair? = try { + val pathToNode = + NodeJsLocalInterpreterManager.getInstance().interpreters.first().interpreterSystemIndependentPath + val (_, errorText) = JsCmdExec.runCommand( + shouldWait = true, cmd = arrayOf("\"${pathToNode}\"", "-v") + ) + if (errorText.isNotEmpty()) throw NoSuchElementException() + val pathToNPM = + pathToNode.substringBeforeLast("/") + "/" + "npm" + OsProvider.getProviderByOs().npmPackagePostfix + pathToNode to pathToNPM + } catch (e: NoSuchElementException) { + Messages.showErrorDialog( + "Node.js interpreter is not found in IDEA settings.\n" + "Please set it in Settings > Languages & Frameworks > Node.js", + "Requirement Error" + ) + logger.error { "Node.js interpreter was not found in IDEA settings." } + null + } catch (e: IOException) { + Messages.showErrorDialog( + "Node.js interpreter path is corrupted in IDEA settings.\n" + "Please check Settings > Languages & Frameworks > Node.js", + "Requirement Error" + ) + logger.error { "Node.js interpreter path is corrupted in IDEA settings." } + null + } + + private fun createJsTestModel( + project: Project, + srcModule: Module, + fileMethods: Set, + focusedMethod: JSMemberInfo?, + filePath: String, + file: JSFile + ): JsTestsModel? { + val testModules = srcModule.testModules() + + if (testModules.isEmpty()) { + val errorMessage = """ + No test source roots found in the project.
    + Please, create or configure at least one test source root. + """.trimIndent() + showErrorDialogLater(project, errorMessage, "Test source roots not found") + return null + } + val (pathToNode, pathToNPM) = findNodeAndNPM() ?: return null + return JsTestsModel( + project = project, + potentialTestModules = testModules, + file = file, + fileMethods = fileMethods, + selectedMethods = if (focusedMethod != null) setOf(focusedMethod) else emptySet(), + ).apply { + containingFilePath = filePath + this.pathToNode = pathToNode + this.pathToNPM = pathToNPM + } + } + + private fun createDialog(jsTestsModel: JsTestsModel?) = jsTestsModel?.let { JsDialogWindow(it) } + + private fun unblockDocument(project: Project, document: Document) { + PsiDocumentManager.getInstance(project).apply { + commitDocument(document) + doPostponedOperationsAndUnblockDocument(document) + } + } + + private fun createTests(model: JsTestsModel, containingFilePath: String, editor: Editor?, contents: String) { + val normalizedContainingFilePath = containingFilePath.replace(File.separator, "/") + (object : Task.Backgroundable(model.project, "Generate tests") { + override fun run(indicator: ProgressIndicator) { + indicator.isIndeterminate = false + indicator.text = "Generate tests: read classes" + val testDir = PsiDirectoryFactory.getInstance(project).createDirectory( + model.testSourceRoot!! + ) + val testFileName = normalizedContainingFilePath.substringAfterLast("/").replace(Regex(".js"), "Test.js") + currentFileText = model.file.getContent() + val testGenerator = JsTestGenerator( + fileText = contents, + sourceFilePath = normalizedContainingFilePath, + projectPath = model.project.basePath?.replace(File.separator, "/") + ?: throw IllegalStateException("Can't access project path."), + selectedMethods = runReadAction { + model.selectedMethods.map { + it.member.name!! + } + }, + parentClassName = runReadAction { + val name = (model.selectedMethods.first().member.parent as ES6Class).name + if (name == dummyClassName) null else name + }, + outputFilePath = "${testDir.virtualFile.path}/$testFileName".replace(File.separator, "/"), + exportsManager = partialApplication( + JsDialogProcessor::manageExports, editor, project, model + ), + settings = JsDynamicSettings( + pathToNode = model.pathToNode, + pathToNYC = model.pathToNYC, + pathToNPM = model.pathToNPM, + timeout = model.timeout, + coverageMode = model.coverageMode + ), + isCancelled = { indicator.isCanceled }) + + indicator.fraction = indicator.fraction.coerceAtLeast(0.9) + indicator.text = "Generate code for tests" + + val generatedCode = testGenerator.run() + invokeLater { + runWriteAction { + val testPsiFile = testDir.findFile(testFileName) ?: run { + val temp = PsiFileFactory.getInstance(project) + .createFileFromText(testFileName, JsLanguageAssistant.jsLanguage, generatedCode) + testDir.add(temp) + testDir.findFile(testFileName)!! + } + OpenFileDescriptor(project, testPsiFile.virtualFile).navigate(true) + } + } + } + }).queue() + } + + private fun partialApplication(f: (A, B, C, D) -> Unit, a: A, b: B, c: C): (D) -> Unit { + return { d: D -> f(a, b, c, d) } + } + + private fun JSFile.getContent(): String = this.viewProvider.contents.toString() + + private fun Project.setNewText(editor: Editor?, filePath: String, text: String) { + editor?.let { + runWriteAction { + with(editor.document) { + unblockDocument(this@setNewText, this@with) + setText(text) + unblockDocument(this@setNewText, this@with) + } + with(FileDocumentManager.getInstance()) { + saveDocument(editor.document) + } + } + } ?: run { + File(filePath).writeText(text) + } + currentFileText = text + } + + // Needed for continuous exports managing + private var currentFileText = "" + + private fun manageExports( + editor: Editor?, + project: Project, + model: JsTestsModel, + swappedText: (String?, String) -> String + ) { + AppExecutorUtil.getAppExecutorService().submit { + invokeLater { + when { + currentFileText.contains(startComment) -> { + val regex = Regex("$startComment((\\r\\n|\\n|\\r|.)*)$endComment") + regex.find(currentFileText)?.groups?.get(1)?.value?.let { existingSection -> + val newText = swappedText(existingSection, currentFileText) + project.setNewText(editor, model.containingFilePath, newText) + } + } + + else -> { + val line = buildString { + append("\n") + appendLine(swappedText(null, currentFileText)) + } + project.setNewText(editor, model.containingFilePath, currentFileText + line) + } + } + } + } + } +} + +private fun Module.testModules() = listOf(this) + +private fun PackageDataService.checkAndInstallRequirements(project: Project): Boolean { + val missingPackages = jsPackagesList.filterNot { this.findPackage(it) } + if (missingPackages.isEmpty()) return true + val message = """ + Requirements are not installed: + ${missingPackages.joinToString { it.packageName }} + Install them? + """.trimIndent() + val result = Messages.showOkCancelDialog( + project, message, "Requirements Missmatch Error", "Install", "Cancel", null + ) + + if (result == Messages.CANCEL) + return false + + try { + val (_, errorText) = this.installMissingPackages(missingPackages) + if (errorText.isNotEmpty()) { + showErrorDialogLater( + project, + "Requirements installing failed with some reason:\n${errorText}", + "Failed to install requirements" + ) + return false + } + return true + } catch (_: TimeoutException) { + showErrorDialogLater( + project, + """ + Requirements installing failed due to the exceeded waiting time for the installation, check your internet connection. + + Try to install missing npm packages manually: + ${ + missingPackages.joinToString(separator = "\n") { + "> npm install ${it.npmListFlag} ${it.packageName}" + } + } + """.trimIndent(), + "Failed to install requirements" + ) + return false + } +} diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/JsDialogWindow.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/JsDialogWindow.kt new file mode 100644 index 0000000000..14b71160c7 --- /dev/null +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/JsDialogWindow.kt @@ -0,0 +1,170 @@ +package org.utbot.intellij.plugin.js + +import com.intellij.lang.javascript.refactoring.ui.JSMemberSelectionTable +import com.intellij.lang.javascript.refactoring.util.JSMemberInfo +import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.ui.DialogPanel +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.ui.ValidationInfo +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.ui.ContextHelpLabel +import com.intellij.ui.JBIntSpinner +import com.intellij.ui.components.Panel +import com.intellij.ui.layout.Cell +import com.intellij.ui.layout.panel +import com.intellij.util.ui.JBUI +import framework.codegen.Mocha +import org.utbot.framework.plugin.api.CodeGenerationSettingItem +import org.utbot.intellij.plugin.ui.components.TestSourceDirectoryChooser +import settings.JsTestGenerationSettings.defaultTimeout +import java.awt.BorderLayout +import java.io.File +import java.nio.file.Paths +import javax.swing.DefaultComboBoxModel +import javax.swing.JComboBox +import javax.swing.JComponent + +class JsDialogWindow(val model: JsTestsModel) : DialogWrapper(model.project) { + + private val items = model.fileMethods + + private val functionsTable = JSMemberSelectionTable(items, null, null).apply { + val height = this.rowHeight * (items.size.coerceAtMost(12) + 1) + this.preferredScrollableViewportSize = JBUI.size(-1, height) + } + + private val testSourceFolderField = TestSourceDirectoryChooser(model, model.file.virtualFile) + private val testFrameworks = ComboBox(DefaultComboBoxModel(arrayOf(Mocha))) + private val nycSourceFileChooserField = NycSourceFileChooser(model) + private val coverageMode = CoverageModeButtons + + private lateinit var panel: DialogPanel + + private val timeoutSpinner = + JBIntSpinner( + defaultTimeout.toInt(), + MINIMUM_TIMEOUT_VALUE_IN_SECONDS, + Int.MAX_VALUE, + MINIMUM_TIMEOUT_VALUE_IN_SECONDS + ) + + init { + title = "Generate Tests with UtBot" + super.setOKButtonText("Generate Tests") + isResizable = false + init() + } + + + @Suppress("UNCHECKED_CAST") + override fun createCenterPanel(): JComponent { + panel = panel { + row("Test source root:") { + component(testSourceFolderField) + } + row("Test framework:") { + component( + Panel().apply { + add(testFrameworks as ComboBox, BorderLayout.LINE_START) + } + ) + } + row("Nyc source path:") { + component(nycSourceFileChooserField) + } + row("Coverage mode:") { + cell { + panelWithHelpTooltip("Fast mode does not guarantee proper handling of user timeouts") { + coverageMode.fastButton() + coverageMode.baseButton() + } + } + } + row("Timeout for Node.js (in seconds):") { + panelWithHelpTooltip("The execution timeout for each generated test") { + component(timeoutSpinner) + } + } + row("Generate test methods for:") {} + row { + scrollPane(functionsTable) + } + } + updateMembersTable() + setListeners() + return panel + } + + + private inline fun Cell.panelWithHelpTooltip(tooltipText: String?, crossinline init: Cell.() -> Unit): Cell { + init() + tooltipText?.let { component(ContextHelpLabel.create(it)) } + return this + } + + + override fun doOKAction() { + val selected = functionsTable.selectedMemberInfos.toSet() + model.selectedMethods = if (selected.any()) selected else emptySet() + model.testFramework = testFrameworks.item + model.timeout = timeoutSpinner.number.toLong() + model.pathToNYC = nycSourceFileChooserField.text + model.coverageMode = CoverageModeButtons.mode + File(testSourceFolderField.text).mkdir() + model.testSourceRoot = + VirtualFileManager.getInstance().refreshAndFindFileByNioPath(Paths.get(testSourceFolderField.text)) + super.doOKAction() + } + + override fun doValidate(): ValidationInfo? { + return testSourceFolderField.validatePath() ?: nycSourceFileChooserField.validateNyc() + } + + private fun updateMembersTable() { + if (items.isEmpty()) isOKActionEnabled = false + val focusedNames = model.selectedMethods.map { it.member.name } + val selectedMethods = items.filter { + focusedNames.contains(it.member.name) + } + if (selectedMethods.isEmpty()) { + checkMembers(items) + } else { + checkMembers(selectedMethods) + } + } + + @Suppress("unused") + private fun configureTestFrameworkIfRequired() { +// initTestFrameworkPresenceThread.join() + val frameworkNotInstalled = !testFrameworks.item.isInstalled + if (frameworkNotInstalled) { + Messages.showErrorDialog( + "Test framework ${testFrameworks.item.displayName} is not installed. " + + "Run \"npm i -g ${testFrameworks.item.displayName}\".", + "Missing Framework" + ) + } + } + + + private fun setListeners() { + + testSourceFolderField.childComponent.addActionListener { event -> + with((event.source as JComboBox<*>).selectedItem) { + if (this is VirtualFile) { + model.setSourceRootAndFindTestModule(this@with) + } else { + model.setSourceRootAndFindTestModule(null) + } + } + } + } + + private fun checkMembers(members: Collection) = members.forEach { it.isChecked = true } + + +} + +private const val MINIMUM_TIMEOUT_VALUE_IN_SECONDS = 1 diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/JsTestsModel.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/JsTestsModel.kt new file mode 100644 index 0000000000..de18043117 --- /dev/null +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/JsTestsModel.kt @@ -0,0 +1,49 @@ +package org.utbot.intellij.plugin.js + +import com.intellij.lang.javascript.psi.JSFile +import com.intellij.lang.javascript.refactoring.util.JSMemberInfo +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.intellij.plugin.models.BaseTestsModel +import service.coverage.CoverageMode +import settings.JsTestGenerationSettings.defaultTimeout + +class JsTestsModel( + project: Project, + val potentialTestModules: List, + val file: JSFile, + val fileMethods: Set, + var selectedMethods: Set, +) : BaseTestsModel( + project +) { + var testModule: Module = potentialTestModules.firstOrNull() ?: error("Empty list of test modules in model") + + var timeout = defaultTimeout + + lateinit var testFramework: TestFramework + lateinit var containingFilePath: String + var pathToNode: String = "node" + var pathToNYC: String = "nyc" + var pathToNPM: String = "npm" + var coverageMode: CoverageMode = CoverageMode.FAST + + fun setSourceRootAndFindTestModule(newTestSourceRoot: VirtualFile?) { + requireNotNull(newTestSourceRoot) + testSourceRoot = newTestSourceRoot + var target = newTestSourceRoot + while (target != null && target is FakeVirtualFile) { + target = target.parent + } + if (target == null) { + error("Could not find module for $newTestSourceRoot") + } + + testModule = ModuleUtil.findModuleForFile(target, project) + ?: error("Could not find module for $newTestSourceRoot") + } +} diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/NycSourceFileChooser.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/NycSourceFileChooser.kt new file mode 100644 index 0000000000..e3683d5a6d --- /dev/null +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/NycSourceFileChooser.kt @@ -0,0 +1,36 @@ +package org.utbot.intellij.plugin.js + +import com.intellij.openapi.fileChooser.FileChooserDescriptor +import com.intellij.openapi.ui.TextBrowseFolderListener +import com.intellij.openapi.ui.TextFieldWithBrowseButton +import com.intellij.openapi.ui.ValidationInfo +import org.utbot.common.PathUtil.replaceSeparator +import settings.PackageDataService +import utils.OsProvider + + +class NycSourceFileChooser(val model: JsTestsModel) : TextFieldWithBrowseButton() { + + + init { + val descriptor = FileChooserDescriptor( + true, + false, + false, + false, + false, + false, + ) + addBrowseFolderListener( + TextBrowseFolderListener(descriptor, model.project) + ) + text = PackageDataService.nycPath + } + + fun validateNyc(): ValidationInfo? { + return if (replaceSeparator(text).endsWith("nyc" + OsProvider.getProviderByOs().npmPackagePostfix)) + null + else + ValidationInfo("Nyc executable file was not found in the specified directory", this) + } +} diff --git a/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/language/JsLanguageAssistant.kt b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/language/JsLanguageAssistant.kt new file mode 100644 index 0000000000..ec84561b6f --- /dev/null +++ b/utbot-intellij-js/src/main/kotlin/org/utbot/intellij/plugin/js/language/JsLanguageAssistant.kt @@ -0,0 +1,167 @@ +package org.utbot.intellij.plugin.js.language + +import com.intellij.lang.Language +import com.intellij.lang.ecmascript6.psi.ES6Class +import com.intellij.lang.javascript.psi.JSFile +import com.intellij.lang.javascript.psi.JSFunction +import com.intellij.lang.javascript.psi.ecmal4.JSClass +import com.intellij.lang.javascript.refactoring.util.JSMemberInfo +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiFileFactory +import com.intellij.psi.util.PsiTreeUtil +import org.utbot.intellij.plugin.js.JsDialogProcessor +import org.utbot.intellij.plugin.language.agnostic.LanguageAssistant +import settings.JsTestGenerationSettings.dummyClassName + +@Suppress("unused") // is used in org.utbot.intellij.plugin.language.agnostic.LanguageAssistant via reflection +object JsLanguageAssistant : LanguageAssistant() { + + private const val jsId = "ECMAScript 6" + val jsLanguage: Language = Language.findLanguageByID(jsId) ?: error("JavaScript language wasn't found") + + private data class PsiTargets( + val methods: Set, + val focusedMethod: JSMemberInfo?, + val module: Module, + val containingFilePath: String, + val editor: Editor?, + val file: JSFile + ) + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val (methods, focusedMethod, module, containingFilePath, editor, file) = getPsiTargets(e) ?: return + JsDialogProcessor.createDialogAndGenerateTests( + project = project, + srcModule = module, + fileMethods = methods, + focusedMethod = focusedMethod, + containingFilePath = containingFilePath, + editor = editor, + file = file, + ) + } + + override fun update(e: AnActionEvent) { + e.presentation.isEnabled = getPsiTargets(e) != null + } + + private fun getPsiTargets(e: AnActionEvent): PsiTargets? { + e.project ?: return null + val editor = e.getData(CommonDataKeys.EDITOR) + val file = e.getData(CommonDataKeys.PSI_FILE) as? JSFile ?: return null + val element = if (editor != null) { + findPsiElement(file, editor) ?: return null + } else { + e.getData(CommonDataKeys.PSI_ELEMENT) ?: return null + } + val module = ModuleUtilCore.findModuleForPsiElement(element) ?: return null + val virtualFile = (e.getData(CommonDataKeys.VIRTUAL_FILE) ?: return null).path + val focusedMethod = getContainingMethod(element) + containingClass(element)?.let { + val methods = it.functions + val memberInfos = generateMemberInfo(e.project!!, methods.toList(), it) + val focusedMethodMI = memberInfos.find { member -> + member.member?.name == focusedMethod?.name + } + return PsiTargets( + methods = memberInfos, + focusedMethod = focusedMethodMI, + module = module, + containingFilePath = virtualFile, + editor = editor, + file = file, + ) + } + var memberInfos = generateMemberInfo(e.project!!, file.statements.filterIsInstance()) + var focusedMethodMI = memberInfos.find { member -> + member.member?.name == focusedMethod?.name + } + // TODO: generate tests for all classes, not only the first one + // (currently not possible since breaks JsTestGenerator routine) + if (memberInfos.isEmpty()) { + val classes = file.statements.filterIsInstance() + if (classes.isEmpty()) return null + + memberInfos = generateMemberInfo( + e.project!!, + emptyList(), + classes.first() + ) + if (memberInfos.isEmpty()) return null + + focusedMethodMI = memberInfos.first() + } + return PsiTargets( + methods = memberInfos, + focusedMethod = focusedMethodMI, + module = module, + containingFilePath = virtualFile, + editor = editor, + file = file, + ) + } + + private fun getContainingMethod(element: PsiElement): JSFunction? { + if (element is JSFunction) + return element + + val parent = element.parent ?: return null + return getContainingMethod(parent) + } + + private fun findPsiElement(file: PsiFile, editor: Editor): PsiElement? { + val offset = editor.caretModel.offset + var element = file.findElementAt(offset) + if (element == null && offset == file.textLength) { + element = file.findElementAt(offset - 1) + } + return element + } + + private fun containingClass(element: PsiElement) = + PsiTreeUtil.getParentOfType(element, ES6Class::class.java, false) + + private fun buildClassStringFromMethods(methods: List): String { + var strBuilder = "\n" + val filteredMethods = methods.filterNot { method -> method.name == "constructor" } + filteredMethods.forEach { + strBuilder += it.text.replace("function ", "") + } + // Creating a class with a random name. It won't affect user's code since it is created in abstract PsiFile. + return "class $dummyClassName {$strBuilder}" + } + + /* + Small hack: generating a string source code of an "impossible" class in order to + generate a PsiFile with it, then extract ES6Class from it, then extract MemberInfos. + Created for top-level functions that don't have a parent class. + */ + private fun generateMemberInfo( + project: Project, + methods: List, + jsClass: JSClass? = null + ): Set { + jsClass?.let { + val res = mutableListOf() + JSMemberInfo.extractClassMembers(it, res) { member -> + member is JSFunction + } + return res.toSet() + } + val strClazz = buildClassStringFromMethods(methods) + val abstractPsiFile = PsiFileFactory.getInstance(project) + .createFileFromText(jsLanguage, strClazz) + val clazz = PsiTreeUtil.getChildOfType(abstractPsiFile, JSClass::class.java) + val res = mutableListOf() + JSMemberInfo.extractClassMembers(clazz!!, res) { true } + return res.toSet() + } +} diff --git a/utbot-intellij-main/build.gradle.kts b/utbot-intellij-main/build.gradle.kts new file mode 100644 index 0000000000..30e728a74d --- /dev/null +++ b/utbot-intellij-main/build.gradle.kts @@ -0,0 +1,157 @@ +val semVer: String? by rootProject +val junit5Version: String by rootProject +val junit4PlatformVersion: String by rootProject + +// === IDE settings === +val projectType: String by rootProject +val communityEdition: String by rootProject +val ultimateEdition: String by rootProject + +val ideType: String by rootProject +val androidStudioPath: String? by rootProject + +val ideaVersion: String? by rootProject +val pycharmVersion: String? by rootProject +val golandVersion: String? by rootProject + +val javaIde: String? by rootProject +val pythonIde: String? by rootProject +val jsIde: String? by rootProject +val goIde: String? by rootProject + +val ideVersion = when(ideType) { + "PC", "PY" -> pycharmVersion + "GO" -> golandVersion + else -> ideaVersion +} + +val pythonCommunityPluginVersion: String? by rootProject +val pythonUltimatePluginVersion: String? by rootProject +val goPluginVersion: String? by rootProject + +// https://plugins.jetbrains.com/docs/intellij/android-studio.html#configuring-the-plugin-pluginxml-file +val ideTypeOrAndroidStudio = if (androidStudioPath == null) ideType else "IC" + +project.tasks.asMap["runIde"]?.enabled = false +// === IDE settings === + +plugins { + id("org.jetbrains.intellij") version "1.13.1" +} + +intellij { + + val androidPlugins = listOf("org.jetbrains.android") + + val jvmPlugins = mutableListOf( + "java" + ) + + val kotlinPlugins = listOf( + "org.jetbrains.kotlin" + ) + + androidStudioPath?.let { jvmPlugins += androidPlugins } + + val pythonCommunityPlugins = listOf( + "PythonCore:${pythonCommunityPluginVersion}" + ) + + val pythonUltimatePlugins = listOf( + "Pythonid:${pythonUltimatePluginVersion}" + ) + + val jsPlugins = listOf( + "JavaScript" + ) + + val goPlugins = listOf( + "org.jetbrains.plugins.go:${goPluginVersion}" + ) + + val mavenUtilsPlugins = listOf( + "org.jetbrains.idea.maven" + ) + + val basePluginSet = jvmPlugins + kotlinPlugins + mavenUtilsPlugins + androidPlugins + + plugins.set( + when (projectType) { + communityEdition -> basePluginSet + pythonCommunityPlugins + ultimateEdition -> when (ideType) { + "IC" -> basePluginSet + pythonCommunityPlugins + "IU" -> basePluginSet + pythonUltimatePlugins + jsPlugins + goPlugins + "PC" -> pythonCommunityPlugins + "PY" -> pythonUltimatePlugins + jsPlugins + "GO" -> goPlugins + else -> basePluginSet + } + else -> basePluginSet + } + ) + + version.set(ideVersion) + type.set(ideTypeOrAndroidStudio) + SettingsTemplateHelper.proceed(project) +} + +tasks { + compileKotlin { + kotlinOptions { + jvmTarget = "17" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class") + allWarningsAsErrors = false + } + } + + java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + runIde { + jvmArgs("-Xmx2048m") + jvmArgs("--add-exports", "java.desktop/sun.awt.windows=ALL-UNNAMED") + androidStudioPath?.let { ideDir.set(file(it)) } + } + + patchPluginXml { + sinceBuild.set("223") + untilBuild.set("232.*") + version.set(semVer) + } +} + +dependencies { + implementation(project(":utbot-ui-commons")) + + //Family + + if (javaIde?.split(',')?.contains(ideType) == true) { + implementation(project(":utbot-intellij")) + } + + if (pythonIde?.split(',')?.contains(ideType) == true) { + implementation(project(":utbot-python")) + implementation(project(":utbot-intellij-python")) + } + + if (projectType == ultimateEdition) { + if (jsIde?.split(',')?.contains(ideType) == true) { + implementation(project(":utbot-js")) + implementation(project(":utbot-intellij-js")) + } + + if (goIde?.split(',')?.contains(ideType) == true) { + implementation(project(":utbot-go")) + implementation(project(":utbot-intellij-go")) + } + } + + implementation(project(":utbot-android-studio")) + + testImplementation("org.junit.jupiter:junit-jupiter-api:$junit5Version") + testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junit4PlatformVersion") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junit5Version") + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:$junit5Version") +} diff --git a/utbot-intellij-main/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt b/utbot-intellij-main/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt new file mode 100644 index 0000000000..a0cdc117e5 --- /dev/null +++ b/utbot-intellij-main/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt @@ -0,0 +1,31 @@ +package org.utbot.intellij.plugin.ui.actions + +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.components.service +import org.utbot.intellij.plugin.language.agnostic.LanguageAssistant +import org.utbot.intellij.plugin.settings.Settings + +class GenerateTestsAction : AnAction() { + override fun actionPerformed(e: AnActionEvent) { + LanguageAssistant.get(e)?.actionPerformed(e) + } + + override fun update(e: AnActionEvent) { + val languageAssistant = LanguageAssistant.get(e) + if (languageAssistant == null || !accessByProjectSettings(e)) { + e.presentation.isEnabled = false + } else { + languageAssistant.update(e) + } + } + + override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT + + private fun accessByProjectSettings(e: AnActionEvent): Boolean { + val experimentalLanguageSetting = e.project?.service()?.experimentalLanguagesSupport + val languagePackageName = LanguageAssistant.get(e)?.toString() + return experimentalLanguageSetting == true || languagePackageName?.contains("JvmLanguageAssistant") == true + } +} diff --git a/utbot-intellij-main/src/main/resources/META-INF/plugin.xml b/utbot-intellij-main/src/main/resources/META-INF/plugin.xml new file mode 100644 index 0000000000..89f84572f4 --- /dev/null +++ b/utbot-intellij-main/src/main/resources/META-INF/plugin.xml @@ -0,0 +1,86 @@ + + + + org.utbot.intellij.plugin.id + UnitTestBot + utbot.org + com.intellij.modules.platform + + com.intellij.modules.java + org.jetbrains.kotlin + com.intellij.modules.python + org.jetbrains.plugins.go + org.jetbrains.android + org.jetbrains.idea.maven + + + + + + + + + + + + + + + + + + + +
    + Discover UnitTestBot key features in our latest release: +
      +
    • generating ready-to-use test cases — with valid inputs, method bodies, assertions, and comments
    • +
    • maximizing branch coverage in regression suite while keeping the number of tests minimized
    • +
    • finding deeply hidden code defects and expressing them as tests
    • +
    • fine-tuned mocking, including mocking static methods
    • +
    • representing all the test descriptions in a human-readable format
    • +
    • generating SARIF reports
    • +
    • innovative symbolic execution engine combined with a smart fuzzing platform
    • +
    + Try UnitTestBot online demo to see how it generates tests for your code in real time. +
    + Contribute to UnitTestBot via GitHub. +
    + Found a bug? File an issue. +
    + Have an idea? Start a discussion. + ]]> +
    + + +
  • It automatically detects if you use the Spring framework and provides you with necessary options right in the dialog window.
  • +
  • You can choose from the three approaches to Spring test generation:
  • +
      +
    • standard unit tests that mock environmental interactions,
    • +
    • Spring-specific unit tests that use information about the Spring application context,
    • +
    • and integration tests that validate interactions between Spring components.
    • +
    + + Find more improvements and bug fixes: +
      +
    • Support for IntelliJ IDEA 2023.2
    • +
    • Taint analysis feature (experimental)
    • +
    • Improved mocking in symbolic execution engine
    • +
    • Enhanced fuzzing mechanism: improved domain-specific API and mutation processes; support for generic fields and resolving generic parameter types; single branch detection, and ability to use all public methods of a class under test
    • +
    • Improved UIs for standard Java, Spring, and Python test generation
    • +
    • Fixed bugs for symbolic execution engine, fuzzing, code generation and instrumented process, summaries, SARIF reports, and more
    • +
    • Multiple improvements for Python support related to rendering constructors; mastering exceptions, timed out tests, and regular expressions; fixes for coverage and shutting down behavior
    • +
    • Enhanced Go test generation: support for maps and user-defined types
    • +
    + ]]> +
    +
    diff --git a/utbot-intellij-main/src/main/resources/META-INF/pluginIcon.svg b/utbot-intellij-main/src/main/resources/META-INF/pluginIcon.svg new file mode 100644 index 0000000000..d24574d6dd --- /dev/null +++ b/utbot-intellij-main/src/main/resources/META-INF/pluginIcon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/utbot-intellij/src/main/resources/META-INF/withAndroid.xml b/utbot-intellij-main/src/main/resources/META-INF/withAndroid.xml similarity index 100% rename from utbot-intellij/src/main/resources/META-INF/withAndroid.xml rename to utbot-intellij-main/src/main/resources/META-INF/withAndroid.xml diff --git a/utbot-intellij-main/src/main/resources/META-INF/withGo.xml b/utbot-intellij-main/src/main/resources/META-INF/withGo.xml new file mode 100644 index 0000000000..65c848f900 --- /dev/null +++ b/utbot-intellij-main/src/main/resources/META-INF/withGo.xml @@ -0,0 +1,3 @@ + + + diff --git a/utbot-intellij-main/src/main/resources/META-INF/withIdeaMaven.xml b/utbot-intellij-main/src/main/resources/META-INF/withIdeaMaven.xml new file mode 100644 index 0000000000..5c2f872b51 --- /dev/null +++ b/utbot-intellij-main/src/main/resources/META-INF/withIdeaMaven.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/utbot-intellij-main/src/main/resources/META-INF/withJS.xml b/utbot-intellij-main/src/main/resources/META-INF/withJS.xml new file mode 100644 index 0000000000..d04570b311 --- /dev/null +++ b/utbot-intellij-main/src/main/resources/META-INF/withJS.xml @@ -0,0 +1,4 @@ + + + JavaScript + \ No newline at end of file diff --git a/utbot-intellij-main/src/main/resources/META-INF/withJava.xml b/utbot-intellij-main/src/main/resources/META-INF/withJava.xml new file mode 100644 index 0000000000..eafe833bc7 --- /dev/null +++ b/utbot-intellij-main/src/main/resources/META-INF/withJava.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/utbot-intellij-main/src/main/resources/META-INF/withKotlin.xml b/utbot-intellij-main/src/main/resources/META-INF/withKotlin.xml new file mode 100644 index 0000000000..07e0e420c3 --- /dev/null +++ b/utbot-intellij-main/src/main/resources/META-INF/withKotlin.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/utbot-intellij-main/src/main/resources/META-INF/withLang.xml b/utbot-intellij-main/src/main/resources/META-INF/withLang.xml new file mode 100644 index 0000000000..ed33e791e3 --- /dev/null +++ b/utbot-intellij-main/src/main/resources/META-INF/withLang.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/utbot-intellij-main/src/main/resources/META-INF/withPython.xml b/utbot-intellij-main/src/main/resources/META-INF/withPython.xml new file mode 100644 index 0000000000..f272fd7601 --- /dev/null +++ b/utbot-intellij-main/src/main/resources/META-INF/withPython.xml @@ -0,0 +1,4 @@ + + + com.intellij.modules.python + \ No newline at end of file diff --git a/utbot-intellij/src/main/resources/application.properties b/utbot-intellij-main/src/main/resources/application.properties similarity index 100% rename from utbot-intellij/src/main/resources/application.properties rename to utbot-intellij-main/src/main/resources/application.properties diff --git a/utbot-intellij-main/src/main/resources/bundles/UtbotBundle.properties b/utbot-intellij-main/src/main/resources/bundles/UtbotBundle.properties new file mode 100644 index 0000000000..4c13064c79 --- /dev/null +++ b/utbot-intellij-main/src/main/resources/bundles/UtbotBundle.properties @@ -0,0 +1,10 @@ +# {0} - Test report url prefix, {1] - suffix +test.report.force.mock.warning=Warning: Some test cases were ignored, because no mocking framework is installed in the project.
    \ +Better results could be achieved by installing mocking framework. +test.report.force.static.mock.warning=Warning: Some test cases were ignored, because mockito-inline is not installed in the project.
    \ +Better results could be achieved by configuring mockito-inline. +test.report.test.framework.warning=Warning: There are several test frameworks in the project.\ +To select run configuration, please refer to the documentation depending on the project build system:\ +Gradle, \ +Maven \ +or Idea. \ No newline at end of file diff --git a/utbot-intellij-main/src/main/resources/inspectionDescriptions/UnitTestBotInspectionTool.html b/utbot-intellij-main/src/main/resources/inspectionDescriptions/UnitTestBotInspectionTool.html new file mode 100644 index 0000000000..a01f16e673 --- /dev/null +++ b/utbot-intellij-main/src/main/resources/inspectionDescriptions/UnitTestBotInspectionTool.html @@ -0,0 +1,11 @@ + + +

    Reports unchecked exceptions detected by UnitTestBot.

    +

    Example:

    +
    +void foo(int a) {
    +    return 1 / a; // throws ArithmeticException when `a == 0`
    +}
    +
    + + \ No newline at end of file diff --git a/utbot-intellij-main/src/main/resources/log4j2.xml b/utbot-intellij-main/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..6a9ae540c8 --- /dev/null +++ b/utbot-intellij-main/src/main/resources/log4j2.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utbot-intellij-main/src/main/resources/settings.properties b/utbot-intellij-main/src/main/resources/settings.properties new file mode 100644 index 0000000000..84bc852a2e --- /dev/null +++ b/utbot-intellij-main/src/main/resources/settings.properties @@ -0,0 +1,621 @@ +# Copyright (c) 2024 utbot.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Setting to disable coroutines debug explicitly. +# Set it to false if debug info is required. +# +# Default value is [true] +#disableCoroutinesDebug=true + +# +# Make `true` for interactive mode (like Intellij plugin). If `false` UTBot can apply certain optimizations. +# +# Default value is [true] +#classfilesCanChange=true + +# +# Timeout for Z3 solver.check calls. +# Set it to 0 to disable timeout. +# +# Default value is [1000] +#checkSolverTimeoutMillis=1000 + +# +# Timeout for symbolic execution +# +# Default value is [60000] +#utBotGenerationTimeoutInMillis=60000 + +# +# Random seed in path selector. +# Set null to disable random. +# +# Default value is [42] +#seedInPathSelector=42 + +# +# Type of path selector. +# +# COVERED_NEW_SELECTOR: [CoveredNewSelector] +# INHERITORS_SELECTOR: [InheritorsSelector] +# BFS_SELECTOR: [BFSSelector] +# SUBPATH_GUIDED_SELECTOR: [SubpathGuidedSelector] +# CPI_SELECTOR: [CPInstSelector] +# FORK_DEPTH_SELECTOR: [ForkDepthSelector] +# ML_SELECTOR: [MLSelector] +# TORCH_SELECTOR: [TorchSelector] +# RANDOM_SELECTOR: [RandomSelector] +# RANDOM_PATH_SELECTOR: [RandomPathSelector] +# +# Default value is [INHERITORS_SELECTOR] +#pathSelectorType=INHERITORS_SELECTOR + +# +# Type of MLSelector recalculation. +# +# WITH_RECALCULATION: [MLSelectorWithRecalculation] +# WITHOUT_RECALCULATION: [MLSelectorWithoutRecalculation] +# +# Default value is [WITHOUT_RECALCULATION] +#mlSelectorRecalculationType=WITHOUT_RECALCULATION + +# +# Type of [MLPredictor]. +# +# MLP: [MultilayerPerceptronPredictor] +# LINREG: [LinearRegressionPredictor] +# +# Default value is [MLP] +#mlPredictorType=MLP + +# +# Steps limit for path selector. +# +# Default value is [3500] +#pathSelectorStepsLimit=3500 + +# +# Determines whether path selector should save remaining states for concrete execution after stopping by strategy. +# False for all framework tests by default. +#saveRemainingStatesForConcreteExecution=true + +# +# Use debug visualization. +# Set it to true if debug visualization is needed. +# +# Default value is [false] +#useDebugVisualization=false + +# +# Set the value to true to show library classes' graphs in visualization. +# +# Default value is [false] +#showLibraryClassesInVisualization=false + +# +# Use simplification of UtExpressions. +# Set it to false to disable expression simplification. +# +# Default value is [true] +#useExpressionSimplification=true + +# +# Enable the Summarization module to generate summaries for methods under test. +# Note: if it is [SummariesGenerationType.NONE], +# all the execution for a particular method will be stored at the same nameless region. +# +# FULL: All possible analysis actions are taken +# LIGHT: Analysis actions based on sources are NOT taken +# NONE: No summaries are generated +# +# Default value is [FULL] +#summaryGenerationType=FULL + +# +# If True test comments will be generated. +# +# Default value is [true] +#enableJavaDocGeneration=true + +# +# If True cluster comments will be generated. +# +# Default value is [true] +#enableClusterCommentsGeneration=true + +# +# If True names for tests will be generated. +# +# Default value is [true] +#enableTestNamesGeneration=true + +# +# If True display names for tests will be generated. +# +# Default value is [true] +#enableDisplayNameGeneration=true + +# +# If True display name in from -> to style will be generated. +# +# Default value is [true] +#useDisplayNameArrowStyle=true + +# +# Generate summaries using plugin's custom JavaDoc tags. +# +# Default value is [true] +#useCustomJavaDocTags=true + +# +# This option regulates which [NullPointerException] check should be performed for nested methods. +# Set an option in true if you want to perform NPE check in the corresponding situations, otherwise set false. +# +# Default value is [true] +#checkNpeInNestedMethods=true + +# +# This option regulates which [NullPointerException] check should be performed for nested not private methods. +# Set an option in true if you want to perform NPE check in the corresponding situations, otherwise set false. +# +# Default value is [false] +#checkNpeInNestedNotPrivateMethods=false + +# +# This option determines whether we should generate [NullPointerException] checks for final or non-public fields +# in non-application classes. Set by true, this option highly decreases test's readability in some cases +# because of using reflection API for setting final/non-public fields in non-application classes. +# NOTE: With false value loses some executions with NPE in system classes, but often most of these executions +# are not expected by user. +# +# Default value is [false] +#maximizeCoverageUsingReflection=false + +# +# Activate or deactivate substituting static fields values set in static initializer +# with symbolic variable to try to set them another value than in initializer. +# +# Default value is [true] +#substituteStaticsWithSymbolicVariable=true + +# +# Use concrete execution. +# +# Default value is [true] +#useConcreteExecution=true + +# +# Enable code generation tests with every possible configuration +# for every method in samples. +# Important: is enabled generation requires enormous amount of time. +# +# Default value is [false] +#checkAllCombinationsForEveryTestInSamples=false + +# +# Enable transformation UtCompositeModels into UtAssembleModels using AssembleModelGenerator. +# Note: false doesn't mean that there will be no assemble models, it means that the generator will be turned off. +# Assemble models will present for lists, sets, etc. +# +# Default value is [true] +#useAssembleModelGenerator=true + +# +# Test related files from the temp directory that are older than [daysLimitForTempFiles] +# will be removed at the beginning of the test run. +# +# Default value is [3] +#daysLimitForTempFiles=3 + +# +# Enables soft constraints in the engine. +# +# Default value is [true] +#preferredCexOption=true + +# +# Type of test minimization strategy. +# +# DO_NOT_MINIMIZE_STRATEGY: Always adds new test +# COVERAGE_STRATEGY: Adds new test only if it increases coverage +# +# Default value is [COVERAGE_STRATEGY] +#testMinimizationStrategyType=COVERAGE_STRATEGY + +# +# Set to true to start fuzzing if symbolic execution haven't return anything +# +# Default value is [true] +#useFuzzing=true + +# +# Set the total attempts to improve coverage by fuzzer. +# +# Default value is [2147483647] +#fuzzingMaxAttempts=2147483647 + +# +# Fuzzer tries to generate and run tests during this time. +# +# Default value is [3000] +#fuzzingTimeoutInMillis=3000 + +# +# Find implementations of interfaces and abstract classes to fuzz. +# +# Default value is [true] +#fuzzingImplementationOfAbstractClasses=true + +# +# Use methods to mutate fields of classes different from class under test or not. +# +# Default value is [false] +#tryMutateOtherClassesFieldsWithMethods=false + +# +# Generate tests that treat possible overflows in arithmetic operations as errors +# that throw Arithmetic Exception. +# +# Default value is [false] +#treatOverflowAsError=false + +# +# Generate tests that treat assertions as error suits. +# +# Default value is [true] +#treatAssertAsErrorSuite=true + +# +# Instrument all classes before start +# +# Default value is [false] +#warmupConcreteExecution=false + +# +# Ignore string literals during the code analysis to make possible to analyze antlr. +# It is a hack and must be removed after the competition. +# +# Default value is [false] +#ignoreStringLiterals=false + +# +# Timeout for specific concrete execution (in milliseconds). +# +# Default value is [1000] +#concreteExecutionDefaultTimeoutInInstrumentedProcessMillis=1000 + +# +# Enable taint analysis or not. +# +# Default value is [false] +#useTaintAnalysis=false + +# +# Path to custom log4j2 configuration file for EngineProcess. +# By default utbot-intellij/src/main/resources/log4j2.xml is used. +# Also default value is used if provided value is not a file. +#engineProcessLogConfigFile="" + +# +# The property is useful only for the IntelliJ IDEs. +# If the property is set in true the engine process opens a debug port. +# @see runInstrumentedProcessWithDebug +# @see org.utbot.intellij.plugin.process.EngineProcess +# +# Default value is [false] +#runEngineProcessWithDebug=false + +# +# The engine process JDWP agent's port of the engine process. +# A debugger attaches to the port in order to debug the process. +# +# Default value is [5005] +#engineProcessDebugPort=5005 + +# +# Value of the suspend mode for the JDWP agent of the engine process. +# If the value is true, the engine process will suspend until a debugger attaches to it. +# +# Default value is [true] +#suspendEngineProcessExecutionInDebugMode=true + +# +# The property is useful only for the IntelliJ IDEs. +# If the property is set in true the spring analyzer process opens a debug port. +# @see runInstrumentedProcessWithDebug +# @see org.utbot.spring.process.SpringAnalyzerProcess +# +# Default value is [false] +#runSpringAnalyzerProcessWithDebug=false + +# +# The spring analyzer process JDWP agent's port. +# A debugger attaches to the port in order to debug the process. +# +# Default value is [5007] +#springAnalyzerProcessDebugPort=5007 + +# +# Value of the suspend mode for the JDWP agent of the spring analyzer process. +# If the value is true, the spring analyzer process will suspend until a debugger attaches to it. +# +# Default value is [true] +#suspendSpringAnalyzerProcessExecutionInDebugMode=true + +# +# The instrumented process JDWP agent's port of the instrumented process. +# A debugger attaches to the port in order to debug the process. +# +# Default value is [5006] +#instrumentedProcessDebugPort=5006 + +# +# Value of the suspend mode for the JDWP agent of the instrumented process. +# If the value is true, the instrumented process will suspend until a debugger attaches to it. +# +# Default value is [true] +#suspendInstrumentedProcessExecutionInDebugMode=true + +# +# If true, runs the instrumented process with the ability to attach a debugger. +# To debug the instrumented process, set the breakpoint in the +# [org.utbot.instrumentation.rd.InstrumentedProcess.Companion.invoke] +# and in the instrumented process's main function and run the main process. +# Then run the remote JVM debug configuration in IDEA. +# If you see the message in console about successful connection, then +# the debugger is attached successfully. +# Now you can put the breakpoints in the instrumented process and debug +# both processes simultaneously. +# @see [org.utbot.instrumentation.rd.InstrumentedProcess.Companion.invoke] +# +# Default value is [false] +#runInstrumentedProcessWithDebug=false + +# +# Number of branch instructions using for clustering executions in the test minimization phase. +# +# Default value is [4] +#numberOfBranchInstructionsForClustering=4 + +# +# Determines should we choose only one crash execution with "minimal" model or keep all. +# +# Default value is [true] +#minimizeCrashExecutions=true + +# +# Determines maximum number of executions with unknown coverage per method per result type. +# In [ContestUsvm] it is useful if concrete fails, so we use symbolic execution result without trace. +# +# Default value is [10] +#maxUnknownCoverageExecutionsPerMethodPerResultType=10 + +# +# Enable it to calculate unsat cores for hard constraints as well. +# It may be usefull during debug. +# Note: it might highly impact performance, so do not enable it in release mode. +# +# Default value is [false] +#enableUnsatCoreCalculationForHardConstraints=false + +# +# Enable it to process states with unknown solver status +# from the queue to concrete execution. +# +# Default value is [true] +#processUnknownStatesDuringConcreteExecution=true + +# +# 2^{this} will be the length of observed subpath. +# See [SubpathGuidedSelector] +# +# Default value is [1] +#subpathGuidedSelectorIndex=1 + +# +# Flag that indicates whether feature processing for execution states enabled or not +# +# Default value is [false] +#enableFeatureProcess=false + +# +# Path to deserialized ML models +# +# Default value is [../models/0] +#modelPath=../models/0 + +# +# Full class name of the class containing the configuration for the ML models to solve path selection task. +# +# Default value is [org.utbot.AnalyticsConfiguration] +#analyticsConfigurationClassPath=org.utbot.AnalyticsConfiguration + +# +# Full class name of the class containing the configuration for the ML models exported from the PyTorch to solve path selection task. +# +# Default value is [org.utbot.AnalyticsTorchConfiguration] +#analyticsTorchConfigurationClassPath=org.utbot.AnalyticsTorchConfiguration + +# +# Number of model iterations that will be used during ContestEstimator +# +# Default value is [1] +#iterations=1 + +# +# Path for state features dir +# +# Default value is [eval/secondFeatures/antlr/INHERITORS_SELECTOR] +#featurePath=eval/secondFeatures/antlr/INHERITORS_SELECTOR + +# +# Counter for tests during testGeneration for one project in ContestEstimator +# +# Default value is [0] +#testCounter=0 + +# +# Flag that indicates whether tests for synthetic (see [Executable.isSynthetic]) and implicitly declared methods (like values, valueOf in enums) should be generated, or not +# +# Default value is [true] +#skipTestGenerationForSyntheticAndImplicitlyDeclaredMethods=true + +# +# Flag that indicates whether should we branch on and set static fields from trusted libraries or not. +# @see [org.utbot.common.WorkaroundReason.IGNORE_STATICS_FROM_TRUSTED_LIBRARIES] +# +# Default value is [true] +#ignoreStaticsFromTrustedLibraries=true + +# +# Use the sandbox in the instrumented process. +# If true, the sandbox will prevent potentially dangerous calls, e.g., file access, reading +# or modifying the environment, calls to `Unsafe` methods etc. +# If false, all these operations will be enabled and may lead to data loss during code analysis +# and test generation. +# +# Default value is [true] +#useSandbox=true + +# +# Transform bytecode in the instrumented process. +# If true, bytecode transformation will help fuzzing to find interesting input data, but the size of bytecode can increase. +# If false, bytecode won`t be changed. +# +# Default value is [false] +#useBytecodeTransformation=false + +# +# Limit for number of generated tests per method (in each region) +# +# Default value is [50] +#maxTestsPerMethodInRegion=50 + +# +# Max file length for generated test file +# +# Default value is [1000000] +#maxTestFileSize=1000000 + +# +# If this options set in true, all soot classes will be removed from a Soot Scene, +# therefore, you will be unable to test soot classes. +# +# Default value is [true] +#removeSootClassesFromHierarchy=true + +# +# If this options set in true, all UtBot classes will be removed from a Soot Scene, +# therefore, you will be unable to test UtBot classes. +# +# Default value is [true] +#removeUtBotClassesFromHierarchy=true + +# +# Use this option to enable calculation and logging of MD5 for dropped states by statistics. +# Example of such logging: +# Dropping state (lastStatus=UNDEFINED) by the distance statistics. MD5: 5d0bccc242e87d53578ca0ef64aa5864 +# +# Default value is [false] +#enableLoggingForDroppedStates=false + +# +# If this option set in true, depending on the number of possible types for +# a particular object will be used either type system based on conjunction +# or on bit vectors. +# @see useBitVecBasedTypeSystem +# +# Default value is [true] +#useBitVecBasedTypeSystem=true + +# +# The number of types on which the choice of the type system depends. +# +# Default value is [64] +#maxTypeNumberForEnumeration=64 + +# +# The threshold for numbers of types for which they will be encoded into solver. +# It is used to do not encode big type storages due to significand performance degradation. +# +# Default value is [512] +#maxNumberOfTypesToEncode=512 + +# +# The behaviour of further analysis if tests generation cancellation is requested. +# +# NONE: Do not react on cancellation +# CANCEL_EVERYTHING: Clear all generated test classes +# SAVE_PROCESSED_RESULTS: Show already processed test classes +# +# Default value is [SAVE_PROCESSED_RESULTS] +#cancellationStrategyType=SAVE_PROCESSED_RESULTS + +# +# Depending on this option, sections might be analyzed or not. +# Note that some clinit sections still will be initialized using runtime information. +# +# Default value is [true] +#enableClinitSectionsAnalysis=true + +# +# Process all clinit sections concretely. +# If [enableClinitSectionsAnalysis] is false, it disables effect of this option as well. +# Note that values processed concretely won't be replaced with unbounded symbolic variables. +# +# Default value is [false] +#processAllClinitSectionsConcretely=false + +# +# In cases where we don't have a body for a method, we can either throw an exception +# or treat this a method as a source of an unbounded symbolic variable returned as a result. +# If this option is set in true, instead of analysis we will return an unbounded symbolic +# variable with a corresponding type. Otherwise, an exception will be thrown. +# Default value is false since it is not a common situation when you cannot retrieve a body +# from a regular method. Setting this option in true might be suitable in situations when +# it is more important not to fall at all rather than work precisely. +#treatAbsentMethodsAsUnboundedValue=false + +# +# A maximum size for any array in the program. Note that input arrays might be less than this value +# due to the symbolic engine limitation, see `org.utbot.engine.Traverser.softMaxArraySize`. +# +# Default value is [1024] +#maxArraySize=1024 + +# +# A maximum size for any array in the program. Note that input arrays might be less than this value +# due to the symbolic engine limitation, see `org.utbot.engine.Traverser.softMaxArraySize`. +# +# Default value is [false] +#disableUnsatChecking=false + +# +# When generating integration tests we only partially reset context in between executions to save time. +# For example, entity id generators do not get reset. It may lead to non-reproduceable results if +# IDs leak to the output of the method under test. +# To cope with that, we rerun executions that are left after minimization, fully resetting Spring context +# between executions. However, full context reset is slow, so we use this setting to limit number of +# tests per method that are rerun with full context reset in case minimization outputs too many tests. +# +# Default value is [25] +#maxSpringContextResetsPerMethod=25 + +# +# Add "test method start marker" and "test method end marker" around each test, can be used to +# detect uncompilable tests and remove them. +# +# Default value is [false] +#addTestMethodMarkers=false diff --git a/utbot-intellij/src/test/resources/application.properties b/utbot-intellij-main/src/test/resources/application.properties similarity index 100% rename from utbot-intellij/src/test/resources/application.properties rename to utbot-intellij-main/src/test/resources/application.properties diff --git a/utbot-intellij-main/src/test/resources/junit-platform.properties b/utbot-intellij-main/src/test/resources/junit-platform.properties new file mode 100644 index 0000000000..6011214f1f --- /dev/null +++ b/utbot-intellij-main/src/test/resources/junit-platform.properties @@ -0,0 +1 @@ +junit.jupiter.testclass.order.default = org.junit.jupiter.api.ClassOrderer$OrderAnnotation \ No newline at end of file diff --git a/utbot-intellij/src/test/resources/log4j.properties b/utbot-intellij-main/src/test/resources/log4j.properties similarity index 100% rename from utbot-intellij/src/test/resources/log4j.properties rename to utbot-intellij-main/src/test/resources/log4j.properties diff --git a/utbot-intellij-main/src/test/resources/log4j2.xml b/utbot-intellij-main/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..faa66a318f --- /dev/null +++ b/utbot-intellij-main/src/test/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/utbot-intellij-python/build.gradle.kts b/utbot-intellij-python/build.gradle.kts new file mode 100644 index 0000000000..222842d165 --- /dev/null +++ b/utbot-intellij-python/build.gradle.kts @@ -0,0 +1,128 @@ +val semVer: String? by rootProject +val kotlinLoggingVersion: String? by rootProject + +// === IDE settings === +val projectType: String by rootProject +val communityEdition: String by rootProject +val ultimateEdition: String by rootProject + +val ideType: String by rootProject +val androidStudioPath: String? by rootProject + +val ideaVersion: String? by rootProject +val pycharmVersion: String? by rootProject +val golandVersion: String? by rootProject + +val javaIde: String? by rootProject +val pythonIde: String? by rootProject +val jsIde: String? by rootProject +val goIde: String? by rootProject + +val ideVersion = when(ideType) { + "PC", "PY" -> pycharmVersion + "GO" -> golandVersion + else -> ideaVersion +} + +val pythonCommunityPluginVersion: String? by rootProject +val pythonUltimatePluginVersion: String? by rootProject +val goPluginVersion: String? by rootProject + +// https://plugins.jetbrains.com/docs/intellij/android-studio.html#configuring-the-plugin-pluginxml-file +val ideTypeOrAndroidStudio = if (androidStudioPath == null) ideType else "IC" + +project.tasks.asMap["runIde"]?.enabled = false +// === IDE settings === + +plugins { + id("org.jetbrains.intellij") version "1.13.1" +} + +tasks { + compileKotlin { + kotlinOptions { + jvmTarget = "17" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class") + allWarningsAsErrors = false + } + } + + java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + patchPluginXml { + sinceBuild.set("223") + untilBuild.set("232.*") + version.set(semVer) + } +} + +val pythonTypesAPIHash: String by rootProject + +dependencies { + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) + testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") + implementation(project(":utbot-ui-commons")) + + //Family + implementation(project(":utbot-python")) + implementation("com.github.UnitTestBot:PythonTypesAPI:$pythonTypesAPIHash") +} + +intellij { + + val androidPlugins = listOf("org.jetbrains.android") + + val jvmPlugins = mutableListOf( + "java" + ) + + val kotlinPlugins = listOf( + "org.jetbrains.kotlin" + ) + + androidStudioPath?.let { jvmPlugins += androidPlugins } + + val pythonCommunityPlugins = listOf( + "PythonCore:${pythonCommunityPluginVersion}" + ) + + val pythonUltimatePlugins = listOf( + "Pythonid:${pythonUltimatePluginVersion}" + ) + + val jsPlugins = listOf( + "JavaScript" + ) + + val goPlugins = listOf( + "org.jetbrains.plugins.go:${goPluginVersion}" + ) + + val mavenUtilsPlugins = listOf( + "org.jetbrains.idea.maven" + ) + + val basePluginSet = jvmPlugins + kotlinPlugins + mavenUtilsPlugins + androidPlugins + + plugins.set( + when (projectType) { + communityEdition -> basePluginSet + pythonCommunityPlugins + ultimateEdition -> when (ideType) { + "IC" -> basePluginSet + pythonCommunityPlugins + "IU" -> basePluginSet + pythonUltimatePlugins + jsPlugins + goPlugins + "PC" -> pythonCommunityPlugins + "PY" -> pythonUltimatePlugins + jsPlugins + "GO" -> goPlugins + else -> basePluginSet + } + else -> basePluginSet + } + ) + + version.set(ideVersion) + type.set(ideType) +} \ No newline at end of file diff --git a/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/UtPyClassItem.java b/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/UtPyClassItem.java new file mode 100644 index 0000000000..f49036a20a --- /dev/null +++ b/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/UtPyClassItem.java @@ -0,0 +1,42 @@ +package org.utbot.intellij.plugin.python.table; + +import com.intellij.icons.AllIcons; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyElement; + +import javax.swing.*; + +public class UtPyClassItem implements UtPyTableItem { + private final PyClass pyClass; + private boolean isChecked; + + public UtPyClassItem(PyClass clazz) { + pyClass = clazz; + isChecked = false; + } + + @Override + public PyElement getContent() { + return pyClass; + } + + @Override + public String getIdName() { + return pyClass.getQualifiedName(); + } + + @Override + public Icon getIcon() { + return AllIcons.Nodes.Class; + } + + @Override + public boolean isChecked() { + return isChecked; + } + + @Override + public void setChecked(boolean valueToBeSet) { + isChecked = valueToBeSet; + } +} diff --git a/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/UtPyFunctionItem.java b/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/UtPyFunctionItem.java new file mode 100644 index 0000000000..b5564512e7 --- /dev/null +++ b/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/UtPyFunctionItem.java @@ -0,0 +1,42 @@ +package org.utbot.intellij.plugin.python.table; + +import com.intellij.icons.AllIcons; +import com.jetbrains.python.psi.PyElement; +import com.jetbrains.python.psi.PyFunction; + +import javax.swing.*; + +public class UtPyFunctionItem implements UtPyTableItem { + private final PyFunction pyFunction; + private boolean isChecked; + + public UtPyFunctionItem(PyFunction function) { + pyFunction = function; + isChecked = false; + } + + @Override + public PyElement getContent() { + return pyFunction; + } + + @Override + public String getIdName() { + return pyFunction.getQualifiedName(); + } + + @Override + public Icon getIcon() { + return AllIcons.Nodes.Function; + } + + @Override + public boolean isChecked() { + return isChecked; + } + + @Override + public void setChecked(boolean valueToBeSet) { + isChecked = valueToBeSet; + } +} diff --git a/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/UtPyMemberSelectionTable.java b/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/UtPyMemberSelectionTable.java new file mode 100644 index 0000000000..26233b9dfe --- /dev/null +++ b/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/UtPyMemberSelectionTable.java @@ -0,0 +1,237 @@ +package org.utbot.intellij.plugin.python.table; + +import com.intellij.openapi.actionSystem.BackgroundableDataProvider; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataProvider; +import com.intellij.refactoring.ui.EnableDisableAction; +import com.intellij.ui.*; +import com.intellij.ui.icons.RowIcon; +import com.intellij.ui.table.JBTable; +import com.intellij.util.ui.JBUI; +import com.jetbrains.python.psi.PyElement; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import java.awt.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class UtPyMemberSelectionTable extends JBTable implements BackgroundableDataProvider { + protected static final int CHECKED_COLUMN = 0; + protected static final int DISPLAY_NAME_COLUMN = 1; + protected static final int ICON_POSITION = 0; + + protected List myItems; + protected MyTableModel myTableModel; + private DataProvider dataProvider; + + public UtPyMemberSelectionTable(Collection items) { + myItems = new ArrayList<>(items); + myTableModel = new MyTableModel<>(this); + setModel(myTableModel); + + TableColumnModel model = getColumnModel(); + model.getColumn(DISPLAY_NAME_COLUMN).setCellRenderer(new MyTableRenderer<>(this)); + TableColumn checkBoxColumn = model.getColumn(CHECKED_COLUMN); + TableUtil.setupCheckboxColumn(checkBoxColumn); + checkBoxColumn.setCellRenderer(new MyBooleanRenderer<>(this)); + setPreferredScrollableViewportSize(JBUI.size(400, -1)); + setVisibleRowCount(12); + getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + setShowGrid(false); + setIntercellSpacing(new Dimension(0, 0)); + new MyEnableDisableAction().register(); + } + + public void setItems(Collection items) { + myItems = new ArrayList<>(items); + } + + @Override + public @Nullable DataProvider createBackgroundDataProvider() { + if (dataProvider == null) { + dataProvider = new DataProvider() { + @Override + public @Nullable Object getData(@NotNull @NonNls String dataId) { + if (CommonDataKeys.PSI_ELEMENT.is(dataId)) { + for (UtPyTableItem item : getSelectedMemberInfos()) { + PyElement pyElement = item.getContent(); + if (pyElement != null) return pyElement; + } + } + return null; + } + }; + } + return dataProvider; + } + + public Collection getSelectedMemberInfos() { + ArrayList list = new ArrayList<>(myItems.size()); + for (T info : myItems) { + if (info.isChecked()) { + list.add(info); + } + } + return list; + } + + private class MyEnableDisableAction extends EnableDisableAction { + + @Override + protected JTable getTable() { + return UtPyMemberSelectionTable.this; + } + + @Override + protected void applyValue(int[] rows, boolean valueToBeSet) { + for (int row : rows) { + final T memberInfo = myItems.get(row); + memberInfo.setChecked(valueToBeSet); + } + final int[] selectedRows = getSelectedRows(); + final ListSelectionModel selectionModel = getSelectionModel(); + for (int selectedRow : selectedRows) { + selectionModel.addSelectionInterval(selectedRow, selectedRow); + } + } + + @Override + protected boolean isRowChecked(final int row) { + return myItems.get(row).isChecked(); + } + } + + private static class MyBooleanRenderer extends BooleanTableCellRenderer { + private final UtPyMemberSelectionTable myTable; + + MyBooleanRenderer(UtPyMemberSelectionTable table) { + myTable = table; + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + if (component instanceof JCheckBox) { + int modelColumn = myTable.convertColumnIndexToModel(column); + T itemInfo = myTable.myItems.get(row); + component.setEnabled(modelColumn == CHECKED_COLUMN || itemInfo.isChecked()); + } + return component; + } + } + + private static class MyTableRenderer extends ColoredTableCellRenderer { + private final UtPyMemberSelectionTable myTable; + + MyTableRenderer(UtPyMemberSelectionTable table) { + myTable = table; + } + + @Override + public void customizeCellRenderer(@NotNull JTable table, final Object value, + boolean isSelected, boolean hasFocus, final int row, final int column) { + + final int modelColumn = myTable.convertColumnIndexToModel(column); + final T item = myTable.myItems.get(row); + if (modelColumn == DISPLAY_NAME_COLUMN) { + Icon itemIcon = item.getIcon(); + RowIcon icon = IconManager.getInstance().createRowIcon(3); + icon.setIcon(itemIcon, ICON_POSITION); + setIcon(icon); + } + else { + setIcon(null); + } + setIconOpaque(false); + setOpaque(false); + + if (value == null) return; + append((String)value); + } + + } + + protected static class MyTableModel extends AbstractTableModel { + private final UtPyMemberSelectionTable myTable; + private Boolean removePrefix; + + public MyTableModel(UtPyMemberSelectionTable table) { + myTable = table; + } + + private void initRemovePrefix() { + List names = new ArrayList<>(); + for (UtPyTableItem item: myTable.myItems) { + names.add(item.getIdName()); + } + removePrefix = Utils.haveCommonPrefix(names); + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public int getRowCount() { + return myTable.myItems.size(); + } + + @Override + public Class getColumnClass(int columnIndex) { + if (columnIndex == CHECKED_COLUMN) { + return Boolean.class; + } + return super.getColumnClass(columnIndex); + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + if (removePrefix == null) { + initRemovePrefix(); + } + final T itemInfo = myTable.myItems.get(rowIndex); + if (columnIndex == CHECKED_COLUMN) { + return itemInfo.isChecked(); + } else if (columnIndex == DISPLAY_NAME_COLUMN) { + if (removePrefix) { + return Utils.getSuffix(itemInfo.getIdName()); + } + return itemInfo.getIdName(); + } else { + throw new RuntimeException("Incorrect column index"); + } + } + + @Override + public String getColumnName(int column) { + if (column == CHECKED_COLUMN) { + return " "; + } else if (column == DISPLAY_NAME_COLUMN) { + return "Members"; + } else { + throw new RuntimeException("Incorrect column index"); + } + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return columnIndex == CHECKED_COLUMN; + } + + + @Override + public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex) { + if (columnIndex == CHECKED_COLUMN) { + myTable.myItems.get(rowIndex).setChecked((Boolean) aValue); + } + } + } +} diff --git a/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/UtPyTableItem.java b/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/UtPyTableItem.java new file mode 100644 index 0000000000..c1e413016a --- /dev/null +++ b/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/UtPyTableItem.java @@ -0,0 +1,18 @@ +package org.utbot.intellij.plugin.python.table; + +import com.jetbrains.python.psi.PyElement; + +import javax.swing.*; + +public interface UtPyTableItem { + + public PyElement getContent(); + + public String getIdName(); + + public Icon getIcon(); + + boolean isChecked(); + + void setChecked(boolean valueToBeSet); +} diff --git a/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/Utils.java b/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/Utils.java new file mode 100644 index 0000000000..0f23b1ba4c --- /dev/null +++ b/utbot-intellij-python/src/main/java/org/utbot/intellij/plugin/python/table/Utils.java @@ -0,0 +1,27 @@ +package org.utbot.intellij.plugin.python.table; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Utils { + public static Boolean haveCommonPrefix(List strings) { + Set prefixes = new HashSet<>(); + for (String str: strings) { + prefixes.add(getPrefix(str)); + } + return prefixes.size() <= 1; + } + + public static String getPrefix(String str) { + String suffix = getSuffix(str); + int len = str.length(); + return str.substring(0, len-suffix.length()-1); + } + + public static String getSuffix(String str) { + String[] parts = str.split("\\."); + int len = parts.length; + return parts[len-1]; + } +} diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/IntellijRequirementsInstaller.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/IntellijRequirementsInstaller.kt new file mode 100644 index 0000000000..08aa8110db --- /dev/null +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/IntellijRequirementsInstaller.kt @@ -0,0 +1,80 @@ +package org.utbot.intellij.plugin.python + +import com.intellij.notification.NotificationType +import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogPanel +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.ui.dsl.builder.panel +import org.utbot.intellij.plugin.ui.Notifier +import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import org.utbot.python.utils.RequirementsInstaller +import org.utbot.python.utils.RequirementsUtils +import javax.swing.JComponent +import org.jetbrains.concurrency.runAsync + + +class IntellijRequirementsInstaller( + val project: Project, +): RequirementsInstaller { + override fun checkRequirements(pythonPath: String, requirements: List): Boolean { + return RequirementsUtils.requirementsAreInstalled(pythonPath, requirements) + } + + override fun installRequirements(pythonPath: String, requirements: List) { + invokeLater { + if (InstallRequirementsDialog(requirements).showAndGet()) { + runAsync { + val installResult = RequirementsUtils.installRequirements(pythonPath, requirements) + invokeLater { + if (installResult.exitValue != 0) { + showErrorDialogLater( + project, + "Requirements installing failed.
    " + + "${installResult.stderr}

    " + + "Try to install with pip:
    " + + " ${requirements.joinToString("
    ")}", + "Requirements error" + ) + } else { + invokeLater { + runReadAction { + PythonNotifier.notify("Requirements installation is complete") + } + } + } + } + } + } + } + } +} + + +class InstallRequirementsDialog(private val requirements: List) : DialogWrapper(true) { + init { + title = "Python Requirements Installation" + init() + } + + private lateinit var panel: DialogPanel + + override fun createCenterPanel(): JComponent { + panel = panel { + row("Some requirements are not installed.") { } + row("Requirements:") { } + indent { + requirements.map { row {text(it)} } + } + row("Install them?") { } + } + return panel + } +} + +object PythonNotifier : Notifier() { + override val notificationType: NotificationType = NotificationType.INFORMATION + + override val displayId: String = "Python notification" +} diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonDialogProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonDialogProcessor.kt new file mode 100644 index 0000000000..e0a250ac1f --- /dev/null +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonDialogProcessor.kt @@ -0,0 +1,420 @@ +package org.utbot.intellij.plugin.python + +import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.components.service +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleUtilCore +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.Task.Backgroundable +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiDirectory +import com.intellij.util.concurrency.AppExecutorUtil +import com.jetbrains.python.psi.PyClass +import com.jetbrains.python.psi.PyElement +import com.jetbrains.python.psi.PyFile +import com.jetbrains.python.psi.PyFunction +import com.jetbrains.python.sdk.pythonSdk +import mu.KotlinLogging +import org.utbot.common.PathUtil.toPath +import org.utbot.framework.plugin.api.util.LockFile +import org.utbot.intellij.plugin.settings.Settings +import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import org.utbot.python.PythonMethodHeader +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.TestFileInformation +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.codegen.PythonCgLanguageAssistant +import org.utbot.python.newtyping.mypy.dropInitFile +import org.utbot.python.utils.RequirementsInstaller +import java.util.concurrent.ScheduledFuture +import java.util.concurrent.TimeUnit +import kotlin.io.path.Path + +object PythonDialogProcessor { + private val logger = KotlinLogging.logger {} + + enum class ProgressRange(val from : Double, val to: Double) { + ANALYZE(from = 0.0, to = 0.1), + SOLVING(from = 0.1, to = 0.95), + CODEGEN(from = 0.95, to = 1.0), + } + + private fun updateIndicator( + indicator: ProgressIndicator, + range: ProgressRange, + text: String, + fraction: Double, + stepCount: Int, + stepNumber: Int = 0, + ) { + assert(stepCount > stepNumber) + val maxValue = 1.0 / stepCount + val shift = stepNumber.toDouble() + invokeLater { + if (indicator.isCanceled) return@invokeLater + text.let { indicator.text = it } + indicator.fraction = indicator.fraction + .coerceAtLeast((shift + range.from + (range.to - range.from) * fraction.coerceIn(0.0, 1.0)) * maxValue) + logger.debug("Phase ${indicator.text} with progress ${String.format("%.2f",indicator.fraction)}") + } + } + + private fun runIndicatorWithTimeHandler(indicator: ProgressIndicator, range: ProgressRange, text: String, globalCount: Int, globalShift: Int, timeout: Long): ScheduledFuture<*> { + val startTime = System.currentTimeMillis() + return AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay({ + val innerTimeoutRatio = + ((System.currentTimeMillis() - startTime).toDouble() / timeout) + .coerceIn(0.0, 1.0) + updateIndicator( + indicator, + range, + text, + innerTimeoutRatio, + globalCount, + globalShift, + ) + }, 0, 100, TimeUnit.MILLISECONDS) + } + + private fun updateIndicatorTemplate( + indicator: ProgressIndicator, + stepCount: Int, + stepNumber: Int + ): (ProgressRange, String, Double) -> Unit { + return { range: ProgressRange, text: String, fraction: Double -> + updateIndicator( + indicator, + range, + text, + fraction, + stepCount, + stepNumber + ) + } + } + + fun createDialogAndGenerateTests( + project: Project, + elementsToShow: Set, + focusedElement: PyElement?, + editor: Editor? = null, + ) { + editor?.let{ + runWriteAction { + with(FileDocumentManager.getInstance()) { + saveDocument(it.document) + } + } + } + val pythonPath = getPythonPath(project) + if (pythonPath == null) { + showErrorDialogLater( + project, + message = "Couldn't find Python interpreter", + title = "Python test generation error" + ) + } else { + val dialog = createDialog( + project, + elementsToShow, + focusedElement, + pythonPath, + ) + if (!dialog.showAndGet()) { + return + } + createTests(project, dialog.model) + } + } + + private fun createDialog( + project: Project, + elementsToShow: Set, + focusedElement: PyElement?, + pythonPath: String, + ): PythonDialogWindow { + val focusedElements = focusedElement + ?.let { setOf(focusedElement.toUtPyTableItem()).filterNotNull() } + ?.toSet() + + return PythonDialogWindow( + PythonTestsModel( + project, + elementsToShow, + focusedElements, + project.service().generationTimeoutInMillis, + project.service().hangingTestsTimeout.timeoutMs, + cgLanguageAssistant = PythonCgLanguageAssistant, + pythonPath = pythonPath, + names = elementsToShow.associateBy { Pair(it.fileName()!!, it.name!!) }, + ) + ) + } + + private fun findSelectedPythonMethods(model: PythonTestLocalModel): List { + return ReadAction.nonBlocking> { + model.selectedElements + .filter { model.selectedElements.contains(it) } + .flatMap { + when (it) { + is PyFunction -> listOf(it) + is PyClass -> it.methods.toList() + else -> emptyList() + } + } + .filter { fineFunction(it) } + .mapNotNull { + val functionName = it.name ?: return@mapNotNull null + val moduleFilename = it.containingFile.virtualFile?.canonicalPath ?: "" + val containingClassId = it.containingClass?.qualifiedName?.let{ cls -> PythonClassId(cls) } + PythonMethodHeader( + functionName, + moduleFilename, + containingClassId, + ) + } + .toSet() + .toList() + }.executeSynchronously() ?: emptyList() + } + + private fun groupPyElementsByModule(model: PythonTestsModel): Set { + return ReadAction.nonBlocking> { + model.selectedElements + .groupBy { it.containingFile } + .flatMap { fileGroup -> + fileGroup.value + .groupBy { it is PyClass }.values + } + .flatMap { fileGroup -> + val classes = fileGroup.filterIsInstance() + val functions = fileGroup.filterIsInstance() + val groups: List> = classes.map { listOf(it) } + listOf(functions) + groups + } + .filter { it.isNotEmpty() } + .map { + val realElements = it.map { member -> model.names[Pair(member.fileName(), member.name)]!! } + val file = realElements.first().containingFile as PyFile + val srcModule = getSrcModule(realElements.first()) + + val (directoriesForSysPath, moduleToImport) = getDirectoriesForSysPath(srcModule, file) + PythonTestLocalModel( + model.project, + model.timeout, + model.timeoutForRun, + model.cgLanguageAssistant, + model.pythonPath, + model.testSourceRootPath, + model.testFramework, + realElements.toSet(), + model.runtimeExceptionTestsBehaviour, + directoriesForSysPath, + moduleToImport.dropInitFile(), + file, + realElements.first().let { pyElement -> + if (pyElement is PyFunction) { + pyElement.containingClass + } else { + null + } + } + ) + } + .toSet() + }.executeSynchronously() ?: emptySet() + } + + private fun createTests(project: Project, baseModel: PythonTestsModel) { + ProgressManager.getInstance().run(object : Backgroundable(project, "Generate python tests") { + override fun run(indicator: ProgressIndicator) { + if (!LockFile.lock()) { + return + } + try { + indicator.text = "Checking requirements..." + indicator.isIndeterminate = false + + val installer = IntellijRequirementsInstaller(project) + + val requirementsAreInstalled = RequirementsInstaller.checkRequirements( + installer, + baseModel.pythonPath, + if (baseModel.testFramework.isInstalled) emptyList() else listOf(baseModel.testFramework.mainPackage) + ) + if (!requirementsAreInstalled) { + return + } + + val modelGroups = groupPyElementsByModule(baseModel) + val totalModules = modelGroups.size + + modelGroups.forEachIndexed { index, model -> + val localUpdateIndicator = updateIndicatorTemplate(indicator, totalModules, index) + localUpdateIndicator(ProgressRange.ANALYZE, "Analyze code: read files", 0.1) + + val methods = findSelectedPythonMethods(model) + val content = getContentFromPyFile(model.file) + + val config = PythonTestGenerationConfig( + pythonPath = model.pythonPath, + testFileInformation = TestFileInformation(model.file.virtualFile.path, content, model.currentPythonModule), + sysPathDirectories = model.directoriesForSysPath, + testedMethods = methods, + timeout = model.timeout, + timeoutForRun = model.timeoutForRun, + testFramework = model.testFramework, + testSourceRootPath = Path(model.testSourceRootPath), + withMinimization = true, + isCanceled = { indicator.isCanceled }, + runtimeExceptionTestsBehaviour = model.runtimeExceptionTestsBehaviour + ) + val processor = PythonIntellijProcessor( + config, + project, + model + ) + + localUpdateIndicator(ProgressRange.ANALYZE, "Analyze module ${model.currentPythonModule}", 0.5) + + val mypyConfig = processor.sourceCodeAnalyze() + + localUpdateIndicator(ProgressRange.ANALYZE, "Analyze module ${model.currentPythonModule}", 1.0) + + val timerHandler = runIndicatorWithTimeHandler( + indicator, + ProgressRange.SOLVING, + "Generate test cases for module ${model.currentPythonModule}", + totalModules, + index, + model.timeout, + ) + try { + val testSets = processor.testGenerate(mypyConfig) + timerHandler.cancel(true) + if (testSets.isEmpty()) return@forEachIndexed + + localUpdateIndicator(ProgressRange.CODEGEN, "Generate tests code for module ${model.currentPythonModule}", 0.0) + val testCode = processor.testCodeGenerate(testSets) + + localUpdateIndicator(ProgressRange.CODEGEN, "Saving tests module ${model.currentPythonModule}", 0.9) + processor.saveTests(testCode) + + logger.info( + "Finished test generation for the following functions: ${ + testSets.map { it.method.name }.toSet().joinToString() + }" + ) + } finally { + timerHandler.cancel(true) + } + } + } finally { + LockFile.unlock() + } + } + }) + } +} + +fun getPythonPath(project: Project): String? { + return project.pythonSdk?.homePath +} + +fun getSrcModule(element: PyElement): Module { + return ModuleUtilCore.findModuleForPsiElement(element) ?: error("Module for source class or function not found") +} + +fun getContentFromPyFile(file: PyFile) = + ReadAction.nonBlocking { + file.viewProvider.contents.toString() + }.executeSynchronously() ?: error("Cannot read file $file") + +/* + * Returns set of sys paths and tested file import path + */ +fun getDirectoriesForSysPath( + srcModule: Module, + file: PyFile +): Pair, String> { + return ReadAction.nonBlocking, String>> { + val sources = ModuleRootManager.getInstance(srcModule).getSourceRoots(false).toMutableList() + val ancestor = ProjectFileIndex.getInstance(file.project).getContentRootForFile(file.virtualFile) + if (ancestor != null) + sources.add(ancestor) + + // Collect sys.path directories with imported modules + val importedPaths = emptyList().toMutableList() + + // 1. import + file.importTargets.forEach { importTarget -> + importTarget.multiResolve().forEach { + val element = it.element + if (element != null) { + val directory = element.parent + if (directory is PsiDirectory) { + // If we have `import a.b.c` we need to add syspath to module `a` only + val additionalLevel = importTarget.importedQName?.componentCount?.dec() ?: 0 + directory.topParent(additionalLevel)?.let { dir -> + importedPaths.add(dir.virtualFile) + } + } + } + } + } + + // 2. from import ... + file.fromImports.forEach { importTarget -> + importTarget.resolveImportSourceCandidates().forEach { + val directory = it.parent + val isRelativeImport = + importTarget.relativeLevel > 0 // If we have `from . import a` we don't need to add syspath + if (directory is PsiDirectory && !isRelativeImport) { + // If we have `from a.b.c import d` we need to add syspath to module `a` only + val additionalLevel = importTarget.importSourceQName?.componentCount?.dec() ?: 0 + directory.topParent(additionalLevel)?.let { dir -> + importedPaths.add(dir.virtualFile) + } + } + } + } + + // Select modules only from this project but not from installation directory + importedPaths.forEach { + val path = it.toNioPath() + val hasSitePackages = + (0 until (path.nameCount)).any { i -> path.subpath(i, i + 1).toString() == "site-packages" } + if (it.isProjectSubmodule(ancestor) && !hasSitePackages) { + sources.add(it) + } + } + + val fileName = file.name.removeSuffix(".py") + val importPath = ancestor?.let { + VfsUtil.getParentDir( + VfsUtilCore.getRelativeLocation(file.virtualFile, it) + ) + } ?: "" + val importStringPath = listOf( + importPath.toPath().joinToString("."), + fileName + ) + .filterNot { it.isEmpty() } + .joinToString(".") + + Pair( + sources.map { it.path }.toSet(), + importStringPath + ) + }.executeSynchronously() ?: error("Cannot collect sys path directories") +} \ No newline at end of file diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonDialogWindow.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonDialogWindow.kt new file mode 100644 index 0000000000..550ab70748 --- /dev/null +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonDialogWindow.kt @@ -0,0 +1,163 @@ +package org.utbot.intellij.plugin.python + +import com.intellij.openapi.components.service +import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.ui.DialogPanel +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.ValidationInfo +import com.intellij.ui.JBIntSpinner +import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.JBScrollPane +import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.dsl.builder.panel +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.components.BorderLayoutPanel +import com.jetbrains.python.psi.PyClass +import com.jetbrains.python.psi.PyFunction +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.intellij.plugin.python.settings.PythonTestFrameworkMapper +import org.utbot.intellij.plugin.python.settings.loadStateFromModel +import org.utbot.intellij.plugin.python.table.UtPyClassItem +import org.utbot.intellij.plugin.python.table.UtPyFunctionItem +import org.utbot.intellij.plugin.python.table.UtPyMemberSelectionTable +import org.utbot.intellij.plugin.python.table.UtPyTableItem +import org.utbot.intellij.plugin.settings.Settings +import org.utbot.intellij.plugin.ui.components.TestSourceDirectoryChooser +import org.utbot.intellij.plugin.ui.utils.createTestFrameworksRenderer +import java.util.concurrent.TimeUnit +import javax.swing.DefaultComboBoxModel +import javax.swing.JComponent + +private const val WILL_BE_INSTALLED_LABEL = " (will be installed)" +private const val MINIMUM_TIMEOUT_VALUE_IN_SECONDS = 5 +private const val STEP_TIMEOUT_VALUE_IN_SECONDS = 5 +private const val ACTION_GENERATE = "Generate Tests" + +class PythonDialogWindow(val model: PythonTestsModel) : DialogWrapper(model.project) { + + private val pyElementsTable = UtPyMemberSelectionTable(emptyList()) + private val testSourceFolderField = TestSourceDirectoryChooser(model) + private val timeoutSpinnerForTotalTimeout = + JBIntSpinner( + TimeUnit.MILLISECONDS.toSeconds(model.timeout).toInt(), + MINIMUM_TIMEOUT_VALUE_IN_SECONDS, + Int.MAX_VALUE, + STEP_TIMEOUT_VALUE_IN_SECONDS + ) + private val testFrameworks = + ComboBox(DefaultComboBoxModel(model.cgLanguageAssistant.getLanguageTestFrameworkManager().testFrameworks.toTypedArray())) + + private lateinit var panel: DialogPanel + private lateinit var currentFrameworkItem: TestFramework + + init { + title = "Generate Tests with UnitTestBot" + isResizable = false + + model.cgLanguageAssistant.getLanguageTestFrameworkManager().testFrameworks.forEach { + it.isInstalled = it.isInstalled || checkModuleIsInstalled(model.pythonPath, it.mainPackage) + } + + init() + setOKButtonText(ACTION_GENERATE) + } + + override fun createCenterPanel(): JComponent { + panel = panel { + row("Test sources root:") { + cell(testSourceFolderField).align(Align.FILL) + } + row("Testing framework:") { + cell(testFrameworks) + } + row("Test generation timeout:") { + cell(BorderLayoutPanel().apply { + addToLeft(timeoutSpinnerForTotalTimeout) + addToRight(JBLabel("seconds per group")) + }) + contextHelp("Set the timeout for all test generation processes per class or top level functions in one module to complete.") + } + row("Generate tests for:") {} + row { + cell(JBScrollPane(pyElementsTable)).align(Align.FILL) + } + } + + initDefaultValues() + updatePyElementsTable() + return panel + } + + private fun initDefaultValues() { + val settings = model.project.service() + + val installedTestFramework = PythonTestFrameworkMapper.allItems.singleOrNull { it.isInstalled } + val testFramework = PythonTestFrameworkMapper.handleUnknown(settings.testFramework) + currentFrameworkItem = installedTestFramework ?: testFramework + + updateTestFrameworksList() + } + + private fun updateTestFrameworksList() { + testFrameworks.item = currentFrameworkItem + testFrameworks.renderer = createTestFrameworksRenderer(WILL_BE_INSTALLED_LABEL) + } + + private fun updatePyElementsTable() { + val functions = model.elementsToDisplay.filterIsInstance() + val classes = model.elementsToDisplay.filterIsInstance() + val functionItems = functions + .groupBy { it.containingClass } + .flatMap { (_, pyFuncs) -> + pyFuncs.map { UtPyFunctionItem(it) } + } + val classItems = classes.map { + UtPyClassItem(it) + } + val items = classItems + functionItems + updateMethodsTable(items) + val height = pyElementsTable.rowHeight * (items.size.coerceAtMost(12) + 1) + pyElementsTable.preferredScrollableViewportSize = JBUI.size(-1, height) + } + + private fun updateMethodsTable(allMethods: Collection) { + val focusedNames = model.focusedElements?.map { it.idName } + val selectedMethods = allMethods.filter { + focusedNames?.contains(it.idName) ?: false + } + + if (selectedMethods.isEmpty()) { + checkMembers(allMethods) + } else { + checkMembers(selectedMethods) + } + + pyElementsTable.setItems(allMethods) + } + + private fun checkMembers(members: Collection) = members.forEach { it.isChecked = true } + + override fun doOKAction() { + val selectedMembers = pyElementsTable.selectedMemberInfos + model.selectedElements = selectedMembers.mapNotNull { it.content }.toSet() + model.testFramework = testFrameworks.item + model.timeout = TimeUnit.SECONDS.toMillis(timeoutSpinnerForTotalTimeout.number.toLong()) + model.testSourceRootPath = testSourceFolderField.text + model.projectType = ProjectType.Python + + val settings = model.project.service() + with(settings) { + model.timeoutForRun = hangingTestsTimeout.timeoutMs + model.runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour + } + + loadStateFromModel(settings, model) + + super.doOKAction() + } + + override fun doValidate(): ValidationInfo? { + return testSourceFolderField.validatePath() + } +} diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonIntellijProcessor.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonIntellijProcessor.kt new file mode 100644 index 0000000000..bb1408958f --- /dev/null +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonIntellijProcessor.kt @@ -0,0 +1,79 @@ +package org.utbot.intellij.plugin.python + +import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.fileEditor.OpenFileDescriptor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiFileFactory +import com.jetbrains.python.psi.PyClass +import org.utbot.intellij.plugin.python.language.PythonLanguageAssistant +import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.PythonTestGenerationProcessor +import org.utbot.python.PythonTestSet +import org.utbot.python.utils.camelToSnakeCase +import java.nio.file.Path +import java.nio.file.Paths + +class PythonIntellijProcessor( + override val configuration: PythonTestGenerationConfig, + val project: Project, + val model: PythonTestLocalModel, +) : PythonTestGenerationProcessor() { + override fun saveTests(testsCode: String) { + invokeLater { + runWriteAction { + val testDir = createPsiDirectoryForTestSourceRoot(model) + val testFileName = getOutputFileName(model) + val testPsiFile = PsiFileFactory.getInstance(model.project) + .createFileFromText(testFileName, PythonLanguageAssistant.language, testsCode) + testDir.findFile(testPsiFile.name)?.delete() + testDir.add(testPsiFile) + val file = testDir.findFile(testPsiFile.name)!! + OpenFileDescriptor(project, file.virtualFile).navigate(true) + } + } + } + + private fun getDirectoriesFromRoot(root: Path, path: Path): List { + if (path == root || path.parent == null) + return emptyList() + return getDirectoriesFromRoot(root, path.parent) + listOf(path.fileName.toString()) + } + + private fun createPsiDirectoryForTestSourceRoot(model: PythonTestLocalModel): PsiDirectory { + val root = getContentRoot(model.project, model.file.virtualFile) + val paths = getDirectoriesFromRoot( + Paths.get(root.path), + Paths.get(model.testSourceRootPath) + ) + val rootPSI = getContainingElement(model.file) { it.virtualFile == root }!! + return paths.fold(rootPSI) { acc, folderName -> + acc.findSubdirectory(folderName) ?: acc.createSubdirectory(folderName) + } + } + + private fun getOutputFileName(model: PythonTestLocalModel): String { + val moduleName = model.currentPythonModule.camelToSnakeCase().replace('.', '_') + return if (model.selectedElements.size == 1 && model.selectedElements.first() is PyClass) { + val className = model.selectedElements.first().name?.camelToSnakeCase()?.replace('.', '_') + "test_${moduleName}_$className.py" + } else if (model.containingClass == null) { + "test_$moduleName.py" + } else { + val className = model.containingClass.name?.camelToSnakeCase()?.replace('.', '_') + "test_${moduleName}_$className.py" + } + } + + override fun notGeneratedTestsAction(testedFunctions: List) { + showErrorDialogLater( + project, + message = "Cannot create tests for the following functions: " + testedFunctions.joinToString(), + title = "Python test generation error" + ) + } + + override fun processCoverageInfo(testSets: List) { } +} \ No newline at end of file diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonTestsModel.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonTestsModel.kt new file mode 100644 index 0000000000..a0ce0e4984 --- /dev/null +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/PythonTestsModel.kt @@ -0,0 +1,45 @@ +package org.utbot.intellij.plugin.python + +import com.intellij.openapi.project.Project +import com.jetbrains.python.psi.PyClass +import com.jetbrains.python.psi.PyElement +import com.jetbrains.python.psi.PyFile +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.codegen.services.language.CgLanguageAssistant +import org.utbot.intellij.plugin.python.table.UtPyTableItem +import org.utbot.intellij.plugin.models.BaseTestsModel + +class PythonTestsModel( + project: Project, + val elementsToDisplay: Set, + val focusedElements: Set?, + var timeout: Long, + var timeoutForRun: Long, + val cgLanguageAssistant: CgLanguageAssistant, + val pythonPath: String, + val names: Map, PyElement>, +) : BaseTestsModel( + project, +) { + lateinit var testSourceRootPath: String + lateinit var testFramework: TestFramework + var selectedElements: Set = emptySet() + lateinit var runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour +} + +data class PythonTestLocalModel( + val project: Project, + val timeout: Long, + val timeoutForRun: Long, + val cgLanguageAssistant: CgLanguageAssistant, + val pythonPath: String, + val testSourceRootPath: String, + val testFramework: TestFramework, + val selectedElements: Set, + val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour, + val directoriesForSysPath: Set, + val currentPythonModule: String, + val file: PyFile, + val containingClass: PyClass?, +) \ No newline at end of file diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/Utils.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/Utils.kt new file mode 100644 index 0000000000..73aab6b635 --- /dev/null +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/Utils.kt @@ -0,0 +1,74 @@ +package org.utbot.intellij.plugin.python + +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiElement +import com.jetbrains.python.psi.PyClass +import com.jetbrains.python.psi.PyElement +import com.jetbrains.python.psi.PyFunction +import org.utbot.intellij.plugin.python.table.UtPyClassItem +import org.utbot.intellij.plugin.python.table.UtPyFunctionItem +import org.utbot.intellij.plugin.python.table.UtPyTableItem +import org.utbot.python.utils.RequirementsUtils + +inline fun getContainingElement( + element: PsiElement, + predicate: (T) -> Boolean = { true } +): T? { + var result = element + while ((result !is T || !predicate(result)) && (result.parent != null)) { + result = result.parent + } + return result as? T +} + +fun getAncestors(element: PsiElement): List = + if (element.parent == null) + listOf(element) + else + getAncestors(element.parent) + element + +fun getContentRoot(project: Project, file: VirtualFile): VirtualFile { + return ProjectFileIndex.getInstance(project) + .getContentRootForFile(file) ?: error("Source file lies outside of a module") +} + +fun VirtualFile.isProjectSubmodule(ancestor: VirtualFile?): Boolean { + return VfsUtil.isUnder(this, setOf(ancestor).toMutableSet()) +} + +fun checkModuleIsInstalled(pythonPath: String, moduleName: String): Boolean { + return RequirementsUtils.requirementsAreInstalled(pythonPath, listOf(moduleName)) +} + +fun fineFunction(function: PyFunction): Boolean { + val hasNotConstructorName = !listOf("__init__", "__new__").contains(function.name) + val decoratorNames = function.decoratorList?.decorators?.mapNotNull { it?.qualifiedName } + val knownDecorators = decoratorNames?.all { it.toString() in listOf("staticmethod") } ?: true + return hasNotConstructorName && knownDecorators +} + +fun fineClass(pyClass: PyClass): Boolean = + getAncestors(pyClass).dropLast(1).all { it !is PyClass && it !is PyFunction } && + pyClass.methods.any { fineFunction(it) } + +fun PsiDirectory.topParent(level: Int): PsiDirectory? { + var directory: PsiDirectory? = this + repeat(level) { + directory = directory?.parent + } + return directory +} + +fun PyElement.fileName(): String? = this.containingFile.virtualFile.canonicalPath + +fun PyElement.toUtPyTableItem(): UtPyTableItem? { + return when (this) { + is PyClass -> UtPyClassItem(this) + is PyFunction -> UtPyFunctionItem(this) + else -> null + } +} \ No newline at end of file diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/language/PythonLanguageAssistant.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/language/PythonLanguageAssistant.kt new file mode 100644 index 0000000000..8c8a9a3a82 --- /dev/null +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/language/PythonLanguageAssistant.kt @@ -0,0 +1,184 @@ +package org.utbot.intellij.plugin.python.language + +import com.intellij.lang.Language +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.PlatformDataKeys +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.* +import com.intellij.psi.util.PsiTreeUtil +import com.jetbrains.python.psi.PyClass +import com.jetbrains.python.psi.PyFile +import com.jetbrains.python.psi.PyFunction +import org.utbot.framework.plugin.api.util.LockFile +import org.utbot.intellij.plugin.language.agnostic.LanguageAssistant +import org.utbot.intellij.plugin.python.* + +@Suppress("unused") // is used in org.utbot.intellij.plugin.language.agnostic.LanguageAssistant via reflection +object PythonLanguageAssistant : LanguageAssistant() { + + private const val pythonID = "Python" + val language: Language = Language.findLanguageByID(pythonID) ?: error("Language wasn't found") + + data class Targets( + val pyClasses: Set, + val pyFunctions: Set, + val focusedClass: PyClass?, + val focusedFunction: PyFunction?, + val editor: Editor? + ) { + override fun toString(): String { + return "Targets($pyClasses, $pyFunctions)" + } + } + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val targets = getPsiTargets(e) ?: return + + PythonDialogProcessor.createDialogAndGenerateTests( + project, + targets.pyClasses + targets.pyFunctions, + targets.focusedFunction ?: targets.focusedClass, + targets.editor, + ) + } + + override fun update(e: AnActionEvent) { + e.presentation.isEnabled = !LockFile.isLocked() && getPsiTargets(e) != null + } + + private fun getPsiTargets(e: AnActionEvent): Targets? { + val project = e.project ?: return null + val editor = e.getData(CommonDataKeys.EDITOR) + + val resultFunctions = mutableSetOf() + val resultClasses = mutableSetOf() + val focusedFunction: PyFunction? + var focusedClass: PyClass? = null + + if (editor != null) { + val file = e.getData(CommonDataKeys.PSI_FILE) as? PyFile ?: return null + val element = findPsiElement(file, editor) ?: return null + + val rootFunctions = file.topLevelFunctions.filter { fineFunction(it) } + val rootClasses = file.topLevelClasses.filter { fineClass(it) } + + val containingClass = getContainingElement(element) { fineClass(it) } + val containingFunction: PyFunction? = + if (containingClass == null) + getContainingElement(element) { it.parent is PsiFile && fineFunction(it) } + else + getContainingElement(element) { func -> + val ancestors = getAncestors(func) + ancestors.dropLast(1).all { it !is PyFunction } && + ancestors.count { it is PyClass } == 1 && fineFunction(func) + } + + if (rootClasses.isEmpty()) { + return if (rootFunctions.isEmpty()) { + null + } else { + resultFunctions.addAll(rootFunctions) + focusedFunction = containingFunction + Targets(resultClasses, resultFunctions, null, focusedFunction, editor) + } + } else { + if (containingClass == null) { + resultClasses.addAll(rootClasses) + resultFunctions.addAll(rootFunctions) + focusedFunction = containingFunction + } else { + resultFunctions.addAll(containingClass.methods.filter { fineFunction(it) }) + focusedClass = containingClass + focusedFunction = containingFunction + } + return Targets(resultClasses, resultFunctions, focusedClass, focusedFunction, editor) + } + } else { + val element = e.getData(CommonDataKeys.PSI_ELEMENT) + if (element is PsiFileSystemItem) { + e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY)?.let { + val (classes, functions) = getAllElements(project, it.toList()) + resultFunctions.addAll(functions) + resultClasses.addAll(classes) + } + } else { + val someSelection = e.getData(PlatformDataKeys.PSI_ELEMENT_ARRAY)?: return null + someSelection.forEach { + when(it) { + is PsiFileSystemItem -> { + val (classes, functions) = getAllElements(project, listOf(it.virtualFile)) + resultFunctions += functions + resultClasses += classes + } + } + } + } + if (resultClasses.isNotEmpty() || resultFunctions.isNotEmpty()) { + return Targets(resultClasses, resultFunctions, null, null, null) + } + } + return null + } + + // this method is copy-paste from GenerateTestsActions.kt + private fun findPsiElement(file: PsiFile, editor: Editor): PsiElement? { + val offset = editor.caretModel.offset + var element = file.findElementAt(offset) + if (element == null && offset == file.textLength) { + element = file.findElementAt(offset - 1) + } + + return element + } + + private fun getAllElements(project: Project, virtualFiles: Collection): Pair, Set> { + val psiFiles = virtualFiles.mapNotNull { + PsiManager.getInstance(project).findFile(it) + } + val psiDirectories = virtualFiles.mapNotNull { + PsiManager.getInstance(project).findDirectory(it) + } + + val classes = psiFiles.flatMap { getClassesFromFile(it) }.toMutableSet() + val functions = psiFiles.flatMap { getFunctionsFromFile(it) }.toMutableSet() + + psiDirectories.forEach { + classes.addAll(getAllClasses(it)) + functions.addAll(getAllFunctions(it)) + } + + return classes to functions + } + + private fun getAllFunctions(directory: PsiDirectory): Set { + val allFunctions = directory.files.flatMap { getFunctionsFromFile(it) }.toMutableSet() + directory.subdirectories.forEach { + allFunctions.addAll(getAllFunctions(it)) + } + return allFunctions + } + + private fun getAllClasses(directory: PsiDirectory): Set { + val allClasses = directory.files.flatMap { getClassesFromFile(it) }.toMutableSet() + directory.subdirectories.forEach { + allClasses.addAll(getAllClasses(it)) + } + return allClasses + } + + private fun getFunctionsFromFile(psiFile: PsiFile): List { + return PsiTreeUtil.getChildrenOfTypeAsList(psiFile, PyFunction::class.java) + .map { it as PyFunction } + .filter { fineFunction(it) } + } + + private fun getClassesFromFile(psiFile: PsiFile): List { + return PsiTreeUtil.getChildrenOfTypeAsList(psiFile, PyClass::class.java) + .map { it as PyClass } + .filter { fineClass(it) } + } +} \ No newline at end of file diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/settings/PythonTestFrameworkMapper.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/settings/PythonTestFrameworkMapper.kt new file mode 100644 index 0000000000..0e2b60ac75 --- /dev/null +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/settings/PythonTestFrameworkMapper.kt @@ -0,0 +1,31 @@ +package org.utbot.intellij.plugin.python.settings + +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.intellij.plugin.settings.TestFrameworkMapper +import org.utbot.python.framework.codegen.PythonTestFrameworkManager +import org.utbot.python.framework.codegen.model.Pytest +import org.utbot.python.framework.codegen.model.Unittest + +object PythonTestFrameworkMapper: TestFrameworkMapper { + override fun toString(value: TestFramework): String = value.id + + override fun fromString(value: String): TestFramework = when (value) { + Unittest.id -> Unittest + Pytest.id -> Pytest + else -> error("Unknown TestFramework $value") + } + + override fun handleUnknown(testFramework: TestFramework): TestFramework { + if (allItems.contains(testFramework)) { + return testFramework + } + return try { + fromString(testFramework.id) + } catch (ex: IllegalStateException) { + defaultItem + } + } + + val defaultItem: TestFramework get() = PythonTestFrameworkManager().defaultTestFramework + val allItems: List get() = PythonTestFrameworkManager().testFrameworks +} \ No newline at end of file diff --git a/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/settings/Settings.kt b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/settings/Settings.kt new file mode 100644 index 0000000000..a61dfcb5f7 --- /dev/null +++ b/utbot-intellij-python/src/main/kotlin/org/utbot/intellij/plugin/python/settings/Settings.kt @@ -0,0 +1,20 @@ +package org.utbot.intellij.plugin.python.settings + +import org.utbot.framework.codegen.domain.HangingTestsTimeout +import org.utbot.intellij.plugin.python.PythonTestsModel +import org.utbot.intellij.plugin.settings.Settings + +fun loadStateFromModel(settings: Settings, model: PythonTestsModel) { + settings.loadState(fromGenerateTestsModel(model)) +} + +private fun fromGenerateTestsModel(model: PythonTestsModel): Settings.State { + return Settings.State( + sourceRootHistory = model.sourceRootHistory, + testFramework = model.testFramework, + generationTimeoutInMillis = model.timeout, + enableExperimentalLanguagesSupport = true, + hangingTestsTimeout = HangingTestsTimeout(model.timeoutForRun), + runtimeExceptionTestsBehaviour = model.runtimeExceptionTestsBehaviour, + ) +} diff --git a/utbot-intellij/build.gradle b/utbot-intellij/build.gradle deleted file mode 100644 index 6859479ac7..0000000000 --- a/utbot-intellij/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" - -// TODO remove when switch plugin baseline to 2020.3 or higher, with kotlin 1.4 inside -compileKotlin { - kotlinOptions { - allWarningsAsErrors = false - } -} - -buildscript { - repositories { - maven { - url "https://plugins.gradle.org/m2/" - } - } - - dependencies { - classpath group: 'org.jetbrains.intellij.plugins', name: 'gradle-intellij-plugin', version: intellij_plugin_version - } -} - -dependencies { - api ('com.esotericsoftware:kryo:5.1.1') - - implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version - implementation group: 'org.apache.commons', name: 'commons-text', version: apache_commons_text_version - implementation 'org.apache.httpcomponents.client5:httpclient5:5.1' - implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: jackson_version - - implementation(project(":utbot-framework")) { exclude group: 'org.slf4j', module: 'slf4j-api' } - implementation(project(":utbot-fuzzers")) - - testImplementation 'org.mock-server:mockserver-netty:5.4.1' - testImplementation(project(":utbot-sample")) - testApi(project(":utbot-framework")) -} - -apply plugin: 'org.jetbrains.intellij' - -// See https://github.com/JetBrains/gradle-intellij-plugin/ -intellij { - version = '2020.2' - // to use local IDEA comment out "version" and add localPath instead: - // localPath 'c:/all/tools/idea' - plugins = [ - 'java', - // TODO: SAT-1539 - specify version of android plugin to be supported by our kotlin version. - "org.jetbrains.kotlin:${kotlin_version}-release-IJ2020.2-1", - 'org.jetbrains.android' - ] - updateSinceUntilBuild false - - patchPluginXml { - sinceBuild = '202' - version = project.version - } -} - -tasks { - runIde { - jvmArgs("-Xmx2048m") - //// Uncomment and set correct path to Android Studio to run it - // ideDirectory = file('d:/AS2021') - } -} diff --git a/utbot-intellij/build.gradle.kts b/utbot-intellij/build.gradle.kts new file mode 100644 index 0000000000..8f0520b4a9 --- /dev/null +++ b/utbot-intellij/build.gradle.kts @@ -0,0 +1,199 @@ +val intellijPluginVersion: String? by rootProject +val kotlinLoggingVersion: String? by rootProject +val apacheCommonsTextVersion: String? by rootProject +val jacksonVersion: String? by rootProject + +val sootVersion: String? by rootProject +val kryoVersion: String? by rootProject +val rdVersion: String? by rootProject +val semVer: String? by rootProject + +val junit5Version: String by rootProject +val junit4PlatformVersion: String by rootProject + +// === IDE settings === +val projectType: String by rootProject +val communityEdition: String by rootProject +val ultimateEdition: String by rootProject + +val ideType: String by rootProject +val androidStudioPath: String? by rootProject + +val ideaVersion: String? by rootProject +val pycharmVersion: String? by rootProject +val golandVersion: String? by rootProject + +val javaIde: String? by rootProject +val pythonIde: String? by rootProject +val jsIde: String? by rootProject +val goIde: String? by rootProject + +val ideVersion = when(ideType) { + "PC", "PY" -> pycharmVersion + "GO" -> golandVersion + else -> ideaVersion +} + +val pythonCommunityPluginVersion: String? by rootProject +val pythonUltimatePluginVersion: String? by rootProject +val goPluginVersion: String? by rootProject + +// https://plugins.jetbrains.com/docs/intellij/android-studio.html#configuring-the-plugin-pluginxml-file +val ideTypeOrAndroidStudio = if (androidStudioPath == null) ideType else "IC" + +project.tasks.asMap["runIde"]?.enabled = false +// === IDE settings === + +plugins { + id("org.jetbrains.intellij") version "1.13.1" +} + +intellij { + + val androidPlugins = listOf("org.jetbrains.android") + + val jvmPlugins = mutableListOf( + "java" + ) + + val kotlinPlugins = listOf( + "org.jetbrains.kotlin" + ) + + androidStudioPath?.let { jvmPlugins += androidPlugins } + + val pythonCommunityPlugins = listOf( + "PythonCore:${pythonCommunityPluginVersion}" + ) + + val pythonUltimatePlugins = listOf( + "Pythonid:${pythonUltimatePluginVersion}" + ) + + val jsPlugins = listOf( + "JavaScript" + ) + + val goPlugins = listOf( + "org.jetbrains.plugins.go:${goPluginVersion}" + ) + + val mavenUtilsPlugins = listOf( + "org.jetbrains.idea.maven" + ) + + val basePluginSet = jvmPlugins + kotlinPlugins + mavenUtilsPlugins + androidPlugins + + plugins.set( + when (projectType) { + communityEdition -> basePluginSet + pythonCommunityPlugins + ultimateEdition -> when (ideType) { + "IC" -> basePluginSet + pythonCommunityPlugins + "IU" -> basePluginSet + pythonUltimatePlugins + jsPlugins + goPlugins + "PC" -> pythonCommunityPlugins + "PY" -> pythonUltimatePlugins + jsPlugins + "GO" -> goPlugins + else -> basePluginSet + } + else -> basePluginSet + } + ) + + version.set(ideVersion) + type.set(ideTypeOrAndroidStudio) +} + +val remoteRobotVersion = "0.11.16" + +tasks { + compileKotlin { + kotlinOptions { + jvmTarget = "17" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class") + allWarningsAsErrors = false + } + } + + java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + runIde { + jvmArgs("-Xmx2048m") + jvmArgs("--add-exports", "java.desktop/sun.awt.windows=ALL-UNNAMED") + androidStudioPath?.let { ideDir.set(file(it)) } + } + + patchPluginXml { + sinceBuild.set("223") + untilBuild.set("232.*") + version.set(semVer) + } + + runIdeForUiTests { + jvmArgs("-Xmx2048m", "-Didea.is.internal=true", "-Didea.ui.debug.mode=true") + + systemProperty("robot-server.port", "8082") // default port 8580 + systemProperty("ide.mac.message.dialogs.as.sheets", "false") + systemProperty("jb.privacy.policy.text", "") + systemProperty("jb.consents.confirmation.enabled", "false") + systemProperty("idea.trust.all.projects", "true") + systemProperty("ide.mac.file.chooser.native", "false") + systemProperty("jbScreenMenuBar.enabled", "false") + systemProperty("apple.laf.useScreenMenuBar", "false") + systemProperty("ide.show.tips.on.startup.default.value", "false") + } + + downloadRobotServerPlugin { + version.set(remoteRobotVersion) + } + + test { + description = "Runs UI integration tests." + useJUnitPlatform { + exclude("/org/utbot/**") //Comment this line to run the tests locally + } + } +} + +repositories { + maven("https://jitpack.io") + maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies") +} + +dependencies { + implementation(group ="com.jetbrains.rd", name = "rd-framework", version = rdVersion) + implementation(group ="com.jetbrains.rd", name = "rd-core", version = rdVersion) + implementation(group ="com.esotericsoftware.kryo", name = "kryo5", version = kryoVersion) + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) + implementation(group = "org.apache.commons", name = "commons-text", version = apacheCommonsTextVersion) + implementation("org.apache.httpcomponents.client5:httpclient5:5.1") + implementation(group = "com.fasterxml.jackson.module", name = "jackson-module-kotlin", version = jacksonVersion) + + implementation(project(":utbot-framework")) { exclude(group = "org.slf4j", module = "slf4j-api") } + implementation(project(":utbot-spring-framework")) { exclude(group = "org.slf4j", module = "slf4j-api") } + implementation(project(":utbot-java-fuzzing")) + //api(project(":utbot-analytics")) + testImplementation("org.mock-server:mockserver-netty:5.4.1") + testApi(project(":utbot-framework")) + + implementation(project(":utbot-ui-commons")) + implementation(project(":utbot-android-studio")) + + testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion") + testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion") + + testImplementation("org.assertj:assertj-core:3.11.1") + + // Logging Network Calls + testImplementation("com.squareup.okhttp3:logging-interceptor:4.10.0") + + // Video Recording + implementation("com.automation-remarks:video-recorder-junit5:2.0") + + testImplementation("org.junit.jupiter:junit-jupiter-api:$junit5Version") + testRuntimeOnly("org.junit.platform:junit-platform-launcher:$junit4PlatformVersion") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junit5Version") + testRuntimeOnly("org.junit.vintage:junit-vintage-engine:$junit5Version") +} diff --git a/utbot-intellij/readme.md b/utbot-intellij/readme.md index 9172f0f5b7..55b6a3c3ab 100644 --- a/utbot-intellij/readme.md +++ b/utbot-intellij/readme.md @@ -1,5 +1,5 @@ -UT Bot Intellij Plugin -====================== +UnitTestBot Intellij Plugin +=========================== To run/debug plugin in IDEA: @@ -10,4 +10,20 @@ To run/debug plugin in IDEA: To compile plugin: * run `gradle buildPlugin` -* find zipped plugin in build/distributions \ No newline at end of file +* find zipped plugin in build/distributions + +## UnitTestBot Intellij Plugin UI Tests + +* comment `exclude("/org/utbot/**")` in utbot-intellij/build.gradle.kts +* correct DEFAULT_PROJECT_DIRECTORY in RunInfo.kt if needed (it is your local directory in which test projects will be created locally) +* run IDEA in sandbox with IntelliJ Robot server plugin installed: `gradle runIdeForUiTests` +* wait till debug IDEA is started +* check it is above other windows and maximized +* check keyboard language is EN +* do NOT lock screen +* run **All** the tests in utbot-intellij/src/test/kotlin/org/utbot/tests + +Note: projects are created first and only on new projects tests are executed. +That is done for independency of each autotest run. + + diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/UtbotBundle.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/UtbotBundle.kt new file mode 100644 index 0000000000..a13bd6424c --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/UtbotBundle.kt @@ -0,0 +1,29 @@ +package org.utbot.intellij.plugin + +import com.intellij.DynamicBundle +import org.jetbrains.annotations.NonNls +import org.jetbrains.annotations.PropertyKey + +class UtbotBundle : DynamicBundle(BUNDLE) { + companion object { + @NonNls + private const val BUNDLE = "bundles.UtbotBundle" + private val INSTANCE: UtbotBundle = UtbotBundle() + + fun message( + @PropertyKey(resourceBundle = BUNDLE) key: String, + vararg params: Any + ): String { + return INSTANCE.getMessage(key, *params) + } + + fun takeIf( + @PropertyKey(resourceBundle = BUNDLE) key: String, + vararg params: Any, + condition: () -> Boolean): String? { + if (condition()) + return message(key, params) + return null + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/error/ErrorUtils.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/error/ErrorUtils.kt deleted file mode 100644 index 1fe7945adb..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/error/ErrorUtils.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.utbot.intellij.plugin.error - -import com.intellij.openapi.application.invokeLater -import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.Messages - -fun showErrorDialogLater(project: Project, message: String, title: String) { - invokeLater { - Messages.showErrorDialog(project, message, title) - } -} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt new file mode 100644 index 0000000000..550c366084 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerationController.kt @@ -0,0 +1,934 @@ +package org.utbot.intellij.plugin.generator + +import com.intellij.analysis.AnalysisScope +import com.intellij.codeInsight.CodeInsightUtil +import com.intellij.codeInsight.FileModificationService +import com.intellij.codeInsight.actions.OptimizeImportsProcessor +import com.intellij.ide.fileTemplates.FileTemplateManager +import com.intellij.ide.fileTemplates.FileTemplateUtil +import com.intellij.ide.fileTemplates.JavaTemplateUtil +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction +import com.intellij.openapi.command.executeCommand +import com.intellij.openapi.editor.Document +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.fileTypes.FileType +import com.intellij.openapi.module.Module +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Computable +import com.intellij.openapi.wm.ToolWindowManager +import com.intellij.psi.JavaDirectoryService +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiClassOwner +import com.intellij.psi.PsiComment +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiFileFactory +import com.intellij.psi.PsiManager +import com.intellij.psi.PsiMethod +import com.intellij.psi.SmartPointerManager +import com.intellij.psi.SmartPsiElementPointer +import com.intellij.psi.codeStyle.CodeStyleManager +import com.intellij.psi.codeStyle.JavaCodeStyleManager +import com.intellij.psi.codeStyle.JavaCodeStyleManager.DO_NOT_ADD_IMPORTS +import com.intellij.psi.search.GlobalSearchScopesCore +import com.intellij.testIntegration.TestIntegrationUtils +import com.siyeh.ig.psiutils.ImportUtils +import mu.KotlinLogging +import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.core.getPackage +import org.jetbrains.kotlin.idea.core.util.toPsiDirectory +import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.utbot.common.FileUtil +import org.utbot.common.HTML_LINE_SEPARATOR +import org.utbot.common.PathUtil.toHtmlLinkTag +import org.utbot.framework.CancellationStrategyType.CANCEL_EVERYTHING +import org.utbot.framework.CancellationStrategyType.NONE +import org.utbot.framework.CancellationStrategyType.SAVE_PROCESSED_RESULTS +import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.domain.Import +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.RegularImport +import org.utbot.framework.codegen.domain.StaticImport +import org.utbot.framework.codegen.tree.ututils.UtilClassKind +import org.utbot.framework.codegen.tree.ututils.UtilClassKind.Companion.UT_UTILS_INSTANCE_NAME +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.intellij.plugin.inspection.UnitTestBotInspectionManager +import org.utbot.intellij.plugin.models.GenerateTestsModel +import org.utbot.intellij.plugin.models.packageName +import org.utbot.intellij.plugin.process.EngineProcess +import org.utbot.intellij.plugin.process.RdTestGenerationResult +import org.utbot.intellij.plugin.sarif.SarifReportIdea +import org.utbot.intellij.plugin.ui.CommonLoggingNotifier +import org.utbot.intellij.plugin.ui.DetailsTestsReportNotifier +import org.utbot.intellij.plugin.ui.SarifReportNotifier +import org.utbot.intellij.plugin.ui.TestReportUrlOpeningListener +import org.utbot.intellij.plugin.ui.TestsReportNotifier +import org.utbot.intellij.plugin.ui.WarningTestsReportNotifier +import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath +import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots +import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.EDT_LATER +import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.THREAD_POOL +import org.utbot.intellij.plugin.util.IntelliJApiHelper.Target.WRITE_ACTION +import org.utbot.intellij.plugin.util.IntelliJApiHelper.run +import org.utbot.intellij.plugin.util.RunConfigurationHelper +import org.utbot.intellij.plugin.util.assertIsDispatchThread +import org.utbot.intellij.plugin.util.assertIsWriteThread +import org.utbot.intellij.plugin.util.extractClassMethodsIncludingNested +import java.nio.file.Path +import java.util.concurrent.CancellationException +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import org.utbot.intellij.plugin.util.showSettingsEditor +import org.utbot.sarif.* + +object CodeGenerationController { + private val logger = KotlinLogging.logger {} + + private class UtilClassListener { + var requiredUtilClassKind: UtilClassKind? = null + + fun onTestClassGenerated(result: UtilClassKind?) { + requiredUtilClassKind = maxOfNullable(requiredUtilClassKind, result) + } + } + + fun generateTests( + model: GenerateTestsModel, + classesWithTests: Map, + psi2KClass: Map, + process: EngineProcess, + indicator: ProgressIndicator + ) { + assertIsDispatchThread() + val baseTestDirectory = model.testSourceRoot?.toPsiDirectory(model.project) + ?: return + val allTestPackages = getPackageDirectories(baseTestDirectory) + val latch = CountDownLatch(classesWithTests.size) + val testFilesPointers = mutableListOf>() + val srcClassPathToSarifReport = mutableMapOf() + val utilClassListener = UtilClassListener() + var index = 0 + for ((srcClass, generateResult) in classesWithTests) { + if (indicator.isCanceled) { + when (UtSettings.cancellationStrategyType) { + NONE, + SAVE_PROCESSED_RESULTS -> {} + CANCEL_EVERYTHING -> break + } + } + + val (count, testSetsId) = generateResult + if (count <= 0) { + latch.countDown() + continue + } + + val classUnderTest = psi2KClass[srcClass] ?: error("Didn't find KClass instance for class ${srcClass.name}") + val testClassName = process.findTestClassName(classUnderTest) + + try { + UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.CODEGEN, "Write test cases for class ${srcClass.name}", index.toDouble() / classesWithTests.size) + val classPackageName = model.getTestClassPackageNameFor(srcClass) + val testDirectory = allTestPackages[classPackageName] ?: baseTestDirectory + + val testClass = createTestClass(testClassName, testDirectory, model) ?: continue + val testFilePointer = SmartPointerManager.getInstance(model.project) + .createSmartPsiElementPointer(testClass.containingFile) + runWriteCommandAction(model.project, "Generate tests with UnitTestBot", null, { + generateCodeAndReport( + process, + testSetsId, + srcClass, + classUnderTest, + testClass, + testFilePointer, + srcClassPathToSarifReport, + model, + latch, + utilClassListener, + indicator + ) + testFilesPointers.add(testFilePointer) + }) + } catch (e : CancellationException) { + throw e + } catch (e: Exception) { + showCreatingClassError(model.project, testClassName) + } finally { + index++ + } + } + + run(THREAD_POOL, indicator, "Waiting for per-class Sarif reports") { + waitForCountDown(latch, indicator = indicator) { + run(EDT_LATER, indicator,"Go to EDT for utility class creation") { + run(WRITE_ACTION, indicator, "Need write action for utility class creation") { + createUtilityClassIfNeeded(utilClassListener, model, baseTestDirectory, indicator) + run(THREAD_POOL, indicator, "Generate summary Sarif report") { + proceedTestReport(process, model) + val sarifReportsPath = + model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) + UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Merge Sarif reports", 0.75) + mergeSarifReports(model, sarifReportsPath) + if (model.runGeneratedTestsWithCoverage) { + UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Start tests with coverage", 0.95) + RunConfigurationHelper.runTestsWithCoverage(model, testFilesPointers) + } + process.terminate() + UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Generation finished", 1.0) + + run(EDT_LATER, null, "Run sarif-based inspections") { + runInspectionsIfNeeded(model, srcClassPathToSarifReport) + } + } + } + } + } + } + } + + /** + * Runs the UTBot inspection if there are detected errors. + */ + private fun runInspectionsIfNeeded( + model: GenerateTestsModel, + srcClassPathToSarifReport: MutableMap + ) { + if (!model.runInspectionAfterTestGeneration) { + return + } + val sarifHasResults = srcClassPathToSarifReport.any { (_, sarif) -> + sarif.getAllResults().isNotEmpty() + } + if (!sarifHasResults) { + return + } + UnitTestBotInspectionManager + .getInstance(model.project, SarifReport.minimizeSarifResults(srcClassPathToSarifReport)) + .createNewGlobalContext() + .doInspections(AnalysisScope(model.project)) + } + + private fun proceedTestReport(proc: EngineProcess, model: GenerateTestsModel) { + try { + // Parametrized tests are not supported in tests report yet + // TODO JIRA:1507 + if (model.parametrizedTestSource != ParametrizedTestSource.PARAMETRIZE) { + showTestsReport(proc, model) + } + } catch (e: Exception) { + logger.error(e) { "Failed to save tests report" } + showErrorDialogLater( + model.project, + message = "Cannot save tests generation report: error occurred '${e.message}'", + title = "Failed to save tests report" + ) + } + } + + private fun createUtilityClassIfNeeded( + utilClassListener: UtilClassListener, + model: GenerateTestsModel, + baseTestDirectory: PsiDirectory, + indicator: ProgressIndicator + ) { + val requiredUtilClassKind = utilClassListener.requiredUtilClassKind + ?: return // no util class needed + + val existingUtilClass = model.codegenLanguage.getUtilClassOrNull(model.project, model.testModule) + val utilClassKind = newUtilClassKindOrNull(existingUtilClass, requiredUtilClassKind, model.codegenLanguage) + if (utilClassKind != null) { + createOrUpdateUtilClass( + testDirectory = baseTestDirectory, + utilClassKind = utilClassKind, + existingUtilClass = existingUtilClass, + model = model, + indicator = indicator + ) + } + } + + /** + * This method decides whether to overwrite an existing util class with a new one. And if so, then with what kind of util class. + * - If no util class exists, then we generate a new one. + * - If existing util class' version is out of date, then we overwrite it with a new one. + * But we use the maximum of two kinds (existing and the new one) to avoid problems with mocks. + * - If existing util class is up-to-date **and** has a greater or equal priority than the new one, + * then we do not need to overwrite it (return null). + * - Lastly, if the new util class kind has a greater priority than the existing one, + * then we do overwrite it with a newer version. + * + * @param existingUtilClass a [PsiFile] representing a file of an existing util class. If it does not exist, then [existingUtilClass] is `null`. + * @param requiredUtilClassKind the kind of the new util class that we attempt to generate. + * @return an [UtilClassKind] of a new util class that will be created or `null`, if no new util class is needed. + */ + private fun newUtilClassKindOrNull( + existingUtilClass: PsiFile?, + requiredUtilClassKind: UtilClassKind, + codegenLanguage: CodegenLanguage, + ): UtilClassKind? { + if (existingUtilClass == null) { + // If no util class exists, then we should create a new one with the given kind. + return requiredUtilClassKind + } + + val existingUtilClassVersion = existingUtilClass.utilClassVersionOrNull ?: return requiredUtilClassKind + val newUtilClassVersion = requiredUtilClassKind.utilClassVersion(codegenLanguage) + val versionIsUpdated = existingUtilClassVersion != newUtilClassVersion + + val existingUtilClassKind = existingUtilClass.utilClassKindOrNull(codegenLanguage) ?: return requiredUtilClassKind + + if (versionIsUpdated) { + // If an existing util class is out of date, then we must overwrite it with a newer version. + // But we choose the kind with more priority, because it is possible that + // the existing util class needed mocks, but the new one doesn't. + // In this case we still want to support mocks, because the previously generated tests + // expect that the util class does support them. + return maxOfNullable(existingUtilClassKind, requiredUtilClassKind) + } + + if (requiredUtilClassKind <= existingUtilClassKind) { + // If the existing util class kind has a greater or equal priority than the new one we attempt to generate, + // then we should not do anything. The existing util class is already enough. + return null + } + + // The last case. The existing util class has a strictly less priority than the new one. + // So we generate the new one to overwrite the previous one with it. + return requiredUtilClassKind + } + + /** + * If [existingUtilClass] is null (no util class exists), then we create package directories for util class, + * create util class itself, and put it into the corresponding directory. + * Otherwise, we overwrite the existing util class with a new one. + * This is necessary in case if existing util class has no mocks support, but the newly generated tests do use mocks. + * So, we overwrite an util class with a new one that does support mocks. + * + * @param testDirectory root test directory where we will put our generated tests. + * @param utilClassKind kind of util class required by the test class(es) that we generated. + * @param existingUtilClass util class that already exists or null if it does not yet exist. + * @param model [GenerateTestsModel] that contains some useful information for util class generation. + */ + private fun createOrUpdateUtilClass( + testDirectory: PsiDirectory, + utilClassKind: UtilClassKind, + existingUtilClass: PsiFile?, + model: GenerateTestsModel, + indicator: ProgressIndicator + ) { + val language = model.codegenLanguage + + val utUtilsFile = if (existingUtilClass == null) { + // create a directory to put utils class into + val utilClassDirectory = createUtUtilSubdirectories(testDirectory, language) + // create util class file and put it into utils directory + createNewUtilClass(utilClassDirectory, language, utilClassKind, model) + } else { + overwriteUtilClass(existingUtilClass, utilClassKind, model, indicator) + } + + val utUtilsClass = runReadAction { + // there's only one class in the file + (utUtilsFile as PsiClassOwner).classes.first() + } + + runWriteCommandAction(model.project, "UnitTestBot util class reformatting", null, { + reformat(model, SmartPointerManager.getInstance(model.project).createSmartPsiElementPointer(utUtilsFile), utUtilsClass) + }) + + val utUtilsDocument = runReadAction { + FileDocumentManager + .getInstance() + .getDocument(utUtilsFile.viewProvider.virtualFile) ?: error("Failed to get a Document for UtUtils file") + } + + unblockDocument(model.project, utUtilsDocument) + } + + private fun overwriteUtilClass( + existingUtilClass: PsiFile, + utilClassKind: UtilClassKind, + model: GenerateTestsModel, + indicator: ProgressIndicator + ): PsiFile { + val utilsClassDocument = runReadAction { + PsiDocumentManager + .getInstance(model.project) + .getDocument(existingUtilClass) + ?: error("Failed to get Document for UtUtils class PsiFile: ${existingUtilClass.name}") + } + + val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage) + + run(EDT_LATER, indicator, "Overwrite utility class") { + run(WRITE_ACTION, indicator, "Overwrite utility class") { + unblockDocument(model.project, utilsClassDocument) + executeCommand { + utilsClassDocument.setText(utUtilsText.replace("jdk.internal.misc", "sun.misc")) + } + unblockDocument(model.project, utilsClassDocument) + } + } + return existingUtilClass + } + + /** + * This method creates an util class file and adds it into [utilClassDirectory]. + * + * @param utilClassDirectory directory to put util class into. + * @param language language of util class. + * @param utilClassKind kind of util class required by the test class(es) that we generated. + * @param model [GenerateTestsModel] that contains some useful information for util class generation. + */ + private fun createNewUtilClass( + utilClassDirectory: PsiDirectory, + language: CodegenLanguage, + utilClassKind: UtilClassKind, + model: GenerateTestsModel, + ): PsiFile { + val utUtilsName = language.utilClassFileName + + val utUtilsText = utilClassKind.getUtilClassText(model.codegenLanguage) + + var utUtilsFile = runReadAction { + PsiFileFactory.getInstance(model.project) + .createFileFromText( + utUtilsName, + model.codegenLanguage.fileType, + utUtilsText + ) + } + + // add UtUtils class file into the utils directory + runWriteCommandAction(model.project) { + // The file actually added to subdirectory may be the copy of original file -- see [PsiElement.add] docs + utUtilsFile = utilClassDirectory.add(utUtilsFile) as PsiFile + } + + return utUtilsFile + } + + /** + * Util class must have a comment that specifies its version. + * This property represents the version specified by this comment if it exists. Otherwise, the property is `null`. + */ + private val PsiFile.utilClassVersionOrNull: String? + get() = runReadAction { + val utilClass = (this as? PsiClassOwner) + ?.classes + ?.firstOrNull() + ?: return@runReadAction null + + utilClass.getChildrenOfType() + .map { comment -> comment.text } + .firstOrNull { text -> UtilClassKind.UTIL_CLASS_VERSION_COMMENT_PREFIX in text } + ?.substringAfterLast(UtilClassKind.UTIL_CLASS_VERSION_COMMENT_PREFIX) + ?.substringBefore("\n") + ?.trim() + } + + /** + * Util class must have a comment that specifies its kind. + * This property obtains the kind specified by this comment if it exists. Otherwise, the property is `null`. + */ + private fun PsiFile.utilClassKindOrNull(codegenLanguage: CodegenLanguage): UtilClassKind? + = runReadAction { + val utilClass = (this as? PsiClassOwner) + ?.classes + ?.firstOrNull() + ?: return@runReadAction null + + utilClass.getChildrenOfType() + .map { comment -> comment.text } + .firstNotNullOfOrNull { text -> UtilClassKind.utilClassKindByCommentOrNull(text, codegenLanguage) } + } + + /** + * @param srcClass class under test + * @return name of the package of a given [srcClass]. + * Null is returned if [PsiDirectory.getPackage] call returns null for the [srcClass] directory. + */ + private fun GenerateTestsModel.getTestClassPackageNameFor(srcClass: PsiClass): String? { + return when { + testPackageName.isNullOrEmpty() -> srcClass.containingFile.containingDirectory.getPackage()?.qualifiedName + else -> testPackageName + } + } + + private val CodegenLanguage.utilClassFileName: String + get() = "$UT_UTILS_INSTANCE_NAME${this.extension}" + + /** + * @param testDirectory root test directory where we will put our generated tests. + * @return directory for util class if it exists or null otherwise. + */ + private fun getUtilDirectoryOrNull( + testDirectory: PsiDirectory, + codegenLanguage: CodegenLanguage, + ): PsiDirectory? { + val directoryNames = UtilClassKind.utilsPackageNames(codegenLanguage) + var currentDirectory = testDirectory + for (name in directoryNames) { + val subdirectory = runReadAction { currentDirectory.findSubdirectory(name) } ?: return null + currentDirectory = subdirectory + } + return currentDirectory + } + + /** + * @param testDirectory root test directory where we will put our generated tests. + * @return file of util class if it exists or null otherwise. + */ + private fun CodegenLanguage.getUtilClassOrNull( + testDirectory: PsiDirectory, + codegenLanguage: CodegenLanguage, + ): PsiFile? { + return runReadAction { + val utilDirectory = getUtilDirectoryOrNull(testDirectory, codegenLanguage) + utilDirectory?.findFile(this.utilClassFileName) + } + } + + /** + * @param project project whose classes we generate tests for. + * @param testModule module where the generated tests will be placed. + * @return an existing util class from one of the test source roots + * in the given [testModule] or `null` if no util class was found. + */ + private fun CodegenLanguage.getUtilClassOrNull(project: Project, testModule: Module): PsiFile? { + val psiManager = PsiManager.getInstance(project) + + // all test roots for the given test module + val testRoots = runReadAction { + testModule + .suitableTestSourceRoots() + .mapNotNull { psiManager.findDirectory(it.dir) } + } + + // return an util class from one of the test source roots or null if no util class was found + return testRoots.firstNotNullOfOrNull { testRoot -> getUtilClassOrNull(testRoot, this) } + } + + /** + * Create all package directories for UtUtils class. + * @return the innermost directory - utils from `org.utbot.runtime.utils` + */ + private fun createUtUtilSubdirectories( + baseTestDirectory: PsiDirectory, + codegenLanguage: CodegenLanguage, + ): PsiDirectory { + val directoryNames = UtilClassKind.utilsPackageNames(codegenLanguage) + var currentDirectory = baseTestDirectory + runWriteCommandAction(baseTestDirectory.project) { + for (name in directoryNames) { + currentDirectory = currentDirectory.findSubdirectory(name) ?: currentDirectory.createSubdirectory(name) + } + } + return currentDirectory + } + + /** + * @return Java or Kotlin file type depending on the given [CodegenLanguage] + */ + private val CodegenLanguage.fileType: FileType + get() = when (this) { + CodegenLanguage.JAVA -> JavaFileType.INSTANCE + CodegenLanguage.KOTLIN -> KotlinFileType.INSTANCE + } + + private fun waitForCountDown(latch: CountDownLatch, timeout: Long = 5, timeUnit: TimeUnit = TimeUnit.SECONDS, indicator : ProgressIndicator, action: Runnable) { + try { + if (!latch.await(timeout, timeUnit)) { + run(THREAD_POOL, indicator, "Waiting for ${latch.count} sarif report(s) in a loop") { + waitForCountDown(latch, timeout, timeUnit, indicator, action) } + } else { + action.run() + } + } catch (ignored: InterruptedException) { + } + } + + private fun mergeSarifReports(model: GenerateTestsModel, sarifReportsPath: Path) { + val mergedReportFile = sarifReportsPath + .resolve("${model.project.name}Report.sarif") + .toFile() + // deleting the old report so that `sarifReports` does not contain it + mergedReportFile.delete() + + val sarifReports = sarifReportsPath.toFile() + .walkTopDown() + .filter { it.extension == "sarif" } + .map { it.readText() } + .toList() + + val mergedReport = SarifReport.mergeReports(sarifReports) + mergedReportFile.writeText(mergedReport) + + // notifying the user + SarifReportNotifier.notify( + info = """ + SARIF report was saved to ${toHtmlLinkTag(mergedReportFile.path)}$HTML_LINE_SEPARATOR + """.trimIndent() + ) + } + + private fun getPackageDirectories(baseDirectory: PsiDirectory): Map { + val allSubdirectories = mutableMapOf() + getPackageDirectoriesRecursively(baseDirectory, allSubdirectories) + + return allSubdirectories + } + + private fun getPackageDirectoriesRecursively( + baseDirectory: PsiDirectory, + innerPackageNames: MutableMap, + ) { + baseDirectory.getPackage()?.qualifiedName?.let { innerPackageNames[it] = baseDirectory } + for (subDir in baseDirectory.subdirectories) { + getPackageDirectoriesRecursively(subDir, innerPackageNames) + } + } + + private fun createTestClass(testClassName: String, testDirectory: PsiDirectory, model: GenerateTestsModel): PsiClass? { + val aPackage = JavaDirectoryService.getInstance().getPackage(testDirectory) + + if (aPackage != null) { + val scope = GlobalSearchScopesCore.directoryScope(testDirectory, false) + + val application = ApplicationManager.getApplication() + val testClass = application.executeOnPooledThread { + return@executeOnPooledThread application.runReadAction { + DumbService.getInstance(model.project).runReadActionInSmartMode(Computable { + // Here we use firstOrNull(), because by some unknown reason + // findClassByShortName() may return two identical objects. + // Be careful, do not use singleOrNull() here, because it expects + // the array to contain strictly one element and otherwise returns null. + return@Computable aPackage.findClassByShortName(testClassName, scope) + .firstOrNull { + when (model.codegenLanguage) { + CodegenLanguage.JAVA -> it !is KtUltraLightClass + CodegenLanguage.KOTLIN -> it is KtUltraLightClass + } + } + }) + } + }.get() + + testClass?.let { + return if (FileModificationService.getInstance().preparePsiElementForWrite(it)) it else null + } + } + + val fileTemplate = FileTemplateManager.getInstance(testDirectory.project).getInternalTemplate( + when (model.codegenLanguage) { + CodegenLanguage.JAVA -> JavaTemplateUtil.INTERNAL_CLASS_TEMPLATE_NAME + CodegenLanguage.KOTLIN -> "Kotlin Class" + } + ) + runWriteAction { testDirectory.findFile(testClassName + model.codegenLanguage.extension)?.delete() } + val createFromTemplate: PsiElement = FileTemplateUtil.createFromTemplate( + fileTemplate, + testClassName, + FileTemplateManager.getInstance(testDirectory.project).defaultProperties, + testDirectory + ) + + return (createFromTemplate.containingFile as PsiClassOwner).classes.first() + } + + private fun generateCodeAndReport( + proc: EngineProcess, + testSetsId: Long, + srcClass: PsiClass, + classUnderTest: ClassId, + testClass: PsiClass, + filePointer: SmartPsiElementPointer, + srcClassPathToSarifReport: MutableMap, + model: GenerateTestsModel, + reportsCountDown: CountDownLatch, + utilClassListener: UtilClassListener, + indicator: ProgressIndicator + ) { + assertIsWriteThread() + val classMethods = srcClass.extractClassMethodsIncludingNested(false) + val testPackageName = testClass.packageName + val editor = CodeInsightUtil.positionCursorAtLBrace(testClass.project, filePointer.containingFile, testClass) + //TODO: Use PsiDocumentManager.getInstance(model.project).getDocument(file) + // if we don't want to open _all_ new files with tests in editor one-by-one + run(THREAD_POOL, indicator, "Rendering test code") { + val (generatedTestsCode, utilClassKind) = try { + val paramNames = try { + proc.findMethodParamNames(classUnderTest, classMethods) + } catch (e: Exception) { + logger.warn(e) { "Cannot find method param names for ${classUnderTest.name}" } + reportsCountDown.countDown() + return@run + } + proc.render( + model, + testSetsId, + classUnderTest, + paramNames.toMutableMap(), + generateUtilClassFile = true, + enableTestsTimeout = true, + testPackageName, + ) + } catch (e: Exception) { + logger.warn(e) { "Cannot render test class ${testClass.name}" } + reportsCountDown.countDown() + return@run + } + utilClassListener.onTestClassGenerated(utilClassKind) + run(EDT_LATER, indicator, "Writing generation text to documents") { + run(WRITE_ACTION, indicator, "Writing generation text to documents") { + try { + unblockDocument(testClass.project, editor.document) + // TODO: JIRA:1246 - display warnings if we rewrite the file + executeCommand(testClass.project, "Insert Generated Tests") { + editor.document.setText(generatedTestsCode.replace("jdk.internal.misc.Unsafe", "sun.misc.Unsafe")) + } + unblockDocument(testClass.project, editor.document) + } catch (e: Exception) { + logger.error(e) { "Cannot save document for ${testClass.name}" } + reportsCountDown.countDown() + return@run + } + + // after committing the document the `testClass` is invalid in PsiTree, + // so we have to reload it from the corresponding `file` + val testClassUpdated = (filePointer.containingFile as PsiClassOwner).classes.first() // only one class in the file + + // reformatting before creating reports due to + // SarifReport requires the final version of the generated tests code +// run(THREAD_POOL, indicator) { +// IntentionHelper(model.project, editor, filePointer).applyIntentions() + run(EDT_LATER, indicator, "Tests reformatting") { + try { + runWriteCommandAction(filePointer.project, "UnitTestBot tests reformatting", null, { + reformat(model, filePointer, testClassUpdated) + }) + unblockDocument(testClassUpdated.project, editor.document) + } catch (e : Exception) { + logger.error(e) { "Cannot save Sarif report for ${testClassUpdated.name}" } + } + // uploading formatted code + val file = filePointer.containingFile + + val srcClassPath = srcClass.containingFile.virtualFile.toNioPath() + saveSarifReport( + proc, + testSetsId, + testClassUpdated, + classUnderTest, + model, + reportsCountDown, + file?.text ?: generatedTestsCode, + srcClassPathToSarifReport, + srcClassPath, + indicator + ) + + unblockDocument(testClassUpdated.project, editor.document) + } +// } + } + } + } + } + + private fun reformat(model: GenerateTestsModel, smartPointer: SmartPsiElementPointer, testClass: PsiClass) { + val project = model.project + val codeStyleManager = CodeStyleManager.getInstance(project) + val file = smartPointer.containingFile?: return + val fileLength = runReadAction { + FileDocumentManager.getInstance().getDocument(file.virtualFile)?.textLength + ?: file.virtualFile.length.toInt() + } + if (fileLength > UtSettings.maxTestFileSize && file.name != model.codegenLanguage.utilClassFileName) { + CommonLoggingNotifier().notify( + "Size of ${file.virtualFile.presentableName} exceeds configured limit " + + "(${FileUtil.byteCountToDisplaySize(UtSettings.maxTestFileSize.toLong())}), reformatting was skipped.", + model.project, model.testModule, arrayOf(DumbAwareAction.create("Configure the Limit") { showSettingsEditor(model.project, "maxTestFileSize") } + )) + return + } + + DumbService.getInstance(model.project).runWhenSmart { + OptimizeImportsProcessor(project, file).run() + codeStyleManager.reformat(file) + when (model.codegenLanguage) { + CodegenLanguage.JAVA -> { + val range = file.textRange + val startOffset = range.startOffset + val endOffset = range.endOffset + val reformatRange = codeStyleManager.reformatRange(file, startOffset, endOffset, false) + JavaCodeStyleManager.getInstance(project).shortenClassReferences(reformatRange, DO_NOT_ADD_IMPORTS) + } + CodegenLanguage.KOTLIN -> ShortenReferences.DEFAULT.process((testClass as KtUltraLightClass).kotlinOrigin.containingKtFile) + } + } + } + + private fun saveSarifReport( + proc: EngineProcess, + testSetsId: Long, + testClass: PsiClass, + testClassId: ClassId, + model: GenerateTestsModel, + reportsCountDown: CountDownLatch, + generatedTestsCode: String, + srcClassPathToSarifReport: MutableMap, + srcClassPath: Path, + indicator: ProgressIndicator + ) { + val project = model.project + + try { + // saving sarif report + SarifReportIdea.createAndSave( + proc, + testSetsId, + testClassId, + model, + generatedTestsCode, + testClass, + reportsCountDown, + srcClassPathToSarifReport, + srcClassPath, + indicator + ) + } catch (e: Exception) { + logger.error(e) { "error in saving sarif report"} + showErrorDialogLater( + project, + message = "Cannot save Sarif report via generated tests: error occurred '${e.message}'", + title = "Failed to save Sarif report" + ) + } + } + + + private fun eventLogMessage(project: Project): String? = runReadAction { + return@runReadAction if (ToolWindowManager.getInstance(project).getToolWindow("Event Log") != null) + """ + See details in Event Log. + """.trimIndent() + else null + } + + private fun showTestsReport(proc: EngineProcess, model: GenerateTestsModel) { + val (notifyMessage, statistics, hasWarnings) = proc.generateTestsReport(model, eventLogMessage(model.project)) + + runReadAction { + if (hasWarnings) { + WarningTestsReportNotifier.notify(notifyMessage) + } else { + TestsReportNotifier.notify(notifyMessage) + } + + statistics?.let { DetailsTestsReportNotifier.notify(it) } + } + } + + @Suppress("unused") + // this method was used in the past, not used in the present but may be used in the future + private fun insertImports(testClass: PsiClass, imports: List, editor: Editor) { + unblockDocument(testClass.project, editor.document) + executeCommand(testClass.project, "Insert Generated Tests") { + imports.forEach { import -> + when (import) { + is StaticImport -> { + if (testClass is KtUltraLightClass) { + ImportInsertHelperImpl.addImport( + (testClass as PsiClass).project, + testClass.kotlinOrigin.containingKtFile, + FqName(import.qualifiedName) + ) + } else { + ImportUtils.addStaticImport(import.qualifierClass, import.memberName, testClass) + } + } + is RegularImport -> { } + else -> { } + } + } + } + unblockDocument(testClass.project, editor.document) + } + + @Suppress("unused") + // this method was used in the past, not used in the present but may be used in the future + private fun insertMethods(testClass: PsiClass, superBody: String, editor: Editor) { + val dummyMethod = TestIntegrationUtils.createDummyMethod(testClass) + if (testClass is KtUltraLightClass) { + val ktClass = testClass.kotlinOrigin as KtClass + val factory = KtPsiFactory((testClass as PsiClass).project) + val function: KtNamedFunction = factory.createFunction(dummyMethod.text) + val addDeclaration: KtNamedFunction = ktClass.addDeclaration(function) + + unblockDocument(testClass.project, editor.document) + executeCommand(testClass.project, "Insert Generated Tests") { + replace(editor, addDeclaration, superBody) + } + unblockDocument(testClass.project, editor.document) + } else { + val method = (testClass.add(dummyMethod)) as PsiMethod + unblockDocument(testClass.project, editor.document) + executeCommand(testClass.project, "Insert Generated Tests") { + replace(editor, method, superBody) + } + unblockDocument(testClass.project, editor.document) + } + } + + private fun unblockDocument(project: Project, document: Document) { + PsiDocumentManager.getInstance(project).apply { + commitDocument(document) + doPostponedOperationsAndUnblockDocument(document) + } + } + + private fun replace(editor: Editor, element: PsiElement, body: String) { + val startOffset = element.startOffset + val endOffset = element.endOffset + editor.document.replaceString(startOffset, endOffset, body) + } + + private fun showCreatingClassError(project: Project, testClassName: String) { + showErrorDialogLater( + project, + message = "Cannot Create Class '$testClassName'", + title = "Failed to Create Class" + ) + } + + private fun > maxOfNullable(a: T?, b: T?): T? { + return when { + a == null -> b + b == null -> a + else -> maxOf(a, b) + } + } +} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerator.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerator.kt deleted file mode 100644 index e8472f9112..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/CodeGenerator.kt +++ /dev/null @@ -1,100 +0,0 @@ -package org.utbot.intellij.plugin.generator - -import org.utbot.framework.TestSelectionStrategyType -import org.utbot.framework.UtSettings -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.UtBotTestCaseGenerator -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.intellij.plugin.settings.Settings -import org.utbot.summary.summarize -import com.intellij.openapi.components.service -import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.project.Project -import com.intellij.psi.PsiMethod -import com.intellij.refactoring.util.classMembers.MemberInfo -import org.utbot.engine.UtBotSymbolicEngine -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.reflect.KClass -import kotlin.reflect.KFunction -import kotlin.reflect.KParameter.Kind -import kotlin.reflect.full.functions -import kotlin.reflect.jvm.javaType - -val logger = Logger.getInstance(CodeGenerator::class.java) - -class CodeGenerator( - private val searchDirectory: Path, - private val mockStrategy: MockStrategyApi, - project: Project, - private val chosenClassesToMockAlways: Set, - buildDir: String, - classpath: String, - pluginJarsPath: String, - configureEngine: (UtBotSymbolicEngine) -> Unit = {}, - isCanceled: () -> Boolean, -) { - init { - UtSettings.testMinimizationStrategyType = TestSelectionStrategyType.COVERAGE_STRATEGY - } - - val generator = (project.service().testCasesGenerator as UtBotTestCaseGenerator).apply { - init(Paths.get(buildDir), classpath, pluginJarsPath, configureEngine, isCanceled) - } - - private val settingsState = project.service().state - - fun executions(method: UtMethod<*>) = generator.generate(method, mockStrategy).summarize(searchDirectory) - - fun generateForSeveralMethods(methods: List>, timeout:Long = UtSettings.utBotGenerationTimeoutInMillis): List { - logger.info("Tests generating parameters $settingsState") - - return generator - .generateForSeveralMethods(methods, mockStrategy, chosenClassesToMockAlways, methodsGenerationTimeout = timeout) - .map { it.summarize(searchDirectory) } - } -} - -fun findMethodsInClassMatchingSelected(clazz: KClass<*>, selectedMethods: List): List> { - val selectedSignatures = selectedMethods.map { it.signature() } - return clazz.functions - .sortedWith(compareBy { selectedSignatures.indexOf(it.signature()) }) - .filter { it.signature().normalized() in selectedSignatures } - .map { UtMethod(it, clazz) } -} - -fun findMethodParams(clazz: KClass<*>, methods: List): Map, List> { - val bySignature = methods.associate { it.signature() to it.paramNames() } - return clazz.functions.mapNotNull { method -> - bySignature[method.signature()]?.let { params -> - UtMethod(method, clazz) to params - } - }.toMap() -} - -private fun MemberInfo.signature(): Signature = - (this.member as PsiMethod).signature() - -private fun MemberInfo.paramNames(): List = - (this.member as PsiMethod).parameterList.parameters.map { it.name } - -private fun PsiMethod.signature() = - Signature(this.name, this.parameterList.parameters.map { - it.type.canonicalText - .replace("...", "[]") //for PsiEllipsisType - .replace(",", ", ") // to fix cases like Pair -> Pair - }) - -private fun KFunction<*>.signature() = - Signature(this.name, this.parameters.filter { it.kind == Kind.VALUE }.map { it.type.javaType.typeName }) - -data class Signature(val name: String, val parameterTypes: List) { - - fun normalized() = this.copy( - parameterTypes = parameterTypes.map { - it?.replace("$", ".") // normalize names of nested classes - } - ) -} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/IntentionHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/IntentionHelper.kt new file mode 100644 index 0000000000..6cb224bd97 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/IntentionHelper.kt @@ -0,0 +1,97 @@ +package org.utbot.intellij.plugin.generator + +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx +import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator +import com.intellij.codeInsight.daemon.impl.HighlightInfo +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Computable +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiFile +import com.intellij.psi.SmartPsiElementPointer +import mu.KotlinLogging +import org.jetbrains.kotlin.idea.util.application.runReadAction + +private val logger = KotlinLogging.logger {} +// The required part of IntelliJ API was changed to com.intellij.codeInsight.daemon.impl.MainPassesRunner that is available since 2022.1 only +class IntentionHelper(val project: Project, private val editor: Editor, private val testFile: SmartPsiElementPointer) { + fun applyIntentions() { + val actions = + DumbService.getInstance(project).runReadActionInSmartMode(Computable> { + val daemonProgressIndicator = DaemonProgressIndicator() + Disposer.register(project) { daemonProgressIndicator.cancel() }//check it + val list = ProgressManager.getInstance().runProcess(Computable> inner@{ + try { + val containingFile = testFile.containingFile ?: return@inner emptyList() + DaemonCodeAnalyzerEx.getInstanceEx(project).runMainPasses( + containingFile, + editor.document, + daemonProgressIndicator + ) + } catch (e: Exception) { + logger.info { e } + emptyList()// 'Cannot obtain read-action' rare case + } + }, daemonProgressIndicator) + val actions = mutableMapOf() + list.forEach { info -> + val quickFixActionRanges = info.quickFixActionRanges + if (!quickFixActionRanges.isNullOrEmpty()) { + val toList = + quickFixActionRanges.map { pair: com.intellij.openapi.util.Pair -> pair.first.action } + .toList() + toList.forEach { intentionAction -> actions[intentionAction] = intentionAction.familyName } + } + } + actions + }) + actions.forEach { + if (runReadAction { + it.value.isApplicable() && it.key.isAvailable( + project, + editor, + testFile.containingFile + ) + }) { + if (it.key.startInWriteAction()) { + WriteCommandAction.runWriteCommandAction(project) { + invokeIntentionAction(it) + } + } else { + runReadAction { + invokeIntentionAction(it) + } + } + } + } + } + + private fun invokeIntentionAction(it: Map.Entry) { + it.key.invoke(project, editor, testFile.containingFile) + } + + private fun String.isApplicable(): Boolean { + if (this.startsWith("Change type of actual to ")) return true + if (this == "Replace 'switch' with 'if'") return true // SetsTest + if (this == "Replace with allMatch()") return true + if (this == "Remove redundant cast(s)") return true // SetsTest + if (this == "Collapse 'catch' blocks") return true // MapsTest + if (this == "Replace lambda with method reference") return true // MockRandomExamplesTest + if (this == "Inline variable") return true // ServiceWithFieldTest + if (this == "Optimize imports") return true + if (this.startsWith("Replace 'if else' with '&&'")) return true + if (this.startsWith("Merge with 'case")) return true // CodegenExampleTest + // if (this.equals("Simplify assertion")) return true // RecursiveTypeTest + // if (this.familyName.startsWith("Try to generify ")) return true + return false + // "Generify File" shows TypeCookDialog to update JavaRefactoringSettings.getInstance() and then call invokeRefactoring + // We may do the same without dialog interaction + // "Collapse into loop" for duplicate lines like collection.add(...) comes from background later + // We may implement it in codegen by ourselves + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt deleted file mode 100644 index b27bd8d365..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/TestGenerator.kt +++ /dev/null @@ -1,513 +0,0 @@ -package org.utbot.intellij.plugin.generator - -import org.utbot.common.HTML_LINE_SEPARATOR -import org.utbot.common.PathUtil.classFqnToPath -import org.utbot.common.PathUtil.toHtmlLinkTag -import org.utbot.common.appendHtmlLine -import org.utbot.framework.codegen.Import -import org.utbot.framework.codegen.ParametrizedTestSource -import org.utbot.framework.codegen.StaticImport -import org.utbot.framework.codegen.TestsCodeWithTestReport -import org.utbot.framework.codegen.model.ModelBasedTestCodeGenerator -import org.utbot.framework.codegen.model.constructor.tree.TestsGenerationReport -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.intellij.plugin.sarif.SarifReportIdea -import org.utbot.intellij.plugin.sarif.SourceFindingStrategyIdea -import org.utbot.intellij.plugin.settings.Settings -import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath -import org.utbot.intellij.plugin.ui.utils.getOrCreateTestResourcesPath -import org.utbot.sarif.SarifReport -import com.intellij.codeInsight.CodeInsightUtil -import com.intellij.codeInsight.FileModificationService -import com.intellij.ide.fileTemplates.FileTemplateManager -import com.intellij.ide.fileTemplates.FileTemplateUtil -import com.intellij.ide.fileTemplates.JavaTemplateUtil -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.application.runReadAction -import com.intellij.openapi.application.runWriteAction -import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction -import com.intellij.openapi.command.executeCommand -import com.intellij.openapi.components.service -import com.intellij.openapi.editor.Document -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.project.DumbService -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.Computable -import com.intellij.openapi.vfs.VfsUtil -import com.intellij.psi.* -import com.intellij.psi.codeStyle.CodeStyleManager -import com.intellij.psi.codeStyle.JavaCodeStyleManager -import com.intellij.psi.search.GlobalSearchScopesCore -import com.intellij.testIntegration.TestIntegrationUtils -import com.intellij.util.IncorrectOperationException -import com.intellij.util.concurrency.AppExecutorUtil -import com.intellij.util.io.exists -import com.siyeh.ig.psiutils.ImportUtils -import java.nio.file.Path -import java.nio.file.Paths -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import org.jetbrains.kotlin.asJava.classes.KtUltraLightClass -import org.jetbrains.kotlin.idea.core.ShortenReferences -import org.jetbrains.kotlin.idea.core.getPackage -import org.jetbrains.kotlin.idea.core.util.toPsiDirectory -import org.jetbrains.kotlin.idea.util.ImportInsertHelperImpl -import org.jetbrains.kotlin.idea.util.application.invokeLater -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi.KtClass -import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.kotlin.psi.KtPsiFactory -import org.jetbrains.kotlin.psi.psiUtil.endOffset -import org.jetbrains.kotlin.psi.psiUtil.startOffset -import org.jetbrains.kotlin.scripting.resolve.classId -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.withUtContext -import org.utbot.intellij.plugin.error.showErrorDialogLater -import org.utbot.intellij.plugin.generator.TestGenerator.Target.* -import org.utbot.intellij.plugin.ui.GenerateTestsModel -import org.utbot.intellij.plugin.ui.SarifReportNotifier -import org.utbot.intellij.plugin.ui.TestReportUrlOpeningListener -import org.utbot.intellij.plugin.ui.TestsReportNotifier -import org.utbot.intellij.plugin.ui.packageName - -object TestGenerator { - private enum class Target {THREAD_POOL, READ_ACTION, WRITE_ACTION, EDT_LATER} - - private fun run(target: Target, runnable: Runnable) { - UtContext.currentContext()?.let { - when (target) { - THREAD_POOL -> AppExecutorUtil.getAppExecutorService().submit { - withUtContext(it) { - runnable.run() - } - } - READ_ACTION -> runReadAction { withUtContext(it) { runnable.run() } } - WRITE_ACTION -> runWriteAction { withUtContext(it) { runnable.run() } } - EDT_LATER -> invokeLater { withUtContext(it) { runnable.run() } } - } - } ?: error("No context in thread ${Thread.currentThread()}") - } - - fun generateTests(model: GenerateTestsModel, testCasesByClass: Map>) { - val baseTestDirectory = model.testSourceRoot?.toPsiDirectory(model.project) - ?: return - val allTestPackages = getPackageDirectories(baseTestDirectory) - val latch = CountDownLatch(testCasesByClass.size) - - for (srcClass in testCasesByClass.keys) { - val testCases = testCasesByClass[srcClass] ?: continue - try { - val classPackageName = if (model.testPackageName.isNullOrEmpty()) - srcClass.containingFile.containingDirectory.getPackage()?.qualifiedName else model.testPackageName - val testDirectory = allTestPackages[classPackageName] ?: baseTestDirectory - val testClass = createTestClass(srcClass, testDirectory, model) ?: continue - val file = testClass.containingFile - runWriteCommandAction(model.project, "Generate tests with UtBot", null, { - try { - addTestMethodsAndSaveReports(testClass, file, testCases, model, latch) - } catch (e: IncorrectOperationException) { - showCreatingClassError(model.project, createTestClassName(srcClass)) - } - }) - } catch (e: IncorrectOperationException) { - showCreatingClassError(model.project, createTestClassName(srcClass)) - } - } - run(READ_ACTION) { - val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) - run(THREAD_POOL) { - waitForCountDown(latch, model, sarifReportsPath) - } - } - } - - private fun waitForCountDown(latch: CountDownLatch, model: GenerateTestsModel, sarifReportsPath : Path) { - try { - if (!latch.await(5, TimeUnit.SECONDS)) { - run(THREAD_POOL) { waitForCountDown(latch, model, sarifReportsPath) } - } else { - mergeSarifReports(model, sarifReportsPath) - } - } catch (ignored: InterruptedException) { - } - } - - private fun mergeSarifReports(model: GenerateTestsModel, sarifReportsPath : Path) { - val sarifReports = sarifReportsPath.toFile() - .walkTopDown() - .filter { it.extension == "sarif" } - .map { it.readText() } - .toList() - - val mergedReport = SarifReport.mergeReports(sarifReports) - val mergedReportPath = sarifReportsPath.resolve("${model.project.name}Report.sarif") - mergedReportPath.toFile().writeText(mergedReport) - - // notifying the user - SarifReportNotifier.notify( - info = """ - SARIF report was saved to ${toHtmlLinkTag(mergedReportPath.toString())}$HTML_LINE_SEPARATOR - You can open it using the VS Code extension "Sarif Viewer" - """.trimIndent() - ) - } - - private fun getPackageDirectories(baseDirectory: PsiDirectory): Map { - val allSubdirectories = mutableMapOf() - getPackageDirectoriesRecursively(baseDirectory, allSubdirectories) - - return allSubdirectories - } - - private fun getPackageDirectoriesRecursively( - baseDirectory: PsiDirectory, - innerPackageNames: MutableMap, - ) { - baseDirectory.getPackage()?.qualifiedName?.let { innerPackageNames[it] = baseDirectory } - for (subDir in baseDirectory.subdirectories) { - getPackageDirectoriesRecursively(subDir, innerPackageNames) - } - } - - private fun createTestClass(srcClass: PsiClass, testDirectory: PsiDirectory, model: GenerateTestsModel): PsiClass? { - val testClassName = createTestClassName(srcClass) - val aPackage = JavaDirectoryService.getInstance().getPackage(testDirectory) - - if (aPackage != null) { - val scope = GlobalSearchScopesCore.directoryScope(testDirectory, false) - - val application = ApplicationManager.getApplication() - val testClass = application.executeOnPooledThread { - return@executeOnPooledThread application.runReadAction { - DumbService.getInstance(model.project).runReadActionInSmartMode(Computable { - // Here we use firstOrNull(), because by some unknown reason - // findClassByShortName() may return two identical objects. - // Be careful, do not use singleOrNull() here, because it expects - // the array to contain strictly one element and otherwise returns null. - return@Computable aPackage.findClassByShortName(testClassName, scope) - .firstOrNull { - when (model.codegenLanguage) { - CodegenLanguage.JAVA -> it !is KtUltraLightClass - CodegenLanguage.KOTLIN -> it is KtUltraLightClass - } - } - }) - } - }.get() - - testClass?.let { - return if (FileModificationService.getInstance().preparePsiElementForWrite(it)) it else null - } - } - - val fileTemplate = FileTemplateManager.getInstance(testDirectory.project).getInternalTemplate( - when (model.codegenLanguage) { - CodegenLanguage.JAVA -> JavaTemplateUtil.INTERNAL_CLASS_TEMPLATE_NAME - CodegenLanguage.KOTLIN -> "Kotlin Class" - } - ) - runWriteAction { testDirectory.findFile(testClassName + model.codegenLanguage.extension)?.delete() } - val createFromTemplate: PsiElement = FileTemplateUtil.createFromTemplate( - fileTemplate, - testClassName, - FileTemplateManager.getInstance(testDirectory.project).defaultProperties, - testDirectory - ) - - return (createFromTemplate.containingFile as PsiClassOwner).classes.first() - } - - private fun addTestMethodsAndSaveReports( - testClass: PsiClass, - file: PsiFile, - testCases: List, - model: GenerateTestsModel, - reportsCountDown: CountDownLatch, - ) { - val selectedMethods = TestIntegrationUtils.extractClassMethods(testClass, false) - val testFramework = model.testFramework - val mockito = model.mockFramework - val staticsMocking = model.staticsMocking - - val classUnderTest = testCases.first().method.clazz - - val params = findMethodParams(classUnderTest, selectedMethods) - - val generator = model.project.service().codeGenerator.apply { - init( - classUnderTest = classUnderTest.java, - params = params.toMutableMap(), - testFramework = testFramework, - mockFramework = mockito, - codegenLanguage = model.codegenLanguage, - parameterizedTestSource = model.parametrizedTestSource, - staticsMocking = staticsMocking, - forceStaticMocking = model.forceStaticMocking, - generateWarningsForStaticMocking = model.generateWarningsForStaticMocking, - runtimeExceptionTestsBehaviour = model.runtimeExceptionTestsBehaviour, - hangingTestsTimeout = model.hangingTestsTimeout, - testClassPackageName = testClass.packageName - ) - } - - when (generator) { - is ModelBasedTestCodeGenerator -> { - val editor = CodeInsightUtil.positionCursorAtLBrace(testClass.project, file, testClass) - //TODO Use PsiDocumentManager.getInstance(model.project).getDocument(file) - // if we don't want to open _all_ new files with tests in editor one-by-one - run(THREAD_POOL) { - val testsCodeWithTestReport = generator.generateAsStringWithTestReport(testCases) - val generatedTestsCode = testsCodeWithTestReport.generatedCode - run(EDT_LATER) { - run(WRITE_ACTION) { - unblockDocument(testClass.project, editor.document) - // TODO: JIRA:1246 - display warnings if we rewrite the file - executeCommand(testClass.project, "Insert Generated Tests") { - editor.document.setText(generatedTestsCode) - } - unblockDocument(testClass.project, editor.document) - - // after committing the document the `testClass` is invalid in PsiTree, - // so we have to reload it from the corresponding `file` - val testClassUpdated = (file as PsiClassOwner).classes.first() // only one class in the file - - // reformatting before creating reports due to - // SarifReport requires the final version of the generated tests code - runWriteCommandAction(testClassUpdated.project, "UtBot tests reformatting", null, { - reformat(model, file, testClassUpdated) - }) - unblockDocument(testClassUpdated.project, editor.document) - - // uploading formatted code - val testsCodeWithTestReportFormatted = - testsCodeWithTestReport.copy(generatedCode = file.text) - - // creating and saving reports - saveSarifAndTestReports( - testClassUpdated, - testCases, - model, - testsCodeWithTestReportFormatted, - reportsCountDown - ) - - unblockDocument(testClassUpdated.project, editor.document) - } - } - } - } - //Note that reportsCountDown.countDown() has to be called in every generator implementation to complete whole process - else -> TODO("Only model based code generator supported, but got: ${generator::class}") - } - } - - private fun reformat(model: GenerateTestsModel, file: PsiFile, testClass: PsiClass) { - val project = model.project - val codeStyleManager = CodeStyleManager.getInstance(project) - codeStyleManager.reformat(file) - when (model.codegenLanguage) { - CodegenLanguage.JAVA -> { - val range = file.textRange - val startOffset = range.startOffset - val endOffset = range.endOffset - val reformatRange = codeStyleManager.reformatRange(file, startOffset, endOffset, false) - JavaCodeStyleManager.getInstance(project).shortenClassReferences(reformatRange) - } - CodegenLanguage.KOTLIN -> ShortenReferences.DEFAULT.process((testClass as KtUltraLightClass).kotlinOrigin.containingKtFile) - } - } - - - private fun saveSarifAndTestReports( - testClass: PsiClass, - testCases: List, - model: GenerateTestsModel, - testsCodeWithTestReport: TestsCodeWithTestReport, - reportsCountDown: CountDownLatch - ) { - val project = model.project - val generatedTestsCode = testsCodeWithTestReport.generatedCode - - try { - // saving sarif report - val sourceFinding = SourceFindingStrategyIdea(testClass) - executeCommand(testClass.project, "Saving Sarif report") { - SarifReportIdea.createAndSave(model, testCases, generatedTestsCode, sourceFinding) - } - } catch (e: Exception) { - showErrorDialogLater( - project, - message = "Cannot save Sarif report via generated tests: error occurred '${e.message}'", - title = "Failed to save Sarif report" - ) - } finally { - reportsCountDown.countDown() - } - - try { - // Parametrized tests are not supported in tests report yet - // TODO JIRA:1507 - if (model.parametrizedTestSource != ParametrizedTestSource.PARAMETRIZE) { - executeCommand(testClass.project, "Saving tests report") { - saveTestsReport(testsCodeWithTestReport, model) - } - } - } catch (e: Exception) { - showErrorDialogLater( - project, - message = "Cannot save tests generation report: error occurred '${e.message}'", - title = "Failed to save tests report" - ) - } - } - - private fun saveTestsReport(testsCodeWithTestReport: TestsCodeWithTestReport, model: GenerateTestsModel) { - val testResourcesDirPath = model.testModule.getOrCreateTestResourcesPath(model.testSourceRoot) - - require(testResourcesDirPath.exists()) { - "Test resources directory $testResourcesDirPath does not exist" - } - - val testReportSubDir = "utbot-tests-report" - val classFqn = with(testsCodeWithTestReport.testsGenerationReport.classUnderTest) { - qualifiedName ?: error("Could not save tests report for anonymous or local class $this") - } - val fileReportPath = classFqnToPath(classFqn) - - val resultedReportedPath = - Paths.get(testResourcesDirPath.toString(), testReportSubDir, fileReportPath + "TestReport" + TestsGenerationReport.EXTENSION) - - val parent = resultedReportedPath.parent - requireNotNull(parent) { - "Expected from parent of $resultedReportedPath to be not null but it is null" - } - - VfsUtil.createDirectories(parent.toString()) - resultedReportedPath.toFile().writeText(testsCodeWithTestReport.testsGenerationReport.getFileContent()) - - processInitialWarnings(testsCodeWithTestReport, model) - - val notifyMessage = buildString { - appendHtmlLine(testsCodeWithTestReport.testsGenerationReport.toString()) - appendHtmlLine() - val classUnderTestPackageName = testsCodeWithTestReport.testsGenerationReport.classUnderTest.classId.packageFqName.toString() - if (classUnderTestPackageName != model.testPackageName) { - val warningMessage = """ - Warning: Destination package ${model.testPackageName} does not match package of the class $classUnderTestPackageName. - This may cause unnecessary usage of reflection for protected or package-private fields and methods access. - """.trimIndent() - appendHtmlLine(warningMessage) - appendHtmlLine() - } - val savedFileMessage = """ - Tests report was saved to ${toHtmlLinkTag(resultedReportedPath.toString())} in TSV format - """.trimIndent() - appendHtmlLine(savedFileMessage) - } - TestsReportNotifier.notify(notifyMessage, model.project, model.testModule) - } - - private fun processInitialWarnings(testsCodeWithTestReport: TestsCodeWithTestReport, model: GenerateTestsModel) { - val hasInitialWarnings = model.forceMockHappened || model.hasTestFrameworkConflict - if (!hasInitialWarnings) { - return - } - - testsCodeWithTestReport.testsGenerationReport.apply { - summaryMessage = { "Unit tests for $classUnderTest were generated with warnings.
    " } - - if (model.forceMockHappened) { - initialWarnings.add { - """ - Warning: Some test cases were ignored, because no mocking framework is installed in the project.
    - Better results could be achieved by installing mocking framework. - """.trimIndent() - } - } - if (model.hasTestFrameworkConflict) { - initialWarnings.add { - """ - Warning: There are several test frameworks in the project. - To select run configuration, please refer to the documentation depending on the project build system: - Gradle, - Maven - or Idea. - """.trimIndent() - } - } - } - } - - @Suppress("unused") - // this method was used in the past, not used in the present but may be used in the future - private fun insertImports(testClass: PsiClass, imports: List, editor: Editor) { - unblockDocument(testClass.project, editor.document) - executeCommand(testClass.project, "Insert Generated Tests") { - imports.forEach { import -> - when (import) { - is StaticImport -> { - if (testClass is KtUltraLightClass) { - ImportInsertHelperImpl.addImport( - (testClass as PsiClass).project, - testClass.kotlinOrigin.containingKtFile, - FqName(import.qualifiedName) - ) - } else { - ImportUtils.addStaticImport(import.qualifierClass, import.memberName, testClass) - } - } - } - } - } - unblockDocument(testClass.project, editor.document) - } - - private fun createTestClassName(srcClass: PsiClass) = srcClass.name + "Test" - - @Suppress("unused") - // this method was used in the past, not used in the present but may be used in the future - private fun insertMethods(testClass: PsiClass, superBody: String, editor: Editor) { - val dummyMethod = TestIntegrationUtils.createDummyMethod(testClass) - if (testClass is KtUltraLightClass) { - val ktClass = testClass.kotlinOrigin as KtClass - val factory = KtPsiFactory((testClass as PsiClass).project) - val function: KtNamedFunction = factory.createFunction(dummyMethod.text) - val addDeclaration: KtNamedFunction = ktClass.addDeclaration(function) - - unblockDocument(testClass.project, editor.document) - executeCommand(testClass.project, "Insert Generated Tests") { - replace(editor, addDeclaration, superBody) - } - unblockDocument(testClass.project, editor.document) - } else { - val method = (testClass.add(dummyMethod)) as PsiMethod - unblockDocument(testClass.project, editor.document) - executeCommand(testClass.project, "Insert Generated Tests") { - replace(editor, method, superBody) - } - unblockDocument(testClass.project, editor.document) - } - } - - private fun unblockDocument(project: Project, document: Document) { - PsiDocumentManager.getInstance(project).apply { - commitDocument(document) - doPostponedOperationsAndUnblockDocument(document) - } - } - - private fun replace(editor: Editor, element: PsiElement, body: String) { - val startOffset = element.startOffset - val endOffset = element.endOffset - editor.document.replaceString(startOffset, endOffset, body) - } - - private fun showCreatingClassError(project: Project, testClassName: String) { - showErrorDialogLater( - project, - message = "Cannot Create Class '$testClassName'", - title = "Failed to Create Class" - ) - } -} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt new file mode 100644 index 0000000000..94fed03ce8 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt @@ -0,0 +1,591 @@ +package org.utbot.intellij.plugin.generator + +import com.intellij.openapi.application.* +import com.intellij.openapi.compiler.CompilerPaths +import com.intellij.openapi.components.service +import com.intellij.openapi.module.Module +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.progress.Task +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.OrderEnumerator +import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.util.Computable +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.project.stateStore +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiMethod +import com.intellij.psi.util.PsiUtil +import com.intellij.refactoring.util.classMembers.MemberInfo +import com.intellij.task.ProjectTask +import com.intellij.task.ProjectTaskManager +import com.intellij.task.impl.ModuleBuildTaskImpl +import com.intellij.task.impl.ModuleFilesBuildTaskImpl +import com.intellij.task.impl.ProjectTaskList +import com.intellij.util.PathsList +import com.intellij.util.concurrency.AppExecutorUtil +import com.intellij.util.containers.ContainerUtil +import com.intellij.util.containers.nullize +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.Arrays +import java.util.concurrent.TimeUnit +import java.util.stream.Collectors +import kotlin.io.path.exists +import kotlin.io.path.pathString +import mu.KotlinLogging +import org.jetbrains.concurrency.Promise +import org.jetbrains.concurrency.all +import org.jetbrains.idea.maven.project.MavenProjectsManager +import org.jetbrains.kotlin.idea.base.util.module +import org.utbot.framework.CancellationStrategyType.CANCEL_EVERYTHING +import org.utbot.framework.CancellationStrategyType.NONE +import org.utbot.framework.CancellationStrategyType.SAVE_PROCESSED_RESULTS +import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.domain.ProjectType.* +import org.utbot.framework.context.simple.SimpleApplicationContext +import org.utbot.framework.context.simple.SimpleMockerContext +import org.utbot.framework.context.spring.SpringApplicationContextImpl +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.SpringSettings.* +import org.utbot.framework.plugin.api.SpringConfiguration.* +import org.utbot.framework.plugin.api.SpringTestType.* +import org.utbot.framework.plugin.api.util.LockFile +import org.utbot.framework.plugin.api.util.withStaticsSubstitutionRequired +import org.utbot.framework.plugin.services.JdkInfoService +import org.utbot.framework.plugin.services.WorkingDirService +import org.utbot.framework.process.SpringAnalyzerTask +import org.utbot.instrumentation.instrumentation.spring.SpringUtExecutionInstrumentation +import org.utbot.intellij.plugin.generator.CodeGenerationController.generateTests +import org.utbot.intellij.plugin.models.GenerateTestsModel +import org.utbot.intellij.plugin.process.EngineProcess +import org.utbot.intellij.plugin.process.RdTestGenerationResult +import org.utbot.intellij.plugin.settings.Settings +import org.utbot.intellij.plugin.ui.GenerateTestsDialogWindow +import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle +import org.utbot.intellij.plugin.ui.utils.showErrorDialogLater +import org.utbot.intellij.plugin.ui.utils.testModules +import org.utbot.intellij.plugin.util.IntelliJApiHelper +import org.utbot.intellij.plugin.util.PsiClassHelper +import org.utbot.intellij.plugin.util.isAbstract +import org.utbot.intellij.plugin.util.binaryName +import org.utbot.intellij.plugin.util.PluginJdkInfoProvider +import org.utbot.intellij.plugin.util.PluginWorkingDirProvider +import org.utbot.intellij.plugin.util.assertIsNonDispatchThread +import org.utbot.intellij.plugin.util.extractClassMethodsIncludingNested +import org.utbot.rd.terminateOnException + +object UtTestsDialogProcessor { + private val logger = KotlinLogging.logger {} + + enum class ProgressRange(val from : Double, val to: Double) { + INITIALIZATION(from = 0.01, to = 0.1), + SOLVING(from = 0.1, to = 0.9), + CODEGEN(from = 0.9, to = 0.95), + SARIF(from = 0.95, to = 1.0) + } + + fun updateIndicator(indicator: ProgressIndicator, range : ProgressRange, text: String? = null, fraction: Double? = null) { + invokeLater { + if (indicator.isCanceled) return@invokeLater + text?.let { indicator.text = it } + fraction?.let { + indicator.fraction = + indicator.fraction.coerceAtLeast(range.from + (range.to - range.from) * fraction.coerceIn(0.0, 1.0)) + } + logger.debug("Phase ${indicator.text} with progress ${String.format("%.2f",indicator.fraction)}") + } + } + + + fun createDialogAndGenerateTests( + project: Project, + srcClasses: Set, + extractMembersFromSrcClasses: Boolean, + focusedMethods: Set, + ) { + createDialog(project, srcClasses, extractMembersFromSrcClasses, focusedMethods)?.let { + if (it.showAndGet()) createTests(project, it.model) + } + } + + private fun createDialog( + project: Project, + srcClasses: Set, + extractMembersFromSrcClasses: Boolean, + focusedMethods: Set, + ): GenerateTestsDialogWindow? { + val srcModule = findSrcModule(srcClasses) + val testModules = srcModule.testModules(project) + + JdkInfoService.jdkInfoProvider = PluginJdkInfoProvider(project) + // we want to start the instrumented process in the same directory as the test runner + WorkingDirService.workingDirProvider = PluginWorkingDirProvider(project) + + val model = GenerateTestsModel( + project, + srcModule, + testModules, + srcClasses, + extractMembersFromSrcClasses, + focusedMethods, + project.service().generationTimeoutInMillis + ) + if (model.getAllTestSourceRoots().isEmpty() && project.isBuildWithGradle) { + val errorMessage = """ + No test source roots found in the project.
    + Please, create or configure at least one test source root. + """.trimIndent() + showErrorDialogLater(project, errorMessage, "Test source roots not found") + return null + } + + return GenerateTestsDialogWindow(model) + } + + private fun compile( + project: Project, + files: Array, + springConfigClass: PsiClass?, + ): Promise { + val buildTasks = runReadAction { + // For Maven project narrow compile scope may not work, see https://github.com/UnitTestBot/UTBotJava/issues/2021. + // For Spring project classes may contain `@ComponentScan` annotations, so we need to compile the whole module. + val isMavenProject = MavenProjectsManager.getInstance(project)?.hasProjects() ?: false + val isSpringProject = springConfigClass != null + val wholeModules = isMavenProject || isSpringProject + + ContainerUtil.map>, ProjectTask>( + Arrays.stream(files).collect(Collectors.groupingBy { file: VirtualFile -> + ProjectFileIndex.getInstance(project).getModuleForFile(file, false) + }).entries + ) { (key, value): Map.Entry?> -> + if (wholeModules) { + // This is a specific case, we have to compile the whole module + ModuleBuildTaskImpl(key!!, false) + } else { + // Compile only chosen classes and their dependencies before generation. + ModuleFilesBuildTaskImpl(key, false, value) + } + } + } + return ProjectTaskManager.getInstance(project).run(ProjectTaskList(buildTasks)) + } + + private fun createTests(project: Project, model: GenerateTestsModel) { + val springConfigClass = + when (val settings = model.springSettings) { + is AbsentSpringSettings -> null + is PresentSpringSettings -> + when (val config = settings.configuration) { + is JavaBasedConfiguration -> { + PsiClassHelper + .findClass(config.configBinaryName, project) + ?: error("Cannot find configuration class ${config.configBinaryName}.") + } + // TODO: for XML config we also need to compile module containing, + // since it may reference classes from that module + is XMLConfiguration -> null + } + } + + val filesToCompile = (model.srcClasses + listOfNotNull(springConfigClass)) + .map { it.containingFile.virtualFile } + .toTypedArray() + + compile(project, filesToCompile, springConfigClass).onSuccess { task -> + if (task.hasErrors() || task.isAborted) + return@onSuccess + + (object : Task.Backgroundable(project, "Generate tests") { + + override fun run(indicator: ProgressIndicator) { + assertIsNonDispatchThread() + if (!LockFile.lock()) { + return + } + + UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis = model.hangingTestsTimeout.timeoutMs + UtSettings.useCustomJavaDocTags = model.commentStyle == JavaDocCommentStyle.CUSTOM_JAVADOC_TAGS + UtSettings.summaryGenerationType = model.summariesGenerationType + + fun now() = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS")) + + try { + logger.info { "Collecting information phase started at ${now()}" } + val secondsTimeout = TimeUnit.MILLISECONDS.toSeconds(model.timeout) + + indicator.isIndeterminate = false + updateIndicator(indicator, ProgressRange.INITIALIZATION, "Generate tests: starting engine", 0.0) + + // TODO sometimes preClasspathCollectionPromises get stuck, even though all + // needed dependencies get installed, we need to figure out why that happens + try { + model.preClasspathCollectionPromises + .all() + .blockingGet(10, TimeUnit.SECONDS) + } catch (e: java.util.concurrent.TimeoutException) { + logger.warn { "preClasspathCollectionPromises are stuck over 10 seconds, ignoring them" } + } + + val buildPaths = ReadAction + .nonBlocking { + findPaths(listOf(findSrcModule(model.srcClasses)) + when (model.projectType) { + Spring -> listOfNotNull( + model.testModule, // needed so we can use `TestContextManager` from `spring-test` + springConfigClass?.let { it.module ?: error("Module for Spring configuration class not found") } + ) + else -> emptyList() + }) + } + .executeSynchronously() + ?: return + + val (buildDirs, classpath, classpathList, pluginJarsPath) = buildPaths + + val testSetsByClass = mutableMapOf() + val psi2KClass = mutableMapOf() + var processedClasses = 0 + val totalClasses = model.srcClasses.size + val classNameToPath = runReadAction { + model.srcClasses.associate { psiClass -> + psiClass.binaryName to psiClass.containingFile.virtualFile.canonicalPath + } + } + + val mockFrameworkInstalled = model.mockFramework.isInstalled + val staticMockingConfigured = model.staticsMocking.isConfigured + + val process = EngineProcess.createBlocking(project, classNameToPath) + updateIndicator(indicator, ProgressRange.INITIALIZATION, fraction = 0.2) + + process.terminateOnException { _ -> + val classpathForClassLoader = buildDirs + classpathList + when (model.projectType) { + Spring -> listOf(SpringUtExecutionInstrumentation.springCommonsJar.path) + else -> emptyList() + } + + process.setupUtContext(classpathForClassLoader) + val simpleApplicationContext = SimpleApplicationContext( + SimpleMockerContext(mockFrameworkInstalled, staticMockingConfigured) + ) + val applicationContext = when (model.projectType) { + Spring -> { + val beanDefinitions = + when (val settings = model.springSettings) { + is AbsentSpringSettings -> emptyList() + is PresentSpringSettings -> { + process.perform( + SpringAnalyzerTask( + classpath = classpathForClassLoader, + settings = settings + ) + ) + } + } + + val clarifiedBeanDefinitions = + clarifyBeanDefinitionReturnTypes(beanDefinitions, project) + + SpringApplicationContextImpl.internalCreate( + simpleApplicationContext, + clarifiedBeanDefinitions, + model.springTestType, + model.springSettings, + ) + } + else -> simpleApplicationContext + } + updateIndicator(indicator, ProgressRange.INITIALIZATION, fraction = 0.25) + process.createTestGenerator( + buildDirs, + classpath, + pluginJarsPath.joinToString(separator = File.pathSeparator), + JdkInfoService.provide(), + applicationContext, + ) { + ApplicationManager.getApplication().runReadAction(Computable { + indicator.isCanceled + }) + } + updateIndicator(indicator, ProgressRange.INITIALIZATION, fraction = 1.0) + + for (srcClass in model.srcClasses) { + if (indicator.isCanceled) { + when (UtSettings.cancellationStrategyType) { + NONE -> {} + SAVE_PROCESSED_RESULTS, + CANCEL_EVERYTHING -> break + } + } + + val (methods, classNameForLog) = process.executeWithTimeoutSuspended { + var binaryName = "" + var srcMethods: List = emptyList() + var srcNameForLog: String? = null + DumbService.getInstance(project) + .runReadActionInSmartMode(Computable { + binaryName = srcClass.binaryName + srcNameForLog = srcClass.name + srcMethods = if (model.extractMembersFromSrcClasses) { + val chosenMethods = + model.selectedMembers.filter { it.member is PsiMethod } + val chosenNestedClasses = + model.selectedMembers.mapNotNull { it.member as? PsiClass } + chosenMethods + chosenNestedClasses.flatMap { + it.extractClassMethodsIncludingNested(false) + } + } else { + srcClass.extractClassMethodsIncludingNested(false) + } + }) + val classId = process.obtainClassId(binaryName) + psi2KClass[srcClass] = classId + process.findMethodsInClassMatchingSelected( + classId, + srcMethods + ) to srcNameForLog + } + + if (methods.isEmpty()) { + logger.error { "No methods matching selected found in class $classNameForLog." } + continue + } + + logger.info { "Collecting information phase finished at ${now()}" } + + updateIndicator( + indicator, + ProgressRange.SOLVING, + "Generate test cases for class $classNameForLog", + processedClasses.toDouble() / totalClasses + ) + + val searchDirectory = ReadAction + .nonBlocking { + project.basePath?.let { Paths.get(it) } + ?: Paths.get(srcClass.containingFile.virtualFile.parent.path) + } + .executeSynchronously() + + val taintConfigPath = getTaintConfigPath(project) + + withStaticsSubstitutionRequired(true) { + val startTime = System.currentTimeMillis() + val timerHandler = + AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay({ + val innerTimeoutRatio = + ((System.currentTimeMillis() - startTime).toDouble() / model.timeout) + .coerceIn(0.0, 1.0) + updateIndicator( + indicator, + ProgressRange.SOLVING, + "Generate test cases for class $classNameForLog", + (processedClasses.toDouble() + innerTimeoutRatio) / totalClasses + ) + }, 0, 500, TimeUnit.MILLISECONDS) + try { + val useEngine = when (model.projectType) { + Spring -> when (model.springTestType) { + UNIT_TEST -> true + INTEGRATION_TEST -> false + } + else -> true + } + val useFuzzing = when (model.projectType) { + Spring -> when (model.springTestType) { + UNIT_TEST -> UtSettings.useFuzzing + INTEGRATION_TEST -> true + } + + else -> UtSettings.useFuzzing + } + val rdGenerateResult = process.generate( + conflictTriggers = model.conflictTriggers, + methods = methods, + mockStrategyApi = model.mockStrategy, + chosenClassesToMockAlways = model.chosenClassesToMockAlways, + timeout = model.timeout, + generationTimeout = model.timeout, + isSymbolicEngineEnabled = useEngine, + isFuzzingEnabled = useFuzzing, + fuzzingValue = project.service().fuzzingValue, + searchDirectory = searchDirectory.pathString, + taintConfigPath = taintConfigPath?.pathString + ) + + if (rdGenerateResult.notEmptyCases == 0) { + if (!indicator.isCanceled) { + if (model.srcClasses.size > 1) { + logger.error { "Failed to generate any tests cases for class $classNameForLog" } + } else { + showErrorDialogLater( + model.project, + errorMessage(classNameForLog, secondsTimeout), + title = "Failed to generate unit tests for class $classNameForLog" + ) + } + } else { + logger.warn { "Generation was cancelled for class $classNameForLog" } + } + } else { + testSetsByClass[srcClass] = rdGenerateResult + } + } finally { + timerHandler.cancel(true) + } + } + processedClasses++ + } + + if (processedClasses == 0) { + invokeLater { + Messages.showInfoMessage( + model.project, + "No methods for test generation were found among selected items", + "No Methods Found" + ) + } + return + } + updateIndicator(indicator, ProgressRange.CODEGEN, "Generate code for tests", 0.0) + // Commented out to generate tests for collected executions even if action was canceled. + // indicator.checkCanceled() + + invokeLater { + generateTests(model, testSetsByClass, psi2KClass, process, indicator) + logger.info { "Generation complete" } + } + } + } finally { + LockFile.unlock() + } + } + }).queue() + } + } + + /** + * Returns "{project}/.idea/utbot-taint-config.yaml" or null if it does not exists + */ + private fun getTaintConfigPath(project: Project): Path? { + val path = project.stateStore.directoryStorePath?.resolve("utbot-taint-config.yaml") + return if (path != null && path.toFile().exists()) path else null + } + + private fun clarifyBeanDefinitionReturnTypes(beanDefinitions: List, project: Project) = + beanDefinitions.map { bean -> + // Here we extract a real return type. + // E.g. for a method + // public Toy getToy() { return new SpecToy() } + // we want not Toy but SpecToy type. + // If there are more than one return type, we take type from a signature. + // We process beans with present additional data only. + val beanType = runReadAction { + val additionalData = bean.additionalData ?: return@runReadAction null + + val configPsiClass = + PsiClassHelper + .findClass(additionalData.configClassName, project) + ?: return@runReadAction null + .also { + logger.warn("Cannot find configuration class ${additionalData.configClassName}.") + } + + val beanPsiMethod = + configPsiClass + .findMethodsByName(bean.beanName) + .mapNotNull { jvmMethod -> + (jvmMethod as PsiMethod) + .takeIf { method -> + !method.isAbstract && method.body?.isEmpty == false && + method.parameterList.parameters.map { it.type.canonicalText } == additionalData.parameterTypes + } + } + // Here we try to take a single element + // because we expect no or one method matching previous conditions only. + // If there were two or more similar methods in one class, it would be a weird case. + .singleOrNull() + ?: return@runReadAction null + .also { + logger.warn( + "Several similar methods named ${bean.beanName} " + + "were found in ${additionalData.configClassName} configuration class." + ) + } + + val beanTypes = + PsiUtil + .findReturnStatements(beanPsiMethod) + .mapNotNullTo(mutableSetOf()) { stmt -> stmt.returnValue?.type?.canonicalText } + + beanTypes.singleOrNull() ?: bean.beanTypeName + } ?: return@map bean + + BeanDefinitionData( + beanName = bean.beanName, + beanTypeName = beanType, + additionalData = bean.additionalData + ) + } + + private fun errorMessage(className: String?, timeout: Long) = buildString { + appendLine("UnitTestBot failed to generate any test cases for class $className.") + appendLine() + appendLine("Try to alter test generation configuration, e.g. enable mocking and static mocking.") + appendLine("Alternatively, you could try to increase current timeout $timeout sec for generating tests in generation dialog.") + } + + private fun findSrcModule(srcClasses: Set): Module { + val srcModules = srcClasses.mapNotNull { it.module }.distinct() + return when (srcModules.size) { + 0 -> error("Module for source classes not found") + 1 -> srcModules.first() + else -> error("Can not generate tests for classes from different modules") + } + } + + private fun findPaths(modules: List): BuildPaths? { + val buildDirs = CompilerPaths.getOutputPaths(modules.distinct().toTypedArray()) + .toList() + .filter { Paths.get(it).exists() } + .nullize() ?: return null + + val pathsList = PathsList() + + modules + .distinct() + .map { module -> OrderEnumerator.orderEntries(module).recursively().pathsList } + .forEach { pathsList.addAll(it.pathList) } + + val (classpath, classpathList) = if (IntelliJApiHelper.isAndroidStudio()) { + // Filter out manifests from classpath. + val filterPredicate = { it: String -> + !it.contains("manifest", ignoreCase = true) + } + val classpathList = pathsList.pathList.filter(filterPredicate) + val classpath = StringUtil.join(classpathList, File.pathSeparator) + Pair(classpath, classpathList) + } else { + val classpath = pathsList.pathsString + val classpathList = pathsList.pathList + Pair(classpath, classpathList) + } + val pluginJarsPath = Paths.get(PathManager.getPluginsPath(), "utbot-intellij-main", "lib").toFile().listFiles() + ?: error("Can't find plugin folder.") + return BuildPaths(buildDirs, classpath, classpathList, pluginJarsPath.map { it.path }) + } + + data class BuildPaths( + val buildDirs: List, + val classpath: String, + val classpathList: List, + val pluginJarsPath: List + // ^ TODO: Now we collect ALL dependent libs and pass them to the instrumented and spring analyzer processes. Most of them are redundant. + ) +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/AnalyzeStackTraceFix.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/AnalyzeStackTraceFix.kt new file mode 100644 index 0000000000..434f24399c --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/AnalyzeStackTraceFix.kt @@ -0,0 +1,49 @@ +package org.utbot.intellij.plugin.inspection + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.icons.AllIcons +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Iconable +import com.intellij.unscramble.AnalyzeStacktraceUtil +import javax.swing.Icon + +/** + * Button that launches the built-in "Analyze Stack Trace" action. Displayed as a quick fix. + * + * @param exceptionMessage short description of the detected exception. + * @param stackTraceLines list of strings of the form "className.methodName(fileName:lineNumber)". + */ +class AnalyzeStackTraceFix( + private val exceptionMessage: String, + private val stackTraceLines: List +) : LocalQuickFix, Iconable { + + /** + * Without `invokeLater` the [com.intellij.execution.impl.ConsoleViewImpl.myPredefinedFilters] will not be filled. + * + * See [com.intellij.execution.impl.ConsoleViewImpl.createCompositeFilter] for more details. + */ + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val stackTraceContent = stackTraceLines.joinToString("\n") { "at $it" } + ApplicationManager.getApplication().invokeLater { + AnalyzeStacktraceUtil.addConsole( + /* project = */ project, + /* consoleFactory = */ null, + /* tabTitle = */ "StackTrace", + /* text = */ "$exceptionMessage\n\n$stackTraceContent", + /* icon = */ AllIcons.Actions.Lightning + ) + } + } + + /** + * This text is displayed on the quick fix button. + */ + override fun getName() = "Analyze stack trace" + + override fun getFamilyName() = name + + override fun getIcon(flags: Int): Icon = AllIcons.Actions.Lightning +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionContext.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionContext.kt new file mode 100644 index 0000000000..076c2612e5 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionContext.kt @@ -0,0 +1,71 @@ +@file:Suppress("UnstableApiUsage") + +package org.utbot.intellij.plugin.inspection + +import com.intellij.codeInspection.ex.* +import com.intellij.codeInspection.ui.InspectionToolPresentation +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.NotNullLazyValue +import com.intellij.ui.content.ContentManager +import org.utbot.sarif.Sarif +import java.nio.file.Path +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap + +/** + * Overrides some methods of [GlobalInspectionContextImpl] to satisfy the logic of [UnitTestBotInspectionTool]. + */ +class UnitTestBotInspectionContext( + project: Project, + contentManager: NotNullLazyValue, + private val srcClassPathToSarifReport: MutableMap +) : GlobalInspectionContextImpl(project, contentManager) { + + /** + * See [GlobalInspectionContextImpl.myPresentationMap] for more details. + */ + private val myPresentationMap: ConcurrentMap, InspectionToolPresentation> = + ConcurrentHashMap() + + private val globalInspectionToolWrapper by lazy { + val utbotInspectionTool = UnitTestBotInspectionTool.getInstance(srcClassPathToSarifReport) + GlobalInspectionToolWrapper(utbotInspectionTool).also { + it.initialize(/* context = */ this) + } + } + + /** + * Returns [InspectionProfileImpl] with only one inspection tool - [UnitTestBotInspectionTool]. + */ + override fun getCurrentProfile(): InspectionProfileImpl { + val supplier = InspectionToolsSupplier.Simple(listOf(globalInspectionToolWrapper)) + return InspectionProfileImpl("UnitTestBot", supplier, BASE_PROFILE) + } + + override fun close(noSuspiciousCodeFound: Boolean) { + try { + if (!noSuspiciousCodeFound && (view == null || view.isRerun)) { + return + } + myPresentationMap.clear() + super.close(noSuspiciousCodeFound) + } catch (_: Throwable) { + // already closed + } + } + + override fun cleanup() { + myPresentationMap.clear() + super.cleanup() + } + + /** + * Overriding is needed to provide [UnitTestBotInspectionToolPresentation] + * instead of the standard implementation of the [InspectionToolPresentation]. + */ + override fun getPresentation(toolWrapper: InspectionToolWrapper<*, *>): InspectionToolPresentation { + return myPresentationMap.computeIfAbsent(toolWrapper) { + UnitTestBotInspectionToolPresentation(globalInspectionToolWrapper, context = this) + } + } +} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionManager.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionManager.kt new file mode 100644 index 0000000000..c942e014d9 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionManager.kt @@ -0,0 +1,41 @@ +@file:Suppress("UnstableApiUsage") + +package org.utbot.intellij.plugin.inspection + +import com.intellij.codeInspection.ex.GlobalInspectionContextImpl +import com.intellij.codeInspection.ex.InspectionManagerEx +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.NotNullLazyValue +import com.intellij.ui.content.ContentManager +import org.utbot.sarif.Sarif +import java.nio.file.Path + +/** + * Overrides some methods of [InspectionManagerEx] to satisfy the logic of [UnitTestBotInspectionTool]. + */ +class UnitTestBotInspectionManager(project: Project) : InspectionManagerEx(project) { + + private var srcClassPathToSarifReport: MutableMap = mutableMapOf() + + companion object { + fun getInstance(project: Project, srcClassPathToSarifReport: MutableMap) = + UnitTestBotInspectionManager(project).also { + it.srcClassPathToSarifReport = srcClassPathToSarifReport + } + } + + /** + * See [InspectionManagerEx.myContentManager] for more details. + */ + private val myContentManager: NotNullLazyValue by lazy { + NotNullLazyValue.createValue { + getProblemsViewContentManager(project) + } + } + + /** + * Overriding is needed to provide [UnitTestBotInspectionContext] instead of [GlobalInspectionContextImpl]. + */ + override fun createNewGlobalContext(): GlobalInspectionContextImpl = + UnitTestBotInspectionContext(project, myContentManager, srcClassPathToSarifReport) +} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionTool.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionTool.kt new file mode 100644 index 0000000000..ad1e36fb73 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionTool.kt @@ -0,0 +1,134 @@ +package org.utbot.intellij.plugin.inspection + +import com.intellij.codeInspection.* +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.idea.core.util.toPsiFile +import com.intellij.psi.search.GlobalSearchScope +import org.utbot.sarif.* +import java.nio.file.Path + +/** + * Global inspection tool that displays detected errors from the SARIF report. + */ +class UnitTestBotInspectionTool : GlobalSimpleInspectionTool() { + + /** + * Map from the path to the class under test to [Sarif] for it. + */ + private var srcClassPathToSarifReport: MutableMap = mutableMapOf() + + companion object { + fun getInstance(srcClassPathToSarifReport: MutableMap) = + UnitTestBotInspectionTool().also { + it.srcClassPathToSarifReport = srcClassPathToSarifReport + } + } + + override fun getShortName() = "UnitTestBotInspectionTool" + + override fun getDisplayName() = "Unchecked exceptions" + + override fun getGroupDisplayName() = "Errors detected by UnitTestBot" + + /** + * Appends all the errors from the SARIF report for [srcPsiFile] to the [problemDescriptionsProcessor]. + */ + override fun checkFile( + srcPsiFile: PsiFile, + manager: InspectionManager, + problemsHolder: ProblemsHolder, + globalContext: GlobalInspectionContext, + problemDescriptionsProcessor: ProblemDescriptionsProcessor + ) { + val sarifReport = srcClassPathToSarifReport[srcPsiFile.virtualFile.toNioPath()] + ?: return // no results for this file + + for (sarifResult in sarifReport.getAllResults()) { + val srcFilePhysicalLocation = sarifResult.locations + .filterIsInstance(SarifPhysicalLocationWrapper::class.java) + .firstOrNull()?.physicalLocation ?: continue + val srcFileLogicalLocation = sarifResult.locations + .filterIsInstance(SarifLogicalLocationsWrapper::class.java) + .firstOrNull() + ?.logicalLocations?.firstOrNull() + + // srcPsiFile may != errorPsiFile (if srcFileLogicalLocation != null) + val errorPsiFile = srcFileLogicalLocation?.fullyQualifiedName?.let { errorClassFqn -> + val psiFacade = JavaPsiFacade.getInstance(srcPsiFile.project) + val psiClass = psiFacade.findClass(errorClassFqn, GlobalSearchScope.allScope(srcPsiFile.project)) + val psiFile = psiClass?.containingFile ?: return@let null + + // We can't just return psiFile because it may be non-physical + if (psiFile.isPhysical) { + psiFile + } else { + psiFile.virtualFile.toPsiFile(srcPsiFile.project) + } + } ?: srcPsiFile + val errorRegion = srcFilePhysicalLocation.region + val errorTextRange = getTextRange(problemsHolder.project, errorPsiFile, errorRegion) + + // see `org.utbot.sarif.SarifReport.processUncheckedException` for the message template + val (exceptionMessage, testCaseMessage) = + sarifResult.message.text.split('\n').take(2) + val sarifResultMessage = "$exceptionMessage $testCaseMessage" + + val testFileLocation = sarifResult.relatedLocations.firstOrNull()?.physicalLocation + val viewGeneratedTestFix = testFileLocation?.let { + ViewGeneratedTestFix( + testFileRelativePath = it.artifactLocation.uri, + lineNumber = it.region.startLine, + columnNumber = it.region.startColumn ?: 1 + ) + } + + val stackTraceLines = sarifResult.extractStackTraceLines() + val analyzeStackTraceFix = AnalyzeStackTraceFix(exceptionMessage, stackTraceLines) + + val problemDescriptor = problemsHolder.manager.createProblemDescriptor( + errorPsiFile, + errorTextRange, + sarifResultMessage, + ProblemHighlightType.ERROR, + /* onTheFly = */ true, + viewGeneratedTestFix as LocalQuickFix, + analyzeStackTraceFix + ) + problemDescriptionsProcessor.addProblemElement( + globalContext.refManager.getReference(errorPsiFile), + problemDescriptor + ) + } + } + + // internal + + /** + * Converts [SarifRegion] to the [TextRange] of the given [file]. + */ + private fun getTextRange(project: Project, file: PsiFile, region: SarifRegion): TextRange { + val documentManager = PsiDocumentManager.getInstance(project) + val document = documentManager.getDocument(file.containingFile) + ?: return TextRange.EMPTY_RANGE + + val lineNumber = region.startLine - 1 // to 0-based + val columnNumber = region.startColumn ?: 1 + + val lineStartOffset = document.getLineStartOffset(lineNumber) + columnNumber - 1 + val lineEndOffset = document.getLineEndOffset(lineNumber) + return TextRange(lineStartOffset, lineEndOffset) + } + + private fun SarifResult.extractStackTraceLines(): List = + this.codeFlows.flatMap { sarifCodeFlow -> + sarifCodeFlow.threadFlows.flatMap { sarifThreadFlow -> + sarifThreadFlow.locations.map { sarifFlowLocationWrapper -> + sarifFlowLocationWrapper.location.message.text + } + } + }.reversed() +} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionToolPresentation.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionToolPresentation.kt new file mode 100644 index 0000000000..f777c2cfb6 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/UnitTestBotInspectionToolPresentation.kt @@ -0,0 +1,26 @@ +@file:Suppress("UnstableApiUsage") + +package org.utbot.intellij.plugin.inspection + +import com.intellij.codeInspection.CommonProblemDescriptor +import com.intellij.codeInspection.ex.InspectionToolWrapper +import com.intellij.codeInspection.ui.DefaultInspectionToolPresentation + +/** + * Overrides [resolveProblem] to avoid suppressing quick fix buttons. + */ +class UnitTestBotInspectionToolPresentation( + toolWrapper: InspectionToolWrapper<*, *>, + context: UnitTestBotInspectionContext +) : DefaultInspectionToolPresentation(toolWrapper, context) { + + /** + * This method is called when the user clicks on the quick fix button. + * In the case of [UnitTestBotInspectionTool] we do not want to remove the button after applying the fix. + * + * See [DefaultInspectionToolPresentation.resolveProblem] for more details. + */ + override fun resolveProblem(descriptor: CommonProblemDescriptor) { + // nothing + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/ViewGeneratedTestFix.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/ViewGeneratedTestFix.kt new file mode 100644 index 0000000000..0261677e7d --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/inspection/ViewGeneratedTestFix.kt @@ -0,0 +1,51 @@ +package org.utbot.intellij.plugin.inspection + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.openapi.editor.LogicalPosition +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Iconable +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.util.ui.EmptyIcon +import javax.swing.Icon +import org.utbot.common.PathUtil.toPath + +/** + * Button with a link to the [testFileRelativePath]. Displayed as a quick fix. + * + * @param testFileRelativePath path to the generated test file. + * Should be relative to the project root + * @param lineNumber one-based line number + * @param columnNumber one-based column number + */ +class ViewGeneratedTestFix( + val testFileRelativePath: String, + val lineNumber: Int, + val columnNumber: Int +) : LocalQuickFix, Iconable { + + /** + * Navigates the user to the [lineNumber] line of the [testFileRelativePath] file. + */ + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val testFileAbsolutePath = project.basePath?.toPath()?.resolve(testFileRelativePath) ?: return + val virtualFile = VfsUtil.findFile(testFileAbsolutePath, /* refreshIfNeeded = */ true) ?: return + val editor = FileEditorManager.getInstance(project) + editor.openFile(virtualFile, /* focusEditor = */ true) + val caretModel = editor.selectedTextEditor?.caretModel ?: return + val zeroBasedPosition = LogicalPosition(lineNumber - 1, columnNumber - 1) + caretModel.moveToLogicalPosition(zeroBasedPosition) + val selectionModel = editor.selectedTextEditor?.selectionModel ?: return + selectionModel.selectLineAtCaret() + } + + /** + * This text is displayed on the quick fix button. + */ + override fun getName() = "View generated test" + + override fun getFamilyName() = name + + override fun getIcon(flags: Int): Icon = EmptyIcon.ICON_0 +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtCustomJavaDocTagProvider.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtCustomJavaDocTagProvider.kt new file mode 100644 index 0000000000..cd3dd4566c --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtCustomJavaDocTagProvider.kt @@ -0,0 +1,32 @@ +package org.utbot.intellij.plugin.javadoc + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiReference +import com.intellij.psi.javadoc.CustomJavadocTagProvider +import com.intellij.psi.javadoc.JavadocTagInfo +import com.intellij.psi.javadoc.PsiDocTagValue +import org.utbot.summary.comment.customtags.symbolic.CustomJavaDocTag +import org.utbot.summary.comment.customtags.symbolic.CustomJavaDocTagProvider + +/** + * Provides plugin's custom JavaDoc tags to make test summaries structured. + */ +class UtCustomJavaDocTagProvider : CustomJavadocTagProvider { + override fun getSupportedTags(): List = + CustomJavaDocTagProvider().getPluginCustomTags().map { UtCustomTagInfo(it) } + + class UtCustomTagInfo(private val tag: CustomJavaDocTag) : JavadocTagInfo { + override fun getName(): String = tag.name + + fun getMessage(): String = tag.message + + override fun isInline() = false + + override fun checkTagValue(value: PsiDocTagValue?): String? = null + + override fun getReference(value: PsiDocTagValue?): PsiReference? = null + + override fun isValidInContext(element: PsiElement?): Boolean = element is PsiMethod + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtDocumentationProvider.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtDocumentationProvider.kt new file mode 100644 index 0000000000..3f2135a962 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtDocumentationProvider.kt @@ -0,0 +1,75 @@ +package org.utbot.intellij.plugin.javadoc + +import com.intellij.codeInsight.javadoc.JavaDocExternalFilter +import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator +import com.intellij.lang.java.JavaDocumentationProvider +import com.intellij.psi.PsiDocCommentBase +import com.intellij.psi.PsiJavaDocumentedElement +import com.intellij.psi.javadoc.PsiDocComment + +/** + * To render UtBot custom JavaDoc tags messages, we need to override basic behaviour of [JavaDocumentationProvider]. + */ +class UtDocumentationProvider : JavaDocumentationProvider() { + + override fun generateRenderedDoc(comment: PsiDocCommentBase): String? { + val target = comment.owner ?: comment + + if (target !is PsiJavaDocumentedElement) { + return "" + } + + val docComment = target.docComment ?: return "" + + val baseJavaDocInfoGenerator = JavaDocInfoGenerator(target.project, target) + + // get JavaDoc comment rendered by the platform. + val baseJavaDocInfo = baseJavaDocInfoGenerator.generateRenderedDocInfo() + + return getRenderedDoc(baseJavaDocInfo, docComment, comment) + } + + /** + * Processes JavaDoc generated by IJ platform to render plugin's custom tags correctly. + */ + private fun getRenderedDoc( + baseJavaDocInfo: String?, + docComment: PsiDocComment, + comment: PsiDocCommentBase + ): String? { + val utJavaDocInfoGenerator = UtJavaDocInfoGenerator() + // case 1 (2022.2): IDE successfully parsed comment with plugin's custom tags, + // and we only need to replace tags names with their messages. + return if (baseJavaDocInfo != null && baseJavaDocInfo.contains("@utbot")) { + val finalJavaDoc = replaceTagNamesWithMessages(baseJavaDocInfo) + JavaDocExternalFilter.filterInternalDocInfo(finalJavaDoc) + // case 2 (2022.1 and older): IDE failed to parse plugin's tags, and we need to add them on our own. + } else if (baseJavaDocInfo != null && comment.text.contains("@utbot")) { + val javaDocInfoWithUtSections = + utJavaDocInfoGenerator.addUtBotSpecificSectionsToJavaDoc(docComment) + val finalJavaDoc = replaceTagNamesWithMessages(javaDocInfoWithUtSections) + JavaDocExternalFilter.filterInternalDocInfo(finalJavaDoc) + } else { + // case 3: comment doesn't contain plugin's tags, so IDE can parse it on its own. + super.generateRenderedDoc(comment) + } + } + + /** + * Replaces names of plugin's custom JavaDoc tags with their messages in the comment generated by the IJ platform. + * Example: utbot.methodUnderTest -> Method under test. + * + * Use it to update comment built by the IJ platform after updating to 2022.2. + */ + private fun replaceTagNamesWithMessages(comment: String?) = + comment?.let { + val docTagProvider = UtCustomJavaDocTagProvider() + docTagProvider.supportedTags.fold(it) { result, tag -> + if (result.contains(tag.name)) { + result.replace(tag.name, "${tag.getMessage()}:") + } else { + result + } + } + } ?: "" +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtJavaDocInfoGenerator.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtJavaDocInfoGenerator.kt new file mode 100644 index 0000000000..97858797e1 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/javadoc/UtJavaDocInfoGenerator.kt @@ -0,0 +1,236 @@ +package org.utbot.intellij.plugin.javadoc + +import com.intellij.codeInsight.documentation.DocumentationManagerUtil +import com.intellij.codeInsight.javadoc.JavaDocUtil +import com.intellij.lang.documentation.DocumentationMarkup +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.IndexNotReadyException +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.JavaDocTokenType +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiJavaToken +import com.intellij.psi.PsiRecursiveElementWalkingVisitor +import com.intellij.psi.PsiWhiteSpace +import com.intellij.psi.javadoc.PsiDocComment +import com.intellij.psi.javadoc.PsiDocTag +import com.intellij.psi.javadoc.PsiDocToken +import com.intellij.psi.javadoc.PsiInlineDocTag +import mu.KotlinLogging + +private const val LINK_TAG = "link" +private const val LINKPLAIN_TAG = "linkplain" +private const val LITERAL_TAG = "literal" +private const val CODE_TAG = "code" +private const val SYSTEM_PROPERTY_TAG = "systemProperty" +private const val MESSAGE_SEPARATOR = ":" +private const val PARAGRAPH_TAG = "

    " +private const val CODE_TAG_START = "" +private const val CODE_TAG_END = "" +private const val BR_TAG = "
    " + +private val logger = KotlinLogging.logger {} + +/** + * Generates UtBot specific sections to include them to rendered JavaDoc comment. + * + * Methods responsible for value generation were taken from IJ platform class (they are private and couldn't be used outside). + * + * See [com.intellij.codeInsight.javadoc.JavaDocInfoGenerator]. + * + * It wouldn't be needed to generate rendered doc on our own after updating to the IJ platform 2022.2, + * so delete it after updating and use basic [com.intellij.codeInsight.javadoc.JavaDocInfoGenerator]. + */ +class UtJavaDocInfoGenerator { + fun addUtBotSpecificSectionsToJavaDoc(comment: PsiDocComment): String { + val builder = StringBuilder() + + val docTagProvider = UtCustomJavaDocTagProvider() + docTagProvider.supportedTags.forEach { + generateUtTagSection(builder, comment, it) + } + + return builder.toString() + } + + /** + * Searches for UtBot tags in the comment and generates a related section for it. + */ + private fun generateUtTagSection( + builder: StringBuilder, + comment: PsiDocComment, + utTag: UtCustomJavaDocTagProvider.UtCustomTagInfo + ) { + val tags = comment.findTagsByName(utTag.name) + + if (tags.isNotEmpty()) { + startHeaderSection(builder, utTag.getMessage()).append(PARAGRAPH_TAG) + + tags.mapIndexed { index, it -> + buildString { + generateValue(this, it.dataElements) + + if (index < tags.size - 1) { + this.append(", $BR_TAG") + } + } + }.forEach { builder.append(it) } + + builder.append(DocumentationMarkup.SECTION_END) + } + } + + private fun startHeaderSection(builder: StringBuilder, message: String): StringBuilder = + builder.append(DocumentationMarkup.SECTION_HEADER_START) + .append(message) + .append(MESSAGE_SEPARATOR) + .append(DocumentationMarkup.SECTION_SEPARATOR) + + /** + * Generates info depending on tag's value type. + */ + private fun generateValue(builder: StringBuilder, elements: Array) { + if (elements.isEmpty()) { + return + } + + var offset = elements[0].textOffset + elements[0].text.length + + for (element in elements) { + with(element) { + if (textOffset > offset) { + builder.append(' ') + } + + offset = textOffset + text.length + + if (element is PsiInlineDocTag) { + when (element.name) { + LITERAL_TAG -> generateLiteralValue(builder, element) + CODE_TAG, SYSTEM_PROPERTY_TAG -> generateCodeValue(element, builder) + LINK_TAG -> generateLinkValue(element, builder, false) + LINKPLAIN_TAG -> generateLinkValue(element, builder, true) + } + } else { + appendPlainText(builder, text) + } + } + } + } + + private fun appendPlainText(builder: StringBuilder, text: String) { + builder.append(StringUtil.replaceUnicodeEscapeSequences(text)) + } + + private fun collectElementText(builder: StringBuilder, element: PsiElement) { + element.accept(object : PsiRecursiveElementWalkingVisitor() { + override fun visitElement(element: PsiElement) { + super.visitElement(element) + if (element is PsiWhiteSpace || + element is PsiJavaToken || + element is PsiDocToken && element.tokenType !== JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS + ) { + builder.append(element.text) + } + } + }) + } + + private fun generateCodeValue(tag: PsiInlineDocTag, builder: StringBuilder) { + builder.append(CODE_TAG_START) + val pos = builder.length + generateLiteralValue(builder, tag) + builder.append(CODE_TAG_END) + if (builder[pos] == '\n') { + builder.insert( + pos, + ' ' + ) // line break immediately after opening tag is ignored by JEditorPane + } + } + + private fun generateLiteralValue(builder: StringBuilder, tag: PsiDocTag) { + val literalValue = buildString { + val children = tag.children + for (i in 2 until children.size - 1) { // process all children except tag opening/closing elements + val child = children[i] + if (child is PsiDocToken && child.tokenType === JavaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS) { + continue + } + + var elementText = child.text + if (child is PsiWhiteSpace) { + val pos = elementText.lastIndexOf('\n') + if (pos >= 0) { + elementText = elementText.substring(0, pos + 1) // skip whitespace before leading asterisk + } + } + appendPlainText(this, StringUtil.escapeXmlEntities(elementText)) + } + } + builder.append(StringUtil.trimLeading(literalValue)) + } + + private fun generateLinkValue(tag: PsiInlineDocTag, builder: StringBuilder, plainLink: Boolean) { + val tagElements = tag.dataElements + val linkText = createLinkText(tagElements) + if (linkText.isNotEmpty()) { + val index = JavaDocUtil.extractReference(linkText) + val referenceText = linkText.substring(0, index).trim() + val label = StringUtil.nullize(linkText.substring(index).trim()) + generateLink(builder, referenceText, label, tagElements[0], plainLink) + } + } + + private fun createLinkText(tagElements: Array): String { + var offset = if (tagElements.isNotEmpty()) { + tagElements[0].textOffset + tagElements[0].text.length + } else { + 0 + } + + return buildString { + for (i in tagElements.indices) { + val tagElement = tagElements[i] + if (tagElement.textOffset > offset) { + this.append(' ') + } + offset = tagElement.textOffset + tagElement.text.length + collectElementText(this, tagElement) + if (i < tagElements.lastIndex) { + this.append(' ') + } + } + }.trim() + } + + private fun generateLink( + builder: StringBuilder, + refText: String?, + label: String?, + context: PsiElement, + plainLink: Boolean + ) { + val linkLabel = label ?: context.manager.let { + JavaDocUtil.getLabelText(it.project, it, refText, context) + } + + var target: PsiElement? = null + try { + if (refText != null) { + target = JavaDocUtil.findReferenceTarget(context.manager, refText, context) + } + } catch (e: IndexNotReadyException) { + logger.info(e) { "Failed to find a reference while generating JavaDoc comment. Details: ${e.message}" } + } + + if (target == null && DumbService.isDumb(context.project)) { + builder.append(linkLabel) + } else if (target == null) { + builder.append("").append(linkLabel).append("") + } else { + JavaDocUtil.getReferenceText(target.project, target)?.let { + DocumentationManagerUtil.createHyperlink(builder, target, it, linkLabel, plainLink) + } + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/language/JavaLanguage.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/language/JavaLanguage.kt new file mode 100644 index 0000000000..b755acd5f9 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/language/JavaLanguage.kt @@ -0,0 +1,250 @@ +package org.utbot.intellij.plugin.language + +import com.intellij.openapi.actionSystem.ActionPlaces +import org.utbot.intellij.plugin.generator.UtTestsDialogProcessor +import org.utbot.intellij.plugin.ui.utils.PsiElementHandler +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.PlatformDataKeys +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.module.ModuleUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.* +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.refactoring.util.classMembers.MemberInfo +import org.jetbrains.kotlin.idea.core.getPackage +import org.jetbrains.kotlin.idea.core.util.toPsiDirectory +import org.jetbrains.kotlin.idea.core.util.toPsiFile +import org.jetbrains.kotlin.idea.util.module +import org.utbot.intellij.plugin.util.extractFirstLevelMembers +import org.utbot.intellij.plugin.util.isVisible +import java.util.* +import org.jetbrains.kotlin.j2k.getContainingClass +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.utils.addIfNotNull +import org.utbot.framework.plugin.api.util.LockFile +import org.utbot.intellij.plugin.models.packageName +import org.utbot.intellij.plugin.ui.InvalidClassNotifier +import org.utbot.intellij.plugin.language.agnostic.LanguageAssistant +import org.utbot.intellij.plugin.util.findSdkVersionOrNull + +@Suppress("unused") // is used in org.utbot.intellij.plugin.language.agnostic.LanguageAssistant via reflection +object JvmLanguageAssistant : LanguageAssistant() { + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + + val (srcClasses, focusedMethods, extractMembersFromSrcClasses) = getPsiTargets(e) ?: return + val validatedSrcClasses = validateSrcClasses(srcClasses) ?: return + + UtTestsDialogProcessor.createDialogAndGenerateTests(project, validatedSrcClasses, extractMembersFromSrcClasses, focusedMethods) + } + + override fun update(e: AnActionEvent) { + if (LockFile.isLocked()) { + e.presentation.isEnabled = false + return + } + if (e.place == ActionPlaces.POPUP) { + e.presentation.text = "Tests with UnitTestBot..." + } + e.presentation.isEnabled = getPsiTargets(e) != null + } + + private fun getPsiTargets(e: AnActionEvent): Triple, Set, Boolean>? { + val project = e.project ?: return null + val editor = e.getData(CommonDataKeys.EDITOR) + if (editor != null) { + //The action is being called from editor + val file = e.getData(CommonDataKeys.PSI_FILE) ?: return null + val element = findPsiElement(file, editor) ?: return null + + val psiElementHandler = PsiElementHandler.makePsiElementHandler(file) + + if (psiElementHandler.isCreateTestActionAvailable(element)) { + val srcClass = psiElementHandler.identifiedContainingClass(element) ?: return null + val srcSourceRoot = srcClass.getSourceRoot() ?: return null + val srcMembers = srcClass.extractFirstLevelMembers(false) + val focusedMethod = focusedMethodOrNull(element, srcMembers, psiElementHandler) + + val module = ModuleUtil.findModuleForFile(srcSourceRoot, project) ?: return null + val matchingRoot = ModuleRootManager.getInstance(module).contentEntries + .flatMap { entry -> entry.sourceFolders.toList() } + .firstOrNull { folder -> folder.file == srcSourceRoot } + if (srcMembers.isEmpty() || matchingRoot == null || matchingRoot.rootType.isForTests) { + return null + } + + return Triple(setOf(srcClass), if (focusedMethod != null) setOf(focusedMethod) else emptySet(), true) + } + } else { + // The action is being called from 'Project' tool window + val srcClasses = mutableSetOf() + val selectedMethods = mutableSetOf() + var extractMembersFromSrcClasses = false + val element = e.getData(CommonDataKeys.PSI_ELEMENT) + if (element is PsiFileSystemItem) { + e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY)?.let { + srcClasses += getAllClasses(project, it) + } + } else if (element is PsiElement){ + val file = element.containingFile ?: return null + val psiElementHandler = PsiElementHandler.makePsiElementHandler(file) + + if (psiElementHandler.isCreateTestActionAvailable(element)) { + psiElementHandler.identifiedContainingClass(element)?.let { + srcClasses += setOf(it) + extractMembersFromSrcClasses = true + val memberInfoList = runReadAction> { + it.extractFirstLevelMembers(false) + } + if (memberInfoList.isEmpty()) + return null + } + + if (element is PsiMethod) { + selectedMethods.add(MemberInfo(element)) + } + } + } else { + val someSelection = e.getData(PlatformDataKeys.PSI_ELEMENT_ARRAY)?: return null + someSelection.forEach { + when(it) { + is PsiFileSystemItem -> srcClasses += getAllClasses(project, arrayOf(it.virtualFile)) + is PsiClass -> srcClasses.add(it) + is KtClass -> srcClasses += getClassesFromFile(it.containingKtFile) + is PsiElement -> { + srcClasses.addIfNotNull(it.getContainingClass()) + if (it is PsiMethod) { + selectedMethods.add(MemberInfo(it)) + extractMembersFromSrcClasses = true + } + } + } + } + } + + if (srcClasses.size > 1) { + extractMembersFromSrcClasses = false + } + var commonSourceRoot = null as VirtualFile? + for (srcClass in srcClasses) { + if (commonSourceRoot == null) { + commonSourceRoot = srcClass.getSourceRoot()?: return null + } else if (commonSourceRoot != srcClass.getSourceRoot()) return null + } + if (commonSourceRoot == null) return null + val module = ModuleUtil.findModuleForFile(commonSourceRoot, project)?: return null + + if (!Arrays.stream(ModuleRootManager.getInstance(module).contentEntries) + .flatMap { entry -> Arrays.stream(entry.sourceFolders) } + .filter { folder -> !folder.rootType.isForTests && folder.file == commonSourceRoot} + .findAny().isPresent ) return null + + return Triple(srcClasses.toSet(), selectedMethods.toSet(), extractMembersFromSrcClasses) + } + return null + } + + private fun PsiElementHandler.identifiedContainingClass(element: PsiElement): PsiClass? { + val clazz = containingClass(element) + return if (clazz is PsiAnonymousClass) PsiTreeUtil.getParentOfType(clazz, PsiClass::class.java) else clazz + } + + /** + * Validates that a set of source classes matches some requirements from [isInvalid]. + * If no one of them matches, shows a warning about the first mismatch reason. + */ + private fun validateSrcClasses(srcClasses: Set): Set? { + val filteredClasses = srcClasses + .filterNot { it.isInvalid(withWarnings = false) } + .toSet() + + if (filteredClasses.isEmpty()) { + srcClasses.first().isInvalid(withWarnings = true) + return null + } + + return filteredClasses + } + + private fun PsiClass.isInvalid(withWarnings: Boolean): Boolean { + if (this.module?.let { findSdkVersionOrNull(it) } == null) { + if (withWarnings) InvalidClassNotifier.notify("class out of module or with undefined SDK") + return true + } + + val isInvisible = !this.isVisible + if (isInvisible) { + if (withWarnings) InvalidClassNotifier.notify("private or protected class ${this.name}") + return true + } + + val packageIsIncorrect = this.packageName.split(".").firstOrNull() == "java" + if (packageIsIncorrect) { + if (withWarnings) InvalidClassNotifier.notify("class ${this.name} located in java.* package") + return true + } + + return false + } + + private fun PsiElement?.getSourceRoot() : VirtualFile? { + val project = this?.project?: return null + val virtualFile = this.containingFile?.originalFile?.virtualFile?: return null + return ProjectFileIndex.getInstance(project).getSourceRootForFile(virtualFile) + } + + private fun findPsiElement(file: PsiFile, editor: Editor): PsiElement? { + val offset = editor.caretModel.offset + var element = file.findElementAt(offset) + if (element == null && offset == file.textLength) { + element = file.findElementAt(offset - 1) + } + + return element + } + + private fun focusedMethodOrNull(element: PsiElement, methods: List, psiElementHandler: PsiElementHandler): MemberInfo? { + // getParentOfType might return element which does not correspond to the standard Psi hierarchy. + // Thus, make transition to the Psi if it is required. + val currentMethod = PsiTreeUtil.getParentOfType(element, psiElementHandler.methodClass) + ?.let { psiElementHandler.toPsi(it, PsiMethod::class.java) } + // For anonymous class, we cannot select the nearest parent method directly. + // So, we have to suggest the nearest "outer" method from the named class. + val topmostCurrentMethod = PsiTreeUtil.getTopmostParentOfType(element, psiElementHandler.methodClass) + ?.let { psiElementHandler.toPsi(it, PsiMethod::class.java) } + + return methods.singleOrNull { it.member == currentMethod } + ?: methods.singleOrNull { it.member == topmostCurrentMethod } + } + + private fun getAllClasses(directory: PsiDirectory): Set { + val allClasses = directory.files.flatMap { getClassesFromFile(it) }.toMutableSet() + for (subDir in directory.subdirectories) allClasses += getAllClasses(subDir) + return allClasses + } + + private fun getAllClasses(project: Project, virtualFiles: Array): Set { + val psiFiles = virtualFiles.mapNotNull { it.toPsiFile(project) } + val psiDirectories = virtualFiles.mapNotNull { it.toPsiDirectory(project) } + val dirsArePackages = psiDirectories.all { it.getPackage()?.qualifiedName?.isNotEmpty() == true } + + if (!dirsArePackages) { + return emptySet() + } + val allClasses = psiFiles.flatMap { getClassesFromFile(it) }.toMutableSet() + for (psiDir in psiDirectories) allClasses += getAllClasses(psiDir) + + return allClasses + } + + private fun getClassesFromFile(psiFile: PsiFile): List { + val psiElementHandler = PsiElementHandler.makePsiElementHandler(psiFile) + return PsiTreeUtil.getChildrenOfTypeAsList(psiFile, psiElementHandler.classClass) + .map { psiElementHandler.toPsi(it, PsiClass::class.java) } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/ExternalLibraryDescriptors.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/ExternalLibraryDescriptors.kt new file mode 100644 index 0000000000..23448876c7 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/ExternalLibraryDescriptors.kt @@ -0,0 +1,90 @@ +package org.utbot.intellij.plugin.models + +import com.intellij.openapi.roots.ExternalLibraryDescriptor +import org.jetbrains.idea.maven.utils.library.RepositoryLibraryDescription +import org.utbot.intellij.plugin.ui.utils.Version + +val ExternalLibraryDescriptor.mavenCoordinates: String + get() = "$libraryGroupId:$libraryArtifactId:${preferredVersion ?: RepositoryLibraryDescription.ReleaseVersionId}" + +val ExternalLibraryDescriptor.id: String + get() = "$libraryGroupId:$libraryArtifactId" + +//TODO: think about using JUnitExternalLibraryDescriptor from intellij-community sources (difficult to install) +fun jUnit4LibraryDescriptor(versionInProject: Version?): ExternalLibraryDescriptor { + val preferredVersion = if (versionInProject?.hasNumericOrEmptyPatch() == true) versionInProject?.plainText else "4.13.2" + return ExternalLibraryDescriptor( + "junit", "junit", + "4.12", null, preferredVersion + ) +} + +fun jUnit5LibraryDescriptor(versionInProject: Version?): ExternalLibraryDescriptor { + val preferredVersion = if (versionInProject?.hasNumericOrEmptyPatch() == true) versionInProject?.plainText else "5.8.1" + return ExternalLibraryDescriptor( + "org.junit.jupiter", "junit-jupiter", + "5.8.1", null, preferredVersion + ) +} + +fun jUnit5ParametrizedTestsLibraryDescriptor(versionInProject: Version?): ExternalLibraryDescriptor { + val preferredVersion = if (versionInProject?.hasNumericOrEmptyPatch() == true) versionInProject?.plainText else "5.8.1" + return ExternalLibraryDescriptor( + "org.junit.jupiter", "junit-jupiter-params", + "5.8.1", null, preferredVersion + ) +} + +fun mockitoCoreLibraryDescriptor(versionInProject: Version?): ExternalLibraryDescriptor { + val preferredVersion = if (versionInProject?.hasNumericOrEmptyPatch() == true) versionInProject?.plainText else "4.11.0" + return ExternalLibraryDescriptor( + "org.mockito", "mockito-core", + "3.5.0", null, preferredVersion + ) +} + +fun springBootTestLibraryDescriptor(versionInProject: Version?): ExternalLibraryDescriptor { + val preferredVersion = if (versionInProject?.hasNumericOrEmptyPatch() == true) versionInProject?.plainText else "3.0.6" + return ExternalLibraryDescriptor( + "org.springframework.boot", "spring-boot-test", + "2.4.0", null, preferredVersion + ) +} + +private const val MIN_SUPPORTED_SPRING_VERSION = "2.5" + +fun springTestLibraryDescriptor(versionInProject: Version?): ExternalLibraryDescriptor { + val preferredVersion = if (versionInProject?.hasNumericOrEmptyPatch() == true) versionInProject.plainText else "6.0.8" + return ExternalLibraryDescriptor( + "org.springframework", "spring-test", + MIN_SUPPORTED_SPRING_VERSION, null, preferredVersion + ) +} + +fun springSecurityLibraryDescriptor(versionInProject: Version?): ExternalLibraryDescriptor { + val preferredVersion = if (versionInProject?.hasNumericOrEmptyPatch() == true) versionInProject.plainText else "6.0.8" + return ExternalLibraryDescriptor( + "org.springframework.security", "spring-security-test", + MIN_SUPPORTED_SPRING_VERSION, null, preferredVersion + ) +} + + +/** + * TestNg requires JDK 11 since version 7.6.0 + * For projects with JDK 8 version 7.5 should be installed. + * See https://groups.google.com/g/testng-users/c/BAFB1vk-kok?pli=1 for more details. + */ +fun testNgNewLibraryDescriptor(versionInProject: Version?): ExternalLibraryDescriptor { + val preferredVersion = if (versionInProject?.hasNumericOrEmptyPatch() == true) versionInProject?.plainText else "7.6.0" + return ExternalLibraryDescriptor( + "org.testng", "testng", + "7.6.0", null, preferredVersion + ) +} + +fun testNgOldLibraryDescriptor() = + ExternalLibraryDescriptor( + "org.testng", "testng", + "7.5", "7.5", "7.5" + ) \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt new file mode 100644 index 0000000000..2ba4bff9bd --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt @@ -0,0 +1,246 @@ +package org.utbot.intellij.plugin.models + +import com.intellij.openapi.components.service +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.HangingTestsTimeout +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.TestFramework +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.rootManager +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiJavaFile +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.searches.AnnotatedElementsSearch +import com.intellij.refactoring.util.classMembers.MemberInfo +import org.jetbrains.concurrency.Promise +import org.jetbrains.kotlin.idea.util.projectStructure.allModules +import org.jetbrains.kotlin.idea.util.sourceRoot +import org.jetbrains.kotlin.psi.KtFile +import org.utbot.common.PathUtil.fileExtension +import org.utbot.framework.SummariesGenerationType +import org.utbot.framework.UtSettings +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.MockFramework +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.JavaDocCommentStyle +import org.utbot.framework.plugin.api.SpringTestType +import org.utbot.framework.plugin.api.SpringSettings +import org.utbot.framework.util.ConflictTriggers +import org.utbot.intellij.plugin.settings.Settings +import org.utbot.intellij.plugin.ui.utils.* +import org.utbot.intellij.plugin.util.binaryName +import java.nio.file.Files +import javax.xml.parsers.DocumentBuilder +import javax.xml.parsers.DocumentBuilderFactory +import kotlin.streams.asSequence + +const val HISTORY_LIMIT = 10 + +const val SPRINGBOOT_APPLICATION_FQN = "org.springframework.boot.autoconfigure.SpringBootApplication" +const val SPRINGBOOT_CONFIGURATION_FQN = "org.springframework.boot.SpringBootConfiguration" +const val SPRING_CONFIGURATION_ANNOTATION_FQN = "org.springframework.context.annotation.Configuration" +const val SPRING_TESTCONFIGURATION_ANNOTATION_FQN = "org.springframework.boot.test.context.TestConfiguration" + +const val SPRING_BEANS_SCHEMA_URL = "http://www.springframework.org/schema/beans" +const val SPRING_LOAD_DTD_GRAMMAR_PROPERTY = "http://apache.org/xml/features/nonvalidating/load-dtd-grammar" +const val SPRING_LOAD_EXTERNAL_DTD_PROPERTY = "http://apache.org/xml/features/nonvalidating/load-external-dtd" + +class GenerateTestsModel( + project: Project, + val srcModule: Module, + val potentialTestModules: List, + var srcClasses: Set, + val extractMembersFromSrcClasses: Boolean, + var selectedMembers: Set, + var timeout: Long, + var generateWarningsForStaticMocking: Boolean = false, + var fuzzingValue: Double = 0.05 +): BaseTestsModel( + project, +) { + // GenerateTestsModel is supposed to be created with non-empty list of potentialTestModules. + // Otherwise, the error window is supposed to be shown earlier. + var testModule: Module = potentialTestModules.firstOrNull() ?: error("Empty list of test modules in model") + + override var sourceRootHistory = project.service().sourceRootHistory + override var codegenLanguage = project.service().codegenLanguage + + lateinit var testFramework: TestFramework + lateinit var mockStrategy: MockStrategyApi + lateinit var mockFramework: MockFramework + lateinit var staticsMocking: StaticsMocking + lateinit var parametrizedTestSource: ParametrizedTestSource + lateinit var runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour + lateinit var hangingTestsTimeout: HangingTestsTimeout + var useTaintAnalysis: Boolean = false + var runInspectionAfterTestGeneration: Boolean = true + lateinit var forceStaticMocking: ForceStaticMocking + lateinit var chosenClassesToMockAlways: Set + lateinit var commentStyle: JavaDocCommentStyle + + lateinit var springSettings: SpringSettings + lateinit var springTestType: SpringTestType + lateinit var springConfig: String + lateinit var springProfileNames: String + + val conflictTriggers: ConflictTriggers = ConflictTriggers() + val preClasspathCollectionPromises: MutableList> = mutableListOf() + + var runGeneratedTestsWithCoverage : Boolean = false + var summariesGenerationType : SummariesGenerationType = UtSettings.summaryGenerationType + + fun setSourceRootAndFindTestModule(newTestSourceRoot: VirtualFile?) { + requireNotNull(newTestSourceRoot) + testSourceRoot = newTestSourceRoot + var target = newTestSourceRoot + while (target != null && target is FakeVirtualFile) { + target = target.parent + } + if (target == null) { + error("Could not find module for $newTestSourceRoot") + } + + testModule = ModuleUtil.findModuleForFile(target, project) + ?: error("Could not find module for $newTestSourceRoot") + } + + val isMultiPackage: Boolean by lazy { + srcClasses.map { it.packageName }.distinct().size != 1 + } + + fun getAllTestSourceRoots() : MutableList { + with(if (project.isBuildWithGradle) project.allModules() else potentialTestModules) { + return this.flatMap { it.suitableTestSourceRoots().toList() }.toMutableList().distinct().toMutableList() + } + } + + fun getSortedTestRoots(): MutableList = getSortedTestRoots( + getAllTestSourceRoots(), + sourceRootHistory, + srcModule.rootManager.sourceRoots.map { file: VirtualFile -> file.toNioPath().toString() }, + codegenLanguage + ) + + /** + * Finds @SpringBootApplication classes in Spring application. + * + * @see [getSortedAnnotatedClasses] + */ + fun getSortedSpringBootApplicationClasses(): Set = + getSortedAnnotatedClasses(SPRINGBOOT_CONFIGURATION_FQN) + + getSortedAnnotatedClasses(SPRINGBOOT_APPLICATION_FQN) + + /** + * Finds @TestConfiguration and @Configuration classes in Spring application. + * + * @see [getSortedAnnotatedClasses] + */ + fun getSortedSpringConfigurationClasses(): Set = + getSortedAnnotatedClasses(SPRING_TESTCONFIGURATION_ANNOTATION_FQN) + + getSortedAnnotatedClasses(SPRING_CONFIGURATION_ANNOTATION_FQN) + + /** + * Finds classes annotated with given annotation in [srcModule] and [potentialTestModules]. + * + * Sorting order: + * - classes from test source roots (in the order provided by [getSortedTestRoots]) + * - classes from production source roots + */ + private fun getSortedAnnotatedClasses(annotationFqn: String): Set { + val searchScope = + potentialTestModules + .fold(GlobalSearchScope.moduleScope(srcModule)) { accScope, module -> + accScope.union(GlobalSearchScope.moduleScope(module)) + } + + val annotationClass = JavaPsiFacade + .getInstance(project) + .findClass(annotationFqn, GlobalSearchScope.allScope(project)) + ?: return emptySet() + + val testRootToIndex = + getSortedTestRoots() + .withIndex() + .associate { (i, root) -> root.dir to i } + + return AnnotatedElementsSearch + .searchPsiClasses(annotationClass, searchScope) + .findAll() + .sortedBy { testRootToIndex[it.containingFile.sourceRoot] ?: Int.MAX_VALUE } + .mapNotNullTo(mutableSetOf()) { it.binaryName } + } + + fun getSpringXMLConfigurationFiles(): Set { + val resourcesPaths = + buildList { + addAll(potentialTestModules) + add(srcModule) + }.distinct().flatMapTo(mutableSetOf()) { it.getResourcesPaths() } + val xmlFilePaths = resourcesPaths.flatMapTo(mutableListOf()) { path -> + Files.walk(path) + .asSequence() + .filter { it.fileExtension == ".xml" } + } + + val builder = customizeXmlBuilder() + return xmlFilePaths.mapNotNullTo(mutableSetOf()) { path -> + try { + val doc = builder.parse(path.toFile()) + + val hasBeanTagName = doc.documentElement.tagName == "beans" + val hasAttribute = doc.documentElement.getAttribute("xmlns") == SPRING_BEANS_SCHEMA_URL + when { + hasBeanTagName && hasAttribute -> path.toString() + else -> null + } + } catch (e: Exception) { + // `DocumentBuilder.parse` is an unpredictable operation, may have some side effects, we suppress them. + null + } + } + } + + /** + * Creates "safe" xml builder instance. + * + * Using standard `DocumentBuilderFactory.newInstance()` may lead to some problems like + * https://stackoverflow.com/questions/343383/unable-to-parse-xml-file-using-documentbuilder. + * + * We try to solve it in accordance with top-rated recommendation here + * https://stackoverflow.com/questions/155101/make-documentbuilder-parse-ignore-dtd-references. + */ + private fun customizeXmlBuilder(): DocumentBuilder { + val builderFactory = DocumentBuilderFactory.newInstance() + builderFactory.isNamespaceAware = true + + // See documentation https://xerces.apache.org/xerces2-j/features.html + builderFactory.setFeature(SPRING_LOAD_DTD_GRAMMAR_PROPERTY, false) + builderFactory.setFeature(SPRING_LOAD_EXTERNAL_DTD_PROPERTY, false) + + return builderFactory.newDocumentBuilder() + } + + fun updateSourceRootHistory(path: String) { + sourceRootHistory.apply { + remove(path)//Remove existing entry if any + add(path)//Add the most recent entry to the end to be brought first at sorting, see org.utbot.intellij.plugin.ui.utils.RootUtilsKt.getSortedTestRoots + while (size > HISTORY_LIMIT) removeFirst() + } + } +} + +val PsiClass.packageName: String + get() { + return when (val currentFile = containingFile) { + is PsiJavaFile -> currentFile.packageName + is KtFile -> currentFile.packageFqName.asString() + else -> error("Can't find package name for $this: it should be located either in Java or Kt file") + } + } \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt new file mode 100644 index 0000000000..7d180f6363 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt @@ -0,0 +1,426 @@ +package org.utbot.intellij.plugin.process + +import com.intellij.ide.plugins.cl.PluginClassLoader +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Computable +import com.intellij.psi.PsiMethod +import com.intellij.psi.impl.file.impl.JavaFileManager +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.refactoring.util.classMembers.MemberInfo +import com.jetbrains.rd.util.ConcurrentHashMap +import com.jetbrains.rd.util.lifetime.LifetimeDefinition +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +import org.utbot.common.* +import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.tree.ututils.UtilClassKind +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.services.JdkInfo +import org.utbot.framework.plugin.services.WorkingDirService +import org.utbot.framework.process.AbstractRDProcessCompanion +import org.utbot.framework.process.EngineProcessTask +import org.utbot.framework.process.generated.* +import org.utbot.framework.process.generated.MethodDescription +import org.utbot.framework.process.kryo.KryoHelper +import org.utbot.framework.util.Conflict +import org.utbot.framework.util.ConflictTriggers +import org.utbot.intellij.plugin.UtbotBundle +import org.utbot.intellij.plugin.models.GenerateTestsModel +import org.utbot.intellij.plugin.ui.TestReportUrlOpeningListener +import org.utbot.intellij.plugin.util.assertReadAccessNotAllowed +import org.utbot.intellij.plugin.util.methodDescription +import org.utbot.rd.* +import org.utbot.rd.exceptions.InstantProcessDeathException +import org.utbot.rd.generated.SettingForResult +import org.utbot.rd.generated.SettingsModel +import org.utbot.rd.generated.settingsModel +import org.utbot.rd.generated.synchronizationModel +import org.utbot.rd.loggers.overrideDefaultRdLoggerFactoryWithKLogger +import org.utbot.sarif.SourceFindingStrategy +import java.io.File +import java.nio.charset.Charset +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import kotlin.io.path.pathString +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.memberProperties + +private val engineProcessLogConfigurationsDirectory = utBotTempDirectory.toFile().resolve("rdEngineProcessLogConfigurations").also { it.mkdirs() } +private val logger = KotlinLogging.logger {}.also { overrideDefaultRdLoggerFactoryWithKLogger(it) } +private val engineProcessLogDirectory = utBotTempDirectory.toFile().resolve("rdEngineProcessLogs").also { it.mkdirs() } + +private const val configurationFileDeleteKey = "delete_this_comment_key" +private const val deleteOpenComment = "" + +private fun createEngineProcessLog4j2Config(): File { + val customFile = File(UtSettings.engineProcessLogConfigFile) + + val log4j2ConfigFile = + if (customFile.exists()) customFile + else Files.createTempFile(engineProcessLogConfigurationsDirectory.toPath(), null, ".xml").toFile() + + EngineProcess::class.java.classLoader.getResourceAsStream("log4j2.xml")?.use { logConfig -> + val resultConfig = logConfig.readBytes().toString(Charset.defaultCharset()) + .replace(Regex("$deleteOpenComment|$deleteCloseComment"), "") + .replace("ref=\"IdeaAppender\"", "ref=\"EngineProcessAppender\"") + .replace("\${env:UTBOT_LOG_DIR}", engineProcessLogDirectory.canonicalPath.trimEnd(File.separatorChar) + File.separatorChar) + Files.copy( + resultConfig.byteInputStream(), + log4j2ConfigFile.toPath(), + StandardCopyOption.REPLACE_EXISTING + ) + } + return log4j2ConfigFile +} + +private val log4j2ConfigFile: File = createEngineProcessLog4j2Config() + +private val log4j2ConfigSwitch = "-Dlog4j2.configurationFile=${log4j2ConfigFile.canonicalPath}" + +private val pluginClasspath: String + get() = (EngineProcess::class.java.classLoader as PluginClassLoader).classPath.baseUrls.joinToString( + separator = File.pathSeparator + ) + +private const val startFileName = "org.utbot.framework.process.EngineProcessMainKt" + +data class RdTestGenerationResult(val notEmptyCases: Int, val testSetsId: Long) + +class EngineProcessInstantDeathException : + InstantProcessDeathException(UtSettings.engineProcessDebugPort, UtSettings.runEngineProcessWithDebug) + +class EngineProcess private constructor(val project: Project, private val classNameToPath: Map, rdProcess: ProcessWithRdServer) : + ProcessWithRdServer by rdProcess { + companion object : AbstractRDProcessCompanion( + debugPort = UtSettings.engineProcessDebugPort, + runWithDebug = UtSettings.runEngineProcessWithDebug, + suspendExecutionInDebugMode = UtSettings.suspendEngineProcessExecutionInDebugMode, + processSpecificCommandLineArgs = { listOf("-ea", log4j2ConfigSwitch, "-cp", pluginClasspath, startFileName) } + ) { + fun createBlocking(project: Project, classNameToPath: Map): EngineProcess = runBlocking { EngineProcess(project, classNameToPath) } + + suspend operator fun invoke(project: Project, classNameToPath: Map): EngineProcess = + LifetimeDefinition().terminateOnException { lifetime -> + val rdProcess = startUtProcessWithRdServer(lifetime) { port -> + val cmd = obtainProcessCommandLine(port) + val directory = WorkingDirService.provide().toFile() + val builder = ProcessBuilder(cmd).directory(directory) + val process = builder.start() + + logger.info { "Engine process started with PID = ${process.getPid}" } + logger.info { "Engine process log directory - ${engineProcessLogDirectory.canonicalPath}" } + logger.info { "Engine process log file - ${engineProcessLogDirectory.resolve("utbot-engine-current.log")}" } + logger.info { "Log4j2 configuration file path - ${log4j2ConfigFile.canonicalPath}" } + + if (!process.isAlive) { + throw EngineProcessInstantDeathException() + } + + process + } + rdProcess.awaitProcessReady() + + return EngineProcess(project, classNameToPath, rdProcess) + } + } + + private val engineModel: EngineProcessModel = onSchedulerBlocking { protocol.engineProcessModel } + private val instrumenterAdapterModel: RdInstrumenterAdapter = onSchedulerBlocking { protocol.rdInstrumenterAdapter } + private val sourceFindingModel: RdSourceFindingStrategy = onSchedulerBlocking { protocol.rdSourceFindingStrategy } + private val settingsModel: SettingsModel = onSchedulerBlocking { protocol.settingsModel } + + private val kryoHelper = KryoHelper(lifetime) + private val sourceFindingStrategies = ConcurrentHashMap() + + fun setupUtContext(classpathForUrlsClassloader: List) { + assertReadAccessNotAllowed() + engineModel.setupUtContext.startBlocking(SetupContextParams(classpathForUrlsClassloader)) + } + + private fun computeSourceFileByClass(params: ComputeSourceFileByClassArguments): String? = + DumbService.getInstance(project).runReadActionInSmartMode { + val scope = GlobalSearchScope.allScope(project) + // JavaFileManager requires canonical name as it is said in import + val psiClass = JavaFileManager.getInstance(project).findClass(params.canonicalClassName, scope) + val sourceFile = psiClass?.navigationElement?.containingFile?.virtualFile?.canonicalPath + + logger.debug { "computeSourceFileByClass result: $sourceFile" } + sourceFile ?: classNameToPath[params.canonicalClassName] + } + + fun createTestGenerator( + buildDir: List, + classPath: String?, + dependencyPaths: String, + jdkInfo: JdkInfo, + applicationContext: ApplicationContext, + isCancelled: (Unit) -> Boolean + ) { + assertReadAccessNotAllowed() + + engineModel.isCancelled.set(handler = isCancelled) + instrumenterAdapterModel.computeSourceFileByClass.set(handler = this::computeSourceFileByClass) + + val params = TestGeneratorParams( + buildDir.toTypedArray(), + classPath, + dependencyPaths, + JdkInfo(jdkInfo.path.pathString, jdkInfo.version), + kryoHelper.writeObject(applicationContext) + ) + engineModel.createTestGenerator.startBlocking(params) + } + + fun obtainClassId(binaryName: String): ClassId { + assertReadAccessNotAllowed() + return kryoHelper.readObject(engineModel.obtainClassId.startBlocking(binaryName)) + } + + fun findMethodsInClassMatchingSelected(clazzId: ClassId, srcMethods: List): List { + assertReadAccessNotAllowed() + + val srcDescriptions = runReadAction { srcMethods.map { it.methodDescription() } } + val rdDescriptions = srcDescriptions.map { MethodDescription(it.name, it.containingClass, it.parameterTypes) } + val binaryClassId = kryoHelper.writeObject(clazzId) + val arguments = FindMethodsInClassMatchingSelectedArguments(binaryClassId, rdDescriptions) + val result = engineModel.findMethodsInClassMatchingSelected.startBlocking(arguments) + + return kryoHelper.readObject(result.executableIds) + } + + fun findMethodParamNames(classId: ClassId, methods: List): Map> { + assertReadAccessNotAllowed() + + val bySignature = executeWithTimeoutSuspended { + DumbService.getInstance(project).runReadActionInSmartMode(Computable { + methods.map { it.methodDescription() to it.paramNames() } + }) + } + val arguments = FindMethodParamNamesArguments( + kryoHelper.writeObject(classId), + kryoHelper.writeObject(bySignature) + ) + val result = engineModel.findMethodParamNames.startBlocking(arguments).paramNames + + return kryoHelper.readObject(result) + } + + private fun MemberInfo.paramNames(): List = (this.member as PsiMethod).parameterList.parameters.map { + if (it.name.startsWith("\$this")) + // If member is Kotlin extension function, name of first argument isn't good for further usage, + // so we better choose name based on type of receiver. + // + // There seems no API to check whether parameter is an extension receiver by PSI + it.type.presentableText + else + it.name + } + + fun generate( + conflictTriggers: ConflictTriggers, + methods: List, + mockStrategyApi: MockStrategyApi, + chosenClassesToMockAlways: Set, + timeout: Long, + generationTimeout: Long, + isSymbolicEngineEnabled: Boolean, + isFuzzingEnabled: Boolean, + fuzzingValue: Double, + searchDirectory: String, + taintConfigPath: String? + ): RdTestGenerationResult { + assertReadAccessNotAllowed() + val params = GenerateParams( + kryoHelper.writeObject(methods), + mockStrategyApi.name, + kryoHelper.writeObject(chosenClassesToMockAlways), + timeout, + generationTimeout, + isSymbolicEngineEnabled, + isFuzzingEnabled, + fuzzingValue, + searchDirectory, + taintConfigPath + ) + val result = engineModel.generate.startBlocking(params) + + return RdTestGenerationResult(result.notEmptyCases, result.testSetsId) + } + + fun render( + model: GenerateTestsModel, + testSetsId: Long, + classUnderTest: ClassId, + paramNames: MutableMap>, + generateUtilClassFile: Boolean, + enableTestsTimeout: Boolean, + testClassPackageName: String, + ): Pair { + assertReadAccessNotAllowed() + val params = makeParams( + model, + testSetsId, + classUnderTest, + paramNames, + generateUtilClassFile, + enableTestsTimeout, + testClassPackageName, + ) + val result = engineModel.render.startBlocking(params) + val realUtilClassKind = result.utilClassKind?.let { + if (UtilClassKind.RegularUtUtils(model.codegenLanguage).javaClass.simpleName == it) + UtilClassKind.RegularUtUtils(model.codegenLanguage) + else + UtilClassKind.UtUtilsWithMockito(model.codegenLanguage) + } + + return result.generatedCode to realUtilClassKind + } + + fun findTestClassName(classUnderTest: ClassId): String { + val params = TestClassNameParams(kryoHelper.writeObject(classUnderTest)) + val result = engineModel.findTestClassName.startBlocking(params) + return result.testClassName + } + + private fun makeParams( + model: GenerateTestsModel, + testSetsId: Long, + classUnderTest: ClassId, + paramNames: MutableMap>, + generateUtilClassFile: Boolean, + enableTestsTimeout: Boolean, + testClassPackageName: String, + ): RenderParams = + RenderParams( + testSetsId, + kryoHelper.writeObject(classUnderTest), + model.projectType.toString(), + kryoHelper.writeObject(paramNames), + generateUtilClassFile, + model.testFramework.id.lowercase(), + model.mockFramework.name, + model.codegenLanguage.name, + model.parametrizedTestSource.name, + model.staticsMocking.id, + kryoHelper.writeObject(model.forceStaticMocking), + model.generateWarningsForStaticMocking, + model.runtimeExceptionTestsBehaviour.name, + model.hangingTestsTimeout.timeoutMs, + enableTestsTimeout, + testClassPackageName, + ) + + private fun getSourceFile(params: SourceStrategyMethodArgs): String? = + DumbService.getInstance(project).runReadActionInSmartMode { + sourceFindingStrategies[params.testSetId]!!.getSourceFile( + params.classFqn, + params.extension + )?.canonicalPath + } + + private fun getSourceRelativePath(params: SourceStrategyMethodArgs): String = + DumbService.getInstance(project).runReadActionInSmartMode { + sourceFindingStrategies[params.testSetId]!!.getSourceRelativePath( + params.classFqn, + params.extension + ) + } + + private fun testsRelativePath(testSetId: Long): String = + DumbService.getInstance(project).runReadActionInSmartMode { + sourceFindingStrategies[testSetId]!!.testsRelativePath + } + + private fun initSourceFindingStrategies() { + sourceFindingModel.getSourceFile.set(handler = this::getSourceFile) + sourceFindingModel.getSourceRelativePath.set(handler = this::getSourceRelativePath) + sourceFindingModel.testsRelativePath.set(handler = this::testsRelativePath) + } + + fun writeSarif( + reportFilePath: Path, + testSetsId: Long, + generatedTestsCode: String, + sourceFindingStrategy: SourceFindingStrategy + ): String { + assertReadAccessNotAllowed() + + val params = WriteSarifReportArguments(testSetsId, reportFilePath.pathString, generatedTestsCode) + + sourceFindingStrategies[testSetsId] = sourceFindingStrategy + return engineModel.writeSarifReport.startBlocking(params) + } + + fun generateTestsReport(model: GenerateTestsModel, eventLogMessage: String?): Triple { + assertReadAccessNotAllowed() + + val forceMockWarning = UtbotBundle.takeIf( + "test.report.force.mock.warning", + TestReportUrlOpeningListener.prefix, + TestReportUrlOpeningListener.mockitoSuffix + ) { model.conflictTriggers[Conflict.ForceMockHappened] == true } + val forceStaticMockWarnings = UtbotBundle.takeIf( + "test.report.force.static.mock.warning", + TestReportUrlOpeningListener.prefix, + TestReportUrlOpeningListener.mockitoInlineSuffix + ) { model.conflictTriggers[Conflict.ForceStaticMockHappened] == true } + val testFrameworkWarnings = + UtbotBundle.takeIf("test.report.test.framework.warning") { model.conflictTriggers[Conflict.TestFrameworkConflict] == true } + val params = GenerateTestReportArgs( + eventLogMessage, + model.testPackageName, + model.isMultiPackage, + forceMockWarning, + forceStaticMockWarnings, + testFrameworkWarnings, + model.conflictTriggers.anyTriggered + ) + val result = engineModel.generateTestReport.startBlocking(params) + + return Triple(result.notifyMessage, result.statistics, result.hasWarnings) + } + + fun perform(engineProcessTask: EngineProcessTask): R { + assertReadAccessNotAllowed() + return kryoHelper.readObject( + engineModel.perform.startBlocking(PerformParams(kryoHelper.writeObject(engineProcessTask))) + ) + } + + init { + lifetime.onTermination { + protocol.synchronizationModel.stopProcess.fire(Unit) + } + settingsModel.settingFor.set { params -> + SettingForResult(AbstractSettings.allSettings[params.key]?.let { settings: AbstractSettings -> + val members: Collection> = + settings.javaClass.kotlin.memberProperties + val names: List> = + members.filter { it.name == params.propertyName } + val sing: KProperty1 = names.single() + val result = sing.get(settings) + logger.trace { "request for settings ${params.key}:${params.propertyName} - $result" } + result.toString() + }) + } + initSourceFindingStrategies() + } + + fun executeWithTimeoutSuspended(block: () -> T): T { + try { + assertReadAccessNotAllowed() + protocol.synchronizationModel.suspendTimeoutTimer.startBlocking(true) + return block() + } + finally { + assertReadAccessNotAllowed() + protocol.synchronizationModel.suspendTimeoutTimer.startBlocking(false) + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt index 19e757a190..7a24088189 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SarifReportIdea.kt @@ -1,36 +1,58 @@ package org.utbot.intellij.plugin.sarif +import com.intellij.openapi.application.WriteAction +import com.intellij.psi.PsiClass +import com.intellij.openapi.progress.ProgressIndicator import org.utbot.common.PathUtil.classFqnToPath -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.intellij.plugin.ui.GenerateTestsModel import org.utbot.intellij.plugin.ui.utils.getOrCreateSarifReportsPath -import org.utbot.sarif.SarifReport -import com.intellij.openapi.vfs.VfsUtil +import java.util.concurrent.CountDownLatch +import mu.KotlinLogging +import org.utbot.framework.plugin.api.ClassId +import org.utbot.intellij.plugin.generator.UtTestsDialogProcessor +import org.utbot.intellij.plugin.models.GenerateTestsModel +import org.utbot.intellij.plugin.process.EngineProcess +import org.utbot.sarif.Sarif +import org.utbot.intellij.plugin.util.IntelliJApiHelper +import java.nio.file.Path object SarifReportIdea { - + private val logger = KotlinLogging.logger {} /** * Creates the SARIF report by calling the SarifReport.createReport(), * saves it to test resources directory and notifies the user about the creation. */ fun createAndSave( + proc: EngineProcess, + testSetsId: Long, + classId: ClassId, model: GenerateTestsModel, - testCases: List, generatedTestsCode: String, - sourceFinding: SourceFindingStrategyIdea + psiClass: PsiClass, + reportsCountDown: CountDownLatch, + srcClassPathToSarifReport: MutableMap, + srcClassPath: Path, + indicator: ProgressIndicator ) { + UtTestsDialogProcessor.updateIndicator(indicator, UtTestsDialogProcessor.ProgressRange.SARIF, "Generate SARIF report for ${classId.name}", .5) // building the path to the report file - val classFqn = testCases.firstOrNull()?.method?.clazz?.qualifiedName ?: return - val sarifReportsPath = model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) + val classFqn = classId.name + val (sarifReportsPath, sourceFinding) = WriteAction.computeAndWait, Exception> { + model.testModule.getOrCreateSarifReportsPath(model.testSourceRoot) to SourceFindingStrategyIdea(psiClass) + } val reportFilePath = sarifReportsPath.resolve("${classFqnToPath(classFqn)}Report.sarif") - // creating report related directory - VfsUtil.createDirectoryIfMissing(reportFilePath.parent.toString()) - - // creating & saving sarif report - reportFilePath - .toFile() - .writeText(SarifReport(testCases, generatedTestsCode, sourceFinding).createReport()) + IntelliJApiHelper.run(IntelliJApiHelper.Target.THREAD_POOL, indicator, "Save SARIF report for ${classId.name}") { + try { + val sarifReportAsJson = proc.writeSarif(reportFilePath, testSetsId, generatedTestsCode, sourceFinding) + val newSarifReport = Sarif.fromJson(sarifReportAsJson) + val oldSarifReport = srcClassPathToSarifReport[srcClassPath] ?: Sarif.empty() + srcClassPathToSarifReport[srcClassPath] = oldSarifReport + newSarifReport + } catch (e: Exception) { + logger.error { e } + } finally { + reportsCountDown.countDown() + } + } } } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SourceFindingStrategyIdea.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SourceFindingStrategyIdea.kt index 0c92eed139..e98f6247f1 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SourceFindingStrategyIdea.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/sarif/SourceFindingStrategyIdea.kt @@ -1,33 +1,46 @@ package org.utbot.intellij.plugin.sarif +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiClass +import com.intellij.psi.search.GlobalSearchScope import org.utbot.common.PathUtil.classFqnToPath import org.utbot.common.PathUtil.safeRelativize import org.utbot.common.PathUtil.toPath import org.utbot.sarif.SourceFindingStrategy -import com.intellij.psi.JavaPsiFacade -import com.intellij.psi.PsiClass -import org.jetbrains.kotlin.idea.search.allScope +import java.io.File /** - * The search strategy based on the information available to the PsiClass + * The search strategy based on the information available to the PsiClass. */ class SourceFindingStrategyIdea(testClass: PsiClass) : SourceFindingStrategy() { /** - * Returns the relative path (against `project.basePath`) to the file with generated tests + * Returns the relative path (against `project.basePath`) to the file with generated tests. */ override val testsRelativePath: String get() = safeRelativize(project.basePath, testsFilePath) ?: testsFilePath.toPath().fileName.toString() /** - * Returns the relative path (against `project.basePath`) to the source file containing the class [classFqn] + * Returns the relative path (against `project.basePath`) to the source file containing the class [classFqn]. */ - override fun getSourceRelativePath(classFqn: String, extension: String?): String = - JavaPsiFacade.getInstance(project) - .findClass(classFqn, project.allScope())?.let { psiClass -> - safeRelativize(project.basePath, psiClass.containingFile.virtualFile.path) - } ?: (classFqnToPath(classFqn) + (extension ?: defaultExtension)) + override fun getSourceRelativePath(classFqn: String, extension: String?): String { + val psiClass = findPsiClass(classFqn) + val absolutePath = psiClass?.containingFile?.virtualFile?.path + val relativePath = safeRelativize(project.basePath, absolutePath) + val defaultRelativePath = classFqnToPath(classFqn) + (extension ?: defaultExtension) + return relativePath ?: defaultRelativePath + } + + /** + * Finds the source file containing the class [classFqn]. + * Returns null if the file does not exist. + */ + override fun getSourceFile(classFqn: String, extension: String?): File? { + val psiClass = findPsiClass(classFqn) + val sourceCodeFile = psiClass?.containingFile?.virtualFile?.path?.let(::File) + return if (sourceCodeFile?.exists() == true) sourceCodeFile else null + } // internal @@ -37,7 +50,23 @@ class SourceFindingStrategyIdea(testClass: PsiClass) : SourceFindingStrategy() { /** * The file extension to be used in [getSourceRelativePath] if the source file - * was not found by the class qualified name and the `extension` parameter is null + * was not found by the class qualified name and the `extension` parameter is null. */ private val defaultExtension = "." + (testClass.containingFile.virtualFile.extension ?: "java") + + /** + * Returns PsiClass by given [classFqn]. + */ + private fun findPsiClass(classFqn: String): PsiClass? { + val psiFacade = JavaPsiFacade.getInstance(project) + val psiClass = psiFacade.findClass(classFqn, GlobalSearchScope.allScope(project)) + if (psiClass != null) + return psiClass + + // If for some reason `psiClass` was not found by the `findClass` method + val packageName = classFqn.substringBeforeLast('.') + val shortClassName = classFqn.substringAfterLast('.') + val neededPackage = psiFacade.findPackage(packageName) + return neededPackage?.classes?.firstOrNull { it.name == shortClassName } + } } \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/CodeGeneratorServiceLoader.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/CodeGeneratorServiceLoader.kt deleted file mode 100644 index 4f4b1cbe07..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/CodeGeneratorServiceLoader.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.utbot.intellij.plugin.settings - -import org.utbot.framework.codegen.CodeGeneratorService -import org.utbot.framework.codegen.TestCodeGenerator -import org.utbot.framework.plugin.api.UtService -import java.util.ServiceLoader - -object CodeGeneratorServiceLoader : UtServiceLoader() { - override val services: List> - override val defaultService: UtService - - init { - services = withLocalClassLoader { - ServiceLoader.load(CodeGeneratorService::class.java).toList() - } - services.forEach { - serviceByName[it.displayName] = it - } - // TODO: process case when no generator is available - defaultService = services.first() - } -} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/JavaConfigurable.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/JavaConfigurable.kt new file mode 100644 index 0000000000..b22f57ce78 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/JavaConfigurable.kt @@ -0,0 +1,23 @@ +package org.utbot.intellij.plugin.settings + +import com.intellij.openapi.options.SearchableConfigurable +import com.intellij.openapi.project.Project +import javax.swing.JComponent + +class JavaConfigurable(val project: Project) : SearchableConfigurable { + private val displayName: String = "UtBot Java Configuration" + private val id: String = "org.utbot.intellij.plugin.settings.UtBotSettingsConfigurableJava" + private val settingsWindow = SettingsWindow(project) + + override fun createComponent(): JComponent = settingsWindow.panel + + override fun isModified(): Boolean = settingsWindow.isModified() + + override fun apply() = settingsWindow.apply() + + override fun reset() = settingsWindow.reset() + + override fun getDisplayName(): String = displayName + + override fun getId(): String = id +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/JavaTestFrameworkMapper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/JavaTestFrameworkMapper.kt new file mode 100644 index 0000000000..e3aee633cf --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/JavaTestFrameworkMapper.kt @@ -0,0 +1,28 @@ +package org.utbot.intellij.plugin.settings + +import org.utbot.framework.codegen.domain.Junit4 +import org.utbot.framework.codegen.domain.Junit5 +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.codegen.domain.TestNg + +object JavaTestFrameworkMapper : TestFrameworkMapper { + override fun toString(value: TestFramework): String = value.id + + override fun fromString(value: String): TestFramework = when (value) { + Junit4.id -> Junit4 + Junit5.id -> Junit5 + TestNg.id -> TestNg + else -> error("Unknown TestFramework $value") + } + + override fun handleUnknown(testFramework: TestFramework): TestFramework { + if (TestFramework.allItems.contains(testFramework)) { + return testFramework + } + return try { + fromString(testFramework.id) + } catch (ex: IllegalStateException) { + TestFramework.defaultItem + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/MockAlwaysClassesTable.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/MockAlwaysClassesTable.kt index 028bdb23f2..133167ff2e 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/MockAlwaysClassesTable.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/MockAlwaysClassesTable.kt @@ -13,6 +13,7 @@ import com.intellij.openapi.ui.cellvalidators.ValidatingTableCellRendererWrapper import com.intellij.openapi.util.Disposer import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiClass +import com.intellij.psi.search.GlobalSearchScope import com.intellij.ui.components.fields.ExtendableTextField import com.intellij.ui.table.JBTable import com.intellij.util.ui.ColumnInfo @@ -25,7 +26,6 @@ import javax.swing.JTextField import javax.swing.table.DefaultTableCellRenderer import javax.swing.table.TableCellEditor import javax.swing.table.TableCellRenderer -import org.jetbrains.kotlin.idea.search.allScope @Suppress("UnstableApiUsage") internal class MockAlwaysClassesTable(project: Project) : ListTableWithButtons() { @@ -123,7 +123,7 @@ internal class MockAlwaysClassesTable(project: Project) : ListTableWithButtons { - data class State( - var generatorName: String = defaultGeneratorName(), - var codeGeneratorName: String = defaultCodeGeneratorName(), - var codegenLanguage: CodegenLanguage = CodegenLanguage.defaultItem, - @OptionTag(converter = TestFrameworkConverter::class) - var testFramework: TestFramework = TestFramework.defaultItem, - var mockStrategy: MockStrategyApi = MockStrategyApi.defaultItem, - var mockFramework: MockFramework = MockFramework.defaultItem, - @OptionTag(converter = StaticsMockingConverter::class) - var staticsMocking: StaticsMocking = StaticsMocking.defaultItem, - var runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.defaultItem, - @OptionTag(converter = HangingTestsTimeoutConverter::class) - var hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(), - var forceStaticMocking: ForceStaticMocking = ForceStaticMocking.defaultItem, - var treatOverflowAsError: TreatOverflowAsError = TreatOverflowAsError.defaultItem, - var parametrizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem, - var classesToMockAlways: Array = Mocker.defaultSuperClassesToMockAlwaysNames.toTypedArray() - ) { - constructor(model: GenerateTestsModel) : this( - codegenLanguage = model.codegenLanguage, - testFramework = model.testFramework, - mockStrategy = model.mockStrategy, - mockFramework = model.mockFramework ?: MockFramework.defaultItem, - staticsMocking = model.staticsMocking, - runtimeExceptionTestsBehaviour = model.runtimeExceptionTestsBehaviour, - hangingTestsTimeout = model.hangingTestsTimeout, - forceStaticMocking = model.forceStaticMocking, - parametrizedTestSource = model.parametrizedTestSource, - classesToMockAlways = model.chosenClassesToMockAlways.mapTo(mutableSetOf()) { it.name }.toTypedArray() - ) - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as State - - if (generatorName != other.generatorName) return false - if (codeGeneratorName != other.codeGeneratorName) return false - if (codegenLanguage != other.codegenLanguage) return false - if (testFramework != other.testFramework) return false - if (mockStrategy != other.mockStrategy) return false - if (mockFramework != other.mockFramework) return false - if (staticsMocking != other.staticsMocking) return false - if (runtimeExceptionTestsBehaviour != other.runtimeExceptionTestsBehaviour) return false - if (hangingTestsTimeout != other.hangingTestsTimeout) return false - if (forceStaticMocking != other.forceStaticMocking) return false - if (treatOverflowAsError != other.treatOverflowAsError) return false - if (parametrizedTestSource != other.parametrizedTestSource) return false - if (!classesToMockAlways.contentEquals(other.classesToMockAlways)) return false - - return true - } - override fun hashCode(): Int { - var result = generatorName.hashCode() - result = 31 * result + codeGeneratorName.hashCode() - result = 31 * result + codegenLanguage.hashCode() - result = 31 * result + testFramework.hashCode() - result = 31 * result + mockStrategy.hashCode() - result = 31 * result + mockFramework.hashCode() - result = 31 * result + staticsMocking.hashCode() - result = 31 * result + runtimeExceptionTestsBehaviour.hashCode() - result = 31 * result + hangingTestsTimeout.hashCode() - result = 31 * result + forceStaticMocking.hashCode() - result = 31 * result + treatOverflowAsError.hashCode() - result = 31 * result + parametrizedTestSource.hashCode() - result = 31 * result + classesToMockAlways.contentHashCode() - - return result - } - } - - private var state = State() - - // TODO: we removed loading from saved settings, but may return to it later - val testCasesGenerator: TestCaseGenerator - get() = defaultTestCaseGenerator() - - // TODO: we removed loading from saved settings, but may return to it later - val codeGenerator: TestCodeGenerator - get() = defaultCodeGenerator() - - val generatorName: String get() = state.generatorName - - val codegenLanguage: CodegenLanguage get() = state.codegenLanguage - - val testFramework: TestFramework get() = state.testFramework - - val mockStrategy: MockStrategyApi get() = state.mockStrategy - - val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour get() = state.runtimeExceptionTestsBehaviour - - var hangingTestsTimeout: HangingTestsTimeout - get() = state.hangingTestsTimeout - set(value) { - state.hangingTestsTimeout = value - } - - val staticsMocking: StaticsMocking get() = state.staticsMocking - - val forceStaticMocking: ForceStaticMocking get() = state.forceStaticMocking - - val treatOverflowAsError: TreatOverflowAsError get() = state.treatOverflowAsError - - val parametrizedTestSource: ParametrizedTestSource get() = state.parametrizedTestSource - - val classesToMockAlways: Set get() = state.classesToMockAlways.toSet() - - fun setClassesToMockAlways(classesToMockAlways: List) { - state.classesToMockAlways = classesToMockAlways.distinct().toTypedArray() - } - - override fun getState(): State = state - - override fun initializeComponent() { - super.initializeComponent() - CompletableFuture.runAsync { FileUtil.clearTempDirectory(UtSettings.daysLimitForTempFiles) } - } - - override fun loadState(state: State) { - this.state = state - } - - fun loadStateFromModel(model: GenerateTestsModel) { - loadState(State(model)) - } - - // these classes are all ref types so we can use only names here - fun chosenClassesToMockAlways(): Set = state.classesToMockAlways.mapTo(mutableSetOf()) { ClassId(it) } - - fun setProviderByLoader(loader: KClass<*>, provider: CodeGenerationSettingItem) = - when (loader) { - // TODO: service loaders for test generator and code generator are removed from settings temporarily -// TestGeneratorServiceLoader::class -> setGeneratorName(provider) -// CodeGeneratorServiceLoader::class -> setCodeGeneratorName(provider) - MockStrategyApi::class -> state.mockStrategy = provider as MockStrategyApi - CodegenLanguage::class -> state.codegenLanguage = provider as CodegenLanguage - RuntimeExceptionTestsBehaviour::class -> { - state.runtimeExceptionTestsBehaviour = provider as RuntimeExceptionTestsBehaviour - } - ForceStaticMocking::class -> state.forceStaticMocking = provider as ForceStaticMocking - TreatOverflowAsError::class -> { - // TODO: SAT-1566 - state.treatOverflowAsError = provider as TreatOverflowAsError - UtSettings.treatOverflowAsError = provider == TreatOverflowAsError.AS_ERROR - } - // TODO: add error processing - else -> error("Unknown class [$loader] to map value [$provider]") - } - - fun providerNameByServiceLoader(loader: KClass<*>): CodeGenerationSettingItem = - when (loader) { - // TODO: service loaders for test generator and code generator are removed from settings temporarily -// TestGeneratorServiceLoader::class -> generatorName -// CodeGeneratorServiceLoader::class -> codeGeneratorName - MockStrategyApi::class -> mockStrategy - CodegenLanguage::class -> codegenLanguage - RuntimeExceptionTestsBehaviour::class -> runtimeExceptionTestsBehaviour - ForceStaticMocking::class -> forceStaticMocking - TreatOverflowAsError::class -> treatOverflowAsError - // TODO: add error processing - else -> error("Unknown service loader: $loader") - } - - companion object { - private fun defaultTestCaseGenerator(): TestCaseGenerator = SymbolicEngineTestGeneratorService().serviceProvider - - private fun defaultCodeGenerator(): TestCodeGenerator = ModelBasedCodeGeneratorService().serviceProvider - - private fun defaultGeneratorName(): String = - TestGeneratorServiceLoader.defaultServiceProviderName - - private fun defaultCodeGeneratorName(): String = - CodeGeneratorServiceLoader.defaultServiceProviderName - } -} - -// use it to serialize testFramework in State -private class TestFrameworkConverter : Converter() { - override fun toString(value: TestFramework): String = "$value" - - override fun fromString(value: String): TestFramework = when (value) { - Junit4.displayName -> Junit4 - Junit5.displayName -> Junit5 - TestNg.displayName -> TestNg - else -> error("Unknown TestFramework $value") - } -} - -// use it to serialize staticsMocking in State -private class StaticsMockingConverter : Converter() { - override fun toString(value: StaticsMocking): String = "$value" - - override fun fromString(value: String): StaticsMocking = when (value) { - NoStaticMocking.displayName -> NoStaticMocking - MockitoStaticMocking.displayName -> MockitoStaticMocking - else -> error("Unknown StaticsMocking $value") - } +fun loadStateFromModel(settings: Settings, model: GenerateTestsModel) { + settings.loadState(fromGenerateTestsModel(model)) } -// TODO is it better to use kotlinx.serialization? -// use it to serialize hangingTestsTimeout in State -private class HangingTestsTimeoutConverter : Converter() { - override fun toString(value: HangingTestsTimeout): String = - "HangingTestsTimeout:${value.timeoutMs}" - - override fun fromString(value: String): HangingTestsTimeout { - val arguments = value.substringAfter("HangingTestsTimeout:") - val timeoutMs = arguments.first().toLong() - - return HangingTestsTimeout(timeoutMs) - } -} +private fun fromGenerateTestsModel(model: GenerateTestsModel): Settings.State { + return Settings.State( + sourceRootHistory = model.sourceRootHistory, + codegenLanguage = model.codegenLanguage, + testFramework = model.testFramework, + mockStrategy = model.mockStrategy, + mockFramework = model.mockFramework ?: MockFramework.defaultItem, + staticsMocking = model.staticsMocking, + runtimeExceptionTestsBehaviour = model.runtimeExceptionTestsBehaviour, + hangingTestsTimeout = model.hangingTestsTimeout, + useTaintAnalysis = model.useTaintAnalysis, + runInspectionAfterTestGeneration = model.runInspectionAfterTestGeneration, + forceStaticMocking = model.forceStaticMocking, + parametrizedTestSource = model.parametrizedTestSource, + classesToMockAlways = model.chosenClassesToMockAlways.mapTo(mutableSetOf()) { it.name }.toTypedArray(), + springTestType = model.springTestType, + springConfig = model.springConfig, + springProfileNames = model.springProfileNames, + fuzzingValue = model.fuzzingValue, + runGeneratedTestsWithCoverage = model.runGeneratedTestsWithCoverage, + commentStyle = model.commentStyle, + generationTimeoutInMillis = model.timeout, + summariesGenerationType = model.summariesGenerationType + ) +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/SettingsWindow.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/SettingsWindow.kt index c1501f51c9..6b268a71d4 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/SettingsWindow.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/SettingsWindow.kt @@ -1,208 +1,223 @@ package org.utbot.intellij.plugin.settings -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.HangingTestsTimeout -import org.utbot.framework.codegen.RuntimeExceptionTestsBehaviour -import org.utbot.framework.plugin.api.CodeGenerationSettingItem -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.TreatOverflowAsError -import org.utbot.intellij.plugin.ui.utils.labeled -import org.utbot.intellij.plugin.ui.utils.panelNoTitle import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.ui.ComboBox -import com.intellij.openapi.util.Comparing -import com.intellij.ui.layout.CCFlags -import com.intellij.ui.layout.panel -import com.intellij.util.ui.JBUI -import java.awt.FlowLayout -import javax.swing.DefaultComboBoxModel -import javax.swing.JComponent -import javax.swing.JLabel -import javax.swing.JPanel -import javax.swing.JSpinner -import javax.swing.SpinnerNumberModel -import javax.swing.event.AncestorEvent -import javax.swing.event.AncestorListener +import com.intellij.openapi.ui.DialogPanel +import com.intellij.ui.ContextHelpLabel +import com.intellij.ui.components.JBLabel +import com.intellij.ui.dsl.builder.* +import com.intellij.ui.layout.selected +import com.intellij.ui.layout.selectedValueMatches +import com.intellij.util.ui.UIUtil +import com.intellij.util.ui.components.BorderLayoutPanel +import javax.swing.* import kotlin.reflect.KClass +import org.utbot.framework.SummariesGenerationType +import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.plugin.api.CodeGenerationSettingItem +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.JavaDocCommentStyle +import org.utbot.framework.plugin.api.TreatOverflowAsError +import org.utbot.framework.plugin.api.isSummarizationCompatible +import org.utbot.intellij.plugin.ui.components.CodeGenerationSettingItemRenderer +import org.utbot.intellij.plugin.util.showSettingsEditor -@Suppress("UNCHECKED_CAST") class SettingsWindow(val project: Project) { - var panel: JPanel - private val settings = project.service() - private val comboBoxes: MutableList = mutableListOf() - - private data class ComboBoxInfo(val loader: KClass<*>, val comboBox: ComboBox) - - private val mockStrategyComboBox = ComboBox(DefaultComboBoxModel(MockStrategyApi.values())) - private val codegenLanguageComboBox = ComboBox(DefaultComboBoxModel(CodegenLanguage.values())) - private val runtimeExceptionTestsBehaviourComboBox = ComboBox( - DefaultComboBoxModel(RuntimeExceptionTestsBehaviour.values()) - ) - private val hangingTestTimeoutSecondsConfigurable = HangingTestTimeoutSecondsConfigurable() - private val forceStaticMockingComboBox = ComboBox(DefaultComboBoxModel(ForceStaticMocking.values())) - private val treatOverflowAsErrorComboBox = ComboBox(DefaultComboBoxModel(TreatOverflowAsError.values())) // TODO it is better to use something like SearchEverywhere for classes but it is complicated to implement + private lateinit var codegenLanguageCombo: ComboBox private val excludeTable = MockAlwaysClassesTable(project) - - - val isModified: Boolean - get() = comboBoxes.any { isComboBoxModified(it) } || - excludeTable.isModified() || - hangingTestTimeoutSecondsConfigurable.isModified - - fun apply() { - comboBoxes.forEach { (loader, comboBox) -> - val newProvider = comboBox.selectedItem as CodeGenerationSettingItem - settings.setProviderByLoader(loader, newProvider) - } - hangingTestTimeoutSecondsConfigurable.apply() - excludeTable.apply() + private lateinit var useTaintAnalysisCheckBox: JCheckBox + private lateinit var runInspectionAfterTestGenerationCheckBox: JCheckBox + private lateinit var forceMockCheckBox: JCheckBox + private lateinit var enableSummarizationGenerationCheckBox: JCheckBox + + private fun Row.createCombo(loader: KClass<*>, values: Array<*>) { + comboBox(DefaultComboBoxModel(values)) + .bindItem( + getter = { settings.providerNameByServiceLoader(loader) }, + setter = { settings.setProviderByLoader(loader, it as CodeGenerationSettingItem) }, + ).component.renderer = CodeGenerationSettingItemRenderer() } - fun reset() { - comboBoxes.forEach { (loader, comboBox) -> - comboBox.selectedItem = settings.providerNameByServiceLoader(loader) - } - excludeTable.reset() - hangingTestTimeoutSecondsConfigurable.reset() - } - - init { - // TODO: service loaders for test generator and code generator are removed from settings temporarily - val serviceLoaderToProviderNames = listOf, Set>>() -// listOf(TestGeneratorServiceLoader, CodeGeneratorServiceLoader).map { -// it to it.serviceProviderNames -// } - serviceLoaderToProviderNames.forEach { (loader, names) -> - val model = DefaultComboBoxModel(names.toTypedArray()).apply { - selectedItem = settings.providerNameByServiceLoader(loader::class) + val panel: JPanel = panel { + group("Java Specific Settings") { + row("Generated test language:") { + codegenLanguageCombo = comboBox(DefaultComboBoxModel(CodegenLanguage.values())).gap(RightGap.COLUMNS) + .apply { + component.renderer = CodeGenerationSettingItemRenderer() + ContextHelpLabel.create("You can generate test methods in Java or Kotlin regardless of your source code language.") + }.bindItem( + getter = { settings.providerNameByServiceLoader(CodegenLanguage::class) as CodegenLanguage }, + setter = { + settings.setProviderByLoader( + CodegenLanguage::class, + it as CodeGenerationSettingItem + ) + } + ).component + codegenLanguageCombo.addActionListener { + if (!codegenLanguageCombo.item.isSummarizationCompatible()) { + enableSummarizationGenerationCheckBox.isSelected = false + } + } } - comboBoxes += ComboBoxInfo(loader::class, ComboBox(model)) - } - comboBoxes += ComboBoxInfo( - MockStrategyApi::class, - mockStrategyComboBox as ComboBox - ) - comboBoxes += ComboBoxInfo( - CodegenLanguage::class, - codegenLanguageComboBox as ComboBox - ) - comboBoxes += ComboBoxInfo( - RuntimeExceptionTestsBehaviour::class, - runtimeExceptionTestsBehaviourComboBox as ComboBox - ) - comboBoxes += ComboBoxInfo( - ForceStaticMocking::class, - forceStaticMockingComboBox as ComboBox - ) - comboBoxes += ComboBoxInfo( - TreatOverflowAsError::class, - treatOverflowAsErrorComboBox as ComboBox - ) - - panel = settingsPanel() - panel.addAncestorListener(object : AncestorListener { - private fun setItems() { - mockStrategyComboBox.item = settings.mockStrategy - codegenLanguageComboBox.item = settings.codegenLanguage - runtimeExceptionTestsBehaviourComboBox.item = settings.runtimeExceptionTestsBehaviour - forceStaticMockingComboBox.item = settings.forceStaticMocking - treatOverflowAsErrorComboBox.item = settings.treatOverflowAsError + row("Overflow detection:") { + createCombo(TreatOverflowAsError::class, TreatOverflowAsError.values()) } - // get settings from project state on window appearance - override fun ancestorAdded(event: AncestorEvent) { - setItems() + row { + useTaintAnalysisCheckBox = + checkBox("Enable taint analysis") + .onApply { + settings.state.useTaintAnalysis = useTaintAnalysisCheckBox.isSelected + } + .onReset { + useTaintAnalysisCheckBox.isSelected = settings.state.useTaintAnalysis + } + .onIsModified { + useTaintAnalysisCheckBox.isSelected xor settings.state.useTaintAnalysis + } + .component + contextHelp("Experimental taint analysis support") + } + row { + runInspectionAfterTestGenerationCheckBox = + checkBox("Display detected errors on the Problems tool window") + .onApply { + settings.state.runInspectionAfterTestGeneration = + runInspectionAfterTestGenerationCheckBox.isSelected + } + .onReset { + runInspectionAfterTestGenerationCheckBox.isSelected = + settings.state.runInspectionAfterTestGeneration + } + .onIsModified { + runInspectionAfterTestGenerationCheckBox.isSelected xor settings.state.runInspectionAfterTestGeneration + } + .component + contextHelp("Automatically run code inspection after test generation") + } + row { + enableSummarizationGenerationCheckBox = checkBox("Enable summaries generation") + .onApply { + settings.state.summariesGenerationType = + if (enableSummarizationGenerationCheckBox.isSelected) SummariesGenerationType.FULL else SummariesGenerationType.NONE + } + .onReset { + enableSummarizationGenerationCheckBox.isSelected = + settings.state.summariesGenerationType != SummariesGenerationType.NONE + } + .onIsModified { + enableSummarizationGenerationCheckBox.isSelected xor (settings.state.summariesGenerationType != SummariesGenerationType.NONE) + }.enabledIf(codegenLanguageCombo.selectedValueMatches(CodegenLanguage?::isSummarizationCompatible)) + .component + } + indent { + row("Javadoc comment style:") { + createCombo(JavaDocCommentStyle::class, JavaDocCommentStyle.values()) + }.enabledIf(enableSummarizationGenerationCheckBox.selected).bottomGap(BottomGap.MEDIUM) } - override fun ancestorRemoved(event: AncestorEvent) {} - - override fun ancestorMoved(event: AncestorEvent) {} - }) - } - - private fun settingsPanel(): JPanel = panel { - comboBoxes - .filterNot { it.loader == ForceStaticMocking::class } - .forEach { (loader, comboBox) -> labeled(labelByServiceLoader(loader), comboBox) } - - row { - val timeoutComponent = hangingTestTimeoutSecondsConfigurable.createComponent() - hangingTestTimeoutSecondsConfigurable.reset() - - panelNoTitle(timeoutComponent) - } - - labeled(labelByServiceLoader(ForceStaticMocking::class), forceStaticMockingComboBox) - row { - excludeTable.component(CCFlags.grow) - .onApply { excludeTable.apply() } - .onReset { excludeTable.reset() } - .onIsModified { excludeTable.isModified() } + row { + forceMockCheckBox = checkBox("Force mocking static methods") + .onApply { + settings.state.forceStaticMocking = + if (forceMockCheckBox.isSelected) ForceStaticMocking.FORCE else ForceStaticMocking.DO_NOT_FORCE + } + .onReset { forceMockCheckBox.isSelected = settings.forceStaticMocking == ForceStaticMocking.FORCE } + .onIsModified { forceMockCheckBox.isSelected xor (settings.forceStaticMocking != ForceStaticMocking.DO_NOT_FORCE) } + .component + contextHelp("Overrides other mocking settings") + } + row("Classes to be forcedly mocked:") {} + row { + val updater = Runnable { + UIUtil.setEnabled(excludeTable.component, forceMockCheckBox.isSelected, true) + } + cell(excludeTable.component) + .align(Align.FILL) + .onApply { excludeTable.apply() } + .onReset { + excludeTable.reset() + updater.run() + } + .onIsModified { excludeTable.isModified() } + + forceMockCheckBox.addActionListener { updater.run() } + }.bottomGap(BottomGap.MEDIUM) + + val fuzzLabel = JBLabel("Fuzzing") + val symLabel = JBLabel("Symbolic execution") + row { + cell(BorderLayoutPanel().apply { + topGap(TopGap.SMALL) + addToLeft(JBLabel("Test generation method:").apply { verticalAlignment = SwingConstants.TOP }) + addToCenter(BorderLayoutPanel().apply { + val granularity = 20 + val slider = object : JSlider() { + val updater = Runnable() { + val fuzzingPercent = 100.0 * (granularity - value) / granularity + fuzzLabel.text = "Fuzzing %.0f %%".format(fuzzingPercent) + symLabel.text = "%.0f %% Symbolic execution".format(100.0 - fuzzingPercent) + } + + override fun getValue() = ((1 - settings.fuzzingValue) * granularity).toInt() + + override fun setValue(n: Int) { + val tmp = value + settings.fuzzingValue = 1 - n / granularity.toDouble() + if (tmp != n) { + updater.run() + } + } + } + UIUtil.setSliderIsFilled(slider, true) + slider.minimum = 0 + slider.maximum = granularity + slider.minorTickSpacing = 1 + slider.majorTickSpacing = granularity / 4 + slider.paintTicks = true + slider.paintTrack = true + slider.paintLabels = false + slider.toolTipText = + "While fuzzer \"guesses\" the values to enter as much execution paths as possible, symbolic executor tries to \"deduce\" them. Choose the proportion of generation time allocated for each of these methods within Test generation timeout. The slide has no effect for Spring Projects." + slider.updater.run() + addToTop(slider) + addToBottom(BorderLayoutPanel().apply { + addToLeft(fuzzLabel) + addToRight(symLabel) + }) + }) + }).align(Align.FILL) + }.enabled(UtSettings.useFuzzing) + if (!UtSettings.useFuzzing) { + row { + comment("Fuzzing is disabled in configuration file.") + link("Edit configuration") { + UIUtil.getWindow(fuzzLabel)?.dispose() + showSettingsEditor(project, "useFuzzing") + } + } + } } } - private fun labelByServiceLoader(loader: KClass<*>): String = - when (loader) { - TestGeneratorServiceLoader::class -> "Test case generator:" - CodeGeneratorServiceLoader::class -> "Code generator:" - MockStrategyApi::class -> "Mock strategy: " - CodegenLanguage::class -> "Language generation: " - RuntimeExceptionTestsBehaviour::class -> "Behavior of the tests producing Runtime exceptions: " - ForceStaticMocking::class -> "Force static mocking: " - TreatOverflowAsError::class -> "Overflow detection: " - // TODO: add error processing - else -> error("Unknown service loader: $loader") - } - - private fun isComboBoxModified(info: ComboBoxInfo): Boolean = - settings.providerNameByServiceLoader(info.loader) != info.comboBox.selectedItem - - private inner class HangingTestTimeoutSecondsConfigurable : com.intellij.openapi.options.Configurable { - private val title = "Hanging test timeout" - private val timeUnit = "ms" - private val spinnerStepSize = 50L - - private lateinit var timeoutSpinner: JSpinner - - override fun createComponent(): JComponent { - val wrapper = JPanel(FlowLayout(FlowLayout.LEFT, 0, 0)) - val titleLabel = JLabel(title) - wrapper.add(titleLabel) - timeoutSpinner = JSpinner(createSpinnerModel()) - wrapper.add(timeoutSpinner) - val timeUnitLabel = JLabel(timeUnit) - timeUnitLabel.border = JBUI.Borders.empty(0, 1) - wrapper.add(timeUnitLabel) - - return wrapper - } - - fun createSpinnerModel(): SpinnerNumberModel = SpinnerNumberModel( - HangingTestsTimeout.DEFAULT_TIMEOUT_MS, - HangingTestsTimeout.MIN_TIMEOUT_MS, - HangingTestsTimeout.MAX_TIMEOUT_MS, - spinnerStepSize - ) - - override fun isModified(): Boolean = - !Comparing.equal(timeoutSpinner.value, settings.hangingTestsTimeout.timeoutMs) - - override fun apply() { - val hangingTestsTimeout = HangingTestsTimeout(timeoutSpinner.value as Long) - - settings.hangingTestsTimeout = hangingTestsTimeout - } + fun isModified(): Boolean { + return excludeTable.isModified() || (panel as DialogPanel).isModified() + } - override fun getDisplayName(): String = "Test milliseconds timeout" + fun apply() { + excludeTable.apply() + (panel as DialogPanel).apply() + } - override fun reset() { - timeoutSpinner.value = settings.hangingTestsTimeout.timeoutMs - } + fun reset() { + excludeTable.reset() + (panel as DialogPanel).reset() } -} \ No newline at end of file +} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/TestGeneratorServiceLoader.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/TestGeneratorServiceLoader.kt deleted file mode 100644 index 39fdd6eca8..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/TestGeneratorServiceLoader.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.utbot.intellij.plugin.settings - -import org.utbot.framework.plugin.api.TestCaseGenerator -import org.utbot.framework.plugin.api.TestGeneratorService -import org.utbot.framework.plugin.api.UtService -import java.util.ServiceLoader - -object TestGeneratorServiceLoader : UtServiceLoader() { - override val services: List> - override val defaultService: UtService - - init { - services = withLocalClassLoader { - ServiceLoader.load(TestGeneratorService::class.java).toList() - } - services.forEach { - serviceByName[it.displayName] = it - } - // TODO: process case when no generator is available - defaultService = services.first() - } -} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/UtServiceLoader.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/UtServiceLoader.kt deleted file mode 100644 index 7b97cdc88c..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/UtServiceLoader.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.utbot.intellij.plugin.settings - -import org.utbot.framework.plugin.api.UtService - -abstract class UtServiceLoader { - abstract val services: List> - abstract val defaultService: UtService - protected val serviceByName: MutableMap> = mutableMapOf() - - val defaultServiceProvider: T - get() = defaultService.serviceProvider - - val defaultServiceProviderName: String - get() = defaultService.displayName - - fun serviceProviderByName(name: String): T? = - serviceByName[name]?.serviceProvider - - val serviceProviderNames: Set - get() = serviceByName.keys - - protected inline fun withLocalClassLoader(block: () -> T): T { - val actualClassLoader = Thread.currentThread().contextClassLoader - try { - Thread.currentThread().contextClassLoader = this::class.java.classLoader - return block() - } finally { - Thread.currentThread().contextClassLoader = actualClassLoader - } - } -} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/ConfigureWindowCommon.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/ConfigureWindowCommon.kt deleted file mode 100644 index d682574bb0..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/ConfigureWindowCommon.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.utbot.intellij.plugin.ui - -import com.intellij.openapi.module.Module -import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.DependencyScope -import com.intellij.openapi.roots.ExternalLibraryDescriptor -import com.intellij.openapi.roots.JavaProjectModelModificationService -import com.intellij.openapi.ui.Messages -import org.jetbrains.concurrency.Promise -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.intellij.plugin.ui.utils.LibrarySearchScope -import org.utbot.intellij.plugin.ui.utils.findFrameworkLibrary -import org.utbot.intellij.plugin.ui.utils.parseVersion - -fun createMockFrameworkNotificationDialog(title: String) = Messages.showYesNoDialog( - """Mock framework ${MockFramework.MOCKITO.displayName} is not installed into current module. - |Would you like to install it now?""".trimMargin(), - title, - "Yes", - "No", - Messages.getQuestionIcon(), -) - -fun configureMockFramework(project: Project, module: Module) { - val selectedMockFramework = MockFramework.MOCKITO - - val libraryInProject = - findFrameworkLibrary(project, module, selectedMockFramework, LibrarySearchScope.Project) - val versionInProject = libraryInProject?.libraryName?.parseVersion() - - selectedMockFramework.isInstalled = true - addDependency(project, module, mockitoCoreLibraryDescriptor(versionInProject)) - .onError { selectedMockFramework.isInstalled = false } -} - -/** - * Adds the dependency for selected framework via [JavaProjectModelModificationService]. - * - * Note that version restrictions will be applied only if they are present on target machine - * Otherwise latest release version will be installed. - */ -fun addDependency(project: Project, module: Module, libraryDescriptor: ExternalLibraryDescriptor): Promise { - return JavaProjectModelModificationService - .getInstance(project) - //this method returns JetBrains internal Promise that is difficult to deal with, but it is our way - .addDependency(module, libraryDescriptor, DependencyScope.TEST) -} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/ExternalLibraryDescriptors.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/ExternalLibraryDescriptors.kt deleted file mode 100644 index c11e53c7c7..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/ExternalLibraryDescriptors.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.utbot.intellij.plugin.ui - -import com.intellij.openapi.roots.ExternalLibraryDescriptor - -//TODO: think about using JUnitExternalLibraryDescriptor from intellij-community sources (difficult to install) -fun jUnit4LibraryDescriptor(versionInProject: String?) = - ExternalLibraryDescriptor("junit", "junit", "4.0", null, versionInProject ?: "4.13.2") - -fun jUnit5LibraryDescriptor(versionInProject: String?) = - ExternalLibraryDescriptor("org.junit.jupiter", "junit-jupiter", "5.8.1", null, versionInProject ?: "5.8.1") - -fun testNgLibraryDescriptor(versionInProject: String?) = - ExternalLibraryDescriptor("org.testng", "testng", "6.8.8", null, versionInProject ?: "6.9.6") - -fun mockitoCoreLibraryDescriptor(versionInProject: String?) = - ExternalLibraryDescriptor("org.mockito", "mockito-core", "3.5.0", "4.2.0", versionInProject ?: "4.2.0") \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt index 6177131a0d..760046ef60 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt @@ -4,40 +4,49 @@ package org.utbot.intellij.plugin.ui import com.intellij.codeInsight.hint.HintUtil import com.intellij.icons.AllIcons -import com.intellij.ide.impl.ProjectNewWindowDoNotAskOption import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.components.service import com.intellij.openapi.editor.colors.EditorColorsManager +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.projectRoots.JavaSdkVersion -import com.intellij.openapi.roots.ContentEntry +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ExternalLibraryDescriptor +import com.intellij.openapi.roots.JavaProjectModelModificationService +import com.intellij.openapi.roots.LibraryOrderEntry +import com.intellij.openapi.roots.ModifiableRootModel import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.roots.ModuleRootModificationUtil +import com.intellij.openapi.roots.ModuleSourceOrderEntry import com.intellij.openapi.roots.ui.configuration.ClasspathEditor import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.DialogPanel import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.Messages +import com.intellij.openapi.ui.OptionAction import com.intellij.openapi.ui.ValidationInfo import com.intellij.openapi.ui.popup.IconButton +import com.intellij.openapi.ui.popup.ListSeparator import com.intellij.openapi.util.Computable +import com.intellij.openapi.util.NlsContexts import com.intellij.openapi.vfs.StandardFileSystems import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VfsUtilCore.urlToPath import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile +import com.intellij.openapi.wm.ToolWindowManager import com.intellij.psi.PsiClass import com.intellij.psi.PsiManager -import com.intellij.psi.PsiMethod import com.intellij.refactoring.PackageWrapper import com.intellij.refactoring.ui.MemberSelectionTable import com.intellij.refactoring.ui.PackageNameReferenceEditorCombo import com.intellij.refactoring.util.RefactoringUtil import com.intellij.refactoring.util.classMembers.MemberInfo -import com.intellij.testIntegration.TestIntegrationUtils import com.intellij.ui.ColoredListCellRenderer -import com.intellij.ui.ContextHelpLabel +import com.intellij.ui.GroupHeaderSeparator import com.intellij.ui.HyperlinkLabel import com.intellij.ui.IdeBorderFactory.createBorder import com.intellij.ui.InplaceButton @@ -45,18 +54,18 @@ import com.intellij.ui.JBColor import com.intellij.ui.JBIntSpinner import com.intellij.ui.SideBorder import com.intellij.ui.SimpleTextAttributes +import org.utbot.framework.plugin.api.SpringSettings.* import com.intellij.ui.components.CheckBox import com.intellij.ui.components.JBLabel -import com.intellij.ui.components.Panel +import com.intellij.ui.components.JBScrollPane +import com.intellij.ui.components.JBTextField import com.intellij.ui.components.panels.HorizontalLayout import com.intellij.ui.components.panels.NonOpaquePanel -import com.intellij.ui.layout.Cell -import com.intellij.ui.layout.CellBuilder -import com.intellij.ui.layout.Row -import com.intellij.ui.layout.panel +import com.intellij.ui.components.panels.OpaquePanel +import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.layout.ComboBoxPredicate import com.intellij.util.IncorrectOperationException -import com.intellij.util.io.exists -import com.intellij.util.lang.JavaVersion import com.intellij.util.ui.JBUI import com.intellij.util.ui.JBUI.Borders.empty import com.intellij.util.ui.JBUI.Borders.merge @@ -64,64 +73,111 @@ import com.intellij.util.ui.JBUI.scale import com.intellij.util.ui.JBUI.size import com.intellij.util.ui.UIUtil import com.intellij.util.ui.components.BorderLayoutPanel +import mu.KotlinLogging +import org.jetbrains.concurrency.Promise +import org.jetbrains.concurrency.thenRun import org.utbot.common.PathUtil.toPath import org.utbot.framework.UtSettings -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.Junit4 -import org.utbot.framework.codegen.Junit5 -import org.utbot.framework.codegen.NoStaticMocking -import org.utbot.framework.codegen.ParametrizedTestSource -import org.utbot.framework.codegen.StaticsMocking -import org.utbot.framework.codegen.TestFramework -import org.utbot.framework.codegen.TestNg -import org.utbot.framework.codegen.model.util.MOCKITO_EXTENSIONS_FILE_CONTENT -import org.utbot.framework.codegen.model.util.MOCKITO_EXTENSIONS_STORAGE -import org.utbot.framework.codegen.model.util.MOCKITO_MOCKMAKER_FILE_NAME -import org.utbot.framework.plugin.api.CodeGenerationSettingItem -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.MockFramework.MOCKITO +import org.utbot.framework.codegen.domain.SpringModule +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.Junit4 +import org.utbot.framework.codegen.domain.Junit5 +import org.utbot.framework.codegen.domain.MockitoStaticMocking +import org.utbot.framework.codegen.domain.NoStaticMocking +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.codegen.domain.SpringModule.* +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.codegen.domain.TestNg import org.utbot.framework.plugin.api.MockStrategyApi import org.utbot.framework.plugin.api.TreatOverflowAsError +import org.utbot.framework.plugin.api.MockFramework.MOCKITO +import org.utbot.framework.plugin.api.SpringTestType +import org.utbot.framework.plugin.api.MockFramework +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.CodeGenerationSettingItem +import org.utbot.framework.plugin.api.SpringConfiguration +import org.utbot.framework.plugin.api.SpringTestType.* +import org.utbot.framework.plugin.api.SpringProfileNames +import org.utbot.framework.plugin.api.utils.MOCKITO_EXTENSIONS_FILE_CONTENT +import org.utbot.framework.plugin.api.utils.MOCKITO_EXTENSIONS_FOLDER +import org.utbot.framework.plugin.api.utils.MOCKITO_MOCKMAKER_FILE_NAME +import org.utbot.framework.plugin.api.NO_SPRING_CONFIGURATION_OPTION +import org.utbot.framework.util.Conflict +import org.utbot.intellij.plugin.models.GenerateTestsModel +import org.utbot.intellij.plugin.models.id +import org.utbot.intellij.plugin.models.jUnit4LibraryDescriptor +import org.utbot.intellij.plugin.models.jUnit5LibraryDescriptor +import org.utbot.intellij.plugin.models.jUnit5ParametrizedTestsLibraryDescriptor +import org.utbot.intellij.plugin.models.mockitoCoreLibraryDescriptor +import org.utbot.intellij.plugin.models.packageName +import org.utbot.intellij.plugin.models.springBootTestLibraryDescriptor +import org.utbot.intellij.plugin.models.springSecurityLibraryDescriptor +import org.utbot.intellij.plugin.models.springTestLibraryDescriptor +import org.utbot.intellij.plugin.models.testNgNewLibraryDescriptor +import org.utbot.intellij.plugin.models.testNgOldLibraryDescriptor +import org.utbot.intellij.plugin.settings.JavaTestFrameworkMapper import org.utbot.intellij.plugin.settings.Settings +import org.utbot.intellij.plugin.settings.loadStateFromModel +import org.utbot.intellij.plugin.ui.components.CodeGenerationSettingItemRenderer import org.utbot.intellij.plugin.ui.components.TestFolderComboWithBrowseButton import org.utbot.intellij.plugin.ui.utils.LibrarySearchScope import org.utbot.intellij.plugin.ui.utils.addSourceRootIfAbsent +import org.utbot.intellij.plugin.ui.utils.allLibraries +import org.utbot.intellij.plugin.ui.utils.createTestFrameworksRenderer +import org.utbot.intellij.plugin.ui.utils.findDependencyInjectionLibrary +import org.utbot.intellij.plugin.ui.utils.findDependencyInjectionTestLibrary import org.utbot.intellij.plugin.ui.utils.findFrameworkLibrary +import org.utbot.intellij.plugin.ui.utils.findParametrizedTestsLibrary import org.utbot.intellij.plugin.ui.utils.getOrCreateTestResourcesPath -import org.utbot.intellij.plugin.ui.utils.kotlinTargetPlatform +import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle import org.utbot.intellij.plugin.ui.utils.parseVersion import org.utbot.intellij.plugin.ui.utils.testResourceRootTypes import org.utbot.intellij.plugin.ui.utils.testRootType -import org.utbot.intellij.plugin.util.AndroidApiHelper +import org.utbot.intellij.plugin.util.* import java.awt.BorderLayout import java.awt.Color +import java.awt.Component +import java.awt.Dimension +import java.awt.event.ActionEvent import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths -import java.util.Objects +import java.text.ParseException +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter import java.util.concurrent.TimeUnit +import javax.swing.AbstractAction +import javax.swing.Action import javax.swing.DefaultComboBoxModel +import javax.swing.JButton +import javax.swing.JCheckBox import javax.swing.JComboBox import javax.swing.JComponent import javax.swing.JList -import javax.swing.JPanel -import kotlin.streams.toList +import javax.swing.JSpinner +import javax.swing.text.DefaultFormatter +import kotlin.io.path.notExists + private const val RECENTS_KEY = "org.utbot.recents" private const val SAME_PACKAGE_LABEL = "same as for sources" private const val WILL_BE_INSTALLED_LABEL = " (will be installed)" -private const val WILL_BE_CONFIGURED_LABEL = " (will be configured)" -private const val MINIMUM_TIMEOUT_VALUE_IN_SECONDS = 1 + +private const val ACTION_GENERATE = "Generate Tests" +private const val ACTION_GENERATE_AND_RUN = "Generate and Run" class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(model.project) { companion object { const val minSupportedSdkVersion = 8 - const val maxSupportedSdkVersion = 11 + const val maxSupportedSdkVersion = 17 } + private val logger = KotlinLogging.logger {} + private val membersTable = MemberSelectionTable(emptyList(), null) private val cbSpecifyTestPackage = CheckBox("Specify destination package", false) @@ -129,39 +185,219 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m findTestPackageComboValue(), model.project, RECENTS_KEY, - "Choose destination package" + "Choose Destination Package" ) private val testSourceFolderField = TestFolderComboWithBrowseButton(model) - private val codegenLanguages = ComboBox(DefaultComboBoxModel(CodegenLanguage.values())) - private val testFrameworks = ComboBox(DefaultComboBoxModel(TestFramework.allItems.toTypedArray())) - private val mockStrategies = ComboBox(DefaultComboBoxModel(MockStrategyApi.values())) - private val staticsMocking = ComboBox(DefaultComboBoxModel(StaticsMocking.allItems.toTypedArray())) + private val codegenLanguages = createComboBox(CodegenLanguage.values()) + private val testFrameworks = createComboBox(TestFramework.allItems.toTypedArray()) + + private val javaConfigurationHelper = SpringConfigurationsHelper(SpringConfigurationType.ClassConfiguration) + private val xmlConfigurationHelper = SpringConfigurationsHelper(SpringConfigurationType.FileConfiguration) + + private val mockStrategies = createComboBox(MockStrategyApi.values()) + private val staticsMocking = JCheckBox("Mock static methods") + + private val springTestType = createComboBox(SpringTestType.values()).also { it.setMinimumAndPreferredWidth(300) } + private val springConfig = createComboBoxWithSeparatorsForSpringConfigs(shortenConfigurationNames()) + private val springProfileNames = JBTextField(23).apply { emptyText.text = SpringProfileNames.defaultItem } + private val timeoutSpinner = - JBIntSpinner( - TimeUnit.MILLISECONDS.toSeconds(UtSettings.utBotGenerationTimeoutInMillis).toInt(), - MINIMUM_TIMEOUT_VALUE_IN_SECONDS, - Int.MAX_VALUE, - MINIMUM_TIMEOUT_VALUE_IN_SECONDS - ) - private val parametrizedTestSources = ComboBox(DefaultComboBoxModel(ParametrizedTestSource.values())) + JBIntSpinner(TimeUnit.MILLISECONDS.toSeconds(model.timeout).toInt(), 1, Int.MAX_VALUE, 1).also { + when(val editor = it.editor) { + is JSpinner.DefaultEditor -> { + when(val formatter = editor.textField.formatter) { + is DefaultFormatter -> {formatter.allowsInvalid = false} + } + } + } + } + private val parametrizedTestSources = JCheckBox("Parameterized tests") - private lateinit var mockExtensionRow: Row private lateinit var panel: DialogPanel @Suppress("UNCHECKED_CAST") private val itemsToHelpTooltip = hashMapOf( - (codegenLanguages as ComboBox) to ContextHelpLabel.create(""), - (testFrameworks as ComboBox) to ContextHelpLabel.create(""), - (mockStrategies as ComboBox) to ContextHelpLabel.create(""), - (staticsMocking as ComboBox) to ContextHelpLabel.create(""), - (parametrizedTestSources as ComboBox) to ContextHelpLabel.create("") + (codegenLanguages as ComboBox) to createHelpLabel(), + (testFrameworks as ComboBox) to createHelpLabel(), + staticsMocking to null, + parametrizedTestSources to null ) + private fun shortenConfigurationNames(): Set>> { + val springBootApplicationClasses = model.getSortedSpringBootApplicationClasses() + val configurationClasses = model.getSortedSpringConfigurationClasses() + val xmlConfigurationFiles = model.getSpringXMLConfigurationFiles() + + val shortenedJavaConfigurationClasses = + javaConfigurationHelper.shortenSpringConfigNames(springBootApplicationClasses + configurationClasses) + + val shortenedSpringXMLConfigurationFiles = + xmlConfigurationHelper.shortenSpringConfigNames(xmlConfigurationFiles) + + return setOf( + null to listOf(NO_SPRING_CONFIGURATION_OPTION), + "@SpringBootApplication" to springBootApplicationClasses.map(shortenedJavaConfigurationClasses::getValue), + "@Configuration" to configurationClasses.map(shortenedJavaConfigurationClasses::getValue), + "XML configuration" to xmlConfigurationFiles.map(shortenedSpringXMLConfigurationFiles::getValue) + ) + } + + private fun shortenConfigurationNameByFullname(fullname: String): String? { + val allShortenConfigurationNames = shortenConfigurationNames().flatMap { it.second } + return allShortenConfigurationNames.firstOrNull { fullname.endsWith(it) } + } + + private fun createComboBox(values: Array) : ComboBox { + val comboBox = object:ComboBox(DefaultComboBoxModel(values)) { + var maxWidth = 0 + // Do not shrink strategy + override fun getPreferredSize(): Dimension { + val size = super.getPreferredSize() + if (size.width > maxWidth) maxWidth = size.width + return size.apply { width = maxWidth } + } + } + return comboBox.also { + it.renderer = CodeGenerationSettingItemRenderer() + } + } + + private fun createComboBoxWithSeparatorsForSpringConfigs( + separatorToValues: Collection>>, + width: Int = 300 + ): ComboBox { + val comboBox = object : ComboBox() { + override fun setSelectedItem(anObject: Any?) { + if (anObject !is ListSeparator) { + super.setSelectedItem(anObject) + } + } + }.apply { + isSwingPopup = false + renderer = MyListCellRenderer() + + setMinimumAndPreferredWidth(width) + separatorToValues.forEach { (separator, values) -> + if (values.isEmpty()) return@forEach + separator?.let { + addItem(ListSeparator(it)) + } + values.forEach(::addItem) + } + } + + return comboBox + } + + private class MyListCellRenderer: ColoredListCellRenderer() { + private val separatorRenderer = SeparatorRenderer() + override fun getListCellRendererComponent( + list: JList?, + value: Any?, + index: Int, + selected: Boolean, + hasFocus: Boolean + ): Component { + return when (value) { + is ListSeparator -> { + separatorRenderer.init(value.text, index < 0) + } + else -> { + super.getListCellRendererComponent(list, value, index, selected, hasFocus) + } + } + } + + override fun customizeCellRenderer( + list: JList, + value: Any?, + index: Int, + selected: Boolean, + hasFocus: Boolean + ) { + append(java.lang.String.valueOf(value)) + } + } + + class SeparatorRenderer : OpaquePanel() { + private val separator = GroupHeaderSeparator(JBUI.insets(3, 8, 1, 0)) + private var emptyPreferredHeight = false + + init { + layout = BorderLayout() + add(separator) + } + + fun init(@NlsContexts.Separator caption: String, emptyPreferredHeight: Boolean) : SeparatorRenderer { + separator.caption = caption + this.emptyPreferredHeight = emptyPreferredHeight + return this + } + + override fun getPreferredSize(): Dimension { + return super.getPreferredSize().apply { + if (emptyPreferredHeight) { + height = 0 + } + } + } + } + + private fun createHelpLabel(commonTooltip: String? = null) = JBLabel(AllIcons.General.ContextHelp).apply { + if (!commonTooltip.isNullOrEmpty()) toolTipText = commonTooltip + } + init { - title = "Generate tests with UtBot" + title = "Generate Tests with UnitTestBot" setResizable(false) + + TestFramework.allItems.forEach { + it.isInstalled = findFrameworkLibrary(model.testModule, it) != null + it.isParametrizedTestsConfigured = findParametrizedTestsLibrary(model.testModule, it) != null + } + MockFramework.allItems.forEach { + it.isInstalled = findFrameworkLibrary(model.testModule, it) != null + } + StaticsMocking.allItems.forEach { + it.isConfigured = staticsMockingConfigured() + } + + + SpringModule.values().forEach { + it.isInstalled = findDependencyInjectionLibrary(model.srcModule, it) != null + } + SpringModule.installedItems.forEach { + it.testFrameworkInstalled = findDependencyInjectionTestLibrary(model.testModule, it) != null + } + + val isUtBotSpringRuntimePresent = this::class.java.classLoader.getResource("lib/utbot-spring-analyzer-shadow.jar") != null + + model.projectType = + // TODO show some warning, when we see Spring project, but don't have `utBotSpringRuntime` + if (isUtBotSpringRuntimePresent && SpringModule.installedItems.isNotEmpty()) ProjectType.Spring + else ProjectType.PureJvm + + // Configure notification urls callbacks + TestsReportNotifier.urlOpeningListener.callbacks[TestReportUrlOpeningListener.mockitoSuffix]?.plusAssign { + configureMockFramework() + } + + TestsReportNotifier.urlOpeningListener.callbacks[TestReportUrlOpeningListener.mockitoInlineSuffix]?.plusAssign { + configureStaticMocking() + } + + TestReportUrlOpeningListener.callbacks[TestReportUrlOpeningListener.eventLogSuffix]?.plusAssign { + with(model.project) { + if (this.isDisposed) return@with + val twm = ToolWindowManager.getInstance(this) + twm.getToolWindow("Event Log")?.activate(null) + } + } + + model.runGeneratedTestsWithCoverage = model.project.service().runGeneratedTestsWithCoverage + init() } @@ -169,95 +405,108 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m @Suppress("UNCHECKED_CAST") override fun createCenterPanel(): JComponent { panel = panel { - row("Test source root:") { - component(testSourceFolderField) + row("Test sources root:") { + cell(testSourceFolderField).align(Align.FILL) } - row("Code generation language:") { - makePanelWithHelpTooltip( - codegenLanguages as ComboBox, - itemsToHelpTooltip[codegenLanguages] - ) + row("Testing framework:") { + cell(testFrameworks) } - row("Test framework:") { - makePanelWithHelpTooltip( - testFrameworks as ComboBox, - itemsToHelpTooltip[testFrameworks] - ) - } - row("Mock strategy:") { - makePanelWithHelpTooltip( - mockStrategies as ComboBox, - itemsToHelpTooltip[mockStrategies] + + if (model.projectType == ProjectType.Spring) { + row("Spring configuration:") { + cell(springConfig) + contextHelp( + "100% Symbolic execution mode.
    " + + "Classes defined in Spring configuration will be used instead " + + "of interfaces and abstract classes.
    " + + "Mocks will be used when necessary." + ) + } + row("Test type:") { + cell(springTestType) + contextHelp( + "Unit tests do not initialize ApplicationContext
    " + + "and do not autowire beans, while integration tests do." + ) + }.enabledIf( + ComboBoxPredicate(springConfig) { isSpringConfigSelected() && !isXmlSpringConfigUsed() } ) - } - mockExtensionRow = row("Mock static:") { - makePanelWithHelpTooltip( - staticsMocking as ComboBox, - itemsToHelpTooltip[staticsMocking] + row("Active profile(s):") { + cell(springProfileNames) + contextHelp( + "One or several comma-separated names.
    " + + "If all names are incorrect, default profile is used" + ) + }.enabledIf( + ComboBoxPredicate(springConfig) { isSpringConfigSelected() } ) } - row("Timeout for class:") { - panelWithHelpTooltip("The execution timeout specifies time for symbolic and concrete analysis") { - component(timeoutSpinner) - component(JBLabel("sec")) - } - } - row("Parametrized test:") { - makePanelWithHelpTooltip( - parametrizedTestSources as ComboBox, - itemsToHelpTooltip[parametrizedTestSources] + row("Mocking strategy:") { + cell(mockStrategies) + contextHelp( + "Mock everything around the target class or the whole package except the system classes.
    " + + "Otherwise, mock nothing. Mockito will be installed, if you don't have one." ) - } + }.enabledIf(ComboBoxPredicate(springConfig) { + model.projectType == ProjectType.PureJvm || !isSpringConfigSelected() + }) + row { cell(staticsMocking)} row { - component(cbSpecifyTestPackage) - } - row("Destination package:") { - component(testPackageField) + cell(parametrizedTestSources) + contextHelp("Parametrization is not supported in some configurations, e.g. if mocks are used.") + }.enabledIf(ComboBoxPredicate(springConfig) { + model.projectType == ProjectType.PureJvm + }) + row("Test generation timeout:") { + cell(BorderLayoutPanel().apply { + addToLeft(timeoutSpinner) + addToRight(JBLabel("seconds per class")) + }) + contextHelp("Set the timeout for all test generation processes per class to complete.") } - row("Generate test methods for:") {} + + row("Generate tests for:") {} row { - scrollPane(membersTable) + cell(JBScrollPane(membersTable)).align(Align.FILL) } } initDefaultValues() setListeners() updateMembersTable() + initValidation() return panel } - private inline fun Cell.panelWithHelpTooltip(tooltipText: String?, crossinline init: Cell.() -> Unit): Cell { - init() - tooltipText?.let { component(ContextHelpLabel.create(it)) } - return this + // TODO:SAT-1571 investigate Android Studio specific sdk issues + fun isSdkSupported() : Boolean = + findSdkVersion(model.srcModule).feature in minSupportedSdkVersion..maxSupportedSdkVersion + || IntelliJApiHelper.isAndroidStudio() + + override fun setOKActionEnabled(isEnabled: Boolean) { + super.setOKActionEnabled(isEnabled) + getButton(okAction)?.apply { + UIUtil.setEnabled(this, isEnabled, true) + okOptionAction?.isEnabled = isEnabled + okOptionAction?.options?.forEach { it.isEnabled = isEnabled } + } } - private fun Row.makePanelWithHelpTooltip( - mainComponent: JComponent, - contextHelpLabel: ContextHelpLabel? - ): CellBuilder = - component(Panel().apply { - add(mainComponent, BorderLayout.LINE_START) - contextHelpLabel?.let { add(it, BorderLayout.LINE_END) } - }) + override fun createTitlePane(): JComponent? = if (isSdkSupported()) null else SdkNotificationPanel(model) - private fun findSdkVersion(): JavaVersion? { - val projectSdk = ModuleRootManager.getInstance(model.srcModule).sdk - return JavaVersion.tryParse(projectSdk?.versionString) - } - - override fun createTitlePane(): JComponent? { - val sdkVersion = findSdkVersion() - //TODO:SAT-1571 investigate Android Studio specific sdk issues - if (sdkVersion?.feature in minSupportedSdkVersion..maxSupportedSdkVersion || AndroidApiHelper.isAndroidStudio()) return null - isOKActionEnabled = false - return SdkNotificationPanel(model, sdkVersion) + override fun createSouthPanel(): JComponent { + val southPanel = super.createSouthPanel() + if (!isSdkSupported()) isOKActionEnabled = false + return southPanel } private fun findTestPackageComboValue(): String { - val packageNames = model.srcClasses.map { it.packageName }.distinct() - return if (packageNames.size == 1) packageNames.first() else SAME_PACKAGE_LABEL + return if (!model.isMultiPackage) { + model.srcClasses.first().packageName + } else { + SAME_PACKAGE_LABEL + } } /** @@ -265,19 +514,20 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m * * Note: this implementation was encouraged by NonModalCommitPromoter. */ - private inner class SdkNotificationPanel( - private val model: GenerateTestsModel, - private val sdkVersion: JavaVersion?, - ) : BorderLayoutPanel() { + private inner class SdkNotificationPanel(private val model: GenerateTestsModel) : + BorderLayoutPanel(scale(UIUtil.DEFAULT_HGAP), 0) { init { border = merge(empty(10), createBorder(JBColor.border(), SideBorder.BOTTOM), true) - addToLeft(JBLabel().apply { + addToCenter(JBLabel().apply { icon = AllIcons.Ide.FatalError - text = if (sdkVersion != null) { - "SDK version $sdkVersion is not supported, use ${JavaSdkVersion.JDK_1_8} or ${JavaSdkVersion.JDK_11}." - } else { - "SDK is not defined" + text = run { + val sdkVersion = findSdkVersionOrNull(this@GenerateTestsDialogWindow.model.srcModule)?.feature + if (sdkVersion != null) { + "SDK version $sdkVersion is not supported, use ${JavaSdkVersion.JDK_1_8.toReadableString()}, ${JavaSdkVersion.JDK_11.toReadableString()} or ${JavaSdkVersion.JDK_17.toReadableString()} instead." + } else { + "SDK is not defined" + } } }) @@ -287,6 +537,8 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m }) } + fun JavaSdkVersion.toReadableString() : String = toString().replace("JDK_", "").replace('_', '.') + override fun getBackground(): Color? = EditorColorsManager.getInstance().globalScheme.getColor(HintUtil.ERROR_COLOR_KEY) ?: super.getBackground() @@ -297,11 +549,11 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m val isEdited = ShowSettingsUtil.getInstance().editConfigurable(model.project, projectStructure) { projectStructure.select(model.srcModule.name, ClasspathEditor.getName(), true) } - val sdkVersion = findSdkVersion() - val sdkFixed = isEdited && sdkVersion?.feature in minSupportedSdkVersion..maxSupportedSdkVersion + val sdkVersion = findSdkVersion(model.srcModule) + val sdkFixed = isEdited && sdkVersion.feature in minSupportedSdkVersion..maxSupportedSdkVersion if (sdkFixed) { this@SdkNotificationPanel.isVisible = false - isOKActionEnabled = true + initValidation() } } } @@ -315,45 +567,30 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m private fun updateMembersTable() { val srcClasses = model.srcClasses - val items: List - if (srcClasses.size == 1) { - items = TestIntegrationUtils.extractClassMethods(srcClasses.single(), false) - updateMethodsTable(items) + val items = if (model.extractMembersFromSrcClasses) { + srcClasses.flatMap { it.extractFirstLevelMembers(false) } } else { - items = srcClasses.map { MemberInfo(it) } - updateClassesTable(items) - } + srcClasses.map { MemberInfo(it) } + }.toMutableList().sortedWith { o1, o2 -> o1.displayName.compareTo(o2.displayName, true) } + checkMembers(items) + membersTable.setMemberInfos(items) if (items.isEmpty()) isOKActionEnabled = false - // fix issue with MemberSelectionTable height, set it directly. + // Fix issue with MemberSelectionTable height, set it directly. // Use row height times methods (12 max) plus one more for header val height = membersTable.rowHeight * (items.size.coerceAtMost(12) + 1) membersTable.preferredScrollableViewportSize = size(-1, height) } - private fun updateMethodsTable(allMethods: List) { - val selectedDisplayNames = model.selectedMethods?.map { it.displayName } ?: emptyList() - val selectedMethods = if (selectedDisplayNames.isEmpty()) - allMethods - else allMethods.filter { it.displayName in selectedDisplayNames } + private fun checkMembers(allMembers: Collection) { + val selectedDisplayNames = model.selectedMembers.map { it.displayName } + val selectedMembers = allMembers.filter { it.displayName in selectedDisplayNames } - if (selectedMethods.isEmpty()) { - checkMembers(allMethods) - } else { - checkMembers(selectedMethods) - } - - membersTable.setMemberInfos(allMethods) - } - - private fun updateClassesTable(srcClasses: List) { - checkMembers(srcClasses) - membersTable.setMemberInfos(srcClasses) + val methodsToCheck = selectedMembers.ifEmpty { allMembers } + methodsToCheck.forEach { it.isChecked = true } } - private fun checkMembers(members: List) = members.forEach { it.isChecked = true } - private fun getTestRoot() : VirtualFile? { model.testSourceRoot?.let { if (it.isDirectory || it is FakeVirtualFile) return it @@ -361,12 +598,14 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m return null } + private fun VirtualFile.toRealFile():VirtualFile = if (this is FakeVirtualFile) this.parent else this + override fun doValidate(): ValidationInfo? { val testRoot = getTestRoot() ?: return ValidationInfo("Test source root is not configured", testSourceFolderField.childComponent) - if (findReadOnlyContentEntry(testRoot) == null) { - return ValidationInfo("Test source root is located out of content entry", testSourceFolderField.childComponent) + if (!model.project.isBuildWithGradle && ModuleUtil.findModuleForFile(testRoot.toRealFile(), model.project) == null) { + return ValidationInfo("Test source root is located out of any module", testSourceFolderField.childComponent) } membersTable.tableHeader?.background = UIUtil.getTableBackground() @@ -378,50 +617,159 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m "Tick any methods to generate tests for", membersTable ) } + if (!isSdkSupported()) { + return ValidationInfo("") + } return null } + inner class OKOptionAction(val okAction : Action) : AbstractAction(model.getActionText()), OptionAction { + init { + putValue(DEFAULT_ACTION, java.lang.Boolean.TRUE) + putValue(FOCUSED_ACTION, java.lang.Boolean.TRUE) + } + private val generateAction = object : AbstractAction(ACTION_GENERATE) { + override fun actionPerformed(e: ActionEvent?) { + model.runGeneratedTestsWithCoverage = false + updateButtonText(e) + } + } + private val generateAndRunAction = object : AbstractAction(ACTION_GENERATE_AND_RUN) { + override fun actionPerformed(e: ActionEvent?) { + model.runGeneratedTestsWithCoverage = true + updateButtonText(e) + } + } + + private fun updateButtonText(e: ActionEvent?) { + with(e?.source as JButton) { + text = this@GenerateTestsDialogWindow.model.getActionText() + this@GenerateTestsDialogWindow.model.project.service().runGeneratedTestsWithCoverage = + this@GenerateTestsDialogWindow.model.runGeneratedTestsWithCoverage + repaint() + } + } + + override fun actionPerformed(e: ActionEvent?) { + okAction.actionPerformed(e) + } + + override fun getOptions(): Array { + if (model.runGeneratedTestsWithCoverage) return arrayOf(generateAndRunAction, generateAction) + return arrayOf(generateAction, generateAndRunAction) + } + + override fun setEnabled(enabled: Boolean) { + super.setEnabled(enabled && isSdkSupported()) + } + } + + private var okOptionAction: OKOptionAction? = null + override fun getOKAction(): Action { + if (okOptionAction == null) { + okOptionAction = OKOptionAction(super.getOKAction()) + } + return okOptionAction!! + } override fun doOKAction() { + if (isSpringConfigSelected() + && springTestType.selectedItem == INTEGRATION_TEST + && Messages.showYesNoDialog( + model.project, + "Generating \"Integration tests\" may lead to corrupting user data or inflicting other harm.\n" + + "Please use a test configuration or profile.", + "Warning", + "Proceed", + "Go Back", + Messages.getWarningIcon() + ) != Messages.YES) { + return; + } + fun now() = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS")) + + logger.info { "Tests generation instantiation phase started at ${now()}" } + model.testPackageName = if (testPackageField.text != SAME_PACKAGE_LABEL) testPackageField.text else "" val selectedMembers = membersTable.selectedMemberInfos - model.srcClasses = selectedMembers - .mapNotNull { it.member as? PsiClass ?: it.member.containingClass } - .toSet() - - val selectedMethods = selectedMembers.filter { it.member is PsiMethod }.toSet() - model.selectedMethods = if (selectedMethods.isEmpty()) null else selectedMethods + if (!model.extractMembersFromSrcClasses) { + model.srcClasses = selectedMembers + .mapNotNull { it.member as? PsiClass } + .toSet() + } + model.selectedMembers = selectedMembers.toSet() model.testFramework = testFrameworks.item model.mockStrategy = mockStrategies.item - model.parametrizedTestSource = parametrizedTestSources.item + model.parametrizedTestSource = + if (parametrizedTestSources.isSelected) ParametrizedTestSource.PARAMETRIZE else ParametrizedTestSource.DO_NOT_PARAMETRIZE model.mockFramework = MOCKITO - model.staticsMocking = staticsMocking.item - model.codegenLanguage = codegenLanguages.item + model.staticsMocking = if (staticsMocking.isSelected) MockitoStaticMocking else NoStaticMocking + try { + timeoutSpinner.commitEdit() + } catch (ignored: ParseException) { + } model.timeout = TimeUnit.SECONDS.toMillis(timeoutSpinner.number.toLong()) + model.testSourceRoot?.apply { model.updateSourceRootHistory(this.toNioPath().toString()) } + + model.springSettings = + when (springConfig.item) { + NO_SPRING_CONFIGURATION_OPTION -> AbsentSpringSettings + else -> { + val shortConfigName = springConfig.item.toString() + val config = + if (isXmlSpringConfigUsed()) { + val absolutePath = xmlConfigurationHelper.restoreFullName(shortConfigName) + SpringConfiguration.XMLConfiguration(absolutePath) + } else { + val classBinaryName = javaConfigurationHelper.restoreFullName(shortConfigName) + + val springBootConfigs = model.getSortedSpringBootApplicationClasses() + if (springBootConfigs.contains(classBinaryName)) { + SpringConfiguration.SpringBootConfiguration( + configBinaryName = classBinaryName, + isDefinitelyUnique = springBootConfigs.size == 1, + ) + } else { + SpringConfiguration.JavaConfiguration(classBinaryName) + } + } + + PresentSpringSettings( + configuration = config, + profiles = parseProfileExpression(springProfileNames.text, SpringProfileNames.defaultItem).toList() + ) + } + } + + model.springTestType = springTestType.item + model.springConfig = springConfig.item.toString() + model.springProfileNames = springProfileNames.text val settings = model.project.service() with(settings) { model.runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour model.hangingTestsTimeout = hangingTestsTimeout + model.useTaintAnalysis = useTaintAnalysis + model.runInspectionAfterTestGeneration = runInspectionAfterTestGeneration model.forceStaticMocking = forceStaticMocking model.chosenClassesToMockAlways = chosenClassesToMockAlways() + model.fuzzingValue = fuzzingValue + model.commentStyle = javaDocCommentStyle + model.summariesGenerationType = state.summariesGenerationType UtSettings.treatOverflowAsError = treatOverflowAsError == TreatOverflowAsError.AS_ERROR + UtSettings.useTaintAnalysis = model.useTaintAnalysis } - // firstly save settings - settings.loadStateFromModel(model) - // then process force static mocking case + // Firstly, save settings + loadStateFromModel(settings, model) + // Then, process force static mocking case model.generateWarningsForStaticMocking = model.staticsMocking is NoStaticMocking if (model.forceStaticMocking == ForceStaticMocking.FORCE) { - // we have to use mock framework to mock statics, no user provided => choose default - if (model.mockFramework == null) { - model.mockFramework = MockFramework.defaultItem - } - // we need mock framework extension to mock statics, no user provided => choose default + // We need mock framework extension to mock statics, no user provided => choose default if (model.staticsMocking is NoStaticMocking) { model.staticsMocking = StaticsMocking.defaultItem } @@ -435,19 +783,14 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m } } catch (e: IncorrectOperationException) { println(e.message) - } - if (cbSpecifyTestPackage.isSelected && testPackageField.text.isEmpty()) { - showTestPackageAbsenceErrorMessage() - return - } - - configureJvmTargetIfRequired() configureTestFrameworkIfRequired() configureMockFrameworkIfRequired() configureStaticMockingIfRequired() + configureParametrizedTestsIfRequired() + logger.info { "Tests generation instantiation phase finished at ${now()}" } super.doOKAction() } @@ -455,8 +798,9 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m * Creates test source root if absent and target packages for tests. */ private fun createTestRootAndPackages(): Boolean { - model.testSourceRoot = createDirectoryIfMissing(model.testSourceRoot) + model.setSourceRootAndFindTestModule(createDirectoryIfMissing(model.testSourceRoot)) val testSourceRoot = model.testSourceRoot ?: return false + if (model.testSourceRoot?.isDirectory != true) return false if (getOrCreateTestRoot(testSourceRoot)) { if (cbSpecifyTestPackage.isSelected) { @@ -501,31 +845,15 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m private fun showTestRootAbsenceErrorMessage() = Messages.showErrorDialog( "Test source root is not configured or is located out of content entry!", - "Generation error" - ) - - private fun showTestPackageAbsenceErrorMessage() = - Messages.showErrorDialog( - "Specify a package to store tests in.", - "Generation error" + "Generation Error" ) - private fun findReadOnlyContentEntry(testSourceRoot: VirtualFile?): ContentEntry? { - if (testSourceRoot == null) return null - if (testSourceRoot is FakeVirtualFile) { - return findReadOnlyContentEntry(testSourceRoot.parent) - } - return ModuleRootManager.getInstance(model.testModule).contentEntries - .filterNot { it.file == null } - .firstOrNull { VfsUtil.isAncestor(it.file!!, testSourceRoot, false) } - } - private fun getOrCreateTestRoot(testSourceRoot: VirtualFile): Boolean { val modifiableModel = ModuleRootManager.getInstance(model.testModule).modifiableModel try { val contentEntry = modifiableModel.contentEntries .filterNot { it.file == null } - .firstOrNull { VfsUtil.isAncestor(it.file!!, testSourceRoot, true) } + .firstOrNull { VfsUtil.isAncestor(it.file!!, testSourceRoot, false) } ?: return false contentEntry.addSourceRootIfAbsent( @@ -544,34 +872,76 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m private fun trimPackageName(name: String?): String = name?.trim() ?: "" + private fun isSpringConfigSelected(): Boolean = springConfig.item != NO_SPRING_CONFIGURATION_OPTION + private fun isXmlSpringConfigUsed(): Boolean = springConfig.item.toString().endsWith(".xml") + private fun initDefaultValues() { testPackageField.isEnabled = false cbSpecifyTestPackage.isEnabled = model.srcClasses.all { cl -> cl.packageName.isNotEmpty() } val settings = model.project.service() - codegenLanguages.item = settings.codegenLanguage - mockStrategies.item = settings.mockStrategy - staticsMocking.item = settings.staticsMocking - parametrizedTestSources.item = settings.parametrizedTestSource - val areMocksSupported = settings.parametrizedTestSource == ParametrizedTestSource.DO_NOT_PARAMETRIZE - mockStrategies.isEnabled = areMocksSupported - staticsMocking.isEnabled = areMocksSupported && mockStrategies.item != MockStrategyApi.NO_MOCKS + when (model.projectType) { + ProjectType.Spring -> { + if (!settings.isSpringHandled) { + settings.isSpringHandled = true + settings.fuzzingValue = + if (settings.fuzzingValue == 0.0) 0.0 + else settings.fuzzingValue.coerceAtLeast(0.3) + } + springConfig.item = settings.springConfig + } + else -> {} + } - //We do not support parameterized tests for JUnit4 - currentFrameworkItem = when (parametrizedTestSources.item) { - ParametrizedTestSource.DO_NOT_PARAMETRIZE -> settings.testFramework - ParametrizedTestSource.PARAMETRIZE -> - if (settings.testFramework == Junit4) TestFramework.parametrizedDefaultItem else settings.testFramework + mockStrategies.item = when (model.projectType) { + ProjectType.Spring -> + if (isSpringConfigSelected()) MockStrategyApi.springDefaultItem else settings.mockStrategy + else -> settings.mockStrategy + } + staticsMocking.isSelected = settings.staticsMocking == MockitoStaticMocking + parametrizedTestSources.isSelected = (settings.parametrizedTestSource == ParametrizedTestSource.PARAMETRIZE + && model.projectType == ProjectType.PureJvm) + + codegenLanguages.item = model.codegenLanguage + + val installedTestFramework = TestFramework.allItems.singleOrNull { it.isInstalled } + val testFramework = JavaTestFrameworkMapper.handleUnknown(settings.testFramework) + currentFrameworkItem = when (parametrizedTestSources.isSelected) { + false -> installedTestFramework ?: testFramework + true -> installedTestFramework + ?: if (testFramework != Junit4) testFramework else TestFramework.parametrizedDefaultItem } - updateTestFrameworksList(settings.parametrizedTestSource) - updateParametrizationVisibility(settings.testFramework) + when (model.projectType) { + ProjectType.PureJvm -> { + updateTestFrameworksList(settings.parametrizedTestSource) + updateParametrizationEnabled() + } + ProjectType.Spring -> { + springProfileNames.text = settings.springProfileNames + springTestType.item = + if (isSpringConfigSelected()) settings.springTestType else SpringTestType.defaultItem + updateMockStrategy(springTestType.item) + updateSpringSettings() + updateTestFrameworksList(springTestType.item) + } + ProjectType.Python, + ProjectType.JavaScript -> { } + } + mockStrategies.isEnabled = !isSpringConfigSelected() + updateStaticMockEnabled() updateMockStrategyList() - updateStaticMockingStrategyList() - itemsToHelpTooltip.forEach { (box, tooltip) -> tooltip.toolTipText = box.item.description } + itemsToHelpTooltip.forEach { (box, tooltip) -> + if (tooltip != null && box is ComboBox<*>) { + val item = box.item + if (item is CodeGenerationSettingItem) { + tooltip.toolTipText = item.description + } + } + } } /** @@ -580,60 +950,113 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m * We need to notify the user about potential problems and to give * him a chance to install missing frameworks into his application. */ - //region configure frameworks - private fun configureTestFrameworkIfRequired() { - val frameworkNotInstalled = !testFrameworks.item.isInstalled - - if (frameworkNotInstalled && createTestFrameworkNotificationDialog() == Messages.YES) { + val testFramework = testFrameworks.item + if (!testFramework.isInstalled) { configureTestFramework() + + // Configuring framework will configure parametrized tests automatically + // TODO: do something more general here + // Note: we can't just update isParametrizedTestsConfigured as before because project.allLibraries() won't be updated immediately + testFramework.isParametrizedTestsConfigured = true } - model.hasTestFrameworkConflict = TestFramework.allItems.count { it.isInstalled } > 1 + model.conflictTriggers[Conflict.TestFrameworkConflict] = TestFramework.allItems.count { it.isInstalled } > 1 + + configureSpringTestFrameworkIfRequired() } private fun configureMockFrameworkIfRequired() { - val frameworkNotInstalled = - mockStrategies.item != MockStrategyApi.NO_MOCKS && !MOCKITO.isInstalled - - if (frameworkNotInstalled && createMockFrameworkNotificationDialog(title) == Messages.YES) { - configureMockFramework(model.project, model.testModule) + if (mockStrategies.item != MockStrategyApi.NO_MOCKS && !MOCKITO.isInstalled) { + configureMockFramework() } } private fun configureStaticMockingIfRequired() { - val frameworkNotConfigured = - staticsMocking.item != NoStaticMocking && !staticsMocking.item.isConfigured - - if (frameworkNotConfigured && createStaticsMockingNotificationDialog() == Messages.YES) { + if (staticsMocking.isSelected && !MockitoStaticMocking.isConfigured) { configureStaticMocking() } } + private fun configureParametrizedTestsIfRequired() { + if (parametrizedTestSources.isSelected && !testFrameworks.item.isParametrizedTestsConfigured) { + configureParametrizedTests() + } + } + + private fun configureSpringTestFrameworkIfRequired() { + if (isSpringConfigSelected()) { + + SpringModule.installedItems + .forEach { configureSpringTestDependency(it) } + } + } + private fun configureTestFramework() { val selectedTestFramework = testFrameworks.item val libraryInProject = - findFrameworkLibrary(model.project, model.testModule, selectedTestFramework, LibrarySearchScope.Project) + findFrameworkLibrary(model.testModule, selectedTestFramework, LibrarySearchScope.Project) val versionInProject = libraryInProject?.libraryName?.parseVersion() + val sdkVersion = findSdkVersion(model.srcModule).feature val libraryDescriptor = when (selectedTestFramework) { Junit4 -> jUnit4LibraryDescriptor(versionInProject) Junit5 -> jUnit5LibraryDescriptor(versionInProject) - TestNg -> testNgLibraryDescriptor(versionInProject) + TestNg -> when (sdkVersion) { + minSupportedSdkVersion -> testNgOldLibraryDescriptor() + else -> testNgNewLibraryDescriptor(versionInProject) + } + else -> throw UnsupportedOperationException() } selectedTestFramework.isInstalled = true - addDependency(model.project, model.testModule, libraryDescriptor) + addDependency(model.testModule, libraryDescriptor) .onError { selectedTestFramework.isInstalled = false } } + private fun configureSpringTestDependency(springModule: SpringModule) { + val frameworkLibrary = + findDependencyInjectionLibrary(model.srcModule, springModule, LibrarySearchScope.Project) + val frameworkTestLibrary = + findDependencyInjectionTestLibrary(model.testModule, springModule, LibrarySearchScope.Project) + + val frameworkVersionInProject = frameworkLibrary?.libraryName?.parseVersion() + ?: error("Trying to install Spring test framework, but Spring framework is not found in module ${model.srcModule.name}") + val frameworkTestVersionInProject = frameworkTestLibrary?.libraryName?.parseVersion() + + if (frameworkTestVersionInProject == null || + !frameworkTestVersionInProject.isCompatibleWith(frameworkVersionInProject) +) { + val libraryDescriptor = when (springModule) { + SPRING_BOOT -> springBootTestLibraryDescriptor(frameworkVersionInProject) + SPRING_BEANS -> springTestLibraryDescriptor(frameworkVersionInProject) + SPRING_SECURITY -> springSecurityLibraryDescriptor(frameworkVersionInProject) + } + + model.preClasspathCollectionPromises += addDependency(model.testModule, libraryDescriptor) + } + + springModule.testFrameworkInstalled = true + } + + private fun configureMockFramework() { + val selectedMockFramework = MOCKITO + + val libraryInProject = + findFrameworkLibrary(model.testModule, selectedMockFramework, LibrarySearchScope.Project) + val versionInProject = libraryInProject?.libraryName?.parseVersion() + + selectedMockFramework.isInstalled = true + addDependency(model.testModule, mockitoCoreLibraryDescriptor(versionInProject)) + .onError { selectedMockFramework.isInstalled = false } + } + private fun configureStaticMocking() { val testResourcesUrl = model.testModule.getOrCreateTestResourcesPath(model.testSourceRoot) configureMockitoResources(testResourcesUrl) - val staticsMockingValue = staticsMocking.item - staticsMockingValue.isConfigured = true + MockitoStaticMocking.isConfigured = true } /** @@ -644,113 +1067,96 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m * for further details. */ private fun configureMockitoResources(testResourcesPath: Path) { - val mockitoExtensionsPath = "$testResourcesPath/$MOCKITO_EXTENSIONS_STORAGE".toPath() + val mockitoExtensionsPath = "$testResourcesPath/$MOCKITO_EXTENSIONS_FOLDER".toPath() val mockitoMockMakerPath = "$mockitoExtensionsPath/$MOCKITO_MOCKMAKER_FILE_NAME".toPath() - if (!testResourcesPath.exists()) Files.createDirectory(testResourcesPath) - if (!mockitoExtensionsPath.exists()) Files.createDirectory(mockitoExtensionsPath) + if (testResourcesPath.notExists()) Files.createDirectories(testResourcesPath) + if (mockitoExtensionsPath.notExists()) Files.createDirectories(mockitoExtensionsPath) - if (!mockitoMockMakerPath.exists()) { + if (mockitoMockMakerPath.notExists()) { Files.createFile(mockitoMockMakerPath) - Files.write(mockitoMockMakerPath, MOCKITO_EXTENSIONS_FILE_CONTENT) + Files.write(mockitoMockMakerPath, listOf(MOCKITO_EXTENSIONS_FILE_CONTENT)) } } - private fun createTestFrameworkNotificationDialog() = Messages.showYesNoDialog( - """Selected test framework ${testFrameworks.item.displayName} is not installed into current module. - |Would you like to install it now?""".trimMargin(), - title, - "Yes", - "No", - Messages.getQuestionIcon(), - ) + private fun configureParametrizedTests() { + // TODO: currently first three declarations are copy-pasted from configureTestFramework(), maybe fix this somehow? + val selectedTestFramework = testFrameworks.item - private fun createStaticsMockingNotificationDialog() = Messages.showYesNoDialog( - """A framework ${MOCKITO.displayName} is not configured to mock static methods. - |Would you like to configure it now?""".trimMargin(), - title, - "Yes", - "No", - Messages.getQuestionIcon(), - ) + val libraryInProject = findFrameworkLibrary(model.testModule, selectedTestFramework, LibrarySearchScope.Project) + val versionInProject = libraryInProject?.libraryName?.parseVersion() - //endregion + val libraryDescriptor: ExternalLibraryDescriptor? = when (selectedTestFramework) { + Junit4 -> error("Parametrized tests are not supported for JUnit 4") + Junit5 -> jUnit5ParametrizedTestsLibraryDescriptor(versionInProject) + TestNg -> null // Parametrized tests come with TestNG by default + else -> throw UnsupportedOperationException() + } + + selectedTestFramework.isParametrizedTestsConfigured = true + libraryDescriptor?.let { + addDependency(model.testModule, it) + .onError { selectedTestFramework.isParametrizedTestsConfigured = false } + } + } /** - * Configures JVM Target if required. - * - * We need to notify the user about potential problems and to give - * him a chance to change JVM targets in his application. + * Adds the dependency for selected framework via [JavaProjectModelModificationService]. * - * Note that now we need it for Kotlin plugin only. + * Note that version restrictions will be applied only if they are present on target machine + * Otherwise latest release version will be installed. */ - private fun configureJvmTargetIfRequired() { - val codegenLanguage = codegenLanguages.item - val parametrization = parametrizedTestSources.item - - if (codegenLanguage == CodegenLanguage.KOTLIN - && parametrization == ParametrizedTestSource.PARAMETRIZE - && createKotlinJvmTargetNotificationDialog() == Messages.YES - ) { - configureKotlinJvmTarget() + private fun addDependency(module: Module, libraryDescriptor: ExternalLibraryDescriptor): Promise { + val promise = JavaProjectModelModificationService + .getInstance(model.project) + //this method returns JetBrains internal Promise that is difficult to deal with, but it is our way + .addDependency(model.testModule, libraryDescriptor, DependencyScope.TEST) + + return promise.thenRun { + module.allLibraries() + .lastOrNull { library -> library.presentableName.contains(libraryDescriptor.id) }?.let { + ModuleRootModificationUtil.updateModel(module) { model -> placeEntryToCorrectPlace(model, it) } + } } } /** - * Checks if JVM target for Kotlin plugin if configured appropriately - * and allows user to configure it via ProjectStructure tab if not. - * - * For Kotlin plugin until version 1.5 default JVM target is 1.6. - * Sometimes (i.e. in parametrized tests) we use some annotations - * and statements that are supported since JVM version 1.8 only. + * Reorders library list to unsure that just added library with proper version is listed prior to old-versioned one */ - private fun configureKotlinJvmTarget() { - val activeKotlinJvmTarget = model.srcModule.kotlinTargetPlatform().description - if (activeKotlinJvmTarget == actualKotlinJvmTarget) { - return + private fun placeEntryToCorrectPlace(model: ModifiableRootModel, addedEntry: LibraryOrderEntry) { + val order = model.orderEntries + val lastEntry = order.last() + if (lastEntry is LibraryOrderEntry && lastEntry.library == addedEntry.library) { + val insertionPoint = order.indexOfFirst { it is ModuleSourceOrderEntry } + 1 + if (insertionPoint > 0) { + System.arraycopy(order, insertionPoint, order, insertionPoint + 1, order.size - 1 - insertionPoint) + order[insertionPoint] = lastEntry + model.rearrangeOrderEntries(order) + } } - - ShowSettingsUtil.getInstance().editConfigurable( - model.project, - ProjectStructureConfigurable.getInstance(Objects.requireNonNull(model.project)) - ) } - private fun createKotlinJvmTargetNotificationDialog() = Messages.showYesNoDialog( - """Your current JVM target is 1.6. Some Kotlin features may not be supported. - |Would you like to update current target to $actualKotlinJvmTarget?""".trimMargin(), - title, - "Yes", - "No", - Messages.getQuestionIcon(), - ProjectNewWindowDoNotAskOption(), - ) - - //language features we use to generate parametrized tests - // (i.e. @JvmStatic attribute or JUnit5 arguments) are supported since JVM target 1.8 - private val actualKotlinJvmTarget = "1.8" + //endregion private fun setListeners() { - itemsToHelpTooltip.forEach { (box, tooltip) -> box.setHelpTooltipTextChanger(tooltip) } + itemsToHelpTooltip.forEach { (box, tooltip) -> if (box is ComboBox<*> && tooltip != null) { + box.setHelpTooltipTextChanger(tooltip) + } } testSourceFolderField.childComponent.addActionListener { event -> with((event.source as JComboBox<*>).selectedItem) { if (this is VirtualFile) { - model.testSourceRoot = this@with - } - else { - model.testSourceRoot = null + model.setSourceRootAndFindTestModule(this@with) + } else { + model.setSourceRootAndFindTestModule(null) } } } - mockStrategies.addActionListener { event -> - val comboBox = event.source as ComboBox<*> - val item = comboBox.item as MockStrategyApi - - staticsMocking.isEnabled = item != MockStrategyApi.NO_MOCKS - if (!staticsMocking.isEnabled) { - staticsMocking.item = NoStaticMocking + mockStrategies.addActionListener { _ -> + updateControlsEnabledStatus() + if (mockStrategies.item == MockStrategyApi.NO_MOCKS) { + staticsMocking.isSelected = false } } @@ -759,119 +1165,184 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m val item = comboBox.item as TestFramework currentFrameworkItem = item - updateParametrizationVisibility(currentFrameworkItem) + + updateControlsEnabledStatus() } - parametrizedTestSources.addActionListener { event -> - val comboBox = event.source as ComboBox<*> - val parametrizedTestSource = comboBox.item as ParametrizedTestSource + codegenLanguages.addActionListener { _ -> + updateControlsEnabledStatus() + } + + parametrizedTestSources.addActionListener { _ -> + val parametrizedTestSource = if (parametrizedTestSources.isSelected) { + ParametrizedTestSource.PARAMETRIZE + } else { + ParametrizedTestSource.DO_NOT_PARAMETRIZE + } - val areMocksSupported = parametrizedTestSource == ParametrizedTestSource.DO_NOT_PARAMETRIZE + updateTestFrameworksList(parametrizedTestSource) + updateControlsEnabledStatus() + } - mockStrategies.isEnabled = areMocksSupported - staticsMocking.isEnabled = areMocksSupported && mockStrategies.item != MockStrategyApi.NO_MOCKS - if (!mockStrategies.isEnabled) { - mockStrategies.item = MockStrategyApi.NO_MOCKS + springConfig.addActionListener { _ -> + if (isSpringConfigSelected()) { + if (isXmlSpringConfigUsed()) { + springTestType.item = SpringTestType.defaultItem + } + + if (springTestType.item == UNIT_TEST) { + mockStrategies.item = MockStrategyApi.springDefaultItem + } + } else { + mockStrategies.item = when (model.projectType) { + ProjectType.Spring -> MockStrategyApi.springDefaultItem + else -> MockStrategyApi.defaultItem + } + + springTestType.item = SpringTestType.defaultItem + + springProfileNames.text = "" } - if (!staticsMocking.isEnabled) { - staticsMocking.item = NoStaticMocking + + if (isSpringConfigSelected() && springTestType.item == UNIT_TEST) { + staticsMocking.isSelected = true } - updateTestFrameworksList(parametrizedTestSource) + updateMockStrategyList() + updateControlsEnabledStatus() + } + + springTestType.addActionListener { event -> + val comboBox = event.source as ComboBox<*> + val item = comboBox.item as SpringTestType + + updateTestFrameworksList(item) + updateMockStrategy(item) + updateMockStrategyList() + updateControlsEnabledStatus() } cbSpecifyTestPackage.addActionListener { val testPackageName = findTestPackageComboValue() + val packageNameIsNeeded = testPackageField.isEnabled || testPackageName != SAME_PACKAGE_LABEL - if (testPackageField.isEnabled) { - testPackageField.isEnabled = false - testPackageField.text = testPackageName - } else { - testPackageField.isEnabled = true - testPackageField.text = if (testPackageName != SAME_PACKAGE_LABEL) testPackageName else "" + testPackageField.text = if (packageNameIsNeeded) testPackageName else "" + testPackageField.isEnabled = !testPackageField.isEnabled + } + } + + private fun updateMockStrategy(springTestType: SpringTestType){ + when (springTestType) { + UNIT_TEST -> { + if(isSpringConfigSelected()){ + mockStrategies.item = MockStrategyApi.springDefaultItem + staticsMocking.isSelected = true + } + } + INTEGRATION_TEST -> { + mockStrategies.item = MockStrategyApi.springIntegrationTestItem + staticsMocking.isSelected = false } } } private lateinit var currentFrameworkItem: TestFramework - //We would like to remove JUnit4 from framework list in parametrized mode private fun updateTestFrameworksList(parametrizedTestSource: ParametrizedTestSource) { - //We do not support parameterized tests for JUnit4 - var enabledTestFrameworks = when (parametrizedTestSource) { + // We do not support parameterized tests for JUnit4 + val enabledTestFrameworks = when (parametrizedTestSource) { ParametrizedTestSource.DO_NOT_PARAMETRIZE -> TestFramework.allItems ParametrizedTestSource.PARAMETRIZE -> TestFramework.allItems.filterNot { it == Junit4 } } - //Will be removed after gradle-intelij-plugin version update upper than 2020.2 - //TestNg will be reverted after https://github.com/UnitTestBot/UTBotJava/issues/309 - if (findSdkVersion()?.let { it.feature < 11 } == true) { - enabledTestFrameworks = enabledTestFrameworks.filterNot { it == TestNg } - } - var defaultItem = when (parametrizedTestSource) { ParametrizedTestSource.DO_NOT_PARAMETRIZE -> TestFramework.defaultItem ParametrizedTestSource.PARAMETRIZE -> TestFramework.parametrizedDefaultItem } - enabledTestFrameworks.forEach { - it.isInstalled = findFrameworkLibrary(model.project, model.testModule, it) != null - if (it.isInstalled && !defaultItem.isInstalled) defaultItem = it + enabledTestFrameworks.forEach { if (it.isInstalled && !defaultItem.isInstalled) defaultItem = it } + + updateTestFrameworksList(enabledTestFrameworks, defaultItem) + } + + private fun updateTestFrameworksList(springTestType: SpringTestType) { + // We do not support Spring integration tests for TestNg + val enabledTestFrameworks = when (springTestType) { + UNIT_TEST -> TestFramework.allItems + INTEGRATION_TEST -> TestFramework.allItems.filterNot { it == TestNg } } + updateTestFrameworksList(enabledTestFrameworks) + } + + private fun updateTestFrameworksList( + enabledTestFrameworks: List, + defaultItem: TestFramework = TestFramework.defaultItem, + ) { testFrameworks.model = DefaultComboBoxModel(enabledTestFrameworks.toTypedArray()) - testFrameworks.item = if (currentFrameworkItem in enabledTestFrameworks && currentFrameworkItem.isInstalled) currentFrameworkItem else defaultItem - testFrameworks.renderer = object : ColoredListCellRenderer() { - override fun customizeCellRenderer( - list: JList, value: TestFramework?, - index: Int, selected: Boolean, hasFocus: Boolean - ) { - this.append(value.toString(), SimpleTextAttributes.REGULAR_ATTRIBUTES) - if (value == null || !value.isInstalled) { - this.append(WILL_BE_INSTALLED_LABEL, SimpleTextAttributes.ERROR_ATTRIBUTES) - } - } - } + testFrameworks.item = if (currentFrameworkItem in enabledTestFrameworks) currentFrameworkItem else defaultItem + testFrameworks.renderer = createTestFrameworksRenderer(WILL_BE_INSTALLED_LABEL) currentFrameworkItem = testFrameworks.item } - //We would like to disable parametrization options for JUnit4 - private fun updateParametrizationVisibility(testFramework: TestFramework) { - when (testFramework) { - Junit4 -> parametrizedTestSources.isEnabled = false - Junit5, - TestNg -> parametrizedTestSources.isEnabled = true + private fun updateParametrizationEnabled() { + val languageIsSupported = codegenLanguages.item == CodegenLanguage.JAVA + val frameworkIsSupported = currentFrameworkItem == Junit5 + || currentFrameworkItem == TestNg && findSdkVersion(model.srcModule).feature > minSupportedSdkVersion + val mockStrategyIsSupported = mockStrategies.item == MockStrategyApi.NO_MOCKS + + // We do not support PUT in Spring projects + val isSupportedProjectType = model.projectType == ProjectType.PureJvm + parametrizedTestSources.isEnabled = + isSupportedProjectType && languageIsSupported && frameworkIsSupported && mockStrategyIsSupported + + if (!parametrizedTestSources.isEnabled) { + parametrizedTestSources.isSelected = false } } - private fun updateMockStrategyList() { - MOCKITO.isInstalled = - findFrameworkLibrary(model.project, model.testModule, MOCKITO) != null + private fun updateStaticMockEnabled() { + val mockStrategyIsSupported = mockStrategies.item != MockStrategyApi.NO_MOCKS + staticsMocking.isEnabled = mockStrategyIsSupported && !isSpringConfigSelected() + } + private fun updateMockStrategyList() { mockStrategies.renderer = object : ColoredListCellRenderer() { override fun customizeCellRenderer( - list: JList, value: MockStrategyApi?, + list: JList, value: MockStrategyApi, index: Int, selected: Boolean, hasFocus: Boolean ) { - this.append(value.toString(), SimpleTextAttributes.REGULAR_ATTRIBUTES) - if (value != MockStrategyApi.NO_MOCKS && !MOCKITO.isInstalled) { - this.append(WILL_BE_INSTALLED_LABEL, SimpleTextAttributes.ERROR_ATTRIBUTES) + if(mockStrategies.item == MockStrategyApi.springDefaultItem && isSpringConfigSelected()) { + this.append("Mock using Spring configuration", SimpleTextAttributes.REGULAR_ATTRIBUTES) + } + else{ + this.append(value.displayName, SimpleTextAttributes.REGULAR_ATTRIBUTES) + if (value != MockStrategyApi.NO_MOCKS && !MOCKITO.isInstalled) { + this.append(WILL_BE_INSTALLED_LABEL, SimpleTextAttributes.ERROR_ATTRIBUTES) + } } } } } - private fun updateStaticMockingStrategyList() { - val staticsMockingConfigured = staticsMockingConfigured() - StaticsMocking.allItems.forEach { it.isConfigured = staticsMockingConfigured } - staticsMocking.renderer = object : ColoredListCellRenderer() { + private fun updateSpringSettings() { + // We check for > 1 because there is already extra-dummy NO_SPRING_CONFIGURATION_OPTION option + springConfig.isEnabled = model.projectType == ProjectType.Spring && springConfig.itemCount > 1 + + springTestType.renderer = object : ColoredListCellRenderer() { override fun customizeCellRenderer( - list: JList, value: StaticsMocking?, + list: JList, value: SpringTestType, index: Int, selected: Boolean, hasFocus: Boolean ) { - this.append(value.toString(), SimpleTextAttributes.REGULAR_ATTRIBUTES) - if (value != NoStaticMocking && value?.isConfigured != true) { - this.append(WILL_BE_CONFIGURED_LABEL, SimpleTextAttributes.ERROR_ATTRIBUTES) + this.append(value.displayName, SimpleTextAttributes.REGULAR_ATTRIBUTES) + if (isSpringConfigSelected()) { + SpringModule.installedItems + // only first missing test framework is shown to avoid overflowing ComboBox + .firstOrNull { !it.testFrameworkInstalled } + ?.let { diFramework -> + val additionalText = " (${diFramework.testFrameworkDisplayName} will be installed)" + this.append(additionalText, SimpleTextAttributes.ERROR_ATTRIBUTES) + } } } } @@ -894,21 +1365,57 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m .map { f -> Paths.get(urlToPath(f.url)) } } - return entriesPaths.all { path -> - if (Files.exists(path)) { - val fileNames = Files.walk(path).map { it.fileName }.toList() - fileNames.any { it.toString() == MOCKITO_MOCKMAKER_FILE_NAME } - } else { - false + return entriesPaths.all { entryPath -> + if (!Files.exists(entryPath)) return false + + val mockMakerPath = "$entryPath/$MOCKITO_EXTENSIONS_FOLDER/$MOCKITO_MOCKMAKER_FILE_NAME".toPath() + if (!Files.exists(mockMakerPath)) return false + + try { + val fileLines = Files.readAllLines(mockMakerPath) + fileLines.singleOrNull() == MOCKITO_EXTENSIONS_FILE_CONTENT + } catch (e: java.io.IOException) { + return false + } + } + } + + private fun updateControlsEnabledStatus() { + mockStrategies.isEnabled = true + + updateParametrizationEnabled() + updateStaticMockEnabled() + + if (model.projectType == ProjectType.Spring) { + updateSpringControlsEnabledStatus() + } + } + + private fun updateSpringControlsEnabledStatus() { + // Parametrized tests are not supported for Spring + parametrizedTestSources.isEnabled = false + + if (isSpringConfigSelected()) { + mockStrategies.isEnabled = false + springProfileNames.isEnabled = true + springTestType.isEnabled = !isXmlSpringConfigUsed() + } else { + springProfileNames.isEnabled = false + springTestType.isEnabled = false } } } -private fun ComboBox.setHelpTooltipTextChanger(helpLabel: ContextHelpLabel) { +fun GenerateTestsModel.getActionText() : String = + if (this.runGeneratedTestsWithCoverage) ACTION_GENERATE_AND_RUN else ACTION_GENERATE + +private fun ComboBox<*>.setHelpTooltipTextChanger(helpLabel: JBLabel) { addActionListener { event -> val comboBox = event.source as ComboBox<*> - val item = comboBox.item as CodeGenerationSettingItem - helpLabel.toolTipText = item.description + val item = comboBox.item + if (item is CodeGenerationSettingItem) { + helpLabel.toolTipText = item.description + } } -} \ No newline at end of file +} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsModel.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsModel.kt deleted file mode 100644 index d2fcf6436c..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsModel.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.utbot.intellij.plugin.ui - -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.HangingTestsTimeout -import org.utbot.framework.codegen.ParametrizedTestSource -import org.utbot.framework.codegen.RuntimeExceptionTestsBehaviour -import org.utbot.framework.codegen.StaticsMocking -import org.utbot.framework.codegen.TestFramework -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.MockStrategyApi -import com.intellij.openapi.module.Module -import com.intellij.openapi.project.Project -import com.intellij.openapi.projectRoots.JavaSdkVersion -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiClass -import com.intellij.refactoring.util.classMembers.MemberInfo -import org.jetbrains.kotlin.idea.core.getPackage - -data class GenerateTestsModel( - val project: Project, - val srcModule: Module, - val testModule: Module, - val jdkVersion: JavaSdkVersion, - var srcClasses: Set, - var selectedMethods: Set?, - var timeout:Long, - var generateWarningsForStaticMocking: Boolean = false, - var forceMockHappened: Boolean = false, - var hasTestFrameworkConflict: Boolean = false, -) { - var testSourceRoot: VirtualFile? = null - var testPackageName: String? = null - lateinit var testFramework: TestFramework - lateinit var mockStrategy: MockStrategyApi - var mockFramework: MockFramework? = null - lateinit var staticsMocking: StaticsMocking - lateinit var parametrizedTestSource: ParametrizedTestSource - lateinit var codegenLanguage: CodegenLanguage - lateinit var runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour - lateinit var hangingTestsTimeout: HangingTestsTimeout - lateinit var forceStaticMocking: ForceStaticMocking - lateinit var chosenClassesToMockAlways: Set -} - -val PsiClass.packageName: String get() = this.containingFile.containingDirectory.getPackage()?.qualifiedName ?: "" \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt deleted file mode 100644 index ffd0af96d9..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt +++ /dev/null @@ -1,182 +0,0 @@ -package org.utbot.intellij.plugin.ui - -import com.intellij.ide.util.PropertiesComponent -import com.intellij.notification.Notification -import com.intellij.notification.NotificationDisplayType -import com.intellij.notification.NotificationGroup -import com.intellij.notification.NotificationListener -import com.intellij.notification.NotificationType -import com.intellij.openapi.actionSystem.ActionManager -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.keymap.KeymapUtil -import com.intellij.openapi.module.Module -import com.intellij.openapi.project.Project -import com.intellij.openapi.startup.StartupActivity -import com.intellij.openapi.ui.Messages -import com.intellij.openapi.ui.popup.Balloon -import com.intellij.openapi.wm.WindowManager -import com.intellij.ui.GotItMessage -import com.intellij.ui.awt.RelativePoint -import com.intellij.util.ui.JBFont -import java.awt.Point -import javax.swing.event.HyperlinkEvent - -abstract class Notifier { - protected abstract val notificationType: NotificationType - protected abstract val displayId: String - protected abstract fun content(project: Project?, module: Module?, info: String): String - - open fun notify(info: String, project: Project? = null, module: Module? = null) { - notificationGroup - .createNotification(content(project, module, info), notificationType) - .notify(project) - } - - protected val notificationGroup: NotificationGroup - get() = NotificationGroup(displayId, NotificationDisplayType.BALLOON) -} - -abstract class WarningNotifier : Notifier() { - override val notificationType: NotificationType = NotificationType.WARNING - final override fun notify(info: String, project: Project?, module: Module?) { - super.notify(info, project, module) - } -} - -abstract class ErrorNotifier : Notifier() { - final override val notificationType: NotificationType = NotificationType.ERROR - - final override fun notify(info: String, project: Project?, module: Module?) { - super.notify(info, project, module) - error(content(project, module, info)) - } -} - -object CommonErrorNotifier : ErrorNotifier() { - override val displayId: String = "UTBot plugin errors" - override fun content(project: Project?, module: Module?, info: String): String = info -} - -object UnsupportedJdkNotifier : ErrorNotifier() { - override val displayId: String = "Unsupported JDK" - override fun content(project: Project?, module: Module?, info: String): String = - "JDK versions older than 8 are not supported. This project's JDK version is $info" -} - -object MissingLibrariesNotifier : WarningNotifier() { - override val displayId: String = "Missing libraries" - override fun content(project: Project?, module: Module?, info: String): String = - "Library $info missing on the test classpath of module ${module?.name}" -} - -@Suppress("unused") -object UnsupportedTestFrameworkNotifier : ErrorNotifier() { - override val displayId: String = "Unsupported test framework" - override fun content(project: Project?, module: Module?, info: String): String = - "Test framework $info is not supported yet" -} - -abstract class UrlNotifier : Notifier() { - - protected abstract val titleText: String - protected abstract val urlOpeningListener: NotificationListener - - override fun notify(info: String, project: Project?, module: Module?) { - notificationGroup - .createNotification( - titleText, - content(project, module, info), - notificationType, - urlOpeningListener, - ).notify(project) - } -} - -abstract class InformationUrlNotifier : UrlNotifier() { - override val notificationType: NotificationType = NotificationType.INFORMATION -} - -object SarifReportNotifier : InformationUrlNotifier() { - - override val displayId: String = "SARIF report" - - override val titleText: String = "" // no title - - override val urlOpeningListener: NotificationListener = NotificationListener.UrlOpeningListener(false) - - override fun content(project: Project?, module: Module?, info: String): String = info -} - -object TestsReportNotifier : InformationUrlNotifier() { - override val displayId: String = "Generated unit tests report" - - override val titleText: String = "Report of the unit tests generation via UtBot" - - override val urlOpeningListener: TestReportUrlOpeningListener = TestReportUrlOpeningListener() - - override fun content(project: Project?, module: Module?, info: String): String { - // Remember last project and module to use them for configurations. - urlOpeningListener.project = project - urlOpeningListener.module = module - return info - } -} - -/** - * Listener that handles URLs starting with [prefix], like "#utbot/configure-mockito". - * - * Current implementation - */ -class TestReportUrlOpeningListener: NotificationListener.Adapter() { - companion object { - const val prefix = "#utbot/" - const val mockitoSuffix = "configure-mockito" - } - private val defaultListener = NotificationListener.UrlOpeningListener(false) - - // Last project and module to be able to use them when activated for configuration tasks. - var project: Project? = null - var module: Module? = null - - override fun hyperlinkActivated(notification: Notification, e: HyperlinkEvent) { - val description = e.description - if (description.startsWith(prefix)) { - handleDescription(description.removePrefix(prefix)) - } else { - return defaultListener.hyperlinkUpdate(notification, e) - } - } - - private fun handleDescription(descriptionSuffix: String) { - when { - descriptionSuffix.startsWith(mockitoSuffix) -> { - project?.let { project -> module?.let { module -> - if (createMockFrameworkNotificationDialog("Configure mock framework") == Messages.YES) { - configureMockFramework(project, module) - } - } ?: error("Could not configure mock framework: module is null for project $project") - } ?: error("Could not configure mock framework: project is null") - } - else -> error("No such command with #utbot prefix: $descriptionSuffix") - } - } -} - -object GotItTooltipActivity : StartupActivity { - private const val KEY = "UTBot.GotItMessageWasShown" - override fun runActivity(project: Project) { - if (PropertiesComponent.getInstance().isTrueValue(KEY)) return - ApplicationManager.getApplication().invokeLater { - val shortcut = ActionManager.getInstance() - .getKeyboardShortcut("org.utbot.intellij.plugin.ui.actions.GenerateTestsAction")?:return@invokeLater - val shortcutText = KeymapUtil.getShortcutText(shortcut) - val message = GotItMessage.createMessage("UTBot is ready!", - "

    " + - "You can get test coverage for methods, Java classes,
    and even for whole source roots
    with $shortcutText
    ") - message.setCallback { PropertiesComponent.getInstance().setValue(KEY, true) } - WindowManager.getInstance().getFrame(project)?.rootPane?.let { - message.show(RelativePoint(it, Point(it.width, it.height)), Balloon.Position.above) - } - } - } -} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/UtTestsDialogProcessor.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/UtTestsDialogProcessor.kt deleted file mode 100644 index 02137c6824..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/UtTestsDialogProcessor.kt +++ /dev/null @@ -1,270 +0,0 @@ -package org.utbot.intellij.plugin.ui - -import org.utbot.framework.JdkPathService -import org.utbot.framework.UtSettings -import org.utbot.framework.codegen.ParametrizedTestSource -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.withSubstitutionCondition -import org.utbot.framework.plugin.api.util.withUtContext -import org.utbot.intellij.plugin.generator.CodeGenerator -import org.utbot.intellij.plugin.generator.TestGenerator.generateTests -import org.utbot.intellij.plugin.generator.findMethodsInClassMatchingSelected -import org.utbot.intellij.plugin.ui.utils.jdkVersion -import org.utbot.intellij.plugin.ui.utils.testModule -import org.utbot.intellij.plugin.util.AndroidApiHelper -import org.utbot.intellij.plugin.util.PluginJdkPathProvider -import com.intellij.compiler.impl.CompositeScope -import com.intellij.compiler.impl.OneProjectItemCompileScope -import com.intellij.openapi.application.PathManager -import com.intellij.openapi.application.ReadAction -import com.intellij.openapi.application.invokeLater -import com.intellij.openapi.compiler.CompileContext -import com.intellij.openapi.compiler.CompilerManager -import com.intellij.openapi.compiler.CompilerPaths -import com.intellij.openapi.module.Module -import com.intellij.openapi.progress.ProgressIndicator -import com.intellij.openapi.progress.Task -import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.OrderEnumerator -import com.intellij.openapi.util.text.StringUtil -import com.intellij.psi.PsiClass -import com.intellij.refactoring.util.classMembers.MemberInfo -import com.intellij.testIntegration.TestIntegrationUtils -import com.intellij.util.concurrency.AppExecutorUtil -import java.io.File -import java.net.URLClassLoader -import java.nio.file.Path -import java.nio.file.Paths -import java.util.concurrent.TimeUnit -import mu.KotlinLogging -import org.jetbrains.kotlin.idea.util.module -import org.utbot.engine.util.mockListeners.ForceMockListener -import org.utbot.intellij.plugin.error.showErrorDialogLater - -object UtTestsDialogProcessor { - - private val logger = KotlinLogging.logger {} - - fun createDialogAndGenerateTests( - project: Project, - srcClasses: Set, - focusedMethod: MemberInfo?, - ) { - createDialog(project, srcClasses, focusedMethod)?.let { - if (it.showAndGet()) createTests(project, it.model) - } - } - - private fun createDialog( - project: Project, - srcClasses: Set, - focusedMethod: MemberInfo?, - ): GenerateTestsDialogWindow? { - val srcModule = findSrcModule(srcClasses) - val testModule = srcModule.testModule(project) - - JdkPathService.jdkPathProvider = PluginJdkPathProvider(project, testModule) - val jdkVersion = try { - testModule.jdkVersion() - } catch (e: IllegalStateException) { - // Just ignore it here, notification will be shown in - // org.utbot.intellij.plugin.ui.utils.ModuleUtilsKt.jdkVersionBy - return null - } - - return GenerateTestsDialogWindow( - GenerateTestsModel( - project, - srcModule, - testModule, - jdkVersion, - srcClasses, - if (focusedMethod != null) setOf(focusedMethod) else null, - UtSettings.utBotGenerationTimeoutInMillis, - ) - ) - } - - private fun createTests(project: Project, model: GenerateTestsModel) { - CompilerManager.getInstance(project) - .make( - // Compile only chosen classes and their dependencies before generation. - CompositeScope( - model.srcClasses.map{ OneProjectItemCompileScope(project, it.containingFile.virtualFile) }.toTypedArray() - ) - ) { aborted: Boolean, errors: Int, _: Int, _: CompileContext -> - if (!aborted && errors == 0) { - (object : Task.Backgroundable(project, "Generate tests") { - - override fun run(indicator: ProgressIndicator) { - val startTime = System.currentTimeMillis() - val secondsTimeout = TimeUnit.MILLISECONDS.toSeconds(model.timeout) - val totalTimeout = model.timeout * model.srcClasses.size - - indicator.isIndeterminate = false - indicator.text = "Generate tests: read classes" - - val timerHandler = AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay({ - indicator.fraction = (System.currentTimeMillis() - startTime).toDouble() / totalTimeout - }, 0, 500, TimeUnit.MILLISECONDS) - - val buildPaths = ReadAction - .nonBlocking { findPaths(model.srcClasses) } - .executeSynchronously() - ?: return - - val (buildDir, classpath, classpathList, pluginJarsPath) = buildPaths - val classLoader = urlClassLoader(listOf(buildDir) + classpathList) - val context = UtContext(classLoader) - - val testCasesByClass = mutableMapOf>() - var processedClasses = 0 - val totalClasses = model.srcClasses.size - - for (srcClass in model.srcClasses) { - val methods = ReadAction.nonBlocking>> { - val clazz = classLoader.loadClass(srcClass.qualifiedName).kotlin - val srcMethods = model.selectedMethods?.toList() ?: - TestIntegrationUtils.extractClassMethods(srcClass, false) - findMethodsInClassMatchingSelected(clazz, srcMethods) - }.executeSynchronously() - - val className = srcClass.name - if (methods.isEmpty()) { - logger.error { "No methods matching selected found in class $className." } - continue - } - - indicator.text = "Generate test cases for class $className" - if (totalClasses > 1) { - indicator.fraction = indicator.fraction.coerceAtLeast(0.9 * processedClasses / totalClasses) - } - - //we should not substitute statics for parametrized tests - val shouldSubstituteStatics = - model.parametrizedTestSource != ParametrizedTestSource.PARAMETRIZE - // set timeout for concrete execution and for generated tests - UtSettings.concreteExecutionTimeoutInChildProcess = model.hangingTestsTimeout.timeoutMs - - val searchDirectory = ReadAction - .nonBlocking { project.basePath?.let { Paths.get(it) } ?: Paths.get(srcClass.containingFile.virtualFile.parent.path) } - .executeSynchronously() - - withSubstitutionCondition(shouldSubstituteStatics) { - val mockFrameworkInstalled = model.mockFramework?.isInstalled ?: true - val codeGenerator = CodeGenerator( - searchDirectory = searchDirectory, - mockStrategy = model.mockStrategy, - project = model.project, - buildDir = buildDir, - classpath = classpath, - pluginJarsPath = pluginJarsPath.joinToString(separator = File.pathSeparator), - chosenClassesToMockAlways = model.chosenClassesToMockAlways - ) { indicator.isCanceled } - - val forceMockListener = if (!mockFrameworkInstalled) { - ForceMockListener().apply { - codeGenerator.generator.configureEngine = { engine -> engine.attachMockListener(this) } - } - } else { - null - } - - val notEmptyCases = withUtContext(context) { - codeGenerator - .generateForSeveralMethods(methods, model.timeout) - .filterNot { it.executions.isEmpty() && it.errors.isEmpty() } - } - - if (notEmptyCases.isEmpty()) { - showErrorDialogLater( - model.project, - errorMessage(className, secondsTimeout), - title = "Failed to generate unit tests for class $className" - ) - } else { - testCasesByClass[srcClass] = notEmptyCases - } - - forceMockListener?.run { - model.forceMockHappened = forceMockHappened - } - - timerHandler.cancel(true) - } - processedClasses++ - } - - indicator.fraction = indicator.fraction.coerceAtLeast(0.9) - indicator.text = "Generate code for tests" - // Commented out to generate tests for collected executions even if action was canceled. - // indicator.checkCanceled() - - invokeLater { - withUtContext(context) { - generateTests(model, testCasesByClass) - } - } - } - }).queue() - } - } - } - - private fun errorMessage(className: String?, timeout: Long) = buildString { - append("UtBot failed to generate any test cases for class $className.") - append("You could try to increase current timeout $timeout sec for generating tests in generation dialog.") - } -} - - -internal fun urlClassLoader(classpath: List) = - URLClassLoader(classpath.map { File(it).toURI().toURL() }.toTypedArray()) - -fun findSrcModule(srcClasses: Set): Module { - val srcModules = srcClasses.mapNotNull { it.module }.distinct() - return when (srcModules.size) { - 0 -> error("Module for source classes not found") - 1 -> srcModules.first() - else -> error("Can not generate tests for classes from different modules") - } -} - -internal fun findPaths(srcClasses: Set): BuildPaths? { - val srcModule = findSrcModule(srcClasses) - val buildDir = CompilerPaths.getModuleOutputPath(srcModule, false) ?: return null - val pathsList = OrderEnumerator.orderEntries(srcModule).recursively().pathsList - - val (classpath, classpathList) = if (AndroidApiHelper.isAndroidStudio()) { - // Add $JAVA_HOME/jre/lib/rt.jar to path. - // This allows Soot to analyze real java instead of stub version in Android SDK on local machine. - pathsList.add( - System.getenv("JAVA_HOME") + File.separator + Paths.get("jre", "lib", "rt.jar") - ) - - // Filter out manifests from classpath. - val filterPredicate = { it: String -> - !it.contains("manifest", ignoreCase = true) - } - val classpathList = pathsList.pathList.filter(filterPredicate) - val classpath = StringUtil.join(classpathList, File.pathSeparator) - Pair(classpath, classpathList) - } else { - val classpath = pathsList.pathsString - val classpathList = pathsList.pathList - Pair(classpath, classpathList) - } - val pluginJarsPath = Paths.get(PathManager.getPluginsPath(), "utbot-intellij", "lib").toFile().listFiles() - ?: error("Can't find plugin folder.") - return BuildPaths(buildDir, classpath, classpathList, pluginJarsPath.map { it.path }) -} - -data class BuildPaths( - val buildDir: String, - val classpath: String, - val classpathList: List, - val pluginJarsPath: List - // ^ TODO: Now we collect ALL dependent libs and pass them to the child process. Most of them are redundant. -) \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt deleted file mode 100644 index 6b079e094f..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/actions/GenerateTestsAction.kt +++ /dev/null @@ -1,139 +0,0 @@ -package org.utbot.intellij.plugin.ui.actions - -import org.utbot.intellij.plugin.ui.UtTestsDialogProcessor -import org.utbot.intellij.plugin.ui.utils.KotlinPsiElementHandler -import org.utbot.intellij.plugin.ui.utils.PsiElementHandler -import com.intellij.openapi.actionSystem.AnAction -import com.intellij.openapi.actionSystem.AnActionEvent -import com.intellij.openapi.actionSystem.CommonDataKeys -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.module.ModuleUtil -import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.ModuleRootManager -import com.intellij.openapi.roots.ProjectFileIndex -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.* -import com.intellij.psi.util.PsiTreeUtil -import com.intellij.refactoring.util.classMembers.MemberInfo -import com.intellij.testIntegration.TestIntegrationUtils -import org.jetbrains.kotlin.idea.core.getPackage -import org.jetbrains.kotlin.idea.core.util.toPsiDirectory -import org.jetbrains.kotlin.idea.core.util.toPsiFile -import org.jetbrains.kotlin.psi.KtClass -import java.util.* - -class GenerateTestsAction : AnAction() { - override fun actionPerformed(e: AnActionEvent) { - val project = e.project ?: return - val psiTargets = getPsiTargets(e) ?: return - UtTestsDialogProcessor.createDialogAndGenerateTests(project, psiTargets.first, psiTargets.second) - } - - override fun update(e: AnActionEvent) { - e.presentation.isEnabled = getPsiTargets(e) != null - } - - private fun getPsiTargets(e: AnActionEvent): Pair, MemberInfo?>? { - val project = e.project ?: return null - val editor = e.getData(CommonDataKeys.EDITOR) - if (editor != null) { - //The action is being called from editor - val file = e.getData(CommonDataKeys.PSI_FILE) ?: return null - val element = findPsiElement(file, editor) ?: return null - - val psiElementHandler = PsiElementHandler.makePsiElementHandler(file) - - if (psiElementHandler.isCreateTestActionAvailable(element)) { - val srcClass = psiElementHandler.containingClass(element) ?: return null - val srcMethods = TestIntegrationUtils.extractClassMethods(srcClass, false) - val focusedMethod = focusedMethodOrNull(element, srcMethods, psiElementHandler) - return Pair(setOf(srcClass), focusedMethod) - } - } else { - // The action is being called from 'Project' tool window - val srcClasses = mutableSetOf() - e.getData(CommonDataKeys.PSI_ELEMENT)?.let { - srcClasses += getAllClasses(it) - } - e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY)?.let { - srcClasses += getAllClasses(project, it) - } - srcClasses.removeIf { it.isInterface } - var commonSourceRoot = null as VirtualFile? - for (srcClass in srcClasses) { - if (commonSourceRoot == null) { - commonSourceRoot = srcClass.getSourceRoot()?: return null - } else if (commonSourceRoot != srcClass.getSourceRoot()) return null - } - if (commonSourceRoot == null) return null - val module = ModuleUtil.findModuleForFile(commonSourceRoot, project)?: return null - - if (!Arrays.stream(ModuleRootManager.getInstance(module).contentEntries) - .flatMap { entry -> Arrays.stream(entry.sourceFolders) } - .filter { folder -> !folder.rootType.isForTests && folder.file == commonSourceRoot} - .findAny().isPresent ) return null - - return Pair(srcClasses, null) - } - return null - } - - private fun PsiElement?.getSourceRoot() : VirtualFile? { - val project = this?.project?: return null - val virtualFile = this.containingFile?.originalFile?.virtualFile?: return null - return ProjectFileIndex.getInstance(project).getSourceRootForFile(virtualFile) - } - - private fun findPsiElement(file: PsiFile, editor: Editor): PsiElement? { - val offset = editor.caretModel.offset - var element = file.findElementAt(offset) - if (element == null && offset == file.textLength) { - element = file.findElementAt(offset - 1) - } - - return element - } - - private fun focusedMethodOrNull(element: PsiElement, methods: List, psiElementHandler: PsiElementHandler): MemberInfo? { - // getParentOfType might return element which does not correspond to the standard Psi hierarchy. - // Thus, make transition to the Psi if it is required. - val currentMethod = PsiTreeUtil.getParentOfType(element, psiElementHandler.methodClass) - ?.let { psiElementHandler.toPsi(it, PsiMethod::class.java) } - - return methods.singleOrNull { it.member == currentMethod } - } - - private fun getAllClasses(psiElement: PsiElement): Set { - return when (psiElement) { - is KtClass -> setOf(KotlinPsiElementHandler().toPsi(psiElement, PsiClass::class.java)) - is PsiClass -> setOf(psiElement) - is PsiDirectory -> getAllClasses(psiElement) - else -> emptySet() - } - } - - private fun getAllClasses(directory: PsiDirectory): Set { - val allClasses = directory.files.flatMap { getClassesFromFile(it) }.toMutableSet() - for (subDir in directory.subdirectories) allClasses += getAllClasses(subDir) - return allClasses - } - private fun getAllClasses(project: Project, virtualFiles: Array): Set { - val psiFiles = virtualFiles.mapNotNull { it.toPsiFile(project) } - val psiDirectories = virtualFiles.mapNotNull { it.toPsiDirectory(project) } - val dirsArePackages = psiDirectories.all { it.getPackage()?.qualifiedName?.isNotEmpty() == true } - - if (!dirsArePackages) { - return emptySet() - } - val allClasses = psiFiles.flatMap { getClassesFromFile(it) }.toMutableSet() - for (psiDir in psiDirectories) allClasses += getAllClasses(psiDir) - - return allClasses - } - - private fun getClassesFromFile(psiFile: PsiFile): List { - val psiElementHandler = PsiElementHandler.makePsiElementHandler(psiFile) - return PsiTreeUtil.getChildrenOfTypeAsList(psiFile, psiElementHandler.classClass) - .map { psiElementHandler.toPsi(it, PsiClass::class.java) } - } -} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestFolderComboWithBrowseButton.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestFolderComboWithBrowseButton.kt index c473054696..14ce8db2e0 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestFolderComboWithBrowseButton.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestFolderComboWithBrowseButton.kt @@ -3,27 +3,38 @@ package org.utbot.intellij.plugin.ui.components import com.intellij.openapi.application.ReadAction import com.intellij.openapi.fileChooser.FileChooser import com.intellij.openapi.fileChooser.FileChooserDescriptor +import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.project.guessProjectDir +import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.ui.ComponentWithBrowseButton +import com.intellij.openapi.ui.FixedSizeButton import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile import com.intellij.ui.ColoredListCellRenderer -import com.intellij.ui.ComboboxWithBrowseButton import com.intellij.ui.SimpleTextAttributes import com.intellij.util.ArrayUtil +import com.intellij.util.ui.UIUtil +import org.utbot.common.PathUtil +import org.utbot.intellij.plugin.models.BaseTestsModel +import org.utbot.intellij.plugin.models.GenerateTestsModel +import org.utbot.intellij.plugin.ui.utils.ITestSourceRoot +import org.utbot.intellij.plugin.ui.utils.addDedicatedTestRoot +import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle import java.io.File import javax.swing.DefaultComboBoxModel import javax.swing.JList -import org.utbot.common.PathUtil -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.intellij.plugin.ui.GenerateTestsModel -import org.utbot.intellij.plugin.ui.utils.addDedicatedTestRoot -import org.utbot.intellij.plugin.ui.utils.suitableTestSourceRoots -class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : ComboboxWithBrowseButton() { +private const val SET_TEST_FOLDER = "set test folder" + +class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : + ComponentWithBrowseButton>(ComboBox(), null) { - private val SET_TEST_FOLDER = "set test folder" init { + if (model.project.isBuildWithGradle) { + setButtonEnabled(false) + UIUtil.findComponentOfType(this, FixedSizeButton::class.java)?.toolTipText = "Please define custom test source root via Gradle" + } childComponent.isEditable = false childComponent.renderer = object : ColoredListCellRenderer() { override fun customizeCellRenderer( @@ -46,8 +57,11 @@ class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : C } } - val testRoots = model.testModule.suitableTestSourceRoots(CodegenLanguage.JAVA).toMutableList() - model.testModule.addDedicatedTestRoot(testRoots) + val testRoots = model.getSortedTestRoots() + + // this method is blocked for Gradle, where multiple test modules can exist + model.testModule.addDedicatedTestRoot(testRoots, model.codegenLanguage) + if (testRoots.isNotEmpty()) { configureRootsCombo(testRoots) } else { @@ -55,44 +69,51 @@ class TestFolderComboWithBrowseButton(private val model: GenerateTestsModel) : C } addActionListener { - val testSourceRoot = createNewTestSourceRoot(model) + val testSourceRoot = chooseTestRoot(model) testSourceRoot?.let { - model.testSourceRoot = it + model.setSourceRootAndFindTestModule(it) if (childComponent.itemCount == 1 && childComponent.selectedItem == SET_TEST_FOLDER) { newItemList(setOf(it)) } else { //Prepend and select newly added test root val testRootItems = linkedSetOf(it) - testRootItems += (0 until childComponent.itemCount).map { i -> childComponent.getItemAt(i) as VirtualFile} + testRootItems += (0 until childComponent.itemCount).map { i -> childComponent.getItemAt(i) as VirtualFile } newItemList(testRootItems) } } } } - private fun createNewTestSourceRoot(model: GenerateTestsModel): VirtualFile? = + private fun chooseTestRoot(model: BaseTestsModel): VirtualFile? = ReadAction.compute { - val desc = FileChooserDescriptor(false, true, false, false, false, false) + val desc = object : FileChooserDescriptor(false, true, false, false, false, false) { + override fun isFileSelectable(file: VirtualFile?): Boolean { + return file != null && ModuleUtil.findModuleForFile( + file, + model.project + ) != null && super.isFileSelectable(file) + } + } val initialFile = model.project.guessProjectDir() val files = FileChooser.chooseFiles(desc, model.project, initialFile) files.singleOrNull() } - private fun configureRootsCombo(testRoots: List) { - // unfortunately, Gradle creates Kotlin test source root with Java source root type, so type is misleading + private fun configureRootsCombo(testRoots: List) { val selectedRoot = testRoots.first() - model.testSourceRoot = selectedRoot - newItemList(testRoots.toSet()) + // do not update model.testModule here, because fake test source root could have been chosen + model.testSourceRoot = selectedRoot.dir + newItemList(testRoots.mapNotNull { it.dir }.toSet()) } private fun newItemList(comboItems: Set) { childComponent.model = DefaultComboBoxModel(ArrayUtil.toObjectArray(comboItems)) } - private fun formatUrl(virtualFile: VirtualFile, model: GenerateTestsModel): String { + private fun formatUrl(virtualFile: VirtualFile, model: BaseTestsModel): String { var directoryUrl = if (virtualFile is FakeVirtualFile) { virtualFile.parent.presentableUrl + File.separatorChar + virtualFile.name } else { diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/JavaPsiElementHandler.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/JavaPsiElementHandler.kt index 0e908d6aa1..e2016f0e69 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/JavaPsiElementHandler.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/JavaPsiElementHandler.kt @@ -4,7 +4,6 @@ import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod import com.intellij.psi.util.PsiTreeUtil -import com.intellij.testIntegration.TestIntegrationUtils import com.intellij.testIntegration.createTest.CreateTestAction class JavaPsiElementHandler( @@ -25,7 +24,5 @@ class JavaPsiElementHandler( CreateTestAction.isAvailableForElement(element) override fun containingClass(element: PsiElement): PsiClass? = - if (PsiTreeUtil.getParentOfType(element, PsiClass::class.java, false) != null) { - TestIntegrationUtils.findOuterClass(element) - } else null + PsiTreeUtil.getParentOfType(element, classClass, false) } \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/KotlinPsiElementHandler.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/KotlinPsiElementHandler.kt index 4365f45d76..1ea7d3e110 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/KotlinPsiElementHandler.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/KotlinPsiElementHandler.kt @@ -2,16 +2,20 @@ package org.utbot.intellij.plugin.ui.utils import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.util.findParentOfType +import org.jetbrains.kotlin.asJava.findFacadeClass import org.jetbrains.kotlin.idea.testIntegration.KotlinCreateTestIntention import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.kotlin.psi.psiUtil.parents +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.uast.toUElement class KotlinPsiElementHandler( + // TODO: KtClassOrObject? override val classClass: Class = KtClass::class.java, override val methodClass: Class = KtNamedFunction::class.java, ) : PsiElementHandler { @@ -23,13 +27,27 @@ class KotlinPsiElementHandler( return element.toUElement()?.javaPsi as? T ?: error("Could not cast $element to $clazz") } - override fun isCreateTestActionAvailable(element: PsiElement): Boolean = - getTarget(element)?.let { KotlinCreateTestIntention().applicabilityRange(it) != null } ?: false + override fun getClassesFromFile(psiFile: PsiFile): List { + return listOfNotNull((psiFile as? KtFile)?.findFacadeClass()) + super.getClassesFromFile(psiFile) + } + + override fun isCreateTestActionAvailable(element: PsiElement): Boolean { + getTarget(element)?.let { + return KotlinCreateTestIntention().applicabilityRange(it) != null + } + return (element.containingFile as? KtFile)?.findFacadeClass() != null + } private fun getTarget(element: PsiElement?): KtNamedDeclaration? = - element?.parents + element?.parentsWithSelf ?.firstOrNull { it is KtClassOrObject || it is KtNamedDeclaration && it.parent is KtFile } as? KtNamedDeclaration - override fun containingClass(element: PsiElement): PsiClass? = - (element.parents.firstOrNull { it is KtClassOrObject })?.let { toPsi(it, PsiClass::class.java) } + override fun containingClass(element: PsiElement): PsiClass? { + element.findParentOfType(strict=false)?.let { + return toPsi(it, PsiClass::class.java) + } + return element.findParentOfType(strict=false)?.findFacadeClass()?.let { + toPsi(it, PsiClass::class.java) + } + } } \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt index af19f7f275..8666a3c4d8 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryMatcher.kt @@ -1,39 +1,55 @@ package org.utbot.intellij.plugin.ui.utils -import org.utbot.framework.codegen.TestFramework -import org.utbot.framework.codegen.model.util.Patterns -import org.utbot.framework.codegen.model.util.patterns +import org.utbot.framework.codegen.domain.TestFramework import org.utbot.framework.plugin.api.MockFramework import com.intellij.openapi.module.Module -import com.intellij.openapi.project.Project import com.intellij.openapi.roots.LibraryOrderEntry +import org.utbot.framework.codegen.domain.SpringModule +import org.utbot.framework.plugin.api.utils.Patterns +import org.utbot.framework.plugin.api.utils.parametrizedTestsPatterns +import org.utbot.framework.plugin.api.utils.patterns +import org.utbot.framework.plugin.api.utils.testPatterns fun findFrameworkLibrary( - project: Project, - testModule: Module, + module: Module, testFramework: TestFramework, scope: LibrarySearchScope = LibrarySearchScope.Module, -): LibraryOrderEntry? { - return findMatchingLibrary(project, testModule, testFramework.patterns(), scope) -} +): LibraryOrderEntry? = findMatchingLibraryOrNull(module, testFramework.patterns(), scope) fun findFrameworkLibrary( - project: Project, - testModule: Module, + module: Module, mockFramework: MockFramework, scope: LibrarySearchScope = LibrarySearchScope.Module, -): LibraryOrderEntry? = findMatchingLibrary(project, testModule, mockFramework.patterns(), scope) +): LibraryOrderEntry? = findMatchingLibraryOrNull(module, mockFramework.patterns(), scope) + +fun findParametrizedTestsLibrary( + module: Module, + testFramework: TestFramework, + scope: LibrarySearchScope = LibrarySearchScope.Module, +): LibraryOrderEntry? = findMatchingLibraryOrNull(module, testFramework.parametrizedTestsPatterns(), scope) + +fun findDependencyInjectionLibrary( + module: Module, + springModule: SpringModule, + scope: LibrarySearchScope = LibrarySearchScope.Module +): LibraryOrderEntry? = findMatchingLibraryOrNull(module, springModule.patterns(), scope) -private fun findMatchingLibrary( - project: Project, - testModule: Module, +fun findDependencyInjectionTestLibrary( + module: Module, + springModule: SpringModule, + scope: LibrarySearchScope = LibrarySearchScope.Module +): LibraryOrderEntry? = findMatchingLibraryOrNull(module, springModule.testPatterns(), scope) + +private fun findMatchingLibraryOrNull( + module: Module, patterns: Patterns, scope: LibrarySearchScope, ): LibraryOrderEntry? { val installedLibraries = when (scope) { - LibrarySearchScope.Module -> testModule.allLibraries() - LibrarySearchScope.Project -> project.allLibraries() + LibrarySearchScope.Module -> module.allLibraries() + LibrarySearchScope.Project -> module.project.allLibraries() } + return installedLibraries .matchesFrameworkPatterns(patterns.moduleLibraryPatterns, patterns.libraryPatterns) } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryUtils.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryUtils.kt index 549b192aa6..13242c2c5f 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryUtils.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/LibraryUtils.kt @@ -5,6 +5,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.roots.LibraryOrderEntry import com.intellij.openapi.roots.ModuleRootManager import com.intellij.openapi.roots.OrderEnumerator +import com.intellij.openapi.roots.OrderRootType import com.intellij.openapi.roots.ProjectRootManager /** @@ -24,24 +25,15 @@ fun Module.allLibraries(): List { val moduleRootManager = ModuleRootManager.getInstance(this) return allLibraries(moduleRootManager.orderEntries()) } - -fun String.parseVersion(): String? { - val lastSemicolon = lastIndexOf(':') - val version = substring(lastSemicolon + 1) - - if (lastSemicolon == -1 || - version.split('.').any { it.toIntOrNull() == null } - ) { - return null - } - - return version -} - fun List.matchesAnyOf(patterns: List): LibraryOrderEntry? = firstOrNull { entry -> patterns.any { pattern -> - entry.libraryName?.let { pattern.containsMatchIn(it) } ?: false + entry.libraryName?.let { + if (pattern.containsMatchIn(it)) return@any true + } + //Fallback to filenames in case library has no name at all, or the name is too generic (e.g. 'JUnit' or 'JUnit4') + return@any entry.library?.getFiles(OrderRootType.CLASSES) + ?.any { virtualFile -> pattern.containsMatchIn(virtualFile.name) } ?: false } } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt index 2d4fbbf79f..5f18c2a0cb 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ModuleUtils.kt @@ -4,16 +4,14 @@ import org.utbot.common.PathUtil.toPath import org.utbot.common.WorkaroundReason import org.utbot.common.workaround import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.intellij.plugin.ui.CommonErrorNotifier -import org.utbot.intellij.plugin.ui.UnsupportedJdkNotifier import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.externalSystem.model.ProjectSystemId +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleManager import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.project.Project -import com.intellij.openapi.projectRoots.JavaSdk -import com.intellij.openapi.projectRoots.JavaSdkVersion -import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.project.guessModuleDir import com.intellij.openapi.roots.ContentEntry import com.intellij.openapi.roots.ModifiableRootModel import com.intellij.openapi.roots.ModuleRootManager @@ -26,7 +24,7 @@ import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile import com.intellij.util.PathUtil.getParentPath import java.nio.file.Path import mu.KotlinLogging -import org.jetbrains.android.sdk.AndroidSdkType +import org.jetbrains.jps.model.java.JavaResourceRootType import org.jetbrains.jps.model.module.JpsModuleSourceRootType import org.jetbrains.kotlin.config.KotlinFacetSettingsProvider import org.jetbrains.kotlin.config.TestResourceKotlinRootType @@ -34,13 +32,23 @@ import org.jetbrains.kotlin.platform.TargetPlatformVersion private val logger = KotlinLogging.logger {} -/** - * @return jdk version of the module - */ -fun Module.jdkVersion(): JavaSdkVersion { - val moduleRootManager = ModuleRootManager.getInstance(this) - val sdk = moduleRootManager.sdk - return jdkVersionBy(sdk) +interface ITestSourceRoot { + val dirPath: String + val dirName: String + val dir: VirtualFile? + val expectedLanguage : CodegenLanguage +} + +class TestSourceRoot(override val dir: VirtualFile, override val expectedLanguage: CodegenLanguage) : ITestSourceRoot { + override val dirPath: String = dir.toNioPath().toString() + override val dirName: String = dir.name + + override fun toString() = dirPath + + override fun equals(other: Any?) = + other is TestSourceRoot && dir == other.dir && expectedLanguage == other.expectedLanguage + + override fun hashCode() = 31 * dir.hashCode() + expectedLanguage.hashCode() } /** @@ -57,11 +65,17 @@ fun Module.kotlinTargetPlatform(): TargetPlatformVersion { ?.singleOrNull() ?: error("Can't determine target platform for module $this") } -fun Module.suitableTestSourceRoots(): List = - suitableTestSourceRoots(CodegenLanguage.JAVA) + suitableTestSourceRoots(CodegenLanguage.KOTLIN) - -fun Module.suitableTestSourceFolders(): List = - suitableTestSourceFolders(CodegenLanguage.JAVA) + suitableTestSourceFolders(CodegenLanguage.KOTLIN) +/** + * Gets paths to project resources source roots. + * + * E.g. src/main/resources + */ +fun Module.getResourcesPaths(): List = + ModuleRootManager.getInstance(this) + .contentEntries + .flatMap { it.sourceFolders.toList() } + .filter { it.rootType is JavaResourceRootType && !it.isTestSource } + .mapNotNull { it.file?.toNioPath() } /** * Gets a path to test resources source root. @@ -82,42 +96,44 @@ fun Module.getOrCreateSarifReportsPath(testSourceRoot: VirtualFile?): Path { } /** - * Find test module by current source module. + * Find test modules by current source module. */ -fun Module.testModule(project: Project): Module { - var testModule = findPotentialModuleForTests(project, this) - val testRootUrls = testModule.suitableTestSourceRoots() +fun Module.testModules(project: Project): List { + var testModules = findPotentialModulesForTests(project, this) + val testRootUrls = testModules.flatMap { it.suitableTestSourceRoots() } //if no suitable module for tests is found, create tests in the same root - if (testRootUrls.isEmpty() && testModule.suitableTestSourceFolders().isEmpty()) { - testModule = this + if (testRootUrls.isEmpty() && testModules.flatMap { it.suitableTestSourceFolders() }.isEmpty()) { + testModules = listOf(this) } - return testModule + return testModules } -private fun findPotentialModuleForTests(project: Project, srcModule: Module): Module { +private fun findPotentialModulesForTests(project: Project, srcModule: Module): List { + val modules = mutableListOf() for (module in ModuleManager.getInstance(project).modules) { if (srcModule == TestModuleProperties.getInstance(module).productionModule) { - return module + modules += module } } + if (modules.isNotEmpty()) return modules if (srcModule.suitableTestSourceFolders().isEmpty()) { - val modules = mutableSetOf() - ModuleUtilCore.collectModulesDependsOn(srcModule, modules) - modules.remove(srcModule) + val modulesWithTestRoot = mutableSetOf().also { + ModuleUtilCore.collectModulesDependsOn(srcModule, it) + it.remove(srcModule) + }.filter { it.suitableTestSourceFolders().isNotEmpty() } - val modulesWithTestRoot = modules.filter { it.suitableTestSourceFolders().isNotEmpty() } - if (modulesWithTestRoot.size == 1) return modulesWithTestRoot[0] + if (modulesWithTestRoot.size == 1) return modulesWithTestRoot } - return srcModule + return listOf(srcModule) } /** * Finds all suitable test root virtual files. */ -fun Module.suitableTestSourceRoots(codegenLanguage: CodegenLanguage): List { - val sourceRootsInModule = suitableTestSourceFolders(codegenLanguage).mapNotNull { it.file } +fun Module.suitableTestSourceRoots(): List { + val sourceRootsInModule = suitableTestSourceFolders().mapNotNull { it.testSourceRoot } if (sourceRootsInModule.isNotEmpty()) { return sourceRootsInModule @@ -128,11 +144,20 @@ fun Module.suitableTestSourceRoots(codegenLanguage: CodegenLanguage): List { +private val SourceFolder.testSourceRoot: TestSourceRoot? + get() { + val file = file + val expectedLanguage = expectedLanguageForTests + if (file != null && expectedLanguage != null) + return TestSourceRoot(file, expectedLanguage) + return null + } + +private fun Module.suitableTestSourceFolders(): List { val sourceFolders = ModuleRootManager.getInstance(this) .contentEntries .flatMap { it.sourceFolders.toList() } @@ -140,26 +165,34 @@ private fun Module.suitableTestSourceFolders(codegenLanguage: CodegenLanguage): return sourceFolders .filterNot { it.isForGeneratedSources() } - .filter { it.rootType == codegenLanguage.testRootType() } - // Heuristics: User is more likely to choose the shorter path - .sortedBy { it.url.length } + .filter { it.isTestSource } } -private const val dedicatedTestSourceRootName = "utbot_tests" -fun Module.addDedicatedTestRoot(testSourceRoots: MutableList): VirtualFile? { +private val GRADLE_SYSTEM_ID = ProjectSystemId("GRADLE") + +val Project.isBuildWithGradle get() = + ModuleManager.getInstance(this).modules.any { + ExternalSystemApiUtil.isExternalSystemAwareModule(GRADLE_SYSTEM_ID, it) + } + +const val dedicatedTestSourceRootName = "utbot_tests" + +fun Module.addDedicatedTestRoot(testSourceRoots: MutableList, language: CodegenLanguage): VirtualFile? { + // Don't suggest new test source roots for a Gradle project where 'unexpected' test roots won't work + if (project.isBuildWithGradle) return null // Dedicated test root already exists - // OR it looks like standard structure of Gradle project where 'unexpected' test roots won't work - if (testSourceRoots.any { file -> - file.name == dedicatedTestSourceRootName || file.path.endsWith("src/test/java") - }) return null + if (testSourceRoots.any { root -> root.dir?.name == dedicatedTestSourceRootName }) return null val moduleInstance = ModuleRootManager.getInstance(this) - val testFolder = moduleInstance.contentEntries.flatMap { it.sourceFolders.toList() } + val testFolder = moduleInstance.contentEntries + .flatMap { it.sourceFolders.toList() } + .filterNot { it.isForGeneratedSources() } .firstOrNull { it.rootType in testSourceRootTypes } - (testFolder?.let { testFolder.file?.parent } ?: (testFolder?.contentEntry - ?: moduleInstance.contentEntries.first()).file ?: moduleFile)?.let { + + (testFolder?.let { testFolder.file?.parent } + ?: testFolder?.contentEntry?.file ?: this.guessModuleDir())?.let { val file = FakeVirtualFile(it, dedicatedTestSourceRootName) - testSourceRoots.add(file) + testSourceRoots.add(TestSourceRoot(file, language)) // We return "true" IFF it's case of not yet created fake directory return if (VfsUtil.findRelativeFile(it, dedicatedTestSourceRootName) == null) file else null } @@ -179,7 +212,7 @@ private fun getOrCreateTestResourcesUrl(module: Module, testSourceRoot: VirtualF } // taking the source folder that has the maximum common prefix // with `testSourceRoot`, which was selected by the user - .maxBy { sourceFolder -> + .maxByOrNull { sourceFolder -> val sourceFolderPath = sourceFolder.file?.path ?: "" val testSourceRootPath = testSourceRoot?.path ?: "" sourceFolderPath.commonPrefixWith(testSourceRootPath).length @@ -189,7 +222,7 @@ private fun getOrCreateTestResourcesUrl(module: Module, testSourceRoot: VirtualF } val testFolder = sourceFolders.firstOrNull { it.rootType in testSourceRootTypes } - val contentEntry = testFolder?.contentEntry ?: rootModel.contentEntries.first() + val contentEntry = testFolder?.getModifiableContentEntry() ?: rootModel.contentEntries.first() val parentFolderUrl = testFolder?.let { getParentPath(testFolder.url) } val testResourcesUrl = @@ -214,6 +247,10 @@ private fun getOrCreateTestResourcesUrl(module: Module, testSourceRoot: VirtualF } } +private fun SourceFolder.getModifiableContentEntry() : ContentEntry? { + return ModuleRootManager.getInstance(contentEntry.rootModel.module).modifiableModel.contentEntries.find { entry -> entry.url == url } +} + fun ContentEntry.addSourceRootIfAbsent( model: ModifiableRootModel, sourceRootUrl: String, @@ -235,30 +272,19 @@ fun ContentEntry.addSourceRootIfAbsent( } } -/** - * Obtain JDK version and make sure that it is JDK8 or JDK11 - */ -private fun jdkVersionBy(sdk: Sdk?): JavaSdkVersion { - if (sdk == null) { - CommonErrorNotifier.notify("Failed to obtain JDK version of the project") - } - requireNotNull(sdk) +private val SourceFolder.expectedLanguageForTests: CodegenLanguage? + get() { + // unfortunately, Gradle creates Kotlin test source root with Java source root type, so type is misleading, + // and we should try looking for name first + if (file?.name == "kotlin") + return CodegenLanguage.KOTLIN - val jdkVersion = when (sdk.sdkType) { - is JavaSdk -> { - (sdk.sdkType as JavaSdk).getVersion(sdk) - } - is AndroidSdkType -> { - ((sdk.sdkType as AndroidSdkType).dependencyType as JavaSdk).getVersion(sdk) + if (file?.name == "java") + return CodegenLanguage.JAVA + + return when (rootType) { + CodegenLanguage.KOTLIN.testRootType() -> CodegenLanguage.KOTLIN + CodegenLanguage.JAVA.testRootType() -> CodegenLanguage.JAVA + else -> null } - else -> null - } - if (jdkVersion == null) { - CommonErrorNotifier.notify("Failed to obtain JDK version of the project") - } - requireNotNull(jdkVersion) - if (!jdkVersion.isAtLeast(JavaSdkVersion.JDK_1_8)) { - UnsupportedJdkNotifier.notify(jdkVersion.description) - } - return jdkVersion -} + } \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/PsiElementHandler.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/PsiElementHandler.kt index 6455cbbf55..eecfda5945 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/PsiElementHandler.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/PsiElementHandler.kt @@ -3,6 +3,7 @@ package org.utbot.intellij.plugin.ui.utils import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.psi.KtFile /** @@ -36,6 +37,14 @@ interface PsiElementHandler { */ fun toPsi(element: PsiElement, clazz: Class): T + /** + * Returns all classes that are declared in the [psiFile] + */ + fun getClassesFromFile(psiFile: PsiFile): List { + return PsiTreeUtil.getChildrenOfTypeAsList(psiFile, classClass) + .map { toPsi(it, PsiClass::class.java) } + } + /** * Get java class of the Class in the corresponding syntax tree (PsiClass, KtClass, e.t.c). */ diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/RootUtils.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/RootUtils.kt index 44d8b36876..ecba364675 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/RootUtils.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/RootUtils.kt @@ -1,7 +1,8 @@ package org.utbot.intellij.plugin.ui.utils -import org.utbot.framework.plugin.api.CodegenLanguage import com.intellij.openapi.roots.SourceFolder +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil import org.jetbrains.jps.model.java.JavaResourceRootProperties import org.jetbrains.jps.model.java.JavaResourceRootType import org.jetbrains.jps.model.java.JavaSourceRootProperties @@ -11,6 +12,8 @@ import org.jetbrains.kotlin.config.ResourceKotlinRootType import org.jetbrains.kotlin.config.SourceKotlinRootType import org.jetbrains.kotlin.config.TestResourceKotlinRootType import org.jetbrains.kotlin.config.TestSourceKotlinRootType +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.intellij.plugin.util.IntelliJApiHelper val sourceRootTypes: Set> = setOf(JavaSourceRootType.SOURCE, SourceKotlinRootType) val testSourceRootTypes: Set> = setOf(JavaSourceRootType.TEST_SOURCE, TestSourceKotlinRootType) @@ -24,6 +27,7 @@ fun CodegenLanguage.testRootType(): JpsModuleSourceRootType JavaSourceRootType.TEST_SOURCE CodegenLanguage.KOTLIN -> TestSourceKotlinRootType + else -> TestSourceKotlinRootType } /** @@ -33,14 +37,77 @@ fun CodegenLanguage.testResourcesRootType(): JpsModuleSourceRootType JavaResourceRootType.TEST_RESOURCE CodegenLanguage.KOTLIN -> TestResourceKotlinRootType + else -> TestResourceKotlinRootType } /** * Generalizes [JavaResourceRootProperties.isForGeneratedSources] for both Java and Kotlin. + * + * Unfortunately, Android Studio has another project model, so we cannot rely on the flag value. + * The only way is to find build/generated substring in the folder path. */ fun SourceFolder.isForGeneratedSources(): Boolean { val properties = jpsElement.getProperties(sourceRootTypes + testSourceRootTypes) val resourceProperties = jpsElement.getProperties(resourceRootTypes + testResourceRootTypes) - return properties?.isForGeneratedSources == true && resourceProperties?.isForGeneratedSources == true + val markedGeneratedSources = + properties?.isForGeneratedSources == true || resourceProperties?.isForGeneratedSources == true + val androidStudioGeneratedSources = + IntelliJApiHelper.isAndroidStudio() && this.file?.path?.contains("build/generated") == true + + return markedGeneratedSources || androidStudioGeneratedSources +} + +const val SRC_MAIN = "src/main/" + +/** + * Sorting test roots, the main idea is to place 'the best' + * test source root the first and to provide readability in general + * @param allTestRoots are all test roots of a project to be sorted + * @param moduleSourcePaths is list of source roots for the module for which we're going to generate tests. + * The first test source root in the resulting list is expected + * to be the closest one to the module based on module source roots. + * @param codegenLanguage is target generation language + */ +fun getSortedTestRoots( + allTestRoots: MutableList, + sourceRootHistory: List, + moduleSourcePaths: List, + codegenLanguage: CodegenLanguage +): MutableList { + var commonModuleSourceDirectory = FileUtil.toSystemIndependentName(moduleSourcePaths.getCommonPrefix()) + //Remove standard suffix that may prevent exact module path matching + commonModuleSourceDirectory = StringUtil.trimEnd(commonModuleSourceDirectory, SRC_MAIN) + + return allTestRoots.distinct().toMutableList().sortedWith( + compareByDescending { + // Heuristics: Dirs with proper code language should go first + it.expectedLanguage == codegenLanguage + }.thenByDescending { + // Heuristics: Dirs from within module 'common' directory should go first + FileUtil.toSystemIndependentName(it.dirPath).startsWith(commonModuleSourceDirectory) + }.thenByDescending { + // Heuristics: dedicated test source root named 'utbot_tests' should go first + it.dirName == dedicatedTestSourceRootName + }.thenByDescending { + // Recent used root should be handy too + sourceRootHistory.indexOf(it.dirPath) + }.thenBy { + // ABC-sorting + it.dirPath + } + ).toMutableList() +} + + +fun List.getCommonPrefix() : String { + var result = "" + for ((i, s) in withIndex()) { + result = if (i == 0) { + s + } else { + StringUtil.commonPrefix(result, s) + } + } + return result } diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/UiUtils.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/UiUtils.kt deleted file mode 100644 index 4c25a29843..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/UiUtils.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.utbot.intellij.plugin.ui.utils - -import com.intellij.ui.components.Panel -import com.intellij.ui.layout.Cell -import com.intellij.ui.layout.CellBuilder -import com.intellij.ui.layout.LayoutBuilder -import java.awt.Component -import javax.swing.JComponent -import javax.swing.JPanel - -fun LayoutBuilder.labeled(text: String, component: JComponent) { - row { - cell(false) { - label(text) - component() - } - } -} - -fun Cell.panelNoTitle(wrappedComponent: Component, hasSeparator: Boolean = true): CellBuilder { - val panel = Panel(null, hasSeparator) - panel.add(wrappedComponent) - return component(panel) -} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/Version.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/Version.kt new file mode 100644 index 0000000000..080f52e590 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/Version.kt @@ -0,0 +1,52 @@ +package org.utbot.intellij.plugin.ui.utils + +/** + * Describes the version of a library. + * Contains three standard components: major, minor and patch. + * + * Major and minor components are always numbers, while patch + * may contain a number with some optional postfix like `-RELEASE` or `.RELEASE`. + * + * Sometimes patch is empty, e.g. for TestNg 7.5 version. + * + * @param plainText is optional and represents whole version as text. + */ +data class Version( + val major: Int, + val minor: Int, + val patch: String, + val plainText: String? = null, +) { + fun isCompatibleWith(another: Version): Boolean { + // Non-numeric versions can't be compared to each other, + // so we cannot be sure that current is compatible unless it's the exact match + if (!hasNumericOrEmptyPatch() || !hasNumericOrEmptyPatch()) { + return plainText == another.plainText + } + + return major > another.major || + major == another.major && minor > another.minor || + major == another.major && minor == another.minor && + (another.patch.isEmpty() || patch.isNotEmpty() && patch.toInt() >= another.patch.toInt()) + } + + fun hasNumericOrEmptyPatch(): Boolean = patch.isEmpty() || patch.toIntOrNull() != null +} + +fun String.parseVersion(): Version? { + val lastSemicolon = lastIndexOf(':') + val versionText = substring(lastSemicolon + 1) + + // Components must be: major, minor and (optional) patch + val versionComponents = versionText.split('.', limit = 3) + + if (versionComponents.size < 2) { + return null + } + + val major = versionComponents[0].toIntOrNull() ?: return null + val minor = versionComponents[1].toIntOrNull() ?: return null + val patch = if (versionComponents.size == 3) versionComponents[2] else "" + + return Version(major, minor, patch, versionText) +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/AndroidApiHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/AndroidApiHelper.kt deleted file mode 100644 index 4f0aaa15f2..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/AndroidApiHelper.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.utbot.intellij.plugin.util - -import com.android.tools.idea.IdeInfo -import com.android.tools.idea.gradle.util.GradleProjectSettingsFinder -import com.intellij.ide.plugins.PluginManagerCore -import com.intellij.openapi.extensions.PluginId -import com.intellij.openapi.project.Project - -/** - * This object is required to encapsulate Android API usage and grant safe access to it. - */ -object AndroidApiHelper { - private val isAndroidPluginAvailable: Boolean = !PluginManagerCore.isDisabled(PluginId.getId("org.jetbrains.android")) - - fun isAndroidStudio(): Boolean = - isAndroidPluginAvailable && IdeInfo.getInstance().isAndroidStudio - - fun gradleSDK(project: Project): String? { - return if (isAndroidPluginAvailable) GradleProjectSettingsFinder.getInstance().findGradleProjectSettings(project)?.gradleJvm - else null - } -} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IdeaThreadingUtil.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IdeaThreadingUtil.kt new file mode 100644 index 0000000000..526dde9bc6 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/IdeaThreadingUtil.kt @@ -0,0 +1,27 @@ +package org.utbot.intellij.plugin.util + +import com.intellij.openapi.application.ApplicationManager + +fun assertIsDispatchThread() { + ApplicationManager.getApplication().assertIsDispatchThread() +} + +fun assertIsWriteThread() { + ApplicationManager.getApplication().isWriteThread() +} + +fun assertReadAccessAllowed() { + ApplicationManager.getApplication().assertReadAccessAllowed() +} + +fun assertWriteAccessAllowed() { + ApplicationManager.getApplication().assertWriteAccessAllowed() +} + +fun assertIsNonDispatchThread() { + ApplicationManager.getApplication().assertIsNonDispatchThread() +} + +fun assertReadAccessNotAllowed() { + ApplicationManager.getApplication().assertReadAccessNotAllowed() +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/MethodDescriptionHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/MethodDescriptionHelper.kt new file mode 100644 index 0000000000..9110efe995 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/MethodDescriptionHelper.kt @@ -0,0 +1,16 @@ +package org.utbot.intellij.plugin.util + +import com.intellij.psi.PsiMethod +import com.intellij.refactoring.util.classMembers.MemberInfo +import org.utbot.framework.plugin.api.MethodDescription + +fun MemberInfo.methodDescription(): MethodDescription = + (this.member as PsiMethod).methodDescription() + +// Note that rules for obtaining signature here should correlate with KFunction<*>.signature() +private fun PsiMethod.methodDescription() = + MethodDescription(this.name, this.containingClass?.qualifiedName, this.parameterList.parameters.map { + it.type.canonicalText + .replace("...", "[]") //for PsiEllipsisType + .replace(",", ", ") // to fix cases like Pair -> Pair + }) \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkInfoProvider.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkInfoProvider.kt new file mode 100644 index 0000000000..e96c5897c5 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkInfoProvider.kt @@ -0,0 +1,37 @@ +package org.utbot.intellij.plugin.util + +import org.utbot.common.PathUtil.toPath +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.ProjectJdkTable +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.roots.ProjectRootManager +import org.utbot.framework.plugin.services.JdkInfo +import org.utbot.framework.plugin.services.JdkInfoDefaultProvider +import org.utbot.framework.plugin.services.fetchJavaVersion + +class PluginJdkInfoProvider( + private val project: Project +) : JdkInfoDefaultProvider() { + + private val sdk: Sdk? + get() { + if (IntelliJApiHelper.isAndroidStudio()) { + // Get Gradle JDK for Android + IntelliJApiHelper.androidGradleSDK(project) + ?.let { sdkName -> + ProjectJdkTable.getInstance().findJdk(sdkName) ?.let { + return it + } + } + } + + // Use Project SDK as analyzed JDK + return ProjectRootManager.getInstance(project).projectSdk + } + + override val info: JdkInfo + get() = JdkInfo( + sdk?.homePath?.toPath() ?: super.info.path, // Return default JDK in case of failure + fetchJavaVersion(sdk?.versionString!!) // Return default JDK in case of failure + ) +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkPathProvider.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkPathProvider.kt deleted file mode 100644 index 0adc27f575..0000000000 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginJdkPathProvider.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.utbot.intellij.plugin.util - -import org.utbot.common.PathUtil.toPath -import org.utbot.framework.JdkPathDefaultProvider -import com.intellij.openapi.module.Module -import com.intellij.openapi.project.Project -import com.intellij.openapi.projectRoots.ProjectJdkTable -import com.intellij.openapi.roots.ModuleRootManager -import com.intellij.openapi.roots.ProjectRootManager -import java.nio.file.Path - -class PluginJdkPathProvider( - private val project: Project, - private val testModule: Module, -) : JdkPathDefaultProvider() { - - override val jdkPath: Path - get() = - if (AndroidApiHelper.isAndroidStudio()) { - // Get Gradle JDK for Android - AndroidApiHelper.gradleSDK(project) - ?.let { sdkName -> - ProjectJdkTable.getInstance().findJdk(sdkName)?.homePath?.toPath() - } - } else { - // Use testModule JDK (or Project SDK) as analyzed JDK - (ModuleRootManager.getInstance(testModule).sdk - ?.homePath ?: ProjectRootManager.getInstance(project).projectSdk?.homePath) - ?.toPath() - } ?: super.jdkPath // Return default JDK in case of failure - -} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginWorkingDirProvider.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginWorkingDirProvider.kt new file mode 100644 index 0000000000..cb09d2065d --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PluginWorkingDirProvider.kt @@ -0,0 +1,19 @@ +package org.utbot.intellij.plugin.util + +import com.intellij.openapi.project.Project +import org.utbot.common.PathUtil.toPath +import org.utbot.framework.plugin.services.WorkingDirDefaultProvider +import java.nio.file.Path + +class PluginWorkingDirProvider( + project: Project, +) : WorkingDirDefaultProvider() { + + /** + * We believe that in most cases the test runner working dir is the project root, otherwise we need to parse test + * configuration, but it's not easy. + */ + override val workingDir: Path = + project.basePath?.toPath() + ?: super.workingDir +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt new file mode 100644 index 0000000000..26cd770797 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/PsiClassHelper.kt @@ -0,0 +1,124 @@ +package org.utbot.intellij.plugin.util + +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiMember +import com.intellij.psi.PsiMethod +import com.intellij.psi.SyntheticElement +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiModifier +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.openapi.project.Project +import com.intellij.refactoring.util.classMembers.MemberInfo +import com.intellij.testIntegration.TestIntegrationUtils +import org.jetbrains.kotlin.asJava.elements.KtLightMember +import org.jetbrains.kotlin.asJava.elements.KtLightMethod +import org.jetbrains.kotlin.asJava.elements.isGetter +import org.jetbrains.kotlin.asJava.elements.isSetter +import org.jetbrains.kotlin.psi.KtClass +import org.utbot.common.filterWhen +import org.utbot.framework.UtSettings +import org.utbot.intellij.plugin.models.packageName + +/** + * Used to build binary name from canonical name + * in a similar form which could be obtained by [java.lang.Class.getName] method. + * + * E.g. ```org.example.OuterClass.InnerClass.InnerInnerClass``` -> ```org.example.OuterClass$InnerClass$InnerInnerClass``` + */ +val PsiClass.binaryName: String + get() = + if (packageName.isEmpty()) { + qualifiedName?.replace(".", "$") ?: "" + } else { + val name = + qualifiedName + ?.substringAfter("$packageName.") + ?.replace(".", "$") + ?: error("Binary name construction failed: unable to get qualified name of $this") + "$packageName.$name" + } + +val PsiMember.isAbstract: Boolean + get() = modifierList?.hasModifierProperty(PsiModifier.ABSTRACT)?: false + +val PsiMember.isStatic: Boolean + get() = modifierList?.hasModifierProperty(PsiModifier.STATIC)?: false + +private val PsiMember.isKotlinGetterOrSetter: Boolean + get() { + if (this !is KtLightMethod) + return false + return this.isGetter || this.isSetter + } + +private val PsiMember.isKotlinAndProtected: Boolean + get() = this is KtLightMember<*> && this.hasModifierProperty(PsiModifier.PROTECTED) + +// By now, we think that method in Kotlin is autogenerated iff navigation to its declaration leads to its declaring class +// rather than the method itself (because such methods don't have bodies that we can navigate to) +private val PsiMember.isKotlinAutogeneratedMethod: Boolean + get() = this is KtLightMethod && navigationElement is KtClass + +private val PsiMethod.canBeCalledStatically: Boolean + get() = isStatic || containingClass?.let { it.isStatic && !it.isInterface && !it.isAbstract } ?: throw IllegalStateException("No containing class found for method $this") + +private val PsiMethod.isUntestableMethodOfAbstractOrInterface: Boolean + get() { + val hasAbstractContext = generateSequence(containingClass) { it.containingClass }.any { it.isAbstract || it.isInterface } + return hasAbstractContext && !canBeCalledStatically + } + +private fun Iterable.filterTestableMethods(): List = this + .filterWhen(UtSettings.skipTestGenerationForSyntheticAndImplicitlyDeclaredMethods) { + it.member !is SyntheticElement && !it.member.isKotlinAutogeneratedMethod + } + .filterNot { (it.member as PsiMethod).isUntestableMethodOfAbstractOrInterface } + .filterNot { it.member.isKotlinGetterOrSetter } + .filterNot { it.member.isKotlinAndProtected } + +private val PsiClass.isPrivateOrProtected: Boolean + get() = this.modifierList?.let { + hasModifierProperty(PsiModifier.PRIVATE) || hasModifierProperty(PsiModifier.PROTECTED) + } ?: false + + +// TODO: maybe we need to delete [includeInherited] param here (always false when calling)? +fun PsiClass.extractClassMethodsIncludingNested(includeInherited: Boolean): List { + val ourMethods = TestIntegrationUtils.extractClassMethods(this, includeInherited) + .filterTestableMethods() + + val methodsFromNestedClasses = + innerClasses + .filter { !it.isPrivateOrProtected } + .flatMap { it.extractClassMethodsIncludingNested(includeInherited) } + + return ourMethods + methodsFromNestedClasses +} + +fun PsiClass.extractFirstLevelMembers(includeInherited: Boolean): List { + val methods = TestIntegrationUtils.extractClassMethods(this, includeInherited) + .filterTestableMethods() + val classes = if (includeInherited) + allInnerClasses + else + innerClasses + return methods + classes.filter { !it.isPrivateOrProtected }.map { MemberInfo(it) } +} + +val PsiClass.isVisible: Boolean + get() = generateSequence(this) { it.containingClass }.none { it.isPrivateOrProtected } + +object PsiClassHelper { + /** + * Finds [PsiClass]. + * + * @param name binary name which is converted to canonical name. + */ + fun findClass(name: String, project: Project): PsiClass? { + // Converting name to canonical name + val canonicalName = name.replace("$", ".") + return JavaPsiFacade + .getInstance(project) + .findClass(canonicalName, GlobalSearchScope.projectScope(project)) + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt new file mode 100644 index 0000000000..900aa50584 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/RunConfigurationHelper.kt @@ -0,0 +1,144 @@ +package org.utbot.intellij.plugin.util + +import com.intellij.coverage.CoverageExecutor +import com.intellij.execution.ConfigurationWithCommandLineShortener +import com.intellij.execution.ExecutorRegistry +import com.intellij.execution.JavaTestConfigurationBase +import com.intellij.execution.Location +import com.intellij.execution.PsiLocation +import com.intellij.execution.RunManagerEx +import com.intellij.execution.ShortenCommandLine +import com.intellij.execution.actions.ConfigurationContext +import com.intellij.execution.actions.ConfigurationFromContext +import com.intellij.execution.actions.RunConfigurationProducer +import com.intellij.execution.configurations.RunConfiguration +import com.intellij.execution.executors.DefaultRunExecutor +import com.intellij.execution.runners.ExecutionUtil +import com.intellij.execution.runners.ProgramRunner +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.actionSystem.DataKey +import com.intellij.openapi.actionSystem.LangDataKeys +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.util.Computable +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.UserDataHolder +import com.intellij.openapi.util.UserDataHolderBase +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.SmartPsiElementPointer +import com.intellij.psi.util.childrenOfType +import java.util.Comparator +import mu.KotlinLogging +import org.utbot.intellij.plugin.models.GenerateTestsModel +import org.utbot.intellij.plugin.util.IntelliJApiHelper.run + +class RunConfigurationHelper { + class MyMapDataContext() : DataContext, UserDataHolder { + private val myMap: MutableMap = HashMap() + private val holder = UserDataHolderBase() + override fun getData(dataId: String): Any? { + return myMap[dataId] + } + + private fun put(dataId: String, data: Any?) { + myMap[dataId] = data + } + + fun put(dataKey: DataKey, data: T) { + put(dataKey.name, data) + } + + override fun getUserData(key: Key): T? { + return holder.getUserData(key) + } + + override fun putUserData(key: Key, value: T?) { + holder.putUserData(key, value) + } + } + + private class MyConfigurationContext(val context: DataContext, psiElement: PsiElement) : ConfigurationContext(psiElement) { + override fun getDataContext() = context + } + + companion object { + private val logger = KotlinLogging.logger {} + + private fun RunConfiguration.isPatternBased() = this is JavaTestConfigurationBase && "pattern".contentEquals(testType, true) + + // In case we do "generate and run" for many files at once, + // desired run configuration has to be one of "pattern" typed test configuration that may run many tests at once. + // Thus, we sort list of all provided configurations to get desired configuration the first. + private val rcComparator = Comparator { o1, o2 -> + val p1 = o1.configuration.isPatternBased() + val p2 = o2.configuration.isPatternBased() + if (p1 xor p2) { + return@Comparator if (p1) -1 else 1 + } + ConfigurationFromContext.COMPARATOR.compare(o1, o2) + } + + fun runTestsWithCoverage( + model: GenerateTestsModel, + testFilesPointers: MutableList>, + ) { + PsiDocumentManager.getInstance(model.project).commitAndRunReadAction() { + val testClasses = testFilesPointers.map { smartPointer: SmartPsiElementPointer -> smartPointer.containingFile?.childrenOfType()?.firstOrNull() }.filterNotNull() + if (testClasses.isNotEmpty()) { + val locations = + testClasses.map { PsiLocation(model.project, model.testModule, it) }.toTypedArray() + val mapDataContext = MyMapDataContext().also { + it.put(LangDataKeys.PSI_ELEMENT_ARRAY, testClasses.toTypedArray()) + it.put(LangDataKeys.MODULE, model.testModule) + it.put(Location.DATA_KEYS, locations) + it.put(Location.DATA_KEY, locations[0]) + } + val myConfigurationContext = try { + MyConfigurationContext(mapDataContext, testClasses[0]) + } catch (e: Exception) { + logger.error { e } + return@commitAndRunReadAction + } + mapDataContext.putUserData( + ConfigurationContext.SHARED_CONTEXT, + myConfigurationContext + ) + run(IntelliJApiHelper.Target.THREAD_POOL, indicator = null, "Get run configurations from all producers") { + val configurations = ApplicationManager.getApplication().runReadAction(Computable { + return@Computable RunConfigurationProducer.getProducers(model.project) + .mapNotNull { it.findOrCreateConfigurationFromContext(myConfigurationContext) } + .toMutableList().sortedWith(rcComparator) + }) + + val settings = if (configurations.isEmpty()) null else configurations[0].configurationSettings + if (settings != null) { + val executor = if (ProgramRunner.getRunner(CoverageExecutor.EXECUTOR_ID, settings.configuration) != null) { + ExecutorRegistry.getInstance().getExecutorById(CoverageExecutor.EXECUTOR_ID) ?: DefaultRunExecutor.getRunExecutorInstance() + } else { + //Fallback in case 'Code Coverage for Java' plugin is not enabled + DefaultRunExecutor.getRunExecutorInstance() + } + run(IntelliJApiHelper.Target.EDT_LATER, null, "Start run configuration with coverage") { + val configuration = settings.configuration + if (configuration is ConfigurationWithCommandLineShortener) { + configuration.shortenCommandLine = ShortenCommandLine.MANIFEST + } + ExecutionUtil.runConfiguration(settings, executor) + with(RunManagerEx.getInstanceEx(model.project)) { + if (findSettings(settings.configuration) == null) { + settings.isTemporary = true + addConfiguration(settings) + } + //TODO check shouldSetRunConfigurationFromContext in API 2021.3+ + selectedConfiguration = settings + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SdkUtils.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SdkUtils.kt new file mode 100644 index 0000000000..21061688bc --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SdkUtils.kt @@ -0,0 +1,15 @@ +package org.utbot.intellij.plugin.util + +import com.intellij.openapi.module.Module +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.util.lang.JavaVersion + +fun findSdkVersion(module:Module): JavaVersion = + findSdkVersionOrNull(module) ?: error("Cannot define sdk version in module $module") + +fun findSdkVersionOrNull(module: Module): JavaVersion? { + val moduleSdk = ModuleRootManager.getInstance(module).sdk + return JavaVersion.tryParse(moduleSdk?.versionString) +} + + diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SpringConfigurationsHelper.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SpringConfigurationsHelper.kt new file mode 100644 index 0000000000..1c21adfd16 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/SpringConfigurationsHelper.kt @@ -0,0 +1,126 @@ +package org.utbot.intellij.plugin.util + +import java.io.File + +/** + * This class is a converter between full Spring configuration names and shortened versions. + * + * Shortened versions are represented on UI. + * Full names are used in further analysis in utbot-spring-analyzer. + * + * The idea of this implementation is to append parent directories to the file name until all names become unique. + * + * Example: + * - [["config.web.WebConfig", "config.web2.WebConfig", "config.web.AnotherConfig"]] + * -> + * [["web.WebConfig", "web2.WebConfig", "AnotherConfig"]] + */ +class SpringConfigurationsHelper(val configType: SpringConfigurationType) { + + private val nameToInfo = mutableMapOf() + + inner class NameInfo(val fullName: String) { + val shortenedName: String + get() = innerShortName + + private val pathFragments: MutableList = fullName.split(*configType.separatorsToSplitBy).toMutableList() + private var innerShortName = pathFragments.removeLast() + + fun enlargeShortName(): Boolean { + if (pathFragments.isEmpty()) { + return false + } + + val lastElement = pathFragments.removeLast() + innerShortName = "${lastElement}${configType.separatorToConcatenateBy}$innerShortName" + return true + } + } + + fun restoreFullName(shortenedName: String): String = + nameToInfo + .values + .singleOrNull { it.shortenedName == shortenedName } + ?.fullName + ?: error("Full name of configuration file cannot be restored from shortened name $shortenedName") + + fun shortenSpringConfigNames(fullNames: Set): Map { + fullNames.forEach { nameToInfo[it] = NameInfo(it) } + var nameInfoCollection = nameToInfo.values + + // this cycle continues until all shortenedNames become unique + while (nameInfoCollection.size != nameInfoCollection.distinctBy { it.shortenedName }.size) { + nameInfoCollection = nameInfoCollection.sortedBy { it.shortenedName }.toMutableList() + + var index = 0 + while (index < nameInfoCollection.size) { + val curShortenedPath = nameInfoCollection[index].shortenedName + + // here we search a block of shortened paths that are equivalent + // and must be enlarged with new fragment so on. + var maxIndexWithSamePath = index + while (maxIndexWithSamePath < nameInfoCollection.size) { + if (nameInfoCollection[maxIndexWithSamePath].shortenedName == curShortenedPath) { + maxIndexWithSamePath++ + } else { + break + } + } + + // if the size of this block is one, we should not enlarge it + if (index == maxIndexWithSamePath - 1) { + index++ + continue + } + + // otherwise, enlarge the block of shortened names with one new fragment + for (i in index until maxIndexWithSamePath) { + if (!nameInfoCollection[i].enlargeShortName()) { + return collectShortenedNames() + } + } + + // after enlarging the block, we proceed to search for the next block + index = maxIndexWithSamePath + } + } + + return collectShortenedNames() + } + + private fun collectShortenedNames() = nameToInfo.values.associate { it.fullName to it.shortenedName } + +} + +/* + * Transforms active profile information + * from the form of user input to a list of active profiles. + * + * NOTICE: Current user input form is comma-separated values, but it may be changed later. + */ +fun parseProfileExpression(profileExpression: String?, default: String): Array { + if (profileExpression.isNullOrEmpty()) { + return arrayOf(default) + } + + return profileExpression + .filter { !it.isWhitespace() } + .split(',') + .toTypedArray() +} + +@Deprecated("To be deleted") +enum class SpringConfigurationType( + val separatorsToSplitBy: Array, + val separatorToConcatenateBy: String, +) { + ClassConfiguration( + separatorsToSplitBy = arrayOf("."), + separatorToConcatenateBy = ".", + ), + + FileConfiguration( + separatorsToSplitBy = arrayOf(File.separator), + separatorToConcatenateBy = File.separator, + ), +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/UtIdeaProjectModelModifier.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/UtIdeaProjectModelModifier.kt new file mode 100644 index 0000000000..f33eb79230 --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/UtIdeaProjectModelModifier.kt @@ -0,0 +1,95 @@ +package org.utbot.intellij.plugin.util + +import com.intellij.codeInsight.daemon.impl.quickfix.LocateLibraryDialog +import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix +import com.intellij.jarRepository.JarRepositoryManager +import com.intellij.openapi.application.WriteAction +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ExternalLibraryDescriptor +import com.intellij.openapi.roots.ModuleRootModificationUtil +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.roots.impl.IdeaProjectModelModifier +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar +import com.intellij.openapi.roots.libraries.LibraryUtil +import com.intellij.util.PathUtil +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.concurrency.Promise +import org.jetbrains.concurrency.resolvedPromise +import org.jetbrains.idea.maven.project.MavenProjectsManager +import org.jetbrains.idea.maven.utils.library.RepositoryLibraryProperties +import org.jetbrains.jps.model.library.JpsMavenRepositoryLibraryDescriptor +import org.utbot.intellij.plugin.models.mavenCoordinates +import org.utbot.intellij.plugin.ui.utils.isBuildWithGradle + +class UtIdeaProjectModelModifier(val project: Project) : IdeaProjectModelModifier(project) { + override fun addExternalLibraryDependency( + modules: Collection, + descriptor: ExternalLibraryDescriptor, + scope: DependencyScope + ): Promise? { + if (project.isBuildWithGradle) { + return null + } + for (module in modules) { + if (MavenProjectsManager.getInstance(project).isMavenizedModule(module)) { + return null + } + } + + val defaultRoots = descriptor.libraryClassesRoots + val firstModule = ContainerUtil.getFirstItem(modules) ?: return null + val classesRoots = if (defaultRoots.isNotEmpty()) { + LocateLibraryDialog( + firstModule, + defaultRoots, + descriptor.presentableName + ).showAndGetResult() + } else { + val roots = JarRepositoryManager.loadDependenciesModal( + project, + RepositoryLibraryProperties(JpsMavenRepositoryLibraryDescriptor(descriptor.mavenCoordinates)), + /* loadSources = */ false, + /* loadJavadoc = */ false, + /* copyTo = */ null, + /* repositories = */ null + ) + if (roots.isEmpty()) { + return null + } + roots.filter { orderRoot -> orderRoot.type === OrderRootType.CLASSES } + .map { PathUtil.getLocalPath(it.file) }.toList() + } + if (classesRoots.isNotEmpty()) { + val urls = OrderEntryFix.refreshAndConvertToUrls(classesRoots) + if (canLoadModuleLibrary(modules)) { + ModuleRootModificationUtil.addModuleLibrary( + firstModule, + if (classesRoots.size > 1) descriptor.presentableName else null, + urls, + emptyList(), + scope + ) + } else { + WriteAction.run { + LibraryUtil.createLibrary( + LibraryTablesRegistrar.getInstance().getLibraryTable(project), + descriptor.presentableName + ).let { + val model = it.modifiableModel + urls.forEach { url -> model.addRoot(url, OrderRootType.CLASSES) } + model.commit() + modules.forEach { module -> + ModuleRootModificationUtil.addDependency(module, it, scope, false) + } + } + } + } + } + return resolvedPromise() + } + + private fun canLoadModuleLibrary(modules: Collection) = + modules.size == 1 && !ContainerUtil.getFirstItem(modules).project.isBuildWithGradle +} \ No newline at end of file diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/UtMavenProjectModelModifier.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/UtMavenProjectModelModifier.kt new file mode 100644 index 0000000000..f9ab071b2e --- /dev/null +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/util/UtMavenProjectModelModifier.kt @@ -0,0 +1,119 @@ +package org.utbot.intellij.plugin.util + +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ExternalLibraryDescriptor +import com.intellij.openapi.roots.JavaProjectModelModifier +import com.intellij.openapi.util.Trinity +import com.intellij.openapi.util.text.StringUtil +import com.intellij.pom.java.LanguageLevel +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.util.PsiUtilCore +import com.intellij.psi.xml.XmlFile +import com.intellij.util.ThrowableRunnable +import com.intellij.util.xml.DomUtil +import org.jetbrains.concurrency.Promise +import org.jetbrains.concurrency.rejectedPromise +import org.jetbrains.idea.maven.dom.MavenDomBundle +import org.jetbrains.idea.maven.dom.MavenDomUtil +import org.jetbrains.idea.maven.dom.converters.MavenDependencyCompletionUtil +import org.jetbrains.idea.maven.dom.model.MavenDomProjectModel +import org.jetbrains.idea.maven.model.MavenConstants +import org.jetbrains.idea.maven.model.MavenId +import org.jetbrains.idea.maven.project.MavenProject +import org.jetbrains.idea.maven.project.MavenProjectsManager + +class UtMavenProjectModelModifier(val project: Project): JavaProjectModelModifier() { + + private val mavenProjectsManager = MavenProjectsManager.getInstance(project) + + override fun addExternalLibraryDependency( + modules: Collection, + descriptor: ExternalLibraryDescriptor, + scope: DependencyScope + ): Promise? { + for (module in modules) { + if (!mavenProjectsManager.isMavenizedModule(module)) { + return null + } + } + + val mavenId = MavenId(descriptor.libraryGroupId, descriptor.libraryArtifactId, descriptor.preferredVersion) + return addDependency(modules, mavenId, descriptor.preferredVersion, scope) + } + + override fun changeLanguageLevel(module: Module, level: LanguageLevel): Promise = rejectedPromise() + + private fun addDependency( + fromModules: Collection, + mavenId: MavenId, + preferredVersion: String?, + scope: DependencyScope, + ): Promise? { + val models: MutableList> = ArrayList(fromModules.size) + val files: MutableList = ArrayList(fromModules.size) + val projectToUpdate: MutableList = ArrayList(fromModules.size) + val mavenScope = getMavenScope(scope) + + for (from in fromModules) { + if (!mavenProjectsManager.isMavenizedModule(from)) return null + val fromProject: MavenProject = mavenProjectsManager.findProject(from) ?: return null + val model = MavenDomUtil.getMavenDomProjectModel(project, fromProject.file) ?: return null + var scopeToSet: String? = null + var version: String? = null + if (mavenId.groupId != null && mavenId.artifactId != null) { + val managedDependency = MavenDependencyCompletionUtil.findManagedDependency( + model, project, + mavenId.groupId!!, + mavenId.artifactId!! + ) + if (managedDependency != null) { + val managedScope = StringUtil.nullize(managedDependency.scope.stringValue, true) + scopeToSet = if (managedScope == null && MavenConstants.SCOPE_COMPILE == mavenScope || + StringUtil.equals(managedScope, mavenScope) + ) null else mavenScope + } + if (managedDependency == null || StringUtil.isEmpty(managedDependency.version.stringValue)) { + version = preferredVersion + scopeToSet = mavenScope + } + } + models.add(Trinity.create(model, MavenId(mavenId.groupId, mavenId.artifactId, version), scopeToSet)) + files.add(DomUtil.getFile(model)) + projectToUpdate.add(fromProject) + } + + WriteCommandAction.writeCommandAction(project, *PsiUtilCore.toPsiFileArray(files)) + .withName(MavenDomBundle.message("fix.add.dependency")).run( + ThrowableRunnable { + val pdm = PsiDocumentManager.getInstance(project) + for (trinity in models) { + val model = trinity.first + val dependency = MavenDomUtil.createDomDependency(model, null, trinity.second) + val ms = trinity.third + if (ms != null) { + dependency.scope.stringValue = ms + } + val document = + pdm.getDocument(DomUtil.getFile(model)) + if (document != null) { + pdm.doPostponedOperationsAndUnblockDocument(document) + FileDocumentManager.getInstance().saveDocument(document) + } + } + }) + + return mavenProjectsManager.forceUpdateProjects(projectToUpdate) + } + + private fun getMavenScope(scope: DependencyScope): String? = when (scope) { + DependencyScope.RUNTIME -> MavenConstants.SCOPE_RUNTIME + DependencyScope.COMPILE -> MavenConstants.SCOPE_COMPILE + DependencyScope.TEST -> MavenConstants.SCOPE_TEST + DependencyScope.PROVIDED -> MavenConstants.SCOPE_PROVIDED + else -> throw IllegalArgumentException(scope.toString()) + } +} \ No newline at end of file diff --git a/utbot-intellij/src/main/resources/META-INF/plugin.xml b/utbot-intellij/src/main/resources/META-INF/plugin.xml deleted file mode 100644 index 7d006496d0..0000000000 --- a/utbot-intellij/src/main/resources/META-INF/plugin.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - org.utbot.intellij.plugin.id - UnitTestBot - utbot.org - - UnitTestBot plugin for IntelliJ IDEA for Java - 2022.7-beta - com.intellij.modules.platform - com.intellij.modules.java - org.jetbrains.kotlin - - - org.jetbrains.android - - - - - - - - - - - - - - - - - - - - - unit tests with a single action! -
    -
    - The UTBot engine goes through your code instructions and generates regression tests. -
    -
    - The engine finds potential problems in your code: -
    -
    -
      -
    • exceptions
    • -
    • hangs
    • -
    • overflows
    • -
    • and even native crashes
    • -
    -
    - They are not a surprise for you anymore. The engine will find the problems and generate tests for them. -
    -
    - The engine carefully selects tests to maximize statement and branch coverage. Our credo is to maximize test coverage and minimize tests number. -
    -
    - You can try the engine online without installation. -
    -
    - Got ideas? Let us know or become a contributor on our GitHub page -
    -
    - Found an issue? Please, submit it here. - ]]> -
    - -
    diff --git a/utbot-intellij/src/main/resources/META-INF/pluginIcon.svg b/utbot-intellij/src/main/resources/META-INF/pluginIcon.svg deleted file mode 100644 index 08d64eb8d1..0000000000 --- a/utbot-intellij/src/main/resources/META-INF/pluginIcon.svg +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - diff --git a/utbot-intellij/src/main/resources/META-INF/pluginIcon_dark.svg b/utbot-intellij/src/main/resources/META-INF/pluginIcon_dark.svg deleted file mode 100644 index 3cf0a92370..0000000000 --- a/utbot-intellij/src/main/resources/META-INF/pluginIcon_dark.svg +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - diff --git a/utbot-intellij/src/main/resources/log4j2.xml b/utbot-intellij/src/main/resources/log4j2.xml deleted file mode 100644 index 47234d7c78..0000000000 --- a/utbot-intellij/src/main/resources/log4j2.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/data/IdeaBuildSystem.kt b/utbot-intellij/src/test/kotlin/org/utbot/data/IdeaBuildSystem.kt new file mode 100644 index 0000000000..0e602fbff7 --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/data/IdeaBuildSystem.kt @@ -0,0 +1,9 @@ +package org.utbot.data + +enum class IdeaBuildSystem (val system: String) { + + INTELLIJ("IntelliJ"), + GRADLE("Gradle"), + MAVEN("Maven") + +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/data/JDKVersion.kt b/utbot-intellij/src/test/kotlin/org/utbot/data/JDKVersion.kt new file mode 100644 index 0000000000..ea4b872573 --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/data/JDKVersion.kt @@ -0,0 +1,10 @@ +package org.utbot.data + +enum class JDKVersion (val namePart: String, val number: Int, val supported: Boolean) { + + JDK_1_8(namePart = "1.8", 8, true), + JDK_11(namePart = "11", 11, true), + JDK_17(namePart = "17", 17, true), + JDK_19(namePart = "19", 19, false); + override fun toString() = namePart +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/data/RunInfo.kt b/utbot-intellij/src/test/kotlin/org/utbot/data/RunInfo.kt new file mode 100644 index 0000000000..4e89fba84e --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/data/RunInfo.kt @@ -0,0 +1,20 @@ +package org.utbot.data + +import org.utbot.common.utBotTempDirectory +import java.io.File +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.Random + +val TEST_RUN_NUMBER: String = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"))!! + +val tempDirectoryPath: String = utBotTempDirectory.toAbsolutePath().toString() +const val DEFAULT_DIRECTORY_NAME = "Autotests" +val DEFAULT_DIRECTORY_FULL_PATH = tempDirectoryPath + File.separator + DEFAULT_DIRECTORY_NAME +val CURRENT_RUN_DIRECTORY_FULL_PATH = DEFAULT_DIRECTORY_FULL_PATH + File.separator + TEST_RUN_NUMBER +val CURRENT_RUN_DIRECTORY_END = DEFAULT_DIRECTORY_NAME + File.separator + TEST_RUN_NUMBER + +const val SPRING_PROJECT_NAME = "spring-petclinic" +const val SPRING_PROJECT_URL = "https://github.com/spring-projects/spring-petclinic.git" + +val random: Random = Random() diff --git a/utbot-intellij/src/test/kotlin/org/utbot/dialogs/AddFileToGitDialogFixture.kt b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/AddFileToGitDialogFixture.kt new file mode 100644 index 0000000000..32c0f25842 --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/AddFileToGitDialogFixture.kt @@ -0,0 +1,20 @@ +package org.utbot.dialogs + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.* +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.utils.waitFor +import org.utbot.data.IdeaBuildSystem +import java.time.Duration + +@FixtureName("Add File to Git Dialog") +@DefaultXpath("Dialog type", "//*[@title.key='vfs.listener.add.single.title']") +class AddFileToGitDialogFixture( + remoteRobot: RemoteRobot, + remoteComponent: RemoteComponent) : DialogFixture(remoteRobot, remoteComponent) { + + val cancelButton + get() = button( + byXpath("//div[@text.key='button.cancel']")) +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/dialogs/DialogFixture.kt b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/DialogFixture.kt new file mode 100644 index 0000000000..ca643c84cc --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/DialogFixture.kt @@ -0,0 +1,36 @@ +package org.utbot.dialogs + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.CommonContainerFixture +import com.intellij.remoterobot.fixtures.ContainerFixture +import com.intellij.remoterobot.fixtures.FixtureName +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.stepsProcessing.step +import java.time.Duration + +fun ContainerFixture.dialog( + title: String, + timeout: Duration = Duration.ofSeconds(20), + function: DialogFixture.() -> Unit = {}): DialogFixture = step("Search for dialog with title $title") { + find(DialogFixture.byTitle(title), timeout).apply(function) +} + +@FixtureName("Dialog") +open class DialogFixture( + remoteRobot: RemoteRobot, + remoteComponent: RemoteComponent) : CommonContainerFixture(remoteRobot, remoteComponent) { + + companion object { + @JvmStatic + fun byTitle(title: String) = byXpath("title $title", "//div[@title='$title' and @class='MyDialog']") + } + + val title: String + get() = callJs("component.getTitle();") + + val closeButton + get() = button( + byXpath("//div[@class='DialogRootPane']//div[@class='JButton']")) + +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/dialogs/GetFromVersionControlDialogFixture.kt b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/GetFromVersionControlDialogFixture.kt new file mode 100644 index 0000000000..21509c10ec --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/GetFromVersionControlDialogFixture.kt @@ -0,0 +1,44 @@ +package org.utbot.dialogs + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.* +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.utils.keyboard +import org.utbot.data.IdeaBuildSystem +import java.awt.event.KeyEvent +import java.io.File + +@FixtureName("Get from Version Control Dialog") +@DefaultXpath("Dialog type", "//*[@title.key='get.from.version.control']") +class GetFromVersionControlDialogFixture( + remoteRobot: RemoteRobot, + remoteComponent: RemoteComponent) : DialogFixture(remoteRobot, remoteComponent) { + + val urlInput + get() = textField( + byXpath("//div[@class='BorderlessTextField']")) + + val directoryInput + get() = textField( + byXpath("//div[@class='ExtendableTextField']")) + + val cloneButton + get() = button( + byXpath("//div[@text.key='clone.dialog.clone.button']")) + + val cancelButton + get() = button( + byXpath("//div[@text.key='button.cancel']")) + + fun fillDialog(url: String, location: String = "") { + urlInput.keyboard { enterText(url) } + if (directoryInput.hasText(location).not()) { // firstly change directory, otherwise it won't be updated with project name + directoryInput.click() + keyboard{ + hotKey(KeyEvent.VK_CONTROL, KeyEvent.VK_A) + enterText(location.replace(File.separator, File.separator + File.separator)) + } + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/dialogs/NewProjectDialogFixture.kt b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/NewProjectDialogFixture.kt new file mode 100644 index 0000000000..277b033e20 --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/NewProjectDialogFixture.kt @@ -0,0 +1,100 @@ +package org.utbot.dialogs + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.* +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.stepsProcessing.step +import com.intellij.remoterobot.utils.Keyboard +import com.intellij.remoterobot.utils.keyboard +import com.intellij.remoterobot.utils.waitForIgnoringError +import org.utbot.data.IdeaBuildSystem +import org.utbot.data.JDKVersion +import java.awt.event.KeyEvent +import java.io.File +import java.time.Duration +import java.time.Duration.ofSeconds + +@FixtureName("NewProjectDialog") +@DefaultXpath("type", "//*[contains(@title.key, 'title.new.project')]") +class NewProjectDialogFixture(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) + : DialogFixture(remoteRobot, remoteComponent) { + val keyboard: Keyboard = Keyboard(remoteRobot) + + val wizardsList + get() = jList( + byXpath("//div[@class='JBList']")) + + val nameInput + get() = textField( + byXpath("//div[@class='JBTextField']")) + + val locationInput + get() = textField( + byXpath("//div[@class='ExtendableTextField']")) + + val addSampleCodeCheckbox + get() = checkBox( + byXpath("//div[@text.key='label.project.wizard.new.project.add.sample.code']")) + + val jdkComboBox + get() = comboBox( + byXpath("//div[@class='JdkComboBox']"), + Duration.ofSeconds(10)) + + val jdkList + get() = heavyWeightWindow().itemsList + + val createButton + get() = button( + byXpath("//div[@text.key='button.create']")) + + val cancelButton + get() = button( + byXpath("//div[@text.key='button.cancel']")) + + fun selectWizard(wizardName: String) { + if (title != wizardName) { + wizardsList.findText(wizardName).click() + } + } + + fun selectJDK(jdkVersion: String) { + step("Select JDK: $jdkVersion") { + jdkComboBox.click() + var jdkMatching = jdkVersion + waitForIgnoringError(ofSeconds(20)) { + findAll(byXpath("//*[@text.key='progress.title.detecting.sdks']")).isEmpty() + jdkMatching = jdkList.collectItems().first { it.contains(jdkVersion) } + jdkMatching.isEmpty().not() + } + jdkList.clickItem(jdkMatching) + } + } + + fun fillDialog(projectName: String, + location: String = "", locationPart: String = "", + language: String = "Java", + buildSystem: IdeaBuildSystem = IdeaBuildSystem.INTELLIJ, + jdkVersion: JDKVersion, + addSampleCode: Boolean = true) { + step("Fill New Project dialog") { + nameInput.doubleClick() + keyboard.hotKey(KeyEvent.VK_CONTROL, KeyEvent.VK_A) + keyboard.enterText(projectName) + if (!locationInput.hasText{it.text.contains(locationPart)}) { + locationInput.click() + keyboard{ + hotKey(KeyEvent.VK_CONTROL, KeyEvent.VK_A) + enterText(location.replace(File.separator, File.separator + File.separator)) + } + } + this.findText(language).click() + this.findText(buildSystem.system).click() + addSampleCodeCheckbox.setValue(addSampleCode) + if (!jdkComboBox.selectedText().contains(jdkVersion.namePart)) { + selectJDK(jdkVersion.namePart) + } + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/dialogs/OpenOrImportProjectDialogFixture.kt b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/OpenOrImportProjectDialogFixture.kt new file mode 100644 index 0000000000..c96e3e3dde --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/OpenOrImportProjectDialogFixture.kt @@ -0,0 +1,38 @@ +package org.utbot.dialogs + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.* +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.utils.waitFor +import org.utbot.data.IdeaBuildSystem +import java.time.Duration + +@FixtureName("Open or Import Project Dialog") +@DefaultXpath("Dialog type", "//*[@title.key='project.open.select.from.multiple.processors.dialog.title']") +class OpenOrImportProjectDialogFixture( + remoteRobot: RemoteRobot, + remoteComponent: RemoteComponent) : DialogFixture(remoteRobot, remoteComponent) { + + val openAsRadioButtons + get() = radioButtons( + byXpath("//div[@class='JBRadioButton']")) + + val okButton + get() = button( + byXpath("//div[@text.key='button.ok']")) + + val cancelButton + get() = button( + byXpath("//div[@text.key='button.cancel']")) + + fun selectBuildSystem(buildSystem: IdeaBuildSystem) { + waitFor { + openAsRadioButtons.isNotEmpty() + } + openAsRadioButtons.filter { + it.text.contains(buildSystem.system) + }[0].click() + okButton.click() + } +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/dialogs/OpenProjectDialogFixture.kt b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/OpenProjectDialogFixture.kt new file mode 100644 index 0000000000..ec2cf4939e --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/OpenProjectDialogFixture.kt @@ -0,0 +1,21 @@ +package org.utbot.dialogs + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.* +import com.intellij.remoterobot.search.locators.byXpath + +@FixtureName("OpenProjectDialog") +@DefaultXpath("Dialog type", "//*[@title.key='title.open.file.or.project']") +class OpenProjectDialogFixture( + remoteRobot: RemoteRobot, + remoteComponent: RemoteComponent) : DialogFixture(remoteRobot, remoteComponent) { + + val pathInput + get() = textField( + byXpath("//div[@class='BorderlessTextField']")) + + val okButton + get() = button( + byXpath("//div[@text.key='button.ok']")) +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/dialogs/ProjectStructureDialogFixture.kt b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/ProjectStructureDialogFixture.kt new file mode 100644 index 0000000000..3df95ce70d --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/ProjectStructureDialogFixture.kt @@ -0,0 +1,42 @@ +package org.utbot.dialogs + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.* +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.utils.keyboard +import com.intellij.remoterobot.utils.waitForIgnoringError +import org.utbot.data.JDKVersion +import java.time.Duration + +@FixtureName("Project Structure Dialog") +@DefaultXpath("Dialog type", "//*[@title.key='project.settings.display.name']") +class ProjectStructureDialogFixture( + remoteRobot: RemoteRobot, + remoteComponent: RemoteComponent) : DialogFixture(remoteRobot, remoteComponent) { + + val projectJdkCombobox + get() = comboBox( + byXpath("//div[@class='JdkComboBox']")) + + val moduleSdkCombobox + get() = comboBox( + byXpath("//div[@text.key='module.libraries.target.jdk.module.radio']/../div[@class='JdkComboBox']")) + + val okButton + get() = button( + byXpath("//div[@text.key='button.ok']")) + + fun setProjectSdk(jdkVersion: JDKVersion) { + findText("Project").click() + projectJdkCombobox.click() + waitForIgnoringError(Duration.ofSeconds(5)) { + heavyWeightWindow().itemsList.isShowing + } + keyboard { + enterText(jdkVersion.namePart) + enter() + } + } + +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/dialogs/UnitTestBotDialogFixture.kt b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/UnitTestBotDialogFixture.kt new file mode 100644 index 0000000000..2d256cff99 --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/UnitTestBotDialogFixture.kt @@ -0,0 +1,117 @@ +package org.utbot.dialogs + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.* +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.utils.Keyboard +import java.time.Duration + +@FixtureName("UnitTestBotDialog") +@DefaultXpath("Dialog type", "//*[contains(@title, 'UnitTestBot')]") +class UnitTestBotDialogFixture( + remoteRobot: RemoteRobot, + remoteComponent: RemoteComponent) : DialogFixture(remoteRobot, remoteComponent) { + val keyboard: Keyboard = Keyboard(remoteRobot) + + val sdkNotificationLabel + get() = jLabel( + byXpath("//div[@class='SdkNotificationPanel']//div[@defaulticon='fatalError.svg']")) + + val setupSdkLink + get() = actionLink( + byXpath("//div[@class='SdkNotificationPanel']//div[@class='HyperlinkLabel']")) + + val testSourcesRootLabel + get() = jLabel( + byXpath("//div[@text='Test sources root:']")) + + val testSourcesRootComboBox + get() = comboBox( + byXpath("//div[@class='TestFolderComboWithBrowseButton']/div[@class='ComboBox']")) + + val testingFrameworkLabel + get() = jLabel( + byXpath("//div[@text='Testing framework:']")) + + val testingFrameworkComboBox + get() = comboBox( + byXpath("//div[@accessiblename='Testing framework:' and @class='ComboBox']")) + + val mockingStrategyLabel + get() = jLabel( + byXpath("//div[@text='Mocking strategy:']")) + + val mockingStrategyComboBox + get() = comboBox( + byXpath("//div[@accessiblename='Mocking strategy:' and @class='ComboBox']")) + + val mockStaticMethodsCheckbox + get() = checkBox( + byXpath("//div[@text='Mock static methods']")) + + val parameterizedTestsCheckbox + get() = checkBox( + byXpath("//div[@text='Parameterized tests']")) + + val testGenerationTimeoutLabel + get() = jLabel( + byXpath("//div[@text='Test generation timeout:']")) + + val testGenerationTimeoutTextField + get() = textField( + byXpath("//div[@class='JFormattedTextField']")) + + val timeoutSecondsPerClassLabel + get() = jLabel( + byXpath("//div[@text='seconds per class']")) + + val generateTestsForLabel + get() = jLabel( + byXpath("//div[@text='Generate tests for:']")) + + val memberListTable + get() = remoteRobot.find(byXpath("//div[@class='MemberSelectionTable']"), + Duration.ofSeconds(5) + ) + + val generateTestsButton + get() = button( + byXpath("//div[@class='MainButton']")) + + val arrowOnGenerateTestsButton + get() = button( + byXpath("//div[@class='JBOptionButton' and @text='Generate Tests']//div[@class='ArrowButton']")) + + val buttonsList + get() = heavyWeightWindow().itemsList + + + // Spring-specific elements + val springConfigurationLabel + get() = jLabel( + byXpath("//div[@text='Spring configuration:']")) + + val springConfigurationComboBox + get() = comboBox( + byXpath("//div[@accessiblename='Spring configuration:' and @class='ComboBox']")) + + val springTestsTypeLabel + get() = jLabel( + byXpath("//div[@text='Test type:']")) + + val springTestsTypeComboBox + get() = comboBox( + byXpath("//div[@accessiblename='Test type:' and @class='ComboBox']")) + + val springActiveProfilesLabel + get() = jLabel( + byXpath("//div[@text='Active profile(s):']")) + + val springActiveProfilesTextField + get() = textField( + byXpath("//div[@accessiblename='Active profile(s):' and @class='JBTextField']")) + + val integrationTestsWarningDialog: WarningDialogFixture + get() = remoteRobot.find(byXpath( "//div[@title='Warning']")) +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/dialogs/WarningDialogFixture.kt b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/WarningDialogFixture.kt new file mode 100644 index 0000000000..210d33e687 --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/dialogs/WarningDialogFixture.kt @@ -0,0 +1,32 @@ +package org.utbot.dialogs + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.* +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.utils.Keyboard + +@FixtureName("MyDialog") +@DefaultXpath("type", "//div[@class='DialogRootPane']") +class WarningDialogFixture( + remoteRobot: RemoteRobot, + remoteComponent: RemoteComponent) : DialogFixture(remoteRobot, remoteComponent) { + val keyboard: Keyboard = Keyboard(remoteRobot) + + val terminateButton + get() = button( + byXpath("//div[@text.key='button.terminate']")) + + val cancelButton + get() = button( + byXpath("//div[@text.key='button.cancel']")) + + val proceedButton + get() = button( + byXpath("//div[@text='Proceed']")) + + val goBackButton + get() = button( + byXpath("//div[@text='Go Back']")) + +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/elements/ActionMenuFixture.kt b/utbot-intellij/src/test/kotlin/org/utbot/elements/ActionMenuFixture.kt new file mode 100644 index 0000000000..6ec17bc84c --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/elements/ActionMenuFixture.kt @@ -0,0 +1,30 @@ +package org.utbot.pages + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.ComponentFixture +import com.intellij.remoterobot.fixtures.FixtureName +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.utils.waitFor + +fun RemoteRobot.actionMenu(text: String): ActionMenuFixture { + val xpath = byXpath("text '$text'", "//div[@class='ActionMenu' and @text='$text']") + waitFor { + findAll(xpath).isNotEmpty() + } + return findAll(xpath).first() +} + +fun RemoteRobot.actionMenuItem(text: String): ActionMenuItemFixture { + val xpath = byXpath("text '$text'", "//div[@class='ActionMenuItem' and @text='$text']") + waitFor { + findAll(xpath).isNotEmpty() + } + return findAll(xpath).first() +} + +@FixtureName("ActionMenu") +class ActionMenuFixture(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : ComponentFixture(remoteRobot, remoteComponent) + +@FixtureName("ActionMenuItem") +class ActionMenuItemFixture(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : ComponentFixture(remoteRobot, remoteComponent) diff --git a/utbot-intellij/src/test/kotlin/org/utbot/elements/NotificationFixture.kt b/utbot-intellij/src/test/kotlin/org/utbot/elements/NotificationFixture.kt new file mode 100644 index 0000000000..1ba3205e88 --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/elements/NotificationFixture.kt @@ -0,0 +1,35 @@ +package org.utbot.elements + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.CommonContainerFixture +import com.intellij.remoterobot.fixtures.ComponentFixture +import com.intellij.remoterobot.fixtures.DefaultXpath +import com.intellij.remoterobot.fixtures.FixtureName +import com.intellij.remoterobot.search.locators.byXpath +import java.time.Duration.ofSeconds + +@FixtureName("Notification Center Panel") +@DefaultXpath("NotificationCenterPanel type", "//div[@class='NotificationCenterPanel']") +class NotificationFixture(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : CommonContainerFixture(remoteRobot, remoteComponent) { + + val title + get() = jLabel(byXpath("//div[@class='JLabel']"), + ofSeconds(5)) + + val body + get() = remoteRobot.find(byXpath("//div[@class='JEditorPane']"), + ofSeconds(5)) + + val projectLoadButton + get() = button(byXpath("//div[@text.key='unlinked.project.notification.load.action']")) + + // For add file to Git notification + val alwaysAddButton + get() = button(byXpath("//div[contains(@text.key, 'external.files.add.notification.action.add')]")) + + val dontAskAgainButton + get() = button(byXpath("//div[contains(@text.key, 'external.files.add.notification.action.mute')]")) + +} + diff --git a/utbot-intellij/src/test/kotlin/org/utbot/intellij/plugin/ui/utils/RootUtilsTest.kt b/utbot-intellij/src/test/kotlin/org/utbot/intellij/plugin/ui/utils/RootUtilsTest.kt new file mode 100644 index 0000000000..cbc295104f --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/intellij/plugin/ui/utils/RootUtilsTest.kt @@ -0,0 +1,68 @@ +package org.utbot.intellij.plugin.ui.utils + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.CodegenLanguage + +internal class RootUtilsTest { + internal class MockTestSourceRoot(override val dirPath: String) : ITestSourceRoot { + override val dir = null + override val dirName = dirPath.substring(dirPath.lastIndexOf("/") + 1) + override val expectedLanguage = if (dirName == "java") CodegenLanguage.JAVA else CodegenLanguage.KOTLIN + override fun toString()= dirPath + } + + @Test + fun testCommonPrefix() { + val commonPrefix = listOf( + "/UTBotJavaTest/utbot-framework/src/main/java", + "/UTBotJavaTest/utbot-framework/src/main/kotlin", + "/UTBotJavaTest/utbot-framework/src/main/resources" + ).getCommonPrefix() + Assertions.assertEquals("/UTBotJavaTest/utbot-framework/src/main/", commonPrefix) + Assertions.assertTrue(commonPrefix.endsWith(SRC_MAIN)) + } + + @Test + fun testRootSorting() { + val allTestRoots = mutableListOf( + MockTestSourceRoot("/UTBotJavaTest/utbot-analytics/src/test/java"), + MockTestSourceRoot("/UTBotJavaTest/utbot-analytics/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-analytics-torch/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-cli/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-framework/src/test/java"), + MockTestSourceRoot("/UTBotJavaTest/utbot-framework-api/src/test/java"), + MockTestSourceRoot("/UTBotJavaTest/utbot-framework-api/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-framework-test/src/test/java"), + MockTestSourceRoot("/UTBotJavaTest/utbot-framework-test/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-java-fuzzing/src/test/java"), + MockTestSourceRoot("/UTBotJavaTest/utbot-java-fuzzing/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-gradle/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-instrumentation/src/test/java"), + MockTestSourceRoot("/UTBotJavaTest/utbot-instrumentation/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-instrumentation-tests/src/test/java"), + MockTestSourceRoot("/UTBotJavaTest/utbot-instrumentation-tests/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-intellij/src/test/java"), + MockTestSourceRoot("/UTBotJavaTest/utbot-maven/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-sample/src/test/java"), + MockTestSourceRoot("/UTBotJavaTest/utbot-summary/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-summary-tests/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-core/src/test/java"), + MockTestSourceRoot("/UTBotJavaTest/utbot-core/src/test/kotlin"), + MockTestSourceRoot("/UTBotJavaTest/utbot-rd/src/test/kotlin"), + ) + val moduleSourcePaths = listOf( + "/UTBotJavaTest/utbot-framework/src/main/java", + "/UTBotJavaTest/utbot-framework/src/main/kotlin", + "/UTBotJavaTest/utbot-framework/src/main/resources", + ) + val sortedTestRoots = getSortedTestRoots( + allTestRoots, + listOf("/UTBotJavaTest/utbot-core/src/test/java"), + moduleSourcePaths, + CodegenLanguage.JAVA + ) + Assertions.assertEquals("/UTBotJavaTest/utbot-framework/src/test/java", sortedTestRoots.first().toString()) + Assertions.assertEquals("/UTBotJavaTest/utbot-core/src/test/java", sortedTestRoots[1].toString()) + } +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/pages/IdeaFrame.kt b/utbot-intellij/src/test/kotlin/org/utbot/pages/IdeaFrame.kt new file mode 100644 index 0000000000..ee569961bb --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/pages/IdeaFrame.kt @@ -0,0 +1,211 @@ +package org.utbot.pages + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.* +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.stepsProcessing.step +import com.intellij.remoterobot.utils.keyboard +import com.intellij.remoterobot.utils.waitFor +import com.intellij.remoterobot.utils.waitForIgnoringError +import org.assertj.swing.core.MouseButton +import org.utbot.data.IdeaBuildSystem +import org.utbot.dialogs.AddFileToGitDialogFixture +import org.utbot.dialogs.ProjectStructureDialogFixture +import org.utbot.dialogs.UnitTestBotDialogFixture +import org.utbot.dialogs.WarningDialogFixture +import org.utbot.elements.NotificationFixture +import org.utbot.tabs.InspectionViewFixture +import java.awt.event.KeyEvent +import java.time.Duration +import java.time.Duration.ofSeconds + +fun RemoteRobot.idea(function: IdeaFrame.() -> Unit) { + find(timeout = ofSeconds(5)).apply(function) +} + +@DefaultXpath("IdeFrameImpl type", "//div[@class='IdeFrameImpl']") +open class IdeaFrame(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : CommonContainerFixture(remoteRobot, remoteComponent) { + + open val buildSystemToUse: IdeaBuildSystem = IdeaBuildSystem.INTELLIJ + + val projectViewTree + get() = find(byXpath("ProjectViewTree", "//div[@class='ProjectViewTree']"), + ofSeconds(10)) + + val projectName + get() = step("Get project name") { return@step callJs("component.getProject().getName()") } + + val menuBar: JMenuBarFixture + get() = step("Menu...") { + return@step remoteRobot.find(JMenuBarFixture::class.java, JMenuBarFixture.byType()) + } + + val inlineProgressTextPanel + get() = remoteRobot.find(byXpath("//div[@class='InlineProgressPanel']//div[@class='TextPanel']"), + ofSeconds(5)) + + val statusTextPanel + get() = remoteRobot.find(byXpath("//div[@class='StatusPanel']//div[@class='TextPanel']"), + ofSeconds(10)) + + val buildResultInEditor + get() = remoteRobot.find(byXpath("//div[@class='TrafficLightButton']"), + ofSeconds(20)) + + val buildResult + get() = textField(byXpath("//div[contains(@accessiblename.key, 'editor.accessible.name')]"), + ofSeconds(20)) + + // Notifications + val ideError + get() = remoteRobot.find(byXpath( "//div[@class='NotificationCenterPanel'][.//div[@accessiblename.key='error.new.notification.title']]"), + ofSeconds(10)) + + val utbotNotification + get() = remoteRobot.find(byXpath( "//div[@class='NotificationCenterPanel' and contains(.,'UnitTestBot')]"), + ofSeconds(10)) + + val loadProjectNotification + get() = remoteRobot.find(byXpath( "//div[@class='NotificationActionPanel' and contains(.,'Load')]"), + ofSeconds(20)) + + val addToGitNotification + get() = remoteRobot.find(byXpath( "//div[@class='NotificationCenterPanel' and contains(.,'Git')]"), + ofSeconds(10)) + + val inspectionsView + get() = remoteRobot.find(InspectionViewFixture::class.java) + + val problemsTabButton + get() = button( byXpath("//div[contains(@text.key, 'toolwindow.stripe.Problems_View')]")) + + // Dialogs + val unitTestBotDialog + get() = remoteRobot.find(UnitTestBotDialogFixture::class.java) + + val projectStructureDialog + get() = remoteRobot.find(ProjectStructureDialogFixture::class.java) + + val addFileToGitDialog + get() = remoteRobot.find(AddFileToGitDialogFixture::class.java) + + @JvmOverloads + fun dumbAware(timeout: Duration = Duration.ofMinutes(5), function: () -> Unit) { + step("Wait for smart mode") { + waitFor(duration = timeout, interval = ofSeconds(5)) { + runCatching { isDumbMode().not() }.getOrDefault(false) + } + function() + step("..wait for smart mode again") { + waitFor(duration = timeout, interval = ofSeconds(5)) { + isDumbMode().not() + } + } + } + } + + fun isDumbMode(): Boolean { + return callJs(""" + const frameHelper = com.intellij.openapi.wm.impl.ProjectFrameHelper.getFrameHelper(component) + if (frameHelper) { + const project = frameHelper.getProject() + project ? com.intellij.openapi.project.DumbService.isDumb(project) : true + } else { + true + } + """, true) + } + + fun closeProject() { + if (remoteRobot.isMac()) { + keyboard { + hotKey(KeyEvent.VK_SHIFT, KeyEvent.VK_META, KeyEvent.VK_A) + enterText("Close Project") + enter() + } + } else { + menuBar.select("File", "Close Project") + } + try { + remoteRobot.find(WarningDialogFixture::class.java, ofSeconds(1)) + .terminateButton.click() + } catch (ignore: Throwable) {} + } + + fun openUTBotDialogFromProjectViewForClass(classname: String, packageName: String = "") { + step("Call UnitTestBot action") { + waitFor(ofSeconds(200)) { !isDumbMode() } + with(projectViewTree) { + if (hasText(classname).not() && packageName != "") { + findText{it.text.endsWith(packageName)}.doubleClick() + } + findText(classname).click(MouseButton.RIGHT_BUTTON) + } + remoteRobot.actionMenuItem("Generate Tests with UnitTestBot...").click() + } + } + + open fun waitProjectIsBuilt() { + projectViewTree.click() + keyboard { key(KeyEvent.VK_PAGE_UP) } + waitForIgnoringError(ofSeconds(30)) { + projectViewTree.hasText(projectName) + } + waitFor(Duration.ofSeconds(90)) { + !isDumbMode() + } + } + + open fun expandProjectTree() { + with(projectViewTree) { + if (hasText("src").not()) { + findText(projectName).doubleClick() + waitForIgnoringError{ + hasText("src").and(hasText(".idea")) + } + } + } + } + + open fun createNewPackage(packageName: String) { + with(projectViewTree) { + if (hasText("src").not()) { + findText(projectName).doubleClick() + waitFor { hasText("src") } + } + findText("src").click(MouseButton.RIGHT_BUTTON) + } + remoteRobot.actionMenu("New").click() + remoteRobot.actionMenuItem("Package").click() + keyboard { + enterText(packageName) + enter() + } + } + + fun createNewJavaClass(newClassname: String = "Example", + textToClickOn: String = "Main") { + with(projectViewTree) { + findText(textToClickOn).click(MouseButton.RIGHT_BUTTON) + } + remoteRobot.actionMenu("New").click() + remoteRobot.actionMenuItem("Java Class").click() + remoteRobot.keyboard { + enterText(newClassname) + enter() + } + } + + fun openProjectStructureDialog() { + if (remoteRobot.isMac()) { + keyboard { + hotKey(KeyEvent.VK_SHIFT, KeyEvent.VK_META, KeyEvent.VK_A) + enterText("Project Structure...") + enter() + } + } else { + menuBar.select("File", "Project Structure...") + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/pages/IdeaGradleFrame.kt b/utbot-intellij/src/test/kotlin/org/utbot/pages/IdeaGradleFrame.kt new file mode 100644 index 0000000000..11fe5fdce0 --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/pages/IdeaGradleFrame.kt @@ -0,0 +1,53 @@ +package org.utbot.pages + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.DefaultXpath +import com.intellij.remoterobot.utils.waitFor +import com.intellij.remoterobot.utils.waitForIgnoringError +import org.utbot.data.IdeaBuildSystem +import java.time.Duration.ofSeconds + +@DefaultXpath("IdeFrameImpl type", "//div[@class='IdeFrameImpl']") +class IdeaGradleFrame(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : IdeaFrame(remoteRobot, remoteComponent) { + + override val buildSystemToUse = IdeaBuildSystem.GRADLE + + override fun waitProjectIsBuilt() { + super.waitProjectIsBuilt() + try { + waitFor (ofSeconds(90)) { + inlineProgressTextPanel.isShowing.not() + } + } catch (ignore: Throwable) {} + } + + override fun expandProjectTree() { + with(projectViewTree) { + waitForIgnoringError(ofSeconds(10)) { + hasText(projectName) + } + if (hasText("src").not()) { + findText(projectName).doubleClick() + } + waitForIgnoringError{ + hasText("src") + } + if (hasText("main").not()) { + findText("src").doubleClick() + } + waitForIgnoringError{ + hasText("src").and(hasText("main")) + } + if (hasText("java").not()) { + findText("main").doubleClick() + } + waitForIgnoringError{ + hasText("src").and(hasText("main")).and(hasText("java")) + } + if (hasText {it.text.startsWith("org") || it.text.startsWith("com")}.not()) { + findText("java").doubleClick() + } + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/pages/IdeaMavenFrame.kt b/utbot-intellij/src/test/kotlin/org/utbot/pages/IdeaMavenFrame.kt new file mode 100644 index 0000000000..c555880aa5 --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/pages/IdeaMavenFrame.kt @@ -0,0 +1,22 @@ +package org.utbot.pages + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.DefaultXpath +import com.intellij.remoterobot.utils.waitForIgnoringError +import org.utbot.data.IdeaBuildSystem +import java.time.Duration.ofSeconds + +@DefaultXpath("IdeFrameImpl type", "//div[@class='IdeFrameImpl']") +class IdeaMavenFrame(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : IdeaFrame(remoteRobot, remoteComponent) { + + override val buildSystemToUse = IdeaBuildSystem.MAVEN + + override fun waitProjectIsBuilt() { + super.waitProjectIsBuilt() + waitForIgnoringError (ofSeconds(60)) { + projectViewTree.hasText("Main.java").not() + projectViewTree.hasText("Main") + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/pages/WelcomeFrame.kt b/utbot-intellij/src/test/kotlin/org/utbot/pages/WelcomeFrame.kt new file mode 100644 index 0000000000..987085e6ae --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/pages/WelcomeFrame.kt @@ -0,0 +1,80 @@ +package org.utbot.pages + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.* +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.utils.Keyboard +import org.utbot.data.IdeaBuildSystem +import org.utbot.data.JDKVersion +import org.utbot.dialogs.GetFromVersionControlDialogFixture +import org.utbot.dialogs.NewProjectDialogFixture +import org.utbot.dialogs.OpenOrImportProjectDialogFixture +import org.utbot.dialogs.OpenProjectDialogFixture +import java.io.File +import java.time.Duration + +fun RemoteRobot.welcomeFrame(function: WelcomeFrame.()-> Unit) { + find(WelcomeFrame::class.java, Duration.ofSeconds(10)).apply(function) +} + +@FixtureName("Welcome Frame") +@DefaultXpath("Welcome Frame type", "//div[@class='FlatWelcomeFrame']") +class WelcomeFrame(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : CommonContainerFixture(remoteRobot, remoteComponent) { + val keyboard: Keyboard = Keyboard(remoteRobot) + val ideaVersion //ToDO good locator + get() = jLabel(byXpath("//div[@class='TabbedWelcomeScreen']//div[@class='JLabel' and contains(@text,'20')]")) + + val newProjectLink + get() = actionLink(byXpath("New Project","//div[(@class='MainButton' and @text='New Project') or (@accessiblename='New Project' and @class='JButton')]")) + val openProjectLink + get() = actionLink(byXpath("Open","//div[(@class='MainButton' and @text='Open') or (@accessiblename.key='action.WelcomeScreen.OpenProject.text')]")) + val getFromVSCLink + get() = actionLink(byXpath("Get from VCS","//div[(@class='MainButton' and @text='Get from VCS')]")) + val moreActions + get() = button(byXpath("More Action", "//div[@accessiblename='More Actions']")) + + val recentProjectLinks + get() = jTree(byXpath("//div[@class='CardLayoutPanel']//div[@class='Tree']")) + + val newProjectDialog + get() = remoteRobot.find(NewProjectDialogFixture::class.java) + + val openProjectDialog + get() = remoteRobot.find(OpenProjectDialogFixture::class.java) + + val getFromVersionControlDialog + get() = remoteRobot.find(GetFromVersionControlDialogFixture::class.java) + + val openOrImportProjectDialog + get() = remoteRobot.find(OpenOrImportProjectDialogFixture::class.java, + Duration.ofSeconds(120)) + + fun openProjectByPath(location: String, projectName: String = "") { + val separator = File.separator + val localPath = location + separator + projectName + openProjectLink.click() + keyboard.enterText(localPath.replace(separator, separator + separator)) + openProjectDialog.okButton.click() + } + + fun createNewProject(projectName: String, location: String = "", locationPart: String = "", + language: String = "Java", buildSystem: IdeaBuildSystem = IdeaBuildSystem.INTELLIJ, + jdkVersion: JDKVersion, addSampleCode: Boolean = true) { + newProjectLink.click() + newProjectDialog.selectWizard("New Project") + newProjectDialog.fillDialog( + projectName, location, locationPart, language, + buildSystem, jdkVersion, addSampleCode + ) + newProjectDialog.createButton.click() + } + + fun cloneProjectFromVC(url: String, location: String = "", + buildSystem: IdeaBuildSystem) { + getFromVSCLink.click() + getFromVersionControlDialog.fillDialog(url, location) + getFromVersionControlDialog.cloneButton.click() + openOrImportProjectDialog.selectBuildSystem(buildSystem) + } +} diff --git a/utbot-intellij/src/test/kotlin/org/utbot/samples/CodeSamples.kt b/utbot-intellij/src/test/kotlin/org/utbot/samples/CodeSamples.kt new file mode 100644 index 0000000000..5fe1cc06a1 --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/samples/CodeSamples.kt @@ -0,0 +1,40 @@ +package org.utbot.samples + +import com.intellij.remoterobot.fixtures.TextEditorFixture +import com.intellij.remoterobot.utils.keyboard +import org.utbot.data.TEST_RUN_NUMBER +import java.awt.event.KeyEvent + +fun TextEditorFixture.typeAdditionFunction(className: String): String { + editor.selectText(className) + keyboard { + key(KeyEvent.VK_END) + enter() + enterText("public int addition(") + enterText("int a, int b") + key(KeyEvent.VK_END) + enterText(" {") + enter() + enterText("// Test run ${TEST_RUN_NUMBER}") + enter() + enterText("return a + b;") + } + return "@utbot.returnsFrom {@code return a + b;}" +} + +fun TextEditorFixture.typeDivisionFunction(className: String) : String { + editor.selectText(className) + keyboard { + key(KeyEvent.VK_END) + enter() + enterText("public int division(") + enterText("int a, int b") + key(KeyEvent.VK_END) + enterText(" {") + enter() + enterText("// Test run ${TEST_RUN_NUMBER}") + enter() + enterText("return a / b;") + } + return "@utbot.returnsFrom {@code return a / b;}" +} diff --git a/utbot-intellij/src/test/kotlin/org/utbot/steps/IDESteps.kt b/utbot-intellij/src/test/kotlin/org/utbot/steps/IDESteps.kt new file mode 100644 index 0000000000..7119df5ebf --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/steps/IDESteps.kt @@ -0,0 +1,60 @@ +package org.utbot.steps + +import com.intellij.remoterobot.fixtures.JButtonFixture +import com.intellij.remoterobot.fixtures.TextEditorFixture +import com.intellij.remoterobot.fixtures.dataExtractor.contains +import com.intellij.remoterobot.search.locators.byXpath +import com.intellij.remoterobot.stepsProcessing.step +import com.intellij.remoterobot.utils.keyboard +import com.intellij.remoterobot.utils.waitFor +import org.utbot.dialogs.DialogFixture +import org.utbot.dialogs.DialogFixture.Companion.byTitle +import org.utbot.pages.IdeaFrame +import java.awt.event.KeyEvent +import java.time.Duration.ofSeconds + +fun IdeaFrame.autocomplete(text: String) { + step("Autocomplete '" + text + "'") { + keyboard { + enterText(text) + } + heavyWeightWindow(ofSeconds(5)) + .findText(contains(text)) + .click() + keyboard { + enter() + } + } +} + +fun TextEditorFixture.goToLineAndColumn(row: Int, column: Int) { + keyboard { + if (remoteRobot.isMac()) { + hotKey(KeyEvent.VK_META, KeyEvent.VK_L) + } else { + hotKey(KeyEvent.VK_CONTROL, KeyEvent.VK_G) + } + enterText("$row:$column") + enter() + } +} + +fun TextEditorFixture.closeAllTabs() { + val closeTabButtons = remoteRobot.findAll(byXpath("//div[@class='EditorTabs']//div[@myicon='close.svg']")) + closeTabButtons.forEach { + it.click() + } +} + +fun IdeaFrame.closeTipOfTheDay() { + step("Close Tip of the Day if it appears") { + waitFor(ofSeconds(20)) { + remoteRobot.findAll(byXpath("//div[@class='MyDialog'][.//div[@text='Running startup activities...']]")) + .isEmpty() + } + try { + find(byTitle ("Tip of the Day")) + .button("Close").click() + } catch (ignore: Throwable) {} + } +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/tabs/InspectionViewFixture.kt b/utbot-intellij/src/test/kotlin/org/utbot/tabs/InspectionViewFixture.kt new file mode 100644 index 0000000000..b6fc97b370 --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/tabs/InspectionViewFixture.kt @@ -0,0 +1,16 @@ +package org.utbot.tabs + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.data.RemoteComponent +import com.intellij.remoterobot.fixtures.* +import com.intellij.remoterobot.search.locators.byXpath +import java.time.Duration.ofSeconds + +@FixtureName("Inspection Results View") +@DefaultXpath("InspectionResultsView type", "//div[@class='InspectionResultsView']") +class InspectionViewFixture(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : CommonContainerFixture(remoteRobot, remoteComponent) { + + val inspectionTree // not all problems, but only inspections tab + get() = find(byXpath("//div[@class='InspectionTree']"), + ofSeconds(10)) +} diff --git a/utbot-intellij/src/test/kotlin/org/utbot/tests/BaseTest.kt b/utbot-intellij/src/test/kotlin/org/utbot/tests/BaseTest.kt new file mode 100644 index 0000000000..becf8db9eb --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/tests/BaseTest.kt @@ -0,0 +1,92 @@ +package org.utbot.tests + +import com.intellij.remoterobot.RemoteRobot +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.provider.Arguments +import org.utbot.data.IdeaBuildSystem +import org.utbot.data.JDKVersion +import org.utbot.pages.* +import org.utbot.utils.RemoteRobotExtension +import org.utbot.utils.StepsLogger +import java.time.Duration.ofSeconds + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@ExtendWith(RemoteRobotExtension::class) +open class BaseTest { + fun getIdeaFrameForBuildSystem(remoteRobot: RemoteRobot, ideaBuildSystem: IdeaBuildSystem): IdeaFrame { + return when (ideaBuildSystem) { + IdeaBuildSystem.INTELLIJ -> remoteRobot.find(IdeaFrame::class.java, ofSeconds(10)) + IdeaBuildSystem.GRADLE -> remoteRobot.find(IdeaGradleFrame::class.java, ofSeconds(10)) + IdeaBuildSystem.MAVEN -> remoteRobot.find(IdeaMavenFrame::class.java, ofSeconds(10)) + } + } + + @BeforeEach + fun `Close each project before test`(remoteRobot: RemoteRobot): Unit = with(remoteRobot) { + try { + idea { + closeProject() + } + } catch (ignore: Throwable) {} + } + + @AfterEach + fun `Close each project after test`(remoteRobot: RemoteRobot): Unit = with(remoteRobot) { + idea { + closeProject() + } + } + + companion object { + @BeforeAll + @JvmStatic + fun init(remoteRobot: RemoteRobot) { + StepsLogger.init() + } + + internal val supportedProjectsList: List = + addPairsToList(true) + private val unsupportedProjectsList: List = + addPairsToList(false) + + @JvmStatic + fun supportedProjectsProvider(): List { + return supportedProjectsList + } + + @JvmStatic + fun unsupportedProjectsProvider(): List { + return unsupportedProjectsList + } + + @JvmStatic + fun allProjectsProvider(): List { + return supportedProjectsList + unsupportedProjectsList + } + + @JvmStatic + private fun addPairsToList(supported: Boolean): List { + val ideaBuildSystems = IdeaBuildSystem.values() + ideaBuildSystems.shuffle() + var j = 0 + + val listOfArguments: MutableList = mutableListOf() + JDKVersion.values().toMutableList().filter { + it.supported == supported + }.forEach { + listOfArguments.add( + Arguments.of(ideaBuildSystems[j], it) //each (un)supported JDK with a random build system + ) + j++ + if (j >= ideaBuildSystems.size) { + j = 0 + } + } + return listOfArguments + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/tests/CreateProjects.kt b/utbot-intellij/src/test/kotlin/org/utbot/tests/CreateProjects.kt new file mode 100644 index 0000000000..82d34b8dbe --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/tests/CreateProjects.kt @@ -0,0 +1,68 @@ +package org.utbot.tests + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.utils.waitFor +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Tags +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import org.utbot.data.* +import org.utbot.pages.welcomeFrame +import java.io.File +import java.time.Duration + +@Order(1) +class CreateProjects : BaseTest() { + @ParameterizedTest(name = "Create {0} project with JDK {1}") + @Tags(Tag("Setup"), Tag("Java"), Tag("UTBot")) + @MethodSource("allProjectsProvider") + fun createProjectWithJDK( + ideaBuildSystem: IdeaBuildSystem, jdkVersion: JDKVersion, + remoteRobot: RemoteRobot + ) { + val newProjectName = ideaBuildSystem.system + jdkVersion.number + remoteRobot.welcomeFrame { + createNewProject( + projectName = newProjectName, + buildSystem = ideaBuildSystem, + jdkVersion = jdkVersion, + location = CURRENT_RUN_DIRECTORY_FULL_PATH, + locationPart = CURRENT_RUN_DIRECTORY_END + ) + } + val ideaFrame = getIdeaFrameForBuildSystem(remoteRobot, ideaBuildSystem) + return with(ideaFrame) { + waitProjectIsBuilt() + waitFor(Duration.ofSeconds(90)) { + !isDumbMode() + } + } + } + + @Test + @DisplayName("Clone Spring project") + @Tags(Tag("Setup"), Tag("Java"), Tag("Spring"), Tag("UTBot")) + fun cloneSpringProject(remoteRobot: RemoteRobot): Unit = with(remoteRobot) { + welcomeFrame { + cloneProjectFromVC( + SPRING_PROJECT_URL, + CURRENT_RUN_DIRECTORY_FULL_PATH + File.separator + SPRING_PROJECT_NAME, + IdeaBuildSystem.MAVEN + ) + } + with (getIdeaFrameForBuildSystem(remoteRobot, IdeaBuildSystem.GRADLE)) { + try { + loadProjectNotification.projectLoadButton.click() + waitProjectIsBuilt() + } catch (ignore: Throwable) {} + openProjectStructureDialog() + projectStructureDialog.setProjectSdk(JDKVersion.JDK_17) + projectStructureDialog.okButton.click() + waitProjectIsBuilt() + expandProjectTree() + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/tests/SpringUTBotActionTest.kt b/utbot-intellij/src/test/kotlin/org/utbot/tests/SpringUTBotActionTest.kt new file mode 100644 index 0000000000..c38b97b00e --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/tests/SpringUTBotActionTest.kt @@ -0,0 +1,222 @@ +package org.utbot.tests + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.utils.waitForIgnoringError +import org.assertj.core.api.SoftAssertions +import org.junit.jupiter.api.* +import org.utbot.data.* +import org.utbot.pages.idea +import org.utbot.pages.welcomeFrame +import java.io.File +import java.time.Duration + +class SpringUTBotActionTest : BaseTest() { + + val EXISTING_PACKAGE_NAME = "vet" + val EXISTING_CLASS_NAME = "VetController" + + @BeforeEach + fun openSpringProject(remoteRobot: RemoteRobot): Unit = with(remoteRobot) { + welcomeFrame { + findText { + it.text.endsWith(CURRENT_RUN_DIRECTORY_END + File.separator + SPRING_PROJECT_NAME) + }.click() + } + with (getIdeaFrameForBuildSystem(remoteRobot, IdeaBuildSystem.GRADLE)) { + waitProjectIsBuilt() + try { + loadProjectNotification.projectLoadButton.click() + waitProjectIsBuilt() + } catch (ignore: Throwable) {} + expandProjectTree() + openUTBotDialogFromProjectViewForClass(EXISTING_CLASS_NAME, EXISTING_PACKAGE_NAME) + } + } + + @Test + @DisplayName("Check action dialog UI default state in a Spring project") + @Tags(Tag("Spring"), Tag("Java"), Tag("UnitTestBot"), Tag("UI")) + fun checkSpringDefaultActionDialog(remoteRobot: RemoteRobot) { + with (getIdeaFrameForBuildSystem(remoteRobot, IdeaBuildSystem.GRADLE)) { + with (unitTestBotDialog) { + val softly = SoftAssertions() + softly.assertThat(springConfigurationLabel.isVisible()) + softly.assertThat(springConfigurationComboBox.isShowing) + softly.assertThat(springConfigurationComboBox.selectedText()== "No Configuration") + softly.assertThat(springConfigurationComboBox.listValues() + .containsAll(listOf("No Configuration", "PetClinicApplication", "CacheConfiguration"))) + softly.assertThat(springTestsTypeLabel.isVisible()) + softly.assertThat(springTestsTypeComboBox.isShowing) + softly.assertThat(springTestsTypeComboBox.selectedText() == "Unit tests") + softly.assertThat(springActiveProfilesLabel.isShowing) + softly.assertThat(springActiveProfilesTextField.text =="default") + softly.assertThat(mockingStrategyLabel.isVisible()) + softly.assertThat(mockingStrategyComboBox.selectedText() == "Mock everything outside the class") + softly.assertThat(mockingStrategyComboBox.listValues() + .containsAll(listOf("Do not mock", "Mock everything outside the package", "Mock everything outside the class"))) + softly.assertThat(mockStaticMethodsCheckbox.isShowing) + softly.assertThat(parameterizedTestsCheckbox.isShowing) + softly.assertThat(parameterizedTestsCheckbox.isSelected().not()) + softly.assertThat(testGenerationTimeoutLabel.isShowing) + softly.assertThat(testGenerationTimeoutTextField.text.isNotEmpty()) + softly.assertThat(memberListTable.isShowing) + softly.assertThat(memberListTable.columnCount == 1) + softly.assertThat(memberListTable.rowCount == 2) + softly.assertThat(memberListTable.data.getAll().map { it.text } + .containsAll(listOf("showResourcesVetList():Vets", "showVetList(page:int, model:Model):String"))) + softly.assertThat(generateTestsButton.isEnabled()) + softly.assertThat(arrowOnGenerateTestsButton.isShowing) + arrowOnGenerateTestsButton.click() + softly.assertThat(buttonsList.isShowing) + softly.assertThat(buttonsList.collectItems().containsAll(listOf("Generate Tests", "Generate and Run"))) + softly.assertAll() + } + } + } + + @Test + @DisplayName("Check action dialog UI when Spring configuration is selected") + @Tags(Tag("Spring"), Tag("Java"), Tag("UnitTestBot"), Tag("UI")) + fun checkActionDialogWithSpringConfiguration(remoteRobot: RemoteRobot) { + with (getIdeaFrameForBuildSystem(remoteRobot, IdeaBuildSystem.GRADLE)) { + with (unitTestBotDialog) { + springConfigurationComboBox.click() /* ComboBoxFixture::selectItem doesn't work with heavyWeightWindow */ + heavyWeightWindow().itemsList.clickItem("PetClinicApplication") + val softly = SoftAssertions() + softly.assertThat(springConfigurationComboBox.selectedText()== "PetClinicApplication") + softly.assertThat(springConfigurationComboBox.listValues() + .containsAll(listOf("No Configuration", "PetClinicApplication", "CacheConfiguration"))) + softly.assertThat(springTestsTypeComboBox.selectedText() == "Unit tests") + softly.assertThat(springActiveProfilesTextField.text =="default") + softly.assertThat(generateTestsButton.isEnabled()) + softly.assertAll() + } + } + } + + @Test + @DisplayName("Check action dialog UI when Integration tests are selected") + @Tags(Tag("Spring"), Tag("Java"), Tag("UnitTestBot"), Tag("UI")) + fun checkActionDialogWithIntegrationTests(remoteRobot: RemoteRobot) { + with (getIdeaFrameForBuildSystem(remoteRobot, IdeaBuildSystem.GRADLE)) { + with (unitTestBotDialog) { + springConfigurationComboBox.click() /* ComboBoxFixture::selectItem doesn't work with heavyWeightWindow */ + heavyWeightWindow().itemsList.clickItem("PetClinicApplication") + springTestsTypeComboBox.selectItem("Integration tests") + val softly = SoftAssertions() + softly.assertThat(springConfigurationComboBox.selectedText()== "PetClinicApplication") + softly.assertThat(springConfigurationComboBox.listValues() + .containsAll(listOf("No Configuration", "PetClinicApplication", "CacheConfiguration"))) + softly.assertThat(springTestsTypeComboBox.selectedText() == "Unit tests") + softly.assertThat(springActiveProfilesTextField.text =="default") + softly.assertThat(generateTestsButton.isEnabled()) + softly.assertAll() + } + } + } + + @Order(1) // to close git notification + @Test + @DisplayName("Check Spring Unit tests generation") + @Tags(Tag("Spring"), Tag("Java"), Tag("UnitTestBot"), Tag("Unit tests"), Tag("Generate tests")) + fun checkSpringUnitTestsGeneration(remoteRobot: RemoteRobot) { + with (getIdeaFrameForBuildSystem(remoteRobot, IdeaBuildSystem.GRADLE)) { + with (unitTestBotDialog) { + springConfigurationComboBox.click() /* ComboBoxFixture::selectItem doesn't work with heavyWeightWindow */ + heavyWeightWindow().itemsList.clickItem("PetClinicApplication") + unitTestBotDialog.generateTestsButton.click() + } + waitForIgnoringError (Duration.ofSeconds(10)){ + inlineProgressTextPanel.isShowing + } + waitForIgnoringError (Duration.ofSeconds(60)){ + inlineProgressTextPanel.hasText("Generate test cases for class $EXISTING_CLASS_NAME") + } + waitForIgnoringError (Duration.ofSeconds(90)){ + addToGitNotification.isShowing + } + addToGitNotification.alwaysAddButton.click() // otherwise prompt dialog will be shown for each created test file + waitForIgnoringError(Duration.ofSeconds(90)) { + utbotNotification.title.hasText("UnitTestBot: unit tests generated with warnings") + // because project has several test frameworks + } + val softly = SoftAssertions() + softly.assertThat(utbotNotification.body.hasText("Target: org.springframework.samples.petclinic.vet.VetController Overall test methods: 7")) + softly.assertThat(textEditor().editor.text).contains("class ${EXISTING_CLASS_NAME}Test") + softly.assertThat(textEditor().editor.text).contains("@Test\n") + softly.assertThat(textEditor().editor.text).contains("@InjectMocks\n\tprivate VetController vetController;") + softly.assertThat(textEditor().editor.text).contains("@Mock\n\tprivate VetRepository vetRepositoryMock;") + softly.assertThat(textEditor().editor.text).contains("@utbot.classUnderTest {@link ${EXISTING_CLASS_NAME}}") + softly.assertThat(textEditor().editor.text).contains("@utbot.methodUnderTest {@link ${EXISTING_CLASS_NAME}#showResourcesVetList") + softly.assertThat(textEditor().editor.text).contains("@utbot.methodUnderTest {@link ${EXISTING_CLASS_NAME}#showVetList") + softly.assertThat(inspectionsView.inspectionTree.isShowing) + softly.assertThat(inspectionsView.inspectionTree.hasText("Errors detected by UnitTestBot")) + softly.assertThat(inspectionsView.inspectionTree.hasText("${EXISTING_CLASS_NAME}.java")) + buildResultInEditor.rightClick() // to check test class is compilable + softly.assertThat(heavyWeightWindow().data.getAll().toString().contains("error").not()) + problemsTabButton.click() //to close problems view + softly.assertAll() + } + } + + @Test + @DisplayName("Check Spring Integration tests generation") + @Tags(Tag("Spring"), Tag("Java"), Tag("UnitTestBot"), Tag("Integration tests"), Tag("Generate tests")) + fun checkSpringIntegrationTestsGeneration(remoteRobot: RemoteRobot) { + with (getIdeaFrameForBuildSystem(remoteRobot, IdeaBuildSystem.GRADLE)) { + with (unitTestBotDialog) { + springConfigurationComboBox.click() /* ComboBoxFixture::selectItem doesn't work with heavyWeightWindow */ + heavyWeightWindow().itemsList.clickItem("PetClinicApplication") + springTestsTypeComboBox.selectItem("Integration tests") + unitTestBotDialog.generateTestsButton.click() + unitTestBotDialog.integrationTestsWarningDialog.proceedButton.click() + } + waitForIgnoringError (Duration.ofSeconds(10)){ + inlineProgressTextPanel.isShowing + } + waitForIgnoringError (Duration.ofSeconds(60)){ + inlineProgressTextPanel.hasText("Generate test cases for class $EXISTING_CLASS_NAME") + } + waitForIgnoringError(Duration.ofSeconds(90)) { + utbotNotification.title.hasText("UnitTestBot: unit tests generated with warnings") + // because project has several test frameworks + } + val softly = SoftAssertions() + softly.assertThat(utbotNotification.body.hasText("Target: org.springframework.samples.petclinic.vet.VetController Overall test methods: ")) + softly.assertThat(textEditor().editor.text).contains("@SpringBootTest\n") + softly.assertThat(textEditor().editor.text).contains("@BootstrapWith(SpringBootTestContextBootstrapper.class)\n") + softly.assertThat(textEditor().editor.text).contains("@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)\n") + softly.assertThat(textEditor().editor.text).contains("@Transactional\n") + softly.assertThat(textEditor().editor.text).contains("@AutoConfigureTestDatabase\n") + softly.assertThat(textEditor().editor.text).contains("@AutoConfigureMockMvc\n") + softly.assertThat(textEditor().editor.text).contains("class ${EXISTING_CLASS_NAME}Test") + softly.assertThat(textEditor().editor.text).contains("@Test\n") + softly.assertThat(textEditor().editor.text).contains(CONTEXT_LOADS_TEST_TEXT) + softly.assertThat(textEditor().editor.text).contains("///region FUZZER: SUCCESSFUL EXECUTIONS for method showResourcesVetList()") + softly.assertThat(inspectionsView.inspectionTree.isShowing) + softly.assertThat(inspectionsView.inspectionTree.hasText("Errors detected by UnitTestBot")) + softly.assertThat(inspectionsView.inspectionTree.hasText("${EXISTING_CLASS_NAME}.java")) + buildResultInEditor.rightClick() // to check test class is compilable + softly.assertThat(heavyWeightWindow().data.getAll().toString().contains("error").not()) + problemsTabButton.click() //to close problems view + softly.assertAll() + } + } + + @AfterEach + fun closeDialogIfNotClosed (remoteRobot: RemoteRobot): Unit = with(remoteRobot){ + idea { + try { + unitTestBotDialog.closeButton.click() + } catch (ignore: Throwable) {} + } + } + + val CONTEXT_LOADS_TEST_TEXT = """ /** + * This sanity check test fails if the application context cannot start. + */ + @Test + public void contextLoads() { + }""" + +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/tests/UTBotActionTest.kt b/utbot-intellij/src/test/kotlin/org/utbot/tests/UTBotActionTest.kt new file mode 100644 index 0000000000..497d6911fe --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/tests/UTBotActionTest.kt @@ -0,0 +1,83 @@ +package org.utbot.tests + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.utils.waitForIgnoringError +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.SoftAssertions +import org.jetbrains.kotlin.konan.file.File +import org.junit.jupiter.api.* +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import org.utbot.data.* +import org.utbot.pages.* +import org.utbot.samples.typeAdditionFunction +import org.utbot.samples.typeDivisionFunction +import java.time.Duration.ofSeconds + +class UTBotActionTest : BaseTest() { + @ParameterizedTest(name = "Generate tests in {0} project with JDK {1}") + @MethodSource("supportedProjectsProvider") + @Tags(Tag("Java"), Tag("UnitTestBot"), Tag("Positive"), Tag("Generate tests")) + fun checkBasicTestGeneration(ideaBuildSystem: IdeaBuildSystem, jdkVersion: JDKVersion, + remoteRobot: RemoteRobot) { + val createdProjectName = ideaBuildSystem.system + jdkVersion.number + remoteRobot.welcomeFrame { + findText { + it.text.endsWith(CURRENT_RUN_DIRECTORY_END + File.separator + createdProjectName) + }.click() + } + return with (getIdeaFrameForBuildSystem(remoteRobot, ideaBuildSystem)) { + waitProjectIsBuilt() + expandProjectTree() + val newClassName = "Arithmetic" + createNewJavaClass(newClassName, "Main") + val returnsFromTagBody = textEditor().typeDivisionFunction(newClassName) + openUTBotDialogFromProjectViewForClass(newClassName) + unitTestBotDialog.generateTestsButton.click() + waitForIgnoringError (ofSeconds(5)){ + inlineProgressTextPanel.isShowing + } + waitForIgnoringError (ofSeconds(90)){ + inlineProgressTextPanel.hasText("Generate test cases for class $newClassName") + } + waitForIgnoringError(ofSeconds(30)) { + utbotNotification.title.hasText("UnitTestBot: unit tests generated successfully") + } + val softly = SoftAssertions() + softly.assertThat(textEditor().editor.text).contains("class ${newClassName}Test") + softly.assertThat(textEditor().editor.text).contains("@Test\n") + softly.assertThat(textEditor().editor.text).contains("assertEquals(") + softly.assertThat(textEditor().editor.text).contains("@utbot.classUnderTest {@link ${newClassName}}") + softly.assertThat(textEditor().editor.text).contains("@utbot.methodUnderTest {@link ${newClassName}#") + softly.assertThat(textEditor().editor.text).contains(returnsFromTagBody) + //ToDo verify how many tests are generated + //ToDo verify Problems view and Arithmetic exception on it + softly.assertAll() + } + } + + @ParameterizedTest(name = "Check Generate tests button is disabled in {0} project with unsupported JDK {1}") + @MethodSource("unsupportedProjectsProvider") + @Tags(Tag("Java"), Tag("UnitTestBot"), Tag("Negative"), Tag("UI")) + fun checkProjectWithUnsupportedJDK(ideaBuildSystem: IdeaBuildSystem, jdkVersion: JDKVersion, + remoteRobot: RemoteRobot) { + val createdProjectName = ideaBuildSystem.system + jdkVersion.number + remoteRobot.welcomeFrame { + findText { + it.text.endsWith(CURRENT_RUN_DIRECTORY_END + File.separator + createdProjectName) + }.click() + } + return with (getIdeaFrameForBuildSystem(remoteRobot, ideaBuildSystem)) { + waitProjectIsBuilt() + expandProjectTree() + val newClassName = "Arithmetic" + createNewJavaClass(newClassName, "Main") + textEditor().typeAdditionFunction(newClassName) + openUTBotDialogFromProjectViewForClass(newClassName) + assertThat(unitTestBotDialog.generateTestsButton.isEnabled().not()) + assertThat(unitTestBotDialog.sdkNotificationLabel.hasText("SDK version ${jdkVersion.number} is not supported, use 1.8, 11 or 17 instead.")) + assertThat(unitTestBotDialog.setupSdkLink.isShowing) + unitTestBotDialog.closeButton.click() + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/utils/RemoteRobotExtension.kt b/utbot-intellij/src/test/kotlin/org/utbot/utils/RemoteRobotExtension.kt new file mode 100644 index 0000000000..650542c1f2 --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/utils/RemoteRobotExtension.kt @@ -0,0 +1,135 @@ +package org.utbot.utils + +import com.intellij.remoterobot.RemoteRobot +import com.intellij.remoterobot.fixtures.ContainerFixture +import com.intellij.remoterobot.search.locators.byXpath +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.logging.HttpLoggingInterceptor +import org.junit.jupiter.api.extension.AfterTestExecutionCallback +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.ParameterContext +import org.junit.jupiter.api.extension.ParameterResolver +import java.awt.image.BufferedImage +import java.io.ByteArrayOutputStream +import java.io.File +import java.lang.IllegalStateException +import java.lang.reflect.Method +import javax.imageio.ImageIO + +class RemoteRobotExtension : AfterTestExecutionCallback, ParameterResolver { + private val url: String = System.getProperty("remote-robot-url") ?: "http://127.0.0.1:8082" + private val remoteRobot: RemoteRobot = if (System.getProperty("debug-retrofit")?.equals("enable") == true) { + val interceptor: HttpLoggingInterceptor = HttpLoggingInterceptor().apply { + this.level = HttpLoggingInterceptor.Level.BODY + } + val client = OkHttpClient.Builder().apply { + this.addInterceptor(interceptor) + }.build() + RemoteRobot(url, client) + } else { + RemoteRobot(url) + } + private val client = OkHttpClient() + + override fun supportsParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Boolean { + return parameterContext?.parameter?.type?.equals(RemoteRobot::class.java) ?: false + } + + override fun resolveParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Any { + return remoteRobot + } + + override fun afterTestExecution(context: ExtensionContext?) { + val testMethod: Method = context?.requiredTestMethod ?: throw IllegalStateException("test method is null") + val testMethodName = testMethod.name + val testFailed: Boolean = context.executionException?.isPresent ?: false + if (testFailed) { + saveScreenshot(testMethodName) + saveIdeaFrames(testMethodName) + saveHierarchy(testMethodName) + } + } + + private fun saveScreenshot(testName: String) { + fetchScreenShot().save(testName) + } + + private fun saveHierarchy(testName: String) { + val hierarchySnapshot = + saveFile(url, "build/reports", "hierarchy-$testName.html") + if (File("build/reports/styles.css").exists().not()) { + saveFile("$url/styles.css", "build/reports", "styles.css") + } + println("Hierarchy snapshot: ${hierarchySnapshot.absolutePath}") + } + + private fun saveFile(url: String, folder: String, name: String): File { + val response = client.newCall(Request.Builder().url(url).build()).execute() + return File(folder).apply { + mkdirs() + }.resolve(name).apply { + writeText(response.body?.string() ?: "") + } + } + + private fun BufferedImage.save(name: String) { + val bytes = ByteArrayOutputStream().use { b -> + ImageIO.write(this, "png", b) + b.toByteArray() + } + File("build/reports").apply { mkdirs() }.resolve("$name.png").writeBytes(bytes) + } + + private fun saveIdeaFrames(testName: String) { + remoteRobot.findAll(byXpath("//div[@class='IdeFrameImpl']")).forEachIndexed { n, frame -> + val pic = try { + frame.callJs( + """ + importPackage(java.io) + importPackage(javax.imageio) + importPackage(java.awt.image) + const screenShot = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_ARGB); + component.paint(screenShot.getGraphics()) + let pictureBytes; + const baos = new ByteArrayOutputStream(); + try { + ImageIO.write(screenShot, "png", baos); + pictureBytes = baos.toByteArray(); + } finally { + baos.close(); + } + pictureBytes; + """, true + ) + } catch (e: Throwable) { + e.printStackTrace() + throw e + } + pic.inputStream().use { + ImageIO.read(it) + }.save(testName + "_" + n) + } + } + + private fun fetchScreenShot(): BufferedImage { + return remoteRobot.callJs( + """ + importPackage(java.io) + importPackage(javax.imageio) + const screenShot = new java.awt.Robot().createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize())); + let pictureBytes; + const baos = new ByteArrayOutputStream(); + try { + ImageIO.write(screenShot, "png", baos); + pictureBytes = baos.toByteArray(); + } finally { + baos.close(); + } + pictureBytes; + """ + ).inputStream().use { + ImageIO.read(it) + } + } +} \ No newline at end of file diff --git a/utbot-intellij/src/test/kotlin/org/utbot/utils/StepsLogger.kt b/utbot-intellij/src/test/kotlin/org/utbot/utils/StepsLogger.kt new file mode 100644 index 0000000000..a97ddfb70b --- /dev/null +++ b/utbot-intellij/src/test/kotlin/org/utbot/utils/StepsLogger.kt @@ -0,0 +1,15 @@ +package org.utbot.utils + +import com.intellij.remoterobot.stepsProcessing.StepLogger +import com.intellij.remoterobot.stepsProcessing.StepWorker + +object StepsLogger { + private var initialized = false + @JvmStatic + fun init() { + if (initialized.not()) { + StepWorker.registerProcessor(StepLogger()) + initialized = true + } + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/build.gradle.kts b/utbot-java-fuzzing/build.gradle.kts new file mode 100644 index 0000000000..eadcf81b91 --- /dev/null +++ b/utbot-java-fuzzing/build.gradle.kts @@ -0,0 +1,17 @@ +val sootVersion: String by rootProject +val kotlinLoggingVersion: String by rootProject +val rgxgenVersion: String by rootProject +val guavaVersion: String by rootProject + +dependencies { + implementation(project(":utbot-framework-api")) + api(project(":utbot-fuzzing")) + api(project(":utbot-modificators-analyzer")) + + implementation("org.unittestbot.soot:soot-utbot-fork:${sootVersion}") { + exclude(group="com.google.guava", module="guava") + } + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) + implementation(group = "com.github.curious-odd-man", name = "rgxgen", version = rgxgenVersion) + implementation(group = "com.google.guava", name = "guava", version = guavaVersion) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedConcreteValue.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedConcreteValue.kt new file mode 100644 index 0000000000..e70aee4e55 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedConcreteValue.kt @@ -0,0 +1,12 @@ +package org.utbot.fuzzer + +import org.utbot.framework.plugin.api.ClassId + +/** + * Object to pass concrete values to fuzzer + */ +data class FuzzedConcreteValue( + val classId: ClassId, + val value: Any, + val fuzzedContext: FuzzedContext = FuzzedContext.Unknown, +) \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedContext.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedContext.kt new file mode 100644 index 0000000000..4b7993bbb6 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedContext.kt @@ -0,0 +1,45 @@ +package org.utbot.fuzzer + +import org.utbot.framework.plugin.api.ExecutableId + +/** + * Context is a bit of information about [FuzzedConcreteValue]'s conditions. + * + * For example, it can be: + * + * 1. Comparison operations: `a > 2` + * 2. Method call: `Double.isNaN(2.0)` + */ +sealed interface FuzzedContext { + + object Unknown : FuzzedContext + + class Call( + val method: ExecutableId + ) : FuzzedContext { + override fun toString(): String { + return method.toString() + } + } + + enum class Comparison( + val sign: String + ) : FuzzedContext { + EQ("=="), + NE("!="), + GT(">"), + GE(">="), + LT("<"), + LE("<="), + ; + + fun reverse(): Comparison = when (this) { + EQ -> NE + NE -> EQ + GT -> LE + LT -> GE + LE -> GT + GE -> LT + } + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt new file mode 100644 index 0000000000..e2061ab214 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedMethodDescription.kt @@ -0,0 +1,91 @@ +package org.utbot.fuzzer + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId + +/** + * Method traverser is an object, + * that helps to collect information about a method. + * + * @param name pretty name of the method + * @param returnType type of returning value + * @param parameters method parameters types + * @param concreteValues any concrete values to be processed by fuzzer + * + */ +open class FuzzedMethodDescription( + val name: String, + val returnType: ClassId, + val parameters: List, + val concreteValues: Collection = emptyList() +) { + /** + * Name that can be used to generate test names + */ + var compilableName: String? = null + + /** + * Class Name + */ + var className: String? = null + + /** + * Package Name + */ + var packageName: String? = null + + /** + * Canonical name. + */ + var canonicalName: String? = null + + /** + * True, if class is nested. + */ + var isNested: Boolean = false + + /** + * True, if method is static, false if it's not and null if it is not defined. + */ + var isStatic: Boolean? = null + + /** + * Returns parameter name by its index in the signature + */ + var parameterNameMap: (Int) -> String? = { null } + + /** + * For every parameter returns a list with acceptable types. + * Usually it keeps upper bound. + * + * Every parameter can have several parameter types. + * For example [Map] has two type parameters, [Collection] has only one. + * + * Fuzzer doesn't care about interconnection between these types, therefore it waits + * that function already has all necessary information to bound this values. + */ + var fuzzerType: (Int) -> FuzzedType? = { null } + + /** + * Returns true if class should be mocked. + */ + var shouldMock: (ClassId) -> Boolean = { false } + + /** + * Map class id to indices of this class in parameters list. + */ + val parametersMap: Map> by lazy { + val result = mutableMapOf>() + parameters.forEachIndexed { index, classId -> + result.computeIfAbsent(classId) { mutableListOf() }.add(index) + } + result + } + + constructor(executableId: ExecutableId, concreteValues: Collection = emptyList()) : this( + executableId.classId.simpleName + "." + executableId.name, + executableId.returnType, + executableId.parameters, + concreteValues + ) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedType.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedType.kt new file mode 100644 index 0000000000..1fe47a142c --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedType.kt @@ -0,0 +1,20 @@ +package org.utbot.fuzzer + +import org.utbot.framework.plugin.api.ClassId + +/** + * Contains information about classId and generic types, that should be applied. + * + * Note that this class can be replaced by the API mechanism for collecting parametrized types, + * but at the moment it doesn't fully support all necessary operations. + * + * @see ClassId.typeParameters + */ +open class FuzzedType( + val classId: ClassId, + val generics: List = emptyList(), +) { + override fun toString(): String { + return "FuzzedType(classId=$classId, generics=${generics.map { it.classId }})" + } +} diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedValue.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedValue.kt new file mode 100644 index 0000000000..fc10480079 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/FuzzedValue.kt @@ -0,0 +1,24 @@ +package org.utbot.fuzzer + +import org.utbot.framework.plugin.api.UtModel + +/** + * Fuzzed Value stores information about concrete UtModel, reference to [ModelProvider] + * and reasons about why this value was generated. + * + * [summary] is a piece of useful information that clarify why this value has a concrete value. + * + * It supports a special character `%var%` that is used as a placeholder for parameter name. + * + * For example: + * 1. `%var% = 2` for a value that have value 2 + * 2. `%var% >= 4` for a value that shouldn't be less than 4 + * 3. `foo(%var%) returns true` for values that should be passed as a function parameter + * 4. `%var% has special characters` to describe content + */ +open class FuzzedValue( + val model: UtModel, + var summary: String? = null, +) + +fun UtModel.fuzzed(block: FuzzedValue.() -> Unit = {}): FuzzedValue = FuzzedValue(this).apply(block) \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/TypeUtils.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/TypeUtils.kt new file mode 100644 index 0000000000..0eb097513a --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/TypeUtils.kt @@ -0,0 +1,108 @@ +package org.utbot.fuzzer + +import com.google.common.reflect.TypeResolver +import com.google.common.reflect.TypeToken +import mu.KotlinLogging +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.constructor +import org.utbot.framework.plugin.api.util.executable +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.method +import java.lang.reflect.GenericArrayType +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.util.Optional + +private val logger = KotlinLogging.logger {} +private val loggedUnresolvedExecutables = mutableSetOf() + +val Type.typeToken: TypeToken<*> get() = TypeToken.of(this) +inline fun typeTokenOf(): TypeToken = object : TypeToken() {} +inline fun jTypeOf(): Type = typeTokenOf().type + +val FuzzedType.jType: Type get() = toType(cache = mutableMapOf()) + +private fun FuzzedType.toType(cache: MutableMap): Type = cache.getOrPut(this) { + when { + generics.isEmpty() -> classId.jClass + classId.isArray && generics.size == 1 -> GenericArrayType { generics.single().toType(cache) } + else -> object : ParameterizedType { + override fun getActualTypeArguments(): Array = + generics.map { it.toType(cache) }.toTypedArray() + + override fun getRawType(): Type = + classId.jClass + + override fun getOwnerType(): Type? = null + } + } +} + +/** + * Returns fully parameterized type, e.g. for `Map` class + * `Map` type is returned, where `K` and `V` are type variables. + */ +fun Class<*>.toTypeParametrizedByTypeVariables(): Type = + if (typeParameters.isEmpty()) this + else object : ParameterizedType { + override fun getActualTypeArguments(): Array = + typeParameters.toList().toTypedArray() + + override fun getRawType(): Type = + this@toTypeParametrizedByTypeVariables + + override fun getOwnerType(): Type? = + declaringClass?.toTypeParametrizedByTypeVariables() + } + +/** + * Returns types of arguments that should be passed to [executableId] for it to return [neededType] and `null` iff + * [executableId] can't return [neededType] (e.g. if it returns `List` while `List` is needed). + * + * For example, if [executableId] is [Optional.of] and [neededType] is `Optional`, + * then one element list containing `String` type is returned. + */ +fun resolveParameterTypes( + executableId: ExecutableId, + neededType: Type +): List? { + return try { + val actualType = when (executableId) { + is MethodId -> executableId.method.genericReturnType + is ConstructorId -> executableId.constructor.declaringClass.toTypeParametrizedByTypeVariables() + } + + val neededClass = neededType.typeToken.rawType + val actualClass = actualType.typeToken.rawType + + if (!neededClass.isAssignableFrom(actualClass)) + return null + + @Suppress("UNCHECKED_CAST") + val actualSuperType = actualType.typeToken.getSupertype(neededClass as Class).type + val typeResolver = try { + TypeResolver().where(actualSuperType, neededType) + } catch (e: Exception) { + // TypeResolver.where() throws an exception when unification of actual & needed types fails + // e.g. when unifying Optional and Optional + return null + } + + // in some cases when bounded wildcards are involved TypeResolver.where() doesn't throw even though types are + // incompatible (e.g. when needed type is `List` while actual super type is `List`) + if (!typeResolver.resolveType(actualSuperType).typeToken.isSubtypeOf(neededType)) + return null + + executableId.executable.genericParameterTypes.map { + typeResolver.resolveType(it) + } + } catch (e: Exception) { + if (loggedUnresolvedExecutables.add(executableId)) + logger.error(e) { "Failed to resolve types for $executableId, using unresolved generic type" } + + executableId.executable.genericParameterTypes.toList() + } +} diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/UtFuzzedExecution.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/UtFuzzedExecution.kt new file mode 100644 index 0000000000..806f3126ec --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/UtFuzzedExecution.kt @@ -0,0 +1,101 @@ +package org.utbot.fuzzer + +import org.utbot.framework.plugin.api.* + +/** + * Fuzzed execution. + * + * Contains: + * - execution parameters, including thisInstance; + * - result; + * - static fields changed during execution; + * - coverage information (instructions) if this execution was obtained from the concrete execution. + * - comments, method names and display names created by utbot-summary module. + */ +class UtFuzzedExecution( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage? = null, + summary: List? = null, + testMethodName: String? = null, + displayName: String? = null, + val fuzzingValues: List? = null, + val fuzzedMethodDescription: FuzzedMethodDescription? = null, + override val instrumentation: List = emptyList(), +) : UtExecution(stateBefore, stateAfter, result, coverage, summary, testMethodName, displayName), UtExecutionWithInstrumentation { + /** + * By design the 'before' and 'after' states contain info about the same fields. + * It means that it is not possible for a field to be present at 'before' and to be absent at 'after'. + * The reverse is also impossible. + */ + val staticFields: Set + get() = stateBefore.statics.keys // TODO: should we keep it for the Fuzzed Execution? + + override fun copy( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage?, + summary: List?, + testMethodName: String?, + displayName: String? + ): UtExecution { + return UtFuzzedExecution( + stateBefore, + stateAfter, + result, + coverage, + summary, + testMethodName, + displayName, + fuzzingValues = fuzzingValues, + fuzzedMethodDescription = fuzzedMethodDescription, + instrumentation = instrumentation, + ) + } + + override fun toString(): String = buildString { + append("UtFuzzedExecution(") + appendLine() + + append(":") + appendLine() + append(stateBefore) + appendLine() + + append(":") + appendLine() + append(stateAfter) + appendLine() + + append(":") + appendLine() + append(result) + append(")") + } + + fun copy( + stateBefore: EnvironmentModels = this.stateBefore, + stateAfter: EnvironmentModels = this.stateAfter, + result: UtExecutionResult = this.result, + coverage: Coverage? = this.coverage, + summary: List? = this.summary, + testMethodName: String? = this.testMethodName, + displayName: String? = this.displayName, + fuzzingValues: List? = this.fuzzingValues, + fuzzedMethodDescription: FuzzedMethodDescription? = this.fuzzedMethodDescription, + instrumentation: List = this.instrumentation, + ): UtExecution = UtFuzzedExecution( + stateBefore = stateBefore, + stateAfter = stateAfter, + result = result, + coverage = coverage, + summary = summary, + testMethodName = testMethodName, + displayName = displayName, + fuzzingValues = fuzzingValues, + fuzzedMethodDescription = fuzzedMethodDescription, + instrumentation = instrumentation, + ) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt new file mode 100644 index 0000000000..bc50efcfa0 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/objects/AssembleModelUtils.kt @@ -0,0 +1,112 @@ +package org.utbot.fuzzer.objects + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.util.voidClassId + +fun ClassId.create( + block: AssembleModelDsl.() -> Unit +): UtAssembleModel { + return AssembleModelDsl(this).apply(block).build() +} + +class AssembleModelDsl internal constructor( + val classId: ClassId +) { + val using = KeyWord.Using + val call = KeyWord.Call + val constructor = KeyWord.Constructor(classId) + val method = KeyWord.Method(classId) + val field = KeyWord.Field(classId) + + var id: () -> Int? = { null } + var name: (Int?) -> String = { "" } + + private lateinit var initialization: () -> UtExecutableCallModel + private val modChain = mutableListOf<(UtAssembleModel) -> UtStatementModel>() + + fun params(vararg params: ClassId) = params.toList() + + fun values(vararg model: UtModel) = model.toList() + + infix fun KeyWord.Using.instance(executableId: T) = UsingDsl(executableId) + + infix fun KeyWord.Call.instance(executableId: T) = CallDsl(executableId, false) + + infix fun KeyWord.Call.instance(field: T) = FieldDsl(field, false) + + infix fun KeyWord.Using.static(executableId: T) = UsingDsl(executableId) + + infix fun KeyWord.Call.static(executableId: T) = CallDsl(executableId, true) + + infix fun KeyWord.Call.static(field: T) = FieldDsl(field, true) + + @Suppress("UNUSED_PARAMETER") + infix fun KeyWord.Using.empty(ignored: KeyWord.Constructor) { + initialization = { UtExecutableCallModel(null, ConstructorId(classId, emptyList()), emptyList()) } + } + + infix fun ConstructorId.with(models: List) { + initialization = { UtExecutableCallModel(null, this, models) } + } + + infix fun UsingDsl.with(models: List) { + initialization = { UtExecutableCallModel(null, executableId, models) } + } + + infix fun CallDsl.with(models: List) { + modChain += { UtExecutableCallModel(it, executableId, models.toList()) } + } + + infix fun FieldDsl.with(model: UtModel) { + modChain += { UtDirectSetFieldModel(it, fieldId, model) } + } + + internal fun build(): UtAssembleModel { + val objectId = id() + return UtAssembleModel( + id = objectId, + classId = classId, + modelName = name(objectId), + instantiationCall = initialization() + ) { + modChain.map { it(this) } + } + } + + sealed class KeyWord { + object Using : KeyWord() + object Call : KeyWord() + class Constructor(val classId: ClassId) : KeyWord() { + operator fun invoke(vararg params: ClassId): ConstructorId { + return ConstructorId(classId, params.toList()) + } + } + class Method(val classId: ClassId) : KeyWord() { + operator fun invoke(name: String, params: List = emptyList(), returns: ClassId = voidClassId): MethodId { + return MethodId(classId, name, returns, params) + } + + operator fun invoke(classId: ClassId, name: String, params: List = emptyList(), returns: ClassId = voidClassId): MethodId { + return MethodId(classId, name, returns, params) + } + } + class Field(val classId: ClassId) : KeyWord() { + operator fun invoke(name: String): FieldId { + return FieldId(classId, name) + } + } + } + + class UsingDsl(val executableId: ExecutableId) + class CallDsl(val executableId: ExecutableId, val isStatic: Boolean) + class FieldDsl(val fieldId: FieldId, val isStatic: Boolean) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt new file mode 100644 index 0000000000..0dc668cc8c --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/JavaLanguage.kt @@ -0,0 +1,251 @@ +package org.utbot.fuzzing + +import mu.KotlinLogging +import org.utbot.framework.fuzzer.IdentityPreservingIdGenerator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.Instruction +import org.utbot.framework.plugin.api.util.* +import org.utbot.fuzzer.* +import org.utbot.fuzzing.providers.* +import org.utbot.fuzzing.utils.Trie +import java.lang.reflect.* +import java.util.concurrent.CancellationException +import java.util.concurrent.TimeUnit +import kotlin.random.Random + +private val logger = KotlinLogging.logger {} + +typealias JavaValueProvider = ValueProvider + +class FuzzedDescription( + val description: FuzzedMethodDescription, + val tracer: Trie, + val typeCache: MutableMap, + val random: Random, + val scope: Scope? = null +) : Description( + description.parameters.mapIndexed { index, classId -> + description.fuzzerType(index) ?: FuzzedType(classId) + } +) { + val constants: Sequence + get() = description.concreteValues.asSequence() + + override fun clone(scope: Scope): FuzzedDescription { + return FuzzedDescription(description, tracer, typeCache, random, scope) + } +} + +fun defaultValueProviders(idGenerator: IdentityPreservingIdGenerator) = listOf( + BooleanValueProvider, + IntegerValueProvider, + FloatValueProvider, + StringValueProvider, + NumberValueProvider, + anyObjectValueProvider(idGenerator), + ArrayValueProvider(idGenerator), + EnumValueProvider(idGenerator), + ListSetValueProvider(idGenerator), + MapValueProvider(idGenerator), + IteratorValueProvider(idGenerator), + EmptyCollectionValueProvider(idGenerator), + DateValueProvider(idGenerator), + VoidValueProvider, + NullValueProvider, +) + +suspend fun runJavaFuzzing( + idGenerator: IdentityPreservingIdGenerator, + methodUnderTest: ExecutableId, + constants: Collection, + names: List, + providers: List = defaultValueProviders(idGenerator), + exec: suspend (thisInstance: FuzzedValue?, description: FuzzedDescription, values: List) -> BaseFeedback, FuzzedType, FuzzedValue> +) { + val random = Random(0) + val classUnderTest = methodUnderTest.classId + val name = methodUnderTest.classId.simpleName + "." + methodUnderTest.name + val returnType = methodUnderTest.returnType + val parameters = methodUnderTest.parameters + + // For a concrete fuzzing run we need to track types we create. + // Because of generics can be declared as recursive structures like `>`, + // we should track them by reference and do not call `equals` and `hashCode` recursively. + val typeCache = hashMapOf() + /** + * To fuzz this instance, the class of it is added into head of parameters list. + * Done for compatibility with old fuzzer logic and should be reworked more robust way. + */ + fun createFuzzedMethodDescription(self: ClassId?) = FuzzedMethodDescription( + name, returnType, listOfNotNull(self) + parameters, constants + ).apply { + compilableName = if (!methodUnderTest.isConstructor) methodUnderTest.name else null + className = classUnderTest.simpleName + canonicalName = classUnderTest.canonicalName + isNested = classUnderTest.isNested + isStatic = methodUnderTest.isStatic + packageName = classUnderTest.packageName + parameterNameMap = { index -> + when { + self != null && index == 0 -> "this" + self != null -> names.getOrNull(index - 1) + else -> names.getOrNull(index) + } + } + fuzzerType = { + try { + when { + self != null && it == 0 -> toFuzzerType(methodUnderTest.executable.declaringClass, typeCache) + self != null -> toFuzzerType(methodUnderTest.executable.genericParameterTypes[it - 1], typeCache) + else -> toFuzzerType(methodUnderTest.executable.genericParameterTypes[it], typeCache) + } + } catch (_: Throwable) { + null + } + } + shouldMock = { false } + } + + val thisInstance = with(methodUnderTest) { + if (!isStatic && !isConstructor) { classUnderTest } else { null } + } + val tracer = Trie(Instruction::id) + val descriptionWithOptionalThisInstance = FuzzedDescription(createFuzzedMethodDescription(thisInstance), tracer, typeCache, random) + val descriptionWithOnlyParameters = FuzzedDescription(createFuzzedMethodDescription(null), tracer, typeCache, random) + val start = System.nanoTime() + try { + logger.info { "Starting fuzzing for method: $methodUnderTest" } + logger.info { "\tuse thisInstance = ${thisInstance != null}" } + logger.info { "\tparameters = $parameters" } + var totalExecutionCalled = 0 + runFuzzing( + provider = ValueProvider.of(providers), + description = descriptionWithOptionalThisInstance, random, + configuration = Configuration() + ) { _, t -> + totalExecutionCalled++ + if (thisInstance == null) { + exec(null, descriptionWithOnlyParameters, t) + } else { + exec(t.first(), descriptionWithOnlyParameters, t.drop(1)) + } + } + val totalFuzzingTime = System.nanoTime() - start + logger.info { "Finishing fuzzing for method: $methodUnderTest in ${TimeUnit.NANOSECONDS.toMillis(totalFuzzingTime)} ms" } + logger.info { "\tTotal execution called: $totalExecutionCalled" } + } catch (ce: CancellationException) { + val totalFuzzingTime = System.nanoTime() - start + logger.info { "Fuzzing is stopped because of timeout. Total execution time: ${TimeUnit.NANOSECONDS.toMillis(totalFuzzingTime)} ms" } + logger.debug(ce) { "\tStacktrace:" } + } catch (t: Throwable) { + logger.info(t) { "Fuzzing is stopped because of an error" } + } +} + +/** + * Traverse though type hierarchy of this fuzzed type. + * Ignores all set [FuzzedType.generics] of source type. + * + * todo Process types like `Fuzzed[Any, generics = T1, T2]` to match those T1 and T2 types with superclass and interfaces + */ +internal fun FuzzedType.traverseHierarchy(typeCache: MutableMap): Sequence = sequence { + val typeQueue = mutableListOf(this@traverseHierarchy) + var index = 0 + while (typeQueue.isNotEmpty()) { + val next = typeQueue.removeFirst() + if (index++ > 0) { + yield(next) + } + val jClass = next.classId.jClass + val superclass = jClass.genericSuperclass + if (superclass != null) { + typeQueue += toFuzzerType(superclass, typeCache) + } + typeQueue += jClass.genericInterfaces.asSequence().map { toFuzzerType(it, typeCache) } + } +} + +/** + * Resolve a fuzzer type that has class info and some generics. + * + * @param type to be resolved + * @param cache is used to store same [FuzzedType] for same java types + */ +fun toFuzzerType(type: Type, cache: MutableMap): FuzzedType { + return toFuzzerType( + type = type, + classId = { t -> toClassId(t, cache) }, + generics = { t -> toGenerics(t) }, + cache = cache, + ) +} + +/** + * Resolve a fuzzer type that has class info and some generics. + * + * Cache is used to stop recursive call in case of some recursive class definition like: + * + * ``` + * public > call(T type) { ... } + * ``` + * + * @param type to be resolved into a fuzzed type. + * @param classId is a function that produces classId by general type. + * @param generics is a function that produced a list of generics for this concrete type. + * @param cache is used to store all generated types. + */ +private fun toFuzzerType( + type: Type, + classId: (type: Type) -> ClassId, + generics: (parent: Type) -> Array, + cache: MutableMap, +): FuzzedType { + val g = mutableListOf() + val t = type.replaceWithUpperBoundUntilNotTypeVariable() + var target = cache[t] + if (target == null) { + target = FuzzedType(classId(t), g) + cache[t] = target + g += generics(t).map { + toFuzzerType(it, classId, generics, cache) + } + } + return target +} + +internal fun Type.replaceWithUpperBoundUntilNotTypeVariable() : Type { + var type: Type = this + while (type is TypeVariable<*>) { + type = type.bounds.firstOrNull() ?: java.lang.Object::class.java + } + return type +} + +private fun toClassId(type: Type, cache: MutableMap): ClassId { + return when (type) { + is WildcardType -> type.upperBounds.firstOrNull()?.let { toClassId(it, cache) } ?: objectClassId + is GenericArrayType -> { + val genericComponentType = type.genericComponentType + val classId = toFuzzerType(genericComponentType, cache).classId + if (genericComponentType !is GenericArrayType) { + ClassId("[L${classId.name};", classId) + } else { + ClassId("[" + classId.name, classId) + } + } + is ParameterizedType -> (type.rawType as Class<*>).id + is Class<*> -> type.id + else -> error("unknown type: $type") + } +} + +private fun toGenerics(t: Type) : Array { + return when (t) { + is WildcardType -> t.upperBounds.firstOrNull()?.let { toGenerics(it) } ?: emptyArray() + is GenericArrayType -> arrayOf(t.genericComponentType) + is ParameterizedType -> t.actualTypeArguments + is Class<*> -> t.typeParameters + else -> emptyArray() + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Arrays.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Arrays.kt new file mode 100644 index 0000000000..b9bbba8018 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Arrays.kt @@ -0,0 +1,55 @@ +package org.utbot.fuzzing.providers + +import org.utbot.framework.fuzzer.IdGenerator +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.plugin.api.util.isArray +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.* + +class ArrayValueProvider( + val idGenerator: IdGenerator, +) : JavaValueProvider { + + override fun accept(type: FuzzedType) = type.classId.isArray + + override fun generate( + description: FuzzedDescription, + type: FuzzedType + ) = sequence> { + yield( + Seed.Collection( + construct = Routine.Collection { + UtArrayModel( + id = idGenerator.createId(), + classId = type.classId, + length = it, + constModel = type.classId.elementClassId!!.defaultValueModel(), + stores = hashMapOf(), + ).fuzzed { + summary = "%var% = ${type.classId.elementClassId!!.simpleName}[$it]" + } + }, + modify = Routine.ForEach(listOf(resolveArrayElementType(type))) { self, i, values -> + (self.model as UtArrayModel).stores[i] = values.first().model + } + )) + } + + /** + * Resolves array's element type. In case a generic type is used, like `T[]` the type should pass generics. + * + * For example, List[] returns List. + */ + private fun resolveArrayElementType(arrayType: FuzzedType): FuzzedType = when { + !arrayType.classId.isArray -> error("$arrayType is not array") + arrayType.generics.size == 1 -> arrayType.generics.first() + arrayType.generics.isEmpty() -> FuzzedType( + arrayType.classId.elementClassId ?: error("Element classId of $arrayType is not found") + ) + + else -> error("Array has ${arrayType.generics.size} generic type for ($arrayType), that should not happen") + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt new file mode 100644 index 0000000000..3a00334d3c --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Collections.kt @@ -0,0 +1,284 @@ +package org.utbot.fuzzing.providers + +import com.google.common.reflect.TypeToken +import org.utbot.framework.fuzzer.IdGenerator +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.* +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzer.jType +import org.utbot.fuzzing.* +import org.utbot.fuzzing.utils.hex +import kotlin.reflect.KClass + +class EmptyCollectionValueProvider( + val idGenerator: IdGenerator +) : JavaValueProvider { + private class Info(val classId: ClassId, val methodName: String, val returnType: ClassId = classId) + + private val unmodifiableCollections = listOf( + Info(java.util.List::class.id, "emptyList"), + Info(java.util.Set::class.id, "emptySet"), + Info(java.util.Map::class.id, "emptyMap"), + Info(java.util.Collection::class.id, "emptyList", returnType = java.util.List::class.id), + Info(java.lang.Iterable::class.id, "emptyList", returnType = java.util.List::class.id), + Info(java.util.Iterator::class.id, "emptyIterator"), + ) + + private val emptyCollections = listOf( + Info(java.util.NavigableSet::class.id, "constructor", java.util.TreeSet::class.id), + Info(java.util.SortedSet::class.id, "constructor", java.util.TreeSet::class.id), + Info(java.util.NavigableMap::class.id, "constructor", java.util.TreeMap::class.id), + Info(java.util.SortedMap::class.id, "constructor", java.util.TreeMap::class.id), + ) + + override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence { + unmodifiableCollections + .asSequence() + .filter { it.classId == type.classId } + .forEach { info -> + yieldWith(info.classId, MethodId(java.util.Collections::class.id, info.methodName, info.returnType, emptyList())) + } + emptyCollections + .asSequence() + .filter { it.classId == type.classId } + .forEach { info -> + yieldWith(info.classId, ConstructorId(info.returnType, emptyList())) + } + } + + private suspend fun SequenceScope>.yieldWith(classId: ClassId, executableId: ExecutableId) { + yield(Seed.Recursive( + construct = Routine.Create(executableId.parameters.map { FuzzedType(it) }) { value -> + UtAssembleModel( + id = idGenerator.createId(), + classId = classId, + modelName = "", + instantiationCall = UtExecutableCallModel(null, executableId, value.map { it.model }) + ).fuzzed { + summary = "%var% = ${executableId.classId.simpleName}#${executableId.name}" + } + }, + empty = Routine.Empty { + if (executableId.parameters.isEmpty()) { + UtAssembleModel( + id = idGenerator.createId(), + classId = classId, + modelName = "", + instantiationCall = UtExecutableCallModel(null, executableId, emptyList()) + + ).fuzzed{ + summary = "%var% = ${executableId.classId.simpleName}#${executableId.name}" + } + } else { + nullFuzzedValue(classId) + } + }, + )) + } +} + +class MapValueProvider( + idGenerator: IdGenerator +) : CollectionValueProvider(idGenerator, java.util.Map::class.id) { + + override fun resolveType(description: FuzzedDescription, type: FuzzedType) = sequence { + val (keyGeneric, valueGeneric) = resolveGenericsOfSuperClass>(description, type) + when (type.classId) { + java.util.Map::class.id -> { + if (keyGeneric.classId isSubtypeOf Comparable::class) { + yield(FuzzedType(java.util.TreeMap::class.id, listOf(keyGeneric, valueGeneric))) + } + yield(FuzzedType(java.util.HashMap::class.id, listOf(keyGeneric, valueGeneric))) + } + java.util.NavigableMap::class.id, + java.util.SortedMap::class.id -> { + if (keyGeneric.classId isSubtypeOf Comparable::class) { + yield(FuzzedType(java.util.TreeMap::class.id, listOf(keyGeneric, valueGeneric))) + } + } + else -> yieldConcreteClass(FuzzedType(type.classId, listOf(keyGeneric, valueGeneric))) + } + } + + override fun findMethod(resolvedType: FuzzedType, values: List): MethodId { + return MethodId(resolvedType.classId, "put", objectClassId, listOf(objectClassId, objectClassId)) + } +} + +class ListSetValueProvider( + idGenerator: IdGenerator +) : CollectionValueProvider(idGenerator, java.util.Collection::class.id) { + + override fun resolveType(description: FuzzedDescription, type: FuzzedType) = sequence { + val (generic) = resolveGenericsOfSuperClass>(description, type) + when (type.classId) { + java.util.Queue::class.id, + java.util.Deque::class.id-> { + yield(FuzzedType(java.util.ArrayDeque::class.id, listOf(generic))) + } + java.util.List::class.id -> { + yield(FuzzedType(java.util.ArrayList::class.id, listOf(generic))) + yield(FuzzedType(java.util.LinkedList::class.id, listOf(generic))) + } + java.util.Collection::class.id -> { + yield(FuzzedType(java.util.ArrayList::class.id, listOf(generic))) + yield(FuzzedType(java.util.HashSet::class.id, listOf(generic))) + } + java.util.Set::class.id -> { + if (generic.classId isSubtypeOf Comparable::class) { + yield(FuzzedType(java.util.TreeSet::class.id, listOf(generic))) + } + yield(FuzzedType(java.util.HashSet::class.id, listOf(generic))) + } + java.util.NavigableSet::class.id, + java.util.SortedSet::class.id -> { + if (generic.classId isSubtypeOf Comparable::class) { + yield(FuzzedType(java.util.TreeSet::class.id, listOf(generic))) + } + } + else -> yieldConcreteClass(FuzzedType(type.classId, listOf(generic))) + } + } + + override fun findMethod(resolvedType: FuzzedType, values: List): MethodId { + return MethodId(resolvedType.classId, "add", booleanClassId, listOf(objectClassId)) + } +} + +/** + * Accepts only instances of Collection or Map + */ +abstract class CollectionValueProvider( + private val idGenerator: IdGenerator, + vararg acceptableCollectionTypes: ClassId +) : JavaValueProvider { + + private val acceptableCollectionTypes = acceptableCollectionTypes.toList() + + override fun accept(type: FuzzedType): Boolean { + return with (type.classId) { + acceptableCollectionTypes.any { acceptableCollectionType -> + isSubtypeOf(acceptableCollectionType.kClass) + } + } + } + + protected suspend fun SequenceScope.yieldConcreteClass(type: FuzzedType) { + if (with (type.classId) { !isAbstract && isPublic && (!isInner || isStatic) }) { + val emptyConstructor = type.classId.allConstructors.find { it.parameters.isEmpty() } + if (emptyConstructor != null && emptyConstructor.isPublic) { + yield(type) + } + } + } + + /** + * Types should be resolved with type parameters + */ + abstract fun resolveType(description: FuzzedDescription, type: FuzzedType) : Sequence + + abstract fun findMethod(resolvedType: FuzzedType, values: List) : MethodId + + override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence> { + resolveType(description, type).forEach { resolvedType -> + val typeParameter = resolvedType.generics + yield(Seed.Collection( + construct = Routine.Collection { + UtAssembleModel( + id = idGenerator.createId(), + classId = resolvedType.classId, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(resolvedType.classId, emptyList()), + emptyList() + ), + modificationsChainProvider = { mutableListOf() } + ).fuzzed { + summary = "%var% = collection" + } + }, + modify = Routine.ForEach(typeParameter) { self, _, values -> + val model = self.model as UtAssembleModel + (model.modificationsChain as MutableList) += UtExecutableCallModel( + model, + findMethod(resolvedType, values), + values.map { it.model } + ) + } + )) + } + } + + protected infix fun ClassId.isSubtypeOf(klass: KClass<*>): Boolean { + // commented code above doesn't work this case: SomeList extends LinkedList {} and Collection +// return isSubtypeOf(another) + return klass.java.isAssignableFrom(this.jClass) + } +} + +class IteratorValueProvider(val idGenerator: IdGenerator) : JavaValueProvider { + override fun accept(type: FuzzedType): Boolean { + return type.classId == Iterator::class.id + } + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> { + val (generic) = resolveGenericsOfSuperClass>(description, type) + return sequenceOf(Seed.Recursive( + construct = Routine.Create(listOf(FuzzedType(iterableClassId, listOf(generic)))) { v -> + val id = idGenerator.createId() + val iterable = when (val model = v.first().model) { + is UtAssembleModel -> model + is UtNullModel -> return@Create v.first() + else -> error("Model can be only UtNullModel or UtAssembleModel, but $model is met") + } + UtAssembleModel( + id = id, + classId = type.classId, + modelName = "iterator#${id.hex()}", + instantiationCall = UtExecutableCallModel( + instance = iterable, + executable = MethodId(iterableClassId, "iterator", type.classId, emptyList()), + params = emptyList() + ) + ).fuzzed { + summary = "%var% = ${iterable.classId.simpleName}#iterator()" + } + }, + empty = Routine.Empty { + val id = idGenerator.createId() + UtAssembleModel( + id = id, + classId = type.classId, + modelName = "emptyIterator#${id.hex()}", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = MethodId(java.util.Collections::class.id, "emptyIterator", type.classId, emptyList()), + params = emptyList() + ) + ).fuzzed { + summary = "%var% = empty iterator" + } + } + )) + } +} + +private inline fun resolveGenericsOfSuperClass( + description: FuzzedDescription, + type: FuzzedType, +): List { + val superClass = T::class.java + return try { + check(superClass.isAssignableFrom(type.classId.jClass)) { "$superClass isn't super class of $type" } + @Suppress("UNCHECKED_CAST") + toFuzzerType( + TypeToken.of(type.jType).getSupertype(superClass as Class).type, + description.typeCache + ).generics + } catch (e: Throwable) { + superClass.typeParameters.map { toFuzzerType(it, description.typeCache) } + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Custom.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Custom.kt new file mode 100644 index 0000000000..cd51c0a6f7 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Custom.kt @@ -0,0 +1,18 @@ +package org.utbot.fuzzing.providers + +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Seed + +interface CustomJavaValueProviderHolder { + val javaValueProvider: JavaValueProvider +} + +object DelegatingToCustomJavaValueProvider : JavaValueProvider { + override fun accept(type: FuzzedType): Boolean = type is CustomJavaValueProviderHolder + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = + (type as CustomJavaValueProviderHolder).javaValueProvider.generate(description, type) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Enums.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Enums.kt new file mode 100644 index 0000000000..d5c1de0a39 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Enums.kt @@ -0,0 +1,33 @@ +package org.utbot.fuzzing.providers + +import org.utbot.framework.fuzzer.IdentityPreservingIdGenerator +import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.util.isEnum +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Seed + +class EnumValueProvider( + val idGenerator: IdentityPreservingIdGenerator, +) : JavaValueProvider { + override fun accept(type: FuzzedType) = type.classId.isEnum + + override fun generate( + description: FuzzedDescription, + type: FuzzedType + ) = sequence> { + val jClass = type.classId.jClass + if (isAccessible(jClass, description.description.packageName)) { + jClass.enumConstants.filterIsInstance>().forEach { enum -> + val id = idGenerator.getOrCreateIdForValue(enum) + yield(Seed.Simple(UtEnumConstantModel(id, type.classId, enum).fuzzed { + summary = "%var% = $enum" + })) + } + } + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/FuzzerProperties.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/FuzzerProperties.kt new file mode 100644 index 0000000000..d5f6da74f8 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/FuzzerProperties.kt @@ -0,0 +1,8 @@ +package org.utbot.fuzzing.providers + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.fuzzing.ScopeProperty + +val NULLABLE_PROP = ScopeProperty("Whether or not type can be nullable") + +val SPRING_BEAN_PROP = ScopeProperty<(ClassId) -> List>("Maps java class to its beans") \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt new file mode 100644 index 0000000000..7c7fd73773 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Objects.kt @@ -0,0 +1,385 @@ +package org.utbot.fuzzing.providers + +import mu.KotlinLogging +import org.utbot.framework.UtSettings +import org.utbot.framework.fuzzer.IdGenerator +import org.utbot.framework.fuzzer.IdentityPreservingIdGenerator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.id +import org.utbot.framework.plugin.api.util.classClassId +import org.utbot.framework.plugin.api.util.constructor +import org.utbot.framework.plugin.api.util.dateClassId +import org.utbot.framework.plugin.api.util.executable +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.isAbstract +import org.utbot.framework.plugin.api.util.isCollectionOrMap +import org.utbot.framework.plugin.api.util.isEnum +import org.utbot.framework.plugin.api.util.isPrimitiveWrapper +import org.utbot.framework.plugin.api.util.isRefType +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.method +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Scope +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.toFuzzerType +import org.utbot.fuzzing.traverseHierarchy +import org.utbot.fuzzing.utils.hex +import org.utbot.modifications.AnalysisMode +import org.utbot.modifications.FieldInvolvementMode +import org.utbot.modifications.UtBotFieldsModificatorsSearcher +import soot.Scene +import soot.SootClass +import java.lang.reflect.Field +import java.lang.reflect.Member +import java.lang.reflect.Method +import java.lang.reflect.Modifier +import java.lang.reflect.TypeVariable + +private val logger = KotlinLogging.logger {} + +private fun isKnownTypes(type: ClassId): Boolean { + return type == stringClassId + || type == dateClassId + || type == NumberValueProvider.classId + || type.isCollectionOrMap + || type.isPrimitiveWrapper + || type.isEnum +} + +private fun isIgnored(type: ClassId): Boolean { + return isKnownTypes(type) + || type.isAbstract + || (type.isInner && !type.isStatic) +} + +fun anyObjectValueProvider(idGenerator: IdentityPreservingIdGenerator) = + ObjectValueProvider(idGenerator).letIf(UtSettings.fuzzingImplementationOfAbstractClasses) { ovp -> + ovp.withFallback(AbstractsObjectValueProvider(idGenerator)) + } + +/** + * Marker interface that shows that this [JavaValueProvider] can potentially provide values of + * arbitrary types, unlike type-specific value providers that were designed to provide values of + * few specific popular types (e.g. `List`, `String`, etc.). + */ +interface AnyObjectValueProvider + +class ObjectValueProvider( + val idGenerator: IdGenerator, +) : JavaValueProvider, AnyObjectValueProvider { + + override fun accept(type: FuzzedType) = !isIgnored(type.classId) + + override fun generate( + description: FuzzedDescription, + type: FuzzedType + ) = sequence { + val classId = type.classId + val constructors = classId.allConstructors + .filter { + isAccessible(it.constructor, description.description.packageName) + } + constructors.forEach { constructorId -> + yield(createValue(classId, constructorId, description)) + } + } + + private fun createValue(classId: ClassId, constructorId: ConstructorId, description: FuzzedDescription): Seed.Recursive = + Seed.Recursive( + construct = Routine.Create(constructorId.executable.genericParameterTypes.map { + toFuzzerType(it, description.typeCache) + }) { values -> + val id = idGenerator.createId() + UtAssembleModel( + id = id, + classId = classId, + modelName = "${constructorId.classId.name}${constructorId.parameters}#" + id.hex(), + instantiationCall = UtExecutableCallModel( + null, + constructorId, + values.map { it.model }), + modificationsChainProvider = { mutableListOf() } + ).fuzzed { + summary = "%var% = ${classId.simpleName}(${constructorId.parameters.joinToString { it.simpleName }})" + } + }, + modify = sequence { + findAccessibleModifiableFields(description, classId, description.description.packageName).forEach { fd -> + when { + fd.canBeSetDirectly -> { + yield(Routine.Call(listOf(fd.type)) { self, values -> + val model = self.model as UtAssembleModel + model.modificationsChain as MutableList += UtDirectSetFieldModel( + model, + FieldId(classId, fd.name), + values.first().model + ) + }) + } + + fd.setter != null && fd.getter != null -> { + yield(Routine.Call(listOf(fd.type)) { self, values -> + val model = self.model as UtAssembleModel + model.modificationsChain as MutableList += UtExecutableCallModel( + model, + fd.setter.executableId, + values.map { it.model }) + }) + } + } + } + }, + empty = nullRoutine(classId) + ) +} + +@Suppress("unused") +object NullValueProvider : JavaValueProvider, AnyObjectValueProvider { + + override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) { + // any value in static function is ok to fuzz + if (description.description.isStatic == true && scope.recursionDepth == 1) { + scope.putProperty(NULLABLE_PROP, true) + } + // any value except this + if (description.description.isStatic == false && scope.parameterIndex > 0 && scope.recursionDepth == 1) { + scope.putProperty(NULLABLE_PROP, true) + } + } + + override fun accept(type: FuzzedType) = type.classId.isRefType + + override fun generate( + description: FuzzedDescription, + type: FuzzedType + ) = sequence> { + if (description.scope?.getProperty(NULLABLE_PROP) == true) { + yield(Seed.Simple(nullFuzzedValue(classClassId))) + } + } +} + +/** + * Unlike [NullValueProvider] can generate `null` values at any depth. + * + * Intended to be used as a last fallback. + */ +object AnyDepthNullValueProvider : JavaValueProvider, AnyObjectValueProvider { + + override fun accept(type: FuzzedType) = type.classId.isRefType + + override fun generate( + description: FuzzedDescription, + type: FuzzedType + ) = sequenceOf>(Seed.Simple(nullFuzzedValue(classClassId))) +} + +/** + * Finds and create object from implementations of abstract classes or interfaces. + */ +class AbstractsObjectValueProvider( + val idGenerator: IdGenerator, +) : JavaValueProvider, AnyObjectValueProvider { + + override fun accept(type: FuzzedType) = type.classId.isRefType && !isKnownTypes(type.classId) + + override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence> { + val t = try { + Scene.v().getRefType(type.classId.name).sootClass + } catch (ignore: Throwable) { + logger.error(ignore) { "Soot may be not initialized" } + return@sequence + } + fun canCreateClass(sc: SootClass): Boolean { + try { + if (!sc.isConcrete) return false + val packageName = sc.packageName + if (packageName != null) { + if (packageName.startsWith("jdk.internal") || + packageName.startsWith("org.utbot") || + packageName.startsWith("sun.")) + return false + } + val isAnonymousClass = sc.name.matches(""".*\$\d+$""".toRegex()) + if (isAnonymousClass) { + return false + } + val jClass = sc.id.jClass + return isAccessible(jClass, description.description.packageName) && + jClass.declaredConstructors.any { isAccessible(it, description.description.packageName) } && + jClass.let { + // This won't work in case of implementations with generics like `Impl implements A`. + // Should be reworked with accurate generic matching between all classes. + toFuzzerType(it, description.typeCache).traverseHierarchy(description.typeCache).contains(type) + } + } catch (ignore: Throwable) { + return false + } + } + + val implementations = when { + t.isInterface -> Scene.v().fastHierarchy.getAllImplementersOfInterface(t).filter(::canCreateClass) + t.isAbstract -> Scene.v().fastHierarchy.getSubclassesOf(t).filter(::canCreateClass) + else -> emptyList() + } + implementations.shuffled(description.random).take(10).forEach { concrete -> + yield(Seed.Recursive( + construct = Routine.Create(listOf(toFuzzerType(concrete.id.jClass, description.typeCache))) { + it.first() + }, + empty = nullRoutine(type.classId) + )) + } + } +} + +internal class PublicSetterGetter( + val setter: Method, + val getter: Method, +) + +internal class FieldDescription( + val name: String, + val type: FuzzedType, + val canBeSetDirectly: Boolean, + val setter: Method?, + val getter: Method? +) + +internal class MethodDescription( + val name: String, + val parameterTypes: List, + val method: Method +) + +internal fun findAccessibleModifiableFields(description: FuzzedDescription?, classId: ClassId, packageName: String?): List { + return generateSequence(classId.jClass) { it.superclass }.flatMap { jClass -> + jClass.declaredFields.map { field -> + val setterAndGetter = jClass.findPublicSetterGetterIfHasPublicGetter(field, packageName) + FieldDescription( + name = field.name, + type = if (description != null) toFuzzerType( + field.genericType, + description.typeCache + ) else FuzzedType(field.type.id), + canBeSetDirectly = isAccessible( + field, + packageName + ) && !Modifier.isFinal(field.modifiers) && !Modifier.isStatic(field.modifiers), + setter = setterAndGetter?.setter, + getter = setterAndGetter?.getter, + ) + } + }.toList() +} + +internal fun findMethodsToModifyWith( + description: FuzzedDescription, + valueClassId: ClassId, + classUnderTest: ClassId, + ): List { + val packageName = description.description.packageName + + val methodUnderTestName = description.description.name.substringAfter(description.description.className + ".") + val modifyingMethods = findModifyingMethodNames(methodUnderTestName, valueClassId, classUnderTest) + return valueClassId.allMethods + .map { it.method } + .mapNotNull { method -> + if (isAccessible(method, packageName)) { + if (method.name !in modifyingMethods) return@mapNotNull null + if (method.genericParameterTypes.any { it is TypeVariable<*> }) return@mapNotNull null + + val parameterTypes = + method + .parameterTypes + .map { toFuzzerType(it, description.typeCache) } + + MethodDescription( + name = method.name, + parameterTypes = parameterTypes, + method = method + ) + } else null + } + .toList() +} + +internal fun Class<*>.findPublicSetterGetterIfHasPublicGetter(field: Field, packageName: String?): PublicSetterGetter? { + @Suppress("DEPRECATION") val postfixName = field.name.capitalize() + val setterName = "set$postfixName" + val getterName = "get$postfixName" + val getter = try { getDeclaredMethod(getterName) } catch (_: NoSuchMethodException) { return null } + return if (isAccessible(getter, packageName) && getter.returnType == field.type) { + declaredMethods.find { + isAccessible(it, packageName) && + it.name == setterName && + it.parameterCount == 1 && + it.parameterTypes[0] == field.type + }?.let { PublicSetterGetter(it, getter) } + } else { + null + } +} + +internal fun isAccessible(member: Member, packageName: String?): Boolean { + var clazz = member.declaringClass + while (clazz != null) { + if (!isAccessible(clazz, packageName)) return false + clazz = clazz.enclosingClass + } + return Modifier.isPublic(member.modifiers) || + (packageName != null && isNotPrivateOrProtected(member.modifiers) && member.declaringClass.`package`?.name == packageName) +} + +internal fun isAccessible(clazz: Class<*>, packageName: String?): Boolean { + return Modifier.isPublic(clazz.modifiers) || + (packageName != null && isNotPrivateOrProtected(clazz.modifiers) && clazz.`package`?.name == packageName) +} + +private fun findModifyingMethodNames( + methodUnderTestName: String, + valueClassId: ClassId, + classUnderTest: ClassId, + ) : Set = + UtBotFieldsModificatorsSearcher(fieldInvolvementMode = FieldInvolvementMode.ReadAndWrite) + .let { searcher -> + searcher.update(setOf(valueClassId, classUnderTest)) + val modificatorsToFields = searcher.getModificatorToFields(analysisMode = AnalysisMode.Methods) + + modificatorsToFields[methodUnderTestName]?.let { fieldsModifiedByMut -> + modificatorsToFields + .mapNotNull { (methodName, fieldSet) -> + val relevantFields = if (UtSettings.tryMutateOtherClassesFieldsWithMethods) { + fieldsModifiedByMut + } else { + fieldsModifiedByMut + .filter { field -> field.declaringClass == classUnderTest } + .toSet() + } + + val methodIsModifying = fieldSet.intersect(relevantFields).isNotEmpty() + && methodName != methodUnderTestName + if (methodIsModifying) methodName else null + } + .toSet() + } + ?: setOf() + } + +private fun isNotPrivateOrProtected(modifiers: Int): Boolean { + val hasAnyAccessModifier = Modifier.isPrivate(modifiers) || Modifier.isProtected(modifiers) + return !hasAnyAccessModifier +} diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Others.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Others.kt new file mode 100644 index 0000000000..c86b3b8064 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Others.kt @@ -0,0 +1,140 @@ +package org.utbot.fuzzing.providers + +import org.utbot.framework.fuzzer.IdGenerator +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.* +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.* +import org.utbot.fuzzing.utils.hex +import java.text.SimpleDateFormat +import java.util.* + +abstract class ClassValueProvider( + val classId: ClassId +) : JavaValueProvider { + override fun accept(type: FuzzedType) = type.classId == classId +} + +object NumberValueProvider : ClassValueProvider(Number::class.id) { + override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence> { + listOf( + byteClassId, shortClassId, intClassId, longClassId, floatClassId, doubleClassId + ).forEach { numberPrimitiveType -> + yield(Seed.Recursive( + construct = Routine.Create(listOf(FuzzedType(numberPrimitiveType))) { v -> v.first() }, + empty = Routine.Empty { UtNullModel(type.classId).fuzzed() } + )) + } + } +} + +class DateValueProvider( + private val idGenerator: IdGenerator +) : ClassValueProvider(Date::class.id) { + override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence> { + // now date + val nowDateModel = UtAssembleModel( + id = idGenerator.createId(), + classId = type.classId, + modelName = "Date::now", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = type.classId.allConstructors.firstOrNull { it.parameters.isEmpty() } + ?: error("Cannot find default constructor of ${type.classId}"), + params = emptyList()) + ).fuzzed { } + yield(Seed.Simple(nowDateModel)) + + // from string dates + val strings = description.constants + .filter { + it.classId == stringClassId + } + .map { it.value } + .filterIsInstance() + .distinct() + val dateFormats = strings + .filter { it.isDateFormat() } + defaultDateFormat + val formatToDates = dateFormats.associateWith { format -> strings.filter { it.isDate(format) } } + formatToDates.forEach { (format, dates) -> + dates.forEach { date -> + yield(Seed.Simple( + assembleDateFromString(idGenerator.createId(), format, date) + )) + } + } + + // from numbers + type.classId.allConstructors + .filter { + it.parameters.isNotEmpty() && it.parameters.all { p -> p == intClassId || p == longClassId } + } + .forEach { constructor -> + yield(Seed.Recursive( + construct = Routine.Create(constructor.parameters.map { FuzzedType(it) }) { values -> + UtAssembleModel( + id = idGenerator.createId(), + classId = type.classId, + modelName = "Date(${values.map { it.model.classId }})", + instantiationCall = UtExecutableCallModel(null, constructor, values.map { it.model }) + ).fuzzed { } + }, + empty = Routine.Empty { nowDateModel } + )) + } + } + + companion object { + private const val defaultDateFormat = "dd-MM-yyyy HH:mm:ss:ms" + } + + private fun String.isDate(format: String): Boolean { + val formatter = SimpleDateFormat(format).apply { + isLenient = false + } + return runCatching { formatter.parse(trim()) }.isSuccess + } + + private fun String.isDateFormat(): Boolean { + return none { it.isDigit() } && // fixes concrete date values + runCatching { SimpleDateFormat(this) }.isSuccess + } + + private fun assembleDateFromString(id: Int, formatString: String, dateString: String): FuzzedValue { + val simpleDateFormatModel = assembleSimpleDateFormat(idGenerator.createId(), formatString) + val dateFormatParse = simpleDateFormatModel.classId.jClass + .getMethod("parse", String::class.java).executableId + val instantiationCall = UtExecutableCallModel( + simpleDateFormatModel, dateFormatParse, listOf(UtPrimitiveModel(dateString)) + ) + return UtAssembleModel( + id, + dateClassId, + "$dateFormatParse#" + id.hex(), + instantiationCall + ).fuzzed { + summary = "%var% = $dateFormatParse($stringClassId)" + } + } + + private fun assembleSimpleDateFormat(id: Int, formatString: String): UtAssembleModel { + val simpleDateFormatId = SimpleDateFormat::class.java.id + val formatStringConstructor = simpleDateFormatId.allConstructors.first { + it.parameters.singleOrNull() == stringClassId + } + val formatSetLenient = SimpleDateFormat::setLenient.executableId + val formatModel = UtPrimitiveModel(formatString) + + val instantiationCall = UtExecutableCallModel(instance = null, formatStringConstructor, listOf(formatModel)) + return UtAssembleModel( + id, + simpleDateFormatId, + "$simpleDateFormatId[$stringClassId]#" + id.hex(), + instantiationCall + ) { + listOf(UtExecutableCallModel(instance = this, formatSetLenient, listOf(UtPrimitiveModel(false)))) + } + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Primitives.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Primitives.kt new file mode 100644 index 0000000000..4cb1ce1879 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Primitives.kt @@ -0,0 +1,245 @@ +package org.utbot.fuzzing.providers + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.framework.plugin.api.util.* +import org.utbot.fuzzer.FuzzedContext +import org.utbot.fuzzer.FuzzedContext.Comparison.* +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.* +import org.utbot.fuzzing.seeds.* +import java.util.regex.Pattern +import java.util.regex.PatternSyntaxException +import kotlin.random.Random + +abstract class PrimitiveValueProvider( + vararg acceptableTypes: ClassId +) : JavaValueProvider { + protected val acceptableTypes = acceptableTypes.toSet() + + final override fun accept(type: FuzzedType) = type.classId in acceptableTypes + + protected suspend fun > SequenceScope>.yieldKnown( + value: T, + toValue: T.() -> Any + ) { + yield(Seed.Known(value) { known -> + UtPrimitiveModel(toValue(known)).fuzzed { + summary = buildString { + append("%var% = ${known.valueToString()}") + if (known.mutatedFrom != null) { + append(" (mutated from ${known.mutatedFrom?.valueToString()})") + } + } + } + }) + } + + private fun > T.valueToString(): String { + when (this) { + is BitVectorValue -> { + for (defaultBound in Signed.values()) { + if (defaultBound.test(this)) { + return defaultBound.name.lowercase() + } + } + return when (size) { + 8 -> toByte().toString() + 16 -> toShort().toString() + 32 -> toInt().toString() + 64 -> toLong().toString() + else -> toString(10) + } + } + is IEEE754Value -> { + for (defaultBound in DefaultFloatBound.values()) { + if (defaultBound.test(this)) { + return defaultBound.name.lowercase().replace("_", " ") + } + } + return when { + is32Float() -> toFloat().toString() + is64Float() -> toDouble().toString() + else -> toString() + } + } + is RegexValue -> { + return "'${value.substringToLength(10, "...")}' from $pattern" + } + is StringValue -> { + return "'${value.substringToLength(10, "...")}'" + } + else -> return toString() + } + } + + private fun String.substringToLength(size: Int, postfix: String): String { + return when { + length <= size -> this + else -> substring(0, size) + postfix + } + } +} + +object BooleanValueProvider : PrimitiveValueProvider(booleanClassId, booleanWrapperClassId) { + override fun generate(description: FuzzedDescription, type: FuzzedType) = sequence { + yieldKnown(Bool.TRUE()) { toBoolean() } + yieldKnown(Bool.FALSE()) { toBoolean() } + } +} + +object IntegerValueProvider : PrimitiveValueProvider( + charClassId, charWrapperClassId, + byteClassId, byteWrapperClassId, + shortClassId, shortWrapperClassId, + intClassId, intWrapperClassId, + longClassId, longWrapperClassId, +) { + + private val ClassId.typeSize: Int + get() = when (this) { + charClassId, charWrapperClassId -> 7 + byteClassId, byteWrapperClassId -> 8 + shortClassId, shortWrapperClassId -> 16 + intClassId, intWrapperClassId -> 32 + longClassId, longWrapperClassId -> 64 + else -> error("unknown type $this") + } + + /** + * Returns null when values cannot be cast. + */ + private fun ClassId.tryCast(value: BitVectorValue): Any? = when (this) { + charClassId, charWrapperClassId -> value.takeIf { typeSize <= charClassId.typeSize }?.toCharacter() + byteClassId, byteWrapperClassId -> value.takeIf { typeSize <= byteClassId.typeSize }?.toByte() + shortClassId, shortWrapperClassId -> value.takeIf { typeSize <= shortClassId.typeSize }?.toShort() + intClassId, intWrapperClassId -> value.takeIf { typeSize <= intClassId.typeSize }?.toInt() + longClassId, longWrapperClassId -> value.takeIf { typeSize <= longClassId.typeSize }?.toLong() + else -> error("unknown type $this") + } + + private fun ClassId.cast(value: BitVectorValue): Any = tryCast(value)!! + + private val randomStubWithNoUsage = Random(0) + private val configurationStubWithNoUsage = Configuration() + + private fun BitVectorValue.change(func: BitVectorValue.() -> Unit): BitVectorValue { + return Mutation> { _, _, _ -> + BitVectorValue(this).apply { func() } + }.mutate(this, randomStubWithNoUsage, configurationStubWithNoUsage) as BitVectorValue + } + + override fun generate( + description: FuzzedDescription, + type: FuzzedType + ) = sequence { + description.constants.forEach { (t, v, c) -> + if (t in acceptableTypes) { + val value = BitVectorValue.fromValue(v) + val values = listOfNotNull( + value, + when (c) { + EQ, NE, LE, GT -> value.change { inc() } + LT, GE -> value.change { dec() } + else -> null + } + ) + values.forEach { + if (type.classId.tryCast(it) != null) { + yieldKnown(it) { + type.classId.cast(this) + } + } + } + + } + } + Signed.values().forEach { bound -> + val s = type.classId.typeSize + val value = bound(s) + if (type.classId.tryCast(value) != null) { + yieldKnown(value) { + type.classId.cast(this) + } + } + } + } +} + +object FloatValueProvider : PrimitiveValueProvider( + floatClassId, floatWrapperClassId, + doubleClassId, doubleWrapperClassId, +) { + private val ClassId.typeSize: Pair + get() = when (this) { + floatClassId, floatWrapperClassId -> 23 to 8 + doubleClassId, doubleWrapperClassId -> 52 to 11 + else -> error("unknown type $this") + } + + /** + * Returns null when values cannot be cast. + */ + private fun ClassId.cast(value: IEEE754Value): Any = when (this) { + floatClassId, floatWrapperClassId -> value.toFloat() + doubleClassId, doubleWrapperClassId -> value.toDouble() + else -> error("unknown type $this") + } + + override fun generate( + description: FuzzedDescription, + type: FuzzedType + ) = sequence { + description.constants.forEach { (t, v, _) -> + if (t in acceptableTypes) { + yieldKnown(IEEE754Value.fromValue(v)) { type.classId.cast(this) } + } + } + DefaultFloatBound.values().forEach { bound -> + val (m, e) = type.classId.typeSize + yieldKnown(bound(m ,e)) { + type.classId.cast(this) + } + } + } +} + +object StringValueProvider : PrimitiveValueProvider(stringClassId, java.lang.CharSequence::class.java.id) { + override fun generate( + description: FuzzedDescription, + type: FuzzedType + ) = sequence { + val constants = description.constants + .filter { it.classId == stringClassId } + val values = constants + .mapNotNull { it.value as? String } + + sequenceOf("", "abc", "XZ", "#$\\\"'", "\n\t\r", "10", "-3") + values.forEach { yieldKnown(StringValue(it), StringValue::value) } + constants + .filter { it.fuzzedContext.isPatterMatchingContext() } + .map { it.value as String } + .distinct() + .filter(String::isSupportedPattern) + .forEach { + yieldKnown(RegexValue(it, Random(0)), StringValue::value) + } + } + + private fun FuzzedContext.isPatterMatchingContext(): Boolean { + if (this !is FuzzedContext.Call) return false + val stringMethodWithRegexArguments = setOf("matches", "split") + return when { + method.classId == Pattern::class.java.id -> true + method.classId == String::class.java.id && stringMethodWithRegexArguments.contains(method.name) -> true + else -> false + } + } +} + +object VoidValueProvider : PrimitiveValueProvider(voidClassId) { + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = + sequenceOf(Seed.Simple(UtVoidModel.fuzzed { summary = "%var% = void" })) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Utils.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Utils.kt new file mode 100644 index 0000000000..3e00a45c15 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/providers/Utils.kt @@ -0,0 +1,21 @@ +package org.utbot.fuzzing.providers + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.Routine + +fun nullRoutine(classId: ClassId): Routine.Empty = + Routine.Empty { nullFuzzedValue(classId) } + +fun nullFuzzedValue(classId: ClassId): FuzzedValue = + UtNullModel(classId).fuzzed { summary = "%var% = null" } + +fun defaultValueRoutine(classId: ClassId): Routine.Empty = + Routine.Empty { defaultFuzzedValue(classId) } + +fun defaultFuzzedValue(classId: ClassId): FuzzedValue = + classId.defaultValueModel().fuzzed { summary = "%var% = $model" } \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/FuzzedTypeWithProperties.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/FuzzedTypeWithProperties.kt new file mode 100644 index 0000000000..83b628d2d2 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/FuzzedTypeWithProperties.kt @@ -0,0 +1,33 @@ +package org.utbot.fuzzing.spring + +import org.utbot.common.DynamicProperties +import org.utbot.common.DynamicProperty +import org.utbot.common.dynamicPropertiesOf +import org.utbot.common.plus +import org.utbot.common.withoutProperty +import org.utbot.fuzzer.FuzzedType + +typealias FuzzedTypeProperties = DynamicProperties +typealias FuzzedTypeProperty = DynamicProperty +typealias FuzzedTypeFlag = FuzzedTypeProperty + +data class FuzzedTypeWithProperties( + val origin: FuzzedType, + val properties: FuzzedTypeProperties +) : FuzzedType(origin.classId, origin.generics) + +val FuzzedType.origin: FuzzedType + get() = + if (this is FuzzedTypeWithProperties) origin + else this + +val FuzzedType.properties: FuzzedTypeProperties + get() = + if (this is FuzzedTypeWithProperties) properties + else dynamicPropertiesOf() + +fun FuzzedType.withoutProperty(property: FuzzedTypeProperty<*>): FuzzedTypeWithProperties = + FuzzedTypeWithProperties(origin, properties.withoutProperty(property)) + +fun FuzzedType.addProperties(properties: FuzzedTypeProperties): FuzzedTypeWithProperties = + FuzzedTypeWithProperties(origin, this.properties + properties) diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/GeneratedField.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/GeneratedField.kt new file mode 100644 index 0000000000..0d2033d983 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/GeneratedField.kt @@ -0,0 +1,80 @@ +package org.utbot.fuzzing.spring + +import mu.KotlinLogging +import org.utbot.common.dynamicPropertiesOf +import org.utbot.common.withValue +import org.utbot.framework.fuzzer.IdGenerator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.DirectFieldAccessId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtDirectGetFieldModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.replaceWithWrapperIfPrimitive +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.providers.defaultFuzzedValue +import org.utbot.fuzzing.providers.defaultValueRoutine +import org.utbot.fuzzing.spring.valid.EntityLifecycleState +import org.utbot.fuzzing.spring.valid.EntityLifecycleStateProperty +import org.utbot.fuzzing.toFuzzerType + +class GeneratedFieldValueProvider( + private val idGenerator: IdGenerator, + private val entityClassId: ClassId, + private val fieldId: FieldId, +) : JavaValueProvider { + companion object { + private val logger = KotlinLogging.logger {} + } + + override fun accept(type: FuzzedType): Boolean = + replaceWithWrapperIfPrimitive(type.classId).jClass + .isAssignableFrom(replaceWithWrapperIfPrimitive(fieldId.type).jClass) + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = sequenceOf( + Seed.Recursive( + construct = Routine.Create(listOf( + toFuzzerType(entityClassId.jClass, description.typeCache).addProperties( + dynamicPropertiesOf( + EntityLifecycleStateProperty.withValue(EntityLifecycleState.MANAGED) + ) + ) + )) { values -> + val thisInstanceValue = values.single() + val thisInstanceModel = when (val model = thisInstanceValue.model) { + is UtReferenceModel -> model + is UtNullModel -> return@Create defaultFuzzedValue(type.classId) + else -> { + logger.warn { "This instance model can be only UtReferenceModel or UtNullModel, but $model is met" } + return@Create defaultFuzzedValue(type.classId) + } + } + UtAssembleModel( + id = idGenerator.createId(), + classId = type.classId, + modelName = "${thisInstanceModel.modelName}.${fieldId.name}", + instantiationCall = UtDirectGetFieldModel( + instance = thisInstanceModel, + fieldAccess = DirectFieldAccessId( + classId = fieldId.declaringClass, + name = "", + fieldId = fieldId + ) + ) + ).fuzzed { + summary = "${thisInstanceValue.summary}.${fieldId.name}" + } + }, + modify = emptySequence(), + empty = defaultValueRoutine(type.classId) + ) + ) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/JavaLangObject.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/JavaLangObject.kt new file mode 100644 index 0000000000..000fdf7356 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/JavaLangObject.kt @@ -0,0 +1,35 @@ +package org.utbot.fuzzing.spring + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.toTypeParametrizedByTypeVariables +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.providers.nullRoutine +import org.utbot.fuzzing.toFuzzerType + +class JavaLangObjectValueProvider( + private val classesToTryUsingAsJavaLangObject: List, +) : JavaValueProvider { + override fun accept(type: FuzzedType): Boolean { + return type.classId == objectClassId + } + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = + classesToTryUsingAsJavaLangObject.map { classToUseAsObject -> + val fuzzedType = toFuzzerType( + type = classToUseAsObject.jClass.toTypeParametrizedByTypeVariables(), + cache = description.typeCache + ) + Seed.Recursive( + construct = Routine.Create(listOf(fuzzedType)) { (value) -> value }, + modify = emptySequence(), + empty = nullRoutine(type.classId) + ) + }.asSequence() +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SpringBeanValueProvider.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SpringBeanValueProvider.kt new file mode 100644 index 0000000000..840822c50c --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/SpringBeanValueProvider.kt @@ -0,0 +1,88 @@ +package org.utbot.fuzzing.spring + +import org.utbot.common.dynamicPropertiesOf +import org.utbot.common.withValue +import org.utbot.framework.fuzzer.IdGenerator +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.SpringModelUtils +import org.utbot.framework.plugin.api.util.SpringModelUtils.persistMethodIdOrNull +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.* +import org.utbot.fuzzing.providers.SPRING_BEAN_PROP +import org.utbot.fuzzing.providers.findMethodsToModifyWith +import org.utbot.fuzzing.providers.nullRoutine +import org.utbot.fuzzing.spring.valid.EntityLifecycleState +import org.utbot.fuzzing.spring.valid.EntityLifecycleStateProperty + +class SpringBeanValueProvider( + private val idGenerator: IdGenerator, + private val beanNameProvider: (ClassId) -> List, + private val relevantRepositories: Set +) : JavaValueProvider { + + override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) { + if (description.description.isStatic == false + && scope.parameterIndex == 0 + && scope.recursionDepth == 1) { + scope.putProperty(SPRING_BEAN_PROP, beanNameProvider) + } + } + + override fun generate( + description: FuzzedDescription, + type: FuzzedType + ) = sequence { + val beans = description.scope?.getProperty(SPRING_BEAN_PROP) + beans?.invoke(type.classId)?.forEach { beanName -> + yield(createValue(type.classId, beanName, description)) + } + } + + private fun createValue(classId: ClassId, beanName: String, description: FuzzedDescription): Seed.Recursive = + Seed.Recursive( + construct = Routine.Create(types = emptyList()) { + SpringModelUtils.createBeanModel( + beanName = beanName, + id = idGenerator.createId(), + classId = classId, + ).fuzzed { summary = "@Autowired ${classId.simpleName} $beanName" } + }, + modify = sequence { + // TODO mutate model itself (not just repositories) + relevantRepositories.forEach { repositoryId -> + yield(Routine.Call( + listOf(toFuzzerType(repositoryId.entityClassId.jClass, description.typeCache).addProperties( + dynamicPropertiesOf( + EntityLifecycleStateProperty.withValue(EntityLifecycleState.MANAGED) + ) + )) + ) { selfValue, (entityValue) -> + val self = selfValue.model as UtAssembleModel + val modificationChain: MutableList = + self.modificationsChain as MutableList + val entity = entityValue.model + if (entity is UtReferenceModel) { + persistMethodIdOrNull?.let { persistMethodId -> + ((entity as? UtAssembleModel)?.modificationsChain as? MutableList)?.removeAll { + it is UtExecutableCallModel && it.executable == persistMethodId + } + modificationChain.add( + UtExecutableCallModel( + instance = UtSpringEntityManagerModel(), + executable = persistMethodId, + params = listOf(entity) + ) + ) + } + + } + }) + } + }, + empty = nullRoutine(classId) + ) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/ModifyingWithMethodsProviderWrapper.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/ModifyingWithMethodsProviderWrapper.kt new file mode 100644 index 0000000000..d94b2ea18a --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/ModifyingWithMethodsProviderWrapper.kt @@ -0,0 +1,56 @@ +package org.utbot.fuzzing.spring.decorators + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.providers.findMethodsToModifyWith + +/** + * Value provider that is a buddy for another provider + * that keeps all it's functionality and also allows + * to use methods to mutate field states of an object. + * + * NOTE!!! + * Instances represented by [UtAssembleModel] only can be mutated with methods. + */ +class ModifyingWithMethodsProviderWrapper( + private val classUnderTest: ClassId, + delegate: JavaValueProvider +) : ValueProviderDecorator(delegate) { + + override fun wrap(provider: JavaValueProvider): JavaValueProvider = + ModifyingWithMethodsProviderWrapper(classUnderTest, provider) + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = + delegate + .generate(description, type) + .map { seed -> + if (seed is Seed.Recursive) { + Seed.Recursive( + construct = seed.construct, + modify = seed.modify + + findMethodsToModifyWith(description, type.classId, classUnderTest) + .asSequence() + .map { md -> + Routine.Call(md.parameterTypes) { self, values -> + val model = self.model as UtAssembleModel + model.modificationsChain as MutableList += + UtExecutableCallModel( + model, + md.method.executableId, + values.map { it.model } + ) + } + }, + empty = seed.empty, + ) + } else seed + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/PropertyPreservingValueProvider.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/PropertyPreservingValueProvider.kt new file mode 100644 index 0000000000..5f52435daa --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/PropertyPreservingValueProvider.kt @@ -0,0 +1,72 @@ +package org.utbot.fuzzing.spring.decorators + +import org.utbot.common.toDynamicProperties +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.spring.FuzzedTypeProperty +import org.utbot.fuzzing.spring.addProperties +import org.utbot.fuzzing.spring.properties + +/** + * @see preserveProperties + */ +interface PreservableFuzzedTypeProperty : FuzzedTypeProperty + +/** + * Returns wrapper of [this] provider that preserves [preservable properties][PreservableFuzzedTypeProperty], + * i.e. all [FuzzedType]s mentioned in any returned [Seed] will have all [preservable properties] + * [PreservableFuzzedTypeProperty] of the type that was used to generate that seed. + * + * That's useful if we want a whole part of the object tree created by fuzzer to possess some property + * (e.g. all entities used to create MANAGED entity should themselves be also MANAGED). + */ +fun JavaValueProvider.preserveProperties() : JavaValueProvider = + PropertyPreservingValueProvider(this) + +class PropertyPreservingValueProvider( + delegate: JavaValueProvider +) : ValueProviderDecorator(delegate) { + override fun wrap(provider: JavaValueProvider): JavaValueProvider = + provider.preserveProperties() + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> { + val delegateSeeds = delegate.generate(description, type) + + val preservedProperties = type.properties.entries + .filter { it.property is PreservableFuzzedTypeProperty } + .toDynamicProperties() + if (preservedProperties.entries.isEmpty()) return delegateSeeds + + fun List.addPreservedProperties() = map { it.addProperties(preservedProperties) } + + return delegateSeeds.map { seed -> + when (seed) { + is Seed.Recursive -> Seed.Recursive( + construct = Routine.Create( + types = seed.construct.types.addPreservedProperties(), + builder = seed.construct.builder + ), + modify = seed.modify.map { modification -> + Routine.Call( + types = modification.types.addPreservedProperties(), + callable = modification.callable + ) + }, + empty = seed.empty + ) + is Seed.Collection -> Seed.Collection( + construct = seed.construct, + modify = Routine.ForEach( + types = seed.modify.types.addPreservedProperties(), + callable = seed.modify.callable + ) + ) + is Seed.Known, is Seed.Simple -> seed + } + } + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/SeedFilteringValueProvider.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/SeedFilteringValueProvider.kt new file mode 100644 index 0000000000..62b9dbf8f0 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/SeedFilteringValueProvider.kt @@ -0,0 +1,19 @@ +package org.utbot.fuzzing.spring.decorators + +import org.utbot.fuzzing.Description +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider + +fun > ValueProvider.filterSeeds(predicate: (Seed) -> Boolean) = + SeedFilteringValueProvider(delegate = this, predicate) + +class SeedFilteringValueProvider>( + delegate: ValueProvider, + private val predicate: (Seed) -> Boolean +) : ValueProviderDecorator(delegate) { + override fun wrap(provider: ValueProvider): ValueProvider = + provider.filterSeeds(predicate) + + override fun generate(description: D, type: T): Sequence> = + delegate.generate(description, type).filter(predicate) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/TypeFilteringValueProvider.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/TypeFilteringValueProvider.kt new file mode 100644 index 0000000000..7c46dfa46b --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/TypeFilteringValueProvider.kt @@ -0,0 +1,18 @@ +package org.utbot.fuzzing.spring.decorators + +import org.utbot.fuzzing.Description +import org.utbot.fuzzing.ValueProvider + +fun > ValueProvider.filterTypes(predicate: (T) -> Boolean) = + TypeFilteringValueProvider(delegate = this, predicate) + +class TypeFilteringValueProvider>( + delegate: ValueProvider, + private val predicate: (T) -> Boolean +) : ValueProviderDecorator(delegate) { + override fun wrap(provider: ValueProvider): ValueProvider = + provider.filterTypes(predicate) + + override fun accept(type: T): Boolean = + predicate(type) && super.accept(type) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/TypeReplacingValueProvider.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/TypeReplacingValueProvider.kt new file mode 100644 index 0000000000..2df9984d00 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/TypeReplacingValueProvider.kt @@ -0,0 +1,28 @@ +package org.utbot.fuzzing.spring.decorators + +import org.utbot.fuzzing.Description +import org.utbot.fuzzing.Scope +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider + +fun > ValueProvider.replaceTypes(typeReplacer: (D, T) -> T) = + TypeReplacingValueProvider(delegate = this, typeReplacer) + +class TypeReplacingValueProvider>( + delegate: ValueProvider, + private val typeReplacer: (D, T) -> T +) : ValueProviderDecorator(delegate) { + override fun wrap(provider: ValueProvider): ValueProvider = + provider.replaceTypes(typeReplacer) + + override fun enrich(description: D, type: T, scope: Scope) = + super.enrich(description, typeReplacer(description, type), scope) + + override fun accept(type: T): Boolean = true + + override fun generate(description: D, type: T): Sequence> = + if (super.accept(typeReplacer(description, type))) + super.generate(description, typeReplacer(description, type)) + else + emptySequence() +} diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/ValueProviderDecorator.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/ValueProviderDecorator.kt new file mode 100644 index 0000000000..142c6c82d8 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/decorators/ValueProviderDecorator.kt @@ -0,0 +1,24 @@ +package org.utbot.fuzzing.spring.decorators + +import org.utbot.fuzzing.Description +import org.utbot.fuzzing.Scope +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider + +abstract class ValueProviderDecorator>( + protected val delegate: ValueProvider +) : ValueProvider { + protected abstract fun wrap(provider: ValueProvider): ValueProvider + + override fun enrich(description: D, type: T, scope: Scope) = + delegate.enrich(description, type, scope) + + override fun accept(type: T): Boolean = + delegate.accept(type) + + override fun generate(description: D, type: T): Sequence> = + delegate.generate(description, type) + + override fun map(transform: (ValueProvider) -> ValueProvider): ValueProvider = + transform(wrap(delegate.map(transform))) +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/InjectMocks.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/InjectMocks.kt new file mode 100644 index 0000000000..59c6f6d1b1 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/InjectMocks.kt @@ -0,0 +1,75 @@ +package org.utbot.fuzzing.spring.unit + +import org.utbot.framework.fuzzer.IdGenerator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.util.allDeclaredFieldIds +import org.utbot.framework.plugin.api.util.isFinal +import org.utbot.framework.plugin.api.util.isStatic +import org.utbot.framework.plugin.api.util.jField +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Scope +import org.utbot.fuzzing.ScopeProperty +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.toFuzzerType + +val INJECT_MOCK_FLAG = ScopeProperty( + "INJECT_MOCK_FLAG is present if composite model should be used (i.e. thisInstance is being created)" +) + +/** + * Models created by this class can be used with `@InjectMock` annotation, because + * they are [UtCompositeModel]s similar to the ones created by the symbolic engine. + * + * This class only creates models for thisInstance of type [classUnderTest]. + */ +class InjectMockValueProvider( + private val idGenerator: IdGenerator, + private val classUnderTest: ClassId, + private val isFieldNonNull: (FieldId) -> Boolean +) : JavaValueProvider { + override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) { + if (description.description.isStatic == false && scope.parameterIndex == 0 && scope.recursionDepth == 1) { + scope.putProperty(INJECT_MOCK_FLAG, Unit) + } + } + + override fun accept(type: FuzzedType): Boolean = type.classId == classUnderTest + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> { + if (description.scope?.getProperty(INJECT_MOCK_FLAG) == null) return emptySequence() + val fields = type.classId.allDeclaredFieldIds.filterNot { it.isStatic && it.isFinal }.toList() + val (nonNullFields, nullableFields) = fields.partition(isFieldNonNull) + return sequenceOf(Seed.Recursive( + construct = Routine.Create( + types = nonNullFields.map { toFuzzerType(it.jField.genericType, description.typeCache) } + ) { values -> + emptyFuzzedValue(type.classId).also { + (it.model as UtCompositeModel).fields.putAll( + nonNullFields.zip(values).associate { (field, value) -> field to value.model } + ) + } + }, + modify = nullableFields.map { field -> + Routine.Call( + types = listOf(toFuzzerType(field.jField.genericType, description.typeCache)) + ) { instance, (value) -> + (instance.model as UtCompositeModel).fields[field] = value.model + } + }.asSequence(), + empty = Routine.Empty { emptyFuzzedValue(type.classId) } + )) + } + + private fun emptyFuzzedValue(classId: ClassId) = UtCompositeModel( + id = idGenerator.createId(), + classId = classId, + isMock = false, + ).fuzzed { summary = "%var% = ${classId.simpleName}()" } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/Mocks.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/Mocks.kt new file mode 100644 index 0000000000..203b24fd52 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/unit/Mocks.kt @@ -0,0 +1,89 @@ +package org.utbot.fuzzing.spring.unit + +import com.google.common.reflect.TypeResolver +import mu.KotlinLogging +import org.utbot.framework.fuzzer.IdGenerator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.method +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Scope +import org.utbot.fuzzing.ScopeProperty +import org.utbot.fuzzing.Seed +import org.utbot.fuzzer.jType +import org.utbot.fuzzer.toTypeParametrizedByTypeVariables +import org.utbot.fuzzer.typeToken +import org.utbot.fuzzing.toFuzzerType + +val methodsToMockProperty = ScopeProperty>( + description = "Method ids that can be mocked by `MockValueProvider`" +) + +// TODO shouldn't be used for primitives and other "easy" to create types +class MockValueProvider(private val idGenerator: IdGenerator) : JavaValueProvider { + companion object { + private val logger = KotlinLogging.logger {} + private val loggedMockedMethods = mutableSetOf() + private val loggedUnresolvedMethods = mutableSetOf() + } + + private val methodsToMock = mutableSetOf() + + override fun enrich(description: FuzzedDescription, type: FuzzedType, scope: Scope) { + val publicMethods = type.classId.jClass.methods.map { it.executableId } + publicMethods.intersect(methodsToMock).takeIf { it.isNotEmpty() }?.let { + scope.putProperty(methodsToMockProperty, it) + } + } + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = + sequenceOf(Seed.Recursive( + construct = Routine.Create(types = emptyList()) { emptyMockFuzzedValue(type.classId) }, + empty = Routine.Empty { emptyMockFuzzedValue(type.classId) }, + modify = (description.scope?.getProperty(methodsToMockProperty)?.asSequence() ?: emptySequence()).map { methodId -> + val methodDeclaringClass = methodId.classId.jClass + + val returnType = try { + TypeResolver().where( + methodDeclaringClass.toTypeParametrizedByTypeVariables(), + @Suppress("UNCHECKED_CAST") + type.jType.typeToken.getSupertype(methodDeclaringClass as Class).type + ).resolveType(methodId.method.genericReturnType) + } catch (e: Exception) { + if (loggedUnresolvedMethods.add(methodId)) + logger.error(e) { "Failed to resolve return type for $methodId, using unresolved generic type" } + + methodId.method.genericReturnType + } + + // TODO accept `List` instead of singular `resolvedReturnType` + Routine.Call(types = listOf(toFuzzerType(returnType, description.typeCache))) { instance, (value) -> + if (loggedMockedMethods.add(methodId)) + logger.info { "Actually mocked $methodId for the first time" } + + (instance.model as UtCompositeModel).mocks[methodId] = listOf(value.model) + } + } + )) + + private fun emptyMockFuzzedValue(classId: ClassId) = UtCompositeModel( + id = idGenerator.createId(), + classId = classId, + isMock = true, + canHaveRedundantOrMissingMocks = true, + ).fuzzed { summary = "%var% = mock()" } + + fun addMockingCandidates(detectedMockingCandidates: Set) = + detectedMockingCandidates.forEach { methodId -> + if (methodsToMock.add(methodId)) + logger.info { "Detected that $methodId may need mocking" } + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/AbstractPrimitiveValidValueProvider.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/AbstractPrimitiveValidValueProvider.kt new file mode 100644 index 0000000000..7adb6442f5 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/AbstractPrimitiveValidValueProvider.kt @@ -0,0 +1,51 @@ +package org.utbot.fuzzing.spring.valid + +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.providers.nullFuzzedValue +import org.utbot.fuzzing.spring.FuzzedTypeProperty + +abstract class AbstractPrimitiveValidValueProvider, T, V> : + AbstractValidValueProvider() { + + protected abstract val primitiveClass: Class + + protected abstract fun defaultValidPrimitiveValue(validationData: T): V + protected abstract fun makeValid(originalValue: V, validationData: T): V + protected abstract fun isNullValid(): Boolean + + override fun acceptsType(type: FuzzedType): Boolean = + primitiveClass.id == type.classId + + final override fun makeValid(originalValue: FuzzedValue, validationData: T): FuzzedValue = + when (val model = originalValue.model) { + is UtNullModel -> if (isNullValid()) originalValue else defaultValidValue(validationData) + is UtPrimitiveModel -> { + if (primitiveClass.isInstance(model.value)) + primitiveFuzzedValue(makeValid(primitiveClass.cast(model.value), validationData)) + else + originalValue + } + else -> originalValue + } + + final override fun defaultValidValue(validationData: T): FuzzedValue = + primitiveFuzzedValue(defaultValidPrimitiveValue(validationData)) + + private fun primitiveFuzzedValue(value: V) = value?.let { + UtPrimitiveModel(value).fuzzed { + summary = "%var% = ${ + when (value) { + is String -> "\"$value\"" + is Char -> "\'$value\'" + else -> value.toString() + } + }" + } + } ?: nullFuzzedValue(primitiveClass.id) + +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/AbstractValidValueProvider.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/AbstractValidValueProvider.kt new file mode 100644 index 0000000000..ca17953fa4 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/AbstractValidValueProvider.kt @@ -0,0 +1,34 @@ +package org.utbot.fuzzing.spring.valid + +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.spring.FuzzedTypeProperty +import org.utbot.fuzzing.spring.properties +import org.utbot.fuzzing.spring.withoutProperty + +abstract class AbstractValidValueProvider, T> : JavaValueProvider { + protected abstract val validationDataTypeProperty: TProp + + protected abstract fun acceptsType(type: FuzzedType): Boolean + protected abstract fun makeValid(originalValue: FuzzedValue, validationData: T): FuzzedValue + protected abstract fun defaultValidValue(validationData: T): FuzzedValue + + final override fun accept(type: FuzzedType): Boolean = + validationDataTypeProperty in type.properties && acceptsType(type) + + final override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> { + val validationData = type.properties.getValue(validationDataTypeProperty) + return sequenceOf( + Seed.Recursive( + construct = Routine.Create(listOf(type.withoutProperty(validationDataTypeProperty))) { (origin) -> + makeValid(origin, validationData) + }, + empty = Routine.Empty { defaultValidValue(validationData) } + ) + ) + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/Email.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/Email.kt new file mode 100644 index 0000000000..fec1c1c111 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/Email.kt @@ -0,0 +1,23 @@ +package org.utbot.fuzzing.spring.valid + +import org.utbot.fuzzing.spring.FuzzedTypeFlag + +object EmailTypeFlag : FuzzedTypeFlag + +class EmailValueProvider : AbstractPrimitiveValidValueProvider() { + override val primitiveClass: Class + get() = String::class.java + + override val validationDataTypeProperty get() = EmailTypeFlag + + override fun defaultValidPrimitiveValue(validationData: Unit): String = "johnDoe@sample.com" + + override fun makeValid(originalValue: String, validationData: Unit): String = + when { + originalValue.length < 2 -> "johnDoe@sample.com" + else -> originalValue.take(originalValue.length / 2) + "@" + originalValue.drop(originalValue.length / 2) + } + + // `null` is a valid email according to `jakarta.validation.constraints.Email` javadoc + override fun isNullValid(): Boolean = true +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/NotBlank.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/NotBlank.kt new file mode 100644 index 0000000000..a8b7309db3 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/NotBlank.kt @@ -0,0 +1,19 @@ +package org.utbot.fuzzing.spring.valid + +import org.utbot.fuzzing.spring.FuzzedTypeFlag + +object NotBlankTypeFlag : FuzzedTypeFlag + +class NotBlankStringValueProvider : AbstractPrimitiveValidValueProvider() { + override val primitiveClass: Class + get() = String::class.java + + override val validationDataTypeProperty get() = NotBlankTypeFlag + + override fun defaultValidPrimitiveValue(validationData: Unit): String = "e" + + override fun makeValid(originalValue: String, validationData: Unit): String = + originalValue.ifBlank { "e" } + + override fun isNullValid(): Boolean = false +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/NotEmpty.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/NotEmpty.kt new file mode 100644 index 0000000000..a0532f6fca --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/NotEmpty.kt @@ -0,0 +1,19 @@ +package org.utbot.fuzzing.spring.valid + +import org.utbot.fuzzing.spring.FuzzedTypeFlag + +object NotEmptyTypeFlag : FuzzedTypeFlag + +class NotEmptyStringValueProvider : AbstractPrimitiveValidValueProvider() { + override val primitiveClass: Class + get() = String::class.java + + override val validationDataTypeProperty get() = NotEmptyTypeFlag + + override fun defaultValidPrimitiveValue(validationData: Unit): String = " " + + override fun makeValid(originalValue: String, validationData: Unit): String = + originalValue.ifEmpty { " " } + + override fun isNullValid(): Boolean = false +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/ValidEntity.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/ValidEntity.kt new file mode 100644 index 0000000000..4a3b349639 --- /dev/null +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzing/spring/valid/ValidEntity.kt @@ -0,0 +1,164 @@ +package org.utbot.fuzzing.spring.valid + +import org.utbot.common.toDynamicProperties +import org.utbot.common.withValue +import org.utbot.framework.fuzzer.IdGenerator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtSpringEntityManagerModel +import org.utbot.framework.plugin.api.util.SpringModelUtils.entityClassIds +import org.utbot.framework.plugin.api.util.SpringModelUtils.generatedValueClassIds +import org.utbot.framework.plugin.api.util.SpringModelUtils.detachMethodIdOrNull +import org.utbot.framework.plugin.api.util.SpringModelUtils.emailClassIds +import org.utbot.framework.plugin.api.util.SpringModelUtils.persistMethodIdOrNull +import org.utbot.framework.plugin.api.util.SpringModelUtils.idClassIds +import org.utbot.framework.plugin.api.util.SpringModelUtils.notBlankClassIds +import org.utbot.framework.plugin.api.util.SpringModelUtils.notEmptyClassIds +import org.utbot.framework.plugin.api.util.allDeclaredFieldIds +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.jField +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.fuzzed +import org.utbot.fuzzing.FuzzedDescription +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.providers.findAccessibleModifiableFields +import org.utbot.fuzzing.providers.nullRoutine +import org.utbot.fuzzing.spring.decorators.PreservableFuzzedTypeProperty +import org.utbot.fuzzing.spring.addProperties +import org.utbot.fuzzing.spring.properties +import org.utbot.fuzzing.utils.hex + +enum class EntityLifecycleState( + val fieldAnnotationsToAvoidInitializing: List = emptyList(), + val entityManagerMethodsGetter: () -> List = { emptyList() }, +) { + NEW_WITHOUT_GENERATED_VALUES(generatedValueClassIds), + NEW_WITHOUT_ID(idClassIds), + NEW, + MANAGED(generatedValueClassIds + idClassIds, { listOfNotNull(persistMethodIdOrNull) }), + DETACHED(generatedValueClassIds + idClassIds, { listOfNotNull(persistMethodIdOrNull, detachMethodIdOrNull) }), +} + +object EntityLifecycleStateProperty : PreservableFuzzedTypeProperty + +class ValidEntityValueProvider( + val idGenerator: IdGenerator, + val onlyAcceptWhenValidIsRequired: Boolean +) : JavaValueProvider { + override fun accept(type: FuzzedType): Boolean { + return (!onlyAcceptWhenValidIsRequired || EntityLifecycleStateProperty in type.properties) && + entityClassIds.any { + @Suppress("UNCHECKED_CAST") + type.classId.jClass.getAnnotation(it.jClass as Class) != null + } + } + + override fun generate(description: FuzzedDescription, type: FuzzedType): Sequence> = + sequence { + val lifecycleStates = type.properties[EntityLifecycleStateProperty]?.let { listOf(it) } ?: + EntityLifecycleState.values().toList() + lifecycleStates.forEach { lifecycleState -> + generateForLifecycleState(description, type.classId, lifecycleState)?.let { yield(it) } + } + } + + private fun generateForLifecycleState( + description: FuzzedDescription, + classId: ClassId, + lifecycleState: EntityLifecycleState + ): Seed.Recursive? { + val noArgConstructorId = try { + classId.jClass.getDeclaredConstructor().executableId + } catch (e: NoSuchMethodException) { + return null + } + return Seed.Recursive( + construct = Routine.Create(types = emptyList()) { _ -> + val id = idGenerator.createId() + UtAssembleModel( + id = id, + classId = classId, + modelName = "${noArgConstructorId.classId.name}${noArgConstructorId.parameters}#" + id.hex(), + instantiationCall = UtExecutableCallModel(null, noArgConstructorId, params = emptyList()), + modificationsChainProvider = { + lifecycleState.entityManagerMethodsGetter().map { methodId -> + UtExecutableCallModel( + instance = UtSpringEntityManagerModel(), + executable = methodId, + params = listOf(this), + ) + } + } + ).fuzzed { + summary = "%var% = ${classId.simpleName}()" + } + }, + modify = sequence { + // TODO maybe all fields + findAccessibleModifiableFields( + description, + classId, + description.description.packageName + ).forEach { fd -> + val field = classId.allDeclaredFieldIds.first { it.name == fd.name }.jField + if (lifecycleState.fieldAnnotationsToAvoidInitializing.any { + @Suppress("UNCHECKED_CAST") + field.getAnnotation(it.jClass as Class) != null + }) return@forEach + + val validationProperties = field.annotatedType.annotations.mapNotNull { annotation -> + when (annotation.annotationClass.id) { + in notEmptyClassIds -> NotEmptyTypeFlag.withValue(Unit) + in notBlankClassIds -> NotBlankTypeFlag.withValue(Unit) + in emailClassIds -> EmailTypeFlag.withValue(Unit) + // TODO support more validators + else -> null + } + }.toDynamicProperties() + + val typeWithProperties = fd.type.addProperties(validationProperties) + + when { + fd.canBeSetDirectly -> { + yield(Routine.Call(listOf(typeWithProperties)) { self, values -> + val model = self.model as UtAssembleModel + (model.modificationsChain as MutableList).add( + index = 0, // prepending extra modifications to keep `persist()` modification last + UtDirectSetFieldModel( + model, + FieldId(classId, fd.name), + values.first().model + ) + ) + }) + } + + fd.setter != null -> { + yield(Routine.Call(listOf(typeWithProperties)) { self, values -> + val model = self.model as UtAssembleModel + (model.modificationsChain as MutableList).add( + index = 0, // prepending extra modifications to keep `persist()` modification last + UtExecutableCallModel( + model, + fd.setter.executableId, + values.map { it.model } + ) + ) + }) + } + } + } + }, + empty = nullRoutine(classId) + ) + } +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/AccessibleObjects.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/AccessibleObjects.java new file mode 100644 index 0000000000..5dd7bb78ec --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/AccessibleObjects.java @@ -0,0 +1,33 @@ +package org.utbot.fuzzing.samples; + +@SuppressWarnings("unused") +public class AccessibleObjects { + + public boolean test(Inn.Node n) { + return n.value * n.value == 36; + } + + private static class Inn { + static class Node { + public int value; + + public Node() { + + } + } + } + + public int ordinal(InnEn val) { + switch (val) { + case ONE: + return 0; + case TWO: + return 1; + } + return -1; + } + + private enum InnEn { + ONE, TWO + } +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/ConcreateMap.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/ConcreateMap.java new file mode 100644 index 0000000000..36a7891e10 --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/ConcreateMap.java @@ -0,0 +1,6 @@ +package org.utbot.fuzzing.samples; + +import java.util.HashMap; + +public class ConcreateMap extends HashMap { +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/ConcreteList.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/ConcreteList.java new file mode 100644 index 0000000000..14d1749cd8 --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/ConcreteList.java @@ -0,0 +1,7 @@ +package org.utbot.fuzzing.samples; + +import java.util.ArrayList; +import java.util.Collection; + +public class ConcreteList extends ArrayList> { +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/DeepNested.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/DeepNested.java new file mode 100644 index 0000000000..88c1748719 --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/DeepNested.java @@ -0,0 +1,14 @@ +package org.utbot.fuzzing.samples; + +public class DeepNested { + public class Nested1 { + public class Nested2 { + public int f(int i) { + if (i > 0) { + return 10; + } + return 0; + } + } + } +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/FailToGenerateListGeneric.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/FailToGenerateListGeneric.java new file mode 100644 index 0000000000..0e8a3d9aaf --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/FailToGenerateListGeneric.java @@ -0,0 +1,14 @@ +package org.utbot.fuzzing.samples; + +import java.util.List; + +@SuppressWarnings("unused") +public class FailToGenerateListGeneric { + + interface Something {} + + int func(List x) { + return x.size(); + } + +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/FieldSetterClass.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/FieldSetterClass.java new file mode 100644 index 0000000000..d8d184c6b3 --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/FieldSetterClass.java @@ -0,0 +1,28 @@ +package org.utbot.fuzzing.samples; + +@SuppressWarnings("All") +public class FieldSetterClass { + + public static int pubStaticField; + public final int pubFinalField = 0; + public int pubField; + public int pubFieldWithSetter; + private int prvField; + private int prvFieldWithSetter; + + public int getPubFieldWithSetter() { + return pubFieldWithSetter; + } + + public void setPubFieldWithSetter(int pubFieldWithSetter) { + this.pubFieldWithSetter = pubFieldWithSetter; + } + + public int getPrvFieldWithSetter() { + return prvFieldWithSetter; + } + + public void setPrvFieldWithSetter(int prvFieldWithSetter) { + this.prvFieldWithSetter = prvFieldWithSetter; + } +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/Implementations.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/Implementations.java new file mode 100644 index 0000000000..8ad0b55a08 --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/Implementations.java @@ -0,0 +1,43 @@ +package org.utbot.fuzzing.samples; + +public final class Implementations { + public interface A { + T getValue(); + } + + public static class AString implements A { + + private final String value; + + public AString(String value) { + this.value = value; + } + + @Override + public String getValue() { + return value; + } + } + + public static class AInteger implements A { + + private final Integer value; + + public AInteger(Integer value) { + this.value = value; + } + + @Override + public Integer getValue() { + return value; + } + } + + @SuppressWarnings("unused") + public static int test(A value) { + if (value.getValue() < 0) { + return 0; + } + return value.getValue(); + } +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/InnerClassWithEnums.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/InnerClassWithEnums.java new file mode 100644 index 0000000000..5e86197403 --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/InnerClassWithEnums.java @@ -0,0 +1,27 @@ +package org.utbot.fuzzing.samples; + +public class InnerClassWithEnums { + private SampleEnum a; + private SampleEnum b; + + public InnerClassWithEnums(SampleEnum a, SampleEnum b) { + this.a = a; + this.b = b; + } + + public SampleEnum getA() { + return a; + } + + public void setA(SampleEnum a) { + this.a = a; + } + + public SampleEnum getB() { + return b; + } + + public void setB(SampleEnum b) { + this.b = b; + } +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/OuterClassWithEnums.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/OuterClassWithEnums.java new file mode 100644 index 0000000000..4118036977 --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/OuterClassWithEnums.java @@ -0,0 +1,37 @@ +package org.utbot.fuzzing.samples; + +public class OuterClassWithEnums { + private SampleEnum value; + private final InnerClassWithEnums left; + private final InnerClassWithEnums right; + + public OuterClassWithEnums(SampleEnum value, InnerClassWithEnums left, InnerClassWithEnums right) { + this.value = value; + this.left = left; + this.right = right; + } + + public void setValue(SampleEnum value) { + this.value = value; + } + + public SampleEnum getA() { + if (value == SampleEnum.LEFT && left != null) { + return left.getA(); + } else if (value == SampleEnum.RIGHT && right != null) { + return right.getA(); + } else { + return null; + } + } + + public SampleEnum getB() { + if (value == SampleEnum.LEFT && left != null) { + return left.getB(); + } else if (value == SampleEnum.RIGHT && right != null) { + return right.getB(); + } else { + return null; + } + } +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/PackagePrivateFieldAndClass.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/PackagePrivateFieldAndClass.java new file mode 100644 index 0000000000..11986d8a16 --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/PackagePrivateFieldAndClass.java @@ -0,0 +1,16 @@ +package org.utbot.fuzzing.samples; + +@SuppressWarnings("All") +public class PackagePrivateFieldAndClass { + + volatile int pkgField = 0; + + PackagePrivateFieldAndClass() { + + } + + PackagePrivateFieldAndClass(int value) { + pkgField = value; + } + +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/SampleEnum.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/SampleEnum.java new file mode 100644 index 0000000000..401bd83217 --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/SampleEnum.java @@ -0,0 +1,6 @@ +package org.utbot.fuzzing.samples; + +public enum SampleEnum { + LEFT, + RIGHT +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/StringList.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/StringList.java new file mode 100644 index 0000000000..0cd91ecd37 --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/StringList.java @@ -0,0 +1,30 @@ +package org.utbot.fuzzing.samples; + +import java.util.*; + +public class StringList extends ArrayList { + public static StringList create() { + return new StringList(); + } + + public static List createAndUpcast() { + return new StringList(); + } + + public static List createListOfLists() { + return new ArrayList<>(); + } + + public static List> createListOfUpcastedLists() { + return new ArrayList<>(); + } + + public static List> createReadOnlyListOfReadOnlyLists() { + return new ArrayList<>(); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public static List> createListOfParametrizedLists(Optional elm) { + return Collections.singletonList(Collections.singletonList(elm.orElse(null))); + } +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/StringListHolder.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/StringListHolder.java new file mode 100644 index 0000000000..88df01f163 --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/StringListHolder.java @@ -0,0 +1,20 @@ +package org.utbot.fuzzing.samples; + +import java.util.List; + +public class StringListHolder { + private List strings; + + + public List getStrings() { + return strings; + } + + + public void setStrings(List strings) { + this.strings = strings; + } + + + public void methodUnderTest() {} +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/Stubs.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/Stubs.java new file mode 100644 index 0000000000..8423b68cbf --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/Stubs.java @@ -0,0 +1,35 @@ +package org.utbot.fuzzing.samples; + +import java.util.List; + +@SuppressWarnings("unused") +public class Stubs { + + public static void name(String value) {} + + public static > int resolve(T recursive) { + int result = 0; + for (T t : recursive) { + result++; + } + return result; + } + + public static > int types(T[] t1, T[] t2, T[] t3) { + return t1.length + t2.length + t3.length; + } + + public static int arrayLength(java.util.List[][] test) { + int length = 0; + for (int i = 0; i < test.length; i++) { + for (int j = 0; j < test[i].length; j++) { + length += test[i][j].size(); + } + } + return length; + } + + public static , B extends List, C extends List>> A example(A c1, B c2, C c) { + return c2.iterator().next(); + } +} diff --git a/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/WithInnerClass.java b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/WithInnerClass.java new file mode 100644 index 0000000000..3b012a73ad --- /dev/null +++ b/utbot-java-fuzzing/src/test/java/org/utbot/fuzzing/samples/WithInnerClass.java @@ -0,0 +1,19 @@ +package org.utbot.fuzzing.samples; + +public class WithInnerClass { + public class NonStatic { + public int x; + public NonStatic(int x) { this.x = x; } + } + int f(NonStatic b) { + return b.x * b.x; + } + + public static class Static { + public int x; + public Static(int x) { this.x = x; } + } + int g(Static b) { + return b.x * b.x; + } +} diff --git a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzer/TypeUtilsTest.kt b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzer/TypeUtilsTest.kt new file mode 100644 index 0000000000..d8e1df48b2 --- /dev/null +++ b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzer/TypeUtilsTest.kt @@ -0,0 +1,200 @@ +package org.utbot.fuzzer + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.fuzzing.runBlockingWithContext +import org.utbot.fuzzing.samples.StringList +import java.io.File +import java.lang.reflect.Type +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.util.Optional + +class TypeUtilsTest { + @Test + fun `resolveParameterTypes works correctly with non-generic method`() = runBlockingWithContext { + val actualTypes = resolveParameterTypes(LocalDateTime::ofInstant.executableId, jTypeOf()) + val expectedTypes = listOf(jTypeOf(), jTypeOf()) + + assertEquals(expectedTypes, actualTypes) + } + + @Test + fun `resolveParameterTypes works correctly with non-generic constructor`() = runBlockingWithContext { + val constructor = File::class.java.getConstructor(File::class.java, String::class.java) + val actualTypes = resolveParameterTypes(constructor.executableId, jTypeOf()) + val expectedTypes = listOf(jTypeOf(), jTypeOf()) + + assertEquals(expectedTypes, actualTypes) + } + + @Test + fun `resolveParameterTypes works correctly with non-generic method and incompatible return type`() = runBlockingWithContext { + val actualTypes = resolveParameterTypes(LocalDateTime::ofInstant.executableId, jTypeOf()) + assertNull(actualTypes) + } + + @Test + fun `resolveParameterTypes works correctly with non-generic constructor and incompatible return type`() = runBlockingWithContext { + val constructor = File::class.java.getConstructor(File::class.java, String::class.java) + val actualTypes = resolveParameterTypes(constructor.executableId, jTypeOf()) + + assertNull(actualTypes) + } + + @Test + fun `resolveParameterTypes works correctly with simple generic method`() = runBlockingWithContext { + val method = Optional::class.java.getMethod("of", Object::class.java) + val actualTypes = resolveParameterTypes(method.executableId, jTypeOf>()) + val expectedTypes = listOf(jTypeOf()) + + assertEquals(expectedTypes, actualTypes) + } + + @Test + fun `resolveParameterTypes works correctly with simple generic constructor`() = runBlockingWithContext { + val method = ArrayList::class.java.getConstructor(Collection::class.java) + val actualTypes = resolveParameterTypes(method.executableId, jTypeOf>()) + val expectedTypes = listOf(jTypeOf>()) + + assertEquals(expectedTypes, actualTypes) + } + + @Test + fun `resolveParameterTypes works correctly with method returning type with incompatible type parameter`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes(StringList::createAndUpcast.executableId, jTypeOf>()) + assertNull(actualTypes) + } + + @Test + fun `resolveParameterTypes works correctly with method returning subtype with incompatible type parameter`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes(StringList::create.executableId, jTypeOf>()) + assertNull(actualTypes) + } + + @Test + fun `resolveParameterTypes works correctly with method returning type with incompatible upper bound type parameter`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes(StringList::createAndUpcast.executableId, jTypeOf>()) + assertNull(actualTypes) + } + + @Test + fun `resolveParameterTypes works correctly with method returning type with incompatible lower bounded type parameter`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes(StringList::createAndUpcast.executableId, jTypeOf>()) + assertNull(actualTypes) + } + + @Test + fun `resolveParameterTypes works correctly with method returning type with compatible type parameter`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes(StringList::createAndUpcast.executableId, jTypeOf>()) + val expectedTypes = emptyList() + + assertEquals(expectedTypes, actualTypes) + } + + @Test + fun `resolveParameterTypes works correctly with method returning subtype with compatible type parameter`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes(StringList::create.executableId, jTypeOf>()) + val expectedTypes = emptyList() + + assertEquals(expectedTypes, actualTypes) + } + + @Test + fun `resolveParameterTypes works correctly with method returning type with compatible upper bound type parameter`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes(StringList::createAndUpcast.executableId, jTypeOf>()) + val expectedTypes = emptyList() + + assertEquals(expectedTypes, actualTypes) + } + + @Test + fun `resolveParameterTypes works with deep compatible bounded types`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes( + StringList::createListOfLists.executableId, + jTypeOf>>() + ) + val expectedTypes = emptyList() + + assertEquals(expectedTypes, actualTypes) + } + + @Test + fun `resolveParameterTypes works with deep types incompatible at the top layer`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes( + StringList::createListOfLists.executableId, + jTypeOf>>() + ) + + assertNull(actualTypes) + } + + @Test + fun `resolveParameterTypes works with deep compatible unbounded types`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes( + StringList::createListOfUpcastedLists.executableId, + jTypeOf>>() + ) + val expectedTypes = emptyList() + + assertEquals(expectedTypes, actualTypes) + } + + @Test + fun `resolveParameterTypes works with deep types incompatible at the second layer`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes( + StringList::createListOfUpcastedLists.executableId, + jTypeOf>>() + ) + + assertNull(actualTypes) + } + + @Test + fun `resolveParameterTypes works with methods returning compatible nested bounded types`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes( + StringList::createReadOnlyListOfReadOnlyLists.executableId, + jTypeOf>>() + ) + val expectedTypes = emptyList() + + assertEquals(expectedTypes, actualTypes) + } + + @Test + fun `resolveParameterTypes works with methods returning incompatible nested bounded types`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes( + StringList::createReadOnlyListOfReadOnlyLists.executableId, + jTypeOf>>() + ) + assertNull(actualTypes) + } + + @Test + fun `resolveParameterTypes resolves non-toplevel generics`() = + runBlockingWithContext { + val actualTypes = resolveParameterTypes( + StringList::class.java.getMethod("createListOfParametrizedLists", Optional::class.java).executableId, + jTypeOf>>>() + ) + val expectedTypes = listOf(jTypeOf>>()) + + assertEquals(expectedTypes, actualTypes) + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/IdGeneratorTest.kt b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/IdGeneratorTest.kt new file mode 100644 index 0000000000..34e36ac950 --- /dev/null +++ b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/IdGeneratorTest.kt @@ -0,0 +1,83 @@ +package org.utbot.fuzzing + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.utbot.framework.fuzzer.ReferencePreservingIntIdGenerator + +class IdGeneratorTest { + private enum class Size { S, M, L, XL } + private enum class Letter { K, L, M, N } + + @Test + fun `default id generator returns sequential values`() { + val idGenerator = ReferencePreservingIntIdGenerator() + val a = idGenerator.createId() + val b = idGenerator.createId() + val c = idGenerator.createId() + assertTrue(a == ReferencePreservingIntIdGenerator.DEFAULT_LOWER_BOUND + 1) + assertTrue(b == a + 1) + assertTrue(c == b + 1) + } + + @Test + fun `caching generator returns different ids for different values`() { + val idGenerator = ReferencePreservingIntIdGenerator() + val a = idGenerator.getOrCreateIdForValue("a") + val b = idGenerator.getOrCreateIdForValue("b") + assertNotEquals(a, b) + } + + @Test + fun `caching generator returns same ids for same values`() { + val idGenerator = ReferencePreservingIntIdGenerator() + val a = idGenerator.getOrCreateIdForValue("a") + idGenerator.getOrCreateIdForValue("b") + val c = idGenerator.getOrCreateIdForValue("a") + assertEquals(a, c) + } + + @Test + fun `caching generator returns consistent ids for enum values`() { + val idGenerator = ReferencePreservingIntIdGenerator(0) + val sizeIds = Size.values().map { it to idGenerator.getOrCreateIdForValue(it) }.toMap() + + assertEquals(idGenerator.getOrCreateIdForValue(Size.S), sizeIds[Size.S]) + assertEquals(idGenerator.getOrCreateIdForValue(Size.M), sizeIds[Size.M]) + assertEquals(idGenerator.getOrCreateIdForValue(Size.L), sizeIds[Size.L]) + assertEquals(idGenerator.getOrCreateIdForValue(Size.XL), sizeIds[Size.XL]) + + idGenerator.getOrCreateIdForValue(Letter.N) + idGenerator.getOrCreateIdForValue(Letter.M) + idGenerator.getOrCreateIdForValue(Letter.L) + idGenerator.getOrCreateIdForValue(Letter.K) + + assertEquals(1, idGenerator.getOrCreateIdForValue(Size.S)) + assertEquals(2, idGenerator.getOrCreateIdForValue(Size.M)) + assertEquals(3, idGenerator.getOrCreateIdForValue(Size.L)) + assertEquals(4, idGenerator.getOrCreateIdForValue(Size.XL)) + + assertEquals(8, idGenerator.getOrCreateIdForValue(Letter.K)) + assertEquals(7, idGenerator.getOrCreateIdForValue(Letter.L)) + assertEquals(6, idGenerator.getOrCreateIdForValue(Letter.M)) + assertEquals(5, idGenerator.getOrCreateIdForValue(Letter.N)) + } + + @Test + fun `caching generator respects reference equality`() { + val idGenerator = ReferencePreservingIntIdGenerator() + + val objA = listOf(1, 2, 3) + val objB = listOf(1, 2, 3) + val objC = objA + + val idA = idGenerator.getOrCreateIdForValue(objA) + val idB = idGenerator.getOrCreateIdForValue(objB) + val idC = idGenerator.getOrCreateIdForValue(objC) + + assertNotEquals(idA, idB) + assertEquals(idA, idC) + } + +} diff --git a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt new file mode 100644 index 0000000000..987b43c323 --- /dev/null +++ b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaFuzzingTest.kt @@ -0,0 +1,254 @@ +package org.utbot.fuzzing + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertInstanceOf +import org.junit.jupiter.api.Assertions.assertNotEquals +import org.junit.jupiter.api.Test +import org.utbot.framework.fuzzer.IdentityPreservingIdGenerator +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.constructor.ValueConstructor +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.fuzzer.FuzzedConcreteValue +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzing.providers.NullValueProvider +import org.utbot.fuzzing.samples.AccessibleObjects +import org.utbot.fuzzing.samples.DeepNested +import org.utbot.fuzzing.samples.FailToGenerateListGeneric +import org.utbot.fuzzing.samples.StringListHolder +import org.utbot.fuzzing.samples.Stubs +import org.utbot.fuzzing.utils.Trie +import java.util.concurrent.atomic.AtomicInteger +import kotlin.reflect.jvm.javaMethod + +internal object TestIdentityPreservingIdGenerator : IdentityPreservingIdGenerator { + private val cache = mutableMapOf() + private val gen = AtomicInteger() + override fun getOrCreateIdForValue(value: Any): Int = cache.computeIfAbsent(value) { createId() } + override fun createId(): Int = gen.incrementAndGet() +} + +class JavaFuzzingTest { + + @Test + fun `fuzzing doesn't throw an exception when type is unknown`() { + var count = 0 + runBlockingWithContext { + runJavaFuzzing( + TestIdentityPreservingIdGenerator, + methodUnderTest = MethodId( + DeepNested.Nested1.Nested2::class.id, + "f", + intClassId, + listOf(intClassId) + ), + constants = emptyList(), + names = emptyList(), + ) { _, _, _ -> + count += 1 + BaseFeedback(Trie.emptyNode(), Control.STOP) + } + } + assertEquals(0, count) + } + + @Test + fun `string generates same values`() { + fun collect(): List { + return runBlockingWithContext { + val results = mutableListOf() + var count = 0 + val probes = 10000 + runJavaFuzzing( + TestIdentityPreservingIdGenerator, + methodUnderTest = MethodId(Stubs::class.id, "name", voidClassId, listOf(stringClassId)), + constants = listOf( + FuzzedConcreteValue(stringClassId, "Hello"), + FuzzedConcreteValue(stringClassId, "World"), + FuzzedConcreteValue(stringClassId, "!"), + ), + names = emptyList() + ) { _, _, values -> + results += (values.first().model as UtPrimitiveModel).value as String + BaseFeedback(Trie.emptyNode(), if (++count < probes) Control.CONTINUE else Control.STOP) + } + assertEquals(count, results.size) + results + } + } + + val probe1 = collect() + val probe2 = collect() + assertEquals(probe1, probe2) + } + + @Test + fun `fuzzing should not generate values of private classes`() { + var exec = 0 + runBlockingWithContext { + runJavaFuzzing( + TestIdentityPreservingIdGenerator, + methodUnderTest = AccessibleObjects::class.java.declaredMethods.first { it.name == "test" }.executableId, + constants = emptyList(), + names = emptyList(), + ) { _, _, v -> + if (v.first().model !is UtNullModel) { + exec += 1 + } + BaseFeedback(Trie.emptyNode(), Control.STOP) + } + } + assertEquals(0, exec) { "Fuzzer should not create any values of private classes" } + } + + @Test + fun `fuzzing should not generate values of private enums`() { + var exec = 0 + runBlockingWithContext { + runJavaFuzzing( + TestIdentityPreservingIdGenerator, + methodUnderTest = AccessibleObjects::class.java.declaredMethods.first { it.name == "ordinal" }.executableId, + constants = emptyList(), + names = emptyList(), + ) { _, _, v -> + if (v.first().model !is UtNullModel) { + exec += 1 + } + BaseFeedback(Trie.emptyNode(), Control.STOP) + } + } + assertEquals(0, exec) { "Fuzzer should not create any values of private classes" } + } + + @Test + fun `fuzzing generate single test in case of collection with fail-to-generate generic type`() { + val size = 100 + var exec = size + val collections = ArrayList(exec) + runBlockingWithContext { + runJavaFuzzing( + TestIdentityPreservingIdGenerator, + methodUnderTest = FailToGenerateListGeneric::class.java.declaredMethods.first { it.name == "func" }.executableId, + constants = emptyList(), + names = emptyList(), + providers = defaultValueProviders(TestIdentityPreservingIdGenerator).map { it.except { it is NullValueProvider } } + ) { _, _, v -> + collections.add(v.first().model as? UtAssembleModel) + BaseFeedback(Trie.emptyNode(), if (--exec > 0) Control.CONTINUE else Control.STOP) + } + } + assertEquals(0, exec) { "Total fuzzer run number must be 0" } + assertEquals(size, collections.size) { "Total generated values number must be $size" } + assertEquals(size, collections.count { it is UtAssembleModel }) { "Total assemble models size must be $size" } + collections.filterIsInstance().forEach { + assertEquals(0, it.modificationsChain.size) + } + } + + @Test + fun `fuzzer correctly works with settable field that has a parameterized type`() { + val seenStringListHolders = mutableListOf() + var remainingRuns = 100 + runBlockingWithContext { + runJavaFuzzing( + TestIdentityPreservingIdGenerator, + methodUnderTest = StringListHolder::methodUnderTest.javaMethod!!.executableId, + constants = emptyList(), + names = emptyList(), + ) { thisInstance, _, _ -> + thisInstance?.let { + seenStringListHolders.add( + ValueConstructor().construct(listOf(it.model)).single().value as StringListHolder + ) + } + remainingRuns-- + BaseFeedback(Trie.emptyNode(), if (remainingRuns > 0) Control.CONTINUE else Control.STOP) + } + } + val seenStrings = seenStringListHolders.flatMap { it.strings.orEmpty().filterNotNull() } + assertNotEquals(emptyList(), seenStrings) + seenStrings.forEach { assertInstanceOf(String::class.java, it) } + } + + @Test + fun `value providers override every function of fuzzing in simple case`() { + val provided = MarkerValueProvider("p") + `value providers override every function of fuzzing`(provided, provided) + } + + @Test + fun `value providers override every function of fuzzing when merging`() { + val provider1 = MarkerValueProvider("p1") + val provider2 = MarkerValueProvider("p2") + val provided = provider1.with(provider2) + `value providers override every function of fuzzing`(provided, provider1) + `value providers override every function of fuzzing`(provided, provider2) + } + + @Test + fun `value providers override every function of fuzzing when excepting`() { + val provider1 = MarkerValueProvider("p1") + val provider2 = MarkerValueProvider("p2") + val provided = provider1.except(provider2) + `value providers override every function of fuzzing`(provided, provider1) + } + + @Test + fun `value providers override every function of fuzzing when fallback`() { + val provider1 = MarkerValueProvider("p1") + val provider2 = MarkerValueProvider("p2") + val provided = provider1.withFallback(provider2) + `value providers override every function of fuzzing`(provided, provider1) + `value providers override every function of fuzzing`(provided, provider2) + } + + private fun `value providers override every function of fuzzing`(provided: JavaValueProvider, valueProvider: MarkerValueProvider) { + var executions = 0 + runBlockingWithContext { + runJavaFuzzing( + TestIdentityPreservingIdGenerator, + methodUnderTest = FailToGenerateListGeneric::class.java.declaredMethods.first { it.name == "func" }.executableId, + constants = emptyList(), + names = emptyList(), + providers = listOfNotNull(provided) + ) { _, _, _ -> + executions++ + BaseFeedback(Trie.emptyNode(), Control.STOP) + } + } + + assertNotEquals(0, valueProvider.enrich) { "Enrich is never called for ${valueProvider.name}" } + assertNotEquals(0, valueProvider.accept) { "Accept is never called for ${valueProvider.name}" } + assertNotEquals(0, valueProvider.generate) { "Generate is never called for ${valueProvider.name}" } + assertEquals(0, executions) { "Execution must be never called, because of empty seed supply for ${valueProvider.name}" } + } +} + +class MarkerValueProvider>( + val name: String +) : ValueProvider { + var enrich: Int = 0 + var accept: Int = 0 + var generate: Int = 0 + + override fun enrich(description: D, type: T, scope: Scope) { + enrich++ + } + + override fun accept(type: T): Boolean { + accept++ + return true + } + + override fun generate(description: D, type: T): Sequence> { + generate++ + return emptySequence() + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaTypesTest.kt b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaTypesTest.kt new file mode 100644 index 0000000000..5b6a5fbbfa --- /dev/null +++ b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaTypesTest.kt @@ -0,0 +1,173 @@ +package org.utbot.fuzzing + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.iterableClassId +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzing.samples.Implementations +import org.utbot.fuzzing.samples.Stubs +import java.lang.Number +import java.lang.reflect.GenericArrayType +import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.util.* +import java.util.List +import kotlin.collections.HashMap +import kotlin.collections.HashSet + +class JavaTypesTest { + + @Test + fun `recursive generic types are recognized correctly`() { + runBlockingWithContext { + val methods = Stubs::class.java.methods + val method = methods.first { it.name == "resolve" && it.returnType == Int::class.javaPrimitiveType } + val typeCache = IdentityHashMap() + val type = toFuzzerType(method.genericParameterTypes.first(), typeCache) + Assertions.assertEquals(1, typeCache.size) + Assertions.assertTrue(typeCache.values.all { type === it }) + Assertions.assertEquals(1, type.generics.size) + Assertions.assertTrue(typeCache.values.all { type.generics[0] === it }) + + try { + // If FuzzerType has implemented `equals` and `hashCode` or is data class, + // that implements those methods implicitly, + // then adding it to hash table throws [StackOverflowError] + val set = HashSet() + set += type + } catch (soe: StackOverflowError) { + Assertions.fail( + "Looks like FuzzerType implements equals and hashCode, " + + "which leads unstable behaviour in recursive generics ", soe + ) + } + } + } + + @Test + fun `can pass types through`() { + runBlockingWithContext { + val cache = HashMap() + val methods = Stubs::class.java.methods + val method = methods.first { it.name == "types" } + val types = method.genericParameterTypes.map { + toFuzzerType(it, cache) + } + Assertions.assertEquals( + 3, + cache.size + ) { "Cache should contain following types: List, Number and T[] for $method" } + Assertions.assertTrue(cache.keys.any { t -> + t is Class<*> && t == Number::class.java + }) + Assertions.assertTrue(cache.keys.any { t -> + t is ParameterizedType + && t.rawType == List::class.java + && t.actualTypeArguments.size == 1 + && t.actualTypeArguments.first() == Number::class.java + }) + Assertions.assertTrue(cache.keys.any { t -> + t is GenericArrayType + && t.typeName == "T[]" + }) + } + } + + @Test + fun `arrays with generics can be resolved`() { + runBlockingWithContext { + val cache = HashMap() + val methods = Stubs::class.java.methods + val method = methods.first { it.name == "arrayLength" } + method.genericParameterTypes.map { + toFuzzerType(it, cache) + } + Assertions.assertEquals( + 4, + cache.size + ) { "Cache should contain following types: List, Number and T[] for $method" } + Assertions.assertTrue(cache.keys.any { t -> + t is Class<*> && t == Number::class.java + }) + Assertions.assertTrue(cache.keys.any { t -> + t is ParameterizedType + && t.rawType == List::class.java + && t.actualTypeArguments.size == 1 + && t.actualTypeArguments.first().typeName == "T" + }) + Assertions.assertTrue(cache.keys.any { t -> + t is GenericArrayType + && t.typeName == "java.util.List[]" + }) + Assertions.assertTrue(cache.keys.any { t -> + t is GenericArrayType + && t.typeName == "java.util.List[][]" + }) + } + } + + @Test + fun `run complex type dependency call`() { + runBlockingWithContext { + val cache = HashMap() + val methods = Stubs::class.java.methods + val method = methods.first { it.name == "example" } + val types = method.genericParameterTypes + Assertions.assertTrue(types.size == 3 && types[0].typeName == "A" && types[1].typeName == "B" && types[2].typeName == "C") { "bad input parameters" } + method.genericParameterTypes.map { + toFuzzerType(it, cache) + } + Assertions.assertEquals(4, cache.size) + val typeIterableB = cache[types[0].replaceWithUpperBoundUntilNotTypeVariable()]!! + val genericOfIterableB = with(typeIterableB) { + Assertions.assertEquals(iterableClassId, classId) + Assertions.assertEquals(1, generics.size) + generics[0] + } + val typeListA = cache[types[1].replaceWithUpperBoundUntilNotTypeVariable()]!! + val genericOfListA = with(typeListA) { + Assertions.assertEquals(List::class.id, classId) + Assertions.assertEquals(1, generics.size) + generics[0] + } + Assertions.assertEquals(1, genericOfIterableB.generics.size) + Assertions.assertEquals(1, genericOfListA.generics.size) + Assertions.assertTrue(genericOfIterableB.generics[0] === typeIterableB) { "Because of recursive types generic of B must depend on B itself" } + Assertions.assertTrue(genericOfListA.generics[0] === typeListA) { "Because of recursive types generic of A must depend on A itself" } + + val typeListC = cache[types[2].replaceWithUpperBoundUntilNotTypeVariable()]!! + val genericOfListC = with(typeListC) { + Assertions.assertEquals(List::class.id, classId) + Assertions.assertEquals(1, generics.size) + generics[0] + } + + Assertions.assertEquals(1, genericOfListC.generics.size) + Assertions.assertEquals(iterableClassId, genericOfListC.generics[0].classId) + Assertions.assertTrue(genericOfListC.generics[0].generics[0] === typeListA) { "Generic of C must lead to type A" } + } + } + + @Test + fun `can correctly gather hierarchy information`() { + runBlockingWithContext { + val cache = HashMap() + val methods = Implementations::class.java.methods + val method = methods.first { it.name == "test" } + val type = method.genericParameterTypes.map { + toFuzzerType(it, cache) + }.first() + + val badType = toFuzzerType(Implementations.AString::class.java, cache) + val badTypeHierarchy = badType.traverseHierarchy(cache).toSet() + Assertions.assertEquals(2, badTypeHierarchy.size) { "There's only one class (Object) and one interface should be found" } + Assertions.assertFalse(badTypeHierarchy.contains(type)) { "Bad type hierarchy should not contain tested type $type" } + + val goodType = toFuzzerType(Implementations.AInteger::class.java, cache) + val goodTypeHierarchy = goodType.traverseHierarchy(cache).toSet() + Assertions.assertEquals(2, goodTypeHierarchy.size) { "There's only one class (Object) and one interface should be found" } + Assertions.assertTrue(goodTypeHierarchy.contains(type)) { "Good type hierarchy should contain tested type $type" } + } + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaValueProviderTest.kt b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaValueProviderTest.kt new file mode 100644 index 0000000000..32fcf7b156 --- /dev/null +++ b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/JavaValueProviderTest.kt @@ -0,0 +1,61 @@ +package org.utbot.fuzzing + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.Instruction +import org.utbot.framework.plugin.api.util.collectionClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.stringClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedType +import org.utbot.fuzzing.providers.ListSetValueProvider +import org.utbot.fuzzing.providers.MapValueProvider +import org.utbot.fuzzing.samples.ConcreateMap +import org.utbot.fuzzing.samples.ConcreteList +import org.utbot.fuzzing.utils.Trie +import java.lang.reflect.Type +import kotlin.random.Random + +fun emptyFuzzerDescription(typeCache: MutableMap) = FuzzedDescription( + FuzzedMethodDescription("no name", voidClassId, emptyList()), + Trie(Instruction::id), + typeCache, + Random(42) +) + +class JavaValueProviderTest { + + @Test + fun `collection value provider correctly resolves types for concrete types of map`() { + val typeCache = mutableMapOf() + runBlockingWithContext { + val seed = MapValueProvider(TestIdentityPreservingIdGenerator).generate( + emptyFuzzerDescription(typeCache), + toFuzzerType(ConcreateMap::class.java, typeCache) + ).first() + val collection = seed as Seed.Collection + val types = collection.modify.types + Assertions.assertEquals(2, types.size) + Assertions.assertEquals(types[0].classId, stringClassId) + Assertions.assertEquals(types[1].classId, java.lang.Number::class.java.id) + } + } + + @Test + fun `collection value provider correctly resolves types for concrete types of list`() { + val typeCache = mutableMapOf() + runBlockingWithContext { + val seed = ListSetValueProvider(TestIdentityPreservingIdGenerator).generate( + emptyFuzzerDescription(typeCache), + toFuzzerType(ConcreteList::class.java, typeCache) + ).first() + val collection = seed as Seed.Collection + val types = collection.modify.types + Assertions.assertEquals(1, types.size) + Assertions.assertEquals(types[0].classId, collectionClassId) + Assertions.assertEquals(1, types[0].generics.size) + Assertions.assertEquals(types[0].generics[0].classId, stringClassId) + } + } +} \ No newline at end of file diff --git a/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/Utils.kt b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/Utils.kt new file mode 100644 index 0000000000..d9a7b10c7e --- /dev/null +++ b/utbot-java-fuzzing/src/test/kotlin/org/utbot/fuzzing/Utils.kt @@ -0,0 +1,16 @@ +package org.utbot.fuzzing + +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.withUtContext + +internal fun V.runBlockingWithContext(block: suspend () -> T) : T { + return withUtContext(UtContext(this!!::class.java.classLoader)) { + runBlocking { + withTimeout(10000) { + block() + } + } + } +} \ No newline at end of file diff --git a/utbot-js/.gitignore b/utbot-js/.gitignore new file mode 100644 index 0000000000..c7708ff1e2 --- /dev/null +++ b/utbot-js/.gitignore @@ -0,0 +1 @@ +samples \ No newline at end of file diff --git a/utbot-js/build.gradle.kts b/utbot-js/build.gradle.kts new file mode 100644 index 0000000000..9fdffc1fb7 --- /dev/null +++ b/utbot-js/build.gradle.kts @@ -0,0 +1,46 @@ +val intellijPluginVersion: String? by rootProject +val kotlinLoggingVersion: String? by rootProject +val apacheCommonsTextVersion: String? by rootProject +val jacksonVersion: String? by rootProject +val ideType: String? by rootProject +val pythonCommunityPluginVersion: String? by rootProject +val pythonUltimatePluginVersion: String? by rootProject + +tasks { + compileKotlin { + kotlinOptions { + jvmTarget = "17" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class") + allWarningsAsErrors = false + } + } + + java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_17 + } + + test { + useJUnitPlatform() + } +} + +dependencies { + testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") + api(project(":utbot-framework")) + // https://mvnrepository.com/artifact/com.google.javascript/closure-compiler + implementation("com.google.javascript:closure-compiler:v20221102") + + // https://mvnrepository.com/artifact/org.json/json + implementation(group = "org.json", name = "json", version = "20220320") + + // https://mvnrepository.com/artifact/commons-io/commons-io + implementation(group = "commons-io", name = "commons-io", version = "2.11.0") + + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) + implementation("org.functionaljava:functionaljava:5.0") + implementation("org.functionaljava:functionaljava-quickcheck:5.0") + implementation("org.functionaljava:functionaljava-java-core:5.0") + implementation(group = "org.apache.commons", name = "commons-text", version = apacheCommonsTextVersion) +} diff --git a/utbot-js/docs/CLI.md b/utbot-js/docs/CLI.md new file mode 100644 index 0000000000..ee2c5ff0ae --- /dev/null +++ b/utbot-js/docs/CLI.md @@ -0,0 +1,70 @@ +## Build + +.jar file can be built in GitHub Actions with script publish-plugin-and-cli-from-branch. + +## Requirements + +* NodeJs 10.0.0 or higher (available to download https://nodejs.org/en/download/) +* Java 11 or higher (available to download https://www.oracle.com/java/technologies/downloads/) + +## Basic usage + +Generate tests: + + java -jar utbot-cli.jar generate_js --source="dir/file_with_sources.js" --output="dir/generated_tests.js" + +This will generate tests for top-level functions from `file_with_sources.js`. + +Run generated tests: + + java -jar utbot-cli.jar run_js --fileOrDir="generated_tests.js" + +This will run generated tests from file or directory. + +Generate coverage report: + + java -jar utbot-cli.jar coverage_js --source=dir/generated_tests.js + +This will generate coverage report from generated tests and print in `StdOut` + +## `generate_js` options + +- `-s, --source ` + + (required) Source code file for a test generation. +- `-c, --class ` + + If not specified tests for top-level functions are generated, otherwise for the specified class. + +- `-o, --output ` + + File for generated tests. +- `-p, --print-test` + + Specifies whether test should be printed out to `StdOut` (default = false) +- `-t, --timeout ` + + Timeout for Node.js to run scripts in seconds (default = 5) + +## `run_js` options + +- `-f, --fileOrDir` + + (required) File or directory with tests. +- `-o, --output` + + Specifies output of .txt file for test framework result (If empty prints to `StdOut`) + +- `-t, --test-framework [mocha]` + + Test framework of tests to run. + +## `coverage_js` options + +- `-s, --source ` + + (required) File with tests to generate a report. + +- `-o, --output` + + Specifies output .json file for generated tests (If empty prints .json to `StdOut`) diff --git a/utbot-js/samples/arrays.js b/utbot-js/samples/arrays.js new file mode 100644 index 0000000000..28e39a4ca6 --- /dev/null +++ b/utbot-js/samples/arrays.js @@ -0,0 +1,38 @@ +///region Simple array test +function simpleArray(arr) { + if (arr[0] === 5) { + return 5 + } + return 1 +} +simpleArray([0, 2]) +///endregion +///region Array of objects test +class ObjectParameter { + + constructor(a) { + this.first = a + } + + performAction(value) { + return 2 * value + } +} + +function arrayOfObjects(arr) { + if (arr[0].first === 2) { + return 1 + } + return 10 +} + +let arr = [] +arr[0] = new ObjectParameter(10) +arrayOfObjects(arr) +///endregion +///region Function returns array test +function returnsArray(num) { + return [num] +} +returnsArray(5) +///endregion \ No newline at end of file diff --git a/utbot-js/samples/bitOperators.js b/utbot-js/samples/bitOperators.js new file mode 100644 index 0000000000..3f69723358 --- /dev/null +++ b/utbot-js/samples/bitOperators.js @@ -0,0 +1,31 @@ +class BitOperators { + + complement(x) { + return (~x) === 1 + } + + xor(x, y) { + return (x ^ y) === 0 + } + + and(x) { + return (x & (x - 1)) === 0 + } + + Not(a, b) { + let d = a && b + let e = !a || b + return d && e ? 100 : 200 + } + + shl(x) { + return (x << 1) === 2 + } + + shlWithBigLongShift(shift) { + if (shift < 40) { + return 1 + } + return (0x77777777 << shift) === 0x77777770 ? 2 : 3 + } +} diff --git a/utbot-js/samples/commonIfStatement.js b/utbot-js/samples/commonIfStatement.js new file mode 100644 index 0000000000..94b6880a7e --- /dev/null +++ b/utbot-js/samples/commonIfStatement.js @@ -0,0 +1,7 @@ +function foo(a,b) { + if (a > 10) { + return a * b + } else { + return -1 + } +} diff --git a/utbot-js/samples/commonLoops.js b/utbot-js/samples/commonLoops.js new file mode 100644 index 0000000000..9aae30694c --- /dev/null +++ b/utbot-js/samples/commonLoops.js @@ -0,0 +1,27 @@ +class Loops { + + whileLoop(value) { + let i = 0 + let sum = 0 + while (i < value) { + sum += i + i += 1 + } + return sum + } + + loopInsideLoop(x) { + for (let i = x - 5; i < x; i++) { + if (i < 0) { + return 2 + } else { + for (let j = i; j < x + i; j++) { + if (j === 7) { + return 1 + } + } + } + } + return -1 + } +} diff --git a/utbot-js/samples/commonRecursion.js b/utbot-js/samples/commonRecursion.js new file mode 100644 index 0000000000..e2bd694537 --- /dev/null +++ b/utbot-js/samples/commonRecursion.js @@ -0,0 +1,19 @@ +class Recursion { + factorial(n) { + if (n < 0) + return -1 + if (n === 0) + return 1 + return n * this.factorial(n - 1) + } + + fib(n) { + if (n < 0 || n > 25) + return -1 + if (n === 0) + return 0 + if (n === 1) + return 1 + return this.fib(n - 1) + this.fib(n - 2) + } +} diff --git a/utbot-js/samples/commonString.js b/utbot-js/samples/commonString.js new file mode 100644 index 0000000000..24a5cdc2a6 --- /dev/null +++ b/utbot-js/samples/commonString.js @@ -0,0 +1,19 @@ +class StringExamples { + + isNotBlank(cs) { + return cs.length !== 0 + } + + nullableStringBuffer(buffer, i) { + if (i >= 0) { + buffer += "Positive" + } else { + buffer += "Negative" + } + return buffer.toString() + } + + length(cs) { + return cs == null ? 0 : cs.length + } +} diff --git a/utbot-js/samples/functionsThrowExceptionsInRow.js b/utbot-js/samples/functionsThrowExceptionsInRow.js new file mode 100644 index 0000000000..0a90662d13 --- /dev/null +++ b/utbot-js/samples/functionsThrowExceptionsInRow.js @@ -0,0 +1,18 @@ +function customError(a) { + if (a > 5) { + throw Error("MyCustomError") + } else { + return 10 + } +} + +function goodBoy(a) { + switch (a) { + case 5: + return 5 + case 10: + return 10 + default: + return 0 + } +} diff --git a/utbot-js/samples/mapStructure.js b/utbot-js/samples/mapStructure.js new file mode 100644 index 0000000000..2bc07494db --- /dev/null +++ b/utbot-js/samples/mapStructure.js @@ -0,0 +1,21 @@ +// Maps in JavaScript are untyped, so only maps with basic key/value types are feasible to support +///region Simple Map test +function simpleMap(map, compareValue) { + if (map.get("a") === compareValue) { + return 5 + } + return 1 +} + +const map1 = new Map() +map1.set("b", 3.0) +simpleMap(map1, 5) +///endregion +///region Function returns Map test +function returnsMap(name, value) { + let temp = new Map() + return temp.set(name, value) +} + +returnsMap("a", 5) +///endregion diff --git a/utbot-js/samples/scenarioMultyClassNoTopLevel.js b/utbot-js/samples/scenarioMultyClassNoTopLevel.js new file mode 100644 index 0000000000..7f6973db3c --- /dev/null +++ b/utbot-js/samples/scenarioMultyClassNoTopLevel.js @@ -0,0 +1,39 @@ +class Na { + constructor(num) { + this.num = num + } + + double() { + return this.num * 2 + } + + static test(a, b) { + return a + 2 * b + } +} + +class Kek { + foo(a, b) { + return a + b + } + + fString(a, b) { + return a + b + } + + fDel(a, b) { + return a / b + } + + fObj(a, b) { + return a.num + b.num + } + + getDone(a) { + a.done() + } + + done() { + return this.toString() + } +} diff --git a/utbot-js/samples/scenarioObjectParameter.js b/utbot-js/samples/scenarioObjectParameter.js new file mode 100644 index 0000000000..4096277c40 --- /dev/null +++ b/utbot-js/samples/scenarioObjectParameter.js @@ -0,0 +1,16 @@ +class ObjectParameter { + + constructor(a) { + this.first = a + } + + performAction(value) { + return 2 * value + } +} + +function functionToTest(obj, v) { + return obj.performAction(v) +} + +functionToTest(new ObjectParameter(5), 5) diff --git a/utbot-js/samples/scenarioStaticMethod.js b/utbot-js/samples/scenarioStaticMethod.js new file mode 100644 index 0000000000..783937e859 --- /dev/null +++ b/utbot-js/samples/scenarioStaticMethod.js @@ -0,0 +1,13 @@ +class Object { + + constructor(a) { + this.first = a + } + + static functionToTest(value) { + if (value > 1024 && value < 1026) { + return 2 * value + } + return value + } +} diff --git a/utbot-js/samples/scenarioThrowError.js b/utbot-js/samples/scenarioThrowError.js new file mode 100644 index 0000000000..2068695dd1 --- /dev/null +++ b/utbot-js/samples/scenarioThrowError.js @@ -0,0 +1,11 @@ +function functionToTest(a) { + if (a === true) { + throw Error("err") + } else if (a === 1) { + while (true) { + } + } else { + return -1 + } + +} diff --git a/utbot-js/samples/setStructure.js b/utbot-js/samples/setStructure.js new file mode 100644 index 0000000000..547a2e8058 --- /dev/null +++ b/utbot-js/samples/setStructure.js @@ -0,0 +1,22 @@ +// Sets in JavaScript are untyped, so only sets with basic value types are feasible to support +///region Simple Set test +function setTest(set, checkValue) { + if (set.has(checkValue)) { + return set + } + return set +} + +let s = new Set() +s.add(5) +s.add(6) +setTest(s, 4) +///endregion +///region Function returns Set test +function returnsSet(num) { + let temp = new Set() + return temp.add(num) +} + +returnsSet(5) +///endregion \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/api/JsTestGenerator.kt b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt new file mode 100644 index 0000000000..4b7be92daf --- /dev/null +++ b/utbot-js/src/main/kotlin/api/JsTestGenerator.kt @@ -0,0 +1,422 @@ +package api + +import codegen.JsCodeGenerator +import com.google.javascript.rhino.Node +import framework.api.js.JsClassId +import framework.api.js.JsMethodId +import framework.api.js.JsUtFuzzedExecution +import framework.api.js.util.isClass +import framework.api.js.util.isJsArray +import framework.api.js.util.isJsBasic +import framework.api.js.util.jsErrorClassId +import framework.api.js.util.jsUndefinedClassId +import framework.codegen.JsImport +import framework.codegen.ModuleType +import fuzzer.JsFeedback +import fuzzer.JsFuzzingExecutionFeedback +import fuzzer.JsMethodDescription +import fuzzer.JsStatement +import fuzzer.JsTimeoutExecution +import fuzzer.JsValidExecution +import fuzzer.runFuzzing +import java.io.File +import java.util.concurrent.CancellationException +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import mu.KotlinLogging +import org.utbot.common.runBlockingWithCancellationPredicate +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtExecutionResult +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtExplicitlyThrownException +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtTimeoutException +import org.utbot.fuzzing.Control +import org.utbot.fuzzing.utils.Trie +import parser.JsAstScrapper +import parser.JsFuzzerAstVisitor +import parser.JsParserUtils +import parser.JsParserUtils.getAbstractFunctionName +import parser.JsParserUtils.getAbstractFunctionParams +import parser.JsParserUtils.getClassMethods +import parser.JsParserUtils.getClassName +import parser.JsParserUtils.getParamName +import parser.JsParserUtils.runParser +import parser.JsToplevelFunctionAstVisitor +import providers.exports.IExportsProvider +import service.InstrumentationService +import service.PackageJson +import service.PackageJsonService +import service.ServiceContext +import service.TernService +import service.coverage.CoverageMode +import service.coverage.CoverageServiceProvider +import settings.JsDynamicSettings +import settings.JsExportsSettings.endComment +import settings.JsExportsSettings.startComment +import settings.JsTestGenerationSettings.fileUnderTestAliases +import settings.JsTestGenerationSettings.fuzzingThreshold +import settings.JsTestGenerationSettings.fuzzingTimeout +import utils.PathResolver +import utils.constructClass +import utils.data.ResultData +import utils.toJsAny + +private val logger = KotlinLogging.logger {} + +class JsTestGenerator( + private val fileText: String, + private var sourceFilePath: String, + private var projectPath: String = sourceFilePath.substringBeforeLast("/"), + private val selectedMethods: List? = null, + private var parentClassName: String? = null, + private var outputFilePath: String?, + private val exportsManager: ((String?, String) -> String) -> Unit, + private val settings: JsDynamicSettings, + private val isCancelled: () -> Boolean = { false } +) { + + private val exports = mutableSetOf() + + private lateinit var parsedFile: Node + + private lateinit var astScrapper: JsAstScrapper + + private val utbotDir = "utbotJs" + + init { + fixPathDelims() + } + + private fun fixPathDelims() { + projectPath = projectPath.replace("\\", "/") + outputFilePath = outputFilePath?.replace("\\", "/") + sourceFilePath = sourceFilePath.replace("\\", "/") + } + + /** + * Returns String representation of generated tests. + */ + fun run(): String { + parsedFile = runParser(fileText) + astScrapper = JsAstScrapper(parsedFile, sourceFilePath) + val context = ServiceContext( + utbotDir = utbotDir, + projectPath = projectPath, + filePathToInference = astScrapper.filesToInfer, + parsedFile = parsedFile, + settings = settings, + ) + context.packageJson = PackageJsonService( + sourceFilePath, + File(projectPath), + ).findClosestConfig() + val paramNames = mutableMapOf>() + val testSets = mutableListOf() + val classNode = + JsParserUtils.searchForClassDecl( + className = parentClassName, + parsedFile = parsedFile, + strict = selectedMethods?.isNotEmpty() ?: false + ) + parentClassName = classNode?.getClassName() + val classId = makeJsClassId(classNode, TernService(context)) + val methods = makeMethodsToTest() + if (methods.isEmpty()) throw IllegalArgumentException("No methods to test were found!") + methods.forEach { funcNode -> + makeTestsForMethod(classId, funcNode, classNode, context, testSets, paramNames) + } + val importPrefix = makeImportPrefix() + val moduleType = ModuleType.fromPackageJson(context.packageJson) + val imports = listOf( + JsImport( + "*", + fileUnderTestAliases, + "./$importPrefix/${sourceFilePath.substringAfterLast("/")}", + moduleType + ), + JsImport( + "*", + "assert", + "assert", + moduleType + ) + ) + val codeGen = JsCodeGenerator( + classUnderTest = classId, + paramNames = paramNames, + imports = imports + ) + return codeGen.generateAsStringWithTestReport(testSets).generatedCode + } + + private fun makeTestsForMethod( + classId: JsClassId, + funcNode: Node, + classNode: Node?, + context: ServiceContext, + testSets: MutableList, + paramNames: MutableMap> + ) { + val execId = classId.allMethods.find { + it.name == funcNode.getAbstractFunctionName() + } ?: throw IllegalStateException() + manageExports(classNode, funcNode, execId, context.packageJson) + val executionResults = mutableListOf() + try { + runBlockingWithCancellationPredicate(isCancelled) { + runFuzzingFlow(funcNode, execId, context).collect { + executionResults += it + } + } + } catch (e: CancellationException) { + logger.info { "Fuzzing was stopped due to test generation cancellation" } + } + if (executionResults.isEmpty()) { + if (isCancelled()) return + throw UnsupportedOperationException("No test cases were generated for ${funcNode.getAbstractFunctionName()}") + } + logger.info { "${executionResults.size} test cases were suggested after fuzzing" } + val testsForGenerator = mutableListOf() + val errorsForGenerator = mutableMapOf() + executionResults.forEach { value -> + when (value) { + is JsTimeoutExecution -> errorsForGenerator[value.utTimeout.exception.message!!] = 1 + is JsValidExecution -> testsForGenerator.add(value.utFuzzedExecution) + } + } + + val testSet = CgMethodTestSet( + executableId = execId, + errors = errorsForGenerator, + executions = testsForGenerator, + ) + testSets += testSet + paramNames[execId] = funcNode.getAbstractFunctionParams().map { it.getParamName() } + } + + private fun makeImportPrefix(): String { + return outputFilePath?.let { + PathResolver.getRelativePath( + File(it).parent, + File(sourceFilePath).parent, + ) + } ?: "" + } + + private fun getUtModelResult( + execId: JsMethodId, + resultData: ResultData, + fuzzedValues: List + ): UtExecutionResult { + if (resultData.isError && resultData.rawString == "Timeout") return UtTimeoutException( + TimeoutException("Timeout in generating test for ${ + execId.parameters + .zip(fuzzedValues) + .joinToString( + prefix = "${execId.name}(", + separator = ", ", + postfix = ")" + ) { (_, value) -> value.toString() } + }") + ) + val (returnValue, valueClassId) = resultData.toJsAny( + if (execId.returnType.isJsBasic) JsClassId(resultData.type) else execId.returnType + ) + val result = JsUtModelConstructor().construct(returnValue, valueClassId) + val utExecResult = when (result.classId) { + jsErrorClassId -> UtExplicitlyThrownException(Throwable(returnValue.toString()), false) + else -> UtExecutionSuccess(result) + } + return utExecResult + } + + private fun runFuzzingFlow( + funcNode: Node, + execId: JsMethodId, + context: ServiceContext, + ): Flow = flow { + val fuzzerVisitor = JsFuzzerAstVisitor() + fuzzerVisitor.accept(funcNode) + val jsDescription = JsMethodDescription( + name = funcNode.getAbstractFunctionName(), + parameters = execId.parameters, + classId = execId.classId, + concreteValues = fuzzerVisitor.fuzzedConcreteValues, + tracer = Trie(JsStatement::number) + ) + val collectedValues = mutableListOf>() + // .location field gets us "jsFile:A:B", then we get A and B as ints + val funcLocation = funcNode.firstChild!!.location.substringAfter("jsFile:") + .split(":").map { it.toInt() } + logger.info { "Function under test location according to parser is [${funcLocation[0]}, ${funcLocation[1]}]" } + val instrService = InstrumentationService(context, funcLocation[0] to funcLocation[1]) + instrService.instrument() + val coverageProvider = CoverageServiceProvider( + context, + instrService, + context.settings.coverageMode, + jsDescription + ) + val allStmts = instrService.allStatements + logger.info { "Statements to cover: (${allStmts.joinToString { toString() }})" } + val currentlyCoveredStmts = mutableSetOf() + val startTime = System.currentTimeMillis() + runFuzzing(jsDescription) { description, values -> + if (isCancelled() || System.currentTimeMillis() - startTime > fuzzingTimeout) + return@runFuzzing JsFeedback(Control.STOP) + collectedValues += values + if (collectedValues.size >= if (context.settings.coverageMode == CoverageMode.FAST) fuzzingThreshold else 1) { + try { + val (coveredStmts, executionResults) = coverageProvider.get( + collectedValues, + execId + ) + coveredStmts.zip(executionResults).forEach { (covData, resultData) -> + val params = collectedValues[resultData.index] + val result = + getUtModelResult( + execId = execId, + resultData = resultData, + jsDescription.thisInstance?.let { params.drop(1) } ?: params + ) + if (result is UtTimeoutException) { + emit(JsTimeoutExecution(result)) + return@runFuzzing JsFeedback(Control.PASS) + } else if (!currentlyCoveredStmts.containsAll(covData.additionalCoverage)) { + val (thisObject, modelList) = if (!funcNode.parent!!.isClassMembers) { + null to params + } else params[0] to params.drop(1) + val initEnv = + EnvironmentModels(thisObject, modelList, mapOf(), execId) + emit( + JsValidExecution( + JsUtFuzzedExecution( + stateBefore = initEnv, + stateAfter = initEnv, + result = result, + ) + ) + ) + currentlyCoveredStmts += covData.additionalCoverage + val trieNode = description.tracer.add(covData.additionalCoverage.map { JsStatement(it) }) + return@runFuzzing JsFeedback(control = Control.CONTINUE, result = trieNode) + } + if (currentlyCoveredStmts.containsAll(allStmts)) return@runFuzzing JsFeedback(Control.STOP) + } + } catch (e: TimeoutException) { + emit( + JsTimeoutExecution( + UtTimeoutException( + TimeoutException("Timeout on unknown test case. Consider using \"Basic\" coverage mode") + ) + ) + ) + return@runFuzzing JsFeedback(Control.STOP) + } finally { + collectedValues.clear() + } + } + return@runFuzzing JsFeedback(Control.PASS) + } + instrService.removeTempFiles() + } + + private fun manageExports( + classNode: Node?, + funcNode: Node, + execId: JsMethodId, + packageJson: PackageJson + ) { + val obligatoryExport = (classNode?.getClassName() ?: funcNode.getAbstractFunctionName()).toString() + val collectedExports = collectExports(execId) + val exportsProvider = IExportsProvider.providerByPackageJson(packageJson) + exports += (collectedExports + obligatoryExport) + exportsManager { existingSection, currentFileText -> + val existingExportsSet = existingSection?.let { section -> + val trimmedSection = section.substringAfter(exportsProvider.exportsPrefix) + .substringBeforeLast(exportsProvider.exportsPostfix) + val exportRegex = exportsProvider.exportsRegex + val existingExports = trimmedSection.split(exportsProvider.exportsDelimiter) + .filter { it.contains(exportRegex) && it.isNotBlank() } + existingExports.map { rawLine -> + exportRegex.find(rawLine)?.groups?.get(1)?.value ?: throw IllegalStateException() + }.toSet() + } ?: emptySet() + val resultSet = existingExportsSet + exports.toSet() + val resSection = resultSet.joinToString( + separator = exportsProvider.exportsDelimiter, + prefix = startComment + exportsProvider.exportsPrefix, + postfix = exportsProvider.exportsPostfix + endComment, + ) { + exportsProvider.getExportsFrame(it) + } + existingSection?.let { currentFileText.replace(startComment + existingSection + endComment, resSection) } ?: resSection + } + } + + private fun makeMethodsToTest(): List { + return selectedMethods?.map { + getFunctionNode( + focusedMethodName = it, + parentClassName = parentClassName, + ) + } ?: getMethodsToTest() + } + + private fun makeJsClassId( + classNode: Node?, + ternService: TernService + ): JsClassId { + return classNode?.let { + JsClassId(parentClassName!!).constructClass(ternService, classNode) + } ?: jsUndefinedClassId.constructClass( + ternService = ternService, + functions = extractToplevelFunctions() + ) + } + + private fun extractToplevelFunctions(): List { + val visitor = JsToplevelFunctionAstVisitor() + visitor.accept(parsedFile) + return visitor.extractedMethods + } + + private fun collectExports(methodId: JsMethodId): List { + return (listOf(methodId.returnType) + methodId.parameters).flatMap { it.collectExportsRecursively() } + } + + private fun JsClassId.collectExportsRecursively(): List { + return when { + this.isClass -> listOf(this.name) + (this.constructor?.parameters ?: emptyList()) + .flatMap { it.collectExportsRecursively() } + + this.isJsArray -> (this.elementClassId as? JsClassId)?.collectExportsRecursively() ?: emptyList() + else -> emptyList() + } + } + + private fun getFunctionNode(focusedMethodName: String, parentClassName: String?): Node { + return parentClassName?.let { astScrapper.findMethod(parentClassName, focusedMethodName, parsedFile) } + ?: astScrapper.findFunction(focusedMethodName, parsedFile) + ?: throw IllegalStateException( + "Couldn't locate function \"$focusedMethodName\" with class ${parentClassName ?: ""}" + ) + } + + private fun getMethodsToTest() = + parentClassName?.let { + getClassMethods(it) + } ?: extractToplevelFunctions().ifEmpty { + getClassMethods("") + } + + private fun getClassMethods(className: String): List { + val classNode = astScrapper.findClass(className, parsedFile) + return classNode?.getClassMethods() ?: throw IllegalStateException("Can't extract methods of class $className") + } +} diff --git a/utbot-js/src/main/kotlin/api/JsUtModelConstructor.kt b/utbot-js/src/main/kotlin/api/JsUtModelConstructor.kt new file mode 100644 index 0000000000..09c5b74af3 --- /dev/null +++ b/utbot-js/src/main/kotlin/api/JsUtModelConstructor.kt @@ -0,0 +1,141 @@ +package api + +import framework.api.js.JsClassId +import framework.api.js.JsEmptyClassId +import framework.api.js.JsMethodId +import framework.api.js.JsNullModel +import framework.api.js.JsPrimitiveModel +import framework.api.js.JsUndefinedModel +import framework.api.js.util.defaultJsValueModel +import framework.api.js.util.isJsArray +import framework.api.js.util.isJsMap +import framework.api.js.util.isJsSet +import framework.api.js.util.jsErrorClassId +import framework.api.js.util.jsUndefinedClassId +import fuzzer.JsIdProvider +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructorInterface + +class JsUtModelConstructor : UtModelConstructorInterface { + + // TODO SEVERE: Requires substantial expansion to other types + @Suppress("NAME_SHADOWING") + override fun construct(value: Any?, classId: ClassId): UtModel { + val classId = classId as JsClassId + if (classId == jsErrorClassId) return UtModel(jsErrorClassId) + return when (value) { + null -> JsNullModel(classId) + is Byte, + is Short, + is Char, + is Int, + is Long, + is Float, + is Double, + is String, + is Boolean -> JsPrimitiveModel(value) + is List<*> -> { + constructStructure(classId, value) + } + is Map<*, *> -> { + constructObject(classId, value) + } + + else -> JsUndefinedModel(classId) + } + } + + private fun constructStructure(classId: JsClassId, values: List): UtModel { + return when { + classId.isJsSet -> { + UtAssembleModel( + id = JsIdProvider.createId(), + classId = classId, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(classId, emptyList()), + emptyList() + ), + modificationsChainProvider = { mutableListOf() } + ).apply { + this.modificationsChain as MutableList += values.map { value -> + UtExecutableCallModel( + this, + JsMethodId( + classId = classId, + name = "add", + returnTypeNotLazy = jsUndefinedClassId, + parametersNotLazy = listOf(jsUndefinedClassId) + ), + listOf(construct(value, jsUndefinedClassId)) + ) + } + } + } + classId.isJsArray -> { + UtArrayModel( + id = JsIdProvider.createId(), + classId = classId, + stores = buildMap { + putAll(values.indices.zip(values.map { + construct(it, jsUndefinedClassId) + })) + } as MutableMap, + length = values.size, + constModel = jsUndefinedClassId.defaultJsValueModel() + ) + } + classId.isJsMap -> { + UtAssembleModel( + id = JsIdProvider.createId(), + classId = classId, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(classId, emptyList()), + emptyList() + ), + modificationsChainProvider = { mutableListOf() } + ).apply { + this.modificationsChain as MutableList += values.map { value -> + UtExecutableCallModel( + this, + JsMethodId( + classId = classId, + name = "set", + returnTypeNotLazy = jsUndefinedClassId, + parametersNotLazy = listOf(jsUndefinedClassId, jsUndefinedClassId) + ), + (value as Pair).toList().map { construct(it, jsUndefinedClassId) } + ) + } + } + } + else -> throw UnsupportedOperationException( + "Can't make UtModel from JavaScript structure with ${classId.name} type" + ) + } + } + + @Suppress("UNCHECKED_CAST") + private fun constructObject(classId: JsClassId, value: Any?): UtModel { + val constructor = classId.allConstructors.first() + val values = (value as Map).values.map { + construct(it, JsEmptyClassId()) + } + val id = JsIdProvider.createId() + val instantiationCall = UtExecutableCallModel(null, constructor, values) + return UtAssembleModel( + id = id, + classId = constructor.classId, + modelName = "${constructor.classId.name}${constructor.parameters}#" + id.toString(16), + instantiationCall = instantiationCall, + ) + } +} diff --git a/utbot-js/src/main/kotlin/codegen/JsCodeGenerator.kt b/utbot-js/src/main/kotlin/codegen/JsCodeGenerator.kt new file mode 100644 index 0000000000..f20a5ba837 --- /dev/null +++ b/utbot-js/src/main/kotlin/codegen/JsCodeGenerator.kt @@ -0,0 +1,79 @@ +package codegen + +import framework.api.js.JsClassId +import framework.codegen.JsCgLanguageAssistant +import framework.codegen.JsImport +import framework.codegen.Mocha +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.HangingTestsTimeout +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgClassFile +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.SimpleTestClassModel +import org.utbot.framework.codegen.generator.CodeGeneratorResult +import org.utbot.framework.codegen.renderer.CgAbstractRenderer +import org.utbot.framework.codegen.tree.CgSimpleTestClassConstructor +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MockFramework + +class JsCodeGenerator( + private val classUnderTest: JsClassId, + paramNames: MutableMap> = mutableMapOf(), + testFramework: TestFramework = Mocha, + runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.defaultItem, + hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(), + enableTestsTimeout: Boolean = true, + testClassPackageName: String = classUnderTest.packageName, + imports: List, +) { + private var context: CgContext = CgContext( + classUnderTest = classUnderTest, + projectType = ProjectType.JavaScript, + paramNames = paramNames, + testFramework = testFramework, + mockFramework = MockFramework.MOCKITO, + codegenLanguage = CodegenLanguage.defaultItem, + cgLanguageAssistant = JsCgLanguageAssistant, + parametrizedTestSource = ParametrizedTestSource.defaultItem, + staticsMocking = StaticsMocking.defaultItem, + forceStaticMocking = ForceStaticMocking.defaultItem, + generateWarningsForStaticMocking = true, + runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour, + hangingTestsTimeout = hangingTestsTimeout, + enableTestsTimeout = enableTestsTimeout, + testClassPackageName = testClassPackageName, + collectedImports = imports.toMutableSet() + ) + + fun generateAsStringWithTestReport( + cgTestSets: List, + testClassCustomName: String? = null, + ): CodeGeneratorResult = withCustomContext(testClassCustomName) { + val testClassModel = SimpleTestClassModel(classUnderTest, cgTestSets) + val astConstructor = CgSimpleTestClassConstructor(context) + val testClassFile = astConstructor.construct(testClassModel) + CodeGeneratorResult(renderClassFile(testClassFile), astConstructor.testsGenerationReport) + } + + private fun withCustomContext(testClassCustomName: String? = null, block: () -> R): R { + val prevContext = context + return try { + context = prevContext.customCopy(shouldOptimizeImports = true, testClassCustomName = testClassCustomName) + block() + } finally { + context = prevContext + } + } + + private fun renderClassFile(file: CgClassFile): String { + val renderer = CgAbstractRenderer.makeRenderer(context) + file.accept(renderer) + return renderer.toString() + } +} diff --git a/utbot-js/src/main/kotlin/framework/api/js/JsApi.kt b/utbot-js/src/main/kotlin/framework/api/js/JsApi.kt new file mode 100644 index 0000000000..c918a98a96 --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/api/js/JsApi.kt @@ -0,0 +1,121 @@ +package framework.api.js + +import framework.api.js.util.toJsClassId +import java.lang.reflect.Modifier +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.primitiveModelValueToClassId + +open class JsClassId( + private val jsName: String, + private val methods: Sequence = emptySequence(), + val constructor: JsConstructorId? = null, + private val classPackagePath: String = "", + private val classFilePath: String = "", + elementClassId: JsClassId? = null +) : ClassId(jsName, elementClassId) { + override val simpleName: String + get() = jsName + + override val simpleNameWithEnclosingClasses: String + get() = jsName + + override val allMethods: Sequence + get() = methods + + override val allConstructors: Sequence + get() = if (constructor == null) emptySequence() else sequenceOf(constructor) + + override val packageName: String + get() = classPackagePath + + override val canonicalName: String + get() = jsName + + override val isAnonymous: Boolean + get() = false + + override val isInDefaultPackage: Boolean + get() = false + + override val isInner: Boolean + get() = false + + override val isLocal: Boolean + get() = false + + override val isNested: Boolean + get() = false + + override val isNullable: Boolean + get() = false + + override val isSynthetic: Boolean + get() = false + + override val outerClass: Class<*>? + get() = null + + val filePath: String + get() = classFilePath + +} + +class JsEmptyClassId : JsClassId("empty") +class JsMethodId( + override var classId: JsClassId, + override val name: String, + private val returnTypeNotLazy: JsClassId, + private val parametersNotLazy: List, + private val staticModifier: Boolean = false, + private val lazyReturnType: Lazy? = null, + private val lazyParameters: Lazy>? = null +) : MethodId(classId, name, returnTypeNotLazy, parametersNotLazy) { + + override val parameters: List + get() = lazyParameters?.value ?: parametersNotLazy + + override val returnType: JsClassId + get() = lazyReturnType?.value ?: returnTypeNotLazy + + override val modifiers: Int + get() = if (staticModifier) Modifier.STATIC else 0 + +} + +class JsConstructorId( + override var classId: JsClassId, + override val parameters: List, +) : ConstructorId(classId, parameters) { + override val modifiers: Int + get() = 0 +} + +class JsMultipleClassId(jsJoinedName: String) : JsClassId(jsJoinedName) + +open class JsUtModel( + override val classId: JsClassId +) : UtModel(classId) + +class JsNullModel( + override val classId: JsClassId +) : JsUtModel(classId) { + override fun toString() = "null" +} + +class JsUndefinedModel( + classId: JsClassId +) : JsUtModel(classId) { + override fun toString() = "undefined" +} + +data class JsPrimitiveModel( + val value: Any, +) : JsUtModel(jsPrimitiveModelValueToClassId(value)) { + override fun toString() = value.toString() +} + +private fun jsPrimitiveModelValueToClassId(value: Any) = + primitiveModelValueToClassId(value).toJsClassId() diff --git a/utbot-js/src/main/kotlin/framework/api/js/JsUtFuzzedExecution.kt b/utbot-js/src/main/kotlin/framework/api/js/JsUtFuzzedExecution.kt new file mode 100644 index 0000000000..4b6168b579 --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/api/js/JsUtFuzzedExecution.kt @@ -0,0 +1,25 @@ +package framework.api.js + +import org.utbot.framework.plugin.api.* + +class JsUtFuzzedExecution( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult +) : UtExecution(stateBefore, stateAfter, result, null, null, null, null) { + override fun copy( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage?, + summary: List?, + testMethodName: String?, + displayName: String? + ): UtExecution { + return JsUtFuzzedExecution( + stateBefore = stateBefore, + stateAfter = stateAfter, + result = result + ) + } +} diff --git a/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt new file mode 100644 index 0000000000..f82ce4ac0a --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/api/js/util/JsIdUtil.kt @@ -0,0 +1,68 @@ +package framework.api.js.util + +import framework.api.js.JsClassId +import framework.api.js.JsMultipleClassId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.floatClassId + +val jsUndefinedClassId = JsClassId("undefined") +val jsNumberClassId = JsClassId("number") +val jsBooleanClassId = JsClassId("bool") +val jsDoubleClassId = JsClassId("double") +val jsStringClassId = JsClassId("string") +val jsErrorClassId = JsClassId("error") + +val jsBasic = setOf( + jsBooleanClassId, + jsDoubleClassId, + jsUndefinedClassId, + jsStringClassId, + jsNumberClassId +) + +fun ClassId.toJsClassId() = + when { + this == booleanClassId -> jsBooleanClassId + this == doubleClassId -> jsDoubleClassId + this == floatClassId -> jsDoubleClassId + this.name.lowercase().contains("string") -> jsStringClassId + else -> jsUndefinedClassId + } + +fun JsClassId.defaultJsValueModel(): UtModel = when (this) { + jsNumberClassId -> UtPrimitiveModel(0.0) + jsDoubleClassId -> UtPrimitiveModel(Double.POSITIVE_INFINITY) + jsBooleanClassId -> UtPrimitiveModel(false) + jsStringClassId -> UtPrimitiveModel("default") + jsUndefinedClassId -> UtPrimitiveModel(0.0) + else -> UtNullModel(this) +} + +val JsClassId.isJsBasic: Boolean + get() = this in jsBasic || this.isJsStdStructure + +val JsClassId.isExportable: Boolean + get() = !(this.isJsBasic || this == jsErrorClassId || this is JsMultipleClassId) + +val JsClassId.isClass: Boolean + get() = !(this.isJsBasic || this == jsErrorClassId || this is JsMultipleClassId) + +val JsClassId.isUndefined: Boolean + get() = this == jsUndefinedClassId + +val JsClassId.isJsArray: Boolean + get() = this.name == "Array" + +val JsClassId.isJsMap: Boolean + get() = this.name == "Map" + +val JsClassId.isJsSet: Boolean + get() = this.name == "Set" + +val JsClassId.isJsStdStructure: Boolean + get() = this.isJsArray || this.isJsSet || this.isJsMap diff --git a/utbot-js/src/main/kotlin/framework/codegen/JsCgLanguageAssistant.kt b/utbot-js/src/main/kotlin/framework/codegen/JsCgLanguageAssistant.kt new file mode 100644 index 0000000000..3c07664b18 --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/codegen/JsCgLanguageAssistant.kt @@ -0,0 +1,47 @@ +package framework.codegen + +import framework.codegen.model.constructor.tree.JsCgCallableAccessManager +import framework.codegen.model.constructor.tree.JsCgMethodConstructor +import framework.codegen.model.constructor.tree.JsCgStatementConstructor +import framework.codegen.model.constructor.tree.JsCgVariableConstructor +import framework.codegen.model.constructor.visitor.CgJsRenderer +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.TestClassContext +import org.utbot.framework.codegen.renderer.CgAbstractRenderer +import org.utbot.framework.codegen.renderer.CgPrinter +import org.utbot.framework.codegen.renderer.CgRendererContext +import org.utbot.framework.codegen.services.language.AbstractCgLanguageAssistant +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.utils.ClassNameUtils.generateTestClassName + +object JsCgLanguageAssistant : AbstractCgLanguageAssistant() { + + override val outerMostTestClassContent: TestClassContext = TestClassContext() + + override val extension: String + get() = ".js" + + override val languageKeywords: Set = setOf( + "abstract", "arguments", "await", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", + "debugger", "default", "delete", "do", "double", "else", "enum", "eval", "export", "extends", "false", "final", + "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", + "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", + "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "var", "void", + "volatile", "while", "with", "yield" + ) + + override fun testClassName( + testClassCustomName: String?, + testClassPackageName: String, + classUnderTest: ClassId + ): Pair { + return generateTestClassName(testClassCustomName, testClassPackageName, classUnderTest) + } + + override fun cgRenderer(context: CgRendererContext, printer: CgPrinter): CgAbstractRenderer = CgJsRenderer(context, printer) + override fun getCallableAccessManagerBy(context: CgContext) = JsCgCallableAccessManager(context) + override fun getMethodConstructorBy(context: CgContext) = JsCgMethodConstructor(context) + override fun getStatementConstructorBy(context: CgContext) = JsCgStatementConstructor(context) + override fun getVariableConstructorBy(context: CgContext) = JsCgVariableConstructor(context) + override fun getLanguageTestFrameworkManager() = JsTestFrameworkManager() +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/framework/codegen/JsDomain.kt b/utbot-js/src/main/kotlin/framework/codegen/JsDomain.kt new file mode 100644 index 0000000000..8e8c32227e --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/codegen/JsDomain.kt @@ -0,0 +1,86 @@ +package framework.codegen + +import framework.api.js.JsClassId +import framework.api.js.util.jsErrorClassId +import framework.api.js.util.jsUndefinedClassId +import org.utbot.framework.codegen.domain.Import +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.BuiltinMethodId +import org.utbot.framework.plugin.api.ClassId +import service.PackageJson + + +object Mocha : TestFramework(id = "Mocha", displayName = "Mocha") { + override val mainPackage = "" + override val assertionsClass = jsUndefinedClassId + override val arraysAssertionsClass = jsUndefinedClassId + override val kotlinFailureAssertionsClass: ClassId = jsUndefinedClassId + + override val beforeMethodId: ClassId = jsUndefinedClassId + override val afterMethodId: ClassId = jsUndefinedClassId + + override val nestedClassesShouldBeStatic: Boolean + get() = false + override val argListClassId: ClassId + get() = jsUndefinedClassId + + override fun getRunTestsCommand( + executionInvoke: String, + classPath: String, + classesNames: List, + buildDirectory: String, + additionalArguments: List + ): List { + throw UnsupportedOperationException() + } + + override val testAnnotationId = BuiltinClassId( + name = "Mocha", + canonicalName = "Mocha", + simpleName = "Test" + ) + + override val parameterizedTestAnnotationId = jsUndefinedClassId + override val methodSourceAnnotationId = jsUndefinedClassId +} + +internal val jsAssertEquals by lazy { + BuiltinMethodId( + JsClassId("assert.deepEqual"), "assert.deepEqual", jsUndefinedClassId, listOf( + jsUndefinedClassId, jsUndefinedClassId + ) + ) +} + +internal val jsAssertThrows by lazy { + BuiltinMethodId( + JsClassId("assert.throws"), "assert.throws", jsErrorClassId, listOf( + jsUndefinedClassId, jsUndefinedClassId, jsUndefinedClassId + ) + ) +} + +enum class ModuleType { + MODULE, + COMMONJS; + + companion object { + fun fromPackageJson(packageJson: PackageJson): ModuleType { + return when (packageJson.isModule) { + true -> MODULE + else -> COMMONJS + } + } + } +} + +data class JsImport( + val name: String, + val aliases: String, + val path: String, + val type: ModuleType +): Import(2) { + + override val qualifiedName: String = "$name as $aliases from $path" +} diff --git a/utbot-js/src/main/kotlin/framework/codegen/JsTestFrameworkManager.kt b/utbot-js/src/main/kotlin/framework/codegen/JsTestFrameworkManager.kt new file mode 100644 index 0000000000..525a267bd6 --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/codegen/JsTestFrameworkManager.kt @@ -0,0 +1,17 @@ +package framework.codegen + +import framework.codegen.model.constructor.tree.MochaManager +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.services.language.LanguageTestFrameworkManager + +class JsTestFrameworkManager: LanguageTestFrameworkManager() { + + override fun managerByFramework(context: CgContext) = when (context.testFramework) { + is Mocha -> MochaManager(context) + else -> throw UnsupportedOperationException("Incorrect TestFramework ${context.testFramework}") + } + + override val defaultTestFramework = Mocha + + override val testFrameworks = listOf(Mocha) +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgCallableAccessManager.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgCallableAccessManager.kt new file mode 100644 index 0000000000..4c44a4c433 --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgCallableAccessManager.kt @@ -0,0 +1,60 @@ +package framework.codegen.model.constructor.tree + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgConstructorCall +import org.utbot.framework.codegen.domain.models.CgExecutableCall +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgStaticFieldAccess +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.services.access.CgIncompleteMethodCall +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId + +class JsCgCallableAccessManager(context: CgContext) : CgCallableAccessManager, + CgContextOwner by context { + + override operator fun CgExpression?.get(methodId: MethodId): CgIncompleteMethodCall = + CgIncompleteMethodCall(methodId, this) + + override operator fun ClassId.get(staticMethodId: MethodId): CgIncompleteMethodCall = + CgIncompleteMethodCall(staticMethodId, null) + + override fun CgExpression.get(fieldId: FieldId): CgExpression { + TODO("Not yet implemented") + } + + override fun ClassId.get(fieldId: FieldId): CgStaticFieldAccess { + TODO("Not yet implemented") + } + + override operator fun ConstructorId.invoke(vararg args: Any?): CgExecutableCall { + val resolvedArgs = args.resolve() + val constructorCall = CgConstructorCall(this, resolvedArgs) + newConstructorCall(this) + return constructorCall + } + + override fun CgIncompleteMethodCall.invoke(vararg args: Any?): CgMethodCall { + val resolvedArgs = args.resolve() + val methodCall = CgMethodCall(caller, method, resolvedArgs) + newMethodCall(method) + return methodCall + } + + private fun newConstructorCall(constructorId: ConstructorId) { + importedClasses += constructorId.classId + } + + private fun newMethodCall(methodId: MethodId) { + if (methodId.classId.name == "undefined") { + importedStaticMethods += methodId + return + } + importedClasses += methodId.classId + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgMethodConstructor.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgMethodConstructor.kt new file mode 100644 index 0000000000..38102712da --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgMethodConstructor.kt @@ -0,0 +1,116 @@ +package framework.codegen.model.constructor.tree + +import framework.api.js.JsClassId +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgTestMethodType +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.tree.CgMethodConstructor +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.onFailure +import org.utbot.framework.plugin.api.onSuccess +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.framework.util.isUnit + +class JsCgMethodConstructor(ctx: CgContext) : CgMethodConstructor(ctx) { + + override fun assertEquality(expected: CgValue, actual: CgVariable) { + testFrameworkManager.assertEquals(expected, actual) + } + + override fun createTestMethod(testSet: CgMethodTestSet, execution: UtExecution): CgTestMethod = + withTestMethodScope(execution) { + val testMethodName = nameGenerator.testMethodNameFor(testSet.executableUnderTest, execution.testMethodName) + execution.displayName = execution.displayName?.let { "${testSet.executableUnderTest.name}: $it" } + testMethod(testMethodName, execution.displayName) { + val statics = currentExecution!!.stateBefore.statics + rememberInitialStaticFields(statics) + val mainBody = { + substituteStaticFields(statics) + // build this instance + thisInstance = execution.stateBefore.thisInstance?.let { + variableConstructor.getOrCreateVariable(it) + } + // build arguments + for ((index, param) in execution.stateBefore.parameters.withIndex()) { + val name = paramNames[testSet.executableUnderTest]?.get(index) + methodArguments += variableConstructor.getOrCreateVariable(param, name) + } + recordActualResult() + generateResultAssertions() + generateFieldStateAssertions() + } + + if (statics.isNotEmpty()) { + +tryBlock { + mainBody() + }.finally { + recoverStaticFields() + } + } else { + mainBody() + } + } + } + + override fun generateResultAssertions() { + emptyLineIfNeeded() + val currentExecution = currentExecution!! + val method = currentExecutableUnderTest as MethodId + // build assertions + currentExecution.result + .onSuccess { result -> + methodType = CgTestMethodType.SUCCESSFUL + if (result.isUnit() || method.returnType == voidClassId) { + +thisInstance[method](*methodArguments.toTypedArray()) + } else { + resultModel = result + assertEquality(result, actual) + } + } + .onFailure { exception -> + processExecutionFailure(currentExecution, exception) + } + } + + private fun processExecutionFailure(execution: UtExecution, exception: Throwable) { + val methodInvocationBlock = { + with(currentExecutableUnderTest) { + when (this) { + is MethodId -> thisInstance[this](*methodArguments.toTypedArray()).intercepted() + is ConstructorId -> this(*methodArguments.toTypedArray()).intercepted() + else -> throw IllegalStateException() + } + } + } + + if (shouldTestPassWithException(execution, exception)) { + testFrameworkManager.expectException(JsClassId(exception.message!!)) { + methodInvocationBlock() + } + methodType = CgTestMethodType.SUCCESSFUL + + return + } + + if (shouldTestPassWithTimeoutException(execution, exception)) { + writeWarningAboutTimeoutExceeding() + testFrameworkManager.expectTimeout(hangingTestsTimeout.timeoutMs) { + methodInvocationBlock() + } + methodType = CgTestMethodType.TIMEOUT + + return + } + + methodType = CgTestMethodType.FAILING + writeWarningAboutFailureTest(exception) + + methodInvocationBlock() + } +} diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgStatementConstructor.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgStatementConstructor.kt new file mode 100644 index 0000000000..daa267d0ed --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgStatementConstructor.kt @@ -0,0 +1,286 @@ +package framework.codegen.model.constructor.tree + +import fj.data.Either +import framework.codegen.model.constructor.util.plus +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.AnnotationTarget +import org.utbot.framework.codegen.domain.models.CgAnnotation +import org.utbot.framework.codegen.domain.models.CgAnonymousFunction +import org.utbot.framework.codegen.domain.models.CgComment +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgEmptyLine +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgIfStatement +import org.utbot.framework.codegen.domain.models.CgInnerBlock +import org.utbot.framework.codegen.domain.models.CgIsInstance +import org.utbot.framework.codegen.domain.models.CgLogicalAnd +import org.utbot.framework.codegen.domain.models.CgLogicalOr +import org.utbot.framework.codegen.domain.models.CgMultilineComment +import org.utbot.framework.codegen.domain.models.CgMultipleArgsAnnotation +import org.utbot.framework.codegen.domain.models.CgNamedAnnotationArgument +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration +import org.utbot.framework.codegen.domain.models.CgReturnStatement +import org.utbot.framework.codegen.domain.models.CgSingleArgAnnotation +import org.utbot.framework.codegen.domain.models.CgSingleLineComment +import org.utbot.framework.codegen.domain.models.CgThrowStatement +import org.utbot.framework.codegen.domain.models.CgTryCatch +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.tree.* +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtModel + +class JsCgStatementConstructor(context: CgContext) : + CgStatementConstructor, + CgContextOwner by context, + CgCallableAccessManager by CgComponents.getCallableAccessManagerBy(context) { + + private val nameGenerator = CgComponents.getNameGeneratorBy(context) + + override fun newVar( + baseType: ClassId, + model: UtModel?, + baseName: String?, + isMock: Boolean, + isMutable: Boolean, + init: () -> CgExpression + ): CgVariable { + val declarationOrVar: Either = + createDeclarationForNewVarAndUpdateVariableScopeOrGetExistingVariable( + baseType, + model, + baseName, + isMock, + isMutable, + init + ) + + return declarationOrVar.either( + { declaration -> + currentBlock += declaration + + declaration.variable + }, + { variable -> variable } + ) + } + + override fun createDeclarationForNewVarAndUpdateVariableScopeOrGetExistingVariable( + baseType: ClassId, + model: UtModel?, + baseName: String?, + isMock: Boolean, + isMutableVar: Boolean, + init: () -> CgExpression + ): Either { + + val baseExpr = init() + + val name = nameGenerator.variableName(baseType, baseName, isMock) + + // TODO SEVERE: here was import section for CgClassId. Implement it +// importIfNeeded(baseType) +// if ((baseType as JsClassId).name != "undefined") { +// importedClasses += baseType +// } + + val declaration = buildDeclaration { + variableType = baseType + variableName = name + initializer = baseExpr + isMutable = isMutableVar + } + + rememberVariableForModel(declaration.variable, model) + + return Either.left(declaration) + } + + override fun CgExpression.`=`(value: Any?) { + currentBlock += buildAssignment { + lValue = this@`=` + rValue = value.resolve() + } + } + + override fun CgExpression.and(other: CgExpression): CgLogicalAnd = + CgLogicalAnd(this, other) + + + override fun CgExpression.or(other: CgExpression): CgLogicalOr = + CgLogicalOr(this, other) + + override fun ifStatement( + condition: CgExpression, + trueBranch: () -> Unit, + falseBranch: (() -> Unit)? + ): CgIfStatement { + val trueBranchBlock = block(trueBranch) + val falseBranchBlock = falseBranch?.let { block(it) } + return CgIfStatement(condition, trueBranchBlock, falseBranchBlock).also { + currentBlock += it + } + } + + override fun forLoop(init: CgForLoopBuilder.() -> Unit) { + currentBlock += buildForLoop(init) + } + + override fun whileLoop(condition: CgExpression, statements: () -> Unit) { + currentBlock += buildWhileLoop { + this.condition = condition + this.statements += block(statements) + } + } + + override fun doWhileLoop(condition: CgExpression, statements: () -> Unit) { + currentBlock += buildDoWhileLoop { + this.condition = condition + this.statements += block(statements) + } + } + + override fun forEachLoop(init: CgForEachLoopBuilder.() -> Unit) { + throw UnsupportedOperationException("JavaScript does not have forEach loops") + } + + override fun getClassOf(classId: ClassId): CgExpression { + TODO("Not yet implemented") + } + + override fun createFieldVariable(fieldId: FieldId): CgVariable { + TODO("Not yet implemented") + } + + override fun createExecutableVariable(executableId: ExecutableId, arguments: List): CgVariable { + TODO("Not yet implemented") + } + + override fun tryBlock(init: () -> Unit): CgTryCatch = tryBlock(init, null) + + override fun tryBlock(init: () -> Unit, resources: List?): CgTryCatch = + buildTryCatch { + statements = block(init) + this.resources = resources + } + + override fun CgTryCatch.catch(exception: ClassId, init: (CgVariable) -> Unit): CgTryCatch { + val newHandler = buildExceptionHandler { + val e = declareVariable(exception, nameGenerator.variableName(exception.simpleName.decapitalize())) + this.exception = e + this.statements = block { init(e) } + } + return this.copy(handlers = handlers + newHandler) + } + + override fun CgTryCatch.finally(init: () -> Unit): CgTryCatch { + val finallyBlock = block(init) + return this.copy(finally = finallyBlock) + } + + override fun CgExpression.isInstance(value: CgExpression): CgIsInstance { + TODO("Not yet implemented") + } + + // TODO MINOR: check whether js has inner blocks + override fun innerBlock(init: () -> Unit): CgInnerBlock = + CgInnerBlock(block(init)).also { + currentBlock += it + } + + override fun comment(text: String): CgComment = + CgSingleLineComment(text).also { + currentBlock += it + } + + override fun comment(): CgComment = + CgSingleLineComment("").also { + currentBlock += it + } + + override fun multilineComment(lines: List): CgComment = + CgMultilineComment(lines).also { + currentBlock += it + } + + override fun lambda(type: ClassId, vararg parameters: CgVariable, body: () -> Unit): CgAnonymousFunction { + return withNameScope { + for (parameter in parameters) { + declareParameter(parameter.type, parameter.name) + } + val paramDeclarations = parameters.map { CgParameterDeclaration(it) } + CgAnonymousFunction(type, paramDeclarations, block(body)) + } + } + + override fun addAnnotation(classId: ClassId, argument: Any?, target: AnnotationTarget): CgAnnotation { + val annotation = CgSingleArgAnnotation(classId, argument.resolve(), target) + addAnnotation(annotation) + return annotation + } + + override fun addAnnotation( + classId: ClassId, + namedArguments: List, + target: AnnotationTarget, + ): CgAnnotation { + val annotation = CgMultipleArgsAnnotation(classId, namedArguments.toMutableList(), target) + addAnnotation(annotation) + return annotation + } + + override fun addAnnotation( + classId: ClassId, + target: AnnotationTarget, + buildArguments: MutableList>.() -> Unit, + ): CgAnnotation { + val arguments = mutableListOf>() + .apply(buildArguments) + .map { (name, value) -> CgNamedAnnotationArgument(name, value) } + val annotation = CgMultipleArgsAnnotation(classId, arguments.toMutableList(), target) + addAnnotation(annotation) + return annotation + } + + private fun addAnnotation(annotation: CgAnnotation) { + when (annotation.target) { + AnnotationTarget.Method -> collectedMethodAnnotations.add(annotation) + AnnotationTarget.Class, + AnnotationTarget.Field -> error("Annotation ${annotation.target} is not supported in JavaScript") + } + + importIfNeeded(annotation.classId) + } + + override fun returnStatement(expression: () -> CgExpression) { + currentBlock += CgReturnStatement(expression()) + } + + override fun throwStatement(exception: () -> CgExpression): CgThrowStatement = + CgThrowStatement(exception()).also { currentBlock += it } + + override fun emptyLine() { + currentBlock += CgEmptyLine + } + + override fun emptyLineIfNeeded() { + val lastStatement = currentBlock.lastOrNull() ?: return + if (lastStatement is CgEmptyLine) return + emptyLine() + } + + override fun declareVariable(type: ClassId, name: String): CgVariable = + CgVariable(name, type).also { + rememberVariableForModel(it) + } + + // TODO SEVERE: think about these 2 functions + override fun guardExpression(baseType: ClassId, expression: CgExpression): ExpressionWithType = + ExpressionWithType(baseType, expression) + + override fun wrapTypeIfRequired(baseType: ClassId): ClassId = baseType +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt new file mode 100644 index 0000000000..ef6f845f32 --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsCgVariableConstructor.kt @@ -0,0 +1,181 @@ +package framework.codegen.model.constructor.tree + +import framework.api.js.JsClassId +import framework.api.js.JsNullModel +import framework.api.js.JsPrimitiveModel +import framework.api.js.util.isExportable +import framework.api.js.util.jsBooleanClassId +import framework.api.js.util.jsDoubleClassId +import framework.api.js.util.jsNumberClassId +import framework.api.js.util.jsStringClassId +import framework.api.js.util.jsUndefinedClassId +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgAllocateArray +import org.utbot.framework.codegen.domain.models.CgArrayInitializer +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.tree.CgComponents +import org.utbot.framework.codegen.tree.CgVariableConstructor +import org.utbot.framework.codegen.util.at +import org.utbot.framework.codegen.util.inc +import org.utbot.framework.codegen.util.lessThan +import org.utbot.framework.codegen.util.nullLiteral +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.util.defaultValueModel +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.set + +class JsCgVariableConstructor(ctx: CgContext) : CgVariableConstructor(ctx) { + + private val nameGenerator = CgComponents.getNameGeneratorBy(context) + + override fun getOrCreateVariable(model: UtModel, name: String?): CgValue = + valueByUtModelWrapper.getOrPut(model.wrap()) { + when (model) { + is UtAssembleModel -> { + // TODO SEVERE: May lead to unexpected behavior in case of changes to the original method + super.getOrCreateVariable(model, name) + } + + is JsPrimitiveModel -> CgLiteral(model.classId, model.value) + is UtArrayModel -> { + val baseName = name ?: nameGenerator.nameFrom(model.classId) + constructArray(model, baseName) + } + + else -> nullLiteral() + } + } + + private val MAX_ARRAY_INITIALIZER_SIZE = 10 + + private operator fun UtArrayModel.get(index: Int): UtModel = stores[index] ?: constModel + + private val defaultByPrimitiveType: Map = mapOf( + jsBooleanClassId to false, + jsStringClassId to "default", + jsUndefinedClassId to 0.0, + jsNumberClassId to 0.0, + jsDoubleClassId to Double.POSITIVE_INFINITY + ) + + private infix fun UtModel.isNotJsDefaultValueOf(type: JsClassId): Boolean = !(this isJsDefaultValueOf type) + + private infix fun UtModel.isJsDefaultValueOf(type: JsClassId): Boolean = when (this) { + is JsNullModel -> type.isExportable + is JsPrimitiveModel -> value == defaultByPrimitiveType[type] + else -> false + } + + private fun CgVariable.setArrayElement(index: Any, value: CgValue) { + val i = index.resolve() + this.at(i) `=` value + } + + private fun basicForLoop(until: Any, body: (i: CgExpression) -> Unit) { + basicForLoop(start = 0, until, body) + } + + private fun basicForLoop(start: Any, until: Any, body: (i: CgExpression) -> Unit) { + forLoop { + val (i, init) = loopInitialization(jsNumberClassId, "i", start.resolve()) + initialization = init + condition = i lessThan until.resolve() + update = i.inc() + statements = block { body(i) } + } + } + + private fun loopInitialization( + variableType: ClassId, + baseVariableName: String, + initializer: Any? + ): Pair { + val declaration = CgDeclaration(variableType, baseVariableName.toVarName(), initializer.resolve()) + val variable = declaration.variable + rememberVariableForModel(variable) + return variable to declaration + } + + private fun constructArray(arrayModel: UtArrayModel, baseName: String?): CgVariable { + val elementType = (arrayModel.classId.elementClassId ?: jsUndefinedClassId) as JsClassId + val elementModels = (0 until arrayModel.length).map { + arrayModel.stores.getOrDefault(it, arrayModel.constModel) + } + + val allPrimitives = elementModels.all { it is JsPrimitiveModel } + val allNulls = elementModels.all { it is JsNullModel } + // we can use array initializer if all elements are primitives or all of them are null, + // and the size of an array is not greater than the fixed maximum size + val canInitWithValues = (allPrimitives || allNulls) && elementModels.size <= MAX_ARRAY_INITIALIZER_SIZE + + val initializer = if (canInitWithValues) { + val elements = elementModels.map { model -> + when (model) { + is JsPrimitiveModel -> model.value.resolve() + is UtNullModel -> null.resolve() + else -> error("Non primitive or null model $model is unexpected in array initializer") + } + } + CgArrayInitializer(arrayModel.classId, elementType, elements) + } else { + CgAllocateArray(arrayModel.classId, elementType, arrayModel.length) + } + + val array = newVar(arrayModel.classId, baseName) { initializer } + valueByUtModelWrapper[arrayModel.wrap()] = array + + if (canInitWithValues) { + return array + } + + if (arrayModel.length <= 0) return array + if (arrayModel.length == 1) { + // take first element value if it is present, otherwise use default value from model + val elementModel = arrayModel[0] + if (elementModel isNotJsDefaultValueOf elementType) { + array.setArrayElement(0, getOrCreateVariable(elementModel)) + } + } else { + val indexedValuesFromStores = + if (arrayModel.stores.size == arrayModel.length) { + // do not use constModel because stores fully cover array + arrayModel.stores.entries.filter { (_, element) -> element isNotJsDefaultValueOf elementType } + } else { + // fill array if constModel is not default type value + if (arrayModel.constModel isNotJsDefaultValueOf elementType) { + val defaultVariable = getOrCreateVariable(arrayModel.constModel, "defaultValue") + basicForLoop(arrayModel.length) { i -> + array.setArrayElement(i, defaultVariable) + } + } + + // choose all not default values + val defaultValue = if (arrayModel.constModel isJsDefaultValueOf elementType) { + arrayModel.constModel + } else { + elementType.defaultValueModel() + } + arrayModel.stores.entries.filter { (_, element) -> element != defaultValue } + } + + // set all values from stores manually + indexedValuesFromStores + .sortedBy { it.key } + .forEach { (index, element) -> array.setArrayElement(index, getOrCreateVariable(element)) } + } + + return array + } + + private fun String.toVarName(): String = nameGenerator.variableName(this) +} diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsTestFrameworkManager.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsTestFrameworkManager.kt new file mode 100644 index 0000000000..13466f194e --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/tree/JsTestFrameworkManager.kt @@ -0,0 +1,62 @@ +package framework.codegen.model.constructor.tree + +import framework.codegen.Mocha +import framework.codegen.jsAssertEquals +import framework.codegen.jsAssertThrows +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.TestClassContext +import org.utbot.framework.codegen.domain.models.CgAnnotation +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.services.framework.TestFrameworkManager +import org.utbot.framework.plugin.api.ClassId + +class MochaManager(context: CgContext) : TestFrameworkManager(context) { + override val isExpectedExceptionExecutionBreaking: Boolean = true + + override fun expectException(exception: ClassId, block: () -> Unit) { + require(testFramework is Mocha) { "According to settings, Mocha.js was expected, but got: $testFramework" } + val lambda = statementConstructor.lambda(exception) { block() } + +assertions[jsAssertThrows](lambda, "Error", exception.name) + } + + override fun addDataProviderAnnotations(dataProviderMethodName: String) { + error("Parametrized tests are not supported for JavaScript") + } + + override fun createArgList(length: Int): CgVariable { + error("Parametrized tests are not supported for JavaScript") + } + + override fun addParameterizedTestAnnotations(dataProviderMethodName: String?) { + error("Parametrized tests are not supported for JavaScript") + } + + override fun passArgumentsToArgsVariable(argsVariable: CgVariable, argsArray: CgVariable, executionIndex: Int) { + error("Parametrized tests are not supported for JavaScript") + } + + override fun addTestDescription(description: String) { + TODO("Not yet implemented") + } + + override val dataProviderMethodsHolder: TestClassContext + get() = error("Parametrized tests are not supported for JavaScript") + + override fun addAnnotationForNestedClasses() { + error("Nested classes annotation does not exist in Mocha") + } + + override fun assertEquals(expected: CgValue, actual: CgValue) { + +assertions[jsAssertEquals](expected, actual) + } + + override fun assertSame(expected: CgValue, actual: CgValue) { + error("assertSame does not exist in Mocha") + } + + override fun disableTestMethod(reason: String) { + + } + +} diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/util/ConstructorUtils.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/util/ConstructorUtils.kt new file mode 100644 index 0000000000..4b33d306c2 --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/util/ConstructorUtils.kt @@ -0,0 +1,16 @@ +package framework.codegen.model.constructor.util + +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.PersistentSet + +internal operator fun PersistentList.plus(element: T): PersistentList = + this.add(element) + +internal operator fun PersistentList.plus(other: PersistentList): PersistentList = + this.addAll(other) + +internal operator fun PersistentSet.plus(element: T): PersistentSet = + this.add(element) + +internal operator fun PersistentSet.plus(other: PersistentSet): PersistentSet = + this.addAll(other) diff --git a/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt new file mode 100644 index 0000000000..4ddcd6de10 --- /dev/null +++ b/utbot-js/src/main/kotlin/framework/codegen/model/constructor/visitor/CgJsRenderer.kt @@ -0,0 +1,428 @@ +package framework.codegen.model.constructor.visitor + +import framework.api.js.JsClassId +import framework.api.js.util.isExportable +import framework.codegen.JsImport +import framework.codegen.ModuleType +import org.apache.commons.text.StringEscapeUtils +import org.utbot.framework.codegen.domain.RegularImport +import org.utbot.framework.codegen.domain.StaticImport +import org.utbot.framework.codegen.domain.models.CgAllocateArray +import org.utbot.framework.codegen.domain.models.CgAllocateInitializedArray +import org.utbot.framework.codegen.domain.models.CgAnonymousFunction +import org.utbot.framework.codegen.domain.models.CgArrayAnnotationArgument +import org.utbot.framework.codegen.domain.models.CgArrayElementAccess +import org.utbot.framework.codegen.domain.models.CgArrayInitializer +import org.utbot.framework.codegen.domain.models.CgClass +import org.utbot.framework.codegen.domain.models.CgClassBody +import org.utbot.framework.codegen.domain.models.CgClassFile +import org.utbot.framework.codegen.domain.models.CgConstructorCall +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgEqualTo +import org.utbot.framework.codegen.domain.models.CgErrorTestMethod +import org.utbot.framework.codegen.domain.models.CgErrorWrapper +import org.utbot.framework.codegen.domain.models.CgExecutableCall +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgFieldAccess +import org.utbot.framework.codegen.domain.models.CgForLoop +import org.utbot.framework.codegen.domain.models.CgFormattedString +import org.utbot.framework.codegen.domain.models.CgFrameworkUtilMethod +import org.utbot.framework.codegen.domain.models.CgGetJavaClass +import org.utbot.framework.codegen.domain.models.CgGetKotlinClass +import org.utbot.framework.codegen.domain.models.CgGetLength +import org.utbot.framework.codegen.domain.models.CgInnerBlock +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgMultipleArgsAnnotation +import org.utbot.framework.codegen.domain.models.CgNamedAnnotationArgument +import org.utbot.framework.codegen.domain.models.CgNotNullAssertion +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration +import org.utbot.framework.codegen.domain.models.CgParameterizedTestDataProviderMethod +import org.utbot.framework.codegen.domain.models.CgSpread +import org.utbot.framework.codegen.domain.models.CgStaticsRegion +import org.utbot.framework.codegen.domain.models.CgSwitchCase +import org.utbot.framework.codegen.domain.models.CgSwitchCaseLabel +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgTypeCast +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.renderer.CgAbstractRenderer +import org.utbot.framework.codegen.renderer.CgPrinter +import org.utbot.framework.codegen.renderer.CgPrinterImpl +import org.utbot.framework.codegen.renderer.CgRendererContext +import org.utbot.framework.codegen.tree.VisibilityModifier +import org.utbot.framework.plugin.api.BuiltinMethodId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.TypeParameters +import org.utbot.framework.plugin.api.util.isStatic +import settings.JsTestGenerationSettings.fileUnderTestAliases + +internal class CgJsRenderer(context: CgRendererContext, printer: CgPrinter = CgPrinterImpl()) : + CgAbstractRenderer(context, printer) { + + override val statementEnding: String = "" + + override val logicalAnd: String + get() = "&&" + + override val logicalOr: String + get() = "||" + + override val langPackage: String = "js" + + override val ClassId.methodsAreAccessibleAsTopLevel: Boolean + get() = false + + override fun visit(element: CgErrorWrapper) { + element.expression.accept(this) + print("alert(\"${element.message}\")") + } + + override fun visit(element: CgInnerBlock) { + println("{") + withIndent { + for (statement in element.statements) { + statement.accept(this) + } + } + println("}") + } + + override fun visit(element: CgParameterDeclaration) { + if (element.isVararg) { + print("...") + } + print(element.name.escapeNamePossibleKeyword()) + } + + override fun visit(element: CgLiteral) { + val value = with(element.value) { + when (this) { + is Double -> toStringConstant() + is String -> "\"" + escapeCharacters() + "\"" + else -> "$this" + } + } + print(value) + } + + private fun Double.toStringConstant() = when { + isNaN() -> "Number.NaN" + this == Double.POSITIVE_INFINITY -> "Number.POSITIVE_INFINITY" + this == Double.NEGATIVE_INFINITY -> "Number.NEGATIVE_INFINITY" + else -> "$this" + } + + override fun renderRegularImport(regularImport: RegularImport) { + println("const ${regularImport.packageName} = require(\"${regularImport.className}\")") + } + + override fun visit(element: CgStaticsRegion) { + if (element.content.isEmpty()) return + + print(regionStart) + element.header?.let { print(" $it") } + println() + + withIndent { + for (item in element.content) { + println() + item.accept(this) + } + } + + println(regionEnd) + } + + + override fun visit(element: CgClass) { + element.body.accept(this) + } + + override fun visit(element: CgFieldAccess) { + element.caller.accept(this) + renderAccess(element.caller) + print(element.fieldId.name) + } + + override fun visit(element: CgArrayElementAccess) { + element.array.accept(this) + print("[") + element.index.accept(this) + print("]") + } + + override fun visit(element: CgArrayAnnotationArgument) { + throw UnsupportedOperationException() + } + + override fun visit(element: CgAnonymousFunction) { + print("function (") + element.parameters.renderSeparated(true) + println(") {") + // cannot use visit(element.body) here because { was already printed + withIndent { + for (statement in element.body) { + statement.accept(this) + } + } + print("}") + } + + override fun visit(element: CgEqualTo) { + element.left.accept(this) + print(" == ") + element.right.accept(this) + } + + // TODO SEVERE + override fun visit(element: CgTypeCast) { + element.expression.accept(this) +// throw Exception("TypeCast not yet implemented") + } + + override fun visit(element: CgSpread) { + print("...") + element.array.accept(this) + } + + override fun visit(element: CgNotNullAssertion) { + throw UnsupportedOperationException("JavaScript does not support not null assertions") + } + + override fun visit(element: CgAllocateArray) { + print("new Array(${element.size})") + } + + override fun visit(element: CgAllocateInitializedArray) { + print("[") + element.initializer.accept(this) + print("]") + } + + // TODO SEVERE: I am unsure about this + override fun visit(element: CgArrayInitializer) { + val elementType = element.elementType + val elementsInLine = arrayElementsInLine(elementType) + print("[") + element.values.renderElements(elementsInLine) + print("]") + } + + override fun visit(element: CgClassFile) { + element.imports.filterIsInstance().forEach { + renderImport(it) + } + println() + element.declaredClass.accept(this) + } + + override fun visit(element: CgSwitchCaseLabel) { + if (element.label != null) { + print("case ") + element.label!!.accept(this) + } else { + print("default") + } + println(": ") + visit(element.statements, printNextLine = true) + } + + @Suppress("DuplicatedCode") + override fun visit(element: CgSwitchCase) { + print("switch (") + element.value.accept(this) + println(") {") + withIndent { + for (caseLabel in element.labels) { + caseLabel.accept(this) + } + element.defaultLabel?.accept(this) + } + println("}") + } + + override fun visit(element: CgGetLength) { + element.variable.accept(this) + print(".size") + } + + override fun visit(element: CgGetJavaClass) { + throw UnsupportedOperationException("No Java classes in JavaScript") + } + + override fun visit(element: CgGetKotlinClass) { + throw UnsupportedOperationException("No Kotlin classes in JavaScript") + } + + override fun visit(element: CgConstructorCall) { + val importPrefix = "$fileUnderTestAliases.".takeIf { + (element.executableId.classId as JsClassId).isExportable + } ?: "" + print("new $importPrefix${element.executableId.classId.name}") + print("(") + element.arguments.renderSeparated() + print(")") + } + + private fun renderImport(import: JsImport) = with(import) { + when (type) { + ModuleType.COMMONJS -> println("const $aliases = require(\"$path\")") + ModuleType.MODULE -> println("import $name as $aliases from \"$path\"") + } + } + + override fun renderStaticImport(staticImport: StaticImport) { + throw Exception("Not implemented yet") + } + + override fun renderMethodSignature(element: CgTestMethod) { + println("it(\"${element.name}\", function ()") + } + + override fun renderMethodSignature(element: CgErrorTestMethod) { + println("it(\"${element.name}\", function ()") + + } + + override fun visit(element: CgMethod) { + super.visit(element) + if (element is CgTestMethod || element is CgErrorTestMethod) { + println(")") + } + } + + override fun visit(element: CgErrorTestMethod) { + renderMethodSignature(element) + visit(element as CgMethod) + } + + override fun visit(element: CgClassBody) { + // render regions for test methods + for ((i, region) in (element.methodRegions + element.nestedClassRegions).withIndex()) { + if (i != 0) println() + + region.accept(this) + } + + if (element.staticDeclarationRegions.isEmpty()) { + return + } + } + + override fun renderMethodSignature(element: CgParameterizedTestDataProviderMethod) { + throw UnsupportedOperationException() + } + + override fun renderMethodSignature(element: CgFrameworkUtilMethod) { + throw UnsupportedOperationException() + } + + override fun visit(element: CgNamedAnnotationArgument) { + + } + + override fun visit(element: CgMultipleArgsAnnotation) { + + } + + override fun visit(element: CgMethodCall) { + val caller = element.caller + if (caller != null) { + caller.accept(this) + renderAccess(caller) + } else { + val method = element.executableId + if (method is BuiltinMethodId) { + + } else if (method.isStatic) { + val line = if (method.classId.toString() == "undefined") "" else "${method.classId}." + print("$fileUnderTestAliases.$line") + } else { + print("$fileUnderTestAliases.") + } + } + print(element.executableId.name.escapeNamePossibleKeyword()) + renderTypeParameters(element.typeParameters) + if (element.type.name == "error") { + print("(") + element.arguments[0].accept(this@CgJsRenderer) + print(", ") + print("Error, ") + element.arguments[2].accept(this@CgJsRenderer) + print(")") + } else { + renderExecutableCallArguments(element) + } + } + + override fun visit(element: CgFormattedString) { + throw NotImplementedError("String interpolation is not supported in JavaScript renderer") + } + + //TODO MINOR: check + override fun renderForLoopVarControl(element: CgForLoop) { + print("for (") + with(element.initialization) { + print("let ") + visit(variable) + print(" = ") + initializer?.accept(this@CgJsRenderer) + print("; ") + visit(element.condition) + print("; ") + print(element.update) + } + } + + override fun renderDeclarationLeftPart(element: CgDeclaration) { + if (element.isMutable) print("var ") else print("let ") + visit(element.variable) + } + + override fun toStringConstantImpl(byte: Byte) = "$byte" + + override fun toStringConstantImpl(short: Short) = "$short" + + override fun toStringConstantImpl(int: Int) = "$int" + + override fun toStringConstantImpl(long: Long) = "$long" + + override fun toStringConstantImpl(float: Float) = "$float" + + override fun renderAccess(caller: CgExpression) { + print(".") + } + + override fun renderTypeParameters(typeParameters: TypeParameters) { + //TODO MINOR: check + } + + override fun renderExecutableCallArguments(executableCall: CgExecutableCall) { + print("(") + executableCall.arguments.renderSeparated() + print(")") + } + + //TODO SEVERE: check + override fun renderExceptionCatchVariable(exception: CgVariable) { + print("${exception.name.escapeNamePossibleKeyword()}: ${exception.type}") + } + + override fun escapeNamePossibleKeywordImpl(s: String): String = s + + override fun renderVisibility(modifier: VisibilityModifier) { + TODO("Not yet implemented") + } + + override fun renderClassModality(aClass: CgClass) { + TODO("Not yet implemented") + } + + //TODO MINOR: check + override fun String.escapeCharacters(): String = + StringEscapeUtils.escapeJava(this) + .replace("$", "\\$") + .replace("\\f", "\\u000C") + .replace("\\xxx", "\\\u0058\u0058\u0058") +} diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt new file mode 100644 index 0000000000..feb68bbc8c --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzerApi.kt @@ -0,0 +1,82 @@ +package fuzzer + +import framework.api.js.JsClassId +import framework.api.js.JsUtFuzzedExecution +import framework.api.js.util.isClass +import java.util.concurrent.atomic.AtomicInteger +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtTimeoutException +import org.utbot.fuzzing.Control +import org.utbot.fuzzing.Description +import org.utbot.fuzzing.Feedback +import org.utbot.fuzzing.utils.Trie + +sealed interface JsFuzzingExecutionFeedback +class JsValidExecution(val utFuzzedExecution: JsUtFuzzedExecution) : JsFuzzingExecutionFeedback + +class JsTimeoutExecution(val utTimeout: UtTimeoutException) : JsFuzzingExecutionFeedback + +class JsMethodDescription( + val name: String, + parameters: List, + val concreteValues: Collection, + val thisInstance: JsClassId? = null, + val tracer: Trie +) : Description(parameters) { + + constructor( + name: String, + parameters: List, + classId: JsClassId, + concreteValues: Collection, + tracer: Trie + ) : this( + name, + if (classId.isClass) listOf(classId) + parameters else parameters, + concreteValues, + classId.takeIf { it.isClass }, + tracer + ) +} + +data class JsFeedback( + override val control: Control = Control.CONTINUE, + val result: Trie.Node = Trie.emptyNode() +) : Feedback + +data class JsStatement( + val number: Int +) + +data class JsFuzzedConcreteValue( + val classId: ClassId, + val value: Any, + val fuzzedContext: JsFuzzedContext = JsFuzzedContext.Unknown, +) + +enum class JsFuzzedContext { + EQ, + NE, + GT, + GE, + LT, + LE, + Unknown; + + fun reverse(): JsFuzzedContext = when (this) { + EQ -> NE + NE -> EQ + GT -> LE + LT -> GE + LE -> GT + GE -> LT + Unknown -> Unknown + } +} + +object JsIdProvider { + private var id = AtomicInteger(0) + + fun createId() = id.incrementAndGet() +} diff --git a/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt new file mode 100644 index 0000000000..c9953083d8 --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/JsFuzzing.kt @@ -0,0 +1,48 @@ +package fuzzer + +import framework.api.js.JsClassId +import fuzzer.providers.ArrayValueProvider +import fuzzer.providers.BoolValueProvider +import fuzzer.providers.MapValueProvider +import fuzzer.providers.NumberValueProvider +import fuzzer.providers.ObjectValueProvider +import fuzzer.providers.SetValueProvider +import fuzzer.providers.StringValueProvider +import org.utbot.framework.plugin.api.UtModel +import org.utbot.fuzzing.Fuzzing +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.fuzz + +fun defaultValueProviders() = listOf( + BoolValueProvider, + NumberValueProvider, + StringValueProvider, + MapValueProvider, + SetValueProvider, + ObjectValueProvider(), + ArrayValueProvider() +) + +class JsFuzzing( + val exec: suspend (JsMethodDescription, List) -> JsFeedback +) : Fuzzing { + + override fun generate(description: JsMethodDescription, type: JsClassId): Sequence> { + return defaultValueProviders().asSequence().flatMap { provider -> + if (provider.accept(type)) { + provider.generate(description, type) + } else { + emptySequence() + } + } + } + + override suspend fun handle(description: JsMethodDescription, values: List): JsFeedback { + return exec(description, values) + } +} + +suspend fun runFuzzing( + description: JsMethodDescription, + exec: suspend (JsMethodDescription, List) -> JsFeedback +) = JsFuzzing(exec).fuzz(description) diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt new file mode 100644 index 0000000000..ab966c29d2 --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ArrayValueProvider.kt @@ -0,0 +1,38 @@ +package fuzzer.providers + +import framework.api.js.JsClassId +import framework.api.js.util.defaultJsValueModel +import framework.api.js.util.isJsArray +import fuzzer.JsIdProvider +import fuzzer.JsMethodDescription +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider + +class ArrayValueProvider : ValueProvider { + + override fun accept(type: JsClassId): Boolean = type.isJsArray + + override fun generate( + description: JsMethodDescription, + type: JsClassId + ) = sequence> { + yield( + Seed.Collection( + construct = Routine.Collection { + UtArrayModel( + id = JsIdProvider.createId(), + classId = type, + length = it, + constModel = (type.elementClassId!! as JsClassId).defaultJsValueModel(), + stores = hashMapOf(), + ) + }, + modify = Routine.ForEach(listOf(type.elementClassId!! as JsClassId)) { self, i, values -> + (self as UtArrayModel).stores[i] = values.first() + } + )) + } +} diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt new file mode 100644 index 0000000000..87031b6559 --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/providers/BoolValueProvider.kt @@ -0,0 +1,27 @@ +package fuzzer.providers + +import framework.api.js.JsClassId +import framework.api.js.JsPrimitiveModel +import framework.api.js.util.isJsBasic +import fuzzer.JsMethodDescription +import org.utbot.framework.plugin.api.UtModel +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.seeds.Bool + +object BoolValueProvider : ValueProvider { + + override fun accept(type: JsClassId): Boolean { + return type.isJsBasic + } + + override fun generate(description: JsMethodDescription, type: JsClassId): Sequence> = + sequence { + yield(Seed.Known(Bool.TRUE()) { + JsPrimitiveModel(true) + }) + yield(Seed.Known(Bool.FALSE()) { + JsPrimitiveModel(false) + }) + } +} diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt new file mode 100644 index 0000000000..79122fa6b5 --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/providers/MapValueProvider.kt @@ -0,0 +1,58 @@ +package fuzzer.providers + + +import framework.api.js.JsClassId +import framework.api.js.JsMethodId +import framework.api.js.util.isJsMap +import framework.api.js.util.jsUndefinedClassId +import fuzzer.JsIdProvider +import fuzzer.JsMethodDescription +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider + +object MapValueProvider : ValueProvider { + + override fun accept(type: JsClassId): Boolean = type.isJsMap + + override fun generate( + description: JsMethodDescription, + type: JsClassId + ) = sequence> { + yield( + Seed.Collection( + construct = Routine.Collection { + UtAssembleModel( + id = JsIdProvider.createId(), + classId = type, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(type, emptyList()), + emptyList() + ), + modificationsChainProvider = { mutableListOf() } + ) + }, + modify = Routine.ForEach(listOf(jsUndefinedClassId, jsUndefinedClassId)) { self, _, values -> + val model = self as UtAssembleModel + model.modificationsChain as MutableList += + UtExecutableCallModel( + model, + JsMethodId( + classId = type, + name = "set", + returnTypeNotLazy = jsUndefinedClassId, + parametersNotLazy = listOf(jsUndefinedClassId, jsUndefinedClassId) + ), + values + ) + } + ) + ) + } +} diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/NumberValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/NumberValueProvider.kt new file mode 100644 index 0000000000..b82f06bdfd --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/providers/NumberValueProvider.kt @@ -0,0 +1,46 @@ +package fuzzer.providers + +import framework.api.js.JsClassId +import framework.api.js.JsPrimitiveModel +import framework.api.js.util.isJsBasic +import fuzzer.JsFuzzedContext.EQ +import fuzzer.JsFuzzedContext.GE +import fuzzer.JsFuzzedContext.GT +import fuzzer.JsFuzzedContext.LE +import fuzzer.JsFuzzedContext.LT +import fuzzer.JsMethodDescription +import org.utbot.framework.plugin.api.UtModel +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.seeds.DefaultFloatBound +import org.utbot.fuzzing.seeds.IEEE754Value + +object NumberValueProvider : ValueProvider { + + override fun accept(type: JsClassId): Boolean { + return type.isJsBasic + } + + override fun generate(description: JsMethodDescription, type: JsClassId): Sequence> = + sequence { + description.concreteValues.forEach { (_, v, c) -> + if (v is Double) { + val balance = when (c) { + EQ, LE, GT -> 1 + LT, GE -> -1 + else -> 0 + } + + yield(Seed.Known(IEEE754Value.fromValue(v)) { known -> + JsPrimitiveModel(known.toDouble() + balance) + }) + } + } + DefaultFloatBound.values().forEach { bound -> + // All numbers in JavaScript are like Double in Java/Kotlin + yield(Seed.Known(bound(52, 11)) { known -> + JsPrimitiveModel(known.toDouble()) + }) + } + } +} diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/ObjectValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/ObjectValueProvider.kt new file mode 100644 index 0000000000..7370654808 --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/providers/ObjectValueProvider.kt @@ -0,0 +1,58 @@ +package fuzzer.providers + +import framework.api.js.JsClassId +import framework.api.js.JsConstructorId +import framework.api.js.util.isClass +import fuzzer.JsIdProvider +import fuzzer.JsMethodDescription +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel + + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.utils.hex + +class ObjectValueProvider : ValueProvider { + + override fun accept(type: JsClassId): Boolean { + return type.isClass + } + + override fun generate( + description: JsMethodDescription, + type: JsClassId + ) = sequence { + val constructor = type.constructor ?: JsConstructorId(type, emptyList()) + yield(createValue(type, constructor)) + } + + private fun createValue( + classId: JsClassId, + constructorId: JsConstructorId + ): Seed.Recursive { + return Seed.Recursive( + construct = Routine.Create(constructorId.parameters) { values -> + val id = JsIdProvider.createId() + UtAssembleModel( + id = id, + classId = classId, + modelName = "${constructorId.classId.name}${constructorId.parameters}#" + id.hex(), + instantiationCall = UtExecutableCallModel( + null, + constructorId, + values + ), + modificationsChainProvider = { mutableListOf() } + ) + }, + modify = emptySequence(), + empty = Routine.Empty { + UtNullModel(classId) + } + ) + } +} diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt new file mode 100644 index 0000000000..6991518c9f --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/providers/SetValueProvider.kt @@ -0,0 +1,57 @@ +package fuzzer.providers + +import framework.api.js.JsClassId +import framework.api.js.JsMethodId +import framework.api.js.util.isJsSet +import framework.api.js.util.jsUndefinedClassId +import fuzzer.JsIdProvider +import fuzzer.JsMethodDescription +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider + +object SetValueProvider : ValueProvider { + + override fun accept(type: JsClassId): Boolean = type.isJsSet + + override fun generate( + description: JsMethodDescription, + type: JsClassId + ) = sequence> { + yield( + Seed.Collection( + construct = Routine.Collection { + UtAssembleModel( + id = JsIdProvider.createId(), + classId = type, + modelName = "", + instantiationCall = UtExecutableCallModel( + null, + ConstructorId(type, emptyList()), + emptyList() + ), + modificationsChainProvider = { mutableListOf() } + ) + }, + modify = Routine.ForEach(listOf(jsUndefinedClassId)) { self, _, values -> + val model = self as UtAssembleModel + model.modificationsChain as MutableList += + UtExecutableCallModel( + model, + JsMethodId( + classId = type, + name = "add", + returnTypeNotLazy = jsUndefinedClassId, + parametersNotLazy = listOf(jsUndefinedClassId) + ), + values + ) + } + ) + ) + } +} diff --git a/utbot-js/src/main/kotlin/fuzzer/providers/StringValueProvider.kt b/utbot-js/src/main/kotlin/fuzzer/providers/StringValueProvider.kt new file mode 100644 index 0000000000..a885c9b92c --- /dev/null +++ b/utbot-js/src/main/kotlin/fuzzer/providers/StringValueProvider.kt @@ -0,0 +1,34 @@ +package fuzzer.providers + +import framework.api.js.JsClassId +import framework.api.js.JsPrimitiveModel +import framework.api.js.util.isJsBasic +import framework.api.js.util.jsStringClassId +import fuzzer.JsMethodDescription +import org.utbot.framework.plugin.api.UtModel +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.seeds.StringValue + +object StringValueProvider : ValueProvider { + + override fun accept(type: JsClassId): Boolean { + return type.isJsBasic + } + + override fun generate( + description: JsMethodDescription, + type: JsClassId + ): Sequence> = sequence { + val constants = description.concreteValues.asSequence() + .filter { it.classId == jsStringClassId } + val values = constants + .mapNotNull { it.value as? String } + + sequenceOf("", "abc", "\n\t\n") + values.forEach { value -> + yield(Seed.Known(StringValue(value)) { known -> + JsPrimitiveModel(known.value) + }) + } + } +} diff --git a/utbot-js/src/main/kotlin/parser/IAstVisitor.kt b/utbot-js/src/main/kotlin/parser/IAstVisitor.kt new file mode 100644 index 0000000000..1d8bcf64a2 --- /dev/null +++ b/utbot-js/src/main/kotlin/parser/IAstVisitor.kt @@ -0,0 +1,8 @@ +package parser + +import com.google.javascript.rhino.Node + +interface IAstVisitor { + + fun accept(rootNode: Node) +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt new file mode 100644 index 0000000000..a22b232cf2 --- /dev/null +++ b/utbot-js/src/main/kotlin/parser/JsAstScrapper.kt @@ -0,0 +1,149 @@ +package parser + +import com.google.javascript.jscomp.Compiler +import com.google.javascript.jscomp.NodeUtil +import com.google.javascript.jscomp.SourceFile +import com.google.javascript.rhino.Node +import java.io.File +import java.nio.file.Paths +import mu.KotlinLogging +import parser.JsParserUtils.getAbstractFunctionName +import parser.JsParserUtils.getClassMethods +import parser.JsParserUtils.getImportSpecAliases +import parser.JsParserUtils.getImportSpecName +import parser.JsParserUtils.getModuleImportSpecsAsList +import parser.JsParserUtils.getModuleImportText +import parser.JsParserUtils.getRequireImportText +import parser.JsParserUtils.isRequireImport +import kotlin.io.path.pathString + +private val logger = KotlinLogging.logger {} + +class JsAstScrapper( + private val parsedFile: Node, + private val basePath: String, +) { + + // Used not to parse the same file multiple times. + private val _parsedFilesCache = mutableMapOf() + private val _filesToInfer: MutableList = mutableListOf(basePath) + val filesToInfer: List + get() = _filesToInfer.toList() + private val _importsMap = mutableMapOf() + val importsMap: Map + get() = _importsMap.toMap() + + init { + _importsMap.apply { + val visitor = Visitor() + visitor.accept(parsedFile) + val res = visitor.importNodes.fold(emptyMap()) { acc, node -> + val currAcc = acc.toList().toTypedArray() + val more = node.importedNodes().toList().toTypedArray() + mapOf(*currAcc, *more) + } + this.putAll(res) + this.toMap() + } + } + + fun findFunction(key: String, file: Node): Node? { + if (_importsMap[key]?.isFunction == true) return _importsMap[key] + val functionVisitor = JsFunctionAstVisitor(key, null) + functionVisitor.accept(file) + return try { + functionVisitor.targetFunctionNode + } catch (e: Exception) { null } + } + + fun findClass(key: String, file: Node): Node? { + if (_importsMap[key]?.isClass == true) return _importsMap[key] + val classVisitor = JsClassAstVisitor(key) + classVisitor.accept(file) + return try { + classVisitor.targetClassNode + } catch (e: Exception) { null } + } + + fun findMethod(classKey: String, methodKey: String, file: Node): Node? { + val classNode = findClass(classKey, file) + return classNode?.getClassMethods()?.find { it.getAbstractFunctionName() == methodKey } + } + + private fun File.parseIfNecessary(): Node = + _parsedFilesCache.getOrPut(this.path) { + _filesToInfer += this.path.replace("\\", "/") + Compiler().parse(SourceFile.fromCode(this.path, readText())) + } + + private fun Node.importedNodes(): Map { + return when { + this.isRequireImport() -> mapOf( + this.parent!!.string to (makePathFromImport(this.getRequireImportText())?.let { + File(it).parseIfNecessary().findEntityInFile(null) + // Workaround for std imports. + } ?: this.firstChild!!.next!!) + ) + this.isImport -> this.processModuleImport() + else -> emptyMap() + } + } + + private fun Node.processModuleImport(): Map { + try { + val pathToFile = makePathFromImport(this.getModuleImportText()) ?: return emptyMap() + val pFile = File(pathToFile).parseIfNecessary() + return when { + NodeUtil.findPreorder(this, { it.isImportSpecs }, { true }) != null -> { + this.getModuleImportSpecsAsList().associate { spec -> + val realName = spec.getImportSpecName() + val aliases = spec.getImportSpecAliases() + aliases to pFile.findEntityInFile(realName) + } + } + NodeUtil.findPreorder(this, { it.isImportStar }, { true }) != null -> { + val aliases = this.getImportSpecAliases() + mapOf(aliases to pFile) + } + // For example: import foo from "bar" + else -> { + val realName = this.getImportSpecName() + mapOf(realName to pFile.findEntityInFile(realName)) + } + } + } catch (e: Exception) { + logger.error { e.toString() } + return emptyMap() + } + } + + private fun makePathFromImport(importText: String): String? { + val relPath = importText + if (importText.endsWith(".js")) "" else ".js" + // If import text doesn't contain "/", then it is NodeJS stdlib import. + if (!relPath.contains("/")) return null + return Paths.get(File(basePath).parent).resolve(Paths.get(relPath)).pathString + } + + private fun Node.findEntityInFile(key: String?): Node { + return key?.let { k -> + findClass(k, this) + ?: findFunction(k, this) + ?: throw ClassNotFoundException("Could not locate entity $k in ${this.sourceFileName}") + } ?: this + } + + private class Visitor: IAstVisitor { + + private val _importNodes = mutableListOf() + + val importNodes: List + get() = _importNodes.toList() + + // TODO: commented for release since features are incomplete + override fun accept(rootNode: Node) { +// NodeUtil.visitPreOrder(rootNode) { node -> +// if (node.isImport || node.isRequireImport()) _importNodes += node +// } + } + } +} diff --git a/utbot-js/src/main/kotlin/parser/JsClassAstVisitor.kt b/utbot-js/src/main/kotlin/parser/JsClassAstVisitor.kt new file mode 100644 index 0000000000..925d9a22f1 --- /dev/null +++ b/utbot-js/src/main/kotlin/parser/JsClassAstVisitor.kt @@ -0,0 +1,26 @@ +package parser + +import com.google.javascript.jscomp.NodeUtil +import com.google.javascript.rhino.Node +import parser.JsParserUtils.getClassName + +class JsClassAstVisitor( + private val target: String? +): IAstVisitor { + + lateinit var targetClassNode: Node + lateinit var atLeastSomeClassNode: Node + var classNodesCount = 0 + + override fun accept(rootNode: Node) = + NodeUtil.visitPreOrder(rootNode) { + if (it.isClass) { + classNodesCount++ + atLeastSomeClassNode = it + if (it.getClassName() == target) { + targetClassNode = it + return@visitPreOrder + } + } + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/parser/JsFunctionAstVisitor.kt b/utbot-js/src/main/kotlin/parser/JsFunctionAstVisitor.kt new file mode 100644 index 0000000000..0ded81e3ff --- /dev/null +++ b/utbot-js/src/main/kotlin/parser/JsFunctionAstVisitor.kt @@ -0,0 +1,40 @@ +package parser + +import com.google.javascript.jscomp.NodeUtil +import com.google.javascript.rhino.Node +import parser.JsParserUtils.getAbstractFunctionName +import parser.JsParserUtils.getClassName + +class JsFunctionAstVisitor( + private val target: String, + private val className: String? +): IAstVisitor { + + lateinit var targetFunctionNode: Node + + override fun accept(rootNode: Node) { + NodeUtil.visitPreOrder(rootNode) { node -> + when { + node.isMemberFunctionDef -> { + val name = node.getAbstractFunctionName() + if ( + name == target && + (node.parent?.parent?.getClassName() + ?: throw IllegalStateException("Method AST node has no parent class node")) == className + ) { + targetFunctionNode = node + return@visitPreOrder + } + } + + node.isFunction -> { + val name = node.getAbstractFunctionName() + if (name == target && className == null && node.parent?.isMemberFunctionDef != true) { + targetFunctionNode = node + return@visitPreOrder + } + } + } + } + } +} diff --git a/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt b/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt new file mode 100644 index 0000000000..463edcfb5b --- /dev/null +++ b/utbot-js/src/main/kotlin/parser/JsFuzzerAstVisitor.kt @@ -0,0 +1,68 @@ +package parser + + +import com.google.javascript.jscomp.NodeUtil +import com.google.javascript.rhino.Node +import framework.api.js.util.jsBooleanClassId +import framework.api.js.util.jsDoubleClassId +import framework.api.js.util.jsStringClassId +import fuzzer.JsFuzzedConcreteValue +import fuzzer.JsFuzzedContext +import parser.JsParserUtils.getAnyValue +import parser.JsParserUtils.getBinaryExprLeftOperand +import parser.JsParserUtils.getBinaryExprRightOperand +import parser.JsParserUtils.toFuzzedContextComparisonOrNull + +class JsFuzzerAstVisitor : IAstVisitor { + + private var lastFuzzedOpGlobal: JsFuzzedContext = JsFuzzedContext.Unknown + val fuzzedConcreteValues = mutableSetOf() + + override fun accept(rootNode: Node) { + NodeUtil.visitPreOrder(rootNode) { node -> + val currentFuzzedOp = node.toFuzzedContextComparisonOrNull() + when { + node.isCase -> validateNode(node.firstChild?.getAnyValue(), JsFuzzedContext.NE) + node.isCall -> { + validateNode(node.getAnyValue(), JsFuzzedContext.NE) + } + + currentFuzzedOp != null -> { + lastFuzzedOpGlobal = currentFuzzedOp + validateNode(node.getBinaryExprLeftOperand().getAnyValue(), lastFuzzedOpGlobal) + lastFuzzedOpGlobal = lastFuzzedOpGlobal.reverse() + validateNode(node.getBinaryExprRightOperand().getAnyValue(), lastFuzzedOpGlobal) + } + } + } + + } + + private fun validateNode(value: Any?, fuzzedOp: JsFuzzedContext) { + when (value) { + is String -> { + fuzzedConcreteValues.add( + JsFuzzedConcreteValue( + jsStringClassId, + value.toString(), + fuzzedOp + ) + ) + } + + is Boolean -> { + fuzzedConcreteValues.add( + JsFuzzedConcreteValue( + jsBooleanClassId, + value, + fuzzedOp + ) + ) + } + + is Double -> { + fuzzedConcreteValues.add(JsFuzzedConcreteValue(jsDoubleClassId, value, fuzzedOp)) + } + } + } +} diff --git a/utbot-js/src/main/kotlin/parser/JsParserUtils.kt b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt new file mode 100644 index 0000000000..e1d1df2385 --- /dev/null +++ b/utbot-js/src/main/kotlin/parser/JsParserUtils.kt @@ -0,0 +1,209 @@ +package parser + +import com.google.javascript.jscomp.Compiler +import com.google.javascript.jscomp.NodeUtil +import com.google.javascript.jscomp.SourceFile +import com.google.javascript.rhino.Node +import fuzzer.JsFuzzedContext +import parser.JsParserUtils.getMethodName + +// TODO: make methods more safe by checking the Node method is called on. +// Used for .children() calls. +@Suppress("DEPRECATION") +object JsParserUtils { + + fun runParser(fileText: String): Node = + Compiler().parse(SourceFile.fromCode("jsFile", fileText)) + + // TODO SEVERE: function only works in the same file scope. Add search in exports. + fun searchForClassDecl(className: String?, parsedFile: Node, strict: Boolean = false): Node? { + val visitor = JsClassAstVisitor(className) + visitor.accept(parsedFile) + return try { + visitor.targetClassNode + } catch (e: Exception) { + if (!strict && visitor.classNodesCount == 1) { + visitor.atLeastSomeClassNode + } else null + } + } + + /** + * Called upon node with Class token. + */ + fun Node.getClassName(): String = + this.firstChild?.string ?: throw IllegalStateException("Class AST node has no children") + + /** + * Called upon node with Method token. + */ + private fun Node.getMethodName(): String = this.string + + /** + * Called upon node with Function token. + */ + private fun Node.getFunctionName(): String = + this.firstChild?.string ?: throw IllegalStateException("Function AST node has no children") + + /** + * Called upon node with Parameter token. + */ + fun Node.getParamName(): String = this.string + + /** + * Convenience method. Used as a wrapper for [getFunctionName] and [getMethodName] + * functions when the type of function is unknown. + */ + fun Node.getAbstractFunctionName(): String = when { + this.isMemberFunctionDef -> this.getMethodName() + this.isFunction -> this.getFunctionName() + else -> throw IllegalStateException() + } + + /** + * Called upon node with any kind of literal value token. + */ + fun Node.getAnyValue(): Any? = when { + this.isNumber -> this.double + this.isString -> this.string + this.isTrue -> true + this.isFalse -> false + this.isCall -> { + if (this.firstChild?.isGetProp == true) { + this.firstChild?.next?.getAnyValue() + } else null + } + + else -> null + } + + // For some reason Closure Compiler doesn't contain a built-in method + // to check for some tokens. + /** + * Called upon node with any kind of binary comparison token. + */ + fun Node.toFuzzedContextComparisonOrNull(): JsFuzzedContext? = when { + this.isEQ -> JsFuzzedContext.EQ + this.isNE -> JsFuzzedContext.NE + this.token.name == "LT" -> JsFuzzedContext.LT + this.token.name == "GT" -> JsFuzzedContext.GT + this.token.name == "LE" -> JsFuzzedContext.LE + this.token.name == "GE" -> JsFuzzedContext.GE + this.token.name == "SHEQ" -> JsFuzzedContext.EQ + else -> null + } + + /** + * Called upon node with any kind of binary comparison token. + */ + fun Node.getBinaryExprLeftOperand(): Node = this.getChildAtIndex(0) + + /** + * Called upon node with any kind of binary comparison token. + */ + fun Node.getBinaryExprRightOperand(): Node = this.getChildAtIndex(1) + + /** + * Called upon node with Function token. + */ + private fun Node.getFunctionParams(): List = this.getChildAtIndex(1).children().map { it } + + /** + * Called upon node with Method token. + */ + private fun Node.getMethodParams(): List = + this.firstChild?.getFunctionParams() ?: throw IllegalStateException("Method AST node has no children") + + /** + * Convenience method. Used as a wrapper for [getFunctionParams] and [getMethodParams] + * function when the type of function is unknown. + */ + fun Node.getAbstractFunctionParams(): List = when { + this.isMemberFunctionDef -> getMethodParams() + this.isFunction -> getFunctionParams() + else -> throw IllegalStateException() + } + + /** + * Called upon node with Class token. + */ + fun Node.getClassMethods(): List { + val classMembers = this.children().find { it.isClassMembers } + ?: throw IllegalStateException("Can't extract class members of class ${this.getClassName()}") + return classMembers.children().filter { it.isMemberFunctionDef } + + } + + /** + * Called upon node with Class token. + * + * Returns null if class has no constructor. + */ + fun Node.getConstructor(): Node? { + val classMembers = this.children().find { it.isClassMembers } + ?: throw IllegalStateException("Can't extract methods of class ${this.getClassName()}") + return classMembers.children().find { + it.isMemberFunctionDef && it.getMethodName() == "constructor" + }?.firstChild + } + + /** + * Called upon node with Method token. + */ + fun Node.isStatic(): Boolean = this.isStaticMember + + /** + * Checks if node is "require" JavaScript import. + */ + fun Node.isRequireImport(): Boolean = try { + this.isCall && this.firstChild?.string == "require" + } catch (e: ClassCastException) { + false + } + + /** + * Called upon "require" JavaScript import. + * + * Returns path to imported file as [String]. + */ + fun Node.getRequireImportText(): String = this.firstChild!!.next!!.string + + /** + * Called upon "import" JavaScript import. + * + * Returns path to imported file as [String]. + */ + fun Node.getModuleImportText(): String = this.firstChild!!.next!!.next!!.string + + /** + * Called upon "import" JavaScript import. + * + * Returns imported objects as [List]. + */ + fun Node.getModuleImportSpecsAsList(): List { + val importSpecsNode = NodeUtil.findPreorder(this, { it.isImportSpecs }, { true }) + ?: throw UnsupportedOperationException("Module import doesn't contain \"import_specs\" token as an AST child") + var currNode: Node? = importSpecsNode.firstChild!! + val importSpecsList = mutableListOf() + do { + importSpecsList += currNode!! + currNode = currNode?.next + } while (currNode?.isImportSpec == true) + return importSpecsList + } + + /** + * Called upon IMPORT_SPEC Node. + * + * Returns name of imported object as [String]. + */ + fun Node.getImportSpecName(): String = this.firstChild!!.string + + /** + * Called upon IMPORT_SPEC Node. + * + * Returns import alias as [String]. + */ + fun Node.getImportSpecAliases(): String = this.firstChild!!.next!!.string + +} diff --git a/utbot-js/src/main/kotlin/parser/JsToplevelFunctionAstVisitor.kt b/utbot-js/src/main/kotlin/parser/JsToplevelFunctionAstVisitor.kt new file mode 100644 index 0000000000..92e32d3b0e --- /dev/null +++ b/utbot-js/src/main/kotlin/parser/JsToplevelFunctionAstVisitor.kt @@ -0,0 +1,18 @@ +package parser + +import com.google.javascript.jscomp.NodeUtil +import com.google.javascript.rhino.Node + +class JsToplevelFunctionAstVisitor : IAstVisitor { + + val extractedMethods = mutableListOf() + + + override fun accept(rootNode: Node) { + NodeUtil.visitPreOrder(rootNode) { node -> + when { + node.isFunction && !(node.parent?.isMemberFunctionDef ?: true) -> extractedMethods += node + } + } + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/exports/IExportsProvider.kt b/utbot-js/src/main/kotlin/providers/exports/IExportsProvider.kt new file mode 100644 index 0000000000..895b0a8cea --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/exports/IExportsProvider.kt @@ -0,0 +1,25 @@ +package providers.exports + +import service.PackageJson + +interface IExportsProvider { + + val exportsRegex: Regex + + val exportsDelimiter: String + + fun getExportsFrame(exportString: String): String + + val exportsPrefix: String + + val exportsPostfix: String + + fun instrumentationFunExport(funName: String): String + + companion object { + fun providerByPackageJson(packageJson: PackageJson): IExportsProvider = when (packageJson.isModule) { + true -> ModuleExportsProvider() + else -> RequireExportsProvider() + } + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/exports/ModuleExportsProvider.kt b/utbot-js/src/main/kotlin/providers/exports/ModuleExportsProvider.kt new file mode 100644 index 0000000000..77fae0fc1e --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/exports/ModuleExportsProvider.kt @@ -0,0 +1,16 @@ +package providers.exports + +class ModuleExportsProvider : IExportsProvider { + + override val exportsDelimiter: String = "," + + override val exportsPostfix: String = "}\n" + + override val exportsPrefix: String = "\nexport {" + + override val exportsRegex: Regex = Regex("(.*)") + + override fun getExportsFrame(exportString: String): String = exportString + + override fun instrumentationFunExport(funName: String): String = "\nexport {$funName}" +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/exports/RequireExportsProvider.kt b/utbot-js/src/main/kotlin/providers/exports/RequireExportsProvider.kt new file mode 100644 index 0000000000..644744bd66 --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/exports/RequireExportsProvider.kt @@ -0,0 +1,16 @@ +package providers.exports + +class RequireExportsProvider : IExportsProvider { + + override val exportsDelimiter: String = "\n" + + override val exportsPostfix: String = "\n" + + override val exportsPrefix: String = "\n" + + override val exportsRegex: Regex = Regex("exports[.](.*) =") + + override fun getExportsFrame(exportString: String): String = "exports.$exportString = $exportString" + + override fun instrumentationFunExport(funName: String): String = "\nexports.$funName = $funName" +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/imports/IImportsProvider.kt b/utbot-js/src/main/kotlin/providers/imports/IImportsProvider.kt new file mode 100644 index 0000000000..c2b821a904 --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/imports/IImportsProvider.kt @@ -0,0 +1,18 @@ +package providers.imports + +import service.PackageJson +import service.ServiceContext + +interface IImportsProvider { + + val ternScriptImports: String + + val tempFileImports: String + + companion object { + fun providerByPackageJson(packageJson: PackageJson, context: ServiceContext): IImportsProvider = when (packageJson.isModule) { + true -> ModuleImportsProvider(context) + else -> RequireImportsProvider(context) + } + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/imports/ModuleImportsProvider.kt b/utbot-js/src/main/kotlin/providers/imports/ModuleImportsProvider.kt new file mode 100644 index 0000000000..7d1aa55c01 --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/imports/ModuleImportsProvider.kt @@ -0,0 +1,22 @@ +package providers.imports + +import service.ContextOwner +import service.ServiceContext +import settings.JsTestGenerationSettings.fileUnderTestAliases + +class ModuleImportsProvider(context: ServiceContext) : IImportsProvider, ContextOwner by context { + + override val ternScriptImports: String = buildString { + appendLine("import * as tern from \"tern/lib/tern.js\"") + appendLine("import * as condense from \"tern/lib/condense.js\"") + appendLine("import * as util from \"tern/test/util.js\"") + appendLine("import * as fs from \"fs\"") + appendLine("import * as path from \"path\"") + } + + override val tempFileImports: String = buildString { + val importFileUnderTest = "./instr/${filePathToInference.first().substringAfterLast("/")}" + appendLine("import * as $fileUnderTestAliases from \"$importFileUnderTest\"") + appendLine("import * as fs from \"fs\"") + } +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/providers/imports/RequireImportsProvider.kt b/utbot-js/src/main/kotlin/providers/imports/RequireImportsProvider.kt new file mode 100644 index 0000000000..d653b2b047 --- /dev/null +++ b/utbot-js/src/main/kotlin/providers/imports/RequireImportsProvider.kt @@ -0,0 +1,23 @@ +package providers.imports + +import service.ContextOwner +import service.ServiceContext +import settings.JsTestGenerationSettings.fileUnderTestAliases + +class RequireImportsProvider(context: ServiceContext) : IImportsProvider, ContextOwner by context { + + override val ternScriptImports: String = buildString { + appendLine("const tern = require(\"tern/lib/tern\")") + appendLine("const condense = require(\"tern/lib/condense.js\")") + appendLine("const util = require(\"tern/test/util.js\")") + appendLine("const fs = require(\"fs\")") + appendLine("const path = require(\"path\")") + } + + override val tempFileImports: String = buildString { + val importFileUnderTest = "instr/${filePathToInference.first().substringAfterLast("/")}" + appendLine("const $fileUnderTestAliases = require(\"./$importFileUnderTest\")") + appendLine("const fs = require(\"fs\")\n") + } + +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/service/InstrumentationService.kt b/utbot-js/src/main/kotlin/service/InstrumentationService.kt new file mode 100644 index 0000000000..71031359dd --- /dev/null +++ b/utbot-js/src/main/kotlin/service/InstrumentationService.kt @@ -0,0 +1,194 @@ +package service + +import com.google.javascript.jscomp.CodePrinter +import com.google.javascript.jscomp.NodeUtil +import com.google.javascript.rhino.Node +import java.io.File +import java.nio.file.Paths +import org.apache.commons.io.FileUtils +import parser.JsFunctionAstVisitor +import parser.JsParserUtils.getAnyValue +import parser.JsParserUtils.getModuleImportText +import parser.JsParserUtils.getRequireImportText +import parser.JsParserUtils.isRequireImport +import parser.JsParserUtils.runParser +import providers.exports.IExportsProvider +import utils.JsCmdExec +import utils.PathResolver.getRelativePath +import kotlin.io.path.pathString +import kotlin.math.roundToInt + +class InstrumentationService(context: ServiceContext, private val funcDeclOffset: Pair) : + ContextOwner by context { + + private val destinationFolderPath = "${projectPath}/${utbotDir}/instr" + private val instrumentedFilePath = "$destinationFolderPath/${filePathToInference.first().substringAfterLast("/")}" + private lateinit var parsedInstrFile: Node + lateinit var covFunName: String + + val allStatements: Set + get() = getStatementMapKeys() + + private data class Location( + val start: Pair, + val end: Pair + ) + + + /* + Extension functions below are used to parse instrumented file's block that looks like this: + + fnMap: { + "0": { + name: "testFunction", + decl: {start: {line: 4, column: 9}, end: {line: 4, column: 12}}, + loc: {start: {line: 4, column: 20}, end: {line: 9, column: 1}}, + line: 4 + } + } + + Node.getObjectFirstKey() returns "0" Node if called on "fnMap" Node. + Node.getObjectField("loc") returns loc Node if called on "0" Node. + Node.getObjectValue() returns 4 if called on any "line" Node. + Node.getObjectLocation("decl") returns Location class ((4, 9), (4, 12)) if called on "0" Node. + */ + private fun Node.getObjectFirstKey(): Node = this.firstFirstChild + ?: throw IllegalStateException("Node doesn't have child of child") + + private fun Node.getObjectField(fieldName: String): Node? { + var fieldNode: Node? = this.getObjectFirstKey() + do { + if (fieldNode?.string == fieldName) return fieldNode + fieldNode = fieldNode?.next + } while (fieldNode != null) + return null + } + + private fun Node.getObjectValue(): Any = this.firstChild?.getAnyValue() + ?: throw IllegalStateException("Can't get Node's simple value") + + private fun Node.getObjectLocation(locKey: String?): Location { + val generalField = locKey?.let { this.getObjectField(it) } ?: this + val startField = generalField.getObjectFirstKey() + val endField = startField.next!! + return Location( + startField.getLineValue() to startField.getColumnValue(), + endField.getLineValue() to endField.getColumnValue() + ) + } + + private fun Node.getLineValue(): Int = this.getObjectFirstKey().getObjectValue() + .toString() + .toFloat() + .roundToInt() + + private fun Node.getColumnValue(): Int = this.getObjectFirstKey().next!! + .getObjectValue() + .toString() + .toFloat() + .roundToInt() + + private fun Node.findAndIterateOver(key: String, func: (Node?) -> Unit) { + NodeUtil.visitPreOrder(this) { node -> + if (node.isStringKey && node.string == key) { + var currKey: Node? = node.firstChild!!.firstChild!! + do { + func(currKey) + currKey = currKey?.next + } while (currKey != null) + return@visitPreOrder + } + } + } + + private fun getStatementMapKeys() = buildSet { + val funcVisitor = JsFunctionAstVisitor(covFunName, null) + funcVisitor.accept(parsedInstrFile) + val funcNode = funcVisitor.targetFunctionNode + val funcLocation = getFuncLocation(funcNode) + funcNode.findAndIterateOver("statementMap") { currKey -> + operator fun Pair.compareTo(other: Pair): Int = + when { + this.first < other.first || (this.first == other.first && this.second <= other.second) -> -1 + this.first > other.first || (this.first == other.first && this.second >= other.second) -> 1 + else -> 0 + } + + val stmtLocation = currKey!!.getObjectLocation(null) + if (funcLocation.start < stmtLocation.start && funcLocation.end > stmtLocation.end) + add(currKey.string.toInt()) + } + } + + private fun getFuncLocation(covFuncNode: Node): Location { + var result = Location(0 to 0, Int.MAX_VALUE to Int.MAX_VALUE) + covFuncNode.findAndIterateOver("fnMap") { currKey -> + val declLocation = currKey!!.getObjectLocation("decl") + if (funcDeclOffset == declLocation.start) { + result = currKey.getObjectLocation("loc") + return@findAndIterateOver + } + } + return result + } + + fun instrument() { + val fileName = filePathToInference.first().substringAfterLast("/") + + JsCmdExec.runCommand( + cmd = arrayOf(settings.pathToNYC, "instrument", fileName, destinationFolderPath), + dir = filePathToInference.first().substringBeforeLast("/"), + shouldWait = true, + timeout = settings.timeout, + ) + val instrumentedFileText = File(instrumentedFilePath).readText() + parsedInstrFile = runParser(instrumentedFileText) + val covFunRegex = Regex("function (cov_.*)\\(\\).*") + val funName = covFunRegex.find(instrumentedFileText.takeWhile { it != '{' })?.groups?.get(1)?.value + ?: throw IllegalStateException("") + val fixedFileText = fixImportsInInstrumentedFile() + + IExportsProvider.providerByPackageJson(packageJson).instrumentationFunExport(funName) + File(instrumentedFilePath).writeTextAndUpdate(fixedFileText) + + covFunName = funName + } + + private fun File.writeTextAndUpdate(newText: String) { + this.writeText(newText) + parsedInstrFile = runParser(File(instrumentedFilePath).readText()) + } + + private fun fixImportsInInstrumentedFile(): String { + // nyc poorly handles imports paths in file to instrument. Manual fix required. + NodeUtil.visitPreOrder(parsedInstrFile) { node -> + when { + node.isRequireImport() -> { + val currString = node.getRequireImportText() + val relPath = Paths.get( + getRelativePath( + "${projectPath}/${utbotDir}/instr", + File(filePathToInference.first()).parent + ) + ).resolve(currString).pathString.replace("\\", "/") + node.firstChild!!.next!!.string = relPath + } + node.isImport -> { + val currString = node.getModuleImportText() + val relPath = Paths.get( + getRelativePath( + "${projectPath}/${utbotDir}/instr", + File(filePathToInference.first()).parent + ) + ).resolve(currString).pathString.replace("\\", "/") + node.firstChild!!.next!!.next!!.string = relPath + } + } + } + return CodePrinter.Builder(parsedInstrFile).build() + } + + fun removeTempFiles() { + FileUtils.deleteDirectory(File("$projectPath/$utbotDir/instr")) + } + +} diff --git a/utbot-js/src/main/kotlin/service/PackageJsonService.kt b/utbot-js/src/main/kotlin/service/PackageJsonService.kt new file mode 100644 index 0000000000..586a0dafe9 --- /dev/null +++ b/utbot-js/src/main/kotlin/service/PackageJsonService.kt @@ -0,0 +1,42 @@ +package service + +import java.io.File +import java.io.FilenameFilter +import org.json.JSONObject + +data class PackageJson( + val isModule: Boolean, + val deps: Set +) { + companion object { + val defaultConfig = PackageJson(false, emptySet()) + } +} + +class PackageJsonService( + private val filePathToInference: String, + private val projectDir: File +) { + + fun findClosestConfig(): PackageJson { + var currDir = File(filePathToInference) + do { + currDir = currDir.parentFile + val matchingFiles: Array = currDir.listFiles( + FilenameFilter { _, name -> + return@FilenameFilter name == "package.json" + } + ) ?: throw IllegalStateException("Error occurred while scanning file system") + if (matchingFiles.isNotEmpty()) return parseConfig(matchingFiles.first()) + } while (currDir != projectDir) + return PackageJson.defaultConfig + } + + private fun parseConfig(configFile: File): PackageJson { + val configAsJson = JSONObject(configFile.readText()) + return PackageJson( + isModule = configAsJson.optString("type") == "module", + deps = configAsJson.optJSONObject("dependencies")?.keySet() ?: emptySet() + ) + } +} diff --git a/utbot-js/src/main/kotlin/service/ServiceContext.kt b/utbot-js/src/main/kotlin/service/ServiceContext.kt new file mode 100644 index 0000000000..d30ccf1fea --- /dev/null +++ b/utbot-js/src/main/kotlin/service/ServiceContext.kt @@ -0,0 +1,22 @@ +package service + +import com.google.javascript.rhino.Node +import settings.JsDynamicSettings + +class ServiceContext( + override val utbotDir: String, + override val projectPath: String, + override val filePathToInference: List, + override val parsedFile: Node, + override val settings: JsDynamicSettings, + override var packageJson: PackageJson = PackageJson.defaultConfig +) : ContextOwner + +interface ContextOwner { + val utbotDir: String + val projectPath: String + val filePathToInference: List + val parsedFile: Node + val settings: JsDynamicSettings + var packageJson: PackageJson +} diff --git a/utbot-js/src/main/kotlin/service/TernService.kt b/utbot-js/src/main/kotlin/service/TernService.kt new file mode 100644 index 0000000000..fd215752a4 --- /dev/null +++ b/utbot-js/src/main/kotlin/service/TernService.kt @@ -0,0 +1,190 @@ +package service + +import com.google.javascript.rhino.Node +import framework.api.js.JsClassId +import framework.api.js.JsMultipleClassId +import framework.api.js.util.jsUndefinedClassId +import java.io.File +import org.json.JSONException +import org.json.JSONObject +import parser.JsParserUtils +import parser.JsParserUtils.getAbstractFunctionName +import parser.JsParserUtils.getAbstractFunctionParams +import parser.JsParserUtils.getClassName +import parser.JsParserUtils.getConstructor +import providers.imports.IImportsProvider +import utils.JsCmdExec +import utils.constructClass +import utils.data.MethodTypes + +/** + * Installs and sets up scripts for running Tern.js type guesser. + */ +class TernService(context: ServiceContext) : ContextOwner by context { + + private val importProvider = IImportsProvider.providerByPackageJson(packageJson, context) + + private fun ternScriptCode() = """ +${generateImportsSection()} + +var condenseDir = ""; + +function runTest(options) { + + var server = new tern.Server({ + projectDir: util.resolve(condenseDir), + defs: [util.ecmascript], + plugins: options.plugins, + getFile: function(name) { + return fs.readFileSync(path.resolve(condenseDir, name), "utf8"); + } + }); + options.load.forEach(function(file) { + server.addFile(file) + }); + server.flush(function() { + var origins = options.include || options.load; + var condensed = condense.condense(origins, null, {sortOutput: true}); + var out = JSON.stringify(condensed, null, 2); + console.log(out) + }); +} + +function test(options) { + options = {load: options}; + runTest(options); +} + +test(["${filePathToInference.joinToString(separator = "\", \"")}"]) + """ + + init { + with(context) { + setupTernEnv("$projectPath/$utbotDir") + runTypeInferencer() + } + } + + private lateinit var json: JSONObject + + private fun generateImportsSection(): String = importProvider.ternScriptImports + + private fun setupTernEnv(path: String) { + File(path).mkdirs() + val ternScriptFile = File("$path/ternScript.js") + ternScriptFile.writeText(ternScriptCode()) + } + + private fun runTypeInferencer() { + val (inputText, _) = JsCmdExec.runCommand( + dir = "$projectPath/$utbotDir/", + shouldWait = true, + timeout = 20, + cmd = arrayOf("\"${settings.pathToNode}\"", "\"${projectPath}/$utbotDir/ternScript.js\""), + ) + json = try { + JSONObject(inputText.replaceAfterLast("}", "")) + } catch (_: Throwable) { + JSONObject() + } + } + + fun processConstructor(classNode: Node): List { + return try { + val classJson = json.getJSONObject(classNode.getClassName()) + val constructorFunc = classJson.getString("!type") + .filterNot { setOf(' ', '+').contains(it) } + extractParameters(constructorFunc) + } catch (e: JSONException) { + classNode.getConstructor()?.getAbstractFunctionParams()?.map { jsUndefinedClassId } ?: emptyList() + } + } + + private fun extractParameters(line: String): List { + val parametersRegex = Regex("fn[(](.+)[)]") + return parametersRegex.find(line)?.groups?.get(1)?.let { matchResult -> + val value = matchResult.value + val paramGroupList = Regex("(\\w+:\\[\\w+(,\\w+)*]|\\w+:\\w+)|\\w+:\\?").findAll(value).toList() + paramGroupList.map { paramGroup -> + val paramReg = Regex("\\w*:(.*)") + try { + val param = paramGroup.groups[0]!!.value + makeClassId( + paramReg.find(param)?.groups?.get(1)?.value + ?: throw IllegalStateException() + ) + } catch (t: Throwable) { + jsUndefinedClassId + } + } + } ?: emptyList() + } + + private fun extractReturnType(line: String): JsClassId { + val returnTypeRegex = Regex("->(.*)") + return returnTypeRegex.find(line)?.groups?.get(1)?.let { matchResult -> + val value = matchResult.value + try { + makeClassId(value) + } catch (t: Throwable) { + jsUndefinedClassId + } + } ?: jsUndefinedClassId + } + + fun processMethod(className: String?, funcNode: Node, isToplevel: Boolean = false): MethodTypes { + // Js doesn't support nested classes, so if the function is not top-level, then we can check for only one parent class. + try { + var scope = className?.let { + if (!isToplevel) json.getJSONObject(it) else json + } ?: json + try { + scope.getJSONObject(funcNode.getAbstractFunctionName()) + } catch (e: JSONException) { + scope = scope.getJSONObject("prototype") + } + val methodJson = scope.getJSONObject(funcNode.getAbstractFunctionName()) + val typesString = methodJson.getString("!type") + .filterNot { setOf(' ', '+').contains(it) } + val parametersList = lazy { extractParameters(typesString) } + val returnType = lazy { extractReturnType(typesString) } + + return MethodTypes(parametersList, returnType) + } catch (e: Exception) { + return MethodTypes( + lazy { funcNode.getAbstractFunctionParams().map { jsUndefinedClassId } }, + lazy { jsUndefinedClassId } + ) + } + } + + private fun makeClassId(name: String): JsClassId { + val classId = when { + name == "?" || name.toIntOrNull() != null || name.contains('!') -> jsUndefinedClassId + Regex("\\[(.*)]").matches(name) -> { + val arrType = Regex("\\[(.*)]").find(name)?.groups?.get(1)?.value?.substringBefore(",") + ?: throw IllegalStateException() + JsClassId( + jsName = "Array", + elementClassId = makeClassId(arrType) + ) + } + + name.contains('|') -> JsMultipleClassId(name) + else -> JsClassId(name) + } + + return try { + val classNode = JsParserUtils.searchForClassDecl( + className = name, + parsedFile = parsedFile, + strict = true, + ) + classNode?.let { + JsClassId(name).constructClass(this, it) + } ?: classId + } catch (e: Exception) { + classId + } + } +} diff --git a/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt new file mode 100644 index 0000000000..9b774d8154 --- /dev/null +++ b/utbot-js/src/main/kotlin/service/coverage/BasicCoverageService.kt @@ -0,0 +1,50 @@ +package service.coverage + +import java.io.File +import mu.KotlinLogging +import org.json.JSONObject +import org.utbot.framework.plugin.api.TimeoutException +import service.ServiceContext +import settings.JsTestGenerationSettings.tempFileName +import utils.JsCmdExec +import utils.data.ResultData + +private val logger = KotlinLogging.logger {} + +class BasicCoverageService( + context: ServiceContext, + baseCoverage: Map, + private val scriptTexts: List, +) : CoverageService(context, baseCoverage, scriptTexts) { + + override fun generateCoverageReport() { + scriptTexts.indices.forEach { index -> + try { + val (_, errorText) = JsCmdExec.runCommand( + cmd = arrayOf("\"${settings.pathToNode}\"", "\"$utbotDirPath/$tempFileName$index.js\""), + dir = projectPath, + shouldWait = true, + timeout = settings.timeout, + ) + val resFile = File("$utbotDirPath/$tempFileName$index.json") + val rawResult = resFile.readText() + resFile.delete() + val json = JSONObject(rawResult) + coverageList.add(index to json.getJSONObject("s")) + val resultData = ResultData(json) + _resultList.add(resultData) + if (errorText.isNotEmpty()) { + logger.error { errorText } + } + } catch (e: TimeoutException) { + val resultData = ResultData( + rawString = "Timeout", + index = index, + isError = true, + ) + coverageList.add(index to JSONObject()) + _resultList.add(resultData) + } + } + } +} diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageMode.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageMode.kt new file mode 100644 index 0000000000..5fee242ac0 --- /dev/null +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageMode.kt @@ -0,0 +1,6 @@ +package service.coverage + +enum class CoverageMode { + FAST, + BASIC +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt new file mode 100644 index 0000000000..b51106e8cf --- /dev/null +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageService.kt @@ -0,0 +1,110 @@ +package service.coverage + +import java.io.File +import org.json.JSONException +import org.json.JSONObject +import service.ContextOwner +import service.ServiceContext +import settings.JsTestGenerationSettings +import utils.JsCmdExec +import utils.data.CoverageData +import utils.data.ResultData + +abstract class CoverageService( + context: ServiceContext, + private val baseCoverage: Map, + private val scriptTexts: List, +) : ContextOwner by context { + + private val _utbotDirPath = lazy { "${projectPath}/${utbotDir}" } + protected val utbotDirPath: String + get() = _utbotDirPath.value + protected val coverageList = mutableListOf>() + protected val _resultList = mutableListOf() + val resultList: List + get() = _resultList.toList() + + companion object { + + private fun createTempScript(path: String, scriptText: String) { + val file = File(path) + file.writeText(scriptText) + file.createNewFile() + } + + fun getBaseCoverage(context: ServiceContext, baseCoverageScriptText: String): Map { + with(context) { + val utbotDirPath = "${projectPath}/${utbotDir}" + createTempScript( + path = "$utbotDirPath/${JsTestGenerationSettings.tempFileName}Base.js", + scriptText = baseCoverageScriptText + ) + JsCmdExec.runCommand( + cmd = arrayOf( + "\"${settings.pathToNode}\"", + "\"$utbotDirPath/${JsTestGenerationSettings.tempFileName}Base.js\"" + ), + dir = projectPath, + shouldWait = true, + timeout = settings.timeout, + ) + return JSONObject(File("$utbotDirPath/${JsTestGenerationSettings.tempFileName}Base.json").readText()) + .getJSONObject("s").let { obj -> + obj.keySet().associate { key -> + key.toInt() to obj.getInt(key) + } + } + } + } + } + + init { + generateTempFiles() + } + + private fun generateTempFiles() { + scriptTexts.forEachIndexed { index, scriptText -> + val tempScriptPath = "$utbotDirPath/${JsTestGenerationSettings.tempFileName}$index.js" + createTempScript( + path = tempScriptPath, + scriptText = scriptText + ) + } + } + + fun getCoveredLines(): List { + try { + // TODO: sort by coverage size desc + return coverageList + .map { (_, obj) -> + val map = obj.keySet().associate { key -> + val intKey = key.toInt() + intKey to (obj.getInt(key) - baseCoverage.getOrDefault(intKey, 0)) + } + CoverageData(map.mapNotNull { entry -> + entry.key.takeIf { entry.value > 0 } + }.toSet()) + } + } catch (e: JSONException) { + throw Exception("Could not get coverage of test cases!") + } finally { + removeTempFiles() + } + } + + abstract fun generateCoverageReport() + + private fun createTempScript(path: String, scriptText: String) { + val file = File(path) + file.writeText(scriptText) + file.createNewFile() + } + + private fun removeTempFiles() { + File("$utbotDirPath/${JsTestGenerationSettings.tempFileName}Base.js").delete() + File("$utbotDirPath/${JsTestGenerationSettings.tempFileName}Base.json").delete() + for (index in scriptTexts.indices) { + File("$utbotDirPath/${JsTestGenerationSettings.tempFileName}$index.js").delete() + } + } +} diff --git a/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt new file mode 100644 index 0000000000..e88b283fcb --- /dev/null +++ b/utbot-js/src/main/kotlin/service/coverage/CoverageServiceProvider.kt @@ -0,0 +1,303 @@ +package service.coverage + +import framework.api.js.JsClassId +import framework.api.js.JsMethodId +import framework.api.js.JsPrimitiveModel +import framework.api.js.util.isExportable +import framework.api.js.util.isUndefined +import fuzzer.JsMethodDescription +import java.util.regex.Pattern +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.util.isStatic +import providers.imports.IImportsProvider +import service.ContextOwner +import service.InstrumentationService +import service.ServiceContext +import settings.JsTestGenerationSettings +import settings.JsTestGenerationSettings.tempFileName +import utils.data.CoverageData +import utils.data.ResultData + +class CoverageServiceProvider( + private val context: ServiceContext, + private val instrumentationService: InstrumentationService, + private val mode: CoverageMode, + private val description: JsMethodDescription +) : ContextOwner by context { + + private val imports = IImportsProvider.providerByPackageJson(packageJson, context).tempFileImports + + private val filePredicate = """ +function check_value(value, json) { + if (value === Infinity) { + json.is_inf = true + json.spec_sign = 1 + } + if (value === -Infinity) { + json.is_inf = true + json.spec_sign = -1 + } + if (Number.isNaN(value)) { + json.is_nan = true + } +} + +function getType(value) { + if (value instanceof Set) { + return "Set" + } else if (value instanceof Map) { + return "Map" + } else if (value instanceof Array) { + return "Array" + } + return typeof value +} + +function getRes(value, type) { + if (type === "Set" || type === "Array") { + return Array.from(value, (value) => { + let json = {} + json.type = getType(value) + check_value(value, json) + json.result = getRes(value, json.type) + json.index = 0 + return json + }) + } else if (type === "Map") { + return Array.from(value, ([name, value]) => { + let json = {} + json.type = getType(value) + check_value(value, json) + json.result = getRes(value, json.type) + json.index = 0 + return {name, json} + }) + } + return value +} + """ + + private val baseCoverage: Map + + init { + val temp = makeScriptForBaseCoverage( + instrumentationService.covFunName, + "${projectPath}/${utbotDir}/${tempFileName}Base.json" + ) + baseCoverage = CoverageService.getBaseCoverage( + context, + temp + ) + } + + fun get( + fuzzedValues: List>, + execId: JsMethodId, + ): Pair, List> { + return when (mode) { + CoverageMode.FAST -> runFastCoverageAnalysis( + fuzzedValues, + execId + ) + + CoverageMode.BASIC -> runBasicCoverageAnalysis( + fuzzedValues, + execId + ) + } + } + + private fun runBasicCoverageAnalysis( + fuzzedValues: List>, + execId: JsMethodId, + ): Pair, List> { + val covFunName = instrumentationService.covFunName + val tempScriptTexts = fuzzedValues.indices.map { + imports + "$filePredicate\n\n" + makeStringForRunJs( + fuzzedValue = fuzzedValues[it], + method = execId, + containingClass = if (!execId.classId.isUndefined) execId.classId.name else null, + covFunName = covFunName, + index = it, + resFilePath = "${projectPath}/${utbotDir}/$tempFileName", + ) + } + val coverageService = BasicCoverageService( + context = context, + baseCoverage = baseCoverage, + scriptTexts = tempScriptTexts, + ) + coverageService.generateCoverageReport() + return coverageService.getCoveredLines() to coverageService.resultList + } + + private fun runFastCoverageAnalysis( + fuzzedValues: List>, + execId: JsMethodId, + ): Pair, List> { + val covFunName = instrumentationService.covFunName + val tempScriptTexts = imports + "$filePredicate\n\n" + fuzzedValues.indices.joinToString("\n\n") { + makeStringForRunJs( + fuzzedValue = fuzzedValues[it], + method = execId, + containingClass = if (!execId.classId.isUndefined) execId.classId.name else null, + covFunName = covFunName, + index = it, + resFilePath = "${projectPath}/${utbotDir}/$tempFileName", + ) + } + val coverageService = FastCoverageService( + context = context, + baseCoverage = baseCoverage, + scriptTexts = listOf(tempScriptTexts), + testCaseIndices = fuzzedValues.indices, + ) + coverageService.generateCoverageReport() + return coverageService.getCoveredLines() to coverageService.resultList + } + + private fun makeScriptForBaseCoverage(covFunName: String, resFilePath: String): String { + return """ +$imports + +let json = {} +json.s = ${JsTestGenerationSettings.fileUnderTestAliases}.$covFunName().s +fs.writeFileSync("$resFilePath", JSON.stringify(json)) + """ + } + + private fun makeStringForRunJs( + fuzzedValue: List, + method: JsMethodId, + containingClass: String?, + covFunName: String, + index: Int, + resFilePath: String, + ): String { + val callString = makeCallFunctionString(fuzzedValue, method, containingClass, index) + return """ +let json$index = {} +json$index.is_inf = false +json$index.is_nan = false +json$index.is_error = false +json$index.spec_sign = 1 +let res$index +try { + $callString + check_value(res$index, json$index) +} catch(e) { + res$index = e.message + json$index.is_error = true +} +json$index.type = getType(res$index) +json$index.result = getRes(res$index, json$index.type) +json$index.index = $index +json$index.s = ${JsTestGenerationSettings.fileUnderTestAliases}.$covFunName().s + +fs.writeFileSync("$resFilePath$index.json", JSON.stringify(json$index)) + """ + } + + private fun makeCallFunctionString( + fuzzedValue: List, + method: JsMethodId, + containingClass: String?, + index: Int + ): String { + val paramsInit = initParams(fuzzedValue) + val actualParams = description.thisInstance?.let { fuzzedValue.drop(1) } ?: fuzzedValue + val initClass = containingClass?.let { + if (!method.isStatic) { + description.thisInstance?.let { fuzzedValue[0].initModelAsString() } + ?: "new ${JsTestGenerationSettings.fileUnderTestAliases}.${it}()" + } else "${JsTestGenerationSettings.fileUnderTestAliases}.$it" + } ?: JsTestGenerationSettings.fileUnderTestAliases + var callString = "$initClass.${method.name}" + callString = List(actualParams.size) { idx -> "param$idx" }.joinToString( + prefix = "res$index = $callString(", + postfix = ")", + ) + return paramsInit + callString + } + + private fun initParams(fuzzedValue: List): String { + val actualParams = description.thisInstance?.let { fuzzedValue.drop(1) } ?: fuzzedValue + return actualParams.mapIndexed { index, param -> + val varName = "param$index" + buildString { + appendLine("let $varName = ${param.initModelAsString()}") + (param as? UtAssembleModel)?.initModificationsAsString(this, varName) + } + }.joinToString(separator = "\n") + } + + private fun Any.quoteWrapIfNecessary(): String = + when (this) { + is String -> "`$this`" + else -> "$this" + } + + private val symbolsToEscape = setOf("`", Pattern.quote("\\")) + + private fun Any.escapeSymbolsIfNecessary(): Any = + when (this) { + is String -> this.replace(Regex(symbolsToEscape.joinToString(separator = "|")), "") + else -> this + } + + private fun UtAssembleModel.toParamString(): String { + val importPrefix = "new ${JsTestGenerationSettings.fileUnderTestAliases}.".takeIf { + (classId as JsClassId).isExportable + } ?: "new " + val callConstructorString = importPrefix + classId.name + val paramsString = instantiationCall.params.joinToString( + prefix = "(", + postfix = ")", + ) { + it.initModelAsString() + } + return callConstructorString + paramsString + } + + private fun UtArrayModel.toParamString(): String { + val paramsString = stores.values.joinToString( + prefix = "[", + postfix = "]", + ) { + it.initModelAsString() + } + return paramsString + } + + private fun UtModel.initModelAsString(): String = + when (this) { + is UtAssembleModel -> this.toParamString() + is UtArrayModel -> this.toParamString() + is UtNullModel -> "null" + else -> { + (this as JsPrimitiveModel).value.escapeSymbolsIfNecessary().quoteWrapIfNecessary() + } + } + + private fun UtAssembleModel.initModificationsAsString(stringBuilder: StringBuilder, varName: String) { + with(stringBuilder) { + this@initModificationsAsString.modificationsChain.forEach { + if (it is UtExecutableCallModel) { + val exec = it.executable as JsMethodId + appendLine( + it.params.joinToString( + prefix = "$varName.${exec.name}(", + postfix = ")" + ) { model -> + model.initModelAsString() + } + ) + } + } + } + } +} diff --git a/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt new file mode 100644 index 0000000000..a2c3c0f05a --- /dev/null +++ b/utbot-js/src/main/kotlin/service/coverage/FastCoverageService.kt @@ -0,0 +1,43 @@ +package service.coverage + +import java.io.File +import mu.KotlinLogging +import org.json.JSONObject +import service.ServiceContext +import settings.JsTestGenerationSettings.fuzzingThreshold +import settings.JsTestGenerationSettings.tempFileName +import utils.JsCmdExec +import utils.data.ResultData + +private val logger = KotlinLogging.logger {} + +class FastCoverageService( + context: ServiceContext, + baseCoverage: Map, + scriptTexts: List, + private val testCaseIndices: IntRange, +) : CoverageService(context, baseCoverage, scriptTexts) { + + override fun generateCoverageReport() { + val (_, errorText) = JsCmdExec.runCommand( + cmd = arrayOf("\"${settings.pathToNode}\"", "\"$utbotDirPath/$tempFileName" + "0.js\""), + dir = projectPath, + shouldWait = true, + timeout = settings.timeout, + ) + for (i in 0..minOf(fuzzingThreshold - 1, testCaseIndices.last)) { + val resFile = File("$utbotDirPath/$tempFileName$i.json") + val rawResult = resFile.readText() + resFile.delete() + val json = JSONObject(rawResult) + val index = json.getInt("index") + if (index != i) logger.error { "Index $index != i $i" } + coverageList.add(index to json.getJSONObject("s")) + val resultData = ResultData(json) + _resultList.add(resultData) + } + if (errorText.isNotEmpty()) { + logger.error { errorText } + } + } +} diff --git a/utbot-js/src/main/kotlin/settings/JsDynamicSettings.kt b/utbot-js/src/main/kotlin/settings/JsDynamicSettings.kt new file mode 100644 index 0000000000..c985b2454f --- /dev/null +++ b/utbot-js/src/main/kotlin/settings/JsDynamicSettings.kt @@ -0,0 +1,11 @@ +package settings + +import service.coverage.CoverageMode + +data class JsDynamicSettings( + val pathToNode: String = "node", + val pathToNYC: String = "nyc", + val pathToNPM: String = "npm", + val timeout: Long = 15L, + val coverageMode: CoverageMode = CoverageMode.FAST +) diff --git a/utbot-js/src/main/kotlin/settings/JsExportsSettings.kt b/utbot-js/src/main/kotlin/settings/JsExportsSettings.kt new file mode 100644 index 0000000000..2c30195953 --- /dev/null +++ b/utbot-js/src/main/kotlin/settings/JsExportsSettings.kt @@ -0,0 +1,8 @@ +package settings + +object JsExportsSettings { + + // Anchors for exports in user's code. Used in regexes to modify this section on demand. + const val startComment = "// Start of exports generated by UTBot" + const val endComment = "// End of exports generated by UTBot" +} \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt new file mode 100644 index 0000000000..ebea20a6ab --- /dev/null +++ b/utbot-js/src/main/kotlin/settings/JsPackagesSettings.kt @@ -0,0 +1,127 @@ +package settings + +import java.io.File +import org.utbot.common.PathUtil.replaceSeparator +import service.PackageJsonService +import settings.JsPackagesSettings.mochaData +import settings.JsPackagesSettings.nycData +import settings.JsPackagesSettings.ternData +import utils.JsCmdExec +import utils.OsProvider + +object JsPackagesSettings { + val mochaData: PackageData = PackageData("mocha", NpmListFlag.L) + val nycData: PackageData = PackageData("nyc", NpmListFlag.G) + val ternData: PackageData = PackageData("tern", NpmListFlag.L) +} + +val jsPackagesList = listOf( + mochaData, + nycData, + ternData +) + +enum class NpmListFlag { + L { + override fun toString(): String = "-l" + }, + G { + override fun toString(): String = "-g" + } +} + +data class PackageData( + val packageName: String, + val npmListFlag: NpmListFlag +) { + + fun findPackagePath(): String? { + val (inputText, _) = JsCmdExec.runCommand( + dir = null, + shouldWait = true, + timeout = 10, + cmd = arrayOf(OsProvider.getProviderByOs().getAbstractivePathTool(), packageName) + ) + + return inputText.split(System.lineSeparator()).first().takeIf { it.contains(packageName) } + } +} + +class PackageDataService( + filePathToInference: String, + private val projectPath: String, + private val pathToNpm: String, +) { + private val packageJson = PackageJsonService(filePathToInference, File(projectPath)).findClosestConfig() + + companion object { + var nycPath: String = "" + private set + } + + fun findPackage(packageData: PackageData): Boolean = with(packageData) { + when (npmListFlag) { + NpmListFlag.G -> { + val (inputText, _) = JsCmdExec.runCommand( + dir = projectPath, + shouldWait = true, + timeout = 10, + cmd = arrayOf("\"$pathToNpm\"", "list", npmListFlag.toString()) + ) + var result = inputText.contains(packageName) + if (!result || this == nycData) { + val packagePath = this.findPackagePath() + nycPath = packagePath?.let { + replaceSeparator(it) + OsProvider.getProviderByOs().npmPackagePostfix + } ?: "Nyc was not found" + if (!result) { + result = this.findPackagePath()?.isNotBlank() ?: false + } + } + return result + } + + NpmListFlag.L -> { + packageJson.deps.contains(packageName) + } + } + } + + fun installMissingPackages(packages: List): Pair { + var inputTextAllPackages = "" + var errorTextAllPackages = "" + if (packages.isEmpty()) return inputTextAllPackages to errorTextAllPackages + + val localPackageNames = packages.filter { it.npmListFlag == NpmListFlag.L } + .map { it.packageName }.toTypedArray() + val globalPackageNames = packages.filter { it.npmListFlag == NpmListFlag.G } + .map { it.packageName }.toTypedArray() + + // Local packages installation + if (localPackageNames.isNotEmpty()) { + val (inputText, errorText) = JsCmdExec.runCommand( + dir = projectPath, + shouldWait = true, + timeout = 10, + cmd = arrayOf("\"$pathToNpm\"", "install", NpmListFlag.L.toString(), *localPackageNames) + ) + inputTextAllPackages += inputText + errorTextAllPackages += errorText + } + // Global packages installation + if (globalPackageNames.isNotEmpty()) { + val (inputText, errorText) = JsCmdExec.runCommand( + dir = projectPath, + shouldWait = true, + timeout = 10, + cmd = arrayOf("\"$pathToNpm\"", "install", NpmListFlag.G.toString(), *globalPackageNames) + ) + inputTextAllPackages += inputText + errorTextAllPackages += errorText + } + // Find path to nyc execution file after installation + if (packages.contains(nycData)) findPackage(nycData) + + return Pair(inputTextAllPackages, errorTextAllPackages) + } +} diff --git a/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt b/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt new file mode 100644 index 0000000000..37922d5dfe --- /dev/null +++ b/utbot-js/src/main/kotlin/settings/JsTestGenerationSettings.kt @@ -0,0 +1,21 @@ +package settings + +object JsTestGenerationSettings { + + // Used for toplevel functions in IDEA plugin. + const val dummyClassName = "toplevelHack" + + // Default timeout for Node.js try to run a single testcase. + const val defaultTimeout = 10L + + const val fuzzingTimeout = 30_000L + + // Name of file under test when importing it. + const val fileUnderTestAliases = "fileUnderTest" + + // Name of temporary files created. + const val tempFileName = "temp" + + // Number of test cases that can fit in one temporary file for Fast coverage mode + const val fuzzingThreshold = 300 +} diff --git a/utbot-js/src/main/kotlin/utils/JsClassConstructors.kt b/utbot-js/src/main/kotlin/utils/JsClassConstructors.kt new file mode 100644 index 0000000000..54149f9202 --- /dev/null +++ b/utbot-js/src/main/kotlin/utils/JsClassConstructors.kt @@ -0,0 +1,76 @@ +package utils + +import com.google.javascript.rhino.Node +import framework.api.js.JsClassId +import framework.api.js.JsConstructorId +import framework.api.js.JsMethodId +import framework.api.js.util.jsUndefinedClassId +import parser.JsParserUtils.getAbstractFunctionName +import parser.JsParserUtils.getClassMethods +import parser.JsParserUtils.getClassName +import parser.JsParserUtils.isStatic +import service.TernService + +fun JsClassId.constructClass( + ternService: TernService, + classNode: Node? = null, + functions: List = emptyList() +): JsClassId { + val className = classNode?.getClassName() + val methods = constructMethods(classNode, ternService, className, functions) + + val constructor = classNode?.let { + JsConstructorId( + JsClassId(name), + ternService.processConstructor(it), + ) + } + val newClassId = JsClassId( + jsName = name, + methods = methods, + constructor = constructor, + classPackagePath = ternService.projectPath, + classFilePath = ternService.filePathToInference.first(), + ) + methods.forEach { + it.classId = newClassId + } + constructor?.classId = newClassId + return newClassId +} + +private fun JsClassId.constructMethods( + classNode: Node?, + ternService: TernService, + className: String?, + functions: List +): Sequence { + with(this) { + val methods = classNode?.getClassMethods()?.map { methodNode -> + val types = ternService.processMethod(className, methodNode) + JsMethodId( + classId = JsClassId(name), + name = methodNode.getAbstractFunctionName(), + returnTypeNotLazy = jsUndefinedClassId, + parametersNotLazy = emptyList(), + staticModifier = methodNode.isStatic(), + lazyReturnType = types.returnType, + lazyParameters = types.parameters, + ) + }?.asSequence() ?: + // used for toplevel functions + functions.map { funcNode -> + val types = ternService.processMethod(className, funcNode, true) + JsMethodId( + classId = JsClassId(name), + name = funcNode.getAbstractFunctionName(), + returnTypeNotLazy = jsUndefinedClassId, + parametersNotLazy = emptyList(), + staticModifier = true, + lazyReturnType = types.returnType, + lazyParameters = types.parameters, + ) + }.asSequence() + return methods + } +} diff --git a/utbot-js/src/main/kotlin/utils/JsCmdExec.kt b/utbot-js/src/main/kotlin/utils/JsCmdExec.kt new file mode 100644 index 0000000000..c94a4377ca --- /dev/null +++ b/utbot-js/src/main/kotlin/utils/JsCmdExec.kt @@ -0,0 +1,35 @@ +package utils + +import java.io.File +import java.util.concurrent.TimeUnit +import org.utbot.framework.plugin.api.TimeoutException +import settings.JsTestGenerationSettings.defaultTimeout + +object JsCmdExec { + + fun runCommand( + dir: String? = null, + shouldWait: Boolean = false, + timeout: Long = defaultTimeout, + vararg cmd: String, + ): Pair { + val builder = ProcessBuilder(*OsProvider.getProviderByOs().getCmdPrefix(), *cmd) + dir?.let { + builder.directory(File(it)) + } + val process = builder.start() + if (shouldWait) { + if (!process.waitFor(timeout, TimeUnit.SECONDS)) { + process.descendants().forEach { + it.destroy() + } + process.destroy() + throw TimeoutException("") + } + } + return Pair( + process.inputStream.bufferedReader().use { it.readText() }, + process.errorStream.bufferedReader().use { it.readText() } + ) + } +} diff --git a/utbot-js/src/main/kotlin/utils/JsOsUtils.kt b/utbot-js/src/main/kotlin/utils/JsOsUtils.kt new file mode 100644 index 0000000000..0b8c295f6d --- /dev/null +++ b/utbot-js/src/main/kotlin/utils/JsOsUtils.kt @@ -0,0 +1,36 @@ +package utils + +import java.util.Locale + +abstract class OsProvider { + + abstract fun getCmdPrefix(): Array + abstract fun getAbstractivePathTool(): String + + abstract val npmPackagePostfix: String + + companion object { + + fun getProviderByOs(): OsProvider { + val osData = System.getProperty("os.name").lowercase(Locale.getDefault()) + return when { + osData.contains("windows") -> WindowsProvider() + else -> LinuxProvider() + } + } + } +} + +class WindowsProvider : OsProvider() { + override fun getCmdPrefix() = emptyArray() + override fun getAbstractivePathTool() = "where" + + override val npmPackagePostfix = ".cmd" +} + +class LinuxProvider : OsProvider() { + override fun getCmdPrefix() = emptyArray() + override fun getAbstractivePathTool() = "which" + + override val npmPackagePostfix = "" +} diff --git a/utbot-js/src/main/kotlin/utils/PathResolver.kt b/utbot-js/src/main/kotlin/utils/PathResolver.kt new file mode 100644 index 0000000000..8e1730db97 --- /dev/null +++ b/utbot-js/src/main/kotlin/utils/PathResolver.kt @@ -0,0 +1,12 @@ +package utils + +import java.nio.file.Paths + +object PathResolver { + + fun getRelativePath(to: String, from: String): String { + val toPath = Paths.get(to) + val fromPath = Paths.get(from) + return toPath.relativize(fromPath).toString().replace("\\", "/") + } +} diff --git a/utbot-js/src/main/kotlin/utils/ValueUtil.kt b/utbot-js/src/main/kotlin/utils/ValueUtil.kt new file mode 100644 index 0000000000..0c9480d01e --- /dev/null +++ b/utbot-js/src/main/kotlin/utils/ValueUtil.kt @@ -0,0 +1,89 @@ +package utils + +import framework.api.js.JsClassId +import framework.api.js.util.isJsStdStructure +import framework.api.js.util.jsBooleanClassId +import framework.api.js.util.jsDoubleClassId +import framework.api.js.util.jsErrorClassId +import framework.api.js.util.jsNumberClassId +import framework.api.js.util.jsStringClassId +import framework.api.js.util.jsUndefinedClassId +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import utils.data.ResultData + +fun ResultData.toJsAny(returnType: JsClassId = jsUndefinedClassId): Pair { + try { + this.buildUniqueValue()?.let { return it } + with(this.rawString) { + return when { + isError -> this to jsErrorClassId + this == "true" || this == "false" -> toBoolean() to jsBooleanClassId + this == "null" || this == "undefined" -> null to jsUndefinedClassId + returnType.isJsStdStructure -> + makeStructure(this, returnType) to returnType + returnType == jsStringClassId || this@toJsAny.type == jsStringClassId.name -> + this.replace("\"", "") to jsStringClassId + + else -> { + if (contains('.')) { + (toDoubleOrNull() ?: toBigDecimal()) to jsDoubleClassId + } else { + val value = toByteOrNull() ?: toShortOrNull() ?: toIntOrNull() ?: toLongOrNull() + ?: toBigIntegerOrNull() ?: toDoubleOrNull() + if (value != null) value to jsNumberClassId else { + val obj = makeObject(this) + obj!! to returnType + } + } + } + } + } + } catch(e: Exception) { + if (e is IllegalStateException) throw e else + throw IllegalStateException("Could not make JavaScript value from $this value with type ${returnType.name}") + } +} + +private fun ResultData.buildUniqueValue(): Pair? { + return when { + isInf -> specSign * Double.POSITIVE_INFINITY to jsDoubleClassId + isNan -> Double.NaN to jsDoubleClassId + else -> null + } +} + +private fun makeObject(objString: String): Map? { + return try { + val trimmed = objString.substringAfter(" ") + val json = JSONObject(trimmed) + val resMap = mutableMapOf() + json.keySet().forEach { + resMap[it] = ResultData(json.get(it).toString(), index = 0).toJsAny().first as Any + } + resMap + } catch (e: JSONException) { + null + } +} + +private fun makeStructure(structString: String, type: JsClassId): List { + val json = JSONArray(structString) + return when (type.name) { + "Array", "Set" -> { + json.map { jsonObj -> + ResultData(jsonObj as JSONObject).toJsAny().first + } + } + "Map" -> { + json.map { jsonObj -> + val name = (jsonObj as JSONObject).get("name") + name to ResultData(jsonObj.getJSONObject("json")).toJsAny().first + } + } + else -> throw UnsupportedOperationException( + "Can't make JavaScript structure from $structString with type ${type.name}" + ) + } +} diff --git a/utbot-js/src/main/kotlin/utils/data/CoverageData.kt b/utbot-js/src/main/kotlin/utils/data/CoverageData.kt new file mode 100644 index 0000000000..b64fdfdaf7 --- /dev/null +++ b/utbot-js/src/main/kotlin/utils/data/CoverageData.kt @@ -0,0 +1,5 @@ +package utils.data + +data class CoverageData( + val additionalCoverage: Set +) diff --git a/utbot-js/src/main/kotlin/utils/data/MethodTypes.kt b/utbot-js/src/main/kotlin/utils/data/MethodTypes.kt new file mode 100644 index 0000000000..cea1c403ee --- /dev/null +++ b/utbot-js/src/main/kotlin/utils/data/MethodTypes.kt @@ -0,0 +1,8 @@ +package utils.data + +import framework.api.js.JsClassId + +data class MethodTypes( + val parameters: Lazy>, + val returnType: Lazy, +) \ No newline at end of file diff --git a/utbot-js/src/main/kotlin/utils/data/ResultData.kt b/utbot-js/src/main/kotlin/utils/data/ResultData.kt new file mode 100644 index 0000000000..c3cc1d68ea --- /dev/null +++ b/utbot-js/src/main/kotlin/utils/data/ResultData.kt @@ -0,0 +1,33 @@ +package utils.data + +import org.json.JSONObject + +/** + * Represents results after running function with arguments using Node.js + * @param rawString raw result as [String]. + * @param type result JavaScript type as [String]. + * @param index result index according to fuzzed parameters index. + * @param isNan true if the result is JavaScript NaN. + * @param isInf true if the result is JavaScript Infinity. + * @param isError true if the result contains JavaScript error text. + * @param specSign used for -Infinity and Infinity. + */ +data class ResultData( + val rawString: String, + val type: String = "string", + val index: Int, + val isNan: Boolean = false, + val isInf: Boolean = false, + val isError: Boolean = false, + val specSign: Byte = 1 +) { + constructor(json: JSONObject) : this( + rawString = if (json.has("result")) json.get("result").toString() else "undefined", + type = json.get("type").toString(), + index = json.getInt("index"), + isNan = json.optBoolean("is_nan", false), + isInf = json.optBoolean("is_inf", false), + isError = json.optBoolean("is_error", false), + specSign = json.optInt("spec_sign", 1).toByte() + ) +} diff --git a/utbot-junit-contest/README.md b/utbot-junit-contest/README.md new file mode 100644 index 0000000000..63717d7d24 --- /dev/null +++ b/utbot-junit-contest/README.md @@ -0,0 +1,47 @@ +# Contest estimator + +Contest estimator runs UnitTestBot on the provided projects and returns the generation statistics such as instruction coverage. + +There are two entry points: +- [ContestEstimator.kt][ep 1] is the main entry point. It runs UnitTestBot on the specified projects, calculates statistics for the target classes and projects, and outputs them to a console. +- [StatisticsMonitoring.kt][ep 2] is an additional entry point, which does the same as the previous one but can be configured from a file and dumps the resulting statistics to a file. +It is used to [monitor and chart][monitoring] statistics nightly. + + +[ep 1]: src/main/kotlin/org/utbot/contest/ContestEstimator.kt +[ep 2]: src/main/kotlin/org/utbot/monitoring/StatisticsMonitoring.kt +[monitoring]: ../docs/NightStatisticsMonitoring.md + +## Key functions + +| Function name | File name | Description | +|---------------|---------------------|----------------------------------------------------------------------------------------| +| runGeneration | Contest.kt | Runs UnitTestBot and manages its work | +| runEstimator | ContestEstimator.kt | Configures a project classpath, runs the main generation loop, and collects statistics | + + +## Projects + +The projects are provided to Contest estimator in advance. + +### Structure +All available projects are placed in the [resources][resources] folder, which contains: +- [projects][projects] consisting of the folders with the project JAR files in them. +- [classes][classes] consisting of the folders — each named after the project and containing the `list` file with the fully qualified class names. +It also may contain an `exceptions` file with the description of the expected exceptions, that utbot should find. +Description is presented in the format: `.: ...`. +For example, see this [file](src/main/resources/classes/codeforces/exceptions). + +### How to add a new project +You should add both the JAR files to the `projects` folder and the file with a list of classes to the `classes` folder. + +[resources]: src/main/resources +[projects]: src/main/resources/projects +[classes]: src/main/resources/classes + +## Statistics +Statistics are collected and memorized by the corresponding classes placed in [Statistics.kt][statistics]. +Then [monitoring][ep 2] dumps them using auxiliary classes that are defined in [MonitoringReport.kt][report] — they describe the format of output data. + +[statistics]: src/main/kotlin/org/utbot/contest/Statistics.kt +[report]: src/main/kotlin/org/utbot/monitoring/MonitoringReport.kt diff --git a/utbot-junit-contest/build.gradle b/utbot-junit-contest/build.gradle index 82dd1e1cb9..0b9d5c0d8e 100644 --- a/utbot-junit-contest/build.gradle +++ b/utbot-junit-contest/build.gradle @@ -1,72 +1,204 @@ -apply from: "${rootProject.projectDir}/gradle/include/jvm-project.gradle" - +plugins { + id 'org.jetbrains.kotlin.plugin.serialization' version '1.7.20' +} apply plugin: 'jacoco' +repositories { + mavenCentral() + maven { url 'https://jitpack.io' } +} + configurations { fetchInstrumentationJar + approximations + usvmApproximationsApi + usvmInstrumentationCollector + usvmInstrumentationRunner + generatedTestCompile } +def approximationsRepo = "com.github.UnitTestBot.java-stdlib-approximations" +def approximationsVersion = "bfce4eedde" + +def usvmRepo = "com.github.UnitTestBot.usvm" +def usvmVersion = "72924ad" + compileJava { options.compilerArgs << '-XDignore.symbol.file' } compileTestJava { options.fork = true - options.forkOptions.executable = 'javac' - options.compilerArgs << "-XDignore.symbol.file" + options.forkOptions.executable = "javac" + options.forkOptions.javaHome = file(System.getProperty("java.home")) + options.compilerArgs << "-XDignore.symbol.file=true" } +def testProjects = [ + 'build/output/test/antlr', + 'build/output/test/codeforces', + 'build/output/test/fastjson-1.2.50', + 'build/output/test/fescar', + 'build/output/test/guava', + 'build/output/test/guava-26.0', + 'build/output/test/guava-30.0', + 'build/output/test/pdfbox', + 'build/output/test/seata', + 'build/output/test/seata-core-0.5.0', + 'build/output/test/spoon', + 'build/output/test/spoon-core-7.0.0', + 'build/output/test/samples', +] + sourceSets { test { java { - srcDir('build/output/test/antlr') - srcDir('build/output/test/custom') - srcDir('build/output/test/guava') - srcDir('build/output/test/fescar') - srcDir('build/output/test/pdfbox') - srcDir('build/output/test/seata') - srcDir('build/output/test/spoon') - srcDir('build/output/test/samples') - srcDir('build/output/test/utbottest') + testProjects.forEach { + srcDir(it) + } } } } test { useJUnit() - // set heap size for the test JVM(s) - minHeapSize = "128m" - maxHeapSize = "16384m" - - // set JVM arguments for the test JVM(s) - jvmArgs '-XX:MaxPermSize=256m' - + if (JavaVersion.current() < JavaVersion.VERSION_1_9) { + jvmArgs = [] + } else { + jvmArgs = [ + "--add-opens", "java.base/java.util.concurrent.atomic=ALL-UNNAMED", + "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED", + "--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED", + "--add-opens", "java.base/java.util.concurrent.locks=ALL-UNNAMED", + "--add-opens", "java.base/java.text=ALL-UNNAMED", + "--add-opens", "java.base/java.time=ALL-UNNAMED", + "--add-opens", "java.base/java.io=ALL-UNNAMED", + "--add-opens", "java.base/java.nio=ALL-UNNAMED", + "--add-opens", "java.base/java.nio.file=ALL-UNNAMED", + "--add-opens", "java.base/java.net=ALL-UNNAMED", + "--add-opens", "java.base/sun.security.util=ALL-UNNAMED", + "--add-opens", "java.base/sun.reflect.generics.repository=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.util=ALL-UNNAMED", + "--add-opens", "java.base/sun.net.fs=ALL-UNNAMED", + "--add-opens", "java.base/java.security=ALL-UNNAMED", + "--add-opens", "java.base/java.lang.ref=ALL-UNNAMED", + "--add-opens", "java.base/java.math=ALL-UNNAMED", + "--add-opens", "java.base/java.util.stream=ALL-UNNAMED", + "--add-opens", "java.base/java.util=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED", + "--add-opens", "java.base/java.lang=ALL-UNNAMED", + "--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED", + "--add-opens", "java.base/sun.security.provider=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.event=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.jimage=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.jimage.decompressor=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.jmod=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.jtrfs=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.loader=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.logger=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.math=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.module=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.objectweb.asm.commons=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.objectweb.asm.signature=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.objectweb.asm.tree.analysis=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.objectweb.asm.util=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.xml.sax=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.org.xml.sax.helpers=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.perf=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.platform=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.ref=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.reflect=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.util=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.util.jar=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.util.xml=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.util.xml.impl=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.vm=ALL-UNNAMED", + "--add-opens", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED" + ] + } finalizedBy jacocoTestReport } jacocoTestReport { + afterEvaluate { + def r = testProjects.collect { + fileTree(dir: it) + }.findAll { + it.dir.exists() + } + sourceDirectories.setFrom(r.collect {files(it) }) + classDirectories.setFrom( + r.collect { + fileTree(dir: it.dir.toPath().parent.resolveSibling("unzipped").resolve(it.dir.name)) + }.findAll { + it.dir.exists() + }.collect { + files(it) + } + ) + } + reports { + csv.enabled = true html.enabled = true } } dependencies { - api project(":utbot-framework") - api project(":utbot-analytics") + implementation project(":utbot-framework") + implementation project(":utbot-analytics") + implementation project(":utbot-usvm") - implementation "com.github.UnitTestBot:soot:${soot_commit_hash}" + implementation("org.unittestbot.soot:soot-utbot-fork:${sootVersion}") { + exclude group:'com.google.guava', module:'guava' + } implementation group: 'org.apache.commons', name: 'commons-exec', version: '1.2' - implementation group: 'commons-io', name: 'commons-io', version: commons_io_version - implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version + implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlinLoggingVersion + implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4j2Version + implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j2Version implementation group: 'org.jsoup', name: 'jsoup', version: '1.6.2' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1' + implementation group: 'com.google.guava', name: 'guava', version: guavaVersion + // need for tests + implementation group: 'org.mockito', name: 'mockito-core', version: mockitoVersion + implementation group: 'org.mockito', name: 'mockito-inline', version: mockitoInlineVersion + implementation 'junit:junit:4.13.2' + + generatedTestCompile group: 'org.mockito', name: 'mockito-core', version: mockitoVersion + generatedTestCompile group: 'org.mockito', name: 'mockito-inline', version: mockitoInlineVersion + generatedTestCompile 'junit:junit:4.13.2' + + implementation "org.burningwave:core:12.62.7" + + implementation "$usvmRepo:usvm-core:$usvmVersion" + implementation "$usvmRepo:usvm-jvm:$usvmVersion" + implementation "$usvmRepo:usvm-jvm-api:$usvmVersion" + implementation "$usvmRepo:usvm-jvm-instrumentation:$usvmVersion" + implementation "$usvmRepo:usvm-jvm-instrumentation-collectors:$usvmVersion" + + implementation group: "org.jacodb", name: "jacodb-core", version: jacoDbVersion + implementation group: "org.jacodb", name: "jacodb-analysis", version: jacoDbVersion + implementation group: "org.jacodb", name: "jacodb-approximations", version: jacoDbVersion + + // TODO uvms-sbft-hack: UtBot has `fastutil:8.3.0` on the classpath that overrides classes from + // `fastutil-core:8.5.11` that USVM adds. Solution: bump `fastutil` version to `8.5.11` + runtimeOnly("it.unimi.dsi:fastutil:8.5.11") + testImplementation fileTree(dir: 'src/main/resources/projects/', include: '*/*.jar') testImplementation files('src/main/resources/evosuite/evosuite-1.2.0.jar') testImplementation files('src/main/resources/evosuite/evosuite-standalone-runtime-1.2.0.jar') - testImplementation group: 'org.mockito', name: 'mockito-core', version: '4.2.0' - testImplementation group: 'org.mockito', name: 'mockito-inline', version: '4.2.0' - testImplementation 'junit:junit:4.13.2' - fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration:'instrumentationArchive') + fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration: 'instrumentationArchive') + + //TODO: currently unused; their jars should be added to resources/lib if we want to switch to USVM instrumentation + approximations "$approximationsRepo:approximations:$approximationsVersion" + + usvmApproximationsApi "$usvmRepo:usvm-jvm-api:$usvmVersion" + usvmInstrumentationCollector "$usvmRepo:usvm-jvm-instrumentation-collectors:$usvmVersion" + usvmInstrumentationRunner "$usvmRepo:usvm-jvm-instrumentation:$usvmVersion" + usvmInstrumentationRunner "$usvmRepo:usvm-jvm-instrumentation-collectors:$usvmVersion" } processResources { @@ -75,7 +207,8 @@ processResources { } } -jar { dependsOn classes +jar { + dependsOn classes manifest { attributes 'Main-Class': 'org.utbot.contest.ContestKt' @@ -85,12 +218,139 @@ jar { dependsOn classes attributes 'JAR-Type': 'Fat JAR' } + processResources.exclude("classes/**") + processResources.exclude("projects/**") + processResources.exclude("evosuite/**") + version '1.0' + dependsOn configurations.runtimeClasspath from { - configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + sourceSets.main.output + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } } duplicatesStrategy = DuplicatesStrategy.EXCLUDE + zip64 = true + +} + +task monitoringJar(type: Jar) { + dependsOn classes + + archiveBaseName.set('monitoring') + archiveClassifier.set('') + archiveVersion.set('') + + dependsOn configurations.runtimeClasspath + from { + sourceSets.main.output + configurations.runtimeClasspath + .collect { it.isDirectory() ? it : zipTree(it) } + } + + manifest { + attributes 'Main-Class': 'org.utbot.monitoring.StatisticsMonitoringKt' + attributes 'Bundle-SymbolicName': 'org.utbot.monitoring' + attributes 'Bundle-Version': "${project.version}" + attributes 'Implementation-Title': 'UtBot Monitoring' + attributes 'JAR-Type': 'Fat JAR' + } -} \ No newline at end of file + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +// TODO usvm-sbft-saloed: replace with runner from usvm (unavailable due to huge jar size) +task usvmInstrumentationRunnerJar(type: Jar) { + archiveBaseName = "usvm-instrumentation-runner" + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + manifest { + attributes( + "Main-Class": "org.usvm.instrumentation.rd.InstrumentedProcessKt", + "Premain-Class": "org.usvm.instrumentation.agent.Agent", + "Can-Retransform-Classes": "true", + "Can-Redefine-Classes": "true" + ) + } + + from { + configurations.usvmInstrumentationRunner.collect { + it.isDirectory() ? it : zipTree(it) + } + } +} + +task run(type: JavaExec) { + mainClass.set("org.utbot.contest.ContestEstimatorKt") + classpath = sourceSets.main.runtimeClasspath + workingDir = project.rootProject.projectDir + + def usvmApproximationJarPath = configurations.approximations.resolvedConfiguration.files.find() + def usvmApproximationApiJarPath = configurations.usvmApproximationsApi.resolvedConfiguration.files.find() + environment "usvm.jvm.api.jar.path", usvmApproximationApiJarPath.absolutePath + environment "usvm.jvm.approximations.jar.path", usvmApproximationJarPath.absolutePath + systemProperty("org.jacodb.impl.storage.defaultBatchSize", 2000) + + def usvmInstrumentationCollectorJarPath = configurations.usvmInstrumentationCollector.resolvedConfiguration.files.find() + environment "usvm-jvm-collectors-jar", usvmInstrumentationCollectorJarPath.absolutePath + + dependsOn(usvmInstrumentationRunnerJar) + environment "usvm-jvm-instrumentation-jar", usvmInstrumentationRunnerJar.outputs.files.singleFile + + // "JAVA_HOME" specifies Java path for instrumented process and JacoDB, + // while `System.getProperty('java.home')` is Java used by this process. + // We want both of them to be the same and we also need JDK (not JRE), since we use `javac` to compile tests. + def javaHome = System.getProperty('java.home') + def jreSuffix = "${File.separatorChar}jre" + if (javaHome.endsWith(jreSuffix)) javaHome = javaHome.dropRight(jreSuffix.length()) + environment "JAVA_HOME", javaHome +} + +tasks.register("generateRuntool") { + dependsOn(jar, usvmInstrumentationRunnerJar) + + doLast { + def distDir = buildDir.toPath().resolve("utbot-usvm-runtool").toFile() + copy { + from jar.outputs + into distDir + rename { "utbot-usvm-tool.jar" } + } + + copy { + from configurations.usvmApproximationsApi.resolvedConfiguration.files.find() + into distDir + rename { "usvm-api.jar" } + } + + copy { + from configurations.approximations.resolvedConfiguration.files.find() + into distDir + rename { "usvm-approximations.jar" } + } + + copy { + from configurations.usvmInstrumentationCollector.resolvedConfiguration.files.find() + into distDir + rename { "usvm-jvm-collectors.jar" } + } + + copy { + from usvmInstrumentationRunnerJar.outputs + into distDir + rename { "usvm-jvm-instrumentation.jar" } + } + + copy { + from projectDir.toPath().resolve("usvm-runtool") + into distDir + rename { "runtool" } + } + + def libsDir = distDir.toPath().resolve("lib").toFile() + configurations.generatedTestCompile.resolvedConfiguration.files.forEach { f -> + copy { + from f + into libsDir + } + } + } +} diff --git a/utbot-junit-contest/runtool b/utbot-junit-contest/runtool index 43e3becd6c..b191e8a6fc 100644 --- a/utbot-junit-contest/runtool +++ b/utbot-junit-contest/runtool @@ -1,10 +1,10 @@ #!/bin/bash # switch to environment JVM as needed -JAVA_HOME=/usr +JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 -APACHE_EXECS_LIB=lib/org/apache/commons/commons-exec/1.2/commons-exec-1.2.jar -TOOL=lib/runtool-1.0.0.jar +TOOL=lib/utbot-junit-contest-1.0.jar +export UTBOT_EXTRA_PARAMS=-Xmx4g export JAVA_HOME=$JAVA_HOME -$JAVA_HOME/bin/java -cp $TOOL:$APACHE_EXECS_LIB sbst.runtool.Main +$JAVA_HOME/bin/java -cp $TOOL sbst.runtool.Main diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ClassUnderTest.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ClassUnderTest.kt index 5c77736dc3..b7252e493a 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ClassUnderTest.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ClassUnderTest.kt @@ -1,8 +1,8 @@ package org.utbot.contest import org.utbot.common.FileUtil -import org.utbot.framework.codegen.model.util.createTestClassName import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.nameWithEnclosingClassesAsContigousString import org.utbot.framework.plugin.api.util.utContext import java.io.File import java.nio.file.Paths @@ -17,28 +17,28 @@ class ClassUnderTest( get() = classId.name val classLoader: ClassLoader get() = utContext.classLoader - val kotlinClass - get() = classLoader.loadClass(fqn).kotlin + val clazz + get() = classLoader.loadClass(fqn) /** * Directory where this .class file is located without package folder structure * E.g. for gradle projects it's `project/build/classes`. */ val classfileDir: File by lazy { - ( - FileUtil.locateClassPath(kotlinClass) - ?: classfileDirSecondaryLocation - ?: FileUtil.isolateClassFiles(kotlinClass) - ).absoluteFile + (FileUtil.locateClassPath(clazz) + ?: classfileDirSecondaryLocation + ?: FileUtil.isolateClassFiles(clazz) + ).absoluteFile } // val classpathDir : File get() = FileUtil.locateClassPath(kotlinClass)?.absoluteFile !! - val packageName: String get() = fqn.substringBeforeLast('.', "") - val simpleName: String get() = createTestClassName(fqn) - + /** + * These properties should be obtained only with utContext set + */ + private val packageName: String get() = classId.packageName + val simpleName: String get() = classId.nameWithEnclosingClassesAsContigousString val testClassSimpleName: String get() = simpleName + "Test" - val generatedTestFile: File get() = Paths.get( generatedTestsSourcesDir.canonicalPath, @@ -47,12 +47,10 @@ class ClassUnderTest( override fun toString(): String { return "ClassUnderTest[ FQN: $fqn" + - "\n classfileDir: $classfileDir" + - "\n testClassSimpleName: $testClassSimpleName" + - "\n generatedTestFile: $generatedTestFile" + - "\n generatedTestsSourcesDir: $generatedTestsSourcesDir" + - "\n]" + "\n classfileDir: $classfileDir" + + "\n testClassSimpleName: $testClassSimpleName" + + "\n generatedTestFile: $generatedTestFile" + + "\n generatedTestsSourcesDir: $generatedTestsSourcesDir" + + "\n]" } - - } \ No newline at end of file diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt index bc7b99e9bb..9382af1ece 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt @@ -1,35 +1,27 @@ package org.utbot.contest -import org.utbot.common.bracket +import mu.KotlinLogging +import org.objectweb.asm.Type +import org.utbot.common.FileUtil +import org.utbot.common.measureTime +import org.utbot.common.filterWhen import org.utbot.common.info +import org.utbot.common.isAbstract import org.utbot.engine.EngineController -import org.utbot.engine.isConstructor import org.utbot.framework.TestSelectionStrategyType import org.utbot.framework.UtSettings -import org.utbot.framework.codegen.ForceStaticMocking -import org.utbot.framework.codegen.StaticsMocking -import org.utbot.framework.codegen.junitByVersion -import org.utbot.framework.codegen.model.ModelBasedTestCodeGenerator -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockFramework -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.UtBotTestCaseGenerator -import org.utbot.framework.plugin.api.UtError -import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtMethod -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.framework.plugin.api.UtValueTestCase import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.executableId import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.utContext import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.framework.plugin.services.JdkInfoService +import org.utbot.framework.util.isKnownImplicitlyDeclaredMethod +import org.utbot.fuzzer.UtFuzzedExecution import org.utbot.instrumentation.ConcreteExecutor import org.utbot.instrumentation.ConcreteExecutorPool import org.utbot.instrumentation.Settings -import org.utbot.instrumentation.execute -import org.utbot.instrumentation.instrumentation.coverage.CoverageInstrumentation -import org.utbot.instrumentation.util.StaticEnvironment import org.utbot.instrumentation.warmup.Warmup import java.io.File import java.lang.reflect.Method @@ -41,14 +33,7 @@ import kotlin.concurrent.thread import kotlin.math.max import kotlin.math.min import kotlin.reflect.KCallable -import kotlin.reflect.KClass -import kotlin.reflect.KFunction -import kotlin.reflect.KProperty -import kotlin.reflect.full.declaredMembers import kotlin.reflect.jvm.isAccessible -import kotlin.reflect.jvm.javaConstructor -import kotlin.reflect.jvm.javaGetter -import kotlin.reflect.jvm.javaMethod import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope @@ -56,16 +41,25 @@ import kotlinx.coroutines.ObsoleteCoroutinesApi import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.isActive import kotlinx.coroutines.job import kotlinx.coroutines.launch import kotlinx.coroutines.newSingleThreadContext import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.coroutines.yield -import mu.KotlinLogging -import org.apache.commons.io.FileUtils +import org.utbot.contest.usvm.createJcContainer +import org.utbot.contest.usvm.runUsvmGeneration +import org.utbot.framework.SummariesGenerationType +import org.utbot.framework.codegen.domain.* +import org.utbot.framework.codegen.generator.CodeGenerator +import org.utbot.framework.codegen.generator.CodeGeneratorParams +import org.utbot.framework.codegen.services.language.CgLanguageAssistant +import org.utbot.framework.minimization.minimizeExecutions +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.isSynthetic +import org.utbot.framework.util.jimpleBody +import org.utbot.summary.summarizeAll +import org.utbot.usvm.jc.JcContainer internal const val junitVersion = 4 private val logger = KotlinLogging.logger {} @@ -116,19 +110,41 @@ fun main(args: Array) { println("Started UtBot Contest, classfileDir = $classfileDir, classpathString=$classpathString, outputDir=$outputDir, mocks=$mockStrategyApi") + // TODO: tmp hack to retrieve tmp dir wrt contest rules + val tmpDir = outputDir.resolveSibling("data") + val cpEntries = classpathString.split(File.pathSeparator).map { File(it) } - val classLoader = URLClassLoader(cpEntries.map { it.toUrl() }.toTypedArray()) + val classLoader = URLClassLoader(cpEntries.map { it.toUrl() }.toTypedArray(), null) val context = UtContext(classLoader) + val testCompileCp = System.getenv("UTBOT_CONTEST_TEST_COMPILE_CP") + var classpathStringForTestCompile = classpathString + if (!testCompileCp.isNullOrBlank()) { + classpathStringForTestCompile = "$classpathStringForTestCompile${File.pathSeparator}$testCompileCp" + } withUtContext(context) { + when (mainTool) { + Tool.USVM -> { + // Initialize the JacoDB and start executor before contest is started. + // This saves the time budget for real work instead of initialization. + runBlocking { + createJcContainer( + tmpDir = tmpDir, + classpathFiles = classpathString.split(File.pathSeparator).map { File(it) } + ).runner.ensureRunnerAlive() + } + } + Tool.UtBot -> { + // Initialize the soot before contest is started. + // This saves the time budget for real work instead of soot initialization. + TestCaseGenerator(listOf(classfileDir), classpathString, dependencyPath, JdkInfoService.provide()) + } + } - //soot initialization - UtBotTestCaseGenerator.init(classfileDir, classpathString, dependencyPath) - - logger.info().bracket("warmup: kotlin reflection :: init") { - prepareClass(ConcreteExecutorPool::class, "") - prepareClass(Warmup::class, "") + logger.info().measureTime({ "warmup: kotlin reflection :: init" }) { + prepareClass(ConcreteExecutorPool::class.java, "") + prepareClass(Warmup::class.java, "") } println("${ContestMessage.INIT}") @@ -147,43 +163,92 @@ fun main(args: Array) { val timeBudgetSec = cmd[2].toLong() val cut = ClassUnderTest(classLoader.loadClass(classUnderTestName).id, outputDir, classfileDir.toFile()) - runGeneration(cut, timeBudgetSec, classpathString, runFromEstimator = false, methodNameFilter = null) + when (mainTool) { + Tool.USVM -> runUsvmGeneration( + project = "Contest", + cut, + timeBudgetSec, + classpathString, + runFromEstimator = false, + expectedExceptions = ExpectedExceptionsForClass(), + tmpDir = tmpDir, + methodNameFilter = null + ) + Tool.UtBot -> runGeneration( + project = "Contest", + cut, + timeBudgetSec, + fuzzingRatio = 0.1, + classpathString, + runFromEstimator = false, + expectedExceptions = ExpectedExceptionsForClass(), + methodNameFilter = null + ) + } + + val compiledClassFileDir = File(outputDir.absolutePath, "compiledClassFiles") + compiledClassFileDir.mkdirs() + compileClassAndRemoveUncompilableTests( + testDir = compiledClassFileDir.absolutePath, + classPath = classpathStringForTestCompile, + testClass = cut.generatedTestFile.absolutePath + ) + compiledClassFileDir.deleteRecursively() + println("${ContestMessage.READY}") } } + ConcreteExecutor.defaultPool.close() + JcContainer.close() } fun setOptions() { Settings.defaultConcreteExecutorPoolSize = 1 UtSettings.useFuzzing = true UtSettings.classfilesCanChange = false - UtSettings.useAssembleModelGenerator = false - UtSettings.enableMachineLearningModule = false + // We need to use assemble model generator to increase readability + UtSettings.useAssembleModelGenerator = true + UtSettings.summaryGenerationType = SummariesGenerationType.LIGHT + when(mainTool){ + Tool.USVM -> { + UtSettings.enableTestNamesGeneration = true + UtSettings.enableDisplayNameGeneration = false + UtSettings.enableJavaDocGeneration = true + } + Tool.UtBot -> { + UtSettings.enableDisplayNameGeneration = true + } + } + UtSettings.preferredCexOption = false UtSettings.warmupConcreteExecution = true UtSettings.testMinimizationStrategyType = TestSelectionStrategyType.COVERAGE_STRATEGY + UtSettings.maxUnknownCoverageExecutionsPerMethodPerResultType = 10 UtSettings.ignoreStringLiterals = true + UtSettings.maximizeCoverageUsingReflection = true + UtSettings.useSandbox = false + UtSettings.addTestMethodMarkers = true } @ObsoleteCoroutinesApi @SuppressWarnings fun runGeneration( + project: String, cut: ClassUnderTest, timeLimitSec: Long, + fuzzingRatio: Double, classpathString: String, runFromEstimator: Boolean, + expectedExceptions: ExpectedExceptionsForClass, methodNameFilter: String? = null // For debug purposes you can specify method name ): StatsForClass = runBlocking { - - - - val testCases: MutableMap = mutableMapOf() + val testsByMethod: MutableMap> = mutableMapOf() val currentContext = utContext val timeBudgetMs = timeLimitSec * 1000 - val generationTimeout: Long = timeBudgetMs - timeBudgetMs*15/100 // 4000 ms for terminate all activities and finalize code in file + val generationTimeout: Long = timeBudgetMs - timeBudgetMs * 15 / 100 // 4000 ms for terminate all activities and finalize code in file logger.debug { "-----------------------------------------------------------------------------" } logger.info( @@ -194,14 +259,13 @@ fun runGeneration( if (runFromEstimator) { setOptions() //will not be executed in real contest - logger.info().bracket("warmup: 1st optional soot initialization and executor warmup (not to be counted in time budget)") { - UtBotTestCaseGenerator.init(cut.classfileDir.toPath(), classpathString, dependencyPath) + logger.info().measureTime({ "warmup: 1st optional soot initialization and executor warmup (not to be counted in time budget)" }) { + TestCaseGenerator(listOf(cut.classfileDir.toPath()), classpathString, dependencyPath, JdkInfoService.provide(), forceSootReload = false) } - logger.info().bracket("warmup (first): kotlin reflection :: init") { - prepareClass(ConcreteExecutorPool::class, "") - prepareClass(Warmup::class, "") + logger.info().measureTime({ "warmup (first): kotlin reflection :: init" }) { + prepareClass(ConcreteExecutorPool::class.java, "") + prepareClass(Warmup::class.java, "") } - } //remaining budget @@ -214,38 +278,25 @@ fun runGeneration( logger.error("Seems like classloader for cut not valid (maybe it was backported to system): ${cut.classLoader}") } - val statsForClass = StatsForClass() + val statsForClass = StatsForClass(project, cut.fqn) - // TODO: this generates not only test method, but also imports, etc - // TODO: replace it by a more lightweight code generator - val codeGenerator = ModelBasedTestCodeGenerator().also { - it.init( - cut.classId.jClass, + val codeGenerator = CodeGenerator( + CodeGeneratorParams( + cut.classId, + projectType = ProjectType.PureJvm, testFramework = junitByVersion(junitVersion), - mockFramework = MockFramework.MOCKITO, staticsMocking = staticsMocking, forceStaticMocking = forceStaticMocking, - generateWarningsForStaticMocking = false + generateWarningsForStaticMocking = false, + cgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage(CodegenLanguage.defaultItem), + runtimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.PASS, ) - } - - // Doesn't work -/* val concreteExecutorForCoverage = - if (estimateCoverage) - ConcreteExecutor( - instrumentation = CoverageInstrumentation, - pathsToUserClasses = cut.classfileDir.toPath().toString(), - pathsToDependencyClasses = System.getProperty("java.class.path") - ).apply { - setKryoClassLoader(utContext.classLoader) - } - else null*/ - + ) - logger.info().bracket("class ${cut.fqn}", { statsForClass }) { + logger.info().measureTime({ "class ${cut.fqn}" }, { statsForClass }) { - val filteredMethods = logger.info().bracket("preparation class ${cut.kotlinClass}: kotlin reflection :: run") { - prepareClass(cut.kotlinClass, methodNameFilter) + val filteredMethods = logger.info().measureTime({ "preparation class ${cut.clazz}: kotlin reflection :: run" }) { + prepareClass(cut.clazz, methodNameFilter) } statsForClass.methodsCount = filteredMethods.size @@ -253,10 +304,10 @@ fun runGeneration( // nothing to process further if (filteredMethods.isEmpty()) return@runBlocking statsForClass - val generator = UtBotTestCaseGenerator - logger.info().bracket("2nd optional soot initialization") { - generator.init(cut.classfileDir.toPath(), classpathString, dependencyPath) - } + val testCaseGenerator = + logger.info().measureTime({ "2nd optional soot initialization" }) { + TestCaseGenerator(listOf(cut.classfileDir.toPath()), classpathString, dependencyPath, JdkInfoService.provide(), forceSootReload = false) + } val engineJob = CoroutineScope(SupervisorJob() + newSingleThreadContext("SymbolicExecution") + currentContext ).launch { @@ -274,7 +325,10 @@ fun runGeneration( val methodJob = currentCoroutineContext().job logger.debug { " ... " } - val statsForMethod = StatsForMethod("${method.clazz.simpleName}#${method.callable.name}") + val statsForMethod = StatsForMethod( + "${method.classId.simpleName}#${method.name}", + expectedExceptions.getForMethod(method.name).exceptionNames + ) statsForClass.statsForMethods.add(statsForMethod) @@ -303,9 +357,11 @@ fun runGeneration( val budgetForSymbolicExecution = max(0, budgetForMethod - budgetForLastSolverRequestAndConcreteExecutionRemainingStates) + UtSettings.utBotGenerationTimeoutInMillis = budgetForMethod + UtSettings.fuzzingTimeoutInMillis = (budgetForMethod * fuzzingRatio).toLong() //start controller that will activate symbolic execution - GlobalScope.launch { + GlobalScope.launch(currentContext) { delay(budgetForSymbolicExecution) if (methodJob.isActive) { @@ -321,8 +377,8 @@ fun runGeneration( } - var testCounter = 0 - logger.info().bracket("method $method", { statsForMethod }) { + var testsCounter = 0 + logger.info().measureTime({ "method $method" }, { statsForMethod }) { logger.info { " -- Remaining time budget: $remainingBudget ms, " + "#remaining_methods: $remainingMethodsCount, " + @@ -334,18 +390,28 @@ fun runGeneration( } - generator.generateAsync(controller, method, mockStrategyApi) + testCaseGenerator.generateAsync(controller, method, mockStrategyApi) .collect { result -> when (result) { is UtExecution -> { try { - val testCase = UtTestCase(method, listOf(result)) - val newName = testMethodName(testCase.method.toString(), ++testCounter) - logger.debug { "--new testCase collected, to generate: $newName" } - statsForMethod.testcasesGeneratedCount++ - - testCases[newName] = testCase - + val testMethodName = testMethodName(method.toString(), ++testsCounter) + val className = Type.getInternalName(method.classId.jClass) + logger.debug { "--new testCase collected, to generate: $testMethodName" } + statsForMethod.testsGeneratedCount++ + result.result.exceptionOrNull()?.let { exception -> + statsForMethod.detectedExceptionFqns += exception::class.java.name + } + result.coverage?.let { + statsForClass.updateCoverage( + newCoverage = it, + isNewClass = !statsForClass.testedClassNames.contains(className), + fromFuzzing = result is UtFuzzedExecution + ) + } + statsForClass.testedClassNames.add(className) + + testsByMethod.getOrPut(method) { mutableListOf() } += result } catch (e: Throwable) { //Here we need isolation logger.error(e) { "Code generation failed" } @@ -362,7 +428,7 @@ fun runGeneration( //hack if (statsForMethod.isSuspicious && (ConcreteExecutor.lastSendTimeMs - ConcreteExecutor.lastReceiveTimeMs) > 5000) { - logger.error { "HEURISTICS: close child process, because it haven't responded for long time: ${ConcreteExecutor.lastSendTimeMs - ConcreteExecutor.lastReceiveTimeMs}" } + logger.error { "HEURISTICS: close instrumented process, because it haven't responded for long time: ${ConcreteExecutor.lastSendTimeMs - ConcreteExecutor.lastReceiveTimeMs}" } ConcreteExecutor.defaultPool.close() } } @@ -370,15 +436,18 @@ fun runGeneration( controller.job = job //don't start other methods while last method still in progress - while (job.isActive) - yield() + try { + job.join() + } catch (t: Throwable) { + logger.error(t) { "Internal job error" } + } remainingMethodsCount-- } } - val cancellator = GlobalScope.launch { + val cancellator = GlobalScope.launch(currentContext) { delay(remainingBudget()) if (engineJob.isActive) { logger.warn { "Cancelling job because timeout $generationTimeout ms elapsed (real cancellation can take time)" } @@ -390,100 +459,60 @@ fun runGeneration( withTimeoutOrNull(remainingBudget() + 200 /* Give job some time to finish gracefully */) { try { engineJob.join() - } catch (e: CancellationException) { + } catch (_: CancellationException) { } catch (e: Exception) { // need isolation because we want to write tests for class in any case logger.error(e) { "Error in engine invocation on class [${cut.fqn}]" } } } cancellator.cancel() - logger.info().bracket("Flushing tests for [${cut.simpleName}] on disk") { - writeTestClass(cut, codeGenerator.generateAsString(testCases.values)) - } - //write classes - } - + val testSets = testsByMethod.map { (method, executions) -> + UtMethodTestSet(method, minimizeExecutions(executions), jimpleBody(method)) + }.summarizeAll(cut.classfileDir.toPath(), sourceFile = null) - //Optional step that doesn't run in actual contest - // Doesn't work -/* - if (concreteExecutorForCoverage != null) { - logger.info().bracket("Estimating coverage") { - require(estimateCoverage) - try { - for ((name, testCase) in testCases) { - logger.debug {"-> estimate for $name" } - concreteExecutorForCoverage.executeTestCase(testCase.toValueTestCase()) - } - statsForClass.coverage = concreteExecutorForCoverage.collectCoverage(cut.classId.jClass) - } catch (e: Throwable) { - logger.error(e) { "Error during coverage estimation" } - } + logger.info().measureTime({ "Flushing tests for [${cut.simpleName}] on disk" }) { + writeTestClass(cut, codeGenerator.generateAsString(testSets)) } + //write classes } -*/ - statsForClass } -private fun ConcreteExecutor, CoverageInstrumentation>.executeTestCase(testCase: UtValueTestCase<*>) { - testCase.executions.forEach { - val method = testCase.method.callable - for (execution in testCase.executions) { - val args = (listOfNotNull(execution.stateBefore.caller) + execution.stateBefore.params) - .map { it.value }.toMutableList() - val staticEnvironment = StaticEnvironment( - execution.stateBefore.statics.map { it.key to it.value.value } - ) - this.execute(method, *args.toTypedArray(), parameters = staticEnvironment) - } - } - -} - -private fun prepareClass(kotlinClass: KClass<*>, methodNameFilter: String?): List> { - //1. all methods and properties from cut - val kotlin2java = kotlinClass.declaredMembers +fun prepareClass(javaClazz: Class<*>, methodNameFilter: String?): List { + //1. all methods from cut + val methods = javaClazz.declaredMethods .filterNot { it.isAbstract } - .map { - it to when (it) { - is KFunction -> it.javaMethod - is KProperty -> it.javaGetter - else -> null - } - } + .filterNotNull() //2. all constructors from cut - val kotlin2javaCtors = - if (kotlinClass.isAbstract) emptyList() else kotlinClass.constructors.map { it to it.javaConstructor } + val constructors = + if (javaClazz.isAbstract || javaClazz.isEnum) emptyList() else javaClazz.declaredConstructors.filterNotNull() - //3. Now join properties, methods and constructors together - val methodsToGenerate = kotlin2java - //filter out abstract and native methods - .filter { (_, javaMethod) -> javaMethod?.isVisibleFromGeneratedTest == true } - //join - .union(kotlin2javaCtors) + //3. Now join methods and constructors together + val methodsToGenerate = methods.filter { it.isVisibleFromGeneratedTest } + constructors val classFilteredMethods = methodsToGenerate - .map { UtMethod(it.first, kotlinClass) } - .filter { methodNameFilter?.equals(it.callable.name) ?: true } - .filterNot { - it.isConstructor && (it.clazz.isAbstract || it.clazz.java.isEnum) + .map { it.executableId } + .filter { methodNameFilter?.equals(it.name) ?: true } + .filterWhen(UtSettings.skipTestGenerationForSyntheticAndImplicitlyDeclaredMethods) { + !it.isSynthetic && !it.isKnownImplicitlyDeclaredMethod } + .toList() - return if (kotlinClass.nestedClasses.isEmpty()) { + + return if (javaClazz.declaredClasses.isEmpty()) { classFilteredMethods } else { - val nestedFilteredMethods = kotlinClass.nestedClasses.flatMap { prepareClass(it, methodNameFilter) } + val nestedFilteredMethods = javaClazz.declaredClasses.flatMap { prepareClass(it, methodNameFilter) } classFilteredMethods + nestedFilteredMethods } } -fun writeTestClass(cut: ClassUnderTest, testCasesAsString: String) { - logger.info { "File size for ${cut.testClassSimpleName}: ${FileUtils.byteCountToDisplaySize(testCasesAsString.length.toLong())}" } +fun writeTestClass(cut: ClassUnderTest, testSetsAsString: String) { + logger.info { "File size for ${cut.testClassSimpleName}: ${FileUtil.byteCountToDisplaySize(testSetsAsString.length.toLong())}" } cut.generatedTestFile.parentFile.mkdirs() - cut.generatedTestFile.writeText(testCasesAsString, charset) + cut.generatedTestFile.writeText(testSetsAsString, charset) } private inline fun KCallable<*>.withAccessibility(block: () -> R): R { @@ -526,4 +555,29 @@ internal fun testMethodName(name: String, num: Int): String = "test${name.capita internal val Method.isVisibleFromGeneratedTest: Boolean get() = (this.modifiers and Modifier.ABSTRACT) == 0 - && (this.modifiers and Modifier.NATIVE) == 0 \ No newline at end of file + && (this.modifiers and Modifier.NATIVE) == 0 + +fun StatsForClass.updateCoverage(newCoverage: Coverage, isNewClass: Boolean, fromFuzzing: Boolean) { + coverage.update(newCoverage, isNewClass) + // other coverage type updates by empty coverage to respect new class + val emptyCoverage = newCoverage.copy( + coveredInstructions = emptyList() + ) + if (fromFuzzing) { + fuzzedCoverage to concolicCoverage + } else { + concolicCoverage to fuzzedCoverage + }.let { (targetSource, otherSource) -> + targetSource.update(newCoverage, isNewClass) + otherSource.update(emptyCoverage, isNewClass) + } +} + +private fun CoverageInstructionsSet.update(newCoverage: Coverage, isNewClass: Boolean) { + if (isNewClass) { + newCoverage.instructionsCount?.let { + totalInstructions += it + } + } + coveredInstructions.addAll(newCoverage.coveredInstructions) +} diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt index 94e67f0dce..6f00634101 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt @@ -1,12 +1,22 @@ package org.utbot.contest +import kotlinx.coroutines.ObsoleteCoroutinesApi +import java.io.File +import java.io.FileInputStream +import java.net.URLClassLoader +import java.nio.file.Files +import java.nio.file.Paths +import java.nio.file.StandardCopyOption +import java.util.concurrent.CancellationException +import java.util.concurrent.TimeUnit +import java.util.zip.ZipInputStream import mu.KotlinLogging import org.utbot.analytics.EngineAnalyticsContext import org.utbot.analytics.Predictors import org.utbot.common.FileUtil -import org.utbot.common.bracket +import org.utbot.common.measureTime +import org.utbot.common.getPid import org.utbot.common.info -import org.utbot.common.pid import org.utbot.contest.Paths.classesLists import org.utbot.contest.Paths.dependenciesJars import org.utbot.contest.Paths.evosuiteGeneratedTestsFile @@ -16,28 +26,19 @@ import org.utbot.contest.Paths.evosuiteReportFile import org.utbot.contest.Paths.jarsDir import org.utbot.contest.Paths.moduleTestDir import org.utbot.contest.Paths.outputDir +import org.utbot.contest.usvm.runUsvmGeneration import org.utbot.features.FeatureExtractorFactoryImpl import org.utbot.features.FeatureProcessorWithStatesRepetitionFactory -import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.PathSelectorType +import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.renderer.CgAbstractRenderer import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.framework.plugin.services.JdkInfoService import org.utbot.instrumentation.ConcreteExecutor -import java.io.File -import java.io.FileInputStream -import java.net.URLClassLoader -import java.nio.file.Files -import java.nio.file.Paths -import java.nio.file.StandardCopyOption -import java.util.concurrent.CancellationException -import java.util.concurrent.TimeUnit -import java.util.zip.ZipInputStream -import kotlin.concurrent.thread +import org.utbot.predictors.MLPredictorFactoryImpl +import org.utbot.usvm.jc.JcContainer import kotlin.math.min -import kotlin.system.exitProcess -import org.utbot.framework.JdkPathService -import org.utbot.predictors.StateRewardPredictorFactoryImpl -import org.utbot.framework.PathSelectorType -import org.utbot.framework.UtSettings private val logger = KotlinLogging.logger {} @@ -49,6 +50,108 @@ private val javaHome = System.getenv("JAVA_HOME") private val javacCmd = "$javaHome/bin/javac" private val javaCmd = "$javaHome/bin/java" +val mainTool: Tool.UtBotBasedTool = Tool.UtBot + +// first attempt is for --add-opens +// second attempt is for removing test methods that still don't compile +// last attempt is for checking if final result compiles +private const val compileAttempts = 3 + +private data class UnnamedPackageInfo(val pack: String, val module: String) + +private fun findAllNotExportedPackages(report: String): List { + val regex = """package ([\d\w.]+) is declared in module ([\d\w.]+), which does not export it to the unnamed module""".toRegex() + return regex.findAll(report).map { + val pack = it.groupValues[1] + val module = it.groupValues[2] + UnnamedPackageInfo(pack, module) + }.toList().distinct() +} + +private fun findErrorLines(report: String): List { + // (\d+) is line number + // (:(\d+)) is optional column number + val regex = """\.java:(\d+)(:(\d+))?: error""".toRegex() + return regex.findAll(report).map { + it.groupValues[1].toInt() - 1 + }.toList().distinct() +} + +fun compileClassAndRemoveUncompilableTests(testDir: String, classPath: String, testClass: String): Int = try { + val exports = mutableSetOf() + var exitCode = 0 + + repeat(compileAttempts) { attemptNumber -> + val cmd = arrayOf( + javacCmd, + *exports.flatMap { + listOf("--add-exports", "${it.module}/${it.pack}=ALL-UNNAMED") + }.toTypedArray(), + "-d", testDir, + "-cp", classPath, + "-nowarn", + "-XDignore.symbol.file", + testClass + ) + logger.debug { "Compile attempt ${attemptNumber + 1}" } + + logger.trace { cmd.toText() } + + val process = Runtime.getRuntime().exec(cmd) + + val errors = process.errorStream.reader().buffered().readText() + + exitCode = process.waitFor() + if (exitCode == 0) { + return 0 + } else { + if (errors.isNotEmpty()) + logger.error { "Compilation errors: $errors" } + exports += findAllNotExportedPackages(errors) + if (attemptNumber >= 1) { + val testFile = File(testClass) + val testClassLines = testFile.readLines() + val testStarts = testClassLines.withIndex() + .filter { it.value.contains(CgAbstractRenderer.TEST_METHOD_START_MARKER) } + .map { it.index } + val testEnds = testClassLines.withIndex() + .filter { it.value.contains(CgAbstractRenderer.TEST_METHOD_END_MARKER) } + .map { it.index } + val errorLines = findErrorLines(errors) + val errorRanges = errorLines.map { errorLine -> + // if error is outside test method, we can't fix that by removing test methods + val testStart = testStarts.filter { it <= errorLine }.maxOrNull() ?: return exitCode + val testEnd = testEnds.filter { it >= errorLine }.minOrNull() ?: return exitCode + testStart..testEnd + }.distinct() + if (errorRanges.size > testStarts.size / 5.0) { + logger.error { "Over 20% of test are uncompilable" } + logger.error { "Speculating that something is wrong with compilation settings, keeping all tests" } + return exitCode + } + val linesToRemove = mutableSetOf() + errorRanges.forEach { linesToRemove.addAll(it) } + val removedText = testClassLines.withIndex() + .filter { it.index in linesToRemove } + .joinToString("\n") { "${it.index}: ${it.value}" } + logger.info { "Removed uncompilable tests:\n$removedText" } + testFile.writeText(testClassLines.filterIndexed { i, _ -> i !in linesToRemove }.joinToString("\n")) + } + } + } + + exitCode +} catch (e: Throwable) { + logger.error(e) { "compileClass failed" } + 1 +} finally { + val testFile = File(testClass) + testFile.writeText(testFile.readLines().filter { + !it.contains(CgAbstractRenderer.TEST_METHOD_START_MARKER) + && !it.contains(CgAbstractRenderer.TEST_METHOD_END_MARKER) + }.joinToString("\n")) +} + fun Array.toText() = joinToString(separator = ",") @Suppress("unused") @@ -77,65 +180,65 @@ object Paths { } @Suppress("unused") -enum class Tool { - UtBot { - @Suppress("EXPERIMENTAL_API_USAGE") +interface Tool { + sealed class UtBotBasedTool : Tool { + abstract fun runGeneration( + project: ProjectToEstimate, + cut: ClassUnderTest, + timeLimit: Long, + fuzzingRatio: Double, + methodNameFilter: String?, + statsForProject: StatsForProject, + compiledTestDir: File, + classFqn: String, + expectedExceptions: ExpectedExceptionsForClass + ) : StatsForClass + override fun run( project: ProjectToEstimate, cut: ClassUnderTest, timeLimit: Long, + fuzzingRatio: Double, methodNameFilter: String?, - globalStats: GlobalStats, + statsForProject: StatsForProject, compiledTestDir: File, - classFqn: String - ) { + classFqn: String, + expectedExceptions: ExpectedExceptionsForClass + ) = withUtContext(ContextManager.createNewContext(project.classloader)) { val classStats: StatsForClass = try { - withUtContext(UtContext(project.classloader)) { - runGeneration( - cut, - timeLimit, - project.sootClasspathString, - runFromEstimator = true, - methodNameFilter - ) - } + runGeneration( + project, + cut, + timeLimit, + fuzzingRatio, + methodNameFilter, + statsForProject, + compiledTestDir, + classFqn, + expectedExceptions + ) } catch (e: CancellationException) { logger.info { "[$classFqn] finished with CancellationException" } return } catch (e: Throwable) { + logger.error(e) { "ISOLATION: $e" } logger.info { "ISOLATION: $e" } logger.info { "continue without compilation" } return } - globalStats.statsForClasses.add(classStats) + statsForProject.statsForClasses.add(classStats) try { val testClass = cut.generatedTestFile classStats.testClassFile = testClass - val cmd = arrayOf( - javacCmd, - "-d", compiledTestDir.absolutePath, - "-cp", project.compileClasspathString, - "-nowarn", - "-XDignore.symbol.file", - testClass.absolutePath - ) - - logger.info().bracket("Compiling class ${testClass.absolutePath}") { - - logger.trace { cmd.toText() } - val process = Runtime.getRuntime().exec(cmd) - - thread { - val errors = process.errorStream.reader().buffered().readText() - if (errors.isNotEmpty()) - logger.error { "Compilation errors: $errors" } - }.join() - - - val exitCode = process.waitFor() + logger.info().measureTime({ "Compiling class ${testClass.absolutePath}" }) { + val exitCode = compileClassAndRemoveUncompilableTests( + compiledTestDir.absolutePath, + project.compileClasspathString, + testClass.absolutePath + ) if (exitCode != 0) { logger.error { "Failed to compile test class ${cut.testClassSimpleName}" } classStats.failedToCompile = true @@ -162,16 +265,75 @@ enum class Tool { override fun moveProducedFilesIfNeeded() { // don't do anything } - }, - EvoSuite { + } + + object UtBot : UtBotBasedTool() { + @OptIn(ObsoleteCoroutinesApi::class) + @Suppress("EXPERIMENTAL_API_USAGE") + override fun runGeneration( + project: ProjectToEstimate, + cut: ClassUnderTest, + timeLimit: Long, + fuzzingRatio: Double, + methodNameFilter: String?, + statsForProject: StatsForProject, + compiledTestDir: File, + classFqn: String, + expectedExceptions: ExpectedExceptionsForClass + ): StatsForClass { + return runGeneration( + project.name, + cut, + timeLimit, + fuzzingRatio, + project.sootClasspathString, + runFromEstimator = true, + expectedExceptions, + methodNameFilter + ) + } + } + + object USVM : UtBotBasedTool() { + @OptIn(ObsoleteCoroutinesApi::class) + @Suppress("EXPERIMENTAL_API_USAGE") + override fun runGeneration( + project: ProjectToEstimate, + cut: ClassUnderTest, + timeLimit: Long, + fuzzingRatio: Double, + methodNameFilter: String?, + statsForProject: StatsForProject, + compiledTestDir: File, + classFqn: String, + expectedExceptions: ExpectedExceptionsForClass + ): StatsForClass = runUsvmGeneration( + project.name, + cut, + timeLimit, + project.sootClasspathString, + runFromEstimator = true, + expectedExceptions = expectedExceptions, + tmpDir = File("."), + methodNameFilter = methodNameFilter + ) + + override fun close() { + JcContainer.close() + } + } + + object EvoSuite : Tool { override fun run( project: ProjectToEstimate, cut: ClassUnderTest, timeLimit: Long, + fuzzingRatio: Double, methodNameFilter: String?, - globalStats: GlobalStats, + statsForProject: StatsForProject, compiledTestDir: File, - classFqn: String + classFqn: String, + expectedExceptions: ExpectedExceptionsForClass ) { // EvoSuite has several phases, the variable below is responsible for assert generation // timeout. We want to give 10s for a big time budgets and timeLimit / 5 for small budgets. @@ -202,7 +364,7 @@ enum class Tool { logger.info { "Started processing $classFqn" } val process = ProcessBuilder(command).redirectErrorStream(true).start() - logger.info { "Pid: ${process.pid}" } + logger.info { "Pid: ${process.getPid}" } process.inputStream.bufferedReader().use { reader -> while (true) { @@ -234,20 +396,28 @@ enum class Tool { } }; - abstract fun run( + fun run( project: ProjectToEstimate, cut: ClassUnderTest, timeLimit: Long, + fuzzingRatio: Double, // maybe create some specific settings methodNameFilter: String?, - globalStats: GlobalStats, + statsForProject: StatsForProject, compiledTestDir: File, - classFqn: String + classFqn: String, + expectedExceptions: ExpectedExceptionsForClass ) - abstract fun moveProducedFilesIfNeeded() + fun moveProducedFilesIfNeeded() + + fun close() {} } fun main(args: Array) { + // See https://dzone.com/articles/how-to-export-all-modules-to-all-modules-at-runtime-in-java?preview=true + // `Modules` is `null` on JDK 8 (see comment to StaticComponentContainer.Modules) + org.burningwave.core.assembler.StaticComponentContainer.Modules?.exportAllToAll() + val estimatorArgs: Array val methodFilter: String? val projectFilter: List? @@ -255,9 +425,10 @@ fun main(args: Array) { val tools: List // very special case when you run your project directly from IntellijIDEA omitting command line arguments - if (args.isEmpty() && System.getProperty("os.name")?.run { contains("win", ignoreCase = true) } == true) { + if (args.isEmpty()) { processedClassesThreshold = 9999 //change to change number of classes to run - val timeLimit = 20 // increase if you want to debug something + val timeLimit = 30 // increase if you want to debug something + val fuzzingRatio = 0.1 // sets fuzzing ratio to total test generation // Uncomment it for debug purposes: // you can specify method for test generation in format `classFqn.methodName` @@ -276,20 +447,21 @@ fun main(args: Array) { // tools = listOf(Tool.EvoSuite) // config for SBST 2022 - methodFilter = null - projectFilter = listOf("fastjson-1.2.50", "guava-26.0", "seata-core-0.5.0", "spoon-core-7.0.0") - tools = listOf(Tool.UtBot) + methodFilter = "com.alibaba.fastjson.asm.ByteVector.*" + projectFilter = listOf("fastjson-1.2.50") + tools = listOf(mainTool) estimatorArgs = arrayOf( classesLists, jarsDir, "$timeLimit", + "$fuzzingRatio", outputDir, moduleTestDir ) } else { - require(args.size == 6) { - "Wrong arguments: + Try UnitTestBot online demo to see how it generates tests for your code in real time. +
    + Contribute to UnitTestBot via GitHub. +
    + Found a bug? File an issue. +
    + Have an idea? Start a discussion. + ]]> + + diff --git a/utbot-python-pycharm/src/main/resources/META-INF/pluginIcon.svg b/utbot-python-pycharm/src/main/resources/META-INF/pluginIcon.svg new file mode 100644 index 0000000000..d24574d6dd --- /dev/null +++ b/utbot-python-pycharm/src/main/resources/META-INF/pluginIcon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/utbot-python-pycharm/src/main/resources/META-INF/withPython.xml b/utbot-python-pycharm/src/main/resources/META-INF/withPython.xml new file mode 100644 index 0000000000..f272fd7601 --- /dev/null +++ b/utbot-python-pycharm/src/main/resources/META-INF/withPython.xml @@ -0,0 +1,4 @@ + + + com.intellij.modules.python + \ No newline at end of file diff --git a/utbot-python-pycharm/src/main/resources/application.properties b/utbot-python-pycharm/src/main/resources/application.properties new file mode 100644 index 0000000000..f5af5a168d --- /dev/null +++ b/utbot-python-pycharm/src/main/resources/application.properties @@ -0,0 +1,5 @@ +# suppress inspection "HttpUrlsUsage" for whole file +backing.service.possibleEndpoints=http://utbot.org +backing.service.urlPath=/utbot/errors +backing.service.port=11000 +request.timeoutMillis=5000 \ No newline at end of file diff --git a/utbot-python-pycharm/src/main/resources/log4j2.xml b/utbot-python-pycharm/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..6a9ae540c8 --- /dev/null +++ b/utbot-python-pycharm/src/main/resources/log4j2.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utbot-python-pycharm/src/main/resources/settings.properties b/utbot-python-pycharm/src/main/resources/settings.properties new file mode 100644 index 0000000000..657e42f4f4 --- /dev/null +++ b/utbot-python-pycharm/src/main/resources/settings.properties @@ -0,0 +1,607 @@ +# Copyright (c) 2023 utbot.org +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Setting to disable coroutines debug explicitly. +# Set it to false if debug info is required. +# +# Default value is [true] +#disableCoroutinesDebug=true + +# +# Make `true` for interactive mode (like Intellij plugin). If `false` UTBot can apply certain optimizations. +# +# Default value is [true] +#classfilesCanChange=true + +# +# Timeout for Z3 solver.check calls. +# Set it to 0 to disable timeout. +# +# Default value is [1000] +#checkSolverTimeoutMillis=1000 + +# +# Timeout for symbolic execution +# +# Default value is [60000] +#utBotGenerationTimeoutInMillis=60000 + +# +# Random seed in path selector. +# Set null to disable random. +# +# Default value is [42] +#seedInPathSelector=42 + +# +# Type of path selector. +# +# COVERED_NEW_SELECTOR: [CoveredNewSelector] +# INHERITORS_SELECTOR: [InheritorsSelector] +# BFS_SELECTOR: [BFSSelector] +# SUBPATH_GUIDED_SELECTOR: [SubpathGuidedSelector] +# CPI_SELECTOR: [CPInstSelector] +# FORK_DEPTH_SELECTOR: [ForkDepthSelector] +# ML_SELECTOR: [MLSelector] +# TORCH_SELECTOR: [TorchSelector] +# RANDOM_SELECTOR: [RandomSelector] +# RANDOM_PATH_SELECTOR: [RandomPathSelector] +# +# Default value is [INHERITORS_SELECTOR] +#pathSelectorType=INHERITORS_SELECTOR + +# +# Type of MLSelector recalculation. +# +# WITH_RECALCULATION: [MLSelectorWithRecalculation] +# WITHOUT_RECALCULATION: [MLSelectorWithoutRecalculation] +# +# Default value is [WITHOUT_RECALCULATION] +#mlSelectorRecalculationType=WITHOUT_RECALCULATION + +# +# Type of [MLPredictor]. +# +# MLP: [MultilayerPerceptronPredictor] +# LINREG: [LinearRegressionPredictor] +# +# Default value is [MLP] +#mlPredictorType=MLP + +# +# Steps limit for path selector. +# +# Default value is [3500] +#pathSelectorStepsLimit=3500 + +# +# Determines whether path selector should save remaining states for concrete execution after stopping by strategy. +# False for all framework tests by default. +#saveRemainingStatesForConcreteExecution=true + +# +# Use debug visualization. +# Set it to true if debug visualization is needed. +# +# Default value is [false] +#useDebugVisualization=false + +# +# Set the value to true to show library classes' graphs in visualization. +# +# Default value is [false] +#showLibraryClassesInVisualization=false + +# +# Use simplification of UtExpressions. +# Set it to false to disable expression simplification. +# +# Default value is [true] +#useExpressionSimplification=true + +# +# Enable the Summarization module to generate summaries for methods under test. +# Note: if it is [SummariesGenerationType.NONE], +# all the execution for a particular method will be stored at the same nameless region. +# +# FULL: All possible analysis actions are taken +# LIGHT: Analysis actions based on sources are NOT taken +# NONE: No summaries are generated +# +# Default value is [FULL] +#summaryGenerationType=FULL + +# +# If True test comments will be generated. +# +# Default value is [true] +#enableJavaDocGeneration=true + +# +# If True cluster comments will be generated. +# +# Default value is [true] +#enableClusterCommentsGeneration=true + +# +# If True names for tests will be generated. +# +# Default value is [true] +#enableTestNamesGeneration=true + +# +# If True display names for tests will be generated. +# +# Default value is [true] +#enableDisplayNameGeneration=true + +# +# If True display name in from -> to style will be generated. +# +# Default value is [true] +#useDisplayNameArrowStyle=true + +# +# Generate summaries using plugin's custom JavaDoc tags. +# +# Default value is [true] +#useCustomJavaDocTags=true + +# +# This option regulates which [NullPointerException] check should be performed for nested methods. +# Set an option in true if you want to perform NPE check in the corresponding situations, otherwise set false. +# +# Default value is [true] +#checkNpeInNestedMethods=true + +# +# This option regulates which [NullPointerException] check should be performed for nested not private methods. +# Set an option in true if you want to perform NPE check in the corresponding situations, otherwise set false. +# +# Default value is [false] +#checkNpeInNestedNotPrivateMethods=false + +# +# This option determines whether we should generate [NullPointerException] checks for final or non-public fields +# in non-application classes. Set by true, this option highly decreases test's readability in some cases +# because of using reflection API for setting final/non-public fields in non-application classes. +# NOTE: With false value loses some executions with NPE in system classes, but often most of these executions +# are not expected by user. +# +# Default value is [false] +#maximizeCoverageUsingReflection=false + +# +# Activate or deactivate substituting static fields values set in static initializer +# with symbolic variable to try to set them another value than in initializer. +# +# Default value is [true] +#substituteStaticsWithSymbolicVariable=true + +# +# Use concrete execution. +# +# Default value is [true] +#useConcreteExecution=true + +# +# Enable code generation tests with every possible configuration +# for every method in samples. +# Important: is enabled generation requires enormous amount of time. +# +# Default value is [false] +#checkAllCombinationsForEveryTestInSamples=false + +# +# Enable transformation UtCompositeModels into UtAssembleModels using AssembleModelGenerator. +# Note: false doesn't mean that there will be no assemble models, it means that the generator will be turned off. +# Assemble models will present for lists, sets, etc. +# +# Default value is [true] +#useAssembleModelGenerator=true + +# +# Test related files from the temp directory that are older than [daysLimitForTempFiles] +# will be removed at the beginning of the test run. +# +# Default value is [3] +#daysLimitForTempFiles=3 + +# +# Enables soft constraints in the engine. +# +# Default value is [true] +#preferredCexOption=true + +# +# Type of test minimization strategy. +# +# DO_NOT_MINIMIZE_STRATEGY: Always adds new test +# COVERAGE_STRATEGY: Adds new test only if it increases coverage +# +# Default value is [COVERAGE_STRATEGY] +#testMinimizationStrategyType=COVERAGE_STRATEGY + +# +# Set to true to start fuzzing if symbolic execution haven't return anything +# +# Default value is [true] +#useFuzzing=true + +# +# Set the total attempts to improve coverage by fuzzer. +# +# Default value is [2147483647] +#fuzzingMaxAttempts=2147483647 + +# +# Fuzzer tries to generate and run tests during this time. +# +# Default value is [3000] +#fuzzingTimeoutInMillis=3000 + +# +# Find implementations of interfaces and abstract classes to fuzz. +# +# Default value is [true] +#fuzzingImplementationOfAbstractClasses=true + +# +# Use methods to mutate fields of classes different from class under test or not. +# +# Default value is [false] +#tryMutateOtherClassesFieldsWithMethods=false + +# +# Generate tests that treat possible overflows in arithmetic operations as errors +# that throw Arithmetic Exception. +# +# Default value is [false] +#treatOverflowAsError=false + +# +# Generate tests that treat assertions as error suits. +# +# Default value is [true] +#treatAssertAsErrorSuite=true + +# +# Instrument all classes before start +# +# Default value is [false] +#warmupConcreteExecution=false + +# +# Ignore string literals during the code analysis to make possible to analyze antlr. +# It is a hack and must be removed after the competition. +# +# Default value is [false] +#ignoreStringLiterals=false + +# +# Timeout for specific concrete execution (in milliseconds). +# +# Default value is [1000] +#concreteExecutionDefaultTimeoutInInstrumentedProcessMillis=1000 + +# +# Enable taint analysis or not. +# +# Default value is [false] +#useTaintAnalysis=false + +# +# Path to custom log4j2 configuration file for EngineProcess. +# By default utbot-intellij/src/main/resources/log4j2.xml is used. +# Also default value is used if provided value is not a file. +#engineProcessLogConfigFile="" + +# +# The property is useful only for the IntelliJ IDEs. +# If the property is set in true the engine process opens a debug port. +# @see runInstrumentedProcessWithDebug +# @see org.utbot.intellij.plugin.process.EngineProcess +# +# Default value is [false] +#runEngineProcessWithDebug=false + +# +# The engine process JDWP agent's port of the engine process. +# A debugger attaches to the port in order to debug the process. +# +# Default value is [5005] +#engineProcessDebugPort=5005 + +# +# Value of the suspend mode for the JDWP agent of the engine process. +# If the value is true, the engine process will suspend until a debugger attaches to it. +# +# Default value is [true] +#suspendEngineProcessExecutionInDebugMode=true + +# +# The property is useful only for the IntelliJ IDEs. +# If the property is set in true the spring analyzer process opens a debug port. +# @see runInstrumentedProcessWithDebug +# @see org.utbot.spring.process.SpringAnalyzerProcess +# +# Default value is [false] +#runSpringAnalyzerProcessWithDebug=false + +# +# The spring analyzer process JDWP agent's port. +# A debugger attaches to the port in order to debug the process. +# +# Default value is [5007] +#springAnalyzerProcessDebugPort=5007 + +# +# Value of the suspend mode for the JDWP agent of the spring analyzer process. +# If the value is true, the spring analyzer process will suspend until a debugger attaches to it. +# +# Default value is [true] +#suspendSpringAnalyzerProcessExecutionInDebugMode=true + +# +# The instrumented process JDWP agent's port of the instrumented process. +# A debugger attaches to the port in order to debug the process. +# +# Default value is [5006] +#instrumentedProcessDebugPort=5006 + +# +# Value of the suspend mode for the JDWP agent of the instrumented process. +# If the value is true, the instrumented process will suspend until a debugger attaches to it. +# +# Default value is [true] +#suspendInstrumentedProcessExecutionInDebugMode=true + +# +# If true, runs the instrumented process with the ability to attach a debugger. +# To debug the instrumented process, set the breakpoint in the +# [org.utbot.instrumentation.rd.InstrumentedProcess.Companion.invoke] +# and in the instrumented process's main function and run the main process. +# Then run the remote JVM debug configuration in IDEA. +# If you see the message in console about successful connection, then +# the debugger is attached successfully. +# Now you can put the breakpoints in the instrumented process and debug +# both processes simultaneously. +# @see [org.utbot.instrumentation.rd.InstrumentedProcess.Companion.invoke] +# +# Default value is [false] +#runInstrumentedProcessWithDebug=false + +# +# Number of branch instructions using for clustering executions in the test minimization phase. +# +# Default value is [4] +#numberOfBranchInstructionsForClustering=4 + +# +# Determines should we choose only one crash execution with "minimal" model or keep all. +# +# Default value is [true] +#minimizeCrashExecutions=true + +# +# Enable it to calculate unsat cores for hard constraints as well. +# It may be usefull during debug. +# Note: it might highly impact performance, so do not enable it in release mode. +# +# Default value is [false] +#enableUnsatCoreCalculationForHardConstraints=false + +# +# Enable it to process states with unknown solver status +# from the queue to concrete execution. +# +# Default value is [true] +#processUnknownStatesDuringConcreteExecution=true + +# +# 2^{this} will be the length of observed subpath. +# See [SubpathGuidedSelector] +# +# Default value is [1] +#subpathGuidedSelectorIndex=1 + +# +# Flag that indicates whether feature processing for execution states enabled or not +# +# Default value is [false] +#enableFeatureProcess=false + +# +# Path to deserialized ML models +# +# Default value is [../models/0] +#modelPath=../models/0 + +# +# Full class name of the class containing the configuration for the ML models to solve path selection task. +# +# Default value is [org.utbot.AnalyticsConfiguration] +#analyticsConfigurationClassPath=org.utbot.AnalyticsConfiguration + +# +# Full class name of the class containing the configuration for the ML models exported from the PyTorch to solve path selection task. +# +# Default value is [org.utbot.AnalyticsTorchConfiguration] +#analyticsTorchConfigurationClassPath=org.utbot.AnalyticsTorchConfiguration + +# +# Number of model iterations that will be used during ContestEstimator +# +# Default value is [1] +#iterations=1 + +# +# Path for state features dir +# +# Default value is [eval/secondFeatures/antlr/INHERITORS_SELECTOR] +#featurePath=eval/secondFeatures/antlr/INHERITORS_SELECTOR + +# +# Counter for tests during testGeneration for one project in ContestEstimator +# +# Default value is [0] +#testCounter=0 + +# +# Flag that indicates whether tests for synthetic (see [Executable.isSynthetic]) and implicitly declared methods (like values, valueOf in enums) should be generated, or not +# +# Default value is [true] +#skipTestGenerationForSyntheticAndImplicitlyDeclaredMethods=true + +# +# Flag that indicates whether should we branch on and set static fields from trusted libraries or not. +# @see [org.utbot.common.WorkaroundReason.IGNORE_STATICS_FROM_TRUSTED_LIBRARIES] +# +# Default value is [true] +#ignoreStaticsFromTrustedLibraries=true + +# +# Use the sandbox in the instrumented process. +# If true, the sandbox will prevent potentially dangerous calls, e.g., file access, reading +# or modifying the environment, calls to `Unsafe` methods etc. +# If false, all these operations will be enabled and may lead to data loss during code analysis +# and test generation. +# +# Default value is [true] +#useSandbox=true + +# +# Transform bytecode in the instrumented process. +# If true, bytecode transformation will help fuzzing to find interesting input data, but the size of bytecode can increase. +# If false, bytecode won`t be changed. +# +# Default value is [false] +#useBytecodeTransformation=false + +# +# Limit for number of generated tests per method (in each region) +# +# Default value is [50] +#maxTestsPerMethodInRegion=50 + +# +# Max file length for generated test file +# +# Default value is [1000000] +#maxTestFileSize=1000000 + +# +# If this options set in true, all soot classes will be removed from a Soot Scene, +# therefore, you will be unable to test soot classes. +# +# Default value is [true] +#removeSootClassesFromHierarchy=true + +# +# If this options set in true, all UtBot classes will be removed from a Soot Scene, +# therefore, you will be unable to test UtBot classes. +# +# Default value is [true] +#removeUtBotClassesFromHierarchy=true + +# +# Use this option to enable calculation and logging of MD5 for dropped states by statistics. +# Example of such logging: +# Dropping state (lastStatus=UNDEFINED) by the distance statistics. MD5: 5d0bccc242e87d53578ca0ef64aa5864 +# +# Default value is [false] +#enableLoggingForDroppedStates=false + +# +# If this option set in true, depending on the number of possible types for +# a particular object will be used either type system based on conjunction +# or on bit vectors. +# @see useBitVecBasedTypeSystem +# +# Default value is [true] +#useBitVecBasedTypeSystem=true + +# +# The number of types on which the choice of the type system depends. +# +# Default value is [64] +#maxTypeNumberForEnumeration=64 + +# +# The threshold for numbers of types for which they will be encoded into solver. +# It is used to do not encode big type storages due to significand performance degradation. +# +# Default value is [512] +#maxNumberOfTypesToEncode=512 + +# +# The behaviour of further analysis if tests generation cancellation is requested. +# +# NONE: Do not react on cancellation +# CANCEL_EVERYTHING: Clear all generated test classes +# SAVE_PROCESSED_RESULTS: Show already processed test classes +# +# Default value is [SAVE_PROCESSED_RESULTS] +#cancellationStrategyType=SAVE_PROCESSED_RESULTS + +# +# Depending on this option, sections might be analyzed or not. +# Note that some clinit sections still will be initialized using runtime information. +# +# Default value is [true] +#enableClinitSectionsAnalysis=true + +# +# Process all clinit sections concretely. +# If [enableClinitSectionsAnalysis] is false, it disables effect of this option as well. +# Note that values processed concretely won't be replaced with unbounded symbolic variables. +# +# Default value is [false] +#processAllClinitSectionsConcretely=false + +# +# In cases where we don't have a body for a method, we can either throw an exception +# or treat this a method as a source of an unbounded symbolic variable returned as a result. +# If this option is set in true, instead of analysis we will return an unbounded symbolic +# variable with a corresponding type. Otherwise, an exception will be thrown. +# Default value is false since it is not a common situation when you cannot retrieve a body +# from a regular method. Setting this option in true might be suitable in situations when +# it is more important not to fall at all rather than work precisely. +#treatAbsentMethodsAsUnboundedValue=false + +# +# A maximum size for any array in the program. Note that input arrays might be less than this value +# due to the symbolic engine limitation, see `org.utbot.engine.Traverser.softMaxArraySize`. +# +# Default value is [1024] +#maxArraySize=1024 + +# +# A maximum size for any array in the program. Note that input arrays might be less than this value +# due to the symbolic engine limitation, see `org.utbot.engine.Traverser.softMaxArraySize`. +# +# Default value is [false] +#disableUnsatChecking=false + +# +# When generating integration tests we only partially reset context in between executions to save time. +# For example, entity id generators do not get reset. It may lead to non-reproduceable results if +# IDs leak to the output of the method under test. +# To cope with that, we rerun executions that are left after minimization, fully resetting Spring context +# between executions. However, full context reset is slow, so we use this setting to limit number of +# tests per method that are rerun with full context reset in case minimization outputs too many tests. +# +# Default value is [25] +#maxSpringContextResetsPerMethod=25 diff --git a/utbot-python/.gitignore b/utbot-python/.gitignore new file mode 100644 index 0000000000..39be1a3893 --- /dev/null +++ b/utbot-python/.gitignore @@ -0,0 +1,2 @@ +*.swp +local_pip_setup/ \ No newline at end of file diff --git a/utbot-python/build.gradle.kts b/utbot-python/build.gradle.kts new file mode 100644 index 0000000000..ac1e3564e6 --- /dev/null +++ b/utbot-python/build.gradle.kts @@ -0,0 +1,32 @@ +val intellijPluginVersion: String? by rootProject +val kotlinLoggingVersion: String? by rootProject +val apacheCommonsTextVersion: String? by rootProject +val jacksonVersion: String? by rootProject +val ideType: String? by rootProject +val pythonCommunityPluginVersion: String? by rootProject +val pythonUltimatePluginVersion: String? by rootProject +val log4j2Version: String? by rootProject +val moshiVersion: String? by rootProject +val pythonTypesAPIHash: String? by rootProject + +dependencies { + api(project(":utbot-fuzzing")) + api(project(":utbot-framework")) + api(project(":utbot-python-parser")) + api(project(":utbot-python-executor")) + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation(group = "org.apache.commons", name = "commons-lang3", version = "3.12.0") + implementation(group = "commons-io", name = "commons-io", version = "2.11.0") + implementation("com.squareup.moshi:moshi:$moshiVersion") + implementation("com.squareup.moshi:moshi-kotlin:$moshiVersion") + implementation("com.squareup.moshi:moshi-adapters:$moshiVersion") + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) + implementation("org.functionaljava:functionaljava:5.0") + implementation("org.functionaljava:functionaljava-quickcheck:5.0") + implementation("org.functionaljava:functionaljava-java-core:5.0") + implementation(group = "org.apache.commons", name = "commons-text", version = apacheCommonsTextVersion) + implementation(group = "org.apache.logging.log4j", name = "log4j-core", version = log4j2Version) + implementation(group = "org.apache.logging.log4j", name = "log4j-api", version = log4j2Version) + implementation("com.github.UnitTestBot:PythonTypesAPI:$pythonTypesAPIHash") +} \ No newline at end of file diff --git a/utbot-python/docs/CLI.md b/utbot-python/docs/CLI.md new file mode 100644 index 0000000000..71aa95e76a --- /dev/null +++ b/utbot-python/docs/CLI.md @@ -0,0 +1,109 @@ +## Build + +.jar file can be built in Github Actions with script `publish-plugin-and-cli-from-branch`. + +## Requirements + + - Required Java version: 11. + + - Prefered Python version: 3.8+. + + Make sure that your Python has `pip` installed (this is usually the case). [Read more about pip installation](https://pip.pypa.io/en/stable/installation/). + + Before running utbot install pip requirements (or use `--install-requirements` flag in `generate_python` command): + + python -m pip install utbot_executor utbot_mypy_runner + +## Basic usage + +Generate tests: + + java -jar utbot-cli.jar generate_python dir/file_with_sources.py -p -o generated_tests.py -s dir + +This will generate tests for top-level functions from `file_with_sources.py`. + +Run generated tests: + + java -jar utbot-cli.jar run_python generated_tests.py -p + +### `generate_python` options + +- `-s, --sys-path ,` + + (required) Directories to add to `sys.path`. One of directories must contain the file with the methods under test. + + `sys.path` is a list of strings that specifies the search path for modules. It must include paths for all user modules that are used in imports. + +- `-p, --python-path ` + + (required) Path to Python interpreter. + +- `-o, --output ` + + (required) File for generated tests. + +- `--coverage ` + + File to write coverage report. + +- `-c, --class ` + + Specify top-level (ordinary, not nested) class under test. Without this option tests will be generated for top-level functions. + +- `-m, --methods ,` + + Specify methods under test. + +- `--install-requirements` + + Install Python requirements if missing. + +- `--do-not-minimize` + + Turn off minimization of the number of generated tests. + +- `--do-not-check-requirements` + + Turn off Python requirements check (to speed up). + +- `-t, --timeout INT` + + Specify the maximum time in milliseconds to spend on generating tests (60000 by default). + +- `--timeout-for-run INT` + + Specify the maximum time in milliseconds to spend on one function run (2000 by default). + +- `--test-framework [pytest|Unittest]` + + Test framework to be used. + +- `--do-not-generate-regression-suite` + + Do not generate regression test suite. + +- `--runtime-exception-behaviour [PASS|FAIL]` + + Runtime exception behaviour (assert exceptions or not). + +- `--coverage` + + File to save coverage report. + +### `run_python` options + +- `-p, --python-path ` + + (required) Path to Python interpreter. + +- `--test-framework [pytest|Unittest]` + + Test framework of tests to run. + +- `-o, --output ` + + Specify file for report. + +## Problems + +- Unittest can not run tests from parent directories diff --git a/utbot-python/docs/docs.md b/utbot-python/docs/docs.md new file mode 100644 index 0000000000..96e0bce318 --- /dev/null +++ b/utbot-python/docs/docs.md @@ -0,0 +1,54 @@ +# UtBot-Python +__Task__: implement utbot for Python using fuzzing to generate tests. + +Subtasks: +* Get list of functions to be tested +* Generate input parameters for this functions +* Compute return values for this parameters +* Render tests + +## Getting list of functions + +We get list of functions to be tested from Intellij IDEA plugin. Other information we get from source code. + +Information about functions: +* Name +* List of parameters +* Source code +* Declaration file +* Type annotations for parameters and return type (optional) + +## Input parameters generation + +### Problem + +If we do not have type annotation, we have to find suitable types for this parameter. + +### Solution +Gather information about Python built-in types (by 'built-in types' we mean types that are implemented in C): + +* Name +* Methods: name + parameters (+ annotations) +* How to generate instances of this type (default, random, using constants from code) + +We can use CPython code and tests for it to gather this. + +For user class we need to initialize its fields recursively. Possible problems: getting types of fields, dynamic addition of new fields. + +To find suitable types for parameter we can look for them only in given and imported files. + +To narrow down the search of suitable types we can gather constraints for function parameters. For that we can analyze AST to see which attributes of parameter are used. + +## Run function with generated parameters + +After generating parameters for fuzzing we pass them on into the function under test and run it in a separate process. This approach is called concrete execution. + +To run the function we need to generate code that imports and calls it and saves result. + +## Get return value + +We write serialized return value in file. To serialize values of the most used built-in types we can use json module. For other types we will have to do it manually. + +## Test generation + +First we build AST of test code and then render it. diff --git a/utbot-python/docs/python_packages.md b/utbot-python/docs/python_packages.md new file mode 100644 index 0000000000..415d410d28 --- /dev/null +++ b/utbot-python/docs/python_packages.md @@ -0,0 +1,65 @@ +# UTBot Python packages managements + +Current UTBot Python packages: + +- `utbot_mypy_runner`: https://pypi.org/project/utbot-mypy-runner/ +- `utbot_executor`: https://pypi.org/project/utbot-executor/ + +To be able to publish new releases on pypi, ask @tochilinak ot @tamarinvs19 to give you permissions. + +## Gradle tasks + +To use Gradle tasks for Python packages, add the following properties in `gradle.properties` in your `GRADLE_USER_HOME` directory (about: https://docs.gradle.org/current/userguide/directory_layout.html#dir:gradle_user_home): + +- `pythonInterpreter` (for example, `python3`) +- `pypiToken`(about: https://pypi.org/help/#apitoken) + +## utbot_mypy_runner + +How this module is stored in a separate repository: https://github.com/UnitTestBot/PythonTypesAPI. + +### Version + +Write version in file `src/main/resources/utbot_mypy_runner_version`. + +Gradle task `setVersion` will update `pyproject.toml`. + +If you want to use some other version locally (for example, version that is not yet published), set the version +in file `utbot-python/src/main/resources/local_pip_setup/local_utbot_mypy_version`. + +### Usage of local version + +Add the following files locally (they are listed in `.gitignore`): + +- `utbot-python/src/main/resources/local_pip_setup/local_utbot_mypy_path` + + Add here absolute path to `utbot_mypy_runner/dist` directory. + +- `utbot-python/src/main/resources/local_pip_setup/local_utbot_mypy_version` + + Add here the version of local package. This will override the version from module `PythonTypesAPI`. + +- `utbot-python/src/main/resources/local_pip_setup/use_local_python_packages` + + Write here `true` if you want to build `utbot-python` that uses local versions of UTBot Python packages. + +## utbot_executor + +### Version + +Write version in file `utbot-python-executor/src/main/resources/utbot_executor_version`. + +Gradle task `utbot-python-executor:setVersion` will update `pyproject.toml`. + +### Usage of local version + +Add the following files locally (they are listed in `.gitignore`): + +- `utbot-python/src/main/resources/local_pip_setup/local_utbot_executor_path` + + Add here absolute path to `utbot_executor/dist` directory. + + +- `utbot-python/src/main/resources/local_pip_setup/use_local_python_packages` + + Write here `true` if you want to build `utbot-python` that uses local versions of UTBot Python packages. diff --git a/utbot-python/samples/.gitignore b/utbot-python/samples/.gitignore new file mode 100644 index 0000000000..766adac5a5 --- /dev/null +++ b/utbot-python/samples/.gitignore @@ -0,0 +1,11 @@ +.tmp/ +utbot_tests/ +utbot_coverage/ +RUN_RESULT +COVERAGE_RESULT +cli_test_dir/ +utbot-cli.jar +__pycache__/ +.venv/ +venv/ +.coverage diff --git a/utbot-python/samples/inference/inference_example.md b/utbot-python/samples/inference/inference_example.md new file mode 100644 index 0000000000..8f048f3842 --- /dev/null +++ b/utbot-python/samples/inference/inference_example.md @@ -0,0 +1,180 @@ +## Библиотека loguru + +### \_colorama.py + +#### Old +```python +def should_colorize(stream: types.NoneType): pass +def should_colorize(stream: builtins.bool): pass +def should_colorize(stream: builtins.float): pass +def should_colorize(stream: builtins.complex): pass +def should_colorize(stream: builtins.bytearray): pass +def should_colorize(stream: typing.Dict[typing.Any, typing.Any]): pass +def should_colorize(stream: builtins.frozenset): pass +def should_colorize(stream: typing.List[typing.Any]): pass +def should_colorize(stream: typing.Dict[typing.List[typing.Any], builtins.str]): pass +def should_colorize(stream: typing.List[typing.List[typing.Any]]): pass +def should_colorize(stream: builtins.object): pass +def should_colorize(stream: typing.Dict[typing.List[typing.Any], typing.List[typing.Any]]): pass +``` +#### New +``` +typing.Callable[[io.TextIOWrapper], typing.Any] +typing.Callable[[io.StringIO], typing.Any] +typing.Callable[[codecs.StreamReaderWriter], typing.Any] +typing.Callable[[io.IOBase], typing.Any] +typing.Callable[[io.TextIOBase], typing.Any] +typing.Callable[[io.FileIO], typing.Any] +typing.Callable[[io.BytesIO], typing.Any] +typing.Callable[[io.BufferedReader], typing.Any] +typing.Callable[[io.BufferedWriter], typing.Any] +typing.Callable[[io.BufferedRandom], typing.Any] +typing.Callable[[codecs.StreamRecoder], typing.Any] +typing.Callable[[io.RawIOBase], typing.Any] +typing.Callable[[io.BufferedIOBase], typing.Any] +typing.Callable[[io.BufferedRWPair], typing.Any] +typing.Callable[[types.SimpleNamespace], typing.Any] +typing.Callable[[types.ModuleType], typing.Any] +typing.Callable[[types.MethodType], typing.Any] +typing.Callable[[types.GenericAlias], typing.Any] +``` + +### \_datetime.py + +TODO + +### \_filters.py + +#### Old + +```python +def filter_none(record: builtins.str): pass +def filter_none(record: builtins.bytes): pass +def filter_none(record: typing.Dict[typing.Any, typing.Any]): pass +def filter_none(record: typing.Dict[builtins.str, builtins.str]): pass +def filter_none(record: typing.Dict[builtins.str, builtins.int]): pass +def filter_none(record: typing.List[builtins.str]): pass +def filter_none(record: builtins.memoryview): pass +def filter_none(record: typing.Dict[builtins.str, builtins.bool]): pass +def filter_none(record: typing.List[builtins.bool]): pass +def filter_none(record: typing.Set[typing.Any]): pass +``` + +#### New +``` +typing.Callable[[builtins.dict[typing.Any, typing.Any]], typing.Any] +typing.Callable[[builtins.dict[builtins.str, builtins.int]], typing.Any] +typing.Callable[[builtins.dict[builtins.str, builtins.list[typing.Any]]], typing.Any] +typing.Callable[[ctypes.CDLL], typing.Any] +typing.Callable[[ctypes.PyDLL], typing.Any] +typing.Callable[[builtins.dict[builtins.str, builtins.list[builtins.int]]], typing.Any] +typing.Callable[[builtins.dict[builtins.str, builtins.list[builtins.list[typing.Any]]]], typing.Any] +typing.Callable[[builtins.dict[builtins.str, builtins.list[builtins.str]]], typing.Any] +typing.Callable[[email.message.Message], typing.Any] +typing.Callable[[builtins.dict[builtins.str, builtins.list[builtins.list[builtins.int]]]], typing.Any] +typing.Callable[[email.message.EmailMessage], typing.Any] +typing.Callable[[builtins.dict[builtins.str, builtins.str]], typing.Any] +typing.Callable[[builtins.dict[builtins.str, builtins.list[builtins.list[builtins.list[typing.Any]]]]], typing.Any] +typing.Callable[[types.MappingProxyType[typing.Any, typing.Any]], typing.Any] +typing.Callable[[builtins.dict[builtins.str, builtins.list[builtins.list[builtins.list[builtins.int]]]]], typing.Any] +... +``` + +### \_string_parsers.py + +Actually, the only correct variant is str. The code is just messy. + +#### Old + +```python +def parse_size(size: builtins.int): pass +def parse_size(size: builtins.str): pass +def parse_size(size: builtins.range): pass +def parse_size(size: builtins.BaseException): pass +def parse_size(size: builtins.bytes): pass +def parse_size(size: typing.Dict[builtins.int, builtins.int]): pass +def parse_size(size: typing.Dict[builtins.int, builtins.str]): pass +def parse_size(size: typing.List[builtins.int]): pass +def parse_size(size: builtins.memoryview): pass +def parse_size(size: typing.Dict[builtins.int, typing.Dict[typing.Any, typing.Any]]): pass +def parse_size(size: typing.List[typing.Dict[typing.Any, typing.Any]]): pass +def parse_size(size: typing.Set[typing.Any]): pass +``` + +#### New + +``` +typing.Callable[[builtins.str], typing.Any] +typing.Callable[[builtins.int], typing.Any] +``` + +## Проект django-cms + +### cms/api.py + +#### Old + + +```python +def _verify_apphook(apphook: builtins.type, namespace: builtins.int): pass +def _verify_apphook(apphook: builtins.type, namespace: builtins.bool): pass +def _verify_apphook(apphook: build.lib.cms.plugin_base.CMSPluginBase, namespace: builtins.int): pass +def _verify_apphook(apphook: builtins.type, namespace: builtins.str): pass +def _verify_apphook(apphook: build.lib.cms.plugin_base.CMSPluginBase, namespace: builtins.bool): pass +def _verify_apphook(apphook: build.lib.cms.models.pluginmodel.CMSPlugin, namespace: builtins.int): pass +def _verify_apphook(apphook: builtins.type, namespace: builtins.float): pass +def _verify_apphook(apphook: build.lib.cms.plugin_base.CMSPluginBase, namespace: builtins.str): pass +def _verify_apphook(apphook: build.lib.cms.models.pluginmodel.CMSPlugin, namespace: builtins.bool): pass +def _verify_apphook(apphook: builtins.type, namespace: builtins.range): pass +def _verify_apphook(apphook: build.lib.cms.plugin_base.CMSPluginBase, namespace: builtins.float): pass +def _verify_apphook(apphook: build.lib.cms.models.pluginmodel.CMSPlugin, namespace: builtins.str): pass +def _verify_apphook(apphook: builtins.type, namespace: builtins.complex): pass +def _verify_apphook(apphook: build.lib.cms.plugin_base.CMSPluginBase, namespace: builtins.range): pass +def _verify_apphook(apphook: build.lib.cms.models.pluginmodel.CMSPlugin, namespace: builtins.float): pass +def _verify_apphook(apphook: cms.plugin_base.CMSPluginBase, namespace: builtins.int): pass +def _verify_apphook(apphook: builtins.type, namespace: builtins.BaseException): pass +def _verify_apphook(apphook: build.lib.cms.plugin_base.CMSPluginBase, namespace: builtins.complex): pass +def _verify_apphook(apphook: build.lib.cms.models.pluginmodel.CMSPlugin, namespace: builtins.range): pass +def _verify_apphook(apphook: cms.plugin_base.CMSPluginBase, namespace: builtins.bool): pass +def _verify_apphook(apphook: cms.models.pluginmodel.CMSPlugin, namespace: builtins.int): pass +... +``` + +#### New + +``` +typing.Callable[[builtins.str, builtins.int], typing.Any] +``` + +### cms/toolbar/items.py + +#### Old + +```python +def may_be_lazy(thing: builtins.int): pass +def may_be_lazy(thing: builtins.bool): pass +def may_be_lazy(thing: builtins.str): pass +def may_be_lazy(thing: builtins.float): pass +def may_be_lazy(thing: builtins.range): pass +def may_be_lazy(thing: builtins.complex): pass +def may_be_lazy(thing: builtins.BaseException): pass +def may_be_lazy(thing: builtins.bytearray): pass +def may_be_lazy(thing: builtins.bytes): pass +... +``` + +#### New + +``` +typing.Callable[[builtins.int], typing.Any] +typing.Callable[[builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.list[builtins.int]], typing.Any] +typing.Callable[[builtins.str], typing.Any] +typing.Callable[[builtins.list[builtins.list[typing.Any]]], typing.Any] +typing.Callable[[builtins.bool], typing.Any] +typing.Callable[[builtins.float], typing.Any] +typing.Callable[[builtins.list[builtins.str]], typing.Any] +... +``` + +#### find_first: TODO \ No newline at end of file diff --git a/utbot-python/samples/inference/inference_testing.md b/utbot-python/samples/inference/inference_testing.md new file mode 100644 index 0000000000..b09f832362 --- /dev/null +++ b/utbot-python/samples/inference/inference_testing.md @@ -0,0 +1,395 @@ +## File 1 + +Source: https://github.com/AtsushiSakai/PythonRobotics/blob/master/PathPlanning/DynamicWindowApproach/dynamic_window_approach.py + +Top-level functions (with only positional arguments): + +- [ ] `def dwa_control(x, config, goal, ob)` +- [x] `def motion(x, u, dt)` +- [x] `def calc_dynamic_window(x, config)` +- [x] `def predict_trajectory(x_init, v, y, config)` +- [x] `def calc_control_and_trajectory(x, dw, config, goal, ob)` +- [x] `def calc_obstacle_cost(trajectory, ob, config)` +- [x] `def calc_to_goal_cost(trajectory, goal)` +- [x] `def plot_robot(x, y, yaw, config)` + +Used time limit: 25 seconds. + +Command: + + java -jar utbot-cli-python-2023.02-SNAPSHOT.jar infer_types -p python3 -s /home/tochilinak/Documents/projects/utbot/PythonRobotics -t 25000 "/home/tochilinak/Documents/projects/utbot/PythonRobotics/PathPlanning/DynamicWindowApproach/dynamic_window_approach.py" + + +### OK: `def motion(x, u, dt)` + +``` +typing.Callable[[builtins.list[typing.Any], builtins.list[typing.Any], builtins.float], typing.Any] +typing.Callable[[builtins.dict[typing.Any, typing.Any], builtins.list[typing.Any], builtins.float], typing.Any] +typing.Callable[[builtins.list[typing.Any], builtins.dict[typing.Any, typing.Any], builtins.float], typing.Any] +typing.Callable[[builtins.list[builtins.float], builtins.list[builtins.int], builtins.float], typing.Any] +typing.Callable[[builtins.dict[typing.Any, typing.Any], builtins.dict[typing.Any, typing.Any], builtins.float], typing.Any] +typing.Callable[[ctypes.pointer[typing.Any], builtins.list[typing.Any], builtins.float], typing.Any] +typing.Callable[[builtins.list[typing.Any], ctypes.pointer[typing.Any], builtins.float], typing.Any] +typing.Callable[[array.array[typing.Any], builtins.list[typing.Any], builtins.float], typing.Any] +typing.Callable[[builtins.list[builtins.float], builtins.dict[builtins.int, builtins.int], builtins.float], typing.Any] +typing.Callable[[builtins.dict[builtins.int, builtins.float], builtins.list[builtins.int], builtins.float], typing.Any] +``` + + +### OK: `def calc_dynamic_window(x, config)` + +``` +typing.Callable[[builtins.list[typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.list[builtins.int], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.dict[typing.Any, typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.list[builtins.bool], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.list[builtins.float], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.dict[builtins.int, builtins.int], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[ctypes.pointer[typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[array.array[typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[types.MappingProxyType[typing.Any, typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +... +``` + + +### OK: `def predict_trajectory(x_init, v, y, config)` + +``` +typing.Callable[[builtins.int, builtins.int, builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.list[typing.Any], builtins.int, builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.list[builtins.int], builtins.int, builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.list[builtins.list[typing.Any]], builtins.int, builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.list[builtins.str], builtins.int, builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.int, builtins.list[typing.Any], builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.int, builtins.list[builtins.int], builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.int, builtins.int, builtins.list[typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.list[builtins.list[builtins.int]], builtins.int, builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.int, builtins.int, builtins.list[builtins.int], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.int, builtins.list[builtins.list[typing.Any]], builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.list[builtins.list[builtins.list[typing.Any]]], builtins.int, builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.int, builtins.int, builtins.list[builtins.list[typing.Any]], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +... +``` + +### OK: `def calc_control_and_trajectory(x, dw, config, goal, ob)` +``` +typing.Callable[[builtins.list[typing.Any], builtins.list[typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config, builtins.list[typing.Any], builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.list[builtins.int], builtins.list[builtins.list[typing.Any]], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config, builtins.list[builtins.list[typing.Any]], builtins.list[builtins.list[typing.Any]]], typing.Any] +typing.Callable[[builtins.list[builtins.list[typing.Any]], builtins.list[builtins.int], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config, builtins.list[builtins.list[typing.Any]], builtins.list[builtins.list[typing.Any]]], typing.Any] +typing.Callable[[builtins.list[builtins.int], builtins.list[builtins.list[builtins.list[typing.Any]]], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config, builtins.list[builtins.list[builtins.list[typing.Any]]], builtins.list[builtins.list[builtins.list[typing.Any]]]], typing.Any] +typing.Callable[[builtins.list[typing.Any], builtins.str, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config, builtins.list[typing.Any], builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.list[builtins.int], builtins.str, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config, builtins.list[builtins.list[typing.Any]], builtins.list[builtins.list[typing.Any]]], typing.Any] +typing.Callable[[builtins.list[typing.Any], builtins.list[typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config, builtins.int, builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.list[typing.Any], builtins.list[typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config, builtins.list[typing.Any], builtins.int], typing.Any] +... +``` + +### OK: `def calc_obstacle_cost(trajectory, ob, config)` + +``` +typing.Callable[[builtins.dict[typing.Any, typing.Any], builtins.dict[typing.Any, typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[numpy.lib.arrayterator.Arrayterator[typing.Any, typing.Any], builtins.dict[typing.Any, typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.dict[typing.Any, typing.Any], numpy.lib.arrayterator.Arrayterator[typing.Any, typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[numpy.lib.arrayterator.Arrayterator[typing.Any, typing.Any], numpy.lib.arrayterator.Arrayterator[typing.Any, typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[numpy.ndarray[typing.Any, typing.Any], builtins.dict[typing.Any, typing.Any], PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +``` + +### OK: `def calc_to_goal_cost(trajectory, goal)` + +``` +typing.Callable[[builtins.dict[typing.Any, typing.Any], builtins.dict[typing.Any, typing.Any]], typing.Any] +typing.Callable[[builtins.dict[builtins.tuple[typing.Any, ...], builtins.float], builtins.dict[builtins.float, builtins.float]], typing.Any] +typing.Callable[[builtins.dict[builtins.tuple[builtins.float, ...], builtins.float], builtins.dict[builtins.float, builtins.float]], typing.Any] +typing.Callable[[builtins.dict[builtins.tuple[builtins.int, ...], builtins.float], builtins.dict[builtins.float, builtins.float]], typing.Any] +typing.Callable[[builtins.dict[builtins.tuple[typing.Any, ...], builtins.int], builtins.dict[builtins.float, builtins.float]], typing.Any] +typing.Callable[[builtins.dict[builtins.tuple[builtins.float, ...], builtins.int], builtins.dict[builtins.float, builtins.float]], typing.Any] +typing.Callable[[builtins.dict[builtins.tuple[builtins.int, ...], builtins.int], builtins.dict[builtins.float, builtins.float]], typing.Any] +typing.Callable[[builtins.dict[builtins.tuple[typing.Any, ...], builtins.float], builtins.dict[builtins.int, builtins.float]], typing.Any] +typing.Callable[[builtins.dict[builtins.tuple[typing.Any, ...], builtins.float], builtins.dict[builtins.float, builtins.int]], typing.Any] +typing.Callable[[numpy.ma.core.MaskedConstant, builtins.dict[typing.Any, typing.Any]], typing.Any] +typing.Callable[[builtins.dict[typing.Any, typing.Any], builtins.str], typing.Any] +typing.Callable[[numpy.ma.core.MaskedConstant, builtins.dict[builtins.float, builtins.float]], typing.Any] +typing.Callable[[builtins.dict[builtins.tuple[builtins.float, ...], builtins.float], builtins.dict[builtins.float, builtins.int]], typing.Any] +typing.Callable[[builtins.dict[builtins.tuple[builtins.int, ...], builtins.float], builtins.dict[builtins.float, builtins.int]], typing.Any] +... +``` + +### OK: `def plot_robot(x, y, yaw, config)` + +`list` is strange here, but without stubs for matplotlib mypy doesn't consider this a mistake. + +``` +typing.Callable[[builtins.int, builtins.int, builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.list[typing.Any], builtins.int, builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.int, builtins.list[typing.Any], builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.int, builtins.list[builtins.int], builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.list[builtins.int], builtins.int, builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.list[builtins.list[typing.Any]], builtins.int, builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.int, builtins.int, builtins.bool, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +typing.Callable[[builtins.str, builtins.int, builtins.int, PathPlanning.DynamicWindowApproach.dynamic_window_approach.Config], typing.Any] +... +``` + +### EXTRA: `def dwa_control(x, config, goal, ob)` + +Found annotations (why?): +``` +typing.Callable[[builtins.object, builtins.object, builtins.object, builtins.object], typing.Any] +typing.Callable[[builtins.str, builtins.object, builtins.object, builtins.object], typing.Any] +typing.Callable[[builtins.object, builtins.str, builtins.object, builtins.object], typing.Any] +typing.Callable[[builtins.object, builtins.object, builtins.str, builtins.object], typing.Any] +typing.Callable[[builtins.object, builtins.object, builtins.object, builtins.str], typing.Any] +typing.Callable[[numpy.str_, builtins.object, builtins.object, builtins.object], typing.Any] +typing.Callable[[builtins.object, numpy.str_, builtins.object, builtins.object], typing.Any] +typing.Callable[[builtins.object, builtins.object, numpy.str_, builtins.object], typing.Any] +typing.Callable[[builtins.object, builtins.object, builtins.object, numpy.str_], typing.Any] +... +``` + +## File 2 + +Source: https://github.com/AtsushiSakai/PythonRobotics/blob/master/PathPlanning/DubinsPath/dubins_path_planner.py + +Top-level functions (with only positional arguments): +- [ ] `def _mod2pi(theta)` +- [x] `def _calc_trig_funcs(alpha, beta)` +- [x] `def _LSL(alpha, beta, d)` +- [x] `def _RSR(alpha, beta, d)` +- [ ] `def _LSR(alpha, beta, d)` +- [x] `def _RSL(alpha, beta, d)` +- [x] `def _RLR(alpha, beta, d)` +- [ ] `def _LRL(alpha, beta, d)` +- [x] `def _dubins_path_planning_from_origin(end_x, end_y, end_yaw, curvature, step_size, planning_funcs)` +- [x] `def _interpolate(length, mode, max_curvature, origin_x, origin_y, origin_yaw, path_x, path_y, path_yaw)` +- [x] `def _generate_local_course(lengths, modes, max_curvature, step_size)` + +Used time limit: 25 seconds. + +Command: + + java -jar utbot-cli-python-2023.02-SNAPSHOT.jar infer_types -p python3 -s /home/tochilinak/Documents/projects/utbot/PythonRobotics -t 25000 "/home/tochilinak/Documents/projects/utbot/PythonRobotics/PathPlanning/DubinsPath/dubins_path_planner.py" + +### OK: `def _calc_trig_funcs(alpha, beta)` + +``` +typing.Callable[[builtins.float, builtins.float], typing.Any] +typing.Callable[[builtins.bool, builtins.float], typing.Any] +typing.Callable[[builtins.float, builtins.bool], typing.Any] +typing.Callable[[builtins.int, builtins.float], typing.Any] +typing.Callable[[builtins.bool, builtins.bool], typing.Any] +typing.Callable[[builtins.float, builtins.int], typing.Any] +typing.Callable[[builtins.int, builtins.bool], typing.Any] +typing.Callable[[builtins.bool, builtins.int], typing.Any] +typing.Callable[[numpy.timedelta64, builtins.bool], typing.Any] +typing.Callable[[builtins.int, builtins.int], typing.Any] +typing.Callable[[builtins.bool, numpy.timedelta64], typing.Any] +typing.Callable[[enum.IntEnum, builtins.float], typing.Any] +... +``` + +### OK: `def _LSL(alpha, beta, d)` + +``` +typing.Callable[[builtins.float, builtins.float, builtins.int], typing.Any] +typing.Callable[[builtins.bool, builtins.float, builtins.int], typing.Any] +typing.Callable[[enum.IntEnum, builtins.float, builtins.int], typing.Any] +typing.Callable[[enum.auto, builtins.float, builtins.int], typing.Any] +typing.Callable[[enum.IntFlag, builtins.float, builtins.int], typing.Any] +typing.Callable[[_pytest.config.ExitCode, builtins.float, builtins.int], typing.Any] +typing.Callable[[signal.Signals, builtins.float, builtins.int], typing.Any] +typing.Callable[[signal.Handlers, builtins.float, builtins.int], typing.Any] +typing.Callable[[signal.Sigmasks, builtins.float, builtins.int], typing.Any] +typing.Callable[[builtins.complex, builtins.float, builtins.int], typing.Any] +``` + + +### OK: `def _RSR(alpha, beta, d)` + +``` +typing.Callable[[builtins.float, builtins.float, builtins.int], typing.Any] +typing.Callable[[builtins.float, builtins.int, builtins.int], typing.Any] +typing.Callable[[builtins.float, builtins.str, builtins.int], typing.Any] +typing.Callable[[builtins.float, builtins.bool, builtins.int], typing.Any] +typing.Callable[[builtins.float, enum.IntEnum, builtins.int], typing.Any] +typing.Callable[[builtins.float, enum.auto, builtins.int], typing.Any] +typing.Callable[[builtins.float, enum.IntFlag, builtins.int], typing.Any] +typing.Callable[[builtins.float, _pytest.config.ExitCode, builtins.int], typing.Any] +typing.Callable[[builtins.float, signal.Signals, builtins.int], typing.Any] +typing.Callable[[builtins.float, signal.Handlers, builtins.int], typing.Any] +typing.Callable[[builtins.float, signal.Sigmasks, builtins.int], typing.Any] +typing.Callable[[builtins.float, builtins.complex, builtins.int], typing.Any] +``` + +### OK: `def _RSL(alpha, beta, d)` + +``` +typing.Callable[[builtins.float, builtins.float, builtins.int], typing.Any] +typing.Callable[[builtins.float, builtins.float, builtins.bool], typing.Any] +typing.Callable[[builtins.float, builtins.float, enum.IntEnum], typing.Any] +typing.Callable[[builtins.float, builtins.float, enum.auto], typing.Any] +typing.Callable[[builtins.float, builtins.float, enum.IntFlag], typing.Any] +typing.Callable[[builtins.float, builtins.float, _pytest.config.ExitCode], typing.Any] +typing.Callable[[builtins.float, builtins.float, signal.Signals], typing.Any] +typing.Callable[[builtins.float, builtins.float, signal.Handlers], typing.Any] +typing.Callable[[builtins.float, builtins.float, signal.Sigmasks], typing.Any] +typing.Callable[[builtins.float, builtins.float, builtins.float], typing.Any] +typing.Callable[[builtins.float, builtins.float, _decimal.Decimal], typing.Any] +typing.Callable[[builtins.float, builtins.float, builtins.complex], typing.Any] +``` + +### OK: `def _RLR(alpha, beta, d)` + +``` +typing.Callable[[builtins.float, builtins.float, builtins.float], typing.Any] +typing.Callable[[builtins.bool, builtins.float, builtins.float], typing.Any] +typing.Callable[[builtins.float, builtins.bool, builtins.float], typing.Any] +typing.Callable[[builtins.int, builtins.float, builtins.float], typing.Any] +typing.Callable[[builtins.float, builtins.int, builtins.float], typing.Any] +typing.Callable[[enum.IntEnum, builtins.float, builtins.float], typing.Any] +typing.Callable[[builtins.float, enum.IntEnum, builtins.float], typing.Any] +typing.Callable[[enum.auto, builtins.float, builtins.float], typing.Any] +typing.Callable[[builtins.float, enum.auto, builtins.float], typing.Any] +typing.Callable[[enum.IntFlag, builtins.float, builtins.float], typing.Any] +typing.Callable[[builtins.float, enum.IntFlag, builtins.float], typing.Any] +... +``` + +### OK: `def _dubins_path_planning_from_origin(end_x, end_y, end_yaw, curvature, step_size, planning_funcs)` + +``` +typing.Callable[[builtins.int, builtins.int, builtins.int, builtins.float, builtins.int, builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.bool, builtins.int, builtins.int, builtins.float, builtins.int, builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.int, builtins.bool, builtins.int, builtins.float, builtins.int, builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.int, builtins.int, builtins.bool, builtins.float, builtins.int, builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.int, builtins.int, builtins.int, builtins.float, builtins.list[typing.Any], builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.int, builtins.int, builtins.int, builtins.float, builtins.int, builtins.list[builtins.staticmethod[typing.Any]]], typing.Any] +typing.Callable[[builtins.int, builtins.int, builtins.int, builtins.float, builtins.int, builtins.list[builtins.staticmethod[builtins.list[typing.Any]]]], typing.Any] +typing.Callable[[builtins.int, builtins.bool, builtins.int, builtins.float, builtins.int, builtins.list[builtins.staticmethod[typing.Any]]], typing.Any] +typing.Callable[[builtins.float, builtins.int, builtins.int, builtins.float, builtins.int, builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.bool, builtins.bool, builtins.int, builtins.float, builtins.int, builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.int, builtins.bool, builtins.int, builtins.float, builtins.int, builtins.list[builtins.staticmethod[builtins.list[typing.Any]]]], typing.Any] +typing.Callable[[builtins.bool, builtins.int, builtins.int, builtins.float, builtins.int, builtins.list[builtins.staticmethod[typing.Any]]], typing.Any] +typing.Callable[[builtins.int, builtins.int, builtins.int, builtins.float, builtins.int, builtins.list[builtins.staticmethod[builtins.list[builtins.float]]]], typing.Any] +... +``` + + +### OK: `def _interpolate(length, mode, max_curvature, origin_x, origin_y, origin_yaw, path_x, path_y, path_yaw)` + +``` +typing.Callable[[builtins.float, builtins.str, builtins.float, builtins.float, builtins.float, builtins.float, builtins.list[typing.Any], builtins.list[typing.Any], builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.float, builtins.str, builtins.float, builtins.float, builtins.float, builtins.float, builtins.list[builtins.float], builtins.list[builtins.float], builtins.list[builtins.float]], typing.Any] +typing.Callable[[builtins.float, builtins.str, builtins.float, builtins.float, builtins.float, builtins.float, builtins.list[builtins.object], builtins.list[builtins.float], builtins.list[builtins.float]], typing.Any] +typing.Callable[[builtins.float, builtins.str, builtins.float, builtins.float, builtins.float, builtins.float, array.array[typing.Any], builtins.list[typing.Any], builtins.list[typing.Any]], typing.Any] +typing.Callable[[builtins.float, builtins.str, builtins.float, builtins.float, builtins.float, builtins.float, array.array[builtins.float], builtins.list[builtins.float], builtins.list[builtins.float]], typing.Any] +``` + +### OK: `def _generate_local_course(lengths, modes, max_curvature, step_size)` + +``` +typing.Callable[[builtins.list[typing.Any], builtins.list[typing.Any], builtins.int, builtins.int], typing.Any] +typing.Callable[[builtins.list[builtins.int], builtins.list[builtins.int], builtins.int, builtins.int], typing.Any] +typing.Callable[[builtins.list[typing.Any], builtins.list[typing.Any], builtins.list[typing.Any], builtins.int], typing.Any] +typing.Callable[[builtins.list[typing.Any], builtins.list[typing.Any], builtins.int, builtins.bool], typing.Any] +typing.Callable[[builtins.dict[typing.Any, typing.Any], builtins.list[typing.Any], builtins.int, builtins.int], typing.Any] +typing.Callable[[builtins.dict[builtins.int, builtins.int], builtins.list[builtins.int], builtins.int, builtins.int], typing.Any] +typing.Callable[[builtins.list[builtins.int], builtins.list[builtins.int], builtins.int, builtins.bool], typing.Any] +typing.Callable[[builtins.list[builtins.int], builtins.list[builtins.list[typing.Any]], builtins.int, builtins.int], typing.Any] +typing.Callable[[builtins.list[builtins.int], builtins.list[builtins.list[builtins.int]], builtins.int, builtins.int], typing.Any] +... +``` + + +### EXTRA: `def _LSR(alpha, beta, d)` + +``` +typing.Callable[[builtins.float, builtins.object, builtins.int], typing.Any] +typing.Callable[[builtins.object, builtins.float, builtins.int], typing.Any] +typing.Callable[[builtins.float, builtins.float, builtins.int], typing.Any] +typing.Callable[[builtins.bool, builtins.object, builtins.int], typing.Any] +typing.Callable[[builtins.int, builtins.float, builtins.int], typing.Any] +typing.Callable[[builtins.float, builtins.int, builtins.int], typing.Any] +typing.Callable[[enum.IntEnum, builtins.object, builtins.int], typing.Any] +typing.Callable[[builtins.bool, builtins.float, builtins.int], typing.Any] +typing.Callable[[builtins.int, builtins.str, builtins.int], typing.Any] +typing.Callable[[builtins.str, builtins.int, builtins.int], typing.Any] +typing.Callable[[builtins.float, builtins.bool, builtins.int], typing.Any] +typing.Callable[[enum.auto, builtins.object, builtins.int], typing.Any] +typing.Callable[[enum.IntEnum, builtins.float, builtins.int], typing.Any] +``` + +### EXTRA: `def _LRL(alpha, beta, d)` + +``` +typing.Callable[[builtins.float, builtins.object, builtins.float], typing.Any] +typing.Callable[[builtins.bool, builtins.object, builtins.float], typing.Any] +typing.Callable[[builtins.float, builtins.float, builtins.float], typing.Any] +typing.Callable[[builtins.int, builtins.object, builtins.float], typing.Any] +typing.Callable[[builtins.bool, builtins.float, builtins.float], typing.Any] +typing.Callable[[builtins.float, builtins.str, builtins.float], typing.Any] +typing.Callable[[enum.IntEnum, builtins.object, builtins.float], typing.Any] +typing.Callable[[builtins.int, builtins.float, builtins.float], typing.Any] +typing.Callable[[builtins.bool, builtins.str, builtins.float], typing.Any] +typing.Callable[[builtins.float, builtins.bool, builtins.float], typing.Any] +typing.Callable[[enum.auto, builtins.object, builtins.float], typing.Any] +typing.Callable[[enum.IntEnum, builtins.float, builtins.float], typing.Any] +... +``` + + +### OK or ERROR? : `def _mod2pi(theta)` + +``` +typing.Callable[[builtins.bool], typing.Any] +typing.Callable[[builtins.object], typing.Any] +typing.Callable[[builtins.int], typing.Any] +typing.Callable[[sys.UnraisableHookArgs], typing.Any] +typing.Callable[[pathlib.PurePath], typing.Any] +typing.Callable[[pathlib.PurePosixPath], typing.Any] +typing.Callable[[pathlib.PureWindowsPath], typing.Any] +typing.Callable[[pathlib.Path], typing.Any] +typing.Callable[[pathlib.PosixPath], typing.Any] +typing.Callable[[pathlib.WindowsPath], typing.Any] +typing.Callable[[builtins.function], typing.Any] +typing.Callable[[builtins.staticmethod[typing.Any]], typing.Any] +... +``` + + +## File 3 + +Source: https://github.com/AtsushiSakai/PythonRobotics/blob/master/utils/angle.py + +Top-level functions: +- [ ] `def rot_mat_2d(angle)` +- [ ] `def angle_mod(x, zero_2_2pi, degree)` + +### EXTRA: `def rot_mat_2d(angle)` + +Incorrect annotations: +``` +typing.Callable[[builtins.object], typing.Any] +typing.Callable[[builtins.int], typing.Any] +typing.Callable[[builtins.str], typing.Any] +typing.Callable[[numpy.str_], typing.Any] +typing.Callable[[builtins.bool], typing.Any] +typing.Callable[[enum.IntEnum], typing.Any] +typing.Callable[[enum.auto], typing.Any] +typing.Callable[[enum.IntFlag], typing.Any] +typing.Callable[[signal.Signals], typing.Any] +typing.Callable[[signal.Handlers], typing.Any] +typing.Callable[[signal.Sigmasks], typing.Any] +typing.Callable[[argparse.FileType], typing.Any] +typing.Callable[[_operator.methodcaller], typing.Any] +typing.Callable[[builtins.function], typing.Any] +typing.Callable[[builtins.staticmethod[typing.Any]], typing.Any] +... +``` + +### FAIL (TODO): `def angle_mod(x, zero_2_2pi, degree)` + +??? + +## File 4 + +!!!!!!!!!!!! https://github.com/AllAlgorithms/python/blob/master/algorithms/dynamic-programming/kadanes_algorithm.py diff --git a/utbot-python/samples/run_tests.py b/utbot-python/samples/run_tests.py new file mode 100644 index 0000000000..c426fed41c --- /dev/null +++ b/utbot-python/samples/run_tests.py @@ -0,0 +1,209 @@ +""" +Example command + run_tests.py generate -c test_configuration.json + -p -o + + run_tests.py run -p -t + -c +""" +import argparse +import contextlib +import json +import os +import shutil +from subprocess import Popen, PIPE +import sys +import threading +import typing +import tqdm +from tqdm.contrib import DummyTqdmFile +import pathlib + + +def parse_arguments(): + parser = argparse.ArgumentParser( + prog="UtBot Python test", description="Generate tests for example files" + ) + subparsers = parser.add_subparsers(dest="command") + parser_generate = subparsers.add_parser("generate", help="Generate tests") + parser_generate.add_argument("java") + parser_generate.add_argument("jar") + parser_generate.add_argument("path_to_test_dir") + parser_generate.add_argument("-c", "--config_file", required=True) + parser_generate.add_argument("-p", "--python_path", required=True) + parser_generate.add_argument("-o", "--output_dir", required=True) + parser_generate.add_argument("-i", "--coverage_output_dir", required=True) + parser_run = subparsers.add_parser("run", help="Run tests") + parser_run.add_argument("-p", "--python_path", required=True) + parser_run.add_argument("-t", "--test_directory", required=True) + parser_run.add_argument("-c", "--code_directory", required=True) + parser_coverage = subparsers.add_parser("check_coverage", help="Check coverage") + parser_coverage.add_argument("-i", "--coverage_output_dir", required=True) + parser_coverage.add_argument("-c", "--config_file", required=True) + return parser.parse_args() + + +def parse_config(config_path: str): + with open(config_path, "r") as fin: + return json.loads(fin.read()) + + +@contextlib.contextmanager +def std_out_err_redirect_tqdm(): + orig_out_err = sys.stdout, sys.stderr + try: + sys.stdout, sys.stderr = map(DummyTqdmFile, orig_out_err) + yield orig_out_err[0] + # Relay exceptions + except Exception as exc: + raise exc + # Always restore sys.stdout/err if necessary + finally: + sys.stdout, sys.stderr = orig_out_err + + +def generate_tests( + java: str, + jar_path: str, + sys_paths: list[str], + python_path: str, + file_under_test: str, + timeout: int, + output: str, + coverage_output: str, + class_names: typing.Optional[list[str]] = None, + method_names: typing.Optional[list[str]] = None, +): + command = f"{java} -jar {jar_path} generate_python {file_under_test}.py -p {python_path} -o {output} -s {' '.join(sys_paths)} --timeout {timeout * 1000} --install-requirements --runtime-exception-behaviour PASS --coverage={coverage_output}" + if class_names is not None: + command += f" -c {','.join(class_names)}" + if method_names is not None: + command += f" -m {','.join(method_names)}" + tqdm.tqdm.write("\n" + command) + + def stdout_printer(p): + for line in p.stdout: + tqdm.tqdm.write(line.rstrip().decode()) + + p = Popen(command.split(), stdout=PIPE) + t = threading.Thread(target=stdout_printer, args=(p,)) + t.run() + + +def run_tests( + python_path: str, + tests_dir: str, + samples_dir: str, +): + command = f'{python_path} -m coverage run --source={samples_dir} -m unittest discover -p "utbot_*" {tests_dir}' + tqdm.tqdm.write(command) + code = os.system(command) + return code + + +def check_coverage( + config_file: str, + coverage_output_dir: str, +): + config = parse_config(config_file) + report: typing.Dict[str, bool] = {} + coverage: typing.Dict[str, typing.Tuple[float, float]] = {} + for part in config["parts"]: + for file in part["files"]: + for i, group in enumerate(file["groups"]): + if i > 0: + suffix = f"_{i}" + else: + suffix = "" + + expected_coverage = group.get("coverage", 0) + + file_suffix = f"{part['path'].replace('/', '_')}_{file['name']}{suffix}" + coverage_output_file = pathlib.Path( + coverage_output_dir, f"coverage_{file_suffix}.json" + ) + if coverage_output_file.exists(): + with open(coverage_output_file, "rt") as fin: + actual_coverage_json = json.loads(fin.readline()) + actual_covered = sum( + lines["end"] - lines["start"] + 1 + for lines in actual_coverage_json["covered"] + ) + actual_not_covered = sum( + lines["end"] - lines["start"] + 1 + for lines in actual_coverage_json["notCovered"] + ) + if actual_covered + actual_not_covered == 0: + actual_coverage = 0 + else: + actual_coverage = round( + actual_covered / (actual_not_covered + actual_covered) * 100 + ) + else: + actual_coverage = 0 + + coverage[file_suffix] = (actual_coverage, expected_coverage) + report[file_suffix] = actual_coverage >= expected_coverage + if all(report.values()): + return True + print("-------------") + print("Bad coverage:") + print("-------------") + for file, good_coverage in report.items(): + if not good_coverage: + print(f"{file}: {coverage[file][0]}/{coverage[file][1]}") + return False + + +def main_test_generation(args): + config = parse_config(args.config_file) + if pathlib.Path(args.coverage_output_dir).exists(): + shutil.rmtree(args.coverage_output_dir) + with std_out_err_redirect_tqdm() as orig_stdout: + for part in tqdm.tqdm( + config["parts"], file=orig_stdout, dynamic_ncols=True, desc="Progress" + ): + for file in tqdm.tqdm( + part["files"], file=orig_stdout, dynamic_ncols=True, desc=part["path"] + ): + for i, group in enumerate(file["groups"]): + if i > 0: + suffix = f"_{i}" + else: + suffix = "" + + full_name = pathlib.PurePath( + args.path_to_test_dir, part["path"], file["name"] + ) + output_file = pathlib.PurePath( + args.output_dir, + f"utbot_tests_{part['path'].replace('/', '_')}_{file['name']}{suffix}.py", + ) + coverage_output_file = pathlib.PurePath( + args.coverage_output_dir, + f"coverage_{part['path'].replace('/', '_')}_{file['name']}{suffix}.json", + ) + generate_tests( + args.java, + args.jar, + [args.path_to_test_dir], + args.python_path, + str(full_name), + group["timeout"], + str(output_file), + str(coverage_output_file), + group["classes"], + group["methods"], + ) + + +if __name__ == "__main__": + arguments = parse_arguments() + if arguments.command == "generate": + main_test_generation(arguments) + elif arguments.command == "run": + run_tests( + arguments.python_path, arguments.test_directory, arguments.code_directory + ) + elif arguments.command == "check_coverage": + check_coverage(arguments.config_file, arguments.coverage_output_dir) diff --git a/utbot-python/samples/run_tests_all.sh b/utbot-python/samples/run_tests_all.sh new file mode 100755 index 0000000000..a22828dd4f --- /dev/null +++ b/utbot-python/samples/run_tests_all.sh @@ -0,0 +1,9 @@ +python_path=$1 + +rm -r utbot_coverage utbot_tests RUN_RESULT COVERAGE_RESULT +mkdir utbot_coverage +$python_path run_tests.py generate java ../../utbot-cli-python/build/libs/utbot-cli-python*.jar `pwd` -c test_configuration.json -p $python_path -o utbot_tests -i utbot_coverage + +$python_path run_tests.py run -p $python_path -c test_configuration.json -t utbot_tests > RUN_RESULT 2> RUN_RESULT + +$python_path run_tests.py check_coverage -i utbot_coverage/ -c test_configuration.json > COVERAGE_RESULT 2> COVERAGE_RESULT diff --git a/utbot-python/samples/samples/__init__.py b/utbot-python/samples/samples/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/algorithms/__init__.py b/utbot-python/samples/samples/algorithms/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/algorithms/bfs.py b/utbot-python/samples/samples/algorithms/bfs.py new file mode 100644 index 0000000000..727bcb7ba2 --- /dev/null +++ b/utbot-python/samples/samples/algorithms/bfs.py @@ -0,0 +1,42 @@ +from __future__ import annotations +from collections import deque +from typing import List + + +class Node: + def __init__(self, name: str, children: List[Node]): + self.name = name + self.children = children + + def __repr__(self): + return f'' + + def __eq__(self, other): + if isinstance(other, Node): + return self.name == other.name + else: + return False + + +def bfs(nodes: List[Node]): + if len(nodes) == 0: + return [] + + visited = [] + queue = deque(nodes) + while len(queue) > 0: + node = queue.pop() + if node not in visited: + visited.append(node) + for child in node.children: + queue.append(child) + return visited + + +if __name__ == '__main__': + a = Node('a', []) + b = Node('b', []) + c = Node('c', []) + a.children.append(b) + b.children.append(c) + print(bfs([a, b, c])) diff --git a/utbot-python/samples/samples/algorithms/floyd_warshall.py b/utbot-python/samples/samples/algorithms/floyd_warshall.py new file mode 100644 index 0000000000..a85e042104 --- /dev/null +++ b/utbot-python/samples/samples/algorithms/floyd_warshall.py @@ -0,0 +1,42 @@ +import math + + +class Graph: + def __init__(self, n=0): # a graph with Node 0,1,...,N-1 + self.n = n + self.w = [ + [math.inf for j in range(0, n)] for i in range(0, n) + ] # adjacency matrix for weight + self.dp = [ + [math.inf for j in range(0, n)] for i in range(0, n) + ] # dp[i][j] stores minimum distance from i to j + + def add_edge(self, u, v, w): + self.dp[u][v] = w + + def floyd_warshall(self): + for k in range(0, self.n): + for i in range(0, self.n): + for j in range(0, self.n): + self.dp[i][j] = min(self.dp[i][j], self.dp[i][k] + self.dp[k][j]) + + def show_min(self, u, v): + return self.dp[u][v] + + +if __name__ == "__main__": + graph = Graph(5) + graph.add_edge(0, 2, 9) + graph.add_edge(0, 4, 10) + graph.add_edge(1, 3, 5) + graph.add_edge(2, 3, 7) + graph.add_edge(3, 0, 10) + graph.add_edge(3, 1, 2) + graph.add_edge(3, 2, 1) + graph.add_edge(3, 4, 6) + graph.add_edge(4, 1, 3) + graph.add_edge(4, 2, 4) + graph.add_edge(4, 3, 9) + graph.floyd_warshall() + graph.show_min(1, 4) + graph.show_min(0, 3) \ No newline at end of file diff --git a/utbot-python/samples/samples/algorithms/longest_subsequence.py b/utbot-python/samples/samples/algorithms/longest_subsequence.py new file mode 100644 index 0000000000..1200f8fb5a --- /dev/null +++ b/utbot-python/samples/samples/algorithms/longest_subsequence.py @@ -0,0 +1,27 @@ +from typing import List + + +def longest_subsequence(array: List[int]) -> List[int]: + array_length = len(array) + if array_length <= 1: + return array + pivot = array[0] + is_found = False + i = 1 + longest_subseq: List[int] = [] + while not is_found and i < array_length: + if array[i] < pivot: + is_found = True + temp_array = [element for element in array[i:] if element >= array[i]] + temp_array = longest_subsequence(temp_array) + if len(temp_array) > len(longest_subseq): + longest_subseq = temp_array + else: + i += 1 + + temp_array = [element for element in array[1:] if element >= pivot] + temp_array = [pivot] + longest_subsequence(temp_array) + if len(temp_array) > len(longest_subseq): + return temp_array + else: + return longest_subseq diff --git a/utbot-python/samples/samples/algorithms/quick_sort.py b/utbot-python/samples/samples/algorithms/quick_sort.py new file mode 100644 index 0000000000..a58f6d3bea --- /dev/null +++ b/utbot-python/samples/samples/algorithms/quick_sort.py @@ -0,0 +1,32 @@ +import random +from typing import List + + +def quick_sort(array: List[int]): + def partition(A, left_index, right_index): + pivot = A[left_index] + i = left_index + 1 + for j in range(left_index + 1, right_index): + if A[j] < pivot: + A[j], A[i] = A[i], A[j] + i += 1 + A[left_index], A[i - 1] = A[i - 1], A[left_index] + return i - 1 + + def quick_sort_random(A, left, right): + if left < right: + pivot = random.randint(left, right - 1) + A[pivot], A[left] = ( + A[left], + A[pivot], + ) # switches the pivot with the left most bound + pivot_index = partition(A, left, right) + quick_sort_random( + A, left, pivot_index + ) # recursive quicksort to the left of the pivot point + quick_sort_random( + A, pivot_index + 1, right + ) # recursive quicksort to the right of the pivot point + quick_sort_random(array, 0, len(array)) + return array + diff --git a/utbot-python/samples/samples/classes/__init__.py b/utbot-python/samples/samples/classes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/classes/constructors.py b/utbot-python/samples/samples/classes/constructors.py new file mode 100644 index 0000000000..aac8cd71c1 --- /dev/null +++ b/utbot-python/samples/samples/classes/constructors.py @@ -0,0 +1,36 @@ +class EmptyClass: + pass + + +class WithInitClass: + def __init__(self, x: int): + self.x = x + + +class EmptyInitClass: + def __init__(self): + pass + + +class BasicNewCass: + def __new__(cls, *args, **kwargs): + super().__new__(cls, *args, **kwargs) + + +class ParentEmptyInitClass(EmptyClass): + pass + + +class ParentWithInitClass(WithInitClass): + pass + + +class ParentEmptyClass(EmptyClass): + pass + + +def func(a: EmptyClass, b: WithInitClass, c: EmptyInitClass, d: BasicNewCass, e: ParentEmptyClass, + f: ParentWithInitClass, g: ParentEmptyInitClass): + return a.__class__.__name__ + str( + b.x) + c.__class__.__name__ + d.__class__.__name__ + e.__class__.__name__ + str( + f.x) + g.__class__.__name__ diff --git a/utbot-python/samples/samples/classes/dataclass.py b/utbot-python/samples/samples/classes/dataclass.py new file mode 100644 index 0000000000..0fd03ee182 --- /dev/null +++ b/utbot-python/samples/samples/classes/dataclass.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass + + +@dataclass +class C: + counter: int = 0 + + def inc(self): + self.counter += 1 diff --git a/utbot-python/samples/samples/classes/dicts.py b/utbot-python/samples/samples/classes/dicts.py new file mode 100644 index 0000000000..3b0e25a549 --- /dev/null +++ b/utbot-python/samples/samples/classes/dicts.py @@ -0,0 +1,35 @@ +from typing import List, Dict, Optional + + +class Word: + def __init__(self, translations: Dict[str, str]): + self.translations = translations + + def __eq__(self, other): + return self.translations == other.translations + + def keys(self): + return list(self.translations.keys()) + + +class Dictionary: + def __init__( + self, + languages: List[str], + words: List[Dict[str, str]], + ): + self.languages = languages + self.words = [Word(translations) for translations in words] + + def __eq__(self, other): + return self.languages == other.languages and self.words == other.words + + def translate(self, word: str, language: Optional[str]): + if language is not None: + for word_ in self.words: + if word_.translations[language] == word: + return word_ + else: + for word_ in self.words: + if word in word_.translations.values(): + return word_ diff --git a/utbot-python/samples/samples/classes/easy_class.py b/utbot-python/samples/samples/classes/easy_class.py new file mode 100644 index 0000000000..2e3d679714 --- /dev/null +++ b/utbot-python/samples/samples/classes/easy_class.py @@ -0,0 +1,9 @@ +class B: + def __init__(self, val: complex): + self.description = val + + def __eq__(self, other): + return self.description == other.description + + def sqrt(self): + return self.description ** 0.5 \ No newline at end of file diff --git a/utbot-python/samples/samples/classes/equals.py b/utbot-python/samples/samples/classes/equals.py new file mode 100644 index 0000000000..6ed34ec5fa --- /dev/null +++ b/utbot-python/samples/samples/classes/equals.py @@ -0,0 +1,23 @@ +class WithEqual: + def __init__(self, x: int): + self.x = x + + def __eq__(self, other): + return self.x == other.x + + +class WithoutEqual: + def __init__(self, x: int): + self.x = x + + +class WithoutEqualChild(WithoutEqual): + def __init__(self, x: int): + super().__init__(x) + + def __eq__(self, other): + return self.x == other.x + + +def f1(a: WithEqual, b: WithoutEqual, c: WithoutEqualChild): + return a.x + b.x + c.x diff --git a/utbot-python/samples/samples/classes/inner_class.py b/utbot-python/samples/samples/classes/inner_class.py new file mode 100644 index 0000000000..e77d866b1a --- /dev/null +++ b/utbot-python/samples/samples/classes/inner_class.py @@ -0,0 +1,12 @@ +class Outer: + class Inner: + a = 1 + + def inc(self): + self.a += 1 + + def __init__(self): + self.inner = Outer.Inner() + + def inc1(self): + self.inner.inc() \ No newline at end of file diff --git a/utbot-python/samples/samples/classes/rename_self.py b/utbot-python/samples/samples/classes/rename_self.py new file mode 100644 index 0000000000..b4081820fe --- /dev/null +++ b/utbot-python/samples/samples/classes/rename_self.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass + + +@dataclass +class Inner: + x: int + + +class A: + def __init__(self, x: Inner): + self.x = x + + def f(cls, self, x: Inner): + self.x += 1 + return cls.x.x, self, x diff --git a/utbot-python/samples/samples/classes/setstate_test.py b/utbot-python/samples/samples/classes/setstate_test.py new file mode 100644 index 0000000000..85dcb32e87 --- /dev/null +++ b/utbot-python/samples/samples/classes/setstate_test.py @@ -0,0 +1,20 @@ +import typing + + +class MyClass: + __slots__ = ['a', 'b'] + + def __init__(self, a: int, b: typing.Dict[int, str]): + self.a = a + self.b = b + + def __eq__(self, other): + return self.a == other.a and self.b == other.b + + def __setstate__(self, state): + self.a = state[1]['a'] + self.b = state[1]['b'] + + +def make_my_class(a: int, b: typing.Dict[int, str]) -> MyClass: + return MyClass(a, b) diff --git a/utbot-python/samples/samples/collection/__init__.py b/utbot-python/samples/samples/collection/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/collection/dicts.py b/utbot-python/samples/samples/collection/dicts.py new file mode 100644 index 0000000000..52651e8893 --- /dev/null +++ b/utbot-python/samples/samples/collection/dicts.py @@ -0,0 +1,24 @@ +import typing + + +class MyClass: + def __init__(self, x: int): + self.x = x + + def __eq__(self, other): + return self.x == other.x + + def __hash__(self): + return hash(self.x) + + +def f(x: typing.Dict[int, int]): + if len(x) == 0: + return "Empty!" + return len(x) + + +def g(x: typing.Dict[MyClass, int]): + if len(x) == 0: + return "Empty!" + return len(x) diff --git a/utbot-python/samples/samples/collection/lists.py b/utbot-python/samples/samples/collection/lists.py new file mode 100644 index 0000000000..005f16401b --- /dev/null +++ b/utbot-python/samples/samples/collection/lists.py @@ -0,0 +1,31 @@ +from dataclasses import dataclass +import datetime +from typing import List + + +@dataclass +class Article: + title: str + author: str + content: str + created_at: datetime.datetime + + +def find_articles_with_author(articles: List[Article], author: str) -> List[Article]: + return [ + article for article in articles + if article.author == author + ] + + +def f(x: List[int]): + if len(x) == 0: + return "Empty!" + return sum(x) + + +if __name__ == '__main__': + print(find_articles_with_author([ + Article('a', 'a1', 'jfls', datetime.datetime.today()), + Article('b', 'a2', 'fjls', datetime.datetime.now()) + ], 'a1')) diff --git a/utbot-python/samples/samples/collection/recursive.py b/utbot-python/samples/samples/collection/recursive.py new file mode 100644 index 0000000000..5d2026f111 --- /dev/null +++ b/utbot-python/samples/samples/collection/recursive.py @@ -0,0 +1,19 @@ +import typing + + +def make_recursive_list(x: int): + xs = [1, 2] + xs.append(xs) + xs.append(x) + return xs + + +def make_recursive_dict(x: int, y: int): + d = {1: 2} + d[x] = d + d[y] = x + return d + + +if __name__ == '__main__': + make_recursive_dict(3, 4) \ No newline at end of file diff --git a/utbot-python/samples/samples/collection/sets.py b/utbot-python/samples/samples/collection/sets.py new file mode 100644 index 0000000000..2913fbcc58 --- /dev/null +++ b/utbot-python/samples/samples/collection/sets.py @@ -0,0 +1,24 @@ +import typing + + +class MyClass: + def __init__(self, x: int): + self.x = x + + def __eq__(self, other): + return self.x == other.x + + def __hash__(self): + return hash(self.x) + + +def f(x: typing.Set[int]): + if len(x) == 0: + return "Empty!" + return len(x) + + +def g(x: typing.Set[MyClass]): + if len(x) == 0: + return "Empty!" + return len(x) diff --git a/utbot-python/samples/samples/collection/tuples.py b/utbot-python/samples/samples/collection/tuples.py new file mode 100644 index 0000000000..2ea141806e --- /dev/null +++ b/utbot-python/samples/samples/collection/tuples.py @@ -0,0 +1,36 @@ +import typing + + +class MyClass: + def __init__(self, x: int): + self.x = x + + def __eq__(self, other): + return self.x == other.x + + def __hash__(self): + return hash(self.x) + + +def f(x: typing.Tuple[int, ...]): + if len(x) == 0: + return "Empty!" + return len(x) + + +def f1(x: typing.Tuple[int, int, int]): + if len(x) != 3: + return "Very bad input!!!" + return x[0] + x[1] + x[2] + + +def g(x: typing.Tuple[MyClass, ...]): + if len(x) == 0: + return "Empty!" + return len(x) + + +def g1(x: typing.Tuple[MyClass, MyClass]): + if len(x) != 2: + return "Very bad input!!!" + return x[0].x + x[1].x diff --git a/utbot-python/samples/samples/collection/using_collections.py b/utbot-python/samples/samples/collection/using_collections.py new file mode 100644 index 0000000000..e8a5e9e1ae --- /dev/null +++ b/utbot-python/samples/samples/collection/using_collections.py @@ -0,0 +1,17 @@ +import collections + + +def generate_collections(collection): + collection[0] = 100 + if isinstance(collection, collections.UserDict): + return collection.data + elements = list(collection.items()) + return [ + collection, + collections.Counter(collection), + elements + ] + + +if __name__ == '__main__': + print(generate_collections({1: 2})) diff --git a/utbot-python/samples/samples/controlflow/__init__.py b/utbot-python/samples/samples/controlflow/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/controlflow/arithmetic.py b/utbot-python/samples/samples/controlflow/arithmetic.py new file mode 100644 index 0000000000..6f26aa94f4 --- /dev/null +++ b/utbot-python/samples/samples/controlflow/arithmetic.py @@ -0,0 +1,17 @@ +import math + + +def calculate_function_value(x, y): + """ + Calculate value `f` + | sqrt(x - 2y) , x > 100 + f(x, y) = | (3x^2 - 2xy + y^2) / sin(x) , -100 < x <= 100 + | (0.01 * x) ^ log2(y) , x < -100 + """ + + if x > 100: + return math.sqrt(x - 2 * y) + elif -100 < x <= 100: + return (3*x**2 - 2*x*y + y**2) / math.sin(x) + else: + return (0.01 * x) ** math.log2(y) diff --git a/utbot-python/samples/samples/controlflow/conditions.py b/utbot-python/samples/samples/controlflow/conditions.py new file mode 100644 index 0000000000..3897f578b1 --- /dev/null +++ b/utbot-python/samples/samples/controlflow/conditions.py @@ -0,0 +1,18 @@ +def f(x): + if x == 10: + return 100 + if x == 20: + return 200 + if x == 30: + return 300 + if x == 40: + return 400 + if x == 50: + return 500 + if x == 60: + return 600 + if x < 0: + return x ** 2 + if x > 0: + return 239 + return 100500 diff --git a/utbot-python/samples/samples/controlflow/inner_conditions.py b/utbot-python/samples/samples/controlflow/inner_conditions.py new file mode 100644 index 0000000000..9a2740abfb --- /dev/null +++ b/utbot-python/samples/samples/controlflow/inner_conditions.py @@ -0,0 +1,12 @@ +def hard_function(x): + if x % 100 == 0: + return 1 + elif x + 100 < 400: + return 2 + else: + if x == complex(1, 2): + return x + elif len(str(x)) > 3: + return 3 + else: + return 4 diff --git a/utbot-python/samples/samples/controlflow/multi_conditions.py b/utbot-python/samples/samples/controlflow/multi_conditions.py new file mode 100644 index 0000000000..b7ba4bce58 --- /dev/null +++ b/utbot-python/samples/samples/controlflow/multi_conditions.py @@ -0,0 +1,14 @@ +def check_interval(x: float, left: float, right: float) -> str: + if left < x < right or right < x < left: + return "between" + elif x < left and x < right: + return "less" + elif x > left and x > right: + return "more" + elif left == right: + return "all equals" + elif x == left: + return "left" + elif x == right: + return "right" + return "what?" diff --git a/utbot-python/samples/samples/easy_samples/__init__.py b/utbot-python/samples/samples/easy_samples/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/easy_samples/deep_equals.py b/utbot-python/samples/samples/easy_samples/deep_equals.py new file mode 100644 index 0000000000..ee3eeeacd4 --- /dev/null +++ b/utbot-python/samples/samples/easy_samples/deep_equals.py @@ -0,0 +1,79 @@ +from __future__ import annotations +from typing import List + + +class ComparableClass: + def __init__(self, x): + self.x = x + + def __eq__(self, other): + return self.x == other.x + + +class BadClass: + def __init__(self, x): + self.x = x + + +def return_bad_class(x: int): + return BadClass(x) + + +def return_comparable_class(x: int): + return ComparableClass(x) + + +def primitive_list(x: int): + return [x] * 10 + + +def primitive_set(x: int): + return set(x+i for i in range(5)) + + +def primitive_dict(x: str, y: int): + return {x: y} + + +def comparable_list(length: int): + return [ComparableClass(x) for x in range(min(length, 10))] + + +def bad_list(length: int): + return [BadClass(x) for x in range(min(length, 10))] + + +class Node: + name: str + children: List[Node] + + def __init__(self, name: str): + self.name = name + self.children = [] + + def __str__(self): + return f'' + + def __eq__(self, other): + if isinstance(other, Node): + return self.name == other.name + else: + return False + + +def cycle(x: str): + a = Node(x + '_a') + b = Node(x + '_b') + a.children.append(b) + b.children.append(a) + return a + + +def cycle2(x: str): + a = Node(x + '_a') + b = Node(x + '_b') + c = Node(x + '_c') + a.children.append(b) + b.children.append(c) + c.children.append(a) + return a diff --git a/utbot-python/samples/samples/easy_samples/deep_equals_2.py b/utbot-python/samples/samples/easy_samples/deep_equals_2.py new file mode 100644 index 0000000000..f34cb42c5b --- /dev/null +++ b/utbot-python/samples/samples/easy_samples/deep_equals_2.py @@ -0,0 +1,22 @@ +class ComparableClass: + def __init__(self, x): + self.x = x + + def __eq__(self, other): + return self.x == other.x + + +class IncomparableClass: + def __init__(self, x): + self.x = x + + def __eq__(self, other): + return id(self) == id(other) + + +def comparable_list(length: int): + return [ComparableClass(x) for x in range(min(length, 10))] + + +def incomparable_list(length: int): + return [IncomparableClass(x) for x in range(min(length, 10))] diff --git a/utbot-python/samples/samples/easy_samples/dummy_with_eq.py b/utbot-python/samples/samples/easy_samples/dummy_with_eq.py new file mode 100644 index 0000000000..05ef7b822e --- /dev/null +++ b/utbot-python/samples/samples/easy_samples/dummy_with_eq.py @@ -0,0 +1,9 @@ +class Dummy: + def __init__(self, value: int): + self.field = value + + def __eq__(self, other): + return self.field == other.field + + def propagate(self): + return [self, self] diff --git a/utbot-python/samples/samples/easy_samples/dummy_without_eq.py b/utbot-python/samples/samples/easy_samples/dummy_without_eq.py new file mode 100644 index 0000000000..8df59b68d2 --- /dev/null +++ b/utbot-python/samples/samples/easy_samples/dummy_without_eq.py @@ -0,0 +1,3 @@ +class Dummy: + def propagate(self): + return [self, self] diff --git a/utbot-python/samples/samples/easy_samples/fully_annotated.py b/utbot-python/samples/samples/easy_samples/fully_annotated.py new file mode 100644 index 0000000000..15854099d1 --- /dev/null +++ b/utbot-python/samples/samples/easy_samples/fully_annotated.py @@ -0,0 +1,78 @@ +import heapq +from datetime import datetime +from typing import Any, List, Union, NoReturn + +""" +Default functions suite: fully annotated. +""" + + +def id_(x: Any) -> Any: + return x + + +def compare_with_5(x: int) -> bool: + return x > 5 + + +def add(x: int, y: int) -> int: + return x + y + + +def add_with_unused_param(x: int, y: int, unused: int) -> int: + return x + y + + +def append_exclamation_mark(s: str) -> str: + return s + "!" + + +def append_two_ints_to_typing_list(l: List[int]) -> List[int]: + return l + [1, -1] + + +def append_two_ints_to_builtin_list(l: list) -> list: + return l + [1, -1] + + +def append_ints_and_chars(l: List[Union[int, str]]) -> List[Union[int, str]]: + return l + [1, -1] + list("ab") + + +def format_data_labels(dates: List[datetime]) -> List[str]: + if all(x.hour == 0 and x.minute == 0 for x in dates): + return [x.strftime('%Y-%m-%d') for x in dates] + else: + return [x.strftime('%H:%M') for x in dates] + + +class ClassWithIntField: + def __init__(self, int_field_value): + self.int_field = int_field_value + + +class ClassWithAnnotatedIntField: + def __init__(self, int_field: int): + self.int_field = int_field + + +def inc_int_field(c: ClassWithIntField) -> int: + c.int_field += 1 + return c.int_field + + +def call_heapify(ints: List[int]) -> List[int]: + heapq.heapify(ints) + return ints + + +def concatenate_args(*args: str) -> str: + return "+".join(args) + + +def concatenate_args_and_kwargs(*args: str, **kwargs: str) -> str: + return "+".join(args) + ";" + "?".join(kwargs.values()) + + +def raise_exception(exc: Exception) -> NoReturn: + raise exc diff --git a/utbot-python/samples/samples/easy_samples/general.py b/utbot-python/samples/samples/easy_samples/general.py new file mode 100644 index 0000000000..40630c2550 --- /dev/null +++ b/utbot-python/samples/samples/easy_samples/general.py @@ -0,0 +1,200 @@ +import collections +import heapq +import typing +from socket import socket +from typing import List, Dict, Set, Optional, AbstractSet +from dataclasses import dataclass + + +class Dummy: + def propagate(self): + return [self, self] + + @staticmethod + def abs(x): + return abs(x) + + +def dict_f(x, a, b, c): + y = {1: 2} + if x == y: + return 1 + x = a + x = b + x = c + return 2 + + +def fact(n): + ans = 1 + for i in range(1, n + 1): + ans *= i + return ans + + +def empty_(): + pass + + +def conditions(x): + if x % 100 == 0: + return 1 + elif x + 100 < 400: + return 2 + else: + if x == complex(1, 2): + return x.real + elif len(str(x)) > 3: + return 3 + else: + return 4 + + +def test_call(x): + return repr_test(x) + + +def zero_division(x): + return x / x + + +def repr_test(x): + x *= 100 + return [1, x + 1, collections.UserList([1, 2, 3]), collections.Counter("flkafksdf"), collections.OrderedDict({1: 2, 4: "jflas"})] + + +def str_test(x): + x += '1"23' + x += "flskjd'jfslk" + if len(x.split('.')) == 1: + return '1"23' + else: + return """100''500""" + + +def return_socket(x: int): + return socket() + + +def empty(): + return 1 + + +def id_(x: AbstractSet[int]): + return x + + +def f(x, y, z, a, c, d, e, g, h, i): + if y % 2 == 0: + x = 1 + y + z += "aba" + a += [2] + list("str") + i + A = c < "abc" + B = "abc" == d + e = {1, 2, 3} + C = g == {1: 2} + h += int("777") + return x + y + + +def g(x: List[int], y: List): + y[0] += 1 + return x, y + + +def i(x: Dict[int, int]): + return x[0] + + +def j(x: Set[int]): + return x + + +def h(x): + if x < 123: + return 1 + return 2 + + +def a(x): + x.description += 1 + return x.description + + +def sqrt(x): + return x.sqrt() + + +@dataclass +class InventoryItem: + name: str + + +def inv(x): + return x.name + "aba" # interesting case with io.BytesIO + + +def b(x, y): + y = len(x) + return bytes(x, 'utf-8') + + +def c(x): + return heapq.heapify(x) + + +def d(x: Optional[int]): + return x + + +def k(x: typing.Any): + if x == complex(1): + return x + + +def constants(x): + if x == 1e5: + return "one" + elif (x > 1e4 - 2) and (x < 1e4): + return "two" + else: + return "three" + + +# interesting case with sets +def get_data_labels(dates): + if len(dates) == 0: + return None + if all(x.hour == 0 and x.minute == 0 for x in dates): + return [x.strftime('%Y-%m-%d') for x in dates] + else: + return [x.strftime('%H:%M') for x in dates] + +""" +# bad function +def m(x): + x = frozenset() + return len(x + 1) + + +# very bad function +def n(x, y): + y = (-x) + 1 + x *= 10 + # z = -x + print(x) + x = len([1]) + if y == len([1]): + y += print() + return x.description +""" + + +def list_of_list(x: List[List[InventoryItem]]): + return x + + +def make_tuple(x: list[int]): + if len(x) == 0: + return tuple() + return tuple(x) diff --git a/utbot-python/samples/samples/easy_samples/long_function_coverage.py b/utbot-python/samples/samples/easy_samples/long_function_coverage.py new file mode 100644 index 0000000000..64cbccc0c0 --- /dev/null +++ b/utbot-python/samples/samples/easy_samples/long_function_coverage.py @@ -0,0 +1,11 @@ +import time + + +def long_function(x: int): + x += 4 + x /= 2 + x += 100 + x *= 3 + x -= 15 + time.sleep(2000) + return x diff --git a/utbot-python/samples/samples/easy_samples/my_func.py b/utbot-python/samples/samples/easy_samples/my_func.py new file mode 100644 index 0000000000..27b63c830b --- /dev/null +++ b/utbot-python/samples/samples/easy_samples/my_func.py @@ -0,0 +1,6 @@ +def my_func(x: int, xs: list[int]): + if len(xs) == x: + return x ** 2 + elif not xs: + return x + return len(xs) diff --git a/utbot-python/samples/samples/exceptions/__init__.py b/utbot-python/samples/samples/exceptions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/exceptions/exception_examples.py b/utbot-python/samples/samples/exceptions/exception_examples.py new file mode 100644 index 0000000000..c052935f66 --- /dev/null +++ b/utbot-python/samples/samples/exceptions/exception_examples.py @@ -0,0 +1,53 @@ +import typing + +from samples.exceptions.my_checked_exception import MyCheckedException + + +def init_array(n: int): + try: + a: typing.List[typing.Optional[int]] = [None] * n + a[n-1] = n + 1 + a[n-2] = n + 2 + return a[n-1] + a[n-2] + except ImportError: + return -1 + except IndexError: + return -2 + + +def nested_exceptions(i: int): + try: + return check_all(i) + except IndexError: + return 100 + except ValueError: + return -100 + + +def check_positive(i: int): + if i > 0: + raise IndexError("Positive") + return 0 + + +def check_all(i: int): + if i < 0: + raise ValueError("Negative") + return check_positive(i) + + +def throw_exception(i: int): + r = 1 + if i > 0: + r += 10 + r -= (i + r) / 0 + else: + r += 100 + return r + + +def throw_my_exception(i: int): + if i > 0: + raise MyCheckedException("i > 0") + return i ** 2 + diff --git a/utbot-python/samples/samples/exceptions/my_checked_exception.py b/utbot-python/samples/samples/exceptions/my_checked_exception.py new file mode 100644 index 0000000000..3be517e3a4 --- /dev/null +++ b/utbot-python/samples/samples/exceptions/my_checked_exception.py @@ -0,0 +1,6 @@ +class MyCheckedException(Exception): + def __init__(self, x: str): + self.x = x + + def method(self, y: str): + return self.x == y diff --git a/utbot-python/samples/samples/imports/__init__.py b/utbot-python/samples/samples/imports/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/imports/builtins_module/__init__.py b/utbot-python/samples/samples/imports/builtins_module/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/imports/builtins_module/crypto.py b/utbot-python/samples/samples/imports/builtins_module/crypto.py new file mode 100644 index 0000000000..5315d61e37 --- /dev/null +++ b/utbot-python/samples/samples/imports/builtins_module/crypto.py @@ -0,0 +1,15 @@ +import hashlib +from collections import Counter + + +def f(word: str): + m = hashlib.sha256() + m.update(word.encode()) + code = m.hexdigest() + if len(code) > len(word): + return Counter(code) + return Counter(word) + + +if __name__ == '__main__': + print(f("fjasld")) diff --git a/utbot-python/samples/samples/imports/pack_1/__init__.py b/utbot-python/samples/samples/imports/pack_1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/imports/pack_1/inner_pack_1/__init__.py b/utbot-python/samples/samples/imports/pack_1/inner_pack_1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/imports/pack_1/inner_pack_1/inner_mod_1.py b/utbot-python/samples/samples/imports/pack_1/inner_pack_1/inner_mod_1.py new file mode 100644 index 0000000000..a8112a5831 --- /dev/null +++ b/utbot-python/samples/samples/imports/pack_1/inner_pack_1/inner_mod_1.py @@ -0,0 +1,7 @@ +from ..inner_pack_2 import inner_mod_2 + + +def inner_func_1(a: int): + if a > 1: + return inner_mod_2.inner_func_2(a) + return a ** 2 diff --git a/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_2.py b/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_2.py new file mode 100644 index 0000000000..8d349164a7 --- /dev/null +++ b/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_2.py @@ -0,0 +1,5 @@ +from ..inner_pack_2 import inner_mod_3 + + +def inner_func_2(a: int): + return inner_mod_3.inner_func_3(a) diff --git a/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_3.py b/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_3.py new file mode 100644 index 0000000000..60d7ab5ea2 --- /dev/null +++ b/utbot-python/samples/samples/imports/pack_1/inner_pack_2/inner_mod_3.py @@ -0,0 +1,2 @@ +def inner_func_3(x: float): + return int(x) diff --git a/utbot-python/samples/samples/mathematics/__init__.py b/utbot-python/samples/samples/mathematics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/named_arguments/__init__.py b/utbot-python/samples/samples/named_arguments/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/named_arguments/method_named_arguments.py b/utbot-python/samples/samples/named_arguments/method_named_arguments.py new file mode 100644 index 0000000000..f86c3d623a --- /dev/null +++ b/utbot-python/samples/samples/named_arguments/method_named_arguments.py @@ -0,0 +1,29 @@ +def g(x): + return x ** 2 + + +class A: + def __init__(self, x: int): + self.x = x + + def __eq__(self, other): + return self.x == other + + def __round__(self, n=None): + if n is not None: + return round(self.x, n) + return round(self.x) + + def __pow__(self, power, modulo=None): + if modulo is None: + return self.x ** power + return pow(self.x, power, modulo) + + def f1(self, x, y=1, *, z, t=2): + if y == 0: + return 100 * x + t + else: + x *= y + if x % 2 == 0: + y = g(x) + z + return x + y + 100 / y \ No newline at end of file diff --git a/utbot-python/samples/samples/named_arguments/named_arguments.py b/utbot-python/samples/samples/named_arguments/named_arguments.py new file mode 100644 index 0000000000..57f7c35b84 --- /dev/null +++ b/utbot-python/samples/samples/named_arguments/named_arguments.py @@ -0,0 +1,16 @@ +def f(x, y=1, *, z, t=2): + if y == 0: + return 100 * x + t + else: + x *= y + if x % 2 == 0: + y = g(x) + z + return x + y + 100 / y + + +def g(x): + return x ** 2 + + +if __name__ == '__main__': + print(f(1, y=2, z=3)) diff --git a/utbot-python/samples/samples/numpy/example_gauss.py b/utbot-python/samples/samples/numpy/example_gauss.py new file mode 100644 index 0000000000..067c97e9b5 --- /dev/null +++ b/utbot-python/samples/samples/numpy/example_gauss.py @@ -0,0 +1,71 @@ +import numpy as np + + +def solve_linear_system(matrix: np.ndarray) -> np.ndarray: + """ + Solve a linear system of equations using Gaussian elimination with partial pivoting + + Args: + - matrix: Coefficient matrix with the last column representing the constants. + + Returns: + - Solution vector. + + Raises: + - ValueError: If the matrix is not correct (i.e., singular). + + https://courses.engr.illinois.edu/cs357/su2013/lect.htm Lecture 7 + + Example: + >>> A = np.array([[2, 1, -1], [-3, -1, 2], [-2, 1, 2]], dtype=float) + >>> B = np.array([8, -11, -3], dtype=float) + >>> solution = solve_linear_system(np.column_stack((A, B))) + >>> np.allclose(solution, np.array([2., 3., -1.])) + True + >>> solve_linear_system(np.array([[0, 0], [0, 0]], dtype=float)) + array([nan, nan]) + """ + ab = np.copy(matrix) + num_of_rows = ab.shape[0] + num_of_columns = ab.shape[1] - 1 + x_lst: list[float] = [] + + for column_num in range(num_of_rows): + for i in range(column_num, num_of_columns): + if abs(ab[i][column_num]) > abs(ab[column_num][column_num]): + ab[[column_num, i]] = ab[[i, column_num]] + if ab[column_num, column_num] == 0.0: + raise ValueError("Matrix is not correct") + else: + pass + if column_num != 0: + for i in range(column_num, num_of_rows): + ab[i, :] -= ( + ab[i, column_num - 1] + / ab[column_num - 1, column_num - 1] + * ab[column_num - 1, :] + ) + + for column_num in range(num_of_rows): + for i in range(column_num, num_of_columns): + if abs(ab[i][column_num]) > abs(ab[column_num][column_num]): + ab[[column_num, i]] = ab[[i, column_num]] + if ab[column_num, column_num] == 0.0: + raise ValueError("Matrix is not correct") + else: + pass + if column_num != 0: + for i in range(column_num, num_of_rows): + ab[i, :] -= ( + ab[i, column_num - 1] + / ab[column_num - 1, column_num - 1] + * ab[column_num - 1, :] + ) + + for column_num in range(num_of_rows - 1, -1, -1): + x = ab[column_num, -1] / ab[column_num, column_num] + x_lst.insert(0, x) + for i in range(column_num - 1, -1, -1): + ab[i, -1] -= ab[i, column_num] * x + + return np.asarray(x_lst) \ No newline at end of file diff --git a/utbot-python/samples/samples/numpy/example_linear_algebra.py b/utbot-python/samples/samples/numpy/example_linear_algebra.py new file mode 100644 index 0000000000..b376fc4e73 --- /dev/null +++ b/utbot-python/samples/samples/numpy/example_linear_algebra.py @@ -0,0 +1,24 @@ +import numpy as np + + +def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + rows, columns = np.shape(table) + if rows != columns: + msg = ( + "'table' has to be of square shaped array but got a " + f"{rows}x{columns} array:\n{table}" + ) + raise ValueError(msg) + lower = np.zeros((rows, columns)) + upper = np.zeros((rows, columns)) + for i in range(columns): + for j in range(i): + total = np.sum(lower[i, :i] * upper[:i, j]) + if upper[j][j] == 0: + raise ArithmeticError("No LU decomposition exists") + lower[i][j] = (table[i][j] - total) / upper[j][j] + lower[i][i] = 1 + for j in range(i, columns): + total = np.sum(lower[i, :i] * upper[:i, j]) + upper[i][j] = table[i][j] - total + return lower, upper diff --git a/utbot-python/samples/samples/numpy/example_matrices.py b/utbot-python/samples/samples/numpy/example_matrices.py new file mode 100644 index 0000000000..0e3205f91e --- /dev/null +++ b/utbot-python/samples/samples/numpy/example_matrices.py @@ -0,0 +1,37 @@ +import numpy as np + + +def _is_matrix_spd(matrix: np.ndarray) -> bool: + """ + Returns True if input matrix is symmetric positive definite. + Returns False otherwise. + + For a matrix to be SPD, all eigenvalues must be positive. + + >>> import numpy as np + >>> matrix = np.array([ + ... [4.12401784, -5.01453636, -0.63865857], + ... [-5.01453636, 12.33347422, -3.40493586], + ... [-0.63865857, -3.40493586, 5.78591885]]) + >>> _is_matrix_spd(matrix) + True + >>> matrix = np.array([ + ... [0.34634879, 1.96165514, 2.18277744], + ... [0.74074469, -1.19648894, -1.34223498], + ... [-0.7687067 , 0.06018373, -1.16315631]]) + >>> _is_matrix_spd(matrix) + False + """ + # Ensure matrix is square. + assert np.shape(matrix)[0] == np.shape(matrix)[1] + + # If matrix not symmetric, exit right away. + if np.allclose(matrix, matrix.T) is False: + return False + + # Get eigenvalues and eignevectors for a symmetric matrix. + eigen_values, _ = np.linalg.eigh(matrix) + + # Check sign of all eigenvalues. + # np.all returns a value of type np.bool_ + return bool(np.all(eigen_values > 0)) \ No newline at end of file diff --git a/utbot-python/samples/samples/numpy/example_matrix_rank.py b/utbot-python/samples/samples/numpy/example_matrix_rank.py new file mode 100644 index 0000000000..21bdb552a7 --- /dev/null +++ b/utbot-python/samples/samples/numpy/example_matrix_rank.py @@ -0,0 +1,76 @@ +def rank_of_matrix(matrix: list[list[int | float]]) -> int: + """ + Finds the rank of a matrix. + Args: + matrix: The matrix as a list of lists. + Returns: + The rank of the matrix. + Example: + >>> matrix1 = [[1, 2, 3], + ... [4, 5, 6], + ... [7, 8, 9]] + >>> rank_of_matrix(matrix1) + 2 + >>> matrix2 = [[1, 0, 0], + ... [0, 1, 0], + ... [0, 0, 0]] + >>> rank_of_matrix(matrix2) + 2 + >>> matrix3 = [[1, 2, 3, 4], + ... [5, 6, 7, 8], + ... [9, 10, 11, 12]] + >>> rank_of_matrix(matrix3) + 2 + >>> rank_of_matrix([[2,3,-1,-1], + ... [1,-1,-2,4], + ... [3,1,3,-2], + ... [6,3,0,-7]]) + 4 + >>> rank_of_matrix([[2,1,-3,-6], + ... [3,-3,1,2], + ... [1,1,1,2]]) + 3 + >>> rank_of_matrix([[2,-1,0], + ... [1,3,4], + ... [4,1,-3]]) + 3 + >>> rank_of_matrix([[3,2,1], + ... [-6,-4,-2]]) + 1 + >>> rank_of_matrix([[],[]]) + 0 + >>> rank_of_matrix([[1]]) + 1 + >>> rank_of_matrix([[]]) + 0 + """ + + rows = len(matrix) + columns = len(matrix[0]) + rank = min(rows, columns) + + for row in range(rank): + # Check if diagonal element is not zero + if matrix[row][row] != 0: + # Eliminate all the elements below the diagonal + for col in range(row + 1, rows): + multiplier = matrix[col][row] / matrix[row][row] + for i in range(row, columns): + matrix[col][i] -= multiplier * matrix[row][i] + else: + # Find a non-zero diagonal element to swap rows + reduce = True + for i in range(row + 1, rows): + if matrix[i][row] != 0: + matrix[row], matrix[i] = matrix[i], matrix[row] + reduce = False + break + if reduce: + rank -= 1 + for i in range(rows): + matrix[i][row] = matrix[i][rank] + + # Reduce the row pointer by one to stay on the same row + row -= 1 + + return rank \ No newline at end of file diff --git a/utbot-python/samples/samples/numpy/example_norm.py b/utbot-python/samples/samples/numpy/example_norm.py new file mode 100644 index 0000000000..61b121bfec --- /dev/null +++ b/utbot-python/samples/samples/numpy/example_norm.py @@ -0,0 +1,23 @@ +import numpy as np +from numpy import ndarray + + +def norm_squared(vector: ndarray) -> float: + """ + Return the squared second norm of vector + norm_squared(v) = sum(x * x for x in v) + + Args: + vector (ndarray): input vector + + Returns: + float: squared second norm of vector + + >>> norm_squared([1, 2]) + 5 + >>> norm_squared(np.asarray([1, 2])) + 5 + >>> norm_squared([0, 0]) + 0 + """ + return np.dot(vector, vector) \ No newline at end of file diff --git a/utbot-python/samples/samples/numpy/example_simple.py b/utbot-python/samples/samples/numpy/example_simple.py new file mode 100644 index 0000000000..a5e2ef18c1 --- /dev/null +++ b/utbot-python/samples/samples/numpy/example_simple.py @@ -0,0 +1,7 @@ +import numpy as np + + +def function_to_test_default(a: np.ndarray): + if a.shape == (2, 1,): + return 2 + return a.shape diff --git a/utbot-python/samples/samples/primitives/__init__.py b/utbot-python/samples/samples/primitives/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/primitives/bytes_example.py b/utbot-python/samples/samples/primitives/bytes_example.py new file mode 100644 index 0000000000..f23a7fa92b --- /dev/null +++ b/utbot-python/samples/samples/primitives/bytes_example.py @@ -0,0 +1,7 @@ +def neg_bytes(b: bytes): + return not b + + +def sum_bytes(a: bytes, b: bytes): + c = a + b + return c diff --git a/utbot-python/samples/samples/primitives/numbers.py b/utbot-python/samples/samples/primitives/numbers.py new file mode 100644 index 0000000000..1305a369ab --- /dev/null +++ b/utbot-python/samples/samples/primitives/numbers.py @@ -0,0 +1,34 @@ +import copy +import math +import typing + + +def summ(a: typing.SupportsInt, b: typing.SupportsInt): + return int(a) + int(b) + + +def create_table(a: int): + table = [] + for i in range(a): + row = [] + for j in range(a): + row.append(i * j) + table.append(copy.deepcopy(row)) + return table + + +def operations(a: int): + if a > 1024: + return math.log2(a) + if a > 512: + return math.exp(a) + if a > 256: + return math.isinf(a) + if a > 128: + return math.e * a + return math.sqrt(a) + + +def check_order(a: int, b: int, c: int): + return a < b < c + diff --git a/utbot-python/samples/samples/primitives/primitive_types.py b/utbot-python/samples/samples/primitives/primitive_types.py new file mode 100644 index 0000000000..5fdadbf0ca --- /dev/null +++ b/utbot-python/samples/samples/primitives/primitive_types.py @@ -0,0 +1,16 @@ +import datetime + + +def pretty_print(x): + if isinstance(x, int): + return 'It is integer.\n' + 'Value ' + str(x) + elif isinstance(x, str): + return 'It is string.\n' + 'Value <<' + x + '>>' + elif isinstance(x, complex): + return 'It is complex.\n' + 'Value (' + str(x.real) + ' + ' + str(x.real) + 'i)' + elif isinstance(x, list): + return 'It is list.\n' + f'Value {x}' + elif isinstance(x, datetime.datetime): + return 'Date ' + x.isoformat() + else: + return 'I do not have any variants' diff --git a/utbot-python/samples/samples/primitives/regex.py b/utbot-python/samples/samples/primitives/regex.py new file mode 100644 index 0000000000..04e4592358 --- /dev/null +++ b/utbot-python/samples/samples/primitives/regex.py @@ -0,0 +1,15 @@ +import re + + +def check_regex(string: str) -> bool: + pattern = r"'(''|\\\\|\\'|[^'])*'" + if re.match(pattern, string): + return True + return False + + +def create_pattern(string: str): + if len(string) > 10: + return re.compile(rf"{string}") + else: + return re.compile(string) diff --git a/utbot-python/samples/samples/primitives/str_example.py b/utbot-python/samples/samples/primitives/str_example.py new file mode 100644 index 0000000000..e9e0a8d3c8 --- /dev/null +++ b/utbot-python/samples/samples/primitives/str_example.py @@ -0,0 +1,59 @@ +import dataclasses +import typing + + +@dataclasses.dataclass +class IntPair: + fst: int + snd: int + + +def concat(a: str, b: str): + return a + b + + +def concat_pair(pair: IntPair): + return pair.fst + pair.snd + + +def string_constants(s: str): + return "String('" + s + "')" + + +def contains(s: str, t: str): + return t in s + + +def const_contains(s: str): + return "ab" in s + + +def to_str(a: int, b: int): + if a > b: + return str(a) + else: + return str(b) + + +def starts_with(s: str): + if s.startswith("1234567890"): + s = s.replace("3", "A") + else: + s = s.strip() + + if s[0] == "x": + return s + else: + return s.upper() + + +def join_str(strings: typing.List[str]): + return "--".join(strings) + + +def separated_str(x: int): + if x == 1: + return r"fjalsdk\\nfjlask" + if 1 < x < 100: + return "fjalsd\n" * x + return x diff --git a/utbot-python/samples/samples/recursion/__init__.py b/utbot-python/samples/samples/recursion/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/recursion/recursion.py b/utbot-python/samples/samples/recursion/recursion.py new file mode 100644 index 0000000000..09c25521c6 --- /dev/null +++ b/utbot-python/samples/samples/recursion/recursion.py @@ -0,0 +1,41 @@ +def factorial(n: int): + if n < 0: + raise ValueError() + if n == 0: + return 1 + return n * factorial(n-1) + + +def fib(n: int): + if n < 0: + raise ValueError + if n == 0: + return 0 + if n == 1: + return 1 + return fib(n-1) + fib(n-2) + + +def summ(fst: int, snd: int): + def signum(x: int): + return 0 if x == 0 else 1 if x > 0 else -1 + if snd == 0: + return fst + return summ(fst + signum(snd), snd - signum(snd)) + + +def recursion_with_exception(n: int): + if n < 42: + recursion_with_exception(n+1) + if n > 42: + recursion_with_exception(n-1) + raise ValueError + + +def first(n: int): + def second(n: int): + first(n) + if n < 4: + return + second(n) + diff --git a/utbot-python/samples/samples/structures/__init__.py b/utbot-python/samples/samples/structures/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/structures/boruvka.py b/utbot-python/samples/samples/structures/boruvka.py new file mode 100644 index 0000000000..1e757abcf8 --- /dev/null +++ b/utbot-python/samples/samples/structures/boruvka.py @@ -0,0 +1,106 @@ +class Graph: + def __init__(self, num_of_nodes: int) -> None: + """ + Arguments: + num_of_nodes - the number of nodes in the graph + Attributes: + m_num_of_nodes - the number of nodes in the graph. + m_edges - the list of edges. + m_component - the dictionary which stores the index of the component which + a node belongs to. + """ + + self.m_num_of_nodes = num_of_nodes + self.m_edges: list[list[int]] = [] + self.m_component: dict[int, int] = {} + + def add_edge(self, u_node: int, v_node: int, weight: int) -> None: + """Adds an edge in the format [first, second, edge weight] to graph.""" + + self.m_edges.append([u_node, v_node, weight]) + + def find_component(self, u_node: int) -> int: + """Propagates a new component throughout a given component.""" + + if self.m_component[u_node] == u_node: + return u_node + return self.find_component(self.m_component[u_node]) + + def set_component(self, u_node: int) -> None: + """Finds the component index of a given node""" + + if self.m_component[u_node] != u_node: + for k in self.m_component: + self.m_component[k] = self.find_component(k) + + def union(self, component_size: list[int], u_node: int, v_node: int) -> None: + """Union finds the roots of components for two nodes, compares the components + in terms of size, and attaches the smaller one to the larger one to form + single component""" + + if component_size[u_node] <= component_size[v_node]: + self.m_component[u_node] = v_node + component_size[v_node] += component_size[u_node] + self.set_component(u_node) + + elif component_size[u_node] >= component_size[v_node]: + self.m_component[v_node] = self.find_component(u_node) + component_size[u_node] += component_size[v_node] + self.set_component(v_node) + + def boruvka(self) -> None: + """Performs Borůvka's algorithm to find MST.""" + + # Initialize additional lists required to algorithm. + component_size = [] + mst_weight = 0 + + minimum_weight_edge: list = [-1] * self.m_num_of_nodes + + # A list of components (initialized to all of the nodes) + for node in range(self.m_num_of_nodes): + self.m_component.update({node: node}) + component_size.append(1) + + num_of_components = self.m_num_of_nodes + + while num_of_components > 1: + for edge in self.m_edges: + u, v, w = edge + + u_component = self.m_component[u] + v_component = self.m_component[v] + + if u_component != v_component: + """If the current minimum weight edge of component u doesn't + exist (is -1), or if it's greater than the edge we're + observing right now, we will assign the value of the edge + we're observing to it. + + If the current minimum weight edge of component v doesn't + exist (is -1), or if it's greater than the edge we're + observing right now, we will assign the value of the edge + we're observing to it""" + + for component in (u_component, v_component): + if ( + minimum_weight_edge[component] == -1 + or minimum_weight_edge[component][2] > w + ): + minimum_weight_edge[component] = [u, v, w] + + for edge in minimum_weight_edge: + if isinstance(edge, list): + u, v, w = edge + + u_component = self.m_component[u] + v_component = self.m_component[v] + + if u_component != v_component: + mst_weight += w + self.union(component_size, u_component, v_component) + print(f"Added edge [{u} - {v}]\nAdded weight: {w}\n") + num_of_components -= 1 + + minimum_weight_edge = [-1] * self.m_num_of_nodes + print(f"The total weight of the minimal spanning tree is: {mst_weight}") diff --git a/utbot-python/samples/samples/structures/deque.py b/utbot-python/samples/samples/structures/deque.py new file mode 100644 index 0000000000..87ea0e958e --- /dev/null +++ b/utbot-python/samples/samples/structures/deque.py @@ -0,0 +1,8 @@ +from collections import deque + + +def generate_people_deque(people_count: int): + names = ['Alex', 'Bob', 'Cate', 'Daisy', 'Ed'] + if people_count > 5: + people_count = 5 + return deque(sorted(names[:people_count])) \ No newline at end of file diff --git a/utbot-python/samples/samples/structures/graph_matrix.py b/utbot-python/samples/samples/structures/graph_matrix.py new file mode 100644 index 0000000000..785c295724 --- /dev/null +++ b/utbot-python/samples/samples/structures/graph_matrix.py @@ -0,0 +1,40 @@ +# An island in matrix is a group of linked areas, all having the same value. +# This code counts number of islands in a given matrix, with including diagonal +# connections. + + +class Matrix: # Public class to implement a graph + def __init__(self, row: int, col: int, graph: list[list[bool]]) -> None: + self.ROW = row + self.COL = col + self.graph = graph + + def __eq__(self, other): + return self.graph == other.graph + + def is_safe(self, i: int, j: int, visited: list[list[bool]]) -> bool: + return ( + 0 <= i < self.ROW + and 0 <= j < self.COL + and not visited[i][j] + and self.graph[i][j] + ) + + def diffs(self, i: int, j: int, visited: list[list[bool]]) -> None: + # Checking all 8 elements surrounding nth element + row_nbr = [-1, -1, -1, 0, 0, 1, 1, 1] # Coordinate order + col_nbr = [-1, 0, 1, -1, 1, -1, 0, 1] + visited[i][j] = True # Make those cells visited + for k in range(8): + if self.is_safe(i + row_nbr[k], j + col_nbr[k], visited): + self.diffs(i + row_nbr[k], j + col_nbr[k], visited) + + def count_islands(self) -> int: # And finally, count all islands. + visited = [[False for j in range(self.COL)] for i in range(self.ROW)] + count = 0 + for i in range(self.ROW): + for j in range(self.COL): + if visited[i][j] is False and self.graph[i][j] == 1: + self.diffs(i, j, visited) + count += 1 + return count \ No newline at end of file diff --git a/utbot-python/samples/samples/structures/matrix.py b/utbot-python/samples/samples/structures/matrix.py new file mode 100644 index 0000000000..b9b284d334 --- /dev/null +++ b/utbot-python/samples/samples/structures/matrix.py @@ -0,0 +1,82 @@ +from __future__ import annotations +from itertools import product +from typing import List + + +class MatrixException(Exception): + def __init__(self, description): + self.description = description + + +class Matrix: + def __init__(self, elements: List[List[float]]): + assert all(len(elements[i-1]) == len(row) for i, row in enumerate(elements)) + self.elements = elements + + @property + def dim(self) -> tuple[int, int]: + return ( + len(self.elements), + max(len(self.elements[i]) for i in range(len(self.elements))) + if len(self.elements) > 0 else 0 + ) + + def __repr__(self): + return str(self.elements) + + def __eq__(self, other): + if isinstance(other, Matrix): + return self.elements == other.elements + + def __add__(self, other: Matrix): + if self.dim == other.dim: + return Matrix([ + [ + elem + other_elem for elem, other_elem in + zip(self.elements[i], other.elements[i]) + ] + for i in range(self.dim[0]) + ]) + + def __mul__(self, other): + if isinstance(other, (int, float, complex)): + return Matrix([ + [ + elem * other for elem in + self.elements[i] + ] + for i in range(self.dim[0]) + ]) + else: + raise MatrixException("Wrong Type") + + def __matmul__(self, other: Matrix): + if isinstance(other, Matrix): + if self.dim[1] == other.dim[0]: + result = [[0 for _ in range(self.dim[0])] * other.dim[1]] + for i, j in product(range(self.dim[0]), range(other.dim[1])): + result[i][j] = sum( + self.elements[i][k] * other.elements[k][j] + for k in range(self.dim[1]) + ) + return Matrix(result) + else: + MatrixException("Wrong dimensions") + else: + raise MatrixException("Wrong Type") + + def is_diagonal(self) -> bool: + if self.dim[0] != self.dim[1]: + raise MatrixException("Bad matrix") + + for i, row in enumerate(self.elements): + for j, elem in enumerate(row): + if i != j and elem != 0: + return False + return True + + +if __name__ == '__main__': + a = Matrix([[1., 2.]]) + b = Matrix([[3.], [4.]]) + print(a @ b) diff --git a/utbot-python/samples/samples/structures/multi_level_feedback_queue.py b/utbot-python/samples/samples/structures/multi_level_feedback_queue.py new file mode 100644 index 0000000000..284c758b8f --- /dev/null +++ b/utbot-python/samples/samples/structures/multi_level_feedback_queue.py @@ -0,0 +1,312 @@ +from collections import deque + + +class Process: + def __init__(self, process_name: str, arrival_time: int, burst_time: int) -> None: + self.process_name = process_name # process name + self.arrival_time = arrival_time # arrival time of the process + # completion time of finished process or last interrupted time + self.stop_time = arrival_time + self.burst_time = burst_time # remaining burst time + self.waiting_time = 0 # total time of the process wait in ready queue + self.turnaround_time = 0 # time from arrival time to completion time + + +class MLFQ: + """ + MLFQ(Multi Level Feedback Queue) + https://en.wikipedia.org/wiki/Multilevel_feedback_queue + MLFQ has a lot of queues that have different priority + In this MLFQ, + The first Queue(0) to last second Queue(N-2) of MLFQ have Round Robin Algorithm + The last Queue(N-1) has First Come, First Served Algorithm + """ + + def __init__( + self, + number_of_queues: int, + time_slices: list[int], + queue: deque[Process], + current_time: int, + ) -> None: + # total number of mlfq's queues + self.number_of_queues = number_of_queues + # time slice of queues that round robin algorithm applied + self.time_slices = time_slices + # unfinished process is in this ready_queue + self.ready_queue = queue + # current time + self.current_time = current_time + # finished process is in this sequence queue + self.finish_queue: deque[Process] = deque() + + def calculate_sequence_of_finish_queue(self) -> list[str]: + """ + This method returns the sequence of finished processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_sequence_of_finish_queue() + ['P2', 'P4', 'P1', 'P3'] + """ + sequence = [] + for i in range(len(self.finish_queue)): + sequence.append(self.finish_queue[i].process_name) + return sequence + + def calculate_waiting_time(self, queue: list[Process]) -> list[int]: + """ + This method calculates waiting time of processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_waiting_time([P1, P2, P3, P4]) + [83, 17, 94, 101] + """ + waiting_times = [] + for i in range(len(queue)): + waiting_times.append(queue[i].waiting_time) + return waiting_times + + def calculate_turnaround_time(self, queue: list[Process]) -> list[int]: + """ + This method calculates turnaround time of processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_turnaround_time([P1, P2, P3, P4]) + [136, 34, 162, 125] + """ + turnaround_times = [] + for i in range(len(queue)): + turnaround_times.append(queue[i].turnaround_time) + return turnaround_times + + def calculate_completion_time(self, queue: list[Process]) -> list[int]: + """ + This method calculates completion time of processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_turnaround_time([P1, P2, P3, P4]) + [136, 34, 162, 125] + """ + completion_times = [] + for i in range(len(queue)): + completion_times.append(queue[i].stop_time) + return completion_times + + def calculate_remaining_burst_time_of_processes( + self, queue: deque[Process] + ) -> list[int]: + """ + This method calculate remaining burst time of processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> finish_queue, ready_queue = mlfq.round_robin(deque([P1, P2, P3, P4]), 17) + >>> mlfq.calculate_remaining_burst_time_of_processes(mlfq.finish_queue) + [0] + >>> mlfq.calculate_remaining_burst_time_of_processes(ready_queue) + [36, 51, 7] + >>> finish_queue, ready_queue = mlfq.round_robin(ready_queue, 25) + >>> mlfq.calculate_remaining_burst_time_of_processes(mlfq.finish_queue) + [0, 0] + >>> mlfq.calculate_remaining_burst_time_of_processes(ready_queue) + [11, 26] + """ + return [q.burst_time for q in queue] + + def update_waiting_time(self, process: Process) -> int: + """ + This method updates waiting times of unfinished processes + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> mlfq.current_time = 10 + >>> P1.stop_time = 5 + >>> mlfq.update_waiting_time(P1) + 5 + """ + process.waiting_time += self.current_time - process.stop_time + return process.waiting_time + + def first_come_first_served(self, ready_queue: deque[Process]) -> deque[Process]: + """ + FCFS(First Come, First Served) + FCFS will be applied to MLFQ's last queue + A first came process will be finished at first + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> _ = mlfq.first_come_first_served(mlfq.ready_queue) + >>> mlfq.calculate_sequence_of_finish_queue() + ['P1', 'P2', 'P3', 'P4'] + """ + finished: deque[Process] = deque() # sequence deque of finished process + while len(ready_queue) != 0: + cp = ready_queue.popleft() # current process + + # if process's arrival time is later than current time, update current time + if self.current_time < cp.arrival_time: + self.current_time += cp.arrival_time + + # update waiting time of current process + self.update_waiting_time(cp) + # update current time + self.current_time += cp.burst_time + # finish the process and set the process's burst-time 0 + cp.burst_time = 0 + # set the process's turnaround time because it is finished + cp.turnaround_time = self.current_time - cp.arrival_time + # set the completion time + cp.stop_time = self.current_time + # add the process to queue that has finished queue + finished.append(cp) + + self.finish_queue.extend(finished) # add finished process to finish queue + # FCFS will finish all remaining processes + return finished + + def round_robin( + self, ready_queue: deque[Process], time_slice: int + ) -> tuple[deque[Process], deque[Process]]: + """ + RR(Round Robin) + RR will be applied to MLFQ's all queues except last queue + All processes can't use CPU for time more than time_slice + If the process consume CPU up to time_slice, it will go back to ready queue + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> finish_queue, ready_queue = mlfq.round_robin(mlfq.ready_queue, 17) + >>> mlfq.calculate_sequence_of_finish_queue() + ['P2'] + """ + finished: deque[Process] = deque() # sequence deque of terminated process + # just for 1 cycle and unfinished processes will go back to queue + for _ in range(len(ready_queue)): + cp = ready_queue.popleft() # current process + + # if process's arrival time is later than current time, update current time + if self.current_time < cp.arrival_time: + self.current_time += cp.arrival_time + + # update waiting time of unfinished processes + self.update_waiting_time(cp) + # if the burst time of process is bigger than time-slice + if cp.burst_time > time_slice: + # use CPU for only time-slice + self.current_time += time_slice + # update remaining burst time + cp.burst_time -= time_slice + # update end point time + cp.stop_time = self.current_time + # locate the process behind the queue because it is not finished + ready_queue.append(cp) + else: + # use CPU for remaining burst time + self.current_time += cp.burst_time + # set burst time 0 because the process is finished + cp.burst_time = 0 + # set the finish time + cp.stop_time = self.current_time + # update the process' turnaround time because it is finished + cp.turnaround_time = self.current_time - cp.arrival_time + # add the process to queue that has finished queue + finished.append(cp) + + self.finish_queue.extend(finished) # add finished process to finish queue + # return finished processes queue and remaining processes queue + return finished, ready_queue + + def multi_level_feedback_queue(self) -> deque[Process]: + """ + MLFQ(Multi Level Feedback Queue) + >>> P1 = Process("P1", 0, 53) + >>> P2 = Process("P2", 0, 17) + >>> P3 = Process("P3", 0, 68) + >>> P4 = Process("P4", 0, 24) + >>> mlfq = MLFQ(3, [17, 25], deque([P1, P2, P3, P4]), 0) + >>> finish_queue = mlfq.multi_level_feedback_queue() + >>> mlfq.calculate_sequence_of_finish_queue() + ['P2', 'P4', 'P1', 'P3'] + """ + + # all queues except last one have round_robin algorithm + for i in range(self.number_of_queues - 1): + finished, self.ready_queue = self.round_robin( + self.ready_queue, self.time_slices[i] + ) + # the last queue has first_come_first_served algorithm + self.first_come_first_served(self.ready_queue) + + return self.finish_queue + + +if __name__ == "__main__": + import doctest + + P1 = Process("P1", 0, 53) + P2 = Process("P2", 0, 17) + P3 = Process("P3", 0, 68) + P4 = Process("P4", 0, 24) + number_of_queues = 3 + time_slices = [17, 25] + queue = deque([P1, P2, P3, P4]) + + if len(time_slices) != number_of_queues - 1: + raise SystemExit(0) + + doctest.testmod(extraglobs={"queue": deque([P1, P2, P3, P4])}) + + P1 = Process("P1", 0, 53) + P2 = Process("P2", 0, 17) + P3 = Process("P3", 0, 68) + P4 = Process("P4", 0, 24) + number_of_queues = 3 + time_slices = [17, 25] + queue = deque([P1, P2, P3, P4]) + mlfq = MLFQ(number_of_queues, time_slices, queue, 0) + finish_queue = mlfq.multi_level_feedback_queue() + + # print total waiting times of processes(P1, P2, P3, P4) + print( + f"waiting time:\ + \t\t\t{MLFQ.calculate_waiting_time(mlfq, [P1, P2, P3, P4])}" + ) + # print completion times of processes(P1, P2, P3, P4) + print( + f"completion time:\ + \t\t{MLFQ.calculate_completion_time(mlfq, [P1, P2, P3, P4])}" + ) + # print total turnaround times of processes(P1, P2, P3, P4) + print( + f"turnaround time:\ + \t\t{MLFQ.calculate_turnaround_time(mlfq, [P1, P2, P3, P4])}" + ) + # print sequence of finished processes + print( + f"sequence of finished processes:\ + {mlfq.calculate_sequence_of_finish_queue()}" + ) \ No newline at end of file diff --git a/utbot-python/samples/samples/type_inference/__init__.py b/utbot-python/samples/samples/type_inference/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/samples/samples/type_inference/annotations.py b/utbot-python/samples/samples/type_inference/annotations.py new file mode 100644 index 0000000000..ad3b4606dd --- /dev/null +++ b/utbot-python/samples/samples/type_inference/annotations.py @@ -0,0 +1,71 @@ +from typing import * + + +def simple_annotations1(x: int, y: int): + if x > 1: + return x + y + else: + return y * 2 + + +def simple_annotations2(x: str, y: str): + if len(x) > 1: + return x + y + else: + return y * 2 + + +def simple_annotations3(x: str, y: bool): + if y: + return x + else: + return x * 2 + + +def simple_annotations4(x: float, y: int): + return x + y + + +def union(x: Union[int, str]): + if isinstance(x, int): + return 10 * x + else: + return x + "!" + + +def list_(x: List[int]): + return len(x) + + +def tuple_1(x: Tuple[int, str]): + return len(x) + + +def tuple_2(x: Tuple[int, ...]): + return len(x) + + +def dict_1(x: Dict[int, str]): + return len(x) + + +def dict_2(x: Dict[int, Union[str, bool]]): + return len(x) + + +def set_1(x: Set[int]): + return len(x) + + +def any_1(x: Any): + return x + + +class Node: + def __init__(self, name: str): + self.name = name + self.children = [] + + +def reduce_1(x: Node): + return x.name diff --git a/utbot-python/samples/samples/type_inference/annotations2.py b/utbot-python/samples/samples/type_inference/annotations2.py new file mode 100644 index 0000000000..3c34fa150f --- /dev/null +++ b/utbot-python/samples/samples/type_inference/annotations2.py @@ -0,0 +1,76 @@ +from __future__ import annotations +from typing import * +from enum import Enum + + +XXX = TypeVar("XXX", "A", int) + + +def f(x: int): + a = [x, XXX] + return a + + +class A(Generic[XXX]): + self_: XXX + + def f(self, a, b: A[int]): + self.y = b + self.self_.x = b + pass + + def g(self): + self.x = 1 + + +def square(collection: Iterable[int], x: XXX): + result = set() + for elem in collection: + result.add(elem ** 2) + return result + + +def not_annotated(x): + return x + + +def same_annotations(x: int, y: int, a: List[Any], b: List[Any], c: List[int]): + return x + y + + +def optional(x: Optional[int]): + return x + + +def literal(x: Literal["w", "r"]): + return x + + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + +def enum_literal(x: Literal[Color.RED, Color.GREEN]): + return x + + +def abstract_set(x: AbstractSet[int]): + return x + + +def mapping(x: Mapping[int, int]): + return x + + +def sequence(x: Sequence[object]): + return x + + +def supports_abs(x: SupportsAbs): + return abs(x) + + +def tuple_(x: Tuple[int, str]): + return x[1] + str(x[0]) diff --git a/utbot-python/samples/samples/type_inference/generics.py b/utbot-python/samples/samples/type_inference/generics.py new file mode 100644 index 0000000000..e0939f3e4e --- /dev/null +++ b/utbot-python/samples/samples/type_inference/generics.py @@ -0,0 +1,24 @@ +from typing import TypeVar, Generic +from logging import Logger + +T = TypeVar('T', bound=bool) +U = TypeVar('U', str, object) + + +class LoggedVar(Generic[T]): + def __init__(self, value: T, name: str) -> None: + self.name = name + self.value = value + + def set(self, new: T) -> None: + self.value = new + + def get(self, x: U): + return self.value, x + + +def func(x: T, y: U): + if x: + return y + else: + return 100 diff --git a/utbot-python/samples/samples/type_inference/list_of_datetime.py b/utbot-python/samples/samples/type_inference/list_of_datetime.py new file mode 100644 index 0000000000..b72f31d95c --- /dev/null +++ b/utbot-python/samples/samples/type_inference/list_of_datetime.py @@ -0,0 +1,11 @@ +import datetime + + +def get_data_labels(dates): + if not dates: + dates.append(datetime.time(hour=23, minute=59)) + return None + if all(x.hour == 0 and x.minute == 0 for x in dates): + return [x.strftime('%Y-%m-%d') for x in dates] + else: + return [x.strftime('%H:%M') for x in dates] diff --git a/utbot-python/samples/samples/type_inference/subtypes.py b/utbot-python/samples/samples/type_inference/subtypes.py new file mode 100644 index 0000000000..24eb1c18e6 --- /dev/null +++ b/utbot-python/samples/samples/type_inference/subtypes.py @@ -0,0 +1,52 @@ +import collections +from typing import * + + +class P(Protocol): + def f(self, x: int) -> dict: + ... + + +class S: + def f(self, x: Union[int, str]) -> collections.Counter: + return collections.Counter([x]) + + +class S1: + def f(self, x: Union[int, str]) -> object: + return collections.Counter([x]) + + +def func_for_p(x: P) -> None: + return None + + +# func_for_p(S()) + + +class R(Protocol): + def f(self) -> 'R': + ... + + +class RImpl: + def f(self) -> 'RImpl': + return self + + +def func_for_r(x: R) -> None: + return None + + +# func_for_r(RImpl()) + + +a: List[int] = [] + + +T = TypeVar('T') + + +def func_abs(x: SupportsAbs[T]): + return abs(x) + diff --git a/utbot-python/samples/samples/type_inference/type_inference.py b/utbot-python/samples/samples/type_inference/type_inference.py new file mode 100644 index 0000000000..5cc524df1e --- /dev/null +++ b/utbot-python/samples/samples/type_inference/type_inference.py @@ -0,0 +1,16 @@ +def type_inference(number, string, string_sep, list_of_number, dict_str_to_list): + new_string = '_' + string + '_' * number + new_string = new_string.capitalize() + string_sep + new_string[::-1] + + if len(list_of_number) < len(new_string): + list_of_number += [0] * (len(new_string) - len(list_of_number)) + + dict_str_to_list[string] = [] + for key in dict_str_to_list.keys(): + list_of_number.append(key) + + return list_of_number + + +if __name__ == '__main__': + print(type_inference(5, 'fjsl', '|', [1, 2, 3], {'fjls': [1, 2]})) \ No newline at end of file diff --git a/utbot-python/samples/samples/type_inference/type_inference_2.py b/utbot-python/samples/samples/type_inference/type_inference_2.py new file mode 100644 index 0000000000..5d55939213 --- /dev/null +++ b/utbot-python/samples/samples/type_inference/type_inference_2.py @@ -0,0 +1,9 @@ +def g(x): + + def f(y): + if y in [0, 100, 200, 500]: + return y // 100 + + if f(x) > 10: + return x ** 2 + diff --git a/utbot-python/samples/test_configuration.json b/utbot-python/samples/test_configuration.json new file mode 100644 index 0000000000..6df697b5d8 --- /dev/null +++ b/utbot-python/samples/test_configuration.json @@ -0,0 +1,667 @@ +{ + "parts": [ + { + "path": "samples/algorithms", + "files": [ + { + "name": "bfs", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10, + "coverage": 92 + } + ] + }, + { + "name": "longest_subsequence", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10, + "coverage": 91 + } + ] + }, + { + "name": "quick_sort", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10, + "coverage": 85 + } + ] + } + ] + }, + { + "path": "samples/classes", + "files": [ + { + "name": "constructors", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10, + "coverage": 80 + } + ] + }, + { + "name": "dataclass", + "groups": [ + { + "classes": ["C"], + "methods": ["C.inc"], + "timeout": 10, + "coverage": 100 + } + ] + }, + { + "name": "dicts", + "groups": [ + { + "classes": ["Dictionary"], + "methods": ["Dictionary.translate"], + "timeout": 60, + "coverage": 100 + } + ] + }, + { + "name": "easy_class", + "groups": [ + { + "classes": ["B"], + "methods": null, + "timeout": 10, + "coverage": 100 + } + ] + }, + { + "name": "equals", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10, + "coverage": 100 + } + ] + }, + { + "name": "inner_class", + "groups": [ + { + "classes": ["Outer"], + "methods": null, + "timeout": 10, + "coverage": 100 + } + ] + }, + { + "name": "rename_self", + "groups": [ + { + "classes": ["A"], + "methods": null, + "timeout": 10, + "coverage": 100 + } + ] + }, + { + "name": "setstate_test", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 10, + "coverage": 100 + } + ] + } + ] + }, + { + "path": "samples/collection", + "files": [ + { + "name": "dicts", + "groups": [ + { + "classes": ["-"], + "methods": ["f", "g"], + "timeout": 120, + "coverage": 100 + } + ] + }, + { + "name": "lists", + "groups": [ + { + "classes": ["-"], + "methods": null, + "timeout": 10, + "coverage": 100 + } + ] + }, + { + "name": "recursive", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 30, + "coverage": 100 + } + ] + }, + { + "name": "sets", + "groups": [ + { + "classes": ["-"], + "methods": ["f", "g"], + "timeout": 120, + "coverage": 100 + } + ] + }, + { + "name": "tuples", + "groups": [ + { + "classes": ["-"], + "methods": null, + "timeout": 10, + "coverage": 75 + } + ] + }, + { + "name": "using_collections", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 30, + "coverage": 88 + } + ] + } + ] + }, + { + "path": "samples/controlflow", + "files": [ + { + "name": "arithmetic", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 100, + "coverage": 46 + } + ] + }, + { + "name": "conditions", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 180, + "coverage": 83 + } + ] + }, + { + "name": "inner_conditions", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 30, + "coverage": 75 + } + ] + }, + { + "name": "multi_conditions", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 15, + "coverage": 93 + } + ] + } + ] + }, + { + "path": "samples/easy_samples", + "files": [ + { + "name": "deep_equals", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 90, + "coverage": 100 + } + ] + }, + { + "name": "deep_equals_2", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 20, + "coverage": 100 + } + ] + }, + { + "name": "fully_annotated", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 300, + "coverage": 100 + } + ] + }, + { + "name": "dummy_with_eq", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 20, + "coverage": 100 + } + ] + }, + { + "name": "dummy_without_eq", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 20, + "coverage": 100 + } + ] + }, + { + "name": "long_function_coverage", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 20, + "coverage": 100 + } + ] + } + ] + }, + { + "path": "samples/exceptions", + "files": [ + { + "name": "exception_examples", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 180, + "coverage": 97 + } + ] + } + ] + }, + { + "path": "samples/imports/builtins_module", + "files": [ + { + "name": "crypto", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 180, + "coverage": 86 + } + ] + } + ] + }, + { + "path": "samples/imports/pack_1/inner_pack_1", + "files": [ + { + "name": "inner_mod_1", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 180, + "coverage": 100 + } + ] + } + ] + }, + { + "path": "samples/imports/pack_1/inner_pack_2", + "files": [ + { + "name": "inner_mod_2", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 20, + "coverage": 100 + } + ] + } + ] + }, + { + "path": "samples/imports/pack_1/inner_pack_2", + "files": [ + { + "name": "inner_mod_2", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 20, + "coverage": 100 + } + ] + } + ] + }, + { + "path": "samples/primitives", + "files": [ + { + "name": "bytes_example", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 30, + "coverage": 100 + } + ] + }, + { + "name": "numbers", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 80, + "coverage": 100 + } + ] + }, + { + "name": "primitive_types", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 60, + "coverage": 92 + } + ] + }, + { + "name": "regex", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 50, + "coverage": 100 + } + ] + }, + { + "name": "str_example", + "groups": [ + { + "classes": null, + "methods": [ + "concat", + "concat_pair", + "string_constants", + "contains", + "const_contains", + "to_str", + "starts_with", + "join_str" + ], + "timeout": 160, + "coverage": 100 + }, + { + "classes": null, + "methods": ["separated_str"], + "timeout": 100, + "coverage": 100 + } + ] + } + ] + }, + { + "path": "samples/recursion", + "files": [ + { + "name": "recursion", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 150, + "coverage": 100 + } + ] + } + ] + }, + { + "path": "samples/structures", + "files": [ + { + "name": "boruvka", + "groups": [ + { + "classes": ["Graph"], + "methods": null, + "timeout": 150, + "coverage": 64 + } + ] + }, + { + "name": "deque", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 150, + "coverage": 100 + } + ] + }, + { + "name": "graph_matrix", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 120, + "coverage": 96 + } + ] + }, + { + "name": "matrix", + "groups": [ + { + "classes": ["Matrix"], + "methods": ["Matrix.__repr__", "Matrix.__eq__", "Matrix.__add__", "Matrix.__mul__", "Matrix.is_diagonal"], + "timeout": 120, + "coverage": 97 + }, + { + "classes": ["Matrix"], + "methods": ["Matrix.__matmul__"], + "timeout": 80, + "coverage": 88 + } + ] + }, + { + "name": "multi_level_feedback_queue", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 180, + "coverage": 62 + } + ] + } + ] + }, + { + "path": "samples/type_inference", + "files": [ + { + "name": "annotations", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 140, + "coverage": 100 + } + ] + }, + { + "name": "annotations2", + "groups": [ + { + "classes": ["A"], + "methods": null, + "timeout": 40, + "coverage": 100 + } + ] + }, + { + "name": "annotations2", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 140, + "coverage": 100 + } + ] + }, + { + "name": "generics", + "groups": [ + { + "classes": ["LoggedVar"], + "methods": null, + "timeout": 30, + "coverage": 80 + } + ] + }, + { + "name": "generics", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 30, + "coverage": 80 + } + ] + }, + { + "name": "list_of_datetime", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 30, + "coverage": 50 + } + ] + }, + { + "name": "subtypes", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 90, + "coverage": 100 + } + ] + }, + { + "name": "type_inference", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 40, + "coverage": 100 + } + ] + }, + { + "name": "type_inference_2", + "groups": [ + { + "classes": null, + "methods": null, + "timeout": 40, + "coverage": 62 + } + ] + } + ] + } + ] +} diff --git a/utbot-python/samples/testing_utils/collect_executions.py b/utbot-python/samples/testing_utils/collect_executions.py new file mode 100644 index 0000000000..81404aa023 --- /dev/null +++ b/utbot-python/samples/testing_utils/collect_executions.py @@ -0,0 +1,34 @@ +import json +import pathlib +import sys +import typing + + +def get_all_files(folder: pathlib.Path) -> typing.List[pathlib.Path]: + if folder.is_dir: + return folder.glob("*.executions") + return [] + + +def get_excecutions_number(file: pathlib.Path) -> int: + with open(file, 'r', encoding='utf-8') as fin: + return int(json.loads(fin.readline().strip())['executions']) + + +def save_data(data: typing.Dict[pathlib.Path, int]) -> None: + with open('data_executions.json', 'w', encoding='utf-8') as fout: + print(json.dumps(data, indent=1), file=fout) + + +def main(folder: pathlib.Path) -> None: + files = get_all_files(folder) + data = { + str(file): get_excecutions_number(file) + for file in files + } + save_data(data) + + +if __name__ == '__main__': + main(pathlib.Path(sys.argv[1])) + diff --git a/utbot-python/samples/testing_utils/collect_timeouts.py b/utbot-python/samples/testing_utils/collect_timeouts.py new file mode 100644 index 0000000000..bbcb8241a2 --- /dev/null +++ b/utbot-python/samples/testing_utils/collect_timeouts.py @@ -0,0 +1,41 @@ +import json +import pathlib +import sys +import typing + +def read_test_config(path: pathlib.Path, coverage_dir: pathlib.Path) -> typing.Dict[str, int]: + with open(path, 'r', encoding='utf-8') as fin: + data = json.loads(fin.read()) + + res = {} + for part in data['parts']: + for file in part['files']: + for group in file['groups']: + file_suffix = f"{part['path'].replace('/', '_')}_{file['name']}" + executions_output_file = pathlib.Path(coverage_dir, f"coverage_{file_suffix}.json.executions") + timeout = group['timeout'] + res[executions_output_file] = timeout + return res + + +def read_executions(path: pathlib.Path): + with open(path, 'r', encoding='utf-8') as fin: + data = json.loads(fin.read()) + return data + + +def main(config_path: pathlib.Path, executions_path: pathlib.Path, coverage_dir: pathlib.Path): + timeouts = read_test_config(config_path, coverage_dir) + executions = read_executions(executions_path) + + for f, timeout in timeouts.items(): + executions[str(f)] /= timeout + + with open('speeds.json', 'w') as fout: + print(json.dumps(executions, indent=1), file=fout) + + +if __name__ == '__main__': + main(*sys.argv[1:]) + + diff --git a/utbot-python/samples/testing_utils/compare.py b/utbot-python/samples/testing_utils/compare.py new file mode 100644 index 0000000000..cb45d0cc49 --- /dev/null +++ b/utbot-python/samples/testing_utils/compare.py @@ -0,0 +1,25 @@ +import json +import matplotlib.pyplot as plt +import pathlib + + +def read_stats(path: pathlib.Path): + with open(path, 'r') as fin: + return json.loads(fin.read()) + + +data1 = read_stats('speeds_basic.json') +data2 = read_stats('speeds_forks.json') +data3 = read_stats('speeds_forks2.json') +data = [list(d.values()) for d in [data1, data2, data3]] + +fig = plt.figure() +ax = fig.add_subplot(1, 1, 1) +ax.boxplot( + data, + labels=['basic', 'forks', 'forks2'], + vert=True, + patch_artist=True + ) +ax.grid(True) +plt.show() diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt new file mode 100644 index 0000000000..5123a67e2f --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestCaseGenerator.kt @@ -0,0 +1,63 @@ +package org.utbot.python + +import mu.KotlinLogging +import org.utbot.framework.minimization.minimizeExecutions +import org.utbot.framework.plugin.api.UtClusterInfo +import org.utbot.framework.plugin.api.UtError +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.python.engine.GlobalPythonEngine +import org.utbot.python.framework.api.python.PythonUtExecution + +private val logger = KotlinLogging.logger {} +private const val MAX_EMPTY_COVERAGE_TESTS = 5 + +class PythonTestCaseGenerator( + private val configuration: PythonTestGenerationConfig, + private val mypyConfig: MypyConfig, +) { + private val withMinimization = configuration.withMinimization + + fun generate(method: PythonMethod, until: Long): List { + logger.info { "Start test generation for ${method.name}" } + val engine = GlobalPythonEngine( + method = method, + configuration = configuration, + mypyConfig, + until, + ) + try { + engine.run() + } catch (_: OutOfMemoryError) { + logger.debug { "Out of memory error. Stop test generation process" } + } + + logger.info { "Collect all test executions for ${method.name}" } + return listOf( + buildTestSet(method, engine.executionStorage.fuzzingExecutions, engine.executionStorage.fuzzingErrors, UtClusterInfo("FUZZER")), + ) + } + + private fun buildTestSet( + method: PythonMethod, + executions: List, + errors: List, + clusterInfo: UtClusterInfo, + ): PythonTestSet { + val (emptyCoverageExecutions, coverageExecutions) = executions.partition { it.coverage == null } + val (successfulExecutions, failedExecutions) = coverageExecutions.partition { it.result is UtExecutionSuccess } + val minimized = + if (withMinimization) + minimizeExecutions(successfulExecutions) + + minimizeExecutions(failedExecutions) + + emptyCoverageExecutions.take(MAX_EMPTY_COVERAGE_TESTS) + else + coverageExecutions + emptyCoverageExecutions.take(MAX_EMPTY_COVERAGE_TESTS) + return PythonTestSet( + method, + minimized, + errors, + executionsNumber = executions.size, + clustersInfo = listOf(Pair(clusterInfo, minimized.indices)) + ) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt new file mode 100644 index 0000000000..69d60bd43d --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationConfig.kt @@ -0,0 +1,52 @@ +package org.utbot.python + +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.python.coverage.CoverageOutputFormat +import org.utbot.python.coverage.PythonCoverageMode +import org.utpython.types.mypy.MypyBuildDirectory +import org.utpython.types.mypy.MypyInfoBuild +import org.utpython.types.mypy.MypyReportLine +import java.nio.file.Path + +data class TestFileInformation( + val testedFilePath: String, + val testedFileContent: String, + val moduleName: String, +) + +class PythonTestGenerationConfig( + val pythonPath: String, + val testFileInformation: TestFileInformation, + val sysPathDirectories: Set, + val testedMethods: List, + val timeout: Long, + val timeoutForRun: Long, + val testFramework: TestFramework, + val testSourceRootPath: Path, + val withMinimization: Boolean, + val isCanceled: () -> Boolean, + val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour, + val coverageMeasureMode: PythonCoverageMode = PythonCoverageMode.Instructions, + val sendCoverageContinuously: Boolean = true, + val coverageOutputFormat: CoverageOutputFormat = CoverageOutputFormat.Lines, + val prohibitedExceptions: List = defaultProhibitedExceptions, + val doNotGenerateStateAssertions: Boolean = false, +) { + companion object { + val defaultProhibitedExceptions: List = listOf( + "builtins.AttributeError", + "builtins.TypeError", + "builtins.NotImplementedError", + ) + + val skipClasses: List = emptyList() + val skipTopLevelFunctions: List = emptyList() + } +} + +data class MypyConfig( + val mypyStorage: MypyInfoBuild, + val mypyReportLine: List, + val mypyBuildDirectory: MypyBuildDirectory, +) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt new file mode 100644 index 0000000000..98b8ff2ca1 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonTestGenerationProcessor.kt @@ -0,0 +1,341 @@ +package org.utbot.python + +import mu.KotlinLogging +import org.parsers.python.PythonParser +import org.utbot.framework.codegen.domain.HangingTestsTimeout +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.python.code.PythonCode +import org.utbot.python.coverage.CoverageFormat +import org.utbot.python.coverage.CoverageInfo +import org.utbot.python.coverage.CoverageOutputFormat +import org.utbot.python.coverage.PyInstruction +import org.utbot.python.coverage.filterMissedLines +import org.utbot.python.coverage.getInstructionsList +import org.utbot.python.coverage.getLinesList +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonMethodId +import org.utbot.python.framework.api.python.PythonModel +import org.utbot.python.framework.api.python.PythonUtExecution +import org.utbot.python.framework.api.python.RawPythonAnnotation +import org.utbot.python.framework.api.python.pythonBuiltinsModuleName +import org.utbot.python.framework.api.python.util.pythonAnyClassId +import org.utbot.python.framework.api.python.util.pythonNoneClassId +import org.utbot.python.framework.codegen.model.PythonCodeGenerator +import org.utbot.python.framework.codegen.model.PythonImport +import org.utbot.python.framework.codegen.model.PythonSysPathImport +import org.utbot.python.framework.codegen.model.PythonSystemImport +import org.utbot.python.framework.codegen.model.PythonUserImport +import org.utpython.types.PythonConcreteCompositeTypeDescription +import org.utpython.types.PythonFunctionDefinition +import org.utpython.types.general.CompositeType +import org.utpython.types.getPythonAttributes +import org.utpython.types.mypy.MypyBuildDirectory +import org.utpython.types.mypy.MypyInfoBuild +import org.utpython.types.mypy.readMypyAnnotationStorageAndInitialErrors +import org.utpython.types.pythonDescription +import org.utpython.types.pythonName +import org.utbot.python.utils.TemporaryFileManager +import org.utbot.python.utils.convertToTime +import org.utbot.python.utils.separateTimeout +import java.nio.file.Path +import kotlin.io.path.Path +import kotlin.io.path.pathString + +private val logger = KotlinLogging.logger {} + +// TODO: add asserts that one or less of containing classes and only one file +abstract class PythonTestGenerationProcessor { + abstract val configuration: PythonTestGenerationConfig + + fun sourceCodeAnalyze(): MypyConfig { + return sourceCodeAnalyze( + configuration.sysPathDirectories, + configuration.pythonPath, + configuration.testFileInformation, + ) + } + + fun testGenerate(mypyConfig: MypyConfig): List { + val testCaseGenerator = PythonTestCaseGenerator( + configuration = configuration, + mypyConfig = mypyConfig, + ) + + val oneFunctionTimeout = separateTimeout(configuration.timeout, configuration.testedMethods.size) + val countOfFunctions = configuration.testedMethods.size + val startTime = System.currentTimeMillis() + + val tests = configuration.testedMethods.mapIndexedNotNull { index, methodHeader -> + if (configuration.isCanceled()) { + return emptyList() + } + val usedTime = System.currentTimeMillis() - startTime + val expectedTime = index * oneFunctionTimeout + val localOneFunctionTimeout = if (usedTime < expectedTime) { + separateTimeout(configuration.timeout - usedTime, countOfFunctions - index) + } else { + oneFunctionTimeout + } + val localUntil = System.currentTimeMillis() + localOneFunctionTimeout + logger.info { "Local timeout ${configuration.timeout / configuration.testedMethods.size}ms. Until ${localUntil.convertToTime()}" } + try { + val method = findMethodByHeader( + mypyConfig.mypyStorage, + methodHeader, + configuration.testFileInformation.moduleName, + configuration.testFileInformation.testedFileContent + ) + testCaseGenerator.generate(method, localUntil) + } catch (e: SelectedMethodIsNotAFunctionDefinition) { + logger.warn { "Skipping method ${e.methodName}: did not find its function definition" } + null + } + }.flatten() + val notEmptyTests = tests.filter { it.executions.isNotEmpty() } + + val emptyTests = tests + .groupBy { it.method } + .filter { it.value.all { testSet -> testSet.executions.isEmpty() } } + .map { it.key.name } + + if (emptyTests.isNotEmpty()) { + notGeneratedTestsAction(emptyTests) + } + + return notEmptyTests + } + + fun testCodeGenerate(testSets: List): String { + if (testSets.isEmpty()) return "" + val containingClassName = getContainingClassName(testSets) + val classId = PythonClassId(configuration.testFileInformation.moduleName, containingClassName) + + val methodIds = testSets.associate { testSet -> + testSet.method to PythonMethodId( + classId, + testSet.method.renderMethodName(), + RawPythonAnnotation(pythonAnyClassId.name), + testSet.method.arguments.map { argument -> + argument.annotation?.let { annotation -> + RawPythonAnnotation(annotation) + } ?: pythonAnyClassId + }, + ) + } + + val paramNames = testSets.associate { testSet -> + var params = testSet.method.arguments.map { it.name } + if (testSet.method.hasThisArgument) { + params = params.drop(1) + } + methodIds[testSet.method] as ExecutableId to params + }.toMutableMap() + + val collectedImports = collectImports(testSets) + + val context = UtContext(this::class.java.classLoader) + withUtContext(context) { + val codegen = PythonCodeGenerator( + classId, + paramNames = paramNames, + testFramework = configuration.testFramework, + testClassPackageName = "", + hangingTestsTimeout = HangingTestsTimeout(configuration.timeoutForRun), + runtimeExceptionTestsBehaviour = configuration.runtimeExceptionTestsBehaviour, + ) + codegen.context.existingVariableNames = codegen.context.existingVariableNames.addAll(collectedImports.flatMap { listOfNotNull(it.moduleName, it.rootModuleName, it.importName) }) + val testCode = codegen.pythonGenerateAsStringWithTestReport( + testSets.map { testSet -> + CgMethodTestSet( + executableId = methodIds[testSet.method] as ExecutableId, + executions = testSet.executions, + clustersInfo = testSet.clustersInfo, + ) + }, + collectedImports, + ).generatedCode + return testCode + } + } + + abstract fun saveTests(testsCode: String) + + abstract fun notGeneratedTestsAction(testedFunctions: List) + + abstract fun processCoverageInfo(testSets: List) + + private fun getContainingClassName(testSets: List): String { + val containingClasses = testSets.map { it.method.containingPythonClass?.pythonName()?.replace(".", "") ?: "TopLevelFunctions" } + return containingClasses.toSet().first() + } + + private fun collectImports(notEmptyTests: List): Set { + val importParamModules = notEmptyTests.flatMap { testSet -> + testSet.executions.flatMap { execution -> + val params = (execution.stateAfter.parameters + execution.stateBefore.parameters).toMutableList() + val self = mutableListOf(execution.stateBefore.thisInstance, execution.stateAfter.thisInstance) + if (execution is PythonUtExecution) { + params.addAll(execution.stateInit.parameters) + self.add(execution.stateInit.thisInstance) + } + (params + self) + .filterNotNull() + .flatMap { utModel -> + (utModel as PythonModel).let { + it.allContainingClassIds + .filterNot { classId -> classId == pythonNoneClassId } + .map { classId -> PythonUserImport(importName_ = classId.moduleName) } + } + } + } + }.toSet() + val importResultModules = notEmptyTests.flatMap { testSet -> + testSet.executions.mapNotNull { execution -> + if (execution.result is UtExecutionSuccess) { + (execution.result as UtExecutionSuccess).let { result -> + (result.model as PythonModel).let { + it.allContainingClassIds + .filterNot { classId -> classId == pythonNoneClassId } + .map { classId -> PythonUserImport(importName_ = classId.moduleName) } + } + } + } else null + }.flatten() + }.toSet() + val rootModule = configuration.testFileInformation.moduleName.split(".").first() + val testRootModule = PythonUserImport(importName_ = rootModule) + val sysImport = PythonSystemImport("sys") + val osImport = PythonSystemImport("os") + val sysPathImports = relativizePaths( + configuration.testSourceRootPath, + configuration.sysPathDirectories + ).map { PythonSysPathImport(it) } + + val testFrameworkModule = + configuration.testFramework.testSuperClass?.let { PythonUserImport(importName_ = (it as PythonClassId).rootModuleName) } + + return (importParamModules + importResultModules + testRootModule + sysPathImports + listOf( + testFrameworkModule, osImport, sysImport + )) + .filterNotNull() + .filterNot { it.rootModuleName == pythonBuiltinsModuleName } + .toSet() + } + + private fun findMethodByHeader( + mypyStorage: MypyInfoBuild, + method: PythonMethodHeader, + curModule: String, + sourceFileContent: String + ): PythonMethod { + var containingClass: CompositeType? = null + val containingClassName = method.containingPythonClassId?.simpleName + val definition = if (containingClassName == null) { + mypyStorage.definitions[curModule]?.get(method.name)?.getUtBotDefinition() + } else { + containingClass = + mypyStorage.definitions[curModule]?.get(containingClassName)?.getUtBotType() as? CompositeType + ?: throw SelectedMethodIsNotAFunctionDefinition(method.name) + val descr = containingClass.pythonDescription() + if (descr !is PythonConcreteCompositeTypeDescription) + throw SelectedMethodIsNotAFunctionDefinition(method.name) + mypyStorage.definitions[curModule]?.get(containingClassName)?.type?.asUtBotType?.getPythonAttributes()?.first { + it.meta.name == method.name + } + } ?: throw SelectedMethodIsNotAFunctionDefinition(method.name) + val parsedFile = PythonParser(sourceFileContent).Module() + val funcDef = PythonCode.findFunctionDefinition(parsedFile, method) + val decorators = funcDef.decorators.map { PyDecorator.decoratorByName(it.name.toString()) } + + if (definition is PythonFunctionDefinition) { + return PythonBaseMethod( + name = method.name, + moduleFilename = method.moduleFilename, + containingPythonClass = containingClass, + codeAsString = funcDef.body.source, + definition = definition, + ast = funcDef.body + ) + } else if (decorators == listOf(PyDecorator.StaticMethod)) { + return PythonDecoratedMethod( + name = method.name, + moduleFilename = method.moduleFilename, + containingPythonClass = containingClass, + codeAsString = funcDef.body.source, + definition = definition, + ast = funcDef.body, + decorator = decorators.first() + ) + } else { + throw SelectedMethodIsNotAFunctionDefinition(method.name) + } + + } + + private fun relativizePaths(rootPath: Path?, paths: Set): Set = + if (rootPath != null) { + paths.map { path -> + rootPath.relativize(Path(path)).pathString + }.toSet() + } else { + paths + } + + private fun getCoverageInfo(testSets: List): CoverageInfo { + val covered = mutableSetOf() + val missed = mutableSetOf() + testSets.forEach { testSet -> + testSet.executions.forEach inner@{ execution -> + val coverage = execution.coverage ?: return@inner + covered.addAll(coverage.coveredInstructions.filterIsInstance()) + missed.addAll(coverage.missedInstructions.filterIsInstance()) + } + } + missed -= covered + val info = when (this.configuration.coverageOutputFormat) { + CoverageOutputFormat.Lines -> { + val coveredLines = getLinesList(covered) + val filteredMissed = filterMissedLines(coveredLines, missed) + val missedLines = getLinesList(filteredMissed) + CoverageInfo(coveredLines, missedLines) + } + CoverageOutputFormat.Instructions -> CoverageInfo( + getInstructionsList(covered), + getInstructionsList(missed) + ) + } + return CoverageInfo(info.covered.toSet().toList(), info.notCovered.toSet().toList()) + } + + protected fun getStringCoverageInfo(testSets: List): String { + val coverageInfo = getCoverageInfo(testSets) + val covered = coverageInfo.covered.map { it.toJson() } + val notCovered = coverageInfo.notCovered.map { it.toJson() } + return "{\"covered\": [${covered.joinToString(", ")}], \"notCovered\": [${notCovered.joinToString(", ")}]}" + } + + companion object { + fun sourceCodeAnalyze( + sysPathDirectories: Set, + pythonPath: String, + testFileInformation: TestFileInformation, + ): MypyConfig { + val mypyBuildRoot = TemporaryFileManager.assignTemporaryFile(tag = "mypyBuildRoot") + val buildDirectory = MypyBuildDirectory(mypyBuildRoot, sysPathDirectories) + val (mypyInfoBuild, mypyReportLines) = readMypyAnnotationStorageAndInitialErrors( + pythonPath, + testFileInformation.testedFilePath, + testFileInformation.moduleName, + buildDirectory + ) + return MypyConfig(mypyInfoBuild, mypyReportLines, buildDirectory) + } + + } +} + +data class SelectedMethodIsNotAFunctionDefinition(val methodName: String): Exception() \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt new file mode 100644 index 0000000000..0de57911d7 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/UTPythonAPI.kt @@ -0,0 +1,308 @@ +package org.utbot.python + +import org.parsers.python.ast.Block +import org.utbot.framework.plugin.api.UtClusterInfo +import org.utbot.framework.plugin.api.UtError +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonTreeModel +import org.utbot.python.framework.api.python.util.pythonAnyClassId +import org.utpython.types.PythonCallableTypeDescription +import org.utpython.types.PythonDefinition +import org.utpython.types.PythonDefinitionDescription +import org.utpython.types.PythonFuncItemDescription +import org.utpython.types.PythonFunctionDefinition +import org.utpython.types.general.CompositeType +import org.utpython.types.general.FunctionType +import org.utpython.types.pythonAnyType +import org.utpython.types.pythonDescription +import org.utpython.types.pythonName +import org.utpython.types.pythonTypeRepresentation +import org.utpython.types.utils.isNamed +import org.utpython.types.utils.isRequired + +data class PythonArgument( + val name: String, + val annotation: String?, + val isNamed: Boolean = false, +) + +class PythonMethodHeader( + val name: String, + val moduleFilename: String, + val containingPythonClassId: PythonClassId?, +) { + val fullname = containingPythonClassId?.let { "${it.typeName}.$name" } ?: name +} + + +interface PythonMethod { + val name: String + val moduleFilename: String + val containingPythonClass: CompositeType? + val codeAsString: String + val ast: Block + + fun methodSignature(): String + val hasThisArgument: Boolean + val arguments: List + val argumentsWithoutSelf: List + val thisObjectName: String? + val argumentsNames: List + val argumentsNamesWithoutSelf: List + + val methodType: FunctionType + val methodMeta: PythonDefinitionDescription + + fun makeCopyWithNewType(newFunctionType: FunctionType): PythonMethod + fun createShortForm(): Pair? + fun changeDefinition(signature: FunctionType) + + fun renderMethodName(): String +} + +class PythonBaseMethod( + override val name: String, + override val moduleFilename: String, + override val containingPythonClass: CompositeType?, + override val codeAsString: String, + private var definition: PythonFunctionDefinition, + override val ast: Block +) : PythonMethod { + override fun methodSignature(): String = "$name(" + arguments.joinToString(", ") { + "${it.name}: ${it.annotation ?: pythonAnyClassId.name}" + } + ")" + + /* + Check that the first argument is `self` of `cls`. + TODO: We should support `@property` decorator + */ + override val hasThisArgument: Boolean + get() = containingPythonClass != null && definition.meta.args.any { it.isSelf } + + override val arguments: List + get() { + val meta = definition.meta + val description = definition.type.pythonDescription() as PythonCallableTypeDescription + return (definition.type.arguments).mapIndexed { index, type -> + PythonArgument( + meta.args[index].name, + type.pythonTypeRepresentation(), // TODO: improve pythonTypeRepresentation + isNamed(description.argumentKinds[index]) + ) + } + } + + override val argumentsWithoutSelf: List + get() = if (hasThisArgument) arguments.drop(1) else arguments + + override val thisObjectName: String? + get() = if (hasThisArgument) arguments[0].name else null + + override val argumentsNames: List + get() = arguments.map { it.name } + + override val argumentsNamesWithoutSelf: List + get() = argumentsNames.drop(if (hasThisArgument) 1 else 0) + + override val methodType: FunctionType = definition.type + + override val methodMeta: PythonDefinitionDescription = definition.meta + + override fun makeCopyWithNewType(newFunctionType: FunctionType): PythonMethod { + val newDefinition = PythonFunctionDefinition(definition.meta, newFunctionType) + return PythonBaseMethod(name, moduleFilename, containingPythonClass, codeAsString, newDefinition, ast) + } + + override fun createShortForm(): Pair? { + val meta = methodType.pythonDescription() as PythonCallableTypeDescription + val argKinds = meta.argumentKinds + if (argKinds.any { !isRequired(it) }) { + val originalDef = definition + val shortType = meta.removeNotRequiredArgs(originalDef.type) + val shortMeta = PythonFuncItemDescription( + originalDef.meta.name, + originalDef.meta.args.filterIndexed { index, _ -> isRequired(argKinds[index]) } + ) + val additionalVars = originalDef.meta.args + .filterIndexed { index, _ -> !isRequired(argKinds[index]) } + .mapIndexed { index, arg -> + "${arg.name}: ${argumentsWithoutSelf[index].annotation ?: pythonAnyType.pythonTypeRepresentation()}" + } + .joinToString(separator = "\n", prefix = "\n") + val shortDef = PythonFunctionDefinition(shortMeta, shortType) + val shortMethod = PythonBaseMethod( + name, + moduleFilename, + containingPythonClass, + codeAsString, + shortDef, + ast + ) + return Pair(shortMethod, additionalVars) + } + return null + } + + fun changeDefinition(newDefinition: PythonDefinition) { + require(newDefinition is PythonFunctionDefinition) + definition = newDefinition + } + + override fun changeDefinition(signature: FunctionType) { + val newDefinition = PythonFunctionDefinition( + definition.meta, + signature + ) + changeDefinition(newDefinition) + } + + override fun renderMethodName(): String { + return name + } +} + +class PythonDecoratedMethod( + override val name: String, + override val moduleFilename: String, + override val containingPythonClass: CompositeType?, + override val codeAsString: String, + private var definition: PythonDefinition, + override val ast: Block, + val decorator: PyDecorator, +) : PythonMethod { + override val methodType: FunctionType = definition.type as FunctionType + override val methodMeta: PythonDefinitionDescription = definition.meta + val typeMeta: PythonCallableTypeDescription = definition.type.pythonDescription() as PythonCallableTypeDescription + + fun changeDefinition(newDefinition: PythonDefinition) { + require(checkDefinition(newDefinition)) { error("Cannot test non-function object") } + definition = newDefinition + } + + override fun changeDefinition(signature: FunctionType) { + val newDefinition = PythonDefinition( + methodMeta, + signature + ) + checkDefinition(newDefinition) + changeDefinition(newDefinition) + } + + override fun renderMethodName(): String { + return decorator.generateCallableName(this) + } + + override fun makeCopyWithNewType(newFunctionType: FunctionType): PythonMethod { + val newDefinition = PythonDefinition(methodMeta, newFunctionType) + return PythonDecoratedMethod( + name, moduleFilename, containingPythonClass, codeAsString, newDefinition, ast, decorator + ) + } + + override fun createShortForm(): Pair? = null + + init { + assert(checkDefinition(definition)) { error("Cannot test non-function object") } + } + override fun methodSignature(): String = "${decorator.generateCallableName(this)}(" + arguments.joinToString(", ") { + "${it.name}: ${it.annotation ?: pythonAnyClassId.name}" + } + ")" + + /* + Check that the first argument is `self` of `cls`. + */ + override val hasThisArgument: Boolean + get() = containingPythonClass != null && decorator.hasSelfArgument() + + override val arguments: List + get() { + return (methodType.arguments).mapIndexed { index, type -> + PythonArgument( + typeMeta.argumentNames[index] ?: "arg$index", + type.pythonTypeRepresentation(), // TODO: improve pythonTypeRepresentation + isNamed(typeMeta.argumentKinds[index]) + ) + } + } + + override val argumentsWithoutSelf: List + get() = if (hasThisArgument) arguments.drop(1) else arguments + + override val thisObjectName: String? + get() = if (hasThisArgument) arguments[0].name else null + + override val argumentsNames: List + get() = arguments.map { it.name } + + override val argumentsNamesWithoutSelf: List + get() = argumentsNames.drop(if (hasThisArgument) 1 else 0) + + companion object { + fun checkDefinition(definition: PythonDefinition): Boolean { + val type = definition.type + val meta = definition.type.pythonDescription() + return type is FunctionType && meta is PythonCallableTypeDescription + } + } +} + +data class PythonTestSet( + val method: PythonMethod, + val executions: List, + val errors: List, + val executionsNumber: Int = 0, + val clustersInfo: List> = listOf(null to executions.indices) +) + +data class FunctionArguments( + val thisObject: PythonTreeModel?, + val thisObjectName: String?, + val arguments: List, + val names: List, +) { + val allArguments: List = (listOf(thisObject) + arguments).filterNotNull() +} + +sealed interface PyDecorator { + fun generateCallableName(method: PythonMethod, baseName: String? = null): String + fun hasSelfArgument(): Boolean + val type: PythonClassId + + object StaticMethod : PyDecorator { + override fun generateCallableName(method: PythonMethod, baseName: String?) = + "${method.containingPythonClass!!.pythonName()}.${method.name}" + + override fun hasSelfArgument() = false + + override val type: PythonClassId = PythonClassId("staticmethod") + } + + object ClassMethod : PyDecorator { + override fun generateCallableName(method: PythonMethod, baseName: String?) = + "${method.containingPythonClass!!.pythonName()}.${method.name}" + + override fun hasSelfArgument() = true + + override val type: PythonClassId = PythonClassId("classmethod") + } + + class UnknownDecorator( + override val type: PythonClassId, + ) : PyDecorator { + override fun generateCallableName(method: PythonMethod, baseName: String?) = + baseName ?: method.name + + override fun hasSelfArgument() = true + } + + companion object { + fun decoratorByName(decoratorName: String): PyDecorator { + return when (decoratorName) { + "classmethod" -> ClassMethod + "staticmethod" -> StaticMethod + else -> UnknownDecorator(PythonClassId(decoratorName)) + } + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/code/CodeGen.kt b/utbot-python/src/main/kotlin/org/utbot/python/code/CodeGen.kt new file mode 100644 index 0000000000..7c9479dc49 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/code/CodeGen.kt @@ -0,0 +1,40 @@ +package org.utbot.python.code + +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.python.PythonMethod +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.codegen.PythonCgLanguageAssistant +import org.utpython.types.general.UtType + + +object PythonCodeGenerator { + + fun generateMypyCheckCode( + method: PythonMethod, + methodAnnotations: Map, + directoriesForSysPath: Set, + moduleToImport: String, + namesInModule: Collection, + additionalVars: String + ): String { + val context = UtContext(this::class.java.classLoader) + withUtContext(context) { + val codegen = org.utbot.python.framework.codegen.model.PythonCodeGenerator( + PythonClassId("TopLevelFunction"), + paramNames = emptyMap>().toMutableMap(), + testFramework = PythonCgLanguageAssistant.getLanguageTestFrameworkManager().testFrameworks[0], + testClassPackageName = "", + ) + return codegen.generateMypyCheckCode( + method, + methodAnnotations, + directoriesForSysPath, + moduleToImport, + namesInModule, + additionalVars + ) + } + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/code/PythonCodeAPI.kt b/utbot-python/src/main/kotlin/org/utbot/python/code/PythonCodeAPI.kt new file mode 100644 index 0000000000..446f38059f --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/code/PythonCodeAPI.kt @@ -0,0 +1,48 @@ +package org.utbot.python.code + +import org.parsers.python.ast.Block +import org.parsers.python.ast.ClassDefinition +import org.parsers.python.ast.FunctionDefinition +import org.parsers.python.ast.Module +import org.utbot.python.PythonMethodHeader +import org.utbot.python.newtyping.ast.ParsedFunctionDefinition +import org.utbot.python.newtyping.ast.parseClassDefinition +import org.utbot.python.newtyping.ast.parseFunctionDefinition + +object PythonCode { + + fun getTopLevelFunctions(parsedFile: Module): List { + return parsedFile.children().filterIsInstance() + } + + fun getTopLevelClasses(parsedFile: Module): List { + return parsedFile.children().filterIsInstance() + } + + fun getInnerClasses(classDef: ClassDefinition): List { + return classDef.children().filterIsInstance().flatMap {it.children() }.filterIsInstance() + } + + fun getClassMethods(class_: Block): List { + return class_.children().filterIsInstance() + } + + fun findFunctionDefinition(parsedFile: Module, method: PythonMethodHeader): ParsedFunctionDefinition { + return if (method.containingPythonClassId == null) { + getTopLevelFunctions(parsedFile).mapNotNull { + parseFunctionDefinition(it) + }.firstOrNull { + it.name.toString() == method.name + } ?: throw Exception("Couldn't find top-level function ${method.name}") + } else { + getTopLevelClasses(parsedFile) + .flatMap { listOf(it) + getInnerClasses(it) } + .mapNotNull { parseClassDefinition(it) } + .flatMap { getClassMethods(it.body) } + .mapNotNull { parseFunctionDefinition(it) } + .firstOrNull { + it.name.toString() == method.name + } ?: throw Exception("Couldn't find method ${method.name}") + } + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/coverage/CoverageApi.kt b/utbot-python/src/main/kotlin/org/utbot/python/coverage/CoverageApi.kt new file mode 100644 index 0000000000..34403ad42a --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/coverage/CoverageApi.kt @@ -0,0 +1,82 @@ +package org.utbot.python.coverage + +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.Instruction + +enum class PythonCoverageMode { + Lines { + override fun toString() = "lines" + }, + + Instructions { + override fun toString() = "instructions" + }; + + companion object { + fun parse(name: String): PythonCoverageMode { + return PythonCoverageMode.values().first { + it.name.lowercase() == name.lowercase() + } + } + } +} + +data class PyInstruction( + val pyLineNumber: Int, + val offset: Long, + val fromMainFrame: Boolean, +) : Instruction( + "", + "", + pyLineNumber, + (pyLineNumber.toLong() to offset).toCoverageId() * 2 + fromMainFrame.toLong()) { + override fun toString(): String = listOf(lineNumber, offset, fromMainFrame).joinToString(":") + + constructor(lineNumber: Int) : this(lineNumber, lineNumber.toLong(), true) + constructor(lineNumber: Int, id: Long) : this(lineNumber, id.floorDiv(2).toPair().second, id % 2 == 1L) +} + +fun Boolean.toLong() = if (this) 1L else 0L + +fun String.toPyInstruction(): PyInstruction? { + val data = this.split(":") + when (data.size) { + 3 -> { + val line = data[0].toInt() + val offset = data[1].toLong() + val fromMainFrame = data[2].toInt() != 0 + return PyInstruction(line, offset, fromMainFrame) + } + 2 -> { + val line = data[0].toInt() + val offset = data[1].toLong() + return PyInstruction(line, offset, true) + } + 1 -> { + val line = data[0].toInt() + return PyInstruction(line) + } + else -> return null + } +} + +fun buildCoverage(coveredStatements: List, missedStatements: List): Coverage { + return Coverage( + coveredInstructions = coveredStatements, + instructionsCount = (coveredStatements.size + missedStatements.size).toLong(), + missedInstructions = missedStatements + ) +} + +enum class CoverageOutputFormat { + Lines, + Instructions; + + companion object { + fun parse(name: String): CoverageOutputFormat { + return CoverageOutputFormat.values().first { + it.name.lowercase() == name.lowercase() + } + } + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/coverage/CoverageIdGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/coverage/CoverageIdGenerator.kt new file mode 100644 index 0000000000..15aafacb54 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/coverage/CoverageIdGenerator.kt @@ -0,0 +1,13 @@ +package org.utbot.python.coverage + +import java.util.concurrent.atomic.AtomicLong + +object CoverageIdGenerator { + private const val LOWER_BOUND: Long = 1500_000_000 + + private val lastId: AtomicLong = AtomicLong(LOWER_BOUND) + + fun createId(): String { + return lastId.incrementAndGet().toString(radix = 16) + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/coverage/CoverageOutput.kt b/utbot-python/src/main/kotlin/org/utbot/python/coverage/CoverageOutput.kt new file mode 100644 index 0000000000..0bd4055da4 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/coverage/CoverageOutput.kt @@ -0,0 +1,41 @@ +package org.utbot.python.coverage + +sealed interface CoverageFormat { + fun toJson(): String +} +data class LineCoverage(val start: Int, val end: Int) : CoverageFormat { + override fun toJson(): String = "{\"start\": ${start}, \"end\": ${end}}" +} + +data class InstructionCoverage( + val line: Int, + val offset: Long, + val fromMainFrame: Boolean +) : CoverageFormat { + override fun toJson(): String = "{\"line\": ${line}, \"offset\": ${offset}, \"fromMainFrame\": ${fromMainFrame}}" +} + +data class CoverageInfo( + val covered: List, + val notCovered: List, +) + +fun getLinesList(instructions: Collection): List = + instructions + .map { it.lineNumber } + .sorted() + .fold(emptyList()) { acc, lineNumber -> + if (acc.isEmpty()) + return@fold listOf(LineCoverage(lineNumber, lineNumber)) + val elem = acc.last() + if (elem.end + 1 == lineNumber || elem.end == lineNumber ) + acc.dropLast(1) + listOf(LineCoverage(elem.start, lineNumber)) + else + acc + listOf(LineCoverage(lineNumber, lineNumber)) + } + +fun filterMissedLines(covered: Collection, missed: Collection): List = + missed.filterNot { missedInstruction -> covered.any { it.start <= missedInstruction.lineNumber && missedInstruction.lineNumber <= it.end } } + +fun getInstructionsList(instructions: Collection): List = + instructions.map { InstructionCoverage(it.lineNumber, it.offset, it.fromMainFrame) }.toSet().toList() diff --git a/utbot-python/src/main/kotlin/org/utbot/python/coverage/Utils.kt b/utbot-python/src/main/kotlin/org/utbot/python/coverage/Utils.kt new file mode 100644 index 0000000000..37e04a747d --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/coverage/Utils.kt @@ -0,0 +1,22 @@ +package org.utbot.python.coverage + +import kotlin.math.ceil +import kotlin.math.max +import kotlin.math.min +import kotlin.math.sqrt + +fun Long.toPair(): Pair { + val n = ceil(sqrt(this + 2.0)).toLong() - 1 + val k = this - (n * n - 1) + return if (k <= n + 1) { + n + 1 to k + } else { + k to n + 1 + } +} + +fun Pair.toCoverageId(): Long { + val n = max(this.first, this.second) - 1 + val k = min(this.first, this.second) + return (n * n - 1) + k +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/engine/ExecutionFeedback.kt b/utbot-python/src/main/kotlin/org/utbot/python/engine/ExecutionFeedback.kt new file mode 100644 index 0000000000..79e43604c8 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/engine/ExecutionFeedback.kt @@ -0,0 +1,12 @@ +package org.utbot.python.engine + +import org.utbot.framework.plugin.api.UtError +import org.utbot.python.framework.api.python.PythonUtExecution + +sealed interface ExecutionFeedback +class ValidExecution(val utFuzzedExecution: PythonUtExecution): ExecutionFeedback +class InvalidExecution(val utError: UtError): ExecutionFeedback +class TypeErrorFeedback(val message: String) : ExecutionFeedback +class ArgumentsTypeErrorFeedback(val message: String) : ExecutionFeedback +class CachedExecutionFeedback(val cachedFeedback: ExecutionFeedback) : ExecutionFeedback +object FakeNodeFeedback : ExecutionFeedback \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/engine/ExecutionResultHandler.kt b/utbot-python/src/main/kotlin/org/utbot/python/engine/ExecutionResultHandler.kt new file mode 100644 index 0000000000..4445ce68a6 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/engine/ExecutionResultHandler.kt @@ -0,0 +1,126 @@ +package org.utbot.python.engine + +import mu.KotlinLogging +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.DocStatement +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.framework.plugin.api.UtExecutionFailure +import org.utbot.framework.plugin.api.UtExecutionResult +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtImplicitlyThrownException +import org.utbot.framework.plugin.api.UtTimeoutException +import org.utbot.python.PythonMethod +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.coverage.PyInstruction +import org.utbot.python.coverage.buildCoverage +import org.utbot.python.engine.utils.transformModelList +import org.utbot.python.evaluation.PythonEvaluationSuccess +import org.utbot.python.evaluation.serialization.toPythonTree +import org.utbot.python.framework.api.python.PythonTreeModel +import org.utbot.python.framework.api.python.PythonUtExecution +import org.utpython.types.general.UtType +import org.utpython.types.pythonTypeRepresentation +import org.utbot.python.utils.camelToSnakeCase +import org.utbot.summary.fuzzer.names.TestSuggestedInfo + +private val logger = KotlinLogging.logger { } + +object ExecutionResultHandler { + fun handleTimeoutResult( + method: PythonMethod, + arguments: List, + coveredInstructions: List, + summary: List? = null, + ): ExecutionFeedback { + val hasThisObject = method.hasThisArgument + val (beforeThisObject, beforeModelList) = if (hasThisObject) { + arguments.first() to arguments.drop(1) + } else { + null to arguments + } + val executionResult = UtTimeoutException(TimeoutException("Execution is too long")) + val testMethodName = suggestExecutionName(method, executionResult) + val coverage = Coverage(coveredInstructions) + val utFuzzedExecution = PythonUtExecution( + stateInit = EnvironmentModels(beforeThisObject, beforeModelList, emptyMap(), executableToCall = null), + stateBefore = EnvironmentModels(beforeThisObject, beforeModelList, emptyMap(), executableToCall = null), + stateAfter = EnvironmentModels(beforeThisObject, beforeModelList, emptyMap(), executableToCall = null), + diffIds = emptyList(), + result = executionResult, + coverage = coverage, + testMethodName = testMethodName.testName?.camelToSnakeCase(), + displayName = testMethodName.displayName, + summary = summary, + arguments = method.argumentsWithoutSelf + ) + return ValidExecution(utFuzzedExecution) + } + + fun handleSuccessResult( + method: PythonMethod, + configuration: PythonTestGenerationConfig, + types: List, + evaluationResult: PythonEvaluationSuccess, + summary: List? = null, + ): ExecutionFeedback { + val hasThisObject = method.hasThisArgument + + val resultModel = evaluationResult.stateAfter.getById(evaluationResult.resultId).toPythonTree(evaluationResult.stateAfter) + + if (evaluationResult.isException && (resultModel.type.name in configuration.prohibitedExceptions)) { // wrong type (sometimes mypy fails) + val errorMessage = "Evaluation with prohibited exception. Substituted types: ${ + types.joinToString { it.pythonTypeRepresentation() } + }. Exception type: ${resultModel.type.name}" + + logger.debug { errorMessage } + return TypeErrorFeedback(errorMessage) + } + val executionResult = + if (evaluationResult.isException) { + UtImplicitlyThrownException(Throwable(resultModel.type.toString()), false) + } + else { + UtExecutionSuccess(PythonTreeModel(resultModel)) + } + val testMethodName = suggestExecutionName(method, executionResult) + + val (thisObject, initModelList) = transformModelList(hasThisObject, evaluationResult.stateInit, evaluationResult.modelListIds) + val (beforeThisObject, beforeModelList) = transformModelList(hasThisObject, evaluationResult.stateBefore, evaluationResult.modelListIds) + val (afterThisObject, afterModelList) = transformModelList(hasThisObject, evaluationResult.stateAfter, evaluationResult.modelListIds) + + val utFuzzedExecution = PythonUtExecution( + stateInit = EnvironmentModels(thisObject, initModelList, emptyMap(), executableToCall = null), + stateBefore = EnvironmentModels(beforeThisObject, beforeModelList, emptyMap(), executableToCall = null), + stateAfter = EnvironmentModels(afterThisObject, afterModelList, emptyMap(), executableToCall = null), + diffIds = evaluationResult.diffIds, + result = executionResult, + coverage = buildCoverage(evaluationResult.coveredStatements, evaluationResult.missedStatements), + testMethodName = testMethodName.testName?.camelToSnakeCase(), + displayName = testMethodName.displayName, + summary = summary, + arguments = method.argumentsWithoutSelf, + ) + return ValidExecution(utFuzzedExecution) + } + + private fun suggestExecutionName( + method: PythonMethod, + executionResult: UtExecutionResult + ): TestSuggestedInfo { + val testSuffix = when (executionResult) { + is UtExecutionSuccess -> { + // can be improved + method.name + } + is UtExecutionFailure -> "${method.name}_with_exception" + else -> method.name + } + val testName = "test_$testSuffix" + return TestSuggestedInfo( + testName, + testName, + ) + } + +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/engine/ExecutionStorage.kt b/utbot-python/src/main/kotlin/org/utbot/python/engine/ExecutionStorage.kt new file mode 100644 index 0000000000..4ace0716db --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/engine/ExecutionStorage.kt @@ -0,0 +1,25 @@ +package org.utbot.python.engine + +import org.utbot.framework.plugin.api.UtError +import org.utbot.python.framework.api.python.PythonUtExecution + +class ExecutionStorage { + val fuzzingExecutions: MutableList = mutableListOf() + val fuzzingErrors: MutableList = mutableListOf() + + fun saveFuzzingExecution(feedback: ExecutionFeedback) { + when (feedback) { + is ValidExecution -> { + synchronized(fuzzingExecutions) { + fuzzingExecutions += feedback.utFuzzedExecution + } + } + is InvalidExecution -> { + synchronized(fuzzingErrors) { + fuzzingErrors += feedback.utError + } + } + else -> {} + } + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/engine/GlobalPythonEngine.kt b/utbot-python/src/main/kotlin/org/utbot/python/engine/GlobalPythonEngine.kt new file mode 100644 index 0000000000..89a98ebf8b --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/engine/GlobalPythonEngine.kt @@ -0,0 +1,87 @@ +package org.utbot.python.engine + +import mu.KotlinLogging +import org.utbot.python.MypyConfig +import org.utbot.python.PythonMethod +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.engine.fuzzing.FuzzingEngine +import org.utpython.types.PythonTypeHintsStorage +import org.utbot.python.newtyping.ast.visitor.Visitor +import org.utbot.python.newtyping.ast.visitor.constants.ConstantCollector +import org.utbot.python.newtyping.ast.visitor.hints.HintCollector +import org.utpython.types.mypy.GlobalNamesStorage +import org.utpython.types.mypy.MypyInfoBuild +import kotlin.concurrent.thread + +private val logger = KotlinLogging.logger {} + +class GlobalPythonEngine( + val method: PythonMethod, + val configuration: PythonTestGenerationConfig, + private val mypyConfig: MypyConfig, + val until: Long, +) { + val executionStorage = ExecutionStorage() + private val typeStorage = PythonTypeHintsStorage.get(mypyConfig.mypyStorage) + private val constantCollector = ConstantCollector(typeStorage) + private val hintCollector = constructHintCollector( + mypyConfig.mypyStorage, + typeStorage, + constantCollector, + method, + configuration.testFileInformation.moduleName + ) + + private fun runFuzzing() { + FuzzingEngine( + method, + configuration, + typeStorage, + hintCollector, + constantCollector, + mypyConfig.mypyStorage, + mypyConfig.mypyReportLine, + until, + executionStorage, + ).start() + } + + fun run() { + val fuzzing = thread( + start = true, + isDaemon = false, + name = "Fuzzer" + ) { + logger.info { " >>>>>>> Start fuzzer >>>>>>> " } + runFuzzing() + logger.info { " <<<<<<< Finish fuzzer <<<<<<< " } + } + fuzzing.join() + } + + private fun constructHintCollector( + mypyStorage: MypyInfoBuild, + typeStorage: PythonTypeHintsStorage, + constantCollector: ConstantCollector, + method: PythonMethod, + moduleName: String, + ): HintCollector { + + // initialize definitions first + mypyStorage.definitions[moduleName]!!.values.map { def -> + def.getUtBotDefinition() + } + + val mypyExpressionTypes = mypyStorage.exprTypes[moduleName]?.let { moduleTypes -> + moduleTypes.associate { + Pair(it.startOffset.toInt(), it.endOffset.toInt() + 1) to it.type.asUtBotType + } + } ?: emptyMap() + + val namesStorage = GlobalNamesStorage(mypyStorage) + val hintCollector = HintCollector(method, typeStorage, mypyExpressionTypes, namesStorage, moduleName) + val visitor = Visitor(listOf(hintCollector, constantCollector)) + visitor.visit(method.ast) + return hintCollector + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/engine/fuzzing/FuzzingEngine.kt b/utbot-python/src/main/kotlin/org/utbot/python/engine/fuzzing/FuzzingEngine.kt new file mode 100644 index 0000000000..3d0bc9fdbe --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/engine/fuzzing/FuzzingEngine.kt @@ -0,0 +1,372 @@ +package org.utbot.python.engine.fuzzing + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +import org.utbot.framework.plugin.api.DocRegularStmt +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.framework.plugin.api.UtError +import org.utbot.fuzzing.Control +import org.utbot.fuzzing.NoSeedValueException +import org.utbot.fuzzing.fuzz +import org.utbot.fuzzing.utils.Trie +import org.utbot.python.FunctionArguments +import org.utbot.python.PythonMethod +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.coverage.CoverageIdGenerator +import org.utbot.python.coverage.PyInstruction +import org.utbot.python.engine.CachedExecutionFeedback +import org.utbot.python.engine.ExecutionFeedback +import org.utbot.python.engine.ExecutionResultHandler +import org.utbot.python.engine.ExecutionStorage +import org.utbot.python.engine.FakeNodeFeedback +import org.utbot.python.engine.InvalidExecution +import org.utbot.python.engine.ValidExecution +import org.utbot.python.engine.fuzzing.typeinference.createMethodAnnotationModifications +import org.utbot.python.evaluation.EvaluationCache +import org.utbot.python.evaluation.PythonEvaluationError +import org.utbot.python.evaluation.PythonEvaluationSuccess +import org.utbot.python.evaluation.PythonEvaluationTimeout +import org.utbot.python.evaluation.PythonWorkerManager +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.PythonTreeModel +import org.utbot.python.framework.api.python.PythonTreeWrapper +import org.utbot.python.framework.api.python.util.pythonStrClassId +import org.utbot.python.fuzzing.PythonExecutionResult +import org.utbot.python.fuzzing.PythonFeedback +import org.utbot.python.fuzzing.PythonFuzzedConcreteValue +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonFuzzing +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utpython.types.PythonTypeHintsStorage +import org.utbot.python.newtyping.ast.visitor.constants.ConstantCollector +import org.utbot.python.newtyping.ast.visitor.hints.HintCollector +import org.utpython.types.general.FunctionType +import org.utpython.types.general.UtType +import org.utbot.python.newtyping.inference.InvalidTypeFeedback +import org.utbot.python.newtyping.inference.SuccessFeedback +import org.utbot.python.newtyping.inference.baseline.BaselineAlgorithm +import org.utbot.python.newtyping.inference.baseline.MethodAndVars +import org.utpython.types.mypy.MypyInfoBuild +import org.utpython.types.mypy.MypyReportLine +import org.utpython.types.mypy.getErrorNumber +import org.utpython.types.pythonModules +import org.utpython.types.pythonTypeName +import org.utpython.types.pythonTypeRepresentation +import org.utpython.types.utils.getOffsetLine +import org.utbot.python.utils.ExecutionWithTimeoutMode +import org.utbot.python.utils.TestGenerationLimitManager +import org.utbot.python.utils.TimeoutMode +import org.utbot.python.utils.convertToTime +import java.net.ServerSocket +import kotlin.random.Random + +private val logger = KotlinLogging.logger {} +private const val RANDOM_TYPE_FREQUENCY = 4 +private const val MINIMAL_TIMEOUT_FOR_SUBSTITUTION = 4_000 // ms + +class FuzzingEngine( + val method: PythonMethod, + val configuration: PythonTestGenerationConfig, + private val typeStorage: PythonTypeHintsStorage, + private val hintCollector: HintCollector, + constantCollector: ConstantCollector, + private val mypyStorage: MypyInfoBuild, + private val mypyReport: List, + val until: Long, + private val executionStorage: ExecutionStorage, +) { + private val cache = EvaluationCache() + + private val constants: List = constantCollector.result + .mapNotNull { (type, value) -> + if (type.pythonTypeName() == pythonStrClassId.name && value is String) { + // Filter doctests + if (value.contains(">>>")) return@mapNotNull null + } + logger.debug { "Collected constant: ${type.pythonTypeRepresentation()}: $value" } + PythonFuzzedConcreteValue(type, value) + } + + fun start() { + logger.info { "Fuzzing until: ${until.convertToTime()}" } + val modifications = createMethodAnnotationModifications(method, typeStorage) + val now = System.currentTimeMillis() + val filterModifications = modifications + .take(minOf(modifications.size, maxOf(((until - now) / MINIMAL_TIMEOUT_FOR_SUBSTITUTION).toInt(), 1))) + .map { (modifiedMethod, additionalVars) -> + logger.info { "Substitution: ${modifiedMethod.methodSignature()}" } + MethodAndVars(modifiedMethod, additionalVars) + } + generateTests(method, filterModifications, until) + } + + private fun generateTests( + method: PythonMethod, + methodModifications: List, + until: Long, + ) { + val timeoutLimitManager = TestGenerationLimitManager( + TimeoutMode, + until, + ) + val namesInModule = mypyStorage.names + .getOrDefault(configuration.testFileInformation.moduleName, emptyList()) + .map { it.name } + .filter { + it.length < 4 || !it.startsWith("__") || !it.endsWith("__") + } + + val sourceFileContent = configuration.testFileInformation.testedFileContent + val algo = BaselineAlgorithm( + typeStorage, + hintCollector.result, + configuration.pythonPath, + MethodAndVars(method, ""), + methodModifications, + configuration.sysPathDirectories, + configuration.testFileInformation.moduleName, + namesInModule, + getErrorNumber( + mypyReport, + configuration.testFileInformation.testedFilePath, + getOffsetLine(sourceFileContent, method.ast.beginOffset), + getOffsetLine(sourceFileContent, method.ast.endOffset) + ), + mypyStorage.buildRoot.configFile, + randomTypeFrequency = RANDOM_TYPE_FREQUENCY, + dMypyTimeout = configuration.timeoutForRun, + ) + + val fuzzerCancellation = { configuration.isCanceled() || timeoutLimitManager.isCancelled() } + runBlocking { + runFuzzing( + method, + algo, + fuzzerCancellation, + until + ).collect { + executionStorage.saveFuzzingExecution(it) + } + } + } + + private fun runFuzzing( + method: PythonMethod, + typeInferenceAlgorithm: BaselineAlgorithm, + isCancelled: () -> Boolean, + until: Long + ): Flow = flow { + ServerSocket(0).use { serverSocket -> + logger.debug { "Server port: ${serverSocket.localPort}" } + val manager = try { + PythonWorkerManager( + method, + serverSocket, + configuration, + until, + ) + } catch (_: TimeoutException) { + return@use + } + logger.debug { "Executor manager was created successfully" } + + val initialType = (typeInferenceAlgorithm.expandState() ?: method.methodType) as FunctionType + + val pmd = PythonMethodDescription( + method.name, + constants, + typeStorage, + Trie(PyInstruction::id), + Random(0), + TestGenerationLimitManager(ExecutionWithTimeoutMode, until, isRootManager = true), + initialType, + ) + + try { + val parameters = method.methodType.arguments + if (parameters.isEmpty()) { + val result = fuzzingResultHandler(pmd, emptyList(), parameters, manager) + result?.let { + emit(it.executionFeedback) + } + } else { + try { + PythonFuzzing(typeStorage, typeInferenceAlgorithm, isCancelled) { description, arguments -> + if (isCancelled()) { + logger.debug { "Fuzzing process was interrupted" } + manager.disconnect() + return@PythonFuzzing PythonFeedback(control = Control.STOP) + } + if (System.currentTimeMillis() >= until) { + logger.debug { "Fuzzing process was interrupted by timeout" } + manager.disconnect() + return@PythonFuzzing PythonFeedback(control = Control.STOP) + } + + if (arguments.any { PythonTree.containsFakeNode(it.tree) }) { + logger.debug { "FakeNode in Python model" } + description.limitManager.addFakeNodeExecutions() + emit(FakeNodeFeedback) + return@PythonFuzzing PythonFeedback(control = Control.CONTINUE) + } else { + description.limitManager.restartFakeNode() + } + + val pair = Pair(description, arguments.map { PythonTreeWrapper(it.tree) }) + val mem = cache.get(pair) + if (mem != null) { + logger.debug { "Repeat in fuzzing ${arguments.map { it.tree }}" } + description.limitManager.addSuccessExecution() + emit(CachedExecutionFeedback(mem.executionFeedback)) + return@PythonFuzzing mem.fuzzingPlatformFeedback.fromCache() + } + val result = fuzzingResultHandler(description, arguments, parameters, manager) + if (result == null) { // timeout + manager.disconnect() + return@PythonFuzzing PythonFeedback(control = Control.STOP) + } + + cache.add(pair, result) + emit(result.executionFeedback) + return@PythonFuzzing result.fuzzingPlatformFeedback + }.fuzz(pmd) + } catch (_: NoSeedValueException) { + logger.debug { "Cannot fuzz values for types: ${parameters.map { it.pythonTypeRepresentation() }}" } + } + } + } finally { + manager.shutdown() + } + } + }.flowOn(Dispatchers.IO) + + private fun handleTimeoutResult( + arguments: List, + coveredInstructions: List, + ): ExecutionFeedback { + val summary = arguments + .zip(method.arguments) + .mapNotNull { it.first.summary?.replace("%var%", it.second.name) } + + return ExecutionResultHandler.handleTimeoutResult( + method, + arguments.map { PythonTreeModel(it.tree) }, + coveredInstructions, + summary.map { DocRegularStmt(it) } + ) + } + + private fun handleSuccessResult( + arguments: List, + types: List, + evaluationResult: PythonEvaluationSuccess, + ): ExecutionFeedback { + val summary = arguments + .zip(method.arguments) + .mapNotNull { it.first.summary?.replace("%var%", it.second.name) } + + return ExecutionResultHandler.handleSuccessResult( + method, + configuration, + types, + evaluationResult, + summary.map { DocRegularStmt(it) }, + ) + } + + private fun fuzzingResultHandler( + description: PythonMethodDescription, + arguments: List, + parameters: List, + manager: PythonWorkerManager, + ): PythonExecutionResult? { + val additionalModules = parameters.flatMap { it.pythonModules() } + + val argumentValues = arguments.map { PythonTreeModel(it.tree, it.tree.type) } + val moduleToImport = configuration.testFileInformation.moduleName + val argumentModules = argumentValues + .flatMap { it.allContainingClassIds } + .map { it.moduleName } + .filterNot { it.startsWith(moduleToImport) } + val localAdditionalModules = (additionalModules + argumentModules + moduleToImport).toSet() + + val (thisObject, modelList) = if (method.hasThisArgument) + Pair(argumentValues[0], argumentValues.drop(1)) + else + Pair(null, argumentValues) + val functionArguments = FunctionArguments( + thisObject, + method.thisObjectName, + modelList, + method.argumentsNamesWithoutSelf + ) + try { + val coverageId = CoverageIdGenerator.createId() + return when (val evaluationResult = + manager.runWithCoverage(functionArguments, localAdditionalModules, coverageId)) { + is PythonEvaluationError -> { + val stackTraceMessage = evaluationResult.stackTrace.joinToString("\n") + val utError = UtError( + "Error evaluation: ${evaluationResult.status}, ${evaluationResult.message}\n${stackTraceMessage}", + Throwable(stackTraceMessage) + ) + description.limitManager.addInvalidExecution() + logger.warn(stackTraceMessage) + PythonExecutionResult(InvalidExecution(utError), PythonFeedback(control = Control.PASS)) + } + + is PythonEvaluationTimeout -> { + val coveredInstructions = + manager.coverageReceiver.coverageStorage.getOrDefault(coverageId, mutableListOf()) + val utTimeoutException = handleTimeoutResult(arguments, coveredInstructions) + val trieNode: Trie.Node = + if (coveredInstructions.isEmpty()) + Trie.emptyNode() + else + description.tracer.add(coveredInstructions) + description.limitManager.addInvalidExecution() + PythonExecutionResult( + utTimeoutException, + PythonFeedback(control = Control.PASS, result = trieNode, SuccessFeedback) + ) + } + + is PythonEvaluationSuccess -> { + val coveredInstructions = evaluationResult.coveredStatements + + val result = handleSuccessResult( + arguments, + parameters, + evaluationResult, + ) + val typeInferenceFeedback = if (result is ValidExecution) SuccessFeedback else InvalidTypeFeedback + when (result) { + is ValidExecution -> { + val trieNode: Trie.Node = description.tracer.add(coveredInstructions) + description.limitManager.addSuccessExecution() + PythonExecutionResult( + result, + PythonFeedback(Control.CONTINUE, trieNode, typeInferenceFeedback) + ) + } + is InvalidExecution -> { + description.limitManager.addInvalidExecution() + PythonExecutionResult(result, PythonFeedback(control = Control.CONTINUE, typeInferenceFeedback = typeInferenceFeedback)) + } + else -> { + description.limitManager.addInvalidExecution() + PythonExecutionResult(result, PythonFeedback(control = Control.PASS, typeInferenceFeedback = typeInferenceFeedback)) + } + } + } + } + } catch (_: TimeoutException) { + logger.debug { "Fuzzing process was interrupted by timeout" } + return null + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/engine/fuzzing/typeinference/FunctionAnnotationUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/engine/fuzzing/typeinference/FunctionAnnotationUtils.kt new file mode 100644 index 0000000000..68f1861db4 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/engine/fuzzing/typeinference/FunctionAnnotationUtils.kt @@ -0,0 +1,91 @@ +package org.utbot.python.engine.fuzzing.typeinference + +import org.utbot.python.PythonMethod +import org.utpython.types.PythonSubtypeChecker +import org.utpython.types.PythonTypeHintsStorage +import org.utpython.types.PythonTypeVarDescription +import org.utpython.types.general.DefaultSubstitutionProvider +import org.utpython.types.general.FunctionType +import org.utpython.types.general.TypeParameter +import org.utpython.types.general.UtType +import org.utpython.types.general.getBoundedParameters +import org.utpython.types.general.hasBoundedParameters +import org.utpython.types.getPythonAttributeByName +import org.utpython.types.pythonDescription +import org.utpython.types.pythonTypeName +import org.utbot.python.utils.PriorityCartesianProduct + +private const val MAX_SUBSTITUTIONS = 10 +private val BAD_TYPES = setOf( + "builtins.function", + "builtins.super", + "builtins.type", + "builtins.slice", + "builtins.range", + "builtins.memoryview", + "builtins.object", +) + +fun getCandidates(param: TypeParameter, typeStorage: PythonTypeHintsStorage): List { + val meta = param.pythonDescription() as PythonTypeVarDescription + return when (meta.parameterKind) { + PythonTypeVarDescription.ParameterKind.WithConcreteValues -> { + param.constraints.map { it.boundary } + } + + PythonTypeVarDescription.ParameterKind.WithUpperBound -> { + typeStorage.simpleTypes.filter { + if (it.hasBoundedParameters()) + return@filter false + val bound = param.constraints.first().boundary + PythonSubtypeChecker.checkIfRightIsSubtypeOfLeft(bound, it, typeStorage) + } + } + } +} + +fun generateTypesAfterSubstitution(type: UtType, typeStorage: PythonTypeHintsStorage): List { + val params = type.getBoundedParameters() + return PriorityCartesianProduct(params.map { getCandidates(it, typeStorage) }).getSequence() + .filter { types -> types.all { it.pythonTypeName() !in BAD_TYPES } } + .map { subst -> + DefaultSubstitutionProvider.substitute(type, (params zip subst).associate { it }) + }.take(MAX_SUBSTITUTIONS).toList() +} + +fun substituteTypeParameters( + method: PythonMethod, + typeStorage: PythonTypeHintsStorage, +): List { + val newClasses = method.containingPythonClass?.let { + generateTypesAfterSubstitution(it, typeStorage) + } ?: listOf(null) + return newClasses.flatMap { newClass -> + val funcType = newClass?.getPythonAttributeByName(typeStorage, method.name)?.type as? FunctionType + ?: method.methodType + val newFuncTypes = generateTypesAfterSubstitution(funcType, typeStorage) + newFuncTypes.map { newFuncType -> + method.makeCopyWithNewType(newFuncType as FunctionType) + } + }.take(MAX_SUBSTITUTIONS) +} + + +data class ModifiedAnnotation( + val method: PythonMethod, + val additionalVars: String +) + +fun createMethodAnnotationModifications( + method: PythonMethod, + typeStorage: PythonTypeHintsStorage, +): List { + return substituteTypeParameters(method, typeStorage).flatMap { newMethod -> + listOfNotNull( + newMethod.createShortForm(), + (newMethod to "") + ) + }.map { + ModifiedAnnotation(it.first, it.second) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/engine/utils/ModelsTransformation.kt b/utbot-python/src/main/kotlin/org/utbot/python/engine/utils/ModelsTransformation.kt new file mode 100644 index 0000000000..69df63af45 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/engine/utils/ModelsTransformation.kt @@ -0,0 +1,30 @@ +package org.utbot.python.engine.utils + +import org.utbot.framework.plugin.api.UtModel +import org.utbot.python.evaluation.serialization.MemoryDump +import org.utbot.python.evaluation.serialization.toPythonTree +import org.utbot.python.framework.api.python.PythonTreeModel + +fun transformModelList( + hasThisObject: Boolean, + state: MemoryDump, + modelListIds: List +): Pair> { + val (stateThisId, resultModelListIds) = + if (hasThisObject) { + Pair(modelListIds.first(), modelListIds.drop(1)) + } else { + Pair(null, modelListIds) + } + val stateThisObject = stateThisId?.let { + PythonTreeModel( + state.getById(it).toPythonTree(state) + ) + } + val modelList = resultModelListIds.map { + PythonTreeModel( + state.getById(it).toPythonTree(state) + ) + } + return Pair(stateThisObject, modelList) +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/CodeEvaluationApi.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/CodeEvaluationApi.kt new file mode 100644 index 0000000000..c8a638bbf1 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/CodeEvaluationApi.kt @@ -0,0 +1,56 @@ +package org.utbot.python.evaluation + +import org.utbot.python.FunctionArguments +import org.utbot.python.PythonMethod +import org.utbot.python.coverage.PyInstruction +import org.utbot.python.evaluation.serialization.MemoryDump + +interface PythonCodeExecutor { + val method: PythonMethod + val moduleToImport: String + val pythonPath: String + val syspathDirectories: Set + val executionTimeout: Long + + fun run( + fuzzedValues: FunctionArguments, + additionalModulesToImport: Set + ): PythonEvaluationResult + + fun runWithCoverage( + fuzzedValues: FunctionArguments, + additionalModulesToImport: Set, + coverageId: String, + ): PythonEvaluationResult + + fun runWithCoverage( + pickledArguments: String, + coverageId: String, + ): PythonEvaluationResult + + fun stop() +} + +sealed class PythonEvaluationResult + +data class PythonEvaluationError( + val status: Int, + val message: String, + val stackTrace: List +) : PythonEvaluationResult() + +data class PythonEvaluationTimeout( + val message: String = "Timeout" +) : PythonEvaluationResult() + +data class PythonEvaluationSuccess( + val isException: Boolean, + val coveredStatements: List, + val missedStatements: List, + val stateInit: MemoryDump, + val stateBefore: MemoryDump, + val stateAfter: MemoryDump, + val diffIds: List, + val modelListIds: List, + val resultId: String, +) : PythonEvaluationResult() \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/EvaluationCache.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/EvaluationCache.kt new file mode 100644 index 0000000000..8890757b06 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/EvaluationCache.kt @@ -0,0 +1,26 @@ +package org.utbot.python.evaluation + +import mu.KotlinLogging +import org.utbot.python.framework.api.python.PythonTreeWrapper +import org.utbot.python.fuzzing.PythonExecutionResult +import org.utbot.python.fuzzing.PythonMethodDescription + +private const val MAX_CACHE_SIZE = 200 + +class EvaluationCache { + private val cache = mutableMapOf>, PythonExecutionResult>() + + fun add(key: Pair>, result: PythonExecutionResult) { + cache[key] = result + if (cache.size > MAX_CACHE_SIZE) { + val elemToDelete = cache.keys.maxBy { (_, args) -> + args.fold(0) { acc, arg -> arg.commonDiversity(acc) } + } + cache.remove(elemToDelete) + } + } + + fun get(key: Pair>): PythonExecutionResult? { + return cache[key] + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt new file mode 100644 index 0000000000..52f5e81beb --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt @@ -0,0 +1,205 @@ +package org.utbot.python.evaluation + +import org.utbot.python.FunctionArguments +import org.utbot.python.PythonMethod +import org.utbot.python.coverage.CoverageIdGenerator +import org.utbot.python.coverage.toPyInstruction +import org.utbot.python.evaluation.serialization.ExecutionRequest +import org.utbot.python.evaluation.serialization.ExecutionRequestSerializer +import org.utbot.python.evaluation.serialization.ExecutionResultDeserializer +import org.utbot.python.evaluation.serialization.FailExecution +import org.utbot.python.evaluation.serialization.MemoryMode +import org.utbot.python.evaluation.serialization.PythonExecutionResult +import org.utbot.python.evaluation.serialization.SuccessExecution +import org.utbot.python.evaluation.serialization.serializeObjects +import org.utpython.types.PythonCallableTypeDescription +import org.utpython.types.pythonDescription +import org.utpython.types.pythonTypeName +import org.utpython.types.utils.isNamed +import java.net.SocketException + +class PythonCodeSocketExecutor( + override val method: PythonMethod, + override val moduleToImport: String, + override val pythonPath: String, + override val syspathDirectories: Set, + override val executionTimeout: Long, +) : PythonCodeExecutor { + private lateinit var pythonWorker: PythonWorker + + constructor( + method: PythonMethod, + moduleToImport: String, + pythonPath: String, + syspathDirectories: Set, + executionTimeout: Long, + pythonWorker: PythonWorker + ) : this( + method, + moduleToImport, + pythonPath, + syspathDirectories, + executionTimeout + ) { + this.pythonWorker = pythonWorker + } + + override fun run( + fuzzedValues: FunctionArguments, + additionalModulesToImport: Set + ): PythonEvaluationResult { + val coverageId = CoverageIdGenerator.createId() + return runWithCoverage(fuzzedValues, additionalModulesToImport, coverageId) + } + + override fun runWithCoverage( + fuzzedValues: FunctionArguments, + additionalModulesToImport: Set, + coverageId: String + ): PythonEvaluationResult { + val (arguments, memory) = serializeObjects(fuzzedValues.allArguments.map { it.tree }) + + val meta = method.methodType.pythonDescription() as PythonCallableTypeDescription + val argKinds = meta.argumentKinds + val namedArgs = meta.argumentNames + .filterIndexed { index, _ -> !isNamed(argKinds[index]) } + + val (positionalArguments, namedArguments) = arguments + .zip(meta.argumentNames) + .partition { (_, name) -> + namedArgs.contains(name) + } + + val containingClass = method.containingPythonClass + val functionTextName = + if (containingClass == null) + method.name + else { + val fullname = "${containingClass.pythonTypeName()}.${method.name}" + fullname.drop(moduleToImport.length).removePrefix(".") + } + + val request = ExecutionRequest( + functionTextName, + moduleToImport, + additionalModulesToImport.toList(), + syspathDirectories.toList(), + positionalArguments.map { it.first }, + namedArguments.associate { it.second!! to it.first }, // here can be only-kwargs arguments + memory, + MemoryMode.REDUCE, + method.moduleFilename, + coverageId, + ) + val message = ExecutionRequestSerializer.serializeRequest(request) ?: error("Cannot serialize request to python executor") + try { + pythonWorker.sendData(message) + } catch (_: SocketException) { + return parseExecutionResult(FailExecution("Send data error")) + } + + val (status, response) = UtExecutorThread.run(pythonWorker, executionTimeout) + return when (status) { + UtExecutorThread.Status.TIMEOUT -> { + + PythonEvaluationTimeout() + } + + UtExecutorThread.Status.OK -> { + val executionResult = response?.let { + ExecutionResultDeserializer.parseExecutionResult(it) + ?: error("Cannot parse execution result: $it") + } ?: FailExecution("Execution result error") + + parseExecutionResult(executionResult) + } + } + } + + override fun runWithCoverage(pickledArguments: String, coverageId: String): PythonEvaluationResult { + val containingClass = method.containingPythonClass + val functionTextName = + if (containingClass == null) + method.name + else { + val fullname = "${containingClass.pythonTypeName()}.${method.name}" + fullname.drop(moduleToImport.length).removePrefix(".") + } + + val request = ExecutionRequest( + functionTextName, + moduleToImport, + emptyList(), + syspathDirectories.toList(), + emptyList(), + emptyMap(), + pickledArguments, + MemoryMode.PICKLE, + method.moduleFilename, + coverageId, + ) + val message = ExecutionRequestSerializer.serializeRequest(request) ?: error("Cannot serialize request to python executor") + try { + pythonWorker.sendData(message) + } catch (_: SocketException) { + return parseExecutionResult(FailExecution("Send data error")) + } + + val (status, response) = UtExecutorThread.run(pythonWorker, executionTimeout) + + return when (status) { + UtExecutorThread.Status.TIMEOUT -> { + PythonEvaluationTimeout() + } + + UtExecutorThread.Status.OK -> { + val executionResult = response?.let { + ExecutionResultDeserializer.parseExecutionResult(it) + ?: error("Cannot parse execution result: $it") + } ?: FailExecution("Execution result error") + + parseExecutionResult(executionResult) + } + } + } + + private fun parseExecutionResult(executionResult: PythonExecutionResult): PythonEvaluationResult { + val parsingException = PythonEvaluationError( + -1, + "Incorrect format of output", + emptyList() + ) + return when (executionResult) { + is SuccessExecution -> { + val stateBefore = ExecutionResultDeserializer.parseMemoryDump(executionResult.stateBefore) ?: return parsingException + val stateAfter = ExecutionResultDeserializer.parseMemoryDump(executionResult.stateAfter) ?: return parsingException + val stateInit = ExecutionResultDeserializer.parseMemoryDump(executionResult.stateInit) ?: stateBefore + val diffIds = executionResult.diffIds.map {it.toLong()} + val statements = executionResult.statements.mapNotNull { it.toPyInstruction() } + val missedStatements = executionResult.missedStatements.mapNotNull { it.toPyInstruction() } + PythonEvaluationSuccess( + executionResult.isException, + statements, + missedStatements, + stateInit, + stateBefore, + stateAfter, + diffIds, + executionResult.argsIds + executionResult.kwargsIds.values, + executionResult.resultId, + ) + } + is FailExecution -> { + PythonEvaluationError( + -2, + "Fail Execution", + executionResult.exception.split(System.lineSeparator()), + ) + } + } + } + + override fun stop() { + pythonWorker.stopServer() + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCoverageReceiver.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCoverageReceiver.kt new file mode 100644 index 0000000000..e28cc2654b --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCoverageReceiver.kt @@ -0,0 +1,61 @@ +package org.utbot.python.evaluation + +import mu.KotlinLogging +import org.utbot.python.coverage.PyInstruction +import org.utbot.python.coverage.toPyInstruction +import java.io.IOException +import java.net.DatagramPacket +import java.net.DatagramSocket +import java.net.SocketException +import java.net.SocketTimeoutException +import kotlin.math.max + +class PythonCoverageReceiver( + val until: Long, +) : Thread() { + val coverageStorage = mutableMapOf>() + private val socket = DatagramSocket() + private val logger = KotlinLogging.logger {} + + init { + updateSoTimeout() + } + + private fun updateSoTimeout() { + socket.soTimeout = max((until - System.currentTimeMillis()).toInt(), 0) + } + + fun address(): Pair { + return "localhost" to socket.localPort.toString() + } + + fun kill() { + socket.close() + this.interrupt() + } + + override fun run() { + try { + while (true) { + updateSoTimeout() + val buf = ByteArray(256) + val request = DatagramPacket(buf, buf.size) + socket.receive(request) + val requestData = request.data.decodeToString().take(request.length).split(":", limit=2) + if (requestData.size == 2) { + val (id, line) = requestData + val instruction = line.toPyInstruction() + if (instruction != null) { + coverageStorage.getOrPut(id) { mutableListOf() }.add(instruction) + } + } + } + } catch (ex: SocketException) { + logger.debug { ex.message } + } catch (ex: IOException) { + logger.debug { "IO error: " + ex.message } + } catch (ex: SocketTimeoutException) { + logger.debug { "IO error: " + ex.message } + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorker.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorker.kt new file mode 100644 index 0000000000..a38d3f2acc --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorker.kt @@ -0,0 +1,52 @@ +package org.utbot.python.evaluation + +import java.io.BufferedReader +import java.io.BufferedWriter +import java.io.InputStreamReader +import java.io.OutputStreamWriter +import java.net.Socket + +class PythonWorker( + private val clientSocket: Socket +) { + private val outStream = BufferedWriter(OutputStreamWriter(clientSocket.getOutputStream(), "UTF8")) + private val inStream = BufferedReader(InputStreamReader(clientSocket.getInputStream(), "UTF8")) + + fun stop() { + outStream.write("STOP") + outStream.flush() + } + + fun sendData(msg: String) { + outStream.write("DATA") + + val size = msg.encodeToByteArray().size + outStream.write(size.toString().padStart(16)) + outStream.flush() + + outStream.write(msg) + outStream.flush() + } + + fun receiveMessage(): String? { + val lengthLine = inStream.readLine() ?: return null + val length = lengthLine.toInt() + val buffer = CharArray(length) + val bytesRead = inStream.read(buffer) + if (bytesRead < length) // TODO: maybe we should add more time for reading? + return null + return String(buffer) + } + + private fun stopConnection() { + inStream.close() + outStream.close() + clientSocket.close() + } + + fun stopServer() { + stop() + stopConnection() + } + +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt new file mode 100644 index 0000000000..17e292dcb1 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonWorkerManager.kt @@ -0,0 +1,200 @@ +package org.utbot.python.evaluation + +import mu.KotlinLogging +import org.apache.logging.log4j.LogManager +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.python.FunctionArguments +import org.utbot.python.PythonMethod +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.coverage.PythonCoverageMode +import org.utbot.python.utils.TemporaryFileManager +import org.utbot.python.utils.getResult +import org.utbot.python.utils.startProcess +import java.lang.Long.max +import java.net.ServerSocket +import java.net.Socket +import java.net.SocketException +import java.net.SocketTimeoutException +import kotlin.math.min + +private val logger = KotlinLogging.logger {} + +class PythonWorkerManager( + private val serverSocket: ServerSocket, + val pythonPath: String, + val until: Long, + private val coverageMeasureMode: PythonCoverageMode = PythonCoverageMode.Instructions, + private val sendCoverageContinuously: Boolean = true, + private val doNotGenerateStateAssertions: Boolean = false, + val pythonCodeExecutorConstructor: (PythonWorker) -> PythonCodeExecutor, +) { + constructor( + method: PythonMethod, + serverSocket: ServerSocket, + configuration: PythonTestGenerationConfig, + until: Long, + ) : this( + serverSocket, + configuration.pythonPath, + until, + configuration.coverageMeasureMode, + configuration.sendCoverageContinuously, + configuration.doNotGenerateStateAssertions, + { + PythonCodeSocketExecutor( + method, + configuration.testFileInformation.moduleName, + configuration.pythonPath, + configuration.sysPathDirectories, + min(configuration.timeoutForRun, until - System.currentTimeMillis()), + it, + ) + } + ) + + var timeout: Long = 0 + lateinit var process: Process + private lateinit var workerSocket: Socket + private lateinit var codeExecutor: PythonCodeExecutor + + val coverageReceiver = PythonCoverageReceiver(until) + + init { + coverageReceiver.start() + connect() + } + + private fun connect() { + val processStartTime = System.currentTimeMillis() + if (this::process.isInitialized && process.isAlive) { + process.destroy() + } + val logLevel = LogManager.getRootLogger().level.name() + if (serverSocket.isClosed) { + serverSocket.accept() + } + process = startProcess( + listOf( + pythonPath, + "-m", "utbot_executor", + "localhost", + serverSocket.localPort.toString(), + coverageReceiver.address().first, + coverageReceiver.address().second, + "--logfile", logfile.absolutePath, + "--loglevel", logLevel, // "DEBUG", "INFO", "WARNING", "ERROR" + "--coverage_type", coverageMeasureMode.toString(), // "lines", "instructions" + sendCoverageContinuously.toSendCoverageContinuouslyString(), // "--send_coverage", "--no-send_coverage" + doNotGenerateStateAssertions.toDoNotGenerateStateAssertionsString(), // "--generate_state_assertions", // "--no-generate_state_assertions" + ) + ) + timeout = max(until - processStartTime, 0) + if (this::workerSocket.isInitialized && !workerSocket.isClosed) { + workerSocket.close() + } + workerSocket = try { + serverSocket.soTimeout = timeout.toInt() + serverSocket.accept() + } catch (e: SocketTimeoutException) { + val result = getResult(process, max(until - processStartTime, 0)) + logger.debug { "utbot_executor exit value: ${result.exitValue}. stderr: ${result.stderr}, stdout: ${result.stdout}." } + process.destroy() + throw TimeoutException("Worker not connected") + } catch (e: SocketException) { + logger.debug { e.message } + throw SocketException("Worker not connected: $e") + } + logger.debug { "Worker connected successfully" } + +// workerSocket.soTimeout = timeoutForRun // TODO: maybe +eps for serialization/deserialization? + val pythonWorker = PythonWorker(workerSocket) + codeExecutor = pythonCodeExecutorConstructor(pythonWorker) + } + + fun disconnect() { + workerSocket.close() + process.destroyForcibly() + } + + private fun reconnect() { + disconnect() + connect() + } + + fun shutdown() { + disconnect() + coverageReceiver.kill() + } + + fun runWithCoverage( + fuzzedValues: FunctionArguments, + additionalModulesToImport: Set, + coverageId: String + ): PythonEvaluationResult { + val evaluationResult = try { + codeExecutor.runWithCoverage(fuzzedValues, additionalModulesToImport, coverageId) + } catch (_: SocketTimeoutException) { + logger.debug { "Socket timeout" } + reconnect() + PythonEvaluationTimeout() + } + if (evaluationResult is PythonEvaluationError || evaluationResult is PythonEvaluationTimeout) { + reconnect() + } + return evaluationResult + } + + fun runWithCoverage( + pickledArguments: String, + coverageId: String + ): PythonEvaluationResult { + val evaluationResult = try { + codeExecutor.runWithCoverage(pickledArguments, coverageId) + } catch (_: SocketTimeoutException) { + logger.debug { "Socket timeout" } + reconnect() + PythonEvaluationTimeout() + } + if (evaluationResult is PythonEvaluationError || evaluationResult is PythonEvaluationTimeout) { + reconnect() + } + return evaluationResult + } + + fun run( + fuzzedValues: FunctionArguments, + additionalModulesToImport: Set + ): PythonEvaluationResult { + val evaluationResult = try { + codeExecutor.run(fuzzedValues, additionalModulesToImport) + } catch (_: SocketTimeoutException) { + logger.debug { "Socket timeout" } + reconnect() + PythonEvaluationTimeout() + } + if (evaluationResult is PythonEvaluationError) { + reconnect() + } + return evaluationResult + } + + companion object { + val logfile = TemporaryFileManager.createTemporaryFile("", "utbot_executor.log", "log", true) + + fun Boolean.toSendCoverageContinuouslyString(): String { + return if (this) { + "--send_coverage" + } else { + "--no-send_coverage" + } + } + + fun Boolean.toDoNotGenerateStateAssertionsString(): String { + return if (this) { + "--no-generate_state_assertions" + } else { + "--generate_state_assertions" + } + } + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/UtExecutorThread.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/UtExecutorThread.kt new file mode 100644 index 0000000000..877f47cfed --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/UtExecutorThread.kt @@ -0,0 +1,42 @@ +package org.utbot.python.evaluation + +import java.net.SocketException +import java.util.concurrent.Callable +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException + +class UtExecutorThread { + enum class Status { + TIMEOUT, + OK, + } + + companion object { + fun run(worker: PythonWorker, executionTimeout: Long): Pair { + val executor = Executors.newSingleThreadExecutor() + val future = executor.submit(Task(worker)) + + val result = try { + Status.OK to future.get(executionTimeout, TimeUnit.MILLISECONDS) + } catch (ex: TimeoutException) { + future.cancel(true) + Status.TIMEOUT to null + } + executor.shutdown() + return result + } + } +} + +class Task( + private val worker: PythonWorker +) : Callable { + override fun call(): String? { + return try { + worker.receiveMessage() + } catch (ex: SocketException) { + null + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/ExecutionRequestSerializer.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/ExecutionRequestSerializer.kt new file mode 100644 index 0000000000..a8db047a6a --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/ExecutionRequestSerializer.kt @@ -0,0 +1,34 @@ +package org.utbot.python.evaluation.serialization + +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory + +object ExecutionRequestSerializer { + private val moshi = Moshi.Builder() + .addLast(KotlinJsonAdapterFactory()) + .build() + + private val jsonAdapter = moshi.adapter(ExecutionRequest::class.java) + + fun serializeRequest(request: ExecutionRequest): String? { + return jsonAdapter.toJson(request) + } +} + +enum class MemoryMode { + PICKLE, + REDUCE +} + +data class ExecutionRequest( + val functionName: String, + val functionModule: String, + val imports: List, + val syspaths: List, + val argumentsIds: List, + val kwargumentsIds: Map, + val serializedMemory: String, + val memoryMode: MemoryMode, + val filepath: String, + val coverageId: String, +) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/ExecutionResultDeserializer.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/ExecutionResultDeserializer.kt new file mode 100644 index 0000000000..0815248c2e --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/ExecutionResultDeserializer.kt @@ -0,0 +1,65 @@ +package org.utbot.python.evaluation.serialization + +import com.squareup.moshi.JsonEncodingException +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory + +object ExecutionResultDeserializer { + private val moshi = Moshi.Builder() + .add( + PolymorphicJsonAdapterFactory.of(PythonExecutionResult::class.java, "status") + .withSubtype(SuccessExecution::class.java, "success") + .withSubtype(FailExecution::class.java, "fail") + ) + .add( + PolymorphicJsonAdapterFactory.of(MemoryObject::class.java, "strategy") + .withSubtype(ReprMemoryObject::class.java, "repr") + .withSubtype(ListMemoryObject::class.java, "list") + .withSubtype(DictMemoryObject::class.java, "dict") + .withSubtype(ReduceMemoryObject::class.java, "reduce") + .withSubtype(IteratorMemoryObject::class.java, "iterator") + .withSubtype(NdarrayMemoryObject::class.java, "ndarray") + ) + .addLast(KotlinJsonAdapterFactory()) + .build() + + private val jsonAdapter = moshi.adapter(PythonExecutionResult::class.java) + private val jsonAdapterMemoryDump = moshi.adapter(MemoryDump::class.java) + + fun parseExecutionResult(content: String): PythonExecutionResult? { + try { + return jsonAdapter.fromJson(content) ?: error("Parsing error with: $content") + } catch (_: JsonEncodingException) { + println(content) + } + return null + } + + fun parseMemoryDump(content: String): MemoryDump? { + return if (content.isNotEmpty()) { + jsonAdapterMemoryDump.fromJson(content) + } else { + null + } + } +} + +sealed class PythonExecutionResult + +data class SuccessExecution( + val isException: Boolean, + val statements: List, + val missedStatements: List, + val stateInit: String, + val stateBefore: String, + val stateAfter: String, + val diffIds: List, + val argsIds: List, + val kwargsIds: Map, + val resultId: String, +): PythonExecutionResult() + +data class FailExecution( + val exception: String, +): PythonExecutionResult() diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/PythonObjectParser.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/PythonObjectParser.kt new file mode 100644 index 0000000000..98a5d3adbe --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/PythonObjectParser.kt @@ -0,0 +1,351 @@ +package org.utbot.python.evaluation.serialization + +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonTree + +object PythonObjectParser { + private val moshi = Moshi.Builder() + .add( + PolymorphicJsonAdapterFactory.of(MemoryObject::class.java, "strategy") + .withSubtype(ReprMemoryObject::class.java, "repr") + .withSubtype(ListMemoryObject::class.java, "list") + .withSubtype(DictMemoryObject::class.java, "dict") + .withSubtype(ReduceMemoryObject::class.java, "reduce") + .withSubtype(IteratorMemoryObject::class.java, "iterator") + .withSubtype(NdarrayMemoryObject::class.java, "ndarray") + ) + .addLast(KotlinJsonAdapterFactory()) + .build() + + private val jsonAdapter = moshi.adapter(MemoryDump::class.java) + + fun parseDumpedObjects(jsonWithDump: String): MemoryDump { + return jsonAdapter.fromJson(jsonWithDump) ?: error("Couldn't parse json dump") + } + + fun serializeMemory(memory: MemoryDump): String { + return jsonAdapter.toJson(memory) ?: error("Couldn't serialize dump to json") + } +} + +class MemoryDump( + private val objects: MutableMap +) { + fun contains(id: String): Boolean { + return objects.containsKey(id) + } + + fun getById(id: String): MemoryObject { + return objects[id]!! + } + + fun addObject(value: MemoryObject) { + objects[value.id] = value + } +} + +data class TypeInfo( + val module: String, + val kind: String, +) { + val qualname: String = if (module.isEmpty()) kind else "$module.$kind" +} + +sealed class MemoryObject( + val id: String, + val typeinfo: TypeInfo, + val comparable: Boolean, +) { + val qualname: String = typeinfo.qualname +} + +class ReprMemoryObject( + id: String, + typeinfo: TypeInfo, + comparable: Boolean, + val value: String, +) : MemoryObject(id, typeinfo, comparable) + +class ListMemoryObject( + id: String, + typeinfo: TypeInfo, + comparable: Boolean, + val items: List, +) : MemoryObject(id, typeinfo, comparable) + +class DictMemoryObject( + id: String, + typeinfo: TypeInfo, + comparable: Boolean, + val items: Map, +) : MemoryObject(id, typeinfo, comparable) + +class IteratorMemoryObject( + id: String, + typeinfo: TypeInfo, + comparable: Boolean, + val items: List, + val exception: TypeInfo, +) : MemoryObject(id, typeinfo, comparable) + +class ReduceMemoryObject( + id: String, + typeinfo: TypeInfo, + comparable: Boolean, + val constructor: TypeInfo, + val args: String, + val state: String, + val listitems: String, + val dictitems: String +) : MemoryObject(id, typeinfo, comparable) + +class NdarrayMemoryObject( + id: String, + typeinfo: TypeInfo, + comparable: Boolean, + val items: List, + val dimensions: List +) : MemoryObject(id, typeinfo, comparable) + +fun PythonTree.PythonTreeNode.toMemoryObject(memoryDump: MemoryDump, reload: Boolean = false): String { + val id = this.id.toString() + if (memoryDump.contains(id) && !reload) return id + + val typeinfo = TypeInfo(this.type.moduleName, this.type.typeName) + val obj = when (this) { + is PythonTree.PrimitiveNode -> { + ReprMemoryObject( + id, + typeinfo, + this.comparable, + this.repr.replace("\n", "\\n").replace("\r", "\\r") + ) + } + + is PythonTree.NDArrayNode -> { // TODO: Optimize for ndarray + val items = this.items.entries + .map { it.value.toMemoryObject(memoryDump) } + val dimensions = this.dimensions + NdarrayMemoryObject(id, typeinfo, this.comparable, items, dimensions) // TODO: Ndarray to memory object + } + + is PythonTree.ListNode -> { + val draft = ListMemoryObject(id, typeinfo, this.comparable, emptyList()) + memoryDump.addObject(draft) + + val items = this.items.entries + .map { it.value.toMemoryObject(memoryDump) } + ListMemoryObject(id, typeinfo, this.comparable, items) + } + + is PythonTree.TupleNode -> { + val items = this.items.entries + .map { it.value.toMemoryObject(memoryDump) } + ListMemoryObject(id, typeinfo, this.comparable, items) + } + + is PythonTree.SetNode -> { + val items = this.items.map { it.toMemoryObject(memoryDump) } + ListMemoryObject(id, typeinfo, this.comparable, items) + } + + is PythonTree.DictNode -> { + val draft = DictMemoryObject(id, typeinfo, this.comparable, emptyMap()) + memoryDump.addObject(draft) + + val items = this.items.entries + .associate { + it.key.toMemoryObject(memoryDump) to it.value.toMemoryObject(memoryDump) + } + DictMemoryObject(id, typeinfo, this.comparable, items) + } + + is PythonTree.IteratorNode -> { + val items = this.items.entries + .map { it.value.toMemoryObject(memoryDump) } + IteratorMemoryObject(id, typeinfo, this.comparable, items, TypeInfo(this.exception.moduleName, this.exception.typeName)) + } + + is PythonTree.ReduceNode -> { + val argsIds = PythonTree.ListNode(this.args.withIndex().associate { it.index to it.value }.toMutableMap()) + val draft = ReduceMemoryObject( + id, + typeinfo, + this.comparable, + TypeInfo( + this.constructor.moduleName, + this.constructor.typeName, + ), + argsIds.toMemoryObject(memoryDump), + "", + "", + "", + ) + memoryDump.addObject(draft) + + val stateObjId = if (this.customState) { + this.state["state"]!! + } else { + PythonTree.DictNode(this.state.entries.associate { + PythonTree.fromString(it.key) to it.value + }.toMutableMap()) + } + val listItemsIds = + PythonTree.ListNode(this.listitems.withIndex().associate { it.index to it.value }.toMutableMap()) + val dictItemsIds = PythonTree.DictNode(this.dictitems.toMutableMap()) + ReduceMemoryObject( + id, + typeinfo, + this.comparable, + TypeInfo( + this.constructor.moduleName, + this.constructor.typeName, + ), + argsIds.toMemoryObject(memoryDump), + stateObjId.toMemoryObject(memoryDump), + listItemsIds.toMemoryObject(memoryDump), + dictItemsIds.toMemoryObject(memoryDump), + ) + } + + else -> { + error("Invalid PythonTree.PythonTreeNode $this") + } + } + memoryDump.addObject(obj) + return obj.id +} + +fun MemoryObject.toPythonTree( + memoryDump: MemoryDump, + visited: MutableMap = mutableMapOf() +): PythonTree.PythonTreeNode { + val obj = visited.getOrPut(this.id) { + val id = this.id.toLong() + val obj = when (this) { + is ReprMemoryObject -> { + PythonTree.PrimitiveNode( + id, + PythonClassId(this.typeinfo.module, this.typeinfo.kind), + value + ) + } + + is DictMemoryObject -> { + val draft = PythonTree.DictNode( + id, + mutableMapOf() + ) + visited[this.id] = draft + items.entries.map { + draft.items[memoryDump.getById(it.key).toPythonTree(memoryDump, visited)] = + memoryDump.getById(it.value).toPythonTree(memoryDump, visited) + } + draft + } + + is ListMemoryObject -> { + val draft = when (this.qualname) { + "builtins.tuple" -> PythonTree.TupleNode(id, mutableMapOf()) + "builtins.set" -> PythonTree.SetNode(id, mutableSetOf()) + else -> PythonTree.ListNode(id, mutableMapOf()) + } + visited[this.id] = draft + + items.mapIndexed { index, valueId -> + val value = memoryDump.getById(valueId).toPythonTree(memoryDump, visited) + when (draft) { + is PythonTree.TupleNode -> draft.items[index] = value + is PythonTree.SetNode -> draft.items.add(value) + is PythonTree.ListNode -> draft.items[index] = value + else -> {} + } + } + draft + } + + is IteratorMemoryObject -> { + val draft = PythonTree.IteratorNode(id, mutableMapOf(), PythonClassId(exception.module, exception.kind)) + + visited[this.id] = draft + + items.mapIndexed { index, valueId -> + val value = memoryDump.getById(valueId).toPythonTree(memoryDump, visited) + draft.items[index] = value + } + draft + } + + is ReduceMemoryObject -> { + val stateObjsDraft = memoryDump.getById(state) + val customState = stateObjsDraft !is DictMemoryObject + + val argumentsDump = memoryDump.getById(args) + if (argumentsDump is ReprMemoryObject && argumentsDump.value == "None") { // This is global variable + return@getOrPut PythonTree.PrimitiveNode( + PythonClassId(this.constructor.module, this.constructor.kind), + this.constructor.qualname + ) + } + val arguments = argumentsDump as ListMemoryObject + val listitemsObjs = memoryDump.getById(listitems) as ListMemoryObject + val dictitemsObjs = memoryDump.getById(dictitems) as DictMemoryObject + val prevObj = PythonTree.ReduceNode( + id, + PythonClassId(this.typeinfo.module, this.typeinfo.kind), + PythonClassId(this.constructor.module, this.constructor.kind), + arguments.items.map { memoryDump.getById(it).toPythonTree(memoryDump, visited) }, + ) + prevObj.customState = customState + visited[this.id] = prevObj + + prevObj.state = if (prevObj.customState) { + val stateObjs = memoryDump.getById(state) + mutableMapOf("state" to stateObjs.toPythonTree(memoryDump, visited)) + } else { + val stateObjs = memoryDump.getById(state) as DictMemoryObject + stateObjs.items.entries.associate { + (memoryDump.getById(it.key).toPythonTree(memoryDump, visited) as PythonTree.PrimitiveNode) + .repr.drop(1).dropLast(1) to + memoryDump.getById(it.value).toPythonTree(memoryDump, visited) + }.toMutableMap() + } + prevObj.listitems = listitemsObjs.items.map { memoryDump.getById(it).toPythonTree(memoryDump, visited) } + prevObj.dictitems = dictitemsObjs.items.entries.associate { + memoryDump.getById(it.key).toPythonTree(memoryDump, visited) to + memoryDump.getById(it.value).toPythonTree(memoryDump, visited) + } + prevObj + } + + is NdarrayMemoryObject -> { + val draft = when (this.qualname) { + "numpy.ndarray" -> PythonTree.NDArrayNode(id, mutableMapOf(), this.dimensions) + else -> PythonTree.ListNode(id, mutableMapOf()) + } + visited[this.id] = draft + + items.mapIndexed { index, valueId -> + val value = memoryDump.getById(valueId).toPythonTree(memoryDump, visited) + when (draft) { + is PythonTree.NDArrayNode -> draft.items[index] = value + else -> {} + } + } + draft + } + } + obj.comparable = this.comparable + return obj + } + return obj +} + +fun serializeObjects(objs: List): Pair, String> { + val memoryDump = MemoryDump(emptyMap().toMutableMap()) + val ids = objs.map { it.toMemoryObject(memoryDump) } + return Pair(ids, PythonObjectParser.serializeMemory(memoryDump)) +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt new file mode 100644 index 0000000000..e2c6ca43ba --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonApi.kt @@ -0,0 +1,136 @@ +package org.utbot.python.framework.api.python + +import org.utbot.framework.plugin.api.* +import org.utbot.python.PythonArgument +import org.utbot.python.framework.api.python.util.moduleOfType + +/** + * PythonClassId represents Python type. + * NormalizedPythonAnnotation represents annotation after normalization. + * + * Example of PythonClassId, but not NormalizedPythonAnnotation: + * builtins.list (normalized annotation is typing.List[typing.Any]) + */ + +const val pythonBuiltinsModuleName = "builtins" + +class PythonClassId( + val moduleName: String, + val typeName: String, +) : ClassId("$moduleName.$typeName") { + constructor(fullName: String) : this( + moduleOfType(fullName) ?: pythonBuiltinsModuleName, + fullName.removePrefix(moduleOfType(fullName) ?: pythonBuiltinsModuleName).removePrefix(".") + ) + override fun toString(): String = canonicalName + val rootModuleName: String = moduleName.split(".").first() + override val simpleName: String = typeName + override val canonicalName = name + override val packageName = moduleName + val prettyName: String = if (rootModuleName == pythonBuiltinsModuleName) + name.split(".", limit=2).last() + else + name +} + +open class RawPythonAnnotation( + annotation: String +): ClassId(annotation) + +class NormalizedPythonAnnotation( + annotation: String +) : RawPythonAnnotation(annotation) + +class PythonMethodId( + override val classId: PythonClassId, // may be a fake class for top-level functions + override val name: String, + override val returnType: RawPythonAnnotation, + override val parameters: List, +) : MethodId(classId, name, returnType, parameters) { + val moduleName: String = classId.moduleName + val rootModuleName: String = this.toString().split(".")[0] + override fun toString(): String = if (moduleName.isNotEmpty()) "$moduleName.$name" else name +} + +sealed class PythonModel(classId: PythonClassId): UtModel(classId) { + open val allContainingClassIds: Set = setOf(classId) +} + +class PythonTreeModel( + val tree: PythonTree.PythonTreeNode, + classId: PythonClassId, +): PythonModel(classId) { + constructor(tree: PythonTree.PythonTreeNode) : this(tree, tree.type) + + override val allContainingClassIds: Set + get() { return findAllContainingClassIds(setOf(this.tree)) } + + private fun findAllContainingClassIds(visited: Set): Set { + val children = tree.children.map { PythonTreeModel(it, it.type) } + val newVisited = (visited + setOf(this.tree)).toMutableSet() + val childrenClassIds = children.filterNot { newVisited.contains(it.tree) }.flatMap { + newVisited.add(it.tree) + it.findAllContainingClassIds(newVisited) + } + return super.allContainingClassIds + childrenClassIds + } + + override fun equals(other: Any?): Boolean { + if (other !is PythonTreeModel) { + return false + } + return tree == other.tree + } + + override fun hashCode(): Int { + return tree.hashCode() + } +} + +class PythonUtExecution( + val stateInit: EnvironmentModels, + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + val diffIds: List, + result: UtExecutionResult, + val arguments: List, + coverage: Coverage? = null, + summary: List? = null, + testMethodName: String? = null, + displayName: String? = null, +) : UtExecution(stateBefore, stateAfter, result, coverage, summary, testMethodName, displayName) { + init { + stateInit.parameters.zip(stateBefore.parameters).map { (init, before) -> + if (init is PythonTreeModel && before is PythonTreeModel) { + init.tree.comparable = before.tree.comparable + } + } + val init = stateInit.thisInstance + val before = stateBefore.thisInstance + if (init is PythonTreeModel && before is PythonTreeModel) { + init.tree.comparable = before.tree.comparable + } + } + override fun copy( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage?, + summary: List?, + testMethodName: String?, + displayName: String? + ): UtExecution { + return PythonUtExecution( + stateInit = stateInit, + stateBefore = stateBefore, + stateAfter = stateAfter, + diffIds = diffIds, + result = result, + coverage = coverage, + summary = summary, + testMethodName = testMethodName, + displayName = displayName, + arguments = arguments + ) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonTree.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonTree.kt new file mode 100644 index 0000000000..20e378f07b --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/PythonTree.kt @@ -0,0 +1,443 @@ +package org.utbot.python.framework.api.python + +import org.utbot.python.framework.api.python.util.pythonBoolClassId +import org.utbot.python.framework.api.python.util.pythonDictClassId +import org.utbot.python.framework.api.python.util.pythonFloatClassId +import org.utbot.python.framework.api.python.util.pythonIntClassId +import org.utbot.python.framework.api.python.util.pythonIteratorClassId +import org.utbot.python.framework.api.python.util.pythonNdarrayClassId +import org.utbot.python.framework.api.python.util.pythonListClassId +import org.utbot.python.framework.api.python.util.pythonNoneClassId +import org.utbot.python.framework.api.python.util.pythonObjectClassId +import org.utbot.python.framework.api.python.util.pythonSetClassId +import org.utbot.python.framework.api.python.util.pythonStopIterationClassId +import org.utbot.python.framework.api.python.util.pythonStrClassId +import org.utbot.python.framework.api.python.util.pythonTupleClassId +import org.utbot.python.framework.api.python.util.toPythonRepr +import org.utpython.types.general.UtType +import org.utpython.types.pythonTypeName +import java.math.BigDecimal +import java.math.BigInteger +import java.util.* +import java.util.concurrent.atomic.AtomicLong + + +object PythonTree { + + const val MAX_ITERATOR_SIZE = 1000 + + fun isRecursiveObject(tree: PythonTreeNode): Boolean { + return isRecursiveObjectDFS(tree, mutableSetOf()) + } + + private fun isRecursiveObjectDFS(tree: PythonTreeNode, visited: Set): Boolean { + if (tree is PrimitiveNode) { + return false + } + if (visited.contains(tree)) + return true + return tree.children.any { isRecursiveObjectDFS(it, visited + tree) } + } + + fun containsFakeNode(tree: PythonTreeNode): Boolean { + return containsFakeNodeDFS(tree, mutableSetOf()) + } + + private fun containsFakeNodeDFS(tree: PythonTreeNode, visited: MutableSet): Boolean { + if (visited.contains(tree)) + return false + if (tree is FakeNode) + return true + visited.add(tree) + return tree.children.any { containsFakeNodeDFS(it, visited) } + } + + open class PythonTreeNode( + val id: Long, + val type: PythonClassId, + var comparable: Boolean = true, + ) { + constructor(type: PythonClassId, comparable: Boolean = true) : this(PythonIdGenerator.createId(), type, comparable) + + fun PythonTreeNode.wrap(): PythonTreeWrapper = PythonTreeWrapper(this) + + open val children: List = emptyList() + + fun isRecursive(): Boolean { + return isRecursiveObject(this) + } + + override fun toString(): String { + return type.name + children.toString() + } + + open fun typeEquals(other: Any?): Boolean { + return if (other is PythonTreeNode) + type == other.type && comparable && other.comparable + else + false + } + + override fun equals(other: Any?): Boolean { + if (other !is PythonTreeNode) { + return false + } + return this.wrap() == other.wrap() +// return this.id == other.id + } + + override fun hashCode(): Int { + return id.hashCode() + } + + open fun softEquals(other: PythonTreeNode): Boolean { // must be called only from PythonTreeWrapper! + return type == other.type && children == other.children + } + + open fun softHashCode(): Int { // must be called only from PythonTreeWrapper! + return type.hashCode() * 31 + children.hashCode() + } + + open fun diversity(): Int = // must be called only from PythonTreeWrapper! + 1 + children.fold(0) { acc, child -> acc + child.diversity() } + } + + object FakeNode: PythonTreeNode(0L, PythonClassId("")) + + class PrimitiveNode( + id: Long, + type: PythonClassId, + val repr: String, + ) : PythonTreeNode(id, type) { + constructor(type: PythonClassId, repr: String) : this(PythonIdGenerator.getOrCreateIdForValue(repr), type, repr) + + override fun toString(): String { + return repr + } + + override fun equals(other: Any?): Boolean { + if (other !is PrimitiveNode) + return false + return repr == other.repr + } + + override fun hashCode(): Int { + return repr.hashCode() + } + + override fun softEquals(other: PythonTreeNode): Boolean { + if (other !is PrimitiveNode) + return false + return repr == other.repr + } + + override fun softHashCode(): Int { + return repr.hashCode() + } + + override fun diversity(): Int = 2 + } + + class ListNode( + id: Long, + val items: MutableMap + ) : PythonTreeNode(id, pythonListClassId) { + constructor(items: MutableMap) : this(PythonIdGenerator.createId(), items) + + override val children: List + get() = items.values.toList() + + override fun typeEquals(other: Any?): Boolean { + return if (other is ListNode) + children.zip(other.children).all { + it.first.typeEquals(it.second) + } + else false + } + } + class NDArrayNode( + id: Long, + val items: MutableMap, + val dimensions: List + ) : PythonTreeNode(id, pythonNdarrayClassId) { + constructor(items: MutableMap, dimensions: List) : this(PythonIdGenerator.createId(), items, dimensions) + + override val children: List + get() = items.values.toList() + + override fun typeEquals(other: Any?): Boolean { + return if (other is NDArrayNode) + dimensions == other.dimensions && + children.zip(other.children).all { + it.first.typeEquals(it.second) + } + else false + } + + + override fun toString(): String { + return "ndarray${items.values}, shape: $dimensions" + } + } + + class DictNode( + id: Long, + val items: MutableMap + ) : PythonTreeNode(id, pythonDictClassId) { + constructor(items: MutableMap) : this(PythonIdGenerator.createId(), items) + + override val children: List + get() = items.values + items.keys + + override fun typeEquals(other: Any?): Boolean { + return if (other is DictNode) { + items.keys.size == other.items.keys.size && items.keys.all { + items[it]?.typeEquals(other.items[it]) ?: false + } + + } else false + } + + } + + class SetNode( + id: Long, + val items: MutableSet + ) : PythonTreeNode(id, pythonSetClassId) { + constructor(items: MutableSet) : this(PythonIdGenerator.createId(), items) + + override val children: List + get() = items.toList() + + override fun typeEquals(other: Any?): Boolean { + return if (other is SetNode) { + items.size == other.items.size && ( + items.isEmpty() || items.all { + items.first().typeEquals(it) + } && other.items.all { + items.first().typeEquals(it) + }) + } else { + false + } + } + } + + class TupleNode( + id: Long, + val items: MutableMap + ) : PythonTreeNode(id, pythonTupleClassId) { + constructor(items: MutableMap) : this(PythonIdGenerator.createId(), items) + + override val children: List + get() = items.values.toList() + + override fun typeEquals(other: Any?): Boolean { + return if (other is TupleNode) { + items.size == other.items.size && children.zip(other.children).all { + it.first.typeEquals(it.second) + } + } else { + false + } + } + } + + class IteratorNode( + id: Long, + val items: MutableMap, + val exception: PythonClassId = pythonStopIterationClassId, + ) : PythonTreeNode(id, pythonIteratorClassId) { + + constructor(items: MutableMap, stopException: PythonClassId = pythonStopIterationClassId) : this(PythonIdGenerator.createId(), items, stopException) + + override val children: List + get() = items.values.toList() + + override fun typeEquals(other: Any?): Boolean { + return if (other is ListNode) + children.zip(other.children).all { + it.first.typeEquals(it.second) + } + else false + } + } + + class ReduceNode( + id: Long, + type: PythonClassId, + val constructor: PythonClassId, + val args: List, + var state: MutableMap, + var listitems: List, + var dictitems: Map, + var customState: Boolean = false, // if this field is true, state must have structure {'state': } + ) : PythonTreeNode(id, type) { + constructor( + type: PythonClassId, + constructor: PythonClassId, + args: List, + ) : this(PythonIdGenerator.createId(), type, constructor, args, emptyMap().toMutableMap(), emptyList(), emptyMap()) + + constructor( + id: Long, + type: PythonClassId, + constructor: PythonClassId, + args: List, + ) : this(id, type, constructor, args, emptyMap().toMutableMap(), emptyList(), emptyMap()) + + override val children: List + get() = args + state.values + listitems + dictitems.values + dictitems.keys + PythonTreeNode(constructor) + + override fun typeEquals(other: Any?): Boolean { + return if (other is ReduceNode) { + type == other.type && state.all { (key, value) -> + other.state.containsKey(key) && value.typeEquals(other.state[key]) + } && listitems.withIndex().all { (index, item) -> + other.listitems.size > index && item.typeEquals(other.listitems[index]) + } && dictitems.all { (key, value) -> + other.dictitems.containsKey(key) && value.typeEquals(other.dictitems[key]) + } + } else false + } + + override fun softEquals(other: PythonTreeNode): Boolean { + if (other !is ReduceNode) + return false + return type == other.type && constructor == other.constructor && args == other.args && + state == other.state && listitems == other.listitems && dictitems == other.dictitems + } + + override fun softHashCode(): Int { + var result = constructor.hashCode() + result = 31 * result + args.hashCode() + result = 31 * result + state.hashCode() + result = 31 * result + listitems.hashCode() + result = 31 * result + dictitems.hashCode() + return result + } + } + + fun allElementsHaveSameStructure(elements: Collection): Boolean { + return if (elements.isEmpty()) { + true + } else { + val firstElement = elements.first() + elements.drop(1).all { + it.typeEquals(firstElement) + } + } + } + + fun fromObject(): PrimitiveNode { + return PrimitiveNode( + pythonObjectClassId, + "object()" + ) + } + + fun fromNone(): PrimitiveNode { + return PrimitiveNode( + pythonNoneClassId, + "None" + ) + } + + fun fromInt(value: BigInteger): PrimitiveNode { + return PrimitiveNode( + pythonIntClassId, + value.toString(10) + ) + } + + fun fromString(value: String): PrimitiveNode { + return PrimitiveNode( + pythonStrClassId, + value.toPythonRepr() + ) + } + + fun fromBool(value: Boolean): PrimitiveNode { + return PrimitiveNode( + pythonBoolClassId, + if (value) "True" else "False" + ) + } + + fun fromFloat(value: Double): PrimitiveNode { + val stringValue = + when (value) { + Double.POSITIVE_INFINITY -> "float('inf')" + Double.NEGATIVE_INFINITY -> "-float('inf')" + else -> value.toString() + } + return PrimitiveNode( + pythonFloatClassId, + stringValue + ) + } + + fun fromParsedConstant(value: Pair): PythonTreeNode? { + return when (value.first.pythonTypeName()) { + "builtins.int" -> fromInt(value.second as? BigInteger ?: return null) + "builtins.float" -> fromFloat((value.second as? BigDecimal)?.toDouble() ?: return null) + "typing.Tuple", "builtins.tuple" -> { + val elemsUntyped = (value.second as? List<*>) ?: return null + val elems = elemsUntyped.map { + val pair = it as? Pair<*,*> ?: return null + Pair(pair.first as? UtType ?: return null, pair.second ?: return null) + } + TupleNode( + elems.mapIndexed { index, pair -> + Pair(index, fromParsedConstant(pair) ?: return null) + }.associate { it }.toMutableMap() + ) + } + else -> null + } + } +} + +object PythonIdGenerator { + private const val lower_bound: Long = 1500_000_000 + + private val lastId: AtomicLong = AtomicLong(lower_bound) + private val cache: IdentityHashMap = IdentityHashMap() + + fun getOrCreateIdForValue(value: Any): Long { + return cache.getOrPut(value) { createId() } + } + + fun createId(): Long { + return lastId.incrementAndGet() + } + +} + +class PythonTreeWrapper(val tree: PythonTree.PythonTreeNode) { + override fun equals(other: Any?): Boolean { + if (other !is PythonTreeWrapper) + return false + if (PythonTree.isRecursiveObject(tree) || PythonTree.isRecursiveObject(other.tree)) + return tree.id == other.tree.id + return tree.softEquals(other.tree) + } + + override fun hashCode(): Int { + if (PythonTree.isRecursiveObject(tree)) + return tree.hashCode() + return tree.softHashCode() + } + + private val INF = 1000_000_000 + + private fun diversity(): Int { + if (PythonTree.isRecursiveObject(tree)) + return INF + return tree.diversity() + } + + fun commonDiversity(other: Int): Int { + return listOf(diversity() + other, INF).min() + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonIdUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonIdUtils.kt new file mode 100644 index 0000000000..3ca45d7a96 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonIdUtils.kt @@ -0,0 +1,26 @@ +package org.utbot.python.framework.api.python.util + +import org.utbot.python.framework.api.python.NormalizedPythonAnnotation +import org.utbot.python.framework.api.python.PythonClassId + +// none annotation can be used in code only since Python 3.10 +val pythonNoneClassId = PythonClassId("types.NoneType") +val pythonObjectClassId = PythonClassId("builtins.object") +val pythonAnyClassId = NormalizedPythonAnnotation("typing.Any") +val pythonIntClassId = PythonClassId("builtins.int") +val pythonFloatClassId = PythonClassId("builtins.float") +val pythonComplexClassId = PythonClassId("builtins.complex") +val pythonStrClassId = PythonClassId("builtins.str") +val pythonBoolClassId = PythonClassId("builtins.bool") +val pythonRangeClassId = PythonClassId("builtins.range") +val pythonListClassId = PythonClassId("builtins.list") +val pythonTupleClassId = PythonClassId("builtins.tuple") +val pythonDictClassId = PythonClassId("builtins.dict") +val pythonSetClassId = PythonClassId("builtins.set") +val pythonBytearrayClassId = PythonClassId("builtins.bytearray") +val pythonBytesClassId = PythonClassId("builtins.bytes") +val pythonExceptionClassId = PythonClassId("builtins.Exception") +val pythonIteratorClassId = PythonClassId("typing.Iterator") +val pythonRePatternClassId = PythonClassId("re.Pattern") +val pythonStopIterationClassId = PythonClassId("builtins.StopIteration") +val pythonNdarrayClassId = PythonClassId("numpy.ndarray") \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonTreeComparator.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonTreeComparator.kt new file mode 100644 index 0000000000..e6a03ee171 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonTreeComparator.kt @@ -0,0 +1,100 @@ +package org.utbot.python.framework.api.python.util + +import org.utbot.python.framework.api.python.PythonTree + +enum class VisitStatus { + OPENED, CLOSED +} + +/* + * Compare python tree by structure. Returns false if: + * - objects have different types + * - incomparable and have different ids + * - have the same type but structures aren't equal recursively + */ +fun comparePythonTree( + left: PythonTree.PythonTreeNode, + right: PythonTree.PythonTreeNode, + visitedLeft: MutableMap = emptyMap().toMutableMap(), + visitedRight: MutableMap = emptyMap().toMutableMap(), + equals: MutableMap, Boolean> = emptyMap, Boolean>().toMutableMap(), + ): Boolean { + if (visitedLeft[left.id] != visitedRight[right.id]) { + visitedLeft[left.id] = VisitStatus.CLOSED + visitedRight[right.id] = VisitStatus.CLOSED + equals[left.id to right.id] = false + return false + } + if (visitedLeft[left.id] == VisitStatus.CLOSED) { + return equals[left.id to right.id] ?: false + } + if (visitedLeft[left.id] == VisitStatus.OPENED) { + return true + } + + visitedLeft[left.id] = VisitStatus.OPENED + visitedRight[right.id] = VisitStatus.OPENED + + val areEquals = if (left.comparable && right.comparable && left.type == right.type) { + when (left) { + is PythonTree.PrimitiveNode -> { + left == right + } + + is PythonTree.ListNode -> { + if (right !is PythonTree.ListNode) false + else if (left.items.keys != right.items.keys) false + else left.items.keys.all { comparePythonTree(left.items[it]!!, right.items[it]!!, visitedLeft, visitedRight, equals) } + } + + is PythonTree.DictNode -> { + if (right !is PythonTree.DictNode) false + else if (left.items.keys != right.items.keys) false + else left.items.keys.all { comparePythonTree(left.items[it]!!, right.items[it]!!, visitedLeft, visitedRight, equals) } + } + + is PythonTree.TupleNode -> { + if (right !is PythonTree.TupleNode) false + else if (left.items.keys != right.items.keys) false + else left.items.keys.all { comparePythonTree(left.items[it]!!, right.items[it]!!, visitedLeft, visitedRight, equals) } + } + + is PythonTree.SetNode -> { + if (right !is PythonTree.SetNode) false + else if (left.items.size != right.items.size) false + else left.items.sortedBy { it.id } + .zip(right.items.sortedBy { it.id }) + .all { comparePythonTree(it.first, it.second, visitedLeft, visitedRight, equals) } + } + + is PythonTree.ReduceNode -> { + if (right !is PythonTree.ReduceNode) false + else { + val type = left.type == right.type + val state = left.state.size == right.state.size && left.state.keys.all { + comparePythonTree( + left.state[it]!!, + right.state[it]!!, + visitedLeft, + visitedRight, + equals + ) + } + val listitems = left.listitems.size == right.listitems.size && left.listitems.zip(right.listitems) + .all { comparePythonTree(it.first, it.second, visitedLeft, visitedRight, equals) } + val dictitems = left.dictitems.keys == right.dictitems.keys && left.dictitems.keys + .all { comparePythonTree(left.dictitems[it]!!, right.dictitems[it]!!, visitedLeft, visitedRight, equals) } + + type && state && listitems && dictitems + } + } + + else -> false + } + } else left.id == right.id + + visitedLeft[left.id] = VisitStatus.CLOSED + visitedRight[right.id] = VisitStatus.CLOSED + equals[left.id to right.id] = areEquals + return areEquals +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonUtils.kt new file mode 100644 index 0000000000..4f5247ffcf --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/api/python/util/PythonUtils.kt @@ -0,0 +1,27 @@ +package org.utbot.python.framework.api.python.util + + +fun moduleOfType(typeName: String): String? { + val lastIndex = typeName.lastIndexOf('.') + return if (lastIndex == -1) null else typeName.substring(0, lastIndex) +} + +fun String.toSnakeCase(): String { + val splitSymbols = "_" + return this.mapIndexed { index: Int, c: Char -> + if (c.isLowerCase() || c.isDigit() || splitSymbols.contains(c)) c + else if (c.isUpperCase()) { + (if (index > 0) "_" else "") + c.lowercase() + } else c + } + .joinToString("") + .replace(".", "_") +} + +fun String.toPythonRepr(): String { + val repr = this + .replace(Regex("((? = setOf( + "True", "False", "None", "and", "as", "assert", "async", "await", "break", "class", "continue", "def", "del", + "elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "is", "lambda", "nonlocal", + "not", "or", "pass", "raise", "return", "try", "while", "with", "yield", "list", "int", "str", "float", "bool", + "bytes", "frozenset", "dict", "set", "tuple", "abs", "aiter", "all", "any", "anext", "ascii", "bool", + "breakpoint", "bytearray", "callable", "chr", "classmethod", "compile", "complex", "delattr", "dir", "divmod", + "enumerate", "eval", "exec", "filter", "format", "getattr", "globals", "hasattr", "hash", "help", "hex", "id", + "input", "isinstance", "issubclass", "iter", "len", "list", "locals", "map", "max", "memoryview", "min", + "next", "object", "oct", "open", "ord", "pow", "print", "property", "range", "repr", "reversed", "round", + "set", "setattr", "slice", "sorted", "staticmethod", "sum", "super", "type", "vars", "zip", "self" + ) + + override fun testClassName( + testClassCustomName: String?, + testClassPackageName: String, + classUnderTest: ClassId + ): Pair { + val simpleName = testClassCustomName ?: "Test${classUnderTest.simpleName}" + return Pair(simpleName, simpleName) + } + + override fun getNameGeneratorBy(context: CgContext) = PythonCgNameGenerator(context) + override fun getCallableAccessManagerBy(context: CgContext) = PythonCgCallableAccessManagerImpl(context) + override fun getStatementConstructorBy(context: CgContext) = PythonCgStatementConstructorImpl(context) + override fun getVariableConstructorBy(context: CgContext) = PythonCgVariableConstructor(context) + override fun getMethodConstructorBy(context: CgContext) = PythonCgMethodConstructor(context) + override fun getLanguageTestFrameworkManager() = PythonTestFrameworkManager() + override fun cgRenderer(context: CgRendererContext, printer: CgPrinter): CgAbstractRenderer = + CgPythonRenderer(context, printer) + + var memoryObjects: MutableMap = emptyMap().toMutableMap() + var memoryObjectsModels: MutableMap = emptyMap().toMutableMap() + + fun clear() { + memoryObjects.clear() + memoryObjectsModels.clear() + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/PythonTestFrameworkManager.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/PythonTestFrameworkManager.kt new file mode 100644 index 0000000000..312b94cc6a --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/PythonTestFrameworkManager.kt @@ -0,0 +1,22 @@ +package org.utbot.python.framework.codegen + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.services.language.LanguageTestFrameworkManager +import org.utbot.python.framework.codegen.model.Pytest +import org.utbot.python.framework.codegen.model.Unittest +import org.utbot.python.framework.codegen.model.constructor.tree.PytestManager +import org.utbot.python.framework.codegen.model.constructor.tree.UnittestManager + +class PythonTestFrameworkManager : LanguageTestFrameworkManager() { + + override fun managerByFramework(context: CgContext) = when (context.testFramework) { + is Unittest -> UnittestManager(context) + is Pytest -> PytestManager(context) + else -> throw UnsupportedOperationException("Incorrect TestFramework ${context.testFramework}") + } + + override val defaultTestFramework = Unittest + + override val testFrameworks = listOf(Unittest, Pytest) + +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonCodeGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonCodeGenerator.kt new file mode 100644 index 0000000000..799fd2852d --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonCodeGenerator.kt @@ -0,0 +1,137 @@ +package org.utbot.python.framework.codegen.model + +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.HangingTestsTimeout +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.SimpleTestClassModel +import org.utbot.framework.codegen.generator.CodeGenerator +import org.utbot.framework.codegen.generator.CodeGeneratorParams +import org.utbot.framework.codegen.generator.CodeGeneratorResult +import org.utbot.framework.codegen.renderer.CgAbstractRenderer +import org.utbot.framework.codegen.renderer.CgPrinterImpl +import org.utbot.framework.codegen.renderer.CgRendererContext +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MockFramework +import org.utbot.python.PythonMethod +import org.utbot.python.framework.codegen.PythonCgLanguageAssistant +import org.utbot.python.framework.codegen.model.constructor.tree.PythonCgTestClassConstructor +import org.utbot.python.framework.codegen.model.constructor.visitor.CgPythonRenderer +import org.utpython.types.general.UtType +import org.utpython.types.pythonAnyType +import org.utpython.types.pythonModules +import org.utpython.types.pythonTypeRepresentation + +class PythonCodeGenerator( + classUnderTest: ClassId, + paramNames: MutableMap> = mutableMapOf(), + testFramework: TestFramework = TestFramework.defaultItem, + mockFramework: MockFramework = MockFramework.defaultItem, + staticsMocking: StaticsMocking = StaticsMocking.defaultItem, + forceStaticMocking: ForceStaticMocking = ForceStaticMocking.defaultItem, + generateWarningsForStaticMocking: Boolean = true, + parameterizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem, + runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.defaultItem, + hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(), + enableTestsTimeout: Boolean = true, + testClassPackageName: String = classUnderTest.packageName +) : CodeGenerator( + CodeGeneratorParams( + classUnderTest = classUnderTest, + projectType = ProjectType.Python, + paramNames = paramNames, + generateUtilClassFile = true, + testFramework = testFramework, + mockFramework = mockFramework, + staticsMocking = staticsMocking, + forceStaticMocking = forceStaticMocking, + generateWarningsForStaticMocking = generateWarningsForStaticMocking, + parameterizedTestSource = parameterizedTestSource, + runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour, + hangingTestsTimeout = hangingTestsTimeout, + enableTestsTimeout = enableTestsTimeout, + testClassPackageName = testClassPackageName, + cgLanguageAssistant = PythonCgLanguageAssistant, + ) +) { + fun pythonGenerateAsStringWithTestReport( + cgTestSets: List, + importModules: Set, + testClassCustomName: String? = null, + ): CodeGeneratorResult = withCustomContext(testClassCustomName) { + context.withTestClassFileScope { + (context.cgLanguageAssistant as PythonCgLanguageAssistant).clear() + val testClassModel = SimpleTestClassModel(classUnderTest, cgTestSets) + context.collectedImports.addAll(importModules) + + val astConstructor = PythonCgTestClassConstructor(context) + val renderer = CgAbstractRenderer.makeRenderer(context) + + val testClassFile = astConstructor.construct(testClassModel) + testClassFile.accept(renderer) + + CodeGeneratorResult(renderer.toString(), astConstructor.testsGenerationReport) + } + } + + fun generateMypyCheckCode( + method: PythonMethod, + methodAnnotations: Map, + directoriesForSysPath: Set, + moduleToImport: String, + namesInModule: Collection, + additionalVars: String + ): String { + val cgRendererContext = CgRendererContext.fromCgContext(context) + val printer = CgPrinterImpl() + val renderer = CgPythonRenderer(cgRendererContext, printer) + + val importOs = PythonSystemImport("os") + val importSys = PythonSystemImport("sys") + val importTyping = PythonSystemImport("typing") + val importSysPaths = directoriesForSysPath.map { PythonSysPathImport(it) } + val importsFromModule = namesInModule.map { name -> + PythonUserImport(name, moduleToImport) + } + + val additionalModules = methodAnnotations.values.flatMap { it.pythonModules() }.map { PythonUserImport(it) } + val imports = + (listOf(importOs, importSys, importTyping) + importSysPaths + (importsFromModule + additionalModules)).toSet() + .toList() + + imports.forEach { renderer.renderPythonImport(it) } + + val paramNames = method.argumentsNames + val parameters: List = paramNames.map { argument -> + if (methodAnnotations[argument]?.meta.toString() == "numpy.ndarray") { + val re = """, .*[^]]""".toRegex() + val type = re.find(methodAnnotations[argument]?.pythonTypeRepresentation().toString())?.value + val newAnnotation: String = + type?.let { + methodAnnotations[argument]?.pythonTypeRepresentation().toString() + .replace(it, ", ${pythonAnyType.pythonTypeRepresentation()}") + } ?: pythonAnyType.pythonTypeRepresentation() + "${argument}: $newAnnotation" + } else + "${argument}: ${methodAnnotations[argument]?.pythonTypeRepresentation() ?: pythonAnyType.pythonTypeRepresentation()}" + } + + val functionPrefix = "__mypy_check" + val functionName = + "def ${functionPrefix}_${method.name}(${parameters.joinToString(", ")}):" // TODO: in future can be "async def" + + val mypyCheckCode = listOf( + renderer.toString(), + "", + additionalVars, + "", + functionName, + ) + method.codeAsString.split("\n") + return mypyCheckCode.joinToString("\n") + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonDomain.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonDomain.kt new file mode 100644 index 0000000000..a9e7dd8f10 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonDomain.kt @@ -0,0 +1,91 @@ +package org.utbot.python.framework.codegen.model + +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.plugin.api.BuiltinClassId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.methodId +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.util.pythonAnyClassId +import org.utbot.python.framework.api.python.util.pythonNoneClassId + +object Pytest : TestFramework(displayName = "pytest", id = "pytest") { + override val mainPackage: String = "pytest" + override val assertionsClass: ClassId = pythonNoneClassId + override val arraysAssertionsClass: ClassId = assertionsClass + override val kotlinFailureAssertionsClass: ClassId = assertionsClass + + override val testAnnotationId: ClassId = BuiltinClassId( + canonicalName = "pytest", + simpleName = "Tests" + ) + + override val beforeMethodId: ClassId = pythonAnyClassId + + override val afterMethodId: ClassId = pythonAnyClassId + + override val parameterizedTestAnnotationId: ClassId = pythonAnyClassId + override val methodSourceAnnotationId: ClassId = pythonAnyClassId + override val nestedClassesShouldBeStatic: Boolean = false + override val argListClassId: ClassId = pythonAnyClassId + + val skipDecoratorClassId = PythonClassId("pytest", "mark.skip") + + @OptIn(ExperimentalStdlibApi::class) + override fun getRunTestsCommand( + executionInvoke: String, + classPath: String, + classesNames: List, + buildDirectory: String, + additionalArguments: List + ): List = buildList { + add(executionInvoke) + addAll(additionalArguments) + add(mainPackage) + } +} + +object Unittest : TestFramework(displayName = "Unittest", id = "Unittest") { + init { + isInstalled = true + } + + override val testSuperClass: ClassId = PythonClassId("unittest.TestCase") + override val mainPackage: String = "unittest" + override val assertionsClass: ClassId = PythonClassId("self") + override val arraysAssertionsClass: ClassId = assertionsClass + override val kotlinFailureAssertionsClass: ClassId = assertionsClass + override val testAnnotationId: ClassId = BuiltinClassId( + canonicalName = "Unittest", + simpleName = "Tests" + ) + + override val beforeMethodId: ClassId = pythonAnyClassId + + override val afterMethodId: ClassId = pythonAnyClassId + + override val parameterizedTestAnnotationId: ClassId = pythonAnyClassId + override val methodSourceAnnotationId: ClassId = pythonAnyClassId + override val nestedClassesShouldBeStatic: Boolean = false + override val argListClassId: ClassId = pythonAnyClassId + + val skipDecoratorClassId = PythonClassId("unittest.skip") + + override fun getRunTestsCommand( + executionInvoke: String, + classPath: String, + classesNames: List, + buildDirectory: String, + additionalArguments: List + ): List { + throw UnsupportedOperationException() + } + + override val assertEquals by lazy { assertionId("assertEqual", objectClassId, objectClassId) } + + override fun assertionId(name: String, vararg params: ClassId): MethodId = + methodId(assertionsClass, name, voidClassId, *params) +} + diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonImports.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonImports.kt new file mode 100644 index 0000000000..c3dfabb64e --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/PythonImports.kt @@ -0,0 +1,92 @@ +package org.utbot.python.framework.codegen.model + +import org.utbot.framework.codegen.domain.Import + +sealed class PythonImport(order: Int) : Import(order) { + var importName: String = "" + var moduleName: String? = null + var alias: String? = null + + constructor(order: Int, importName: String, moduleName: String? = null, alias: String? = null) : this(order) { + this.importName = importName + this.moduleName = moduleName + this.alias = alias + } + + override val qualifiedName: String + get() = if (moduleName != null) "${moduleName}.${importName}" else importName + + val rootModuleName: String + get() = qualifiedName.split(".")[0] + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as PythonImport + return qualifiedName == other.qualifiedName + } + + override fun hashCode(): Int { + var result = importName.hashCode() + result = 31 * result + (moduleName?.hashCode() ?: 0) + return result + } +} + +data class PythonSysPathImport(val sysPath: String) : PythonImport(2) { + override val qualifiedName: String + get() = sysPath + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as PythonSysPathImport + return qualifiedName == other.qualifiedName + } + + override fun hashCode(): Int { + return sysPath.hashCode() + } +} + +data class PythonUserImport(val importName_: String, val moduleName_: String? = null, val alias_: String? = null) : + PythonImport(3, importName_, moduleName_, alias_) { + override val qualifiedName: String + get() = if (moduleName != null) "${moduleName}.${importName}" else importName + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as PythonUserImport + return qualifiedName == other.qualifiedName + } + + override fun hashCode(): Int { + var result = importName.hashCode() + result = 31 * result + (moduleName?.hashCode() ?: 0) + return result + } +} + +data class PythonSystemImport(val importName_: String, val moduleName_: String? = null, val alias_: String? = null) : + PythonImport(1, importName_, moduleName_, alias_) { + override val qualifiedName: String + get() = if (moduleName != null) "${moduleName}.${importName}" else importName + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as PythonSystemImport + return qualifiedName == other.qualifiedName + } + + override fun hashCode(): Int { + var result = importName.hashCode() + result = 31 * result + (moduleName?.hashCode() ?: 0) + return result + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/name/PythonCgNameGenerator.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/name/PythonCgNameGenerator.kt new file mode 100644 index 0000000000..ca8987d3d4 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/name/PythonCgNameGenerator.kt @@ -0,0 +1,107 @@ +package org.utbot.python.framework.codegen.model.constructor.name + +import org.utbot.python.framework.codegen.model.PythonImport +import org.utbot.framework.codegen.services.language.isLanguageKeyword +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.services.CgNameGenerator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.python.framework.api.python.NormalizedPythonAnnotation +import org.utbot.python.framework.api.python.util.toSnakeCase + +internal fun infiniteInts(): Sequence = + generateSequence(1) { it + 1 } + +class PythonCgNameGenerator(val context: CgContext) + : CgNameGenerator, CgContextOwner by context { + + private fun nextIndexedVarName(base: String): String = + infiniteInts() + .map { "$base$it" } + .first { it !in existingVariableNames } + + private fun nextIndexedMethodName(base: String, skipOne: Boolean = false): String = + infiniteInts() + .map { if (skipOne && it == 1) base else "$base$it" } + .first { it !in existingMethodNames } + + private fun createNameFromKeyword(baseName: String): String = + nextIndexedVarName(baseName) + + private fun createExecutableName(executableId: ExecutableId): String { + return when (executableId) { + is ConstructorId -> executableId.classId.prettifiedName + is MethodId -> executableId.name + } + } + + override fun nameFrom(id: ClassId): String = + when (id) { + is NormalizedPythonAnnotation -> "var" + else -> id.simpleName.toSnakeCase() + } + + override fun variableName(base: String, isMock: Boolean, isStatic: Boolean): String { + val baseName = when { + isMock -> base + "_mock" + isStatic -> base + "_static" + else -> base + } + return when { + baseName in existingVariableNames -> nextIndexedVarName(baseName) + isLanguageKeyword(baseName, context.cgLanguageAssistant) -> createNameFromKeyword(baseName) + else -> baseName + }.also { + existingVariableNames = existingVariableNames.add(it) + } + } + + override fun variableName(type: ClassId, base: String?, isMock: Boolean): String { + val baseName = base?.toSnakeCase() ?: nameFrom(type) + val importedModuleNames = collectedImports.mapNotNull { + if (it is PythonImport) it.rootModuleName else null + } + return when { + baseName in existingVariableNames -> nextIndexedVarName(baseName) + baseName in importedModuleNames -> nextIndexedVarName(baseName) + isLanguageKeyword(baseName, context.cgLanguageAssistant) -> createNameFromKeyword(baseName) + else -> baseName + }.also { + existingVariableNames = existingVariableNames.add(it) + } + } + + override fun testMethodNameFor(executableId: ExecutableId, customName: String?): String { + val executableName = createExecutableName(executableId) + + val name = if (customName != null && customName !in existingMethodNames) { + customName + } else { + val base = customName ?: "test_${executableName.toSnakeCase()}" + nextIndexedMethodName(base) + } + existingMethodNames += name + return name + } + + override fun parameterizedTestMethodName(dataProviderMethodName: String): String { + TODO("Not yet implemented") + } + + override fun dataProviderMethodNameFor(executableId: ExecutableId): String { + TODO("Not yet implemented") + } + + override fun errorMethodNameFor(executableId: ExecutableId): String { + val executableName = createExecutableName(executableId) + val newName = when (val base = "test_${executableName.toSnakeCase()}_errors") { + !in existingMethodNames -> base + else -> nextIndexedMethodName(base) + } + existingMethodNames += newName + return newName + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgCallableAccessManager.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgCallableAccessManager.kt new file mode 100644 index 0000000000..7830ad22a8 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgCallableAccessManager.kt @@ -0,0 +1,77 @@ +package org.utbot.python.framework.codegen.model.constructor.tree + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgConstructorCall +import org.utbot.framework.codegen.domain.models.CgExecutableCall +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgFieldAccess +import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgStaticFieldAccess +import org.utbot.framework.codegen.domain.models.CgThisInstance +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.services.access.CgIncompleteMethodCall +import org.utbot.framework.codegen.tree.importIfNeeded +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.exceptions +import org.utbot.python.framework.api.python.PythonMethodId +import org.utbot.python.framework.api.python.util.pythonAnyClassId +import org.utbot.python.framework.codegen.model.constructor.util.importIfNeeded +import org.utbot.python.framework.codegen.model.tree.CgPythonTree + +class PythonCgCallableAccessManagerImpl(val context: CgContext) : CgCallableAccessManager, + CgContextOwner by context { + + override fun CgExpression?.get(methodId: MethodId): CgIncompleteMethodCall = + CgIncompleteMethodCall(methodId, this) + + override fun ClassId.get(staticMethodId: MethodId): CgIncompleteMethodCall = + CgIncompleteMethodCall(staticMethodId, CgThisInstance(pythonAnyClassId)) + + override fun CgExpression.get(fieldId: FieldId): CgExpression { + return CgFieldAccess(this, fieldId) + } + + override fun ClassId.get(fieldId: FieldId): CgStaticFieldAccess { + TODO("Not yet implemented") + } + + override fun ConstructorId.invoke(vararg args: Any?): CgExecutableCall { + val resolvedArgs = args.resolve() + val constructorCall = CgConstructorCall(this, resolvedArgs) + newConstructorCall(this) + return constructorCall + } + + override fun CgIncompleteMethodCall.invoke(vararg args: Any?): CgMethodCall { + val resolvedArgs = emptyList().toMutableList() + args.forEach { arg -> + // if arg is named argument we must use this name + if (arg is CgPythonTree) { + resolvedArgs.add(arg.value) + } else { + resolvedArgs.add(arg as CgExpression) + } + } + val methodCall = CgMethodCall(caller, method, resolvedArgs) + if (method is PythonMethodId) + newMethodCall(method) + return methodCall + } + + private fun newMethodCall(methodId: MethodId) { + importIfNeeded(methodId as PythonMethodId) + } + + private fun newConstructorCall(constructorId: ConstructorId) { + importIfNeeded(constructorId.classId) + for (exception in constructorId.exceptions) { + addExceptionIfNeeded(exception) + } + } + +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt new file mode 100644 index 0000000000..24938294e0 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt @@ -0,0 +1,517 @@ +package org.utbot.python.framework.codegen.model.constructor.tree + +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgDocumentationComment +import org.utbot.framework.codegen.domain.models.CgFieldAccess +import org.utbot.framework.codegen.domain.models.CgGetLength +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.CgMultilineComment +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration +import org.utbot.framework.codegen.domain.models.CgReferenceExpression +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgTestMethodType +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.domain.models.convertDocToCg +import org.utbot.framework.codegen.tree.CgMethodConstructor +import org.utbot.framework.codegen.tree.buildTestMethod +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.InstrumentedProcessDeathException +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtExecutionFailure +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtTimeoutException +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonMethodId +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.PythonTreeModel +import org.utbot.python.framework.api.python.PythonUtExecution +import org.utbot.python.framework.api.python.util.pythonExceptionClassId +import org.utbot.python.framework.api.python.util.pythonIntClassId +import org.utbot.python.framework.api.python.util.pythonNoneClassId +import org.utbot.python.framework.codegen.PythonCgLanguageAssistant +import org.utbot.python.framework.codegen.model.constructor.util.importIfNeeded +import org.utbot.python.framework.codegen.model.tree.CgPythonFunctionCall +import org.utbot.python.framework.codegen.model.tree.CgPythonIndex +import org.utbot.python.framework.codegen.model.tree.CgPythonNamedArgument +import org.utbot.python.framework.codegen.model.tree.CgPythonRange +import org.utbot.python.framework.codegen.model.tree.CgPythonRepr +import org.utbot.python.framework.codegen.model.tree.CgPythonTree +import org.utbot.python.framework.codegen.model.tree.CgPythonZip + +class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(context) { + private val maxDepth: Int = 5 + + private fun CgVariable.deepcopy(): CgVariable { + val classId = PythonClassId("copy.deepcopy") + importIfNeeded(classId) + return newVar(this.type) { CgPythonFunctionCall(classId, "copy.deepcopy", listOf(this)) } + } + + override fun assertEquality(expected: CgValue, actual: CgVariable) { + pythonDeepEquals(expected, actual) + } + + override fun createTestMethod(testSet: CgMethodTestSet, execution: UtExecution): CgTestMethod = + withTestMethodScope(execution) { + val constructorState = (execution as PythonUtExecution).stateInit + val diffIds = execution.diffIds + (context.cgLanguageAssistant as PythonCgLanguageAssistant).clear() + val testMethodName = nameGenerator.testMethodNameFor(testSet.executableUnderTest, execution.testMethodName) + if (execution.testMethodName == null) { + execution.testMethodName = testMethodName + } + // TODO: remove this line when SAT-1273 is completed + execution.displayName = execution.displayName?.let { "${testSet.executableUnderTest.name}: $it" } + pythonTestMethod(testMethodName, execution.displayName) { + val statics = currentExecution!!.stateBefore.statics + rememberInitialStaticFields(statics) + + // TODO: move such methods to another class and leave only 2 public methods: remember initial and final states + val mainBody = { + substituteStaticFields(statics) + setupInstrumentation() + // build this instance + thisInstance = constructorState.thisInstance?.let { + variableConstructor.getOrCreateVariable(it) + } + + val beforeThisInstance = execution.stateBefore.thisInstance + val afterThisInstance = execution.stateAfter.thisInstance + val assertThisObject = emptyList>().toMutableList() + if (beforeThisInstance is PythonTreeModel && afterThisInstance is PythonTreeModel) { + if (diffIds.contains(afterThisInstance.tree.id)) { + thisInstance = thisInstance?.let { + val newValue = + if (it is CgPythonTree) { + if (it.value is CgVariable) { + it.value + } else { + newVar(it.type) {it.value} + } + } else { + newVar(it.type) {it} + } + assertThisObject.add(Pair(newValue, afterThisInstance)) + newValue + } + } + } + if (thisInstance is CgPythonTree) { + context.currentBlock.addAll((thisInstance as CgPythonTree).arguments) + } + + // build arguments + val stateAssertions = emptyMap>().toMutableMap() + for ((index, param) in constructorState.parameters.withIndex()) { + val name = execution.arguments[index].name + var argument = variableConstructor.getOrCreateVariable(param, name) + + val beforeValue = execution.stateBefore.parameters[index] + val afterValue = execution.stateAfter.parameters[index] + if (afterValue is PythonTreeModel && beforeValue is PythonTreeModel) { + if (diffIds.contains(afterValue.tree.id)) { + if (argument !is CgVariable) { + argument = newVar(argument.type, name) {argument} + } + stateAssertions[index] = Pair(argument, afterValue) + } + } + if (execution.arguments[index].isNamed) { + argument = CgPythonNamedArgument(name, argument) + } + + methodArguments += argument + } + methodArguments.forEach { + if (it is CgPythonTree) { + context.currentBlock.addAll(it.arguments) + } + if (it is CgPythonNamedArgument && it.value is CgPythonTree) { + context.currentBlock.addAll(it.value.arguments) + } + } + + recordActualResult() + generateResultAssertions() + + if (currentExecution?.result !is UtExecutionFailure && methodType == CgTestMethodType.SUCCESSFUL) { + generateFieldStateAssertions(stateAssertions, assertThisObject, testSet.executableUnderTest) + } + } + + if (statics.isNotEmpty()) { + +tryBlock { + mainBody() + }.finally { + recoverStaticFields() + } + } else { + mainBody() + } + } + } + + override fun generateResultAssertions() { + if (currentExecutableToCall is MethodId) { + val currentExecution = currentExecution!! + val executionResult = currentExecution.result + if (executionResult is UtExecutionFailure) { + val exceptionId = executionResult.rootCauseException.message?.let {PythonClassId(it)} ?: pythonExceptionClassId + val executionBlock = { + with(currentExecutableToCall) { + when (this) { + is MethodId -> thisInstance[this](*methodArguments.toTypedArray()).intercepted() + else -> {} + } + } + } + when (methodType) { + CgTestMethodType.PASSED_EXCEPTION -> { + testFrameworkManager.expectException(exceptionId) { + executionBlock() + } + return + } + CgTestMethodType.FAILING -> { + val executable = currentExecutableToCall!! as PythonMethodId + val executableName = "${executable.moduleName}.${executable.name}" + val warningLine = + "This test fails because function [$executableName] produces [${exceptionId.prettyName}]" + +CgMultilineComment(warningLine) + emptyLineIfNeeded() + executionBlock() + return + } + else -> {} + } + } + } + super.generateResultAssertions() + } + + private fun generateFieldStateAssertions( + stateAssertions: MutableMap>, + assertThisObject: MutableList>, + executableId: ExecutableId, + ) { + if (stateAssertions.isNotEmpty()) { + emptyLineIfNeeded() + } + stateAssertions.forEach { (index, it) -> + assertEquality( + expected = it.second, + actual = it.first, + expectedVariableName = paramNames[executableId]?.get(index) + "_expected" + ) + } + if (assertThisObject.isNotEmpty()) { + emptyLineIfNeeded() + } + assertThisObject.forEach { + assertEquality( + expected = it.second, + actual = it.first, + expectedVariableName = it.first.name + "_expected" + ) + } + } + + override fun shouldTestPassWithException(execution: UtExecution, exception: Throwable): Boolean { + if (exception is TimeoutException || exception is InstrumentedProcessDeathException) return false + return runtimeExceptionTestsBehaviour == RuntimeExceptionTestsBehaviour.PASS + } + + private fun pythonTestMethod( + methodName: String, + displayName: String?, + params: List = emptyList(), + body: () -> Unit, + ): CgTestMethod { + displayName?.let { + testFrameworkManager.addTestDescription(displayName) + } + + val result = currentExecution!!.result + if (result is UtTimeoutException) { + testFrameworkManager.disableTestMethod( + "Disabled due to the fact that the execution is longer then ${hangingTestsTimeout.timeoutMs} ms" + ) + } + + val testMethod = buildTestMethod { + name = methodName + parameters = params + statements = block(body) + // Exceptions and annotations assignment must run after the statements block is build, + // because we collect info about exceptions and required annotations while building the statements + exceptions += collectedExceptions + annotations += collectedMethodAnnotations + methodType = this@PythonCgMethodConstructor.methodType + + val docComment = currentExecution?.summary?.map { convertDocToCg(it) }?.toMutableList() ?: mutableListOf() + documentation = CgDocumentationComment(docComment) + } + testMethods += testMethod + return testMethod + } + + private fun pythonDeepEquals(expected: CgValue, actual: CgVariable) { + require(expected is CgPythonTree) { + "Expected value have to be CgPythonTree but `${expected::class}` found" + } + pythonDeepTreeEquals(expected.tree, expected, actual) + } + + private fun pythonLenAssertConstructor(expected: CgVariable, actual: CgVariable): CgVariable { + val expectedValue = newVar(pythonIntClassId, "expected_length") { + CgGetLength(expected) + } + val actualValue = newVar(pythonIntClassId, "actual_length") { + CgGetLength(actual) + } + emptyLineIfNeeded() + testFrameworkManager.assertEquals(expectedValue, actualValue) + return expectedValue + } + + private fun assertIsInstance(expected: CgValue, actual: CgVariable) { + when (testFrameworkManager) { + is PytestManager -> + (testFrameworkManager as PytestManager).assertIsinstance(listOf(expected.type as PythonClassId), actual) + is UnittestManager -> + (testFrameworkManager as UnittestManager).assertIsinstance(listOf(expected.type as PythonClassId), actual) + else -> testFrameworkManager.assertEquals(expected, actual) + } + } + + private fun pythonAssertElementsByKey( + expectedNode: PythonTree.PythonTreeNode, + expected: CgVariable, + actual: CgVariable, + iterator: CgReferenceExpression, + keyName: String = "index", + ) { + val elements = when (expectedNode) { + is PythonTree.ListNode -> expectedNode.items.values + is PythonTree.TupleNode -> expectedNode.items.values + is PythonTree.DictNode -> expectedNode.items.values + else -> throw UnsupportedOperationException() + } + if (elements.isNotEmpty()) { + val elementsHaveSameStructure = PythonTree.allElementsHaveSameStructure(elements) + val firstChild = + elements.first() // TODO: We can use only structure => we should use another element if the first is empty + + emptyLineIfNeeded() + if (elementsHaveSameStructure) { + val index = newVar(pythonNoneClassId, keyName) { + CgPythonRepr(pythonNoneClassId, "None") + } + forEachLoop { + innerBlock { + condition = index + iterable = iterator + val indexExpected = newVar(firstChild.type, "expected_element") { + CgPythonIndex( + pythonIntClassId, + expected, + index + ) + } + val indexActual = newVar(firstChild.type, "actual_element") { + CgPythonIndex( + pythonIntClassId, + actual, + index + ) + } + pythonDeepTreeEquals(firstChild, indexExpected, indexActual, useExpectedAsValue = true) + statements = currentBlock + } + } + } else { + emptyLineIfNeeded() + assertIsInstance(expected, actual) + } + } + } + + private fun pythonAssertBuiltinsCollection( + expectedNode: PythonTree.PythonTreeNode, + expected: CgValue, + actual: CgVariable, + expectedName: String, + elementName: String = "index", + ) { + val expectedCollection = newVar(expected.type, expectedName) { expected } + + val length = pythonLenAssertConstructor(expectedCollection, actual) + + val iterator = if (expectedNode is PythonTree.DictNode) expected else CgPythonRange(length) + pythonAssertElementsByKey(expectedNode, expectedCollection, actual, iterator, elementName) + } + + private fun pythonAssertIterators( + expectedNode: PythonTree.IteratorNode, + actual: CgVariable, + ) { + val zip = CgPythonZip( + variableConstructor.getOrCreateVariable(PythonTreeModel(expectedNode)), + actual, + ) + val index = newVar(pythonNoneClassId, "pair") { + CgPythonRepr(pythonNoneClassId, "None") + } + forEachLoop { + innerBlock { + condition = index + iterable = zip + testFrameworkManager.assertEquals( + CgPythonIndex( + pythonIntClassId, + index, + CgPythonRepr(pythonIntClassId, "1") + ), + CgPythonIndex( + pythonIntClassId, + index, + CgPythonRepr(pythonIntClassId, "0") + ) + ) + statements = currentBlock + } + } + if (expectedNode.items.size < PythonTree.MAX_ITERATOR_SIZE) { + testFrameworkManager.expectException(expectedNode.exception) { + +CgPythonFunctionCall( + PythonClassId("builtins.next"), + "next", + listOf(actual) + ) + emptyLineIfNeeded() + } + } + } + + private fun pythonDeepTreeEquals( + expectedNode: PythonTree.PythonTreeNode, + expected: CgValue, + actual: CgVariable, + depth: Int = maxDepth, + useExpectedAsValue: Boolean = false + ) { + if (!expectedNode.comparable && expectedNode.isRecursive()) { + emptyLineIfNeeded() + comment("Cannot compare recursive objects") // TODO: add special function for recursive comparison + assertIsInstance(expected, actual) + return + } + if (expectedNode.comparable || depth == 0) { + val expectedValue = if (useExpectedAsValue) { + expected + } else { + variableConstructor.getOrCreateVariable(PythonTreeModel(expectedNode)) + } + if (expectedNode is PythonTree.IteratorNode) { + pythonAssertIterators( + expectedNode, + actual + ) + } else { + testFrameworkManager.assertEquals( + expectedValue, + actual, + ) + } + return + } + when (expectedNode) { + is PythonTree.PrimitiveNode -> { + emptyLineIfNeeded() + assertIsInstance(expected, actual) + } + + is PythonTree.ListNode -> { + pythonAssertBuiltinsCollection( + expectedNode, + expected, + actual, + "expected_list" + ) + } + + is PythonTree.TupleNode -> { + pythonAssertBuiltinsCollection( + expectedNode, + expected, + actual, + "expected_tuple" + ) + } + + is PythonTree.SetNode -> { + emptyLineIfNeeded() + testFrameworkManager.assertEquals( + expected, actual + ) + } + + is PythonTree.DictNode -> { + pythonAssertBuiltinsCollection( + expectedNode, + expected, + actual, + "expected_dict", + "key" + ) + } + + is PythonTree.IteratorNode -> { + pythonAssertIterators( + expectedNode, + actual + ) + } + + is PythonTree.ReduceNode -> { + if (expectedNode.state.isNotEmpty()) { + expectedNode.state.forEach { (field, value) -> + if (value.comparable) { + val fieldActual = newVar(value.type, "actual_$field") { + CgFieldAccess( + actual, FieldId( + value.type, + field + ) + ) + } + val fieldExpected = if (useExpectedAsValue) { + newVar(value.type, "expected_$field") { + CgFieldAccess( + expected, FieldId( + value.type, + field + ) + ) + } + } else { + variableConstructor.getOrCreateVariable(PythonTreeModel(expectedNode)) + } + pythonDeepTreeEquals(value, fieldExpected, fieldActual, depth - 1, useExpectedAsValue = useExpectedAsValue) + } + } + } else { + emptyLineIfNeeded() + assertIsInstance(expected, actual) + } + } + + else -> {} + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgStatementConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgStatementConstructor.kt new file mode 100644 index 0000000000..349c207617 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgStatementConstructor.kt @@ -0,0 +1,292 @@ +package org.utbot.python.framework.codegen.model.constructor.tree + +import fj.data.Either +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.AnnotationTarget +import org.utbot.framework.codegen.domain.models.CgAnnotation +import org.utbot.framework.codegen.domain.models.CgAnonymousFunction +import org.utbot.framework.codegen.domain.models.CgComment +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgEmptyLine +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgIfStatement +import org.utbot.framework.codegen.domain.models.CgInnerBlock +import org.utbot.framework.codegen.domain.models.CgIsInstance +import org.utbot.framework.codegen.domain.models.CgLogicalAnd +import org.utbot.framework.codegen.domain.models.CgLogicalOr +import org.utbot.framework.codegen.domain.models.CgMultilineComment +import org.utbot.framework.codegen.domain.models.CgMultipleArgsAnnotation +import org.utbot.framework.codegen.domain.models.CgNamedAnnotationArgument +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration +import org.utbot.framework.codegen.domain.models.CgReturnStatement +import org.utbot.framework.codegen.domain.models.CgSingleArgAnnotation +import org.utbot.framework.codegen.domain.models.CgSingleLineComment +import org.utbot.framework.codegen.domain.models.CgThrowStatement +import org.utbot.framework.codegen.domain.models.CgTryCatch +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.tree.* +import org.utbot.framework.codegen.util.isAccessibleFrom +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.codegen.PythonCgLanguageAssistant.getCallableAccessManagerBy +import org.utbot.python.framework.codegen.PythonCgLanguageAssistant.getNameGeneratorBy +import org.utbot.python.framework.codegen.model.constructor.util.importIfNeeded +import org.utbot.python.framework.codegen.model.constructor.util.plus +import java.util.* + +class PythonCgStatementConstructorImpl(context: CgContext) : + CgStatementConstructor, + CgContextOwner by context, + CgCallableAccessManager by getCallableAccessManagerBy(context) { + + private val nameGenerator = getNameGeneratorBy(context) + + override fun newVar( + baseType: ClassId, + model: UtModel?, + baseName: String?, + isMock: Boolean, + isMutable: Boolean, + init: () -> CgExpression + ): CgVariable { + val declarationOrVar: Either = + createDeclarationForNewVarAndUpdateVariableScopeOrGetExistingVariable( + baseType, + model, + baseName, + isMock, + isMutable, + init + ) + + return declarationOrVar.either( + { declaration -> + currentBlock += declaration + + declaration.variable + }, + { variable -> variable } + ) + } + + override fun createDeclarationForNewVarAndUpdateVariableScopeOrGetExistingVariable( + baseType: ClassId, + model: UtModel?, + baseName: String?, + isMock: Boolean, + isMutableVar: Boolean, + init: () -> CgExpression + ): Either { + val baseExpr = init() + + val name = nameGenerator.variableName(baseType, baseName) + val (type, expr) = guardExpression(baseType, baseExpr) + + val declaration = buildDeclaration { + variableType = type + variableName = name + initializer = expr + } + rememberVariableForModel(declaration.variable, model) + return Either.left(declaration) + } + + override fun CgExpression.`=`(value: Any?) { + currentBlock += buildAssignment { + lValue = this@`=` + rValue = value.resolve() + } + } + + override fun CgExpression.and(other: CgExpression): CgLogicalAnd = + CgLogicalAnd(this, other) + + override fun CgExpression.or(other: CgExpression): CgLogicalOr = + CgLogicalOr(this, other) + + override fun ifStatement( + condition: CgExpression, + trueBranch: () -> Unit, + falseBranch: (() -> Unit)? + ): CgIfStatement { + val trueBranchBlock = block(trueBranch) + val falseBranchBlock = falseBranch?.let { block(it) } + return CgIfStatement(condition, trueBranchBlock, falseBranchBlock).also { + currentBlock += it + } + } + + override fun forLoop(init: CgForLoopBuilder.() -> Unit) { + currentBlock += buildForLoop(init) + } + + override fun whileLoop(condition: CgExpression, statements: () -> Unit) { + currentBlock += buildWhileLoop { + this.condition = condition + this.statements += block(statements) + } + } + + override fun doWhileLoop(condition: CgExpression, statements: () -> Unit) { + currentBlock += buildDoWhileLoop { + this.condition = condition + this.statements += block(statements) + } + } + + override fun forEachLoop(init: CgForEachLoopBuilder.() -> Unit) = withNameScope { + currentBlock += buildCgForEachLoop(init) + } + + override fun getClassOf(classId: ClassId): CgExpression { + TODO("Not yet implemented") + } + + override fun createFieldVariable(fieldId: FieldId): CgVariable { + TODO("Not yet implemented") + } + + override fun createExecutableVariable(executableId: ExecutableId, arguments: List): CgVariable { + TODO("Not yet implemented") + } + + override fun tryBlock(init: () -> Unit): CgTryCatch = tryBlock(init, null) + + override fun tryBlock(init: () -> Unit, resources: List?): CgTryCatch = + buildTryCatch { + statements = block(init) + this.resources = resources + } + + override fun CgTryCatch.catch(exception: ClassId, init: (CgVariable) -> Unit): CgTryCatch { + val newHandler = buildExceptionHandler { + val e = declareVariable(exception, nameGenerator.variableName(exception.simpleName.replaceFirstChar { + it.lowercase( + Locale.getDefault() + ) + })) + this.exception = e + this.statements = block { init(e) } + } + return this.copy(handlers = handlers + newHandler) + } + + override fun CgTryCatch.finally(init: () -> Unit): CgTryCatch { + val finallyBlock = block(init) + return this.copy(finally = finallyBlock) + } + + override fun CgExpression.isInstance(value: CgExpression): CgIsInstance = TODO("Not yet implemented") + + override fun innerBlock(init: () -> Unit): CgInnerBlock = + CgInnerBlock(block(init)).also { + currentBlock += it + } + + override fun comment(text: String): CgComment = + CgSingleLineComment(text).also { + currentBlock += it + } + + override fun comment(): CgComment = + CgSingleLineComment("").also { + currentBlock += it + } + + override fun multilineComment(lines: List): CgComment = + CgMultilineComment(lines).also { + currentBlock += it + } + + override fun lambda(type: ClassId, vararg parameters: CgVariable, body: () -> Unit): CgAnonymousFunction { + return withNameScope { + for (parameter in parameters) { + declareParameter(parameter.type, parameter.name) + } + val paramDeclarations = parameters.map { CgParameterDeclaration(it) } + CgAnonymousFunction(type, paramDeclarations, block(body)) + } + } + + override fun addAnnotation(classId: ClassId, argument: Any?, target: AnnotationTarget): CgAnnotation { + val annotation = CgSingleArgAnnotation(classId, argument.resolve(), target) + addAnnotation(annotation) + return annotation + } + + override fun addAnnotation( + classId: ClassId, + namedArguments: List, + target: AnnotationTarget, + ): CgAnnotation { + val annotation = CgMultipleArgsAnnotation( + classId, + namedArguments.toMutableList(), + target, + ) + addAnnotation(annotation) + return annotation + } + + override fun addAnnotation( + classId: ClassId, + target: AnnotationTarget, + buildArguments: MutableList>.() -> Unit, + ): CgAnnotation { + val arguments = mutableListOf>() + .apply(buildArguments) + .map { (name, value) -> CgNamedAnnotationArgument(name, value) } + val annotation = CgMultipleArgsAnnotation(classId, arguments.toMutableList(), target) + addAnnotation(annotation) + return annotation + } + + private fun addAnnotation(annotation: CgAnnotation) { + when (annotation.target) { + AnnotationTarget.Method -> collectedMethodAnnotations.add(annotation) + AnnotationTarget.Class, + AnnotationTarget.Field -> error("Annotation ${annotation.target} is not supported in Python") + } + + val classId = annotation.classId + if (classId is PythonClassId) { + importIfNeeded(classId) + } + } + + override fun returnStatement(expression: () -> CgExpression) { + currentBlock += CgReturnStatement(expression()) + } + + override fun throwStatement(exception: () -> CgExpression): CgThrowStatement = + CgThrowStatement(exception()).also { currentBlock += it } + + override fun emptyLine() { + currentBlock += CgEmptyLine + } + + override fun emptyLineIfNeeded() { + val lastStatement = currentBlock.lastOrNull() ?: return + if (lastStatement is CgEmptyLine) return + emptyLine() + } + + override fun declareVariable(type: ClassId, name: String): CgVariable = + CgVariable(name, type).also { + rememberVariableForModel(it) + } + + override fun guardExpression(baseType: ClassId, expression: CgExpression): ExpressionWithType { + return ExpressionWithType(baseType, expression) + } + + override fun wrapTypeIfRequired(baseType: ClassId): ClassId = + if (baseType.isAccessibleFrom(testClassPackageName)) baseType else objectClassId +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgTestClassConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgTestClassConstructor.kt new file mode 100644 index 0000000000..665b1c9f16 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgTestClassConstructor.kt @@ -0,0 +1,19 @@ +package org.utbot.python.framework.codegen.model.constructor.tree + +import org.utbot.framework.codegen.domain.models.SimpleTestClassModel +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgClassFile +import org.utbot.framework.codegen.tree.CgSimpleTestClassConstructor +import org.utbot.framework.codegen.tree.buildClassFile + +internal class PythonCgTestClassConstructor(context: CgContext) : CgSimpleTestClassConstructor(context) { + override fun construct(testClassModel: SimpleTestClassModel): CgClassFile { + return buildClassFile { + this.declaredClass = withTestClassScope { + with(currentTestClassContext) { testClassSuperclass = testFramework.testSuperClass } + constructTestClass(testClassModel) + } + imports.addAll(context.collectedImports) + } + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgVariableConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgVariableConstructor.kt new file mode 100644 index 0000000000..7bf8676333 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgVariableConstructor.kt @@ -0,0 +1,211 @@ +package org.utbot.python.framework.codegen.model.constructor.tree + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgConstructorCall +import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgStatement +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.tree.CgComponents +import org.utbot.framework.codegen.tree.CgVariableConstructor +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.python.framework.api.python.NormalizedPythonAnnotation +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonMethodId +import org.utbot.python.framework.api.python.PythonModel +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.PythonTreeModel +import org.utbot.python.framework.api.python.RawPythonAnnotation +import org.utbot.python.framework.api.python.util.comparePythonTree +import org.utbot.python.framework.api.python.util.pythonDictClassId +import org.utbot.python.framework.api.python.util.pythonListClassId +import org.utbot.python.framework.api.python.util.pythonNoneClassId +import org.utbot.python.framework.codegen.PythonCgLanguageAssistant +import org.utbot.python.framework.codegen.model.tree.CgPythonDict +import org.utbot.python.framework.codegen.model.tree.CgPythonIndex +import org.utbot.python.framework.codegen.model.tree.CgPythonIterator +import org.utbot.python.framework.codegen.model.tree.CgPythonList +import org.utbot.python.framework.codegen.model.tree.CgPythonNdarray +import org.utbot.python.framework.codegen.model.tree.CgPythonRepr +import org.utbot.python.framework.codegen.model.tree.CgPythonSet +import org.utbot.python.framework.codegen.model.tree.CgPythonTree +import org.utbot.python.framework.codegen.model.tree.CgPythonTuple + + +class PythonCgVariableConstructor(cgContext: CgContext) : CgVariableConstructor(cgContext) { + private val nameGenerator = CgComponents.getNameGeneratorBy(context) + override fun getOrCreateVariable(model: UtModel, name: String?): CgValue { + val baseName = name ?: nameGenerator.nameFrom(model.classId) + + return valueByUtModelWrapper.getOrPut(model.wrap()) { + when (model) { + is PythonTreeModel -> { + val (value, arguments) = pythonBuildObject(model.tree, baseName) + CgPythonTree(model.classId, model.tree, value, arguments) + } + is PythonModel -> error("Unexpected PythonModel: ${model::class}") + else -> super.getOrCreateVariable(model, name) + } + } + } + + private fun pythonBuildObject(objectNode: PythonTree.PythonTreeNode, baseName: String? = null): Pair> { + val id = objectNode.id + val assistant = (context.cgLanguageAssistant as PythonCgLanguageAssistant) + return when (objectNode) { + is PythonTree.PrimitiveNode -> { + Pair(CgPythonRepr(objectNode.type, objectNode.repr), emptyList()) + } + + is PythonTree.ListNode -> { + if (PythonTree.isRecursiveObject(objectNode)) { + val obj = PythonTree.ReduceNode( + id, + pythonListClassId, + PythonClassId("builtins.list"), + emptyList(), + mutableMapOf(), + objectNode.items.values.toList(), + emptyMap(), + ) + pythonBuildObject(obj) + } else { + val items = objectNode.items.values.map { pythonBuildObject(it) } + Pair(CgPythonList(items.map { it.first }), items.flatMap { it.second }) + } + } + + is PythonTree.NDArrayNode -> { + val items = objectNode.items.values.map { pythonBuildObject(it) } + val shape = objectNode.dimensions + val type = objectNode.items.values.firstOrNull()?.type + Pair(CgPythonNdarray(items.map { it.first }, shape, type?:PythonClassId("int")), items.flatMap { it.second }) + } + + is PythonTree.TupleNode -> { + val items = objectNode.items.values.map { pythonBuildObject(it) } + Pair(CgPythonTuple(items.map {it.first}), items.flatMap { it.second }) + } + + is PythonTree.SetNode -> { + val items = objectNode.items.map { pythonBuildObject(it) } + Pair(CgPythonSet(items.map {it.first}.toSet()), items.flatMap { it.second }) + } + + is PythonTree.DictNode -> { + if (PythonTree.isRecursiveObject(objectNode)) { + val obj = PythonTree.ReduceNode( + id, + pythonDictClassId, + PythonClassId("builtins.dict"), + emptyList(), + mutableMapOf(), + emptyList(), + objectNode.items, + ) + pythonBuildObject(obj) + } else { + val keys = objectNode.items.keys.map { pythonBuildObject(it) } + val values = objectNode.items.values.map { pythonBuildObject(it) } + Pair( + CgPythonDict( + keys.zip(values).associate { (key, value) -> + key.first to value.first + } + ), + keys.flatMap { it.second } + values.flatMap { it.second } + ) + } + } + + is PythonTree.IteratorNode -> { + val items = objectNode.items.values.map { pythonBuildObject(it) } + Pair(CgPythonIterator(items.map {it.first}), items.flatMap { it.second }) + } + + is PythonTree.ReduceNode -> { + if (assistant.memoryObjects.containsKey(id)) { + val tree = assistant.memoryObjectsModels[id] + val savedObj = assistant.memoryObjects[id] + if (tree != null && savedObj != null && comparePythonTree(tree, objectNode)) { + return Pair(savedObj, emptyList()) + } + } + + val initArgs = objectNode.args.map { + getOrCreateVariable(PythonTreeModel(it, it.type)) + } + val constructor = ConstructorId( + objectNode.constructor, + initArgs.map { it.type } + ) + val constructorCall = CgConstructorCall(constructor, initArgs) + val obj = newVar(objectNode.type, baseName) { + constructorCall + } + + assistant.memoryObjects[id] = obj + assistant.memoryObjectsModels[id] = objectNode + + val state = objectNode.state.map { (key, value) -> + key to getOrCreateVariable(PythonTreeModel(value, value.type)) + }.toMap() + val listitems = objectNode.listitems.map { + getOrCreateVariable(PythonTreeModel(it, it.type)) + } + val dictitems = objectNode.dictitems.map { (key, value) -> + val keyObj = getOrCreateVariable(PythonTreeModel(key, key.type)) + val valueObj = getOrCreateVariable(PythonTreeModel(value, value.type)) + keyObj to valueObj + } + + if (objectNode.customState) { + val setstate = state["state"]!! + val methodCall = CgMethodCall( + obj, + PythonMethodId( + obj.type as PythonClassId, + "__setstate__", + NormalizedPythonAnnotation(pythonNoneClassId.name), + listOf(RawPythonAnnotation(setstate.type.name)) + ), + listOf(setstate) + ) + +methodCall + } else { + state.forEach { (key, value) -> + obj[FieldId(objectNode.type, key)] `=` value + } + } + listitems.forEach { + val methodCall = CgMethodCall( + obj, + PythonMethodId( + obj.type as PythonClassId, + "append", + NormalizedPythonAnnotation(pythonNoneClassId.name), + listOf(RawPythonAnnotation(it.type.name)) + ), + listOf(it) + ) + +methodCall + } + dictitems.forEach { (key, value) -> + val index = CgPythonIndex( + value.type as PythonClassId, + obj, + key + ) + index `=` value + } + + return Pair(obj, context.currentBlock.toList()) + } + + else -> { + throw UnsupportedOperationException() + } + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonTestFrameworkManager.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonTestFrameworkManager.kt new file mode 100644 index 0000000000..2bd9e38bb2 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonTestFrameworkManager.kt @@ -0,0 +1,183 @@ +package org.utbot.python.framework.codegen.model.constructor.tree + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.TestClassContext +import org.utbot.framework.codegen.domain.models.AnnotationTarget.Method +import org.utbot.framework.codegen.domain.models.CgEqualTo +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgNamedAnnotationArgument +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.services.framework.TestFrameworkManager +import org.utbot.framework.plugin.api.ClassId +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.util.pythonAnyClassId +import org.utbot.python.framework.api.python.util.pythonBoolClassId +import org.utbot.python.framework.api.python.util.pythonNoneClassId +import org.utbot.python.framework.api.python.util.pythonStrClassId +import org.utbot.python.framework.codegen.model.Pytest +import org.utbot.python.framework.codegen.model.Unittest +import org.utbot.python.framework.codegen.model.constructor.util.importIfNeeded +import org.utbot.python.framework.codegen.model.tree.CgPythonAssertEquals +import org.utbot.python.framework.codegen.model.tree.CgPythonFunctionCall +import org.utbot.python.framework.codegen.model.tree.CgPythonRepr +import org.utbot.python.framework.codegen.model.tree.CgPythonTuple +import org.utbot.python.framework.codegen.model.tree.CgPythonWith + +internal class PytestManager(context: CgContext) : TestFrameworkManager(context) { + override fun expectException(exception: ClassId, block: () -> Unit) { + require(testFramework is Pytest) { "According to settings, Pytest was expected, but got: $testFramework" } + require(exception is PythonClassId) { "Exceptions must be PythonClassId" } + context.importIfNeeded(PythonClassId("pytest.raises")) + importIfNeeded(exception) + val withExpression = CgPythonFunctionCall( + pythonNoneClassId, + "pytest.raises", + listOf(CgLiteral(exception, exception.prettyName)) + ) + +CgPythonWith(withExpression, null, context.block(block)) + } + + override val isExpectedExceptionExecutionBreaking: Boolean = true + + override fun addDataProviderAnnotations(dataProviderMethodName: String) { + error("Parametrized tests are not supported for Python") + } + + override fun createArgList(length: Int): CgVariable { + error("Parametrized tests are not supported for Python") + } + + override fun addParameterizedTestAnnotations(dataProviderMethodName: String?) { + error("Parametrized tests are not supported for Python") + } + + override fun passArgumentsToArgsVariable(argsVariable: CgVariable, argsArray: CgVariable, executionIndex: Int) { + TODO("Not yet implemented") + } + + override fun addTestDescription(description: String) = Unit + + override fun disableTestMethod(reason: String) { + require(testFramework is Pytest) { "According to settings, Pytest was expected, but got: $testFramework" } + + context.importIfNeeded(Pytest.skipDecoratorClassId) + val reasonArgument = CgNamedAnnotationArgument( + name = "reason", + value = CgPythonRepr(pythonStrClassId, "'${reason.replace("\"", "'")}'"), + ) + statementConstructor.addAnnotation( + classId = Pytest.skipDecoratorClassId, + namedArguments = listOf(reasonArgument), + target = Method + ) + } + + override val dataProviderMethodsHolder: TestClassContext get() = + error("Parametrized tests are not supported for Python") + + override fun addAnnotationForNestedClasses() { + error("Nested classes annotation does not exist in PyTest") + } + + override fun assertEquals(expected: CgValue, actual: CgValue) { + +CgPythonAssertEquals( + CgEqualTo(actual, expected) + ) + } + + override fun assertSame(expected: CgValue, actual: CgValue) { + error("assertSame does not exist in PyTest") + } + + fun assertIsinstance(types: List, actual: CgVariable) { + +CgPythonAssertEquals( + CgPythonFunctionCall( + pythonBoolClassId, + "isinstance", + listOf( + actual, + if (types.size == 1) + CgLiteral(pythonAnyClassId, types[0].prettyName) + else + CgPythonTuple(types.map { CgLiteral(pythonAnyClassId, it.prettyName) }) + ), + ), + ) + } +} + +internal class UnittestManager(context: CgContext) : TestFrameworkManager(context) { + override val isExpectedExceptionExecutionBreaking: Boolean = true + + override val dataProviderMethodsHolder: TestClassContext + get() = error("Parametrized tests are not supported in Unittest") + + override fun addAnnotationForNestedClasses() { + error("Nested classes annotation does not exist in Unittest") + } + + override fun assertSame(expected: CgValue, actual: CgValue) { + error("assertSame does not exist in Unittest") + } + + override fun expectException(exception: ClassId, block: () -> Unit) { + require(testFramework is Unittest) { "According to settings, Unittest was expected, but got: $testFramework" } + require(exception is PythonClassId) { "Exceptions must be PythonClassId" } + importIfNeeded(exception) + val withExpression = CgPythonFunctionCall( + pythonNoneClassId, + "self.assertRaises", + listOf(CgLiteral(exception, exception.prettyName)) + ) + +CgPythonWith(withExpression, null, context.block(block)) + } + + override fun addDataProviderAnnotations(dataProviderMethodName: String) { + error("Parametrized tests are not supported for Python") + } + + override fun createArgList(length: Int): CgVariable { + error("Parametrized tests are not supported for Python") + } + + override fun addParameterizedTestAnnotations(dataProviderMethodName: String?) { + error("Parametrized tests are not supported for Python") + } + + override fun passArgumentsToArgsVariable(argsVariable: CgVariable, argsArray: CgVariable, executionIndex: Int) { + TODO("Not yet implemented") + } + + override fun addTestDescription(description: String) = Unit + + override fun disableTestMethod(reason: String) { + require(testFramework is Unittest) { "According to settings, Unittest was expected, but got: $testFramework" } + + val reasonArgument = CgNamedAnnotationArgument( + name = "reason", + value = CgPythonRepr(pythonStrClassId, "'${reason.replace("\"", "'")}'"), + ) + statementConstructor.addAnnotation( + classId = Unittest.skipDecoratorClassId, + namedArguments = listOf(reasonArgument), + target = Method, + ) + } + + fun assertIsinstance(types: List, actual: CgVariable) { + +assertions[assertTrue]( + CgPythonFunctionCall( + pythonBoolClassId, + "isinstance", + listOf( + actual, + if (types.size == 1) + CgLiteral(pythonAnyClassId, types[0].prettyName) + else + CgPythonTuple(types.map { CgLiteral(pythonAnyClassId, it.prettyName) }) + ), + ), + ) + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/util/ConstructorUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/util/ConstructorUtils.kt new file mode 100644 index 0000000000..0feb5e99d8 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/util/ConstructorUtils.kt @@ -0,0 +1,52 @@ +package org.utbot.python.framework.codegen.model.constructor.util + +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.PersistentSet +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonMethodId +import org.utbot.python.framework.api.python.pythonBuiltinsModuleName +import org.utbot.python.framework.codegen.model.PythonUserImport +import org.utbot.python.framework.codegen.model.tree.CgPythonFunctionCall + +internal fun CgContextOwner.importIfNeeded(method: PythonMethodId) { + collectedImports += PythonUserImport(method.moduleName) +} + +internal fun CgContextOwner.importIfNeeded(pyClass: PythonClassId) { + collectedImports += PythonUserImport(pyClass.moduleName) +} + +internal operator fun PersistentList.plus(element: T): PersistentList = + this.add(element) + +internal operator fun PersistentList.plus(other: PersistentList): PersistentList = + this.addAll(other) + +internal operator fun PersistentSet.plus(element: T): PersistentSet = + this.add(element) + +internal operator fun PersistentSet.plus(other: PersistentSet): PersistentSet = + this.addAll(other) + +internal fun PythonClassId.dropBuiltins(): PythonClassId { + return if (this.rootModuleName == pythonBuiltinsModuleName) { + val moduleParts = this.moduleName.split(".", limit = 2) + if (moduleParts.size > 1) { + PythonClassId(moduleParts[1], this.simpleName) + } else { + PythonClassId(this.name.split(".", limit = 2).last()) + } + } else + this +} + +internal fun String.dropBuiltins(): String { + val builtinsPrefix = "$pythonBuiltinsModuleName." + return if (this.startsWith(builtinsPrefix)) + this.drop(builtinsPrefix.length) + else + this +} + diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt new file mode 100644 index 0000000000..47b0cc1437 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonRenderer.kt @@ -0,0 +1,676 @@ +package org.utbot.python.framework.codegen.model.constructor.visitor + +import org.apache.commons.text.StringEscapeUtils +import org.utbot.framework.codegen.domain.RegularImport +import org.utbot.framework.codegen.domain.StaticImport +import org.utbot.framework.codegen.domain.models.CgAbstractMultilineComment +import org.utbot.framework.codegen.domain.models.CgAllocateArray +import org.utbot.framework.codegen.domain.models.CgAllocateInitializedArray +import org.utbot.framework.codegen.domain.models.CgAnonymousFunction +import org.utbot.framework.codegen.domain.models.CgArrayAnnotationArgument +import org.utbot.framework.codegen.domain.models.CgArrayInitializer +import org.utbot.framework.codegen.domain.models.CgClass +import org.utbot.framework.codegen.domain.models.CgClassBody +import org.utbot.framework.codegen.domain.models.CgClassFile +import org.utbot.framework.codegen.domain.models.CgCommentedAnnotation +import org.utbot.framework.codegen.domain.models.CgConstructorCall +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgDocRegularStmt +import org.utbot.framework.codegen.domain.models.CgDocumentationComment +import org.utbot.framework.codegen.domain.models.CgElement +import org.utbot.framework.codegen.domain.models.CgEqualTo +import org.utbot.framework.codegen.domain.models.CgErrorTestMethod +import org.utbot.framework.codegen.domain.models.CgErrorWrapper +import org.utbot.framework.codegen.domain.models.CgExecutableCall +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgForEachLoop +import org.utbot.framework.codegen.domain.models.CgForLoop +import org.utbot.framework.codegen.domain.models.CgFormattedString +import org.utbot.framework.codegen.domain.models.CgFrameworkUtilMethod +import org.utbot.framework.codegen.domain.models.CgGetJavaClass +import org.utbot.framework.codegen.domain.models.CgGetKotlinClass +import org.utbot.framework.codegen.domain.models.CgGetLength +import org.utbot.framework.codegen.domain.models.CgInnerBlock +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgMultilineComment +import org.utbot.framework.codegen.domain.models.CgMultipleArgsAnnotation +import org.utbot.framework.codegen.domain.models.CgNamedAnnotationArgument +import org.utbot.framework.codegen.domain.models.CgNotNullAssertion +import org.utbot.framework.codegen.domain.models.CgParameterDeclaration +import org.utbot.framework.codegen.domain.models.CgParameterizedTestDataProviderMethod +import org.utbot.framework.codegen.domain.models.CgSingleArgAnnotation +import org.utbot.framework.codegen.domain.models.CgSingleLineComment +import org.utbot.framework.codegen.domain.models.CgStatement +import org.utbot.framework.codegen.domain.models.CgSwitchCase +import org.utbot.framework.codegen.domain.models.CgSwitchCaseLabel +import org.utbot.framework.codegen.domain.models.CgTestMethod +import org.utbot.framework.codegen.domain.models.CgThisInstance +import org.utbot.framework.codegen.domain.models.CgTripleSlashMultilineComment +import org.utbot.framework.codegen.domain.models.CgTryCatch +import org.utbot.framework.codegen.domain.models.CgTypeCast +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.renderer.CgAbstractRenderer +import org.utbot.framework.codegen.renderer.CgPrinter +import org.utbot.framework.codegen.renderer.CgPrinterImpl +import org.utbot.framework.codegen.renderer.CgRendererContext +import org.utbot.framework.codegen.tree.VisibilityModifier +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.TypeParameters +import org.utbot.framework.plugin.api.WildcardTypeParameter +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.pythonBuiltinsModuleName +import org.utbot.python.framework.api.python.util.pythonAnyClassId +import org.utbot.python.framework.codegen.model.PythonImport +import org.utbot.python.framework.codegen.model.PythonSysPathImport +import org.utbot.python.framework.codegen.model.constructor.util.dropBuiltins +import org.utbot.python.framework.codegen.model.tree.CgPythonAssertEquals +import org.utbot.python.framework.codegen.model.tree.CgPythonDict +import org.utbot.python.framework.codegen.model.tree.CgPythonFunctionCall +import org.utbot.python.framework.codegen.model.tree.CgPythonIndex +import org.utbot.python.framework.codegen.model.tree.CgPythonIterator +import org.utbot.python.framework.codegen.model.tree.CgPythonList +import org.utbot.python.framework.codegen.model.tree.CgPythonNdarray +import org.utbot.python.framework.codegen.model.tree.CgPythonNamedArgument +import org.utbot.python.framework.codegen.model.tree.CgPythonRange +import org.utbot.python.framework.codegen.model.tree.CgPythonRepr +import org.utbot.python.framework.codegen.model.tree.CgPythonSet +import org.utbot.python.framework.codegen.model.tree.CgPythonTree +import org.utbot.python.framework.codegen.model.tree.CgPythonTuple +import org.utbot.python.framework.codegen.model.tree.CgPythonWith +import org.utbot.python.framework.codegen.model.tree.CgPythonZip +import org.utbot.python.framework.codegen.utils.toRelativeRawPath + +internal class CgPythonRenderer( + context: CgRendererContext, + printer: CgPrinter = CgPrinterImpl() +) : + CgAbstractRenderer(context, printer), + CgPythonVisitor { + + override val regionStart: String = "# region" + override val regionEnd: String = "# endregion" + + override val statementEnding: String = "" + + override val logicalAnd: String + get() = "and" + + override val logicalOr: String + get() = "or" + + override val langPackage: String = "python" + + override val ClassId.methodsAreAccessibleAsTopLevel: Boolean + get() = false + + override fun visit(element: CgClassFile) { + renderClassFileImports(element) + + println() + println() + + element.declaredClass.accept(this) + } + + override fun visit(element: CgClass) { + print("class ") + print(element.simpleName) + if (element.superclass != null) { + print("(${element.superclass!!.asString()})") + } + println(":") + withIndent { element.body.accept(this) } + println("") + } + + override fun visit(element: CgCommentedAnnotation) { + print("#") + element.annotation.accept(this) + } + + override fun visit(element: CgSingleArgAnnotation) { + print("@${element.classId.asString()}") + print("(") + element.argument.accept(this) + println(")") + } + + override fun visit(element: CgMultipleArgsAnnotation) { + print("@${element.classId.asString()}") + if (element.arguments.isNotEmpty()) { + print("(") + element.arguments.renderSeparated() + print(")") + } + println() + } + + override fun visit(element: CgNamedAnnotationArgument) { + print(element.name) + print("=") + element.value.accept(this) + } + + override fun visit(element: CgSingleLineComment) { + println("# ${element.comment}") + } + + override fun visit(element: CgAbstractMultilineComment) { + visit(element as CgElement) + } + + override fun visit(element: CgTripleSlashMultilineComment) { + element.lines.forEach { line -> + println("# $line") + } + } + + override fun visit(element: CgMultilineComment) { + val lines = element.lines + if (lines.isEmpty()) return + + if (lines.size == 1) { + print("# ${lines.first()}") + return + } + + // print lines saving indentation + print("\"\"\"") + println(lines.first()) + lines.subList(1, lines.lastIndex).forEach { println(it) } + print(lines.last()) + println("\"\"\"") + } + + override fun visit(element: CgDocumentationComment) { + if (element.lines.all { it.isEmpty() }) return + + println("\"\"\"") + for (line in element.lines) line.accept(this) + println("\"\"\"") + } + + override fun visit(element: CgDocRegularStmt) { + if (element.isEmpty()) return + + println(element.stmt) + } + + override fun visit(element: CgErrorWrapper) { + element.expression.accept(this) + } + + override fun visit(element: CgClassBody) { + // render regions for test methods + for ((i, region) in (element.methodRegions + element.nestedClassRegions).withIndex()) { + if (i != 0) println() + + region.accept(this) + } + + if (element.staticDeclarationRegions.isEmpty()) { + return + } + } + + override fun visit(element: CgTryCatch) { + print("try") + // TODO introduce CgBlock + visit(element.statements) + for ((exception, statements) in element.handlers) { + print("except ") + renderExceptionCatchVariable(exception) + // TODO introduce CgBlock + visit(statements, printNextLine = element.finally == null) + } + element.finally?.let { + print("finally") + // TODO introduce CgBlock + visit(element.finally!!, printNextLine = true) + } + } + + override fun visit(element: CgArrayAnnotationArgument) { + throw UnsupportedOperationException() + } + + override fun visit(element: CgAnonymousFunction) { + print("lambda ") + element.parameters.renderSeparated() + print(": ") + + visit(element.body) + } + + override fun visit(element: CgEqualTo) { + element.left.accept(this) + val isCompareTypes = listOf("builtins.bool", "types.NoneType") + if (isCompareTypes.contains(element.right.type.canonicalName)) { + print(" is ") + } else { + print(" == ") + } + element.right.accept(this) + } + + override fun visit(element: CgTypeCast) { + TODO("Not yet implemented") + } + + override fun visit(element: CgNotNullAssertion) { + element.expression.accept(this) + } + + override fun visit(element: CgAllocateArray) { + print("[None] * ${element.size}") + } + + override fun visit(element: CgAllocateInitializedArray) { + print(" [") + element.initializer.accept(this) + print(" ]") + } + + override fun visit(element: CgArrayInitializer) { + val elementType = element.elementType + val elementsInLine = arrayElementsInLine(elementType) + + print("[") + element.values.renderElements(elementsInLine) + print("]") + } + + override fun visit(element: CgSwitchCaseLabel) { + throw UnsupportedOperationException() + } + + override fun visit(element: CgSwitchCase) { + throw UnsupportedOperationException() + } + + override fun visit(element: CgParameterDeclaration) { + print(element.name.escapeNamePossibleKeyword()) + if (element.type.name != "") + print(": ") + print(element.type.name) + } + + override fun visit(element: CgGetLength) { + print("len(") + element.variable.accept(this) + print(")") + } + + override fun visit(element: CgGetJavaClass) { + throw UnsupportedOperationException() + } + + override fun visit(element: CgGetKotlinClass) { + throw UnsupportedOperationException() + } + + override fun visit(element: CgConstructorCall) { + print(element.executableId.classId.name.dropBuiltins()) + renderExecutableCallArguments(element) + } + + override fun renderRegularImport(regularImport: RegularImport) { + val escapedImport = getEscapedImportRendering(regularImport) + println("import $escapedImport") + } + + override fun renderStaticImport(staticImport: StaticImport) { + throw UnsupportedOperationException() + } + + override fun renderClassFileImports(element: CgClassFile) { + element.imports + .filterIsInstance() + .sortedBy { it.order } + .forEach { renderPythonImport(it) } + } + + fun renderPythonImport(pythonImport: PythonImport) { + val importBuilder = StringBuilder() + if (pythonImport is PythonSysPathImport) { + importBuilder.append("sys.path.append(${pythonImport.sysPath.toRelativeRawPath()})") + } else if (pythonImport.moduleName == null) { + importBuilder.append("import ${pythonImport.importName}") + } else { + importBuilder.append("from ${pythonImport.moduleName} import ${pythonImport.importName}") + } + if (pythonImport.alias != null) { + importBuilder.append(" as ${pythonImport.alias}") + } + println(importBuilder.toString()) + } + + override fun renderMethodSignature(element: CgTestMethod) { + print("def ") + print(element.name) + + print("(") + val newLinesNeeded = element.parameters.size > maxParametersAmountInOneLine + val selfParameter = CgThisInstance(pythonAnyClassId) + (listOf(selfParameter) + element.parameters).renderSeparated(newLinesNeeded) + print(")") + } + + override fun renderMethodSignature(element: CgErrorTestMethod) { + print("def ") + print(element.name) + print("(") + val selfParameter = CgThisInstance(pythonAnyClassId) + listOf(selfParameter).renderSeparated() + print(")") + } + + override fun visit(element: CgErrorTestMethod) { + renderMethodDocumentation(element) + renderMethodSignature(element) + visit(element as CgMethod) + withIndent { + println("pass") + } + } + + override fun renderMethodSignature(element: CgParameterizedTestDataProviderMethod) { + val returnType = element.returnType.canonicalName + println("def ${element.name}() -> $returnType: pass") + } + + override fun renderMethodSignature(element: CgFrameworkUtilMethod) { + throw UnsupportedOperationException() + } + + override fun visit(element: CgInnerBlock) { + withIndent { + for (statement in element.statements) { + statement.accept(this) + } + } + } + + override fun renderForLoopVarControl(element: CgForLoop) { + print("for ") + visit(element.condition) + print(" in ") + element.initialization.accept(this@CgPythonRenderer) + println(":") + } + + override fun renderDeclarationLeftPart(element: CgDeclaration) { + visit(element.variable) + } + + override fun toStringConstantImpl(byte: Byte): String { + return "b'$byte'" + } + + override fun toStringConstantImpl(short: Short): String { + return "$short" + } + + override fun toStringConstantImpl(int: Int): String { + return "$int" + } + + override fun toStringConstantImpl(long: Long): String { + return "$long" + } + + override fun toStringConstantImpl(float: Float): String { + return "$float" + } + + override fun renderAccess(caller: CgExpression) { + print(".") + } + + override fun renderTypeParameters(typeParameters: TypeParameters) { + if (typeParameters.parameters.isNotEmpty()) { + print("[") + if (typeParameters is WildcardTypeParameter) { + print("typing.Any") + } else { + print(typeParameters.parameters.joinToString { it.name }) + } + print("]") + } + } + + override fun renderExecutableCallArguments(executableCall: CgExecutableCall) { + print("(") + executableCall.arguments.renderSeparated() + print(")") + } + + override fun renderExceptionCatchVariable(exception: CgVariable) { + print(exception.type.canonicalName) + print(" as ") + print(exception.name.escapeNamePossibleKeyword()) + } + + override fun escapeNamePossibleKeywordImpl(s: String): String = s + override fun renderVisibility(modifier: VisibilityModifier) { + throw UnsupportedOperationException() + } + + override fun renderClassModality(aClass: CgClass) { + throw UnsupportedOperationException() + } + + override fun visit(block: List, printNextLine: Boolean) { + println(":") + + withIndent { + for (statement in block) { + statement.accept(this) + } + } + + if (printNextLine) println() + } + + override fun visit(element: CgThisInstance) { + print("self") + } + + override fun visit(element: CgMethod) { + visit(listOf(element.documentation) + element.statements, printNextLine = false) + } + + override fun visit(element: CgTestMethod) { + for (annotation in element.annotations) { + annotation.accept(this) + } + renderMethodSignature(element) + visit(element as CgMethod) + } + + override fun visit(element: CgMethodCall) { + if (element.caller == null) { + val module = (element.executableId.classId as PythonClassId).moduleName + if (module != pythonBuiltinsModuleName) { + print("$module.") + } + } else { + element.caller!!.accept(this) + print(".") + } + print(element.executableId.name) + + renderTypeParameters(element.typeParameters) + renderExecutableCallArguments(element) + } + + override fun visit(element: CgPythonRepr) { + val content = element.content.dropBuiltins() + if (content.startsWith("\"") && content.endsWith("\"")) { + val realContent = content.slice(1 until content.length - 1) + if (realContent.startsWith("r\\\"") && realContent.endsWith("\\\"")) { // raw string + val innerContent = realContent.slice(5 until realContent.length - 4) + print("r\"${innerContent.replace("\r", "\\r").replace("\n", "\\n")}\"") + } else { + print("\"${realContent.replace("\r", "\\r").replace("\n", "\\n")}\"") + } + } else { + print(content.dropBuiltins()) + } + } + + override fun visit(element: CgPythonIndex) { + visit(element.obj) + print("[") + element.index.accept(this) + print("]") + } + + override fun visit(element: CgPythonFunctionCall) { + print(element.name.dropBuiltins()) + print("(") + val newLinesNeeded = element.parameters.size > maxParametersAmountInOneLine + element.parameters.renderSeparated(newLinesNeeded) + print(")") + } + + override fun visit(element: CgPythonAssertEquals) { + print("${element.keyword} ") + element.expression.accept(this) + println() + } + + override fun visit(element: CgPythonRange) { + print("range(") + listOf(element.start, element.stop, element.step).renderSeparated() + print(")") + } + + override fun visit(element: CgPythonZip) { + print("zip(") + listOf(element.first, element.second).renderSeparated() + print(")") + } + + override fun visit(element: CgPythonList) { + print("[") + element.elements.renderSeparated() + print("]") + } + + override fun visit(element: CgPythonNdarray) { + + print("numpy.ndarray(") + print("dtype=") + print(element.dtype.toString().removePrefix("builtins.")) + print(",") + print("shape=(") + + print(element.shape.joinToString(",") { it.toString() }) + print("), buffer=numpy.array([") + element.elements.renderSeparated() + print("]))") + + + } + + override fun visit(element: CgPythonTuple) { + if (element.elements.isEmpty()) { + print("tuple()") + } else { + print("(") + element.elements.renderSeparated() + if (element.elements.size == 1) { + print(",") + } + print(")") + } + } + + override fun visit(element: CgPythonSet) { + if (element.elements.isEmpty()) + print("set()") + else { + print("{") + element.elements.toList().renderSeparated() + print("}") + } + } + + override fun visit(element: CgPythonIterator) { + print("iter([") + element.elements.renderSeparated() + print("])") + } + + override fun visit(element: CgPythonTree) { + element.value.accept(this) + } + + override fun visit(element: CgPythonWith) { + print("with ") + element.expression.accept(this) + if (element.target != null) { + print(" as ") + element.target.accept(this) + } + println(":") + withIndent { element.statements.forEach { it.accept(this) } } + } + + override fun visit(element: CgPythonNamedArgument) { + element.name?.let { print("$it=") } + element.value.accept(this) + } + + override fun visit(element: CgPythonDict) { + print("{") + element.elements.map { (key, value) -> + key.accept(this) + print(": ") + value.accept(this) + print(", ") + } + print("}") + } + + override fun visit(element: CgForEachLoop) { + print("for ") + element.condition.accept(this) + print(" in ") + element.iterable.accept(this) + println(":") + withIndent { element.statements.forEach { it.accept(this) } } + } + + override fun visit(element: CgLiteral) { + print(element.value.toString().dropBuiltins()) + } + + override fun visit(element: CgFormattedString) { + print("f\"") + element.array.forEachIndexed { index, cgElement -> + if (cgElement is CgLiteral) { + print("{") + print(cgElement.toStringConstant(asRawString = true)) + print("}") + } else { + print("{") + cgElement.accept(this) + print("}") + } + + if (index < element.array.lastIndex) print(" ") + } + print("\"") + } + + override fun String.escapeCharacters(): String = + StringEscapeUtils + .escapeJava(this) + .replace("\"", "\\\"") + .replace("\\f", "\\u000C") + .replace("\\xxx", "\\\u0058\u0058\u0058") +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonVisitor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonVisitor.kt new file mode 100644 index 0000000000..2dcc5b10e4 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/visitor/CgPythonVisitor.kt @@ -0,0 +1,37 @@ +package org.utbot.python.framework.codegen.model.constructor.visitor + +import org.utbot.framework.codegen.renderer.CgVisitor +import org.utbot.python.framework.codegen.model.tree.CgPythonAssertEquals +import org.utbot.python.framework.codegen.model.tree.CgPythonDict +import org.utbot.python.framework.codegen.model.tree.CgPythonFunctionCall +import org.utbot.python.framework.codegen.model.tree.CgPythonIndex +import org.utbot.python.framework.codegen.model.tree.CgPythonIterator +import org.utbot.python.framework.codegen.model.tree.CgPythonList +import org.utbot.python.framework.codegen.model.tree.CgPythonNamedArgument +import org.utbot.python.framework.codegen.model.tree.CgPythonRange +import org.utbot.python.framework.codegen.model.tree.CgPythonRepr +import org.utbot.python.framework.codegen.model.tree.CgPythonSet +import org.utbot.python.framework.codegen.model.tree.CgPythonTree +import org.utbot.python.framework.codegen.model.tree.CgPythonTuple +import org.utbot.python.framework.codegen.model.tree.CgPythonWith +import org.utbot.python.framework.codegen.model.tree.CgPythonZip +import org.utbot.python.framework.codegen.model.tree.CgPythonNdarray + +interface CgPythonVisitor : CgVisitor { + + fun visit(element: CgPythonRepr): R + fun visit(element: CgPythonIndex): R + fun visit(element: CgPythonAssertEquals): R + fun visit(element: CgPythonFunctionCall): R + fun visit(element: CgPythonRange): R + fun visit(element: CgPythonDict): R + fun visit(element: CgPythonTuple): R + fun visit(element: CgPythonList): R + fun visit(element: CgPythonNdarray): R + fun visit(element: CgPythonSet): R + fun visit(element: CgPythonIterator): R + fun visit(element: CgPythonTree): R + fun visit(element: CgPythonWith): R + fun visit(element: CgPythonNamedArgument): R + fun visit(element: CgPythonZip): R +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/tree/CgPythonElement.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/tree/CgPythonElement.kt new file mode 100644 index 0000000000..d8dadd090e --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/tree/CgPythonElement.kt @@ -0,0 +1,161 @@ +package org.utbot.python.framework.codegen.model.tree + +import org.utbot.framework.codegen.domain.models.CgElement +import org.utbot.framework.codegen.domain.models.CgExpression +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgStatement +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.renderer.CgVisitor +import org.utbot.framework.plugin.api.ClassId +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonDictClassId +import org.utbot.python.framework.api.python.util.pythonIntClassId +import org.utbot.python.framework.api.python.util.pythonIteratorClassId +import org.utbot.python.framework.api.python.util.pythonListClassId +import org.utbot.python.framework.api.python.util.pythonRangeClassId +import org.utbot.python.framework.api.python.util.pythonSetClassId +import org.utbot.python.framework.api.python.util.pythonTupleClassId +import org.utbot.python.framework.api.python.util.pythonNdarrayClassId +import org.utbot.python.framework.codegen.model.constructor.visitor.CgPythonVisitor + +interface CgPythonElement : CgElement { + override fun accept(visitor: CgVisitor): R = visitor.run { + if (visitor is CgPythonVisitor) { + when (val element = this@CgPythonElement) { + is CgPythonRepr -> visitor.visit(element) + is CgPythonIndex -> visitor.visit(element) + is CgPythonAssertEquals -> visitor.visit(element) + is CgPythonFunctionCall -> visitor.visit(element) + is CgPythonRange -> visitor.visit(element) + is CgPythonList -> visitor.visit(element) + is CgPythonSet -> visitor.visit(element) + is CgPythonDict -> visitor.visit(element) + is CgPythonTuple -> visitor.visit(element) + is CgPythonTree -> visitor.visit(element) + is CgPythonWith -> visitor.visit(element) + is CgPythonNamedArgument -> visitor.visit(element) + is CgPythonIterator -> visitor.visit(element) + is CgPythonZip -> visitor.visit(element) + is CgPythonNdarray -> visitor.visit(element) + else -> throw IllegalArgumentException("Can not visit element of type ${element::class}") + } + } else { + super.accept(visitor) + } + } +} + +class CgPythonTree( + override val type: ClassId, + val tree: PythonTree.PythonTreeNode, + val value: CgValue, + val arguments: List = emptyList() +) : CgValue, CgPythonElement + +class CgPythonRepr( + override val type: ClassId, + val content: String +) : CgValue, CgPythonElement + +class CgPythonAssertEquals( + val expression: CgExpression, + val keyword: String = "assert", +) : CgStatement, CgPythonElement + +class CgPythonFunctionCall( + override val type: PythonClassId, + val name: String, + val parameters: List, +) : CgExpression, CgPythonElement + +class CgPythonIndex( + override val type: PythonClassId, + val obj: CgVariable, + val index: CgExpression, +) : CgValue, CgPythonElement + +class CgPythonRange( + val start: CgValue, + val stop: CgValue, + val step: CgValue, +) : CgValue, CgPythonElement { + override val type: PythonClassId + get() = pythonRangeClassId + + constructor(stop: Int) : this( + CgLiteral(pythonIntClassId, 0), + CgLiteral(pythonIntClassId, stop), + CgLiteral(pythonIntClassId, 1), + ) + + constructor(stop: CgValue) : this( + CgLiteral(pythonIntClassId, 0), + stop, + CgLiteral(pythonIntClassId, 1), + ) +} + +class CgPythonZip( + val first: CgValue, + val second: CgValue +): CgValue, CgPythonElement { + override val type: ClassId + get() = PythonClassId("builtins.zip") +} + +class CgPythonList( + val elements: List +) : CgValue, CgPythonElement { + override val type: PythonClassId = pythonListClassId +} + +class CgPythonTuple( + val elements: List +) : CgValue, CgPythonElement { + override val type: PythonClassId = pythonTupleClassId +} + +class CgPythonSet( + val elements: Set +) : CgValue, CgPythonElement { + override val type: PythonClassId = pythonSetClassId +} + +class CgPythonNdarray( + val elements: List, + val shape: List, + val dtype: PythonClassId +) : CgValue, CgPythonElement { + override val type: PythonClassId = pythonNdarrayClassId +} + + +class CgPythonDict( + val elements: Map +) : CgValue, CgPythonElement { + override val type: PythonClassId = pythonDictClassId +} + +class CgPythonIterator( + val elements: List, +) : CgValue, CgPythonElement { + override val type: PythonClassId = pythonIteratorClassId +} + +data class CgPythonWith( + val expression: CgExpression, + val target: CgExpression?, + val statements: List, +) : CgStatement, CgPythonElement + +class CgPythonNamedArgument( + val name: String?, + val value: CgExpression, +) : CgValue, CgPythonElement { + override val type: ClassId = value.type +} + + + diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/utils/StringUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/utils/StringUtils.kt new file mode 100644 index 0000000000..40f3b5b831 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/utils/StringUtils.kt @@ -0,0 +1,12 @@ +package org.utbot.python.framework.codegen.utils + +import java.nio.file.FileSystems + + +fun String.toRelativeRawPath(): String { + val dirname = "os.path.dirname(__file__)" + if (this.isEmpty()) { + return dirname + } + return "$dirname + r'${FileSystems.getDefault().separator}${this}'" +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/external/JavaApiProcessor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/external/JavaApiProcessor.kt new file mode 100644 index 0000000000..5e18044a11 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/external/JavaApiProcessor.kt @@ -0,0 +1,18 @@ +package org.utbot.python.framework.external + +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.PythonTestGenerationProcessor +import org.utbot.python.PythonTestSet + +class JavaApiProcessor( + override val configuration: PythonTestGenerationConfig +) : PythonTestGenerationProcessor() { + override fun saveTests(testsCode: String) { + } + + override fun notGeneratedTestsAction(testedFunctions: List) { + } + + override fun processCoverageInfo(testSets: List) { + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/external/PythonObjectName.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/external/PythonObjectName.kt new file mode 100644 index 0000000000..92b663d905 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/external/PythonObjectName.kt @@ -0,0 +1,15 @@ +package org.utbot.python.framework.external + +import org.utbot.python.framework.api.python.pythonBuiltinsModuleName +import org.utbot.python.framework.api.python.util.moduleOfType + +data class PythonObjectName( + val moduleName: String, + val name: String, +) { + constructor(fullName: String) : this( + moduleOfType(fullName) ?: pythonBuiltinsModuleName, + fullName.removePrefix(moduleOfType(fullName) ?: pythonBuiltinsModuleName).removePrefix(".") + ) + val fullName = "$moduleName.$name" +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/external/PythonTestMethodInfo.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/external/PythonTestMethodInfo.kt new file mode 100644 index 0000000000..1269131aca --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/external/PythonTestMethodInfo.kt @@ -0,0 +1,16 @@ +package org.utbot.python.framework.external + +import org.utbot.python.PythonMethod +import org.utpython.types.pythonTypeName + +class PythonTestMethodInfo( + val methodName: PythonObjectName, + val moduleFilename: String, + val containingClassName: PythonObjectName? = null +) + +fun PythonMethod.toPythonMethodInfo() = PythonTestMethodInfo( + PythonObjectName(this.name), + this.moduleFilename, + this.containingPythonClass?.let { PythonObjectName(it.pythonTypeName()) } +) diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/external/PythonUtBotJavaApi.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/external/PythonUtBotJavaApi.kt new file mode 100644 index 0000000000..3392756728 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/external/PythonUtBotJavaApi.kt @@ -0,0 +1,181 @@ +package org.utbot.python.framework.external + +import mu.KLogger +import mu.KotlinLogging +import org.utbot.common.PathUtil.toPath +import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.python.PythonMethodHeader +import org.utbot.python.PythonTestGenerationConfig +import org.utbot.python.PythonTestGenerationProcessor +import org.utbot.python.PythonTestSet +import org.utbot.python.TestFileInformation +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.codegen.model.Pytest +import org.utbot.python.framework.codegen.model.Unittest +import org.utbot.python.utils.RequirementsInstaller +import org.utbot.python.utils.Success +import org.utbot.python.utils.findCurrentPythonModule +import java.io.File + +object PythonUtBotJavaApi { + private val logger: KLogger = KotlinLogging.logger {} + + /** + * Generate test sets + * + * @param testMethods methods for test generation + * @param pythonPath a path to the Python executable file + * @param pythonRunRoot a path to the directory where test sets will be executed + * @param directoriesForSysPath a collection of strings that specifies the additional search path for modules, usually it is only project root + * @param timeout a timeout to the test generation process (in milliseconds) + * @param executionTimeout a timeout to one concrete execution + */ + @JvmStatic + fun generateTestSets ( + testMethods: List, + pythonPath: String, + pythonRunRoot: String, + directoriesForSysPath: Collection, + timeout: Long, + executionTimeout: Long = UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis, + ): List { + logger.info("Checking requirements...") + + val installer = RequirementsInstaller() + RequirementsInstaller.checkRequirements( + installer, + pythonPath, + emptyList() + ) + val processor = initPythonTestGeneratorProcessor( + testMethods, + pythonPath, + pythonRunRoot, + directoriesForSysPath.toSet(), + timeout, + executionTimeout, + ) + logger.info("Loading information about Python types...") + val mypyConfig = processor.sourceCodeAnalyze() + logger.info("Generating tests...") + return processor.testGenerate(mypyConfig) + } + + /** + * Generate test sets code + * + * @param testSets a list of test sets + * @param pythonRunRoot a path to the directory where test sets will be executed + * @param directoriesForSysPath a collection of strings that specifies the additional search path for modules, usually it is only project root + * @param testFramework a test framework (Unittest or Pytest) + */ + @JvmStatic + fun renderTestSets ( + testSets: List, + pythonRunRoot: String, + directoriesForSysPath: Collection, + testFramework: TestFramework = Unittest, + ): String { + if (testSets.isEmpty()) return "" + + require(testFramework is Unittest || testFramework is Pytest) { "TestFramework should be Unittest or Pytest" } + + testSets.map { it.method.containingPythonClass } .toSet().let { + require(it.size == 1) { "All test methods should be from one class or only top level" } + it.first() + } + + val containingFile = testSets.map { it.method.moduleFilename } .toSet().let { + require(it.size == 1) { "All test methods should be from one module" } + it.first() + } + val moduleUnderTest = findCurrentPythonModule(directoriesForSysPath, containingFile) + require(moduleUnderTest is Success) + + val testMethods = testSets.map { it.method.toPythonMethodInfo() }.toSet().toList() + + val processor = initPythonTestGeneratorProcessor( + testMethods = testMethods, + pythonRunRoot = pythonRunRoot, + directoriesForSysPath = directoriesForSysPath.toSet(), + testFramework = testFramework, + ) + return processor.testCodeGenerate(testSets) + } + + /** + * Generate test sets and render code + * + * @param testMethods methods for test generation + * @param pythonPath a path to the Python executable file + * @param pythonRunRoot a path to the directory where test sets will be executed + * @param directoriesForSysPath a collection of strings that specifies the additional search path for modules, usually it is only project root + * @param timeout a timeout to the test generation process (in milliseconds) + * @param executionTimeout a timeout to one concrete execution + * @param testFramework a test framework (Unittest or Pytest) + */ + @JvmStatic + fun generate( + testMethods: List, + pythonPath: String, + pythonRunRoot: String, + directoriesForSysPath: Collection, + timeout: Long, + executionTimeout: Long = UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis, + testFramework: TestFramework = Unittest, + ): String { + val testSets = + generateTestSets(testMethods, pythonPath, pythonRunRoot, directoriesForSysPath, timeout, executionTimeout) + return renderTestSets(testSets, pythonRunRoot, directoriesForSysPath, testFramework) + } + + private fun initPythonTestGeneratorProcessor ( + testMethods: List, + pythonPath: String = "", + pythonRunRoot: String, + directoriesForSysPath: Set, + timeout: Long = 60_000, + timeoutForRun: Long = 2_000, + testFramework: TestFramework = Unittest, + ): PythonTestGenerationProcessor { + + val pythonFilePath = testMethods.map { it.moduleFilename }.let { + require(it.size == 1) {"All test methods should be from one file"} + it.first() + } + val contentFile = File(pythonFilePath) + val pythonFileContent = contentFile.readText() + + val pythonModule = testMethods.map { it.methodName.moduleName }.let { + require(it.size == 1) {"All test methods should be from one module"} + it.first() + } + + val pythonMethods = testMethods.map { + PythonMethodHeader( + it.methodName.name, + it.moduleFilename, + it.containingClassName?.let { objName -> + PythonClassId(objName.moduleName, objName.name) + }) + } + + return JavaApiProcessor( + PythonTestGenerationConfig( + pythonPath, + TestFileInformation(pythonFilePath, pythonFileContent, pythonModule), + directoriesForSysPath, + pythonMethods, + timeout, + timeoutForRun, + testFramework, + pythonRunRoot.toPath(), + true, + { false }, + RuntimeExceptionTestsBehaviour.FAIL + ) + ) + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/external/RequirementsInstaller.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/external/RequirementsInstaller.kt new file mode 100644 index 0000000000..e060b3e2a3 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/external/RequirementsInstaller.kt @@ -0,0 +1,18 @@ +package org.utbot.python.framework.external + +import org.utbot.python.utils.RequirementsInstaller +import org.utbot.python.utils.RequirementsUtils + +class RequirementsInstaller : RequirementsInstaller { + override fun checkRequirements(pythonPath: String, requirements: List): Boolean { + return RequirementsUtils.requirementsAreInstalled(pythonPath, requirements) + } + + override fun installRequirements(pythonPath: String, requirements: List) { + val result = RequirementsUtils.installRequirements(pythonPath, requirements) + if (result.exitValue != 0) { + System.err.println(result.stderr) + error("Failed to install requirements: ${requirements.joinToString()}.") + } + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/PythonApi.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/PythonApi.kt new file mode 100644 index 0000000000..50ca857d45 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/PythonApi.kt @@ -0,0 +1,246 @@ +package org.utbot.python.fuzzing + +import mu.KotlinLogging +import org.utbot.fuzzer.FuzzedContext +import org.utbot.fuzzing.Control +import org.utbot.fuzzing.Description +import org.utbot.fuzzing.Feedback +import org.utbot.fuzzing.Fuzzing +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.Statistic +import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.utils.Trie +import org.utbot.python.coverage.PyInstruction +import org.utbot.python.engine.ExecutionFeedback +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.provider.BoolValueProvider +import org.utbot.python.fuzzing.provider.BytearrayValueProvider +import org.utbot.python.fuzzing.provider.BytesValueProvider +import org.utbot.python.fuzzing.provider.ComplexValueProvider +import org.utbot.python.fuzzing.provider.ConstantValueProvider +import org.utbot.python.fuzzing.provider.DictValueProvider +import org.utbot.python.fuzzing.provider.FloatValueProvider +import org.utbot.python.fuzzing.provider.NDArrayValueProvider +import org.utbot.python.fuzzing.provider.IntValueProvider +import org.utbot.python.fuzzing.provider.IteratorValueProvider +import org.utbot.python.fuzzing.provider.ListValueProvider +import org.utbot.python.fuzzing.provider.NoneValueProvider +import org.utbot.python.fuzzing.provider.OptionalValueProvider +import org.utbot.python.fuzzing.provider.RePatternValueProvider +import org.utbot.python.fuzzing.provider.ReduceValueProvider +import org.utbot.python.fuzzing.provider.SetValueProvider +import org.utbot.python.fuzzing.provider.StrValueProvider +import org.utbot.python.fuzzing.provider.SubtypeValueProvider +import org.utbot.python.fuzzing.provider.TupleFixSizeValueProvider +import org.utbot.python.fuzzing.provider.TupleValueProvider +import org.utbot.python.fuzzing.provider.TypeAliasValueProvider +import org.utbot.python.fuzzing.provider.UnionValueProvider +import org.utbot.python.fuzzing.provider.utils.isAny +import org.utpython.types.PythonTypeHintsStorage +import org.utpython.types.general.FunctionType +import org.utpython.types.general.UtType +import org.utbot.python.newtyping.inference.InferredTypeFeedback +import org.utbot.python.newtyping.inference.InvalidTypeFeedback +import org.utbot.python.newtyping.inference.SuccessFeedback +import org.utbot.python.newtyping.inference.baseline.BaselineAlgorithm +import org.utpython.types.pythonModuleName +import org.utpython.types.pythonName +import org.utpython.types.pythonTypeName +import org.utpython.types.pythonTypeRepresentation +import org.utbot.python.utils.ExecutionWithTimeoutMode +import org.utbot.python.utils.FakeWithTimeoutMode +import org.utbot.python.utils.TestGenerationLimitManager +import kotlin.random.Random + +private val logger = KotlinLogging.logger {} + +typealias PythonValueProvider = ValueProvider + +data class PythonFuzzedConcreteValue( + val type: UtType, + val value: Any, + val fuzzedContext: FuzzedContext = FuzzedContext.Unknown, +) + +data class FuzzedUtType( + val utType: UtType, + val fuzzAny: Boolean = false, +) { + + fun isAny(): Boolean = utType.isAny() + fun pythonName(): String = utType.pythonName() + fun pythonTypeName(): String = utType.pythonTypeName() + fun pythonModuleName(): String = utType.pythonModuleName() + fun pythonTypeRepresentation(): String = utType.pythonTypeRepresentation() + + companion object { + fun FuzzedUtType.activateAny() = FuzzedUtType(this.utType, true) + fun FuzzedUtType.activateAnyIf(parent: FuzzedUtType) = FuzzedUtType(this.utType, parent.fuzzAny) + fun Collection.activateAny() = this.map { it.activateAny() } + fun Collection.activateAnyIf(parent: FuzzedUtType) = this.map { it.activateAnyIf(parent) } + fun UtType.toFuzzed() = FuzzedUtType(this) + fun Collection.toFuzzed() = this.map { it.toFuzzed() } + } +} + +class PythonMethodDescription( + val name: String, + val concreteValues: Collection = emptyList(), + val pythonTypeStorage: PythonTypeHintsStorage, + val tracer: Trie, + val random: Random, + val limitManager: TestGenerationLimitManager, + val type: FunctionType, +) : Description(type.arguments.toFuzzed()) + +data class PythonExecutionResult( + val executionFeedback: ExecutionFeedback, + val fuzzingPlatformFeedback: PythonFeedback +) + +data class PythonFeedback( + override val control: Control = Control.CONTINUE, + val result: Trie.Node = Trie.emptyNode(), + val typeInferenceFeedback: InferredTypeFeedback = InvalidTypeFeedback, + val fromCache: Boolean = false, +) : Feedback { + fun fromCache(): PythonFeedback { + return PythonFeedback( + control = control, + result = result, + typeInferenceFeedback = typeInferenceFeedback, + fromCache = true, + ) + } +} + +class PythonFuzzedValue( + val tree: PythonTree.PythonTreeNode, + val summary: String? = null, +) + +fun pythonDefaultValueProviders(typeStorage: PythonTypeHintsStorage) = listOf( + NoneValueProvider, + BoolValueProvider, + IntValueProvider, + FloatValueProvider, + ComplexValueProvider, + StrValueProvider, + ListValueProvider, + SetValueProvider, + DictValueProvider, + TupleValueProvider, + TupleFixSizeValueProvider, + OptionalValueProvider, + UnionValueProvider, + BytesValueProvider, + BytearrayValueProvider, + ReduceValueProvider, + RePatternValueProvider, + ConstantValueProvider, + TypeAliasValueProvider, + IteratorValueProvider, + NDArrayValueProvider(typeStorage), + SubtypeValueProvider(typeStorage) +) + +fun pythonAnyTypeValueProviders() = listOf( + NoneValueProvider, + BoolValueProvider, + IntValueProvider, + FloatValueProvider, + ComplexValueProvider, + StrValueProvider, + BytesValueProvider, + BytearrayValueProvider, + ConstantValueProvider, +) + +class PythonFuzzing( + private val pythonTypeStorage: PythonTypeHintsStorage, + private val typeInferenceAlgorithm: BaselineAlgorithm, + private val globalIsCancelled: () -> Boolean, + val execute: suspend (description: PythonMethodDescription, values: List) -> PythonFeedback, +) : Fuzzing { + + private fun generateDefault(providers: List, description: PythonMethodDescription, type: FuzzedUtType)= sequence { + providers.asSequence().forEach { provider -> + if (provider.accept(type)) { + logger.debug { "Provider ${provider.javaClass.simpleName} accepts type ${type.pythonTypeRepresentation()}" } + yieldAll(provider.generate(description, type)) + } + } + } + + private fun generateAnyProviders(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + pythonAnyTypeValueProviders().asSequence().forEach { provider -> + logger.debug { "Provider ${provider.javaClass.simpleName} accepts type ${type.pythonTypeRepresentation()} with activated any" } + yieldAll(provider.generate(description, type)) + } + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType): Sequence> { + val providers = mutableSetOf>() + if (type.isAny()) { + if (type.fuzzAny) { + providers += generateAnyProviders(description, type) + } else { + logger.debug("Any does not have provider") + } + } else { + providers += generateDefault(pythonDefaultValueProviders(pythonTypeStorage), description, type) + } + + return providers.asSequence() + } + + override suspend fun handle(description: PythonMethodDescription, values: List): PythonFeedback { + val result = execute(description, values) + if (result.typeInferenceFeedback is SuccessFeedback && !result.fromCache) { + typeInferenceAlgorithm.laudType(description.type) + } + if (description.limitManager.isCancelled()) { + typeInferenceAlgorithm.feedbackState(description.type, result.typeInferenceFeedback) + } + return result + } + + private suspend fun forkType(description: PythonMethodDescription, stats: Statistic) { + val type: UtType? = typeInferenceAlgorithm.expandState() + if (type != null) { + val d = PythonMethodDescription( + description.name, + description.concreteValues, + description.pythonTypeStorage, + description.tracer, + description.random, + TestGenerationLimitManager(ExecutionWithTimeoutMode, description.limitManager.until), + type as FunctionType + ) + if (!d.limitManager.isCancelled()) { + logger.debug { "Fork new type" } + fork(d, stats) + } + logger.debug { "Fork ended" } + } else { + description.limitManager.mode = FakeWithTimeoutMode + } + } + + override suspend fun isCancelled( + description: PythonMethodDescription, + stats: Statistic + ): Boolean { + if (globalIsCancelled()) { + return true + } + if (description.limitManager.isCancelled() || description.parameters.any { it.isAny() }) { + forkType(description, stats) + if (description.limitManager.isRootManager) { + return FakeWithTimeoutMode.isCancelled(description.limitManager) + } + } + return description.limitManager.isCancelled() + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BoolValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BoolValueProvider.kt new file mode 100644 index 0000000000..945272d609 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BoolValueProvider.kt @@ -0,0 +1,32 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.seeds.Bool +import org.utbot.fuzzing.seeds.KnownValue +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonBoolClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utbot.python.fuzzing.provider.utils.generateSummary + +object BoolValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonBoolClassId.canonicalName + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + yieldBool(Bool.TRUE()) { true } + yieldBool(Bool.FALSE()) { false } + } + + private suspend fun > SequenceScope>.yieldBool(value: T, block: T.() -> Boolean) { + yield(Seed.Known(value) { + PythonFuzzedValue( + PythonTree.fromBool(block(it)), + it.generateSummary(), + ) + }) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BytearrayValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BytearrayValueProvider.kt new file mode 100644 index 0000000000..7ecbb00b80 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BytearrayValueProvider.kt @@ -0,0 +1,43 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonBytearrayClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider + +object BytearrayValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonBytearrayClassId.canonicalName + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + yield(Seed.Recursive( + construct = Routine.Create( + listOf( + description.pythonTypeStorage.pythonInt, + ).toFuzzed() + ) { v -> + val value = v.first().tree as PythonTree.PrimitiveNode + PythonFuzzedValue( + PythonTree.PrimitiveNode( + pythonBytearrayClassId, + "bytearray(${value.repr})" + ), + "%var% = ${type.pythonTypeRepresentation()}", + ) + }, + empty = Routine.Empty { PythonFuzzedValue( + PythonTree.PrimitiveNode( + pythonBytearrayClassId, + "bytearray()" + ), + "%var% = ${type.pythonTypeRepresentation()}" + ) } + )) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BytesValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BytesValueProvider.kt new file mode 100644 index 0000000000..ba107fe158 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/BytesValueProvider.kt @@ -0,0 +1,45 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonBytesClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider + +object BytesValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonBytesClassId.canonicalName + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + yield(Seed.Recursive( + construct = Routine.Create( + listOf( + description.pythonTypeStorage.pythonInt, + ).toFuzzed() + ) { v -> + val value = v.first().tree as PythonTree.PrimitiveNode + PythonFuzzedValue( + PythonTree.PrimitiveNode( + pythonBytesClassId, + "bytes(${value.repr})" + ), + "%var% = ${type.pythonTypeRepresentation()}", + ) + }, + empty = Routine.Empty { + PythonFuzzedValue( + PythonTree.PrimitiveNode( + pythonBytesClassId, + "bytes()" + ), + "%var% = ${type.pythonTypeRepresentation()}" + ) + } + )) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ComplexValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ComplexValueProvider.kt new file mode 100644 index 0000000000..4ccc991f5c --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ComplexValueProvider.kt @@ -0,0 +1,59 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonComplexClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utpython.types.createPythonUnionType + +object ComplexValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonComplexClassId.canonicalName + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + val numberType = createPythonUnionType( + listOf( + description.pythonTypeStorage.pythonFloat, + description.pythonTypeStorage.pythonInt + ) + ) + val emptyValue = + PythonFuzzedValue( + PythonTree.PrimitiveNode( + pythonComplexClassId, + "complex()" + ), + "%var% = ${type.pythonTypeRepresentation()}" + ) + yield(Seed.Recursive( + construct = Routine.Create( + listOf( + numberType, + numberType + ).toFuzzed() + ) { v -> + if (v[0].tree is PythonTree.FakeNode || v[1].tree is PythonTree.FakeNode) { + emptyValue + } else { + val real = v[0].tree as PythonTree.PrimitiveNode + val imag = v[1].tree as PythonTree.PrimitiveNode + val repr = "complex(real=${real.repr}, imag=${imag.repr})" + PythonFuzzedValue( + PythonTree.PrimitiveNode( + pythonComplexClassId, + repr + ), + "%var% = $repr" + ) + } + }, + empty = Routine.Empty { emptyValue } + )) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ConstantValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ConstantValueProvider.kt new file mode 100644 index 0000000000..9201a17d94 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ConstantValueProvider.kt @@ -0,0 +1,41 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utbot.python.fuzzing.value.TypesFromJSONStorage + +object ConstantValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return TypesFromJSONStorage.getTypesFromJsonStorage().containsKey(type.pythonTypeName()) + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType): Sequence> = + sequence { + val storage = TypesFromJSONStorage.getTypesFromJsonStorage() + storage.values.forEach { values -> + val constants = if (values.name == type.pythonTypeName()) { + values.instances + } else { + emptyList() + } + constants.forEach { + yield( + Seed.Simple( + PythonFuzzedValue( + PythonTree.PrimitiveNode( + PythonClassId(values.name), + it + ), + "%var% = $it" + ) + ) + ) + } + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/DictValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/DictValueProvider.kt new file mode 100644 index 0000000000..fe7f9ad72a --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/DictValueProvider.kt @@ -0,0 +1,42 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonDictClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.activateAnyIf +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utpython.types.pythonAnnotationParameters + +object DictValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonDictClassId.canonicalName + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + val params = type.utType.pythonAnnotationParameters() + + yield(Seed.Collection( + construct = Routine.Collection { _ -> + PythonFuzzedValue( + PythonTree.DictNode(mutableMapOf()), + "%var% = ${type.pythonTypeRepresentation()}" + ) + }, + modify = Routine.ForEach(params.toFuzzed().activateAnyIf(type)) { instance, _, arguments -> + val key = arguments[0].tree + val value = arguments[1].tree + val dict = instance.tree as PythonTree.DictNode + if (dict.items.keys.toList().contains(key)) { + dict.items.replace(key, value) + } else { + dict.items[key] = value + } + }, + )) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/FloatValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/FloatValueProvider.kt new file mode 100644 index 0000000000..a8f0dee725 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/FloatValueProvider.kt @@ -0,0 +1,58 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.seeds.IEEE754Value +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonFloatClassId +import org.utbot.python.framework.api.python.util.pythonIntClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedConcreteValue +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utbot.python.fuzzing.provider.utils.generateSummary +import org.utpython.types.pythonTypeName +import java.math.BigDecimal +import java.math.BigInteger + +object FloatValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonFloatClassId.canonicalName + } + + private fun getFloatConstants(concreteValues: Collection): List { + return concreteValues + .filter { accept(it.type.toFuzzed()) } + .map { fuzzedValue -> + (fuzzedValue.value as BigDecimal).let { + IEEE754Value.fromValue(it.toDouble()) + } + } + } + + private fun getIntConstants(concreteValues: Collection): List { + return concreteValues + .filter { it.type.pythonTypeName() == pythonIntClassId.canonicalName } + .map { fuzzedValue -> + (fuzzedValue.value as BigInteger).let { + IEEE754Value.fromValue(it.toDouble()) + } + } + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType): Sequence> = sequence { + val floatConstants = getFloatConstants(description.concreteValues) + val intConstants = getIntConstants(description.concreteValues) + val constants = floatConstants + intConstants + listOf(0, 1).map { IEEE754Value.fromValue(it.toDouble()) } + + constants.asSequence().forEach { value -> + yield(Seed.Known(value) { + PythonFuzzedValue( + PythonTree.fromFloat(it.toDouble()), + it.generateSummary() + ) + }) + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/IntValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/IntValueProvider.kt new file mode 100644 index 0000000000..35c7371817 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/IntValueProvider.kt @@ -0,0 +1,73 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Configuration +import org.utbot.fuzzing.Mutation +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.seeds.BitVectorValue +import org.utbot.fuzzing.seeds.KnownValue +import org.utbot.fuzzing.seeds.Signed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonIntClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedConcreteValue +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utbot.python.fuzzing.provider.utils.generateSummary +import java.math.BigInteger +import kotlin.random.Random +import org.utpython.types.pythonTypeName + +object IntValueProvider : PythonValueProvider { + private val randomStubWithNoUsage = Random(0) + private val configurationStubWithNoUsage = Configuration() + + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonIntClassId.canonicalName + } + + private fun BitVectorValue.change(func: BitVectorValue.() -> Unit): BitVectorValue { + return Mutation> { _, _, _ -> + BitVectorValue(this).apply { func() } + }.mutate(this, randomStubWithNoUsage, configurationStubWithNoUsage) as BitVectorValue + } + + private fun getIntConstants(concreteValues: Collection): List { + return concreteValues + .filter { accept(it.type.toFuzzed()) } + .map { fuzzedValue -> + (fuzzedValue.value as BigInteger).let { + BitVectorValue.fromBigInteger(it) + } + } + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence> { + val isNDArray: Boolean = description.type.arguments.any { it.pythonTypeName() == "numpy.ndarray" } + val bits = if (isNDArray) { + 32 + } else { + 128 + } + val integerConstants = getIntConstants(description.concreteValues) + val modifiedConstants = integerConstants.flatMap { value -> + listOf( + value, + value.change { inc() }, + value.change { dec() } + ) + }.toSet() + + val constants = modifiedConstants + Signed.values().map { it.invoke(bits) } + + constants.asSequence().forEach { vector -> + yield(Seed.Known(vector) { + PythonFuzzedValue( + PythonTree.fromInt(it.toBigInteger()), + it.generateSummary() + ) + }) + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/IteratorValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/IteratorValueProvider.kt new file mode 100644 index 0000000000..a60075f3e4 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/IteratorValueProvider.kt @@ -0,0 +1,37 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonIteratorClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.activateAnyIf +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utpython.types.pythonAnnotationParameters + +object IteratorValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonIteratorClassId.canonicalName + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + val param = type.utType.pythonAnnotationParameters() + yield( + Seed.Collection( + construct = Routine.Collection { + PythonFuzzedValue( + PythonTree.IteratorNode( + emptyMap().toMutableMap(), + ), + "%var% = ${type.pythonTypeRepresentation()}" + ) + }, + modify = Routine.ForEach(param.toFuzzed().activateAnyIf(type)) { self, i, values -> + (self.tree as PythonTree.IteratorNode).items[i] = values.first().tree + } + )) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ListValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ListValueProvider.kt new file mode 100644 index 0000000000..676926c03a --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ListValueProvider.kt @@ -0,0 +1,37 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonListClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.activateAnyIf +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utpython.types.pythonAnnotationParameters + +object ListValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonListClassId.canonicalName + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + val param = type.utType.pythonAnnotationParameters() + yield( + Seed.Collection( + construct = Routine.Collection { + PythonFuzzedValue( + PythonTree.ListNode( + emptyMap().toMutableMap(), + ), + "%var% = ${type.pythonTypeRepresentation()}" + ) + }, + modify = Routine.ForEach(param.toFuzzed().activateAnyIf(type)) { self, i, values -> + (self.tree as PythonTree.ListNode).items[i] = values.first().tree + } + )) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/NDArrayValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/NDArrayValueProvider.kt new file mode 100644 index 0000000000..6c6e8c5fb2 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/NDArrayValueProvider.kt @@ -0,0 +1,73 @@ +package org.utbot.python.fuzzing.provider + +import mu.KotlinLogging +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonNdarrayClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utpython.types.* +import kotlin.math.abs + + +private val logger = KotlinLogging.logger {} +private const val SHAPE_SIZE = 3 +private const val MAX_SHAPE_DIGITS = 2 + +class NDArrayValueProvider( + private val typeStorage: PythonTypeHintsStorage +) : PythonValueProvider { + + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonNdarrayClassId.canonicalName + } + + @Suppress("UNCHECKED_CAST") + override fun generate( + description: PythonMethodDescription, type: FuzzedUtType + ) = sequence { + val param = type.utType.pythonAnnotationParameters() + val intType = typeStorage.pythonInt + val listType = typeStorage.pythonList + + yield(Seed.Recursive( + construct = Routine.Create( + listOf( + listType + .pythonDescription() + .createTypeWithNewAnnotationParameters(listType, listOf(intType)).toFuzzed() + ) + ) { + PythonFuzzedValue( + PythonTree.NDArrayNode( + emptyMap().toMutableMap(), + ((it.first().tree as PythonTree.ListNode).items as Map).values.map { node -> + abs(node.repr.take(MAX_SHAPE_DIGITS).toInt()) % 10 + }.take(SHAPE_SIZE).let { self -> + if (self.fold(1, Int::times) == 0){ + listOf(0) + } else { + self + } + } // TODO: Rethink logic + ), "%var% = ${type.pythonTypeRepresentation()}" + ) + }, + modify = sequence { + yield(Routine.Call((0 until 1000000).map { param[1] }.toFuzzed()) { instance, arguments -> + val obj = instance.tree as PythonTree.NDArrayNode + (0 until obj.dimensions.fold(1, Int::times)).map { + obj.items[it] = arguments[it].tree + } + }) + }, + empty = Routine.Empty { PythonFuzzedValue(PythonTree.FakeNode) } + ) + + ) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/NoneValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/NoneValueProvider.kt new file mode 100644 index 0000000000..119c30db13 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/NoneValueProvider.kt @@ -0,0 +1,19 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utpython.types.PythonNoneTypeDescription + +object NoneValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.utType.meta is PythonNoneTypeDescription + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType): Sequence> = sequence { + yield(Seed.Simple(PythonFuzzedValue(PythonTree.fromNone(), "%var% = None"))) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/OptionalValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/OptionalValueProvider.kt new file mode 100644 index 0000000000..8356958121 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/OptionalValueProvider.kt @@ -0,0 +1,30 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.activateAnyIf +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utpython.types.PythonNoneTypeDescription +import org.utpython.types.PythonUnionTypeDescription +import org.utpython.types.pythonAnnotationParameters + +object OptionalValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.utType.meta is PythonUnionTypeDescription && type.utType.parameters.any { it.meta is PythonNoneTypeDescription } + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + val params = type.utType.pythonAnnotationParameters() + params.forEach { unionParam -> + yield(Seed.Recursive( + construct = Routine.Create(listOf(unionParam).toFuzzed().activateAnyIf(type)) { v -> v.first() }, + empty = Routine.Empty { PythonFuzzedValue(PythonTree.fromNone()) } + )) + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/RePatternValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/RePatternValueProvider.kt new file mode 100644 index 0000000000..aaf2075391 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/RePatternValueProvider.kt @@ -0,0 +1,50 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonRePatternClassId +import org.utbot.python.framework.api.python.util.toPythonRepr +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utbot.python.fuzzing.provider.utils.makeRawString + +object RePatternValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonRePatternClassId.canonicalName + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + yield(Seed.Recursive( + construct = Routine.Create( + listOf( + description.pythonTypeStorage.pythonStr, + ).toFuzzed() + ) { v -> + val value = v.first().tree as PythonTree.PrimitiveNode + val rawValue = value.repr.toPythonRepr().makeRawString() + PythonFuzzedValue( + PythonTree.ReduceNode( + pythonRePatternClassId, + PythonClassId("re.compile"), + listOf(PythonTree.fromString(rawValue)) + ), + "%var% = re.compile(${rawValue})" + ) + }, + empty = Routine.Empty { + PythonFuzzedValue( + PythonTree.PrimitiveNode( + pythonRePatternClassId, + "re.compile('')" + ), + "%var% = re.compile('')" + ) + } + )) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ReduceValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ReduceValueProvider.kt new file mode 100644 index 0000000000..ee5e25899f --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/ReduceValueProvider.kt @@ -0,0 +1,238 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonBoolClassId +import org.utbot.python.framework.api.python.util.pythonNdarrayClassId +import org.utbot.python.framework.api.python.util.pythonBytearrayClassId +import org.utbot.python.framework.api.python.util.pythonBytesClassId +import org.utbot.python.framework.api.python.util.pythonComplexClassId +import org.utbot.python.framework.api.python.util.pythonDictClassId +import org.utbot.python.framework.api.python.util.pythonFloatClassId +import org.utbot.python.framework.api.python.util.pythonIntClassId +import org.utbot.python.framework.api.python.util.pythonListClassId +import org.utbot.python.framework.api.python.util.pythonObjectClassId +import org.utbot.python.framework.api.python.util.pythonRePatternClassId +import org.utbot.python.framework.api.python.util.pythonSetClassId +import org.utbot.python.framework.api.python.util.pythonStrClassId +import org.utbot.python.framework.api.python.util.pythonTupleClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.activateAny +import org.utbot.python.fuzzing.FuzzedUtType.Companion.activateAnyIf +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utbot.python.fuzzing.provider.utils.isAny +import org.utbot.python.fuzzing.provider.utils.isCallable +import org.utbot.python.fuzzing.provider.utils.isConcreteType +import org.utbot.python.fuzzing.provider.utils.isMagic +import org.utbot.python.fuzzing.provider.utils.isPrivate +import org.utbot.python.fuzzing.provider.utils.isProperty +import org.utbot.python.fuzzing.provider.utils.isProtected +import org.utpython.types.PythonCallableTypeDescription +import org.utpython.types.PythonCompositeTypeDescription +import org.utpython.types.PythonDefinition +import org.utpython.types.general.FunctionType +import org.utpython.types.getPythonAttributeByName +import org.utpython.types.getPythonAttributes +import org.utpython.types.pythonDescription +import org.utpython.types.pythonNoneType +import org.utpython.types.pythonTypeName + +object ReduceValueProvider : PythonValueProvider { + private val unsupportedTypes = listOf( + pythonRePatternClassId.canonicalName, + pythonListClassId.canonicalName, + pythonSetClassId.canonicalName, + pythonTupleClassId.canonicalName, + pythonDictClassId.canonicalName, + pythonBytesClassId.canonicalName, + pythonBytearrayClassId.canonicalName, + pythonComplexClassId.canonicalName, + pythonIntClassId.canonicalName, + pythonFloatClassId.canonicalName, + pythonStrClassId.canonicalName, + pythonBoolClassId.canonicalName, + pythonObjectClassId.canonicalName, + pythonNdarrayClassId.canonicalName + + ) + + override fun accept(type: FuzzedUtType): Boolean { + val hasSupportedType = + !unsupportedTypes.contains(type.pythonTypeName()) + return hasSupportedType && isConcreteType(type.utType) && !type.isAny() + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + val fields = findFields(description, type) + val modifications = emptyList>().toMutableList() + modifications.addAll(fields.map { field -> + Routine.Call(listOf(field.type).toFuzzed().activateAny()) { instance, arguments -> + val obj = instance.tree as PythonTree.ReduceNode + obj.state[field.meta.name] = arguments.first().tree + } + }) + + val (constructors, newType) = findConstructors(description, type) + constructors + .forEach { + yieldAll(callConstructors(newType, it, modifications.asSequence(), description)) + } + } + + private fun findFields(description: PythonMethodDescription, type: FuzzedUtType): List { + // TODO: here we need to use same as .getPythonAttributeByName but without name + // TODO: now we do not have fields from parents + // TODO: here we should use only attributes from __slots__ + return type.utType.getPythonAttributes().filter { attr -> + !attr.isMagic() && !attr.isProtected() && !attr.isPrivate() && !attr.isProperty() && !attr.isCallable( + description.pythonTypeStorage + ) + } + } + + /* + * 1. Annotated __init__ without functional arguments and mro(__init__) <= mro(__new__) -> listOf(__init__) + * 2. Not 1 and annotated __new__ without functional arguments -> listOf(__new__) + * 3. Not 1 and not 2 and __init__ without tp.Any -> listOf(__init__) + * 4. Not 1 and not 2 and not 3 and type is not generic -> listOf(__init__, __new__) + activateAny + * 5. emptyList() + */ + private fun findConstructors(description: PythonMethodDescription, type: FuzzedUtType): Pair, FuzzedUtType> { + val initMethodName = "__init__" + val newMethodName = "__new__" + val typeDescr = type.utType.pythonDescription() + return if (typeDescr is PythonCompositeTypeDescription) { + val mro = typeDescr.mro(description.pythonTypeStorage, type.utType) + val initParent = mro.indexOfFirst { p -> p.getPythonAttributes().any { it.meta.name == initMethodName } } + val newParent = mro.indexOfFirst { p -> p.getPythonAttributes().any { it.meta.name == newMethodName } } + val initMethod = type.utType.getPythonAttributeByName(description.pythonTypeStorage, initMethodName) + val newMethod = type.utType.getPythonAttributeByName(description.pythonTypeStorage, newMethodName) + + val initWithoutCallable = initMethod?.isCallable(description.pythonTypeStorage) ?: false + val newWithoutCallable = newMethod?.isCallable(description.pythonTypeStorage) ?: false + + val initWithoutAny = (initMethod?.type as? FunctionType)?.arguments?.drop(1)?.all { !it.isAny() } ?: false + val newWithoutAny = (initMethod?.type as? FunctionType)?.arguments?.drop(1)?.all { !it.isAny() } ?: false + + if (initParent <= newParent && initMethod != null && initWithoutCallable && initWithoutAny) { + listOf(initMethod) to type + } else if (newMethod != null && newWithoutCallable && newWithoutAny) { + listOf(newMethod) to type + } else if (initMethod != null && initWithoutAny) { + listOf(initMethod) to type + } else { + listOfNotNull(initMethod, newMethod) to type.activateAny() + } + } else { + emptyList() to type + } + } + + private fun callConstructors( + type: FuzzedUtType, + constructor: PythonDefinition, + modifications: Sequence>, + description: PythonMethodDescription, + ): Sequence> = sequence { + val constructors = emptyList>().toMutableList() + if (constructor.type.pythonTypeName() == "Overload") { + constructor.type.parameters.forEach { + if (it is FunctionType) { + constructors.add(it to constructor.meta.name) + } + } + } else { + constructors.add(constructor.type as FunctionType to constructor.meta.name) + } + constructors.forEach { + yield(constructObject(type, it, modifications, description)) + } + } + + private fun constructObject( + type: FuzzedUtType, + constructorFunction: Pair, + modifications: Sequence>, + description: PythonMethodDescription, + ): Seed.Recursive { + return Seed.Recursive( + construct = buildConstructor(type, constructorFunction), + modify = modifications, + empty = Routine.Empty { PythonFuzzedValue(buildEmptyValue(type, description)) } + ) + } + + private fun buildEmptyValue( + type: FuzzedUtType, + description: PythonMethodDescription, + ): PythonTree.PythonTreeNode { + val newMethodName = "__new__" + val newMethod = type.utType.getPythonAttributeByName(description.pythonTypeStorage, newMethodName) + return if (newMethod?.type?.parameters?.size == 1) { + val classId = PythonClassId(type.pythonModuleName(), type.pythonName()) + PythonTree.ReduceNode( + classId, + PythonClassId(type.pythonModuleName(), "${type.pythonName()}.__new__"), + listOf(PythonTree.PrimitiveNode(classId, classId.name)), + ) + } else { + PythonTree.FakeNode + } + } + + private fun buildConstructor( + type: FuzzedUtType, + constructor: Pair, + ): Routine.Create { + val (constructorFunction, constructorName) = constructor + val newMethodName = "__new__" + if (constructorName == newMethodName) { + val newMethodArgs = constructorFunction.arguments + return if (newMethodArgs.size == 1) { + val classId = PythonClassId(type.pythonModuleName(), type.pythonName()) + Routine.Create(listOf(pythonNoneType).toFuzzed()) { _ -> + PythonFuzzedValue( + PythonTree.ReduceNode( + classId, + PythonClassId(type.pythonModuleName(), "${type.pythonName()}.__new__"), + listOf(PythonTree.PrimitiveNode(classId, classId.name)), + ), + "%var% = ${type.pythonTypeRepresentation()}" + ) + } + } else { + val classId = PythonClassId(type.pythonModuleName(), type.pythonName()) + Routine.Create(listOf(pythonNoneType).toFuzzed()) { _ -> + PythonFuzzedValue( + PythonTree.ReduceNode( + classId, + PythonClassId(pythonObjectClassId.moduleName, "object.__new__"), + listOf(PythonTree.PrimitiveNode(classId, classId.name)), + ), + "%var% = ${type.pythonTypeRepresentation()}" + ) + } + } + } else { + val typeDescription = constructorFunction.pythonDescription() as PythonCallableTypeDescription + val positionalArgs = typeDescription.argumentKinds.count { it == PythonCallableTypeDescription.ArgKind.ARG_POS } + val arguments = constructorFunction.arguments.take(positionalArgs) + val nonSelfArgs = arguments.drop(1) + return Routine.Create(nonSelfArgs.toFuzzed().activateAnyIf(type)) { v -> + PythonFuzzedValue( + PythonTree.ReduceNode( + PythonClassId(type.pythonModuleName(), type.pythonName()), + PythonClassId(type.pythonModuleName(), type.pythonName()), + v.map { it.tree }, + ), + "%var% = ${type.pythonTypeRepresentation()}" + ) + } + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/SetValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/SetValueProvider.kt new file mode 100644 index 0000000000..20def0c7d3 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/SetValueProvider.kt @@ -0,0 +1,37 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonSetClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.activateAnyIf +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utpython.types.pythonAnnotationParameters + +object SetValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonSetClassId.canonicalName + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + val params = type.utType.pythonAnnotationParameters() + + yield(Seed.Collection( + construct = Routine.Collection { _ -> + PythonFuzzedValue( + PythonTree.SetNode(mutableSetOf()), + "%var% = ${type.pythonTypeRepresentation()}" + ) + }, + modify = Routine.ForEach(params.toFuzzed().activateAnyIf(type)) { instance, _, arguments -> + val item = arguments[0].tree + val set = instance.tree as PythonTree.SetNode + set.items.add(item) + }, + )) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/StrValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/StrValueProvider.kt new file mode 100644 index 0000000000..7e05b51737 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/StrValueProvider.kt @@ -0,0 +1,66 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Seed +import org.utbot.fuzzing.seeds.KnownValue +import org.utbot.fuzzing.seeds.RegexValue +import org.utbot.fuzzing.seeds.StringValue +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonStrClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedConcreteValue +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utbot.python.fuzzing.provider.utils.generateSummary +import org.utbot.python.fuzzing.provider.utils.isPattern +import org.utbot.python.fuzzing.provider.utils.transformQuotationMarks +import org.utbot.python.fuzzing.provider.utils.transformRawString + +object StrValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonStrClassId.canonicalName + } + + private fun getConstants(concreteValues: Collection): List { + return concreteValues + .filter { accept(it.type.toFuzzed()) } + .map { it.value as String } + } + + private fun getStrConstants(concreteValues: Collection): List { + return getConstants(concreteValues) + .filterNot { it.isPattern() } + .map { it.transformQuotationMarks() } + } + + private fun getRegexConstants(concreteValues: Collection): List { + return getConstants(concreteValues) + .filter { it.isPattern() } + .map { it.transformRawString().transformQuotationMarks() } + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + val strConstants = getStrConstants(description.concreteValues) + listOf( + "pythön", + "foo", + "", + ) + strConstants.forEach { yieldStrings(StringValue(it)) { value } } + + val regexConstants = getRegexConstants(description.concreteValues) + regexConstants.forEach { + val maxLength = listOf(16, 256, 2048).random(description.random).coerceAtLeast(it.length) + yieldStrings(RegexValue(it, description.random, maxLength), StringValue::value) + } + } + + private suspend fun > SequenceScope>.yieldStrings(value: T, block: T.() -> Any) { + yield(Seed.Known(value) { + PythonFuzzedValue( + PythonTree.fromString(block(it).toString()), + it.generateSummary(), + ) + }) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/SubtypeValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/SubtypeValueProvider.kt new file mode 100644 index 0000000000..41d4fe14fa --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/SubtypeValueProvider.kt @@ -0,0 +1,45 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.activateAnyIf +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utbot.python.fuzzing.provider.utils.isConcreteType +import org.utpython.types.PythonConcreteCompositeTypeDescription +import org.utpython.types.PythonProtocolDescription +import org.utpython.types.PythonSubtypeChecker.Companion.checkIfRightIsSubtypeOfLeft +import org.utpython.types.PythonTypeHintsStorage +import org.utpython.types.general.DefaultSubstitutionProvider +import org.utpython.types.pythonAnyType +import org.utpython.types.pythonDescription + +class SubtypeValueProvider( + private val typeStorage: PythonTypeHintsStorage +) : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.utType.meta is PythonProtocolDescription || + ((type.utType.meta as? PythonConcreteCompositeTypeDescription)?.isAbstract == true) + } + + private val concreteTypes = typeStorage.simpleTypes.filter { + isConcreteType(it) && it.pythonDescription().name.name.first() != '_' // Don't substitute private classes + }.map { + DefaultSubstitutionProvider.substituteAll(it, it.parameters.map { pythonAnyType }) + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + val subtypes = concreteTypes.filter { checkIfRightIsSubtypeOfLeft(type.utType, it, typeStorage) } + subtypes.forEach { subtype -> + yield( + Seed.Recursive( + construct = Routine.Create(listOf(subtype).toFuzzed().activateAnyIf(type)) { v -> v.first() }, + empty = Routine.Empty { PythonFuzzedValue(PythonTree.FakeNode) } + )) + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/TupleFixSizeValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/TupleFixSizeValueProvider.kt new file mode 100644 index 0000000000..813a63b0a6 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/TupleFixSizeValueProvider.kt @@ -0,0 +1,40 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.activateAnyIf +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utpython.types.PythonTupleTypeDescription +import org.utpython.types.pythonAnnotationParameters + +object TupleFixSizeValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.utType.meta is PythonTupleTypeDescription + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + val params = type.utType.pythonAnnotationParameters() + val length = params.size + val modifications = emptyList>().toMutableList() + for (i in 0 until length) { + modifications.add(Routine.Call(listOf(params[i]).toFuzzed().activateAnyIf(type)) { instance, arguments -> + (instance.tree as PythonTree.TupleNode).items[i] = arguments.first().tree + }) + } + yield(Seed.Recursive( + construct = Routine.Create(params.toFuzzed().activateAnyIf(type)) { v -> + PythonFuzzedValue( + PythonTree.TupleNode(v.withIndex().associate { it.index to it.value.tree }.toMutableMap()), + "%var% = ${type.pythonTypeRepresentation()}" + ) + }, + modify = modifications.asSequence(), + empty = Routine.Empty { PythonFuzzedValue(PythonTree.FakeNode) } + )) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/TupleValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/TupleValueProvider.kt new file mode 100644 index 0000000000..b15466b873 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/TupleValueProvider.kt @@ -0,0 +1,57 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.pythonTupleClassId +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.activateAnyIf +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utpython.types.PythonSubtypeChecker +import org.utpython.types.pythonAnnotationParameters +import org.utpython.types.pythonAnyType +import org.utpython.types.typesAreEqual + +object TupleValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.pythonTypeName() == pythonTupleClassId.canonicalName + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + yieldAll(getConstants(description, type)) + val param = type.utType.pythonAnnotationParameters() + yield( + Seed.Collection( + construct = Routine.Collection { + PythonFuzzedValue( + PythonTree.TupleNode( + emptyMap().toMutableMap(), + ), + "%var% = ${type.pythonTypeRepresentation()}" + ) + }, + modify = Routine.ForEach(param.toFuzzed().activateAnyIf(type)) { self, i, values -> + (self.tree as PythonTree.TupleNode).items[i] = values.first().tree + } + )) + } + + private fun getConstants(description: PythonMethodDescription, type: FuzzedUtType): List> { + if (!typesAreEqual(type.utType.parameters.first(), pythonAnyType)) + return getSuitableConstantsFromCode(description, type) + return emptyList() + } + private fun getSuitableConstantsFromCode(description: PythonMethodDescription, type: FuzzedUtType): List> { + return description.concreteValues.filter { + PythonSubtypeChecker.checkIfRightIsSubtypeOfLeft(type.utType, it.type, description.pythonTypeStorage) + }.mapNotNull { value -> + PythonTree.fromParsedConstant(Pair(value.type, value.value))?.let { + Seed.Simple(PythonFuzzedValue(it)) + } + } + } + +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/TypeAliasValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/TypeAliasValueProvider.kt new file mode 100644 index 0000000000..a4f640c465 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/TypeAliasValueProvider.kt @@ -0,0 +1,31 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.activateAnyIf +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utpython.types.PythonTypeAliasDescription + +object TypeAliasValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.utType.meta is PythonTypeAliasDescription + } + + override fun generate( + description: PythonMethodDescription, + type: FuzzedUtType + ): Sequence> { + val compositeType = PythonTypeAliasDescription.castToCompatibleTypeApi(type.utType) + return sequenceOf( + Seed.Recursive( + construct = Routine.Create(listOf(compositeType.members[0]).toFuzzed().activateAnyIf(type)) { v -> v.first() }, + empty = Routine.Empty { PythonFuzzedValue(PythonTree.FakeNode) } + ) + ) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/UnionValueProvider.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/UnionValueProvider.kt new file mode 100644 index 0000000000..f7ac3bef77 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/UnionValueProvider.kt @@ -0,0 +1,30 @@ +package org.utbot.python.fuzzing.provider + +import org.utbot.fuzzing.Routine +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.fuzzing.FuzzedUtType +import org.utbot.python.fuzzing.FuzzedUtType.Companion.activateAnyIf +import org.utbot.python.fuzzing.FuzzedUtType.Companion.toFuzzed +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utbot.python.fuzzing.PythonValueProvider +import org.utpython.types.PythonNoneTypeDescription +import org.utpython.types.PythonUnionTypeDescription +import org.utpython.types.pythonAnnotationParameters + +object UnionValueProvider : PythonValueProvider { + override fun accept(type: FuzzedUtType): Boolean { + return type.utType.meta is PythonUnionTypeDescription && type.utType.parameters.all { it.meta !is PythonNoneTypeDescription } + } + + override fun generate(description: PythonMethodDescription, type: FuzzedUtType) = sequence { + val params = type.utType.pythonAnnotationParameters() + params.forEach { unionParam -> + yield(Seed.Recursive( + construct = Routine.Create(listOf(unionParam).toFuzzed().activateAnyIf(type)) { v -> v.first() }, + empty = Routine.Empty { PythonFuzzedValue(PythonTree.FakeNode) } + )) + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/ProviderUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/ProviderUtils.kt new file mode 100644 index 0000000000..c54917c298 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/ProviderUtils.kt @@ -0,0 +1,48 @@ +package org.utbot.python.fuzzing.provider.utils + +import org.utbot.fuzzing.Seed +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.fuzzing.PythonFuzzedValue +import org.utbot.python.fuzzing.PythonMethodDescription +import org.utpython.types.* +import org.utpython.types.general.UtType + +fun UtType.isAny(): Boolean { + return meta is PythonAnyTypeDescription +} + +fun getSuitableConstantsFromCode(description: PythonMethodDescription, type: UtType): List> { + return description.concreteValues.filter { + PythonSubtypeChecker.checkIfRightIsSubtypeOfLeft(type, it.type, description.pythonTypeStorage) + }.mapNotNull { value -> + PythonTree.fromParsedConstant(Pair(value.type, value.value))?.let { + Seed.Simple(PythonFuzzedValue(it)) + } + } +} + +fun isConcreteType(type: UtType): Boolean { + return (type.meta as? PythonConcreteCompositeTypeDescription)?.isAbstract == false +} + +fun PythonDefinition.isProtected(): Boolean { + val name = this.meta.name + return name.startsWith("_") && !this.isMagic() +} + +fun PythonDefinition.isPrivate(): Boolean { + val name = this.meta.name + return name.startsWith("__") && !this.isMagic() +} + +fun PythonDefinition.isMagic(): Boolean { + return this.meta.name.startsWith("__") && this.meta.name.endsWith("__") && this.meta.name.length >= 4 +} + +fun PythonDefinition.isProperty(): Boolean { + return (this.meta as? PythonVariableDescription)?.isProperty == true +} + +fun PythonDefinition.isCallable(typeStorage: PythonTypeHintsStorage): Boolean { + return this.type.getPythonAttributeByName(typeStorage, "__call__") != null +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/StringUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/StringUtils.kt new file mode 100644 index 0000000000..2d413a1ceb --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/StringUtils.kt @@ -0,0 +1,51 @@ +package org.utbot.python.fuzzing.provider.utils + +import org.utbot.fuzzing.seeds.isSupportedPattern + +fun String.transformQuotationMarks(): String { + + val doubleQuotationMarks = this.startsWith("\"") && this.endsWith("\"") +// val oneQuotationMarks = this.startsWith("'") && this.endsWith("'") + + val tripleDoubleQuotationMarks = this.startsWith("\"\"\"") && this.endsWith("\"\"\"") + val tripleOneQuotationMarks = this.startsWith("'''") && this.endsWith("'''") + + if (tripleOneQuotationMarks || tripleDoubleQuotationMarks) { + return this.drop(3).dropLast(3) + } + + if (doubleQuotationMarks) { + return this.drop(1).dropLast(1) + } + + return this +} + +fun String.transformRawString(): String { + return if (this.isRawString()) { + this.substring(2, this.length-1) + } else { + this + } +} + +fun String.makeRawString(): String { + return if (this.isRawString()) { + this + } else { + "r${this}" + } +} + +fun String.isRawString(): Boolean { + val rawStringWithDoubleQuotationMarks = this.startsWith("r\"") && this.endsWith("\"") + val rawStringWithOneQuotationMarks = this.startsWith("r'") && this.endsWith("'") + return rawStringWithOneQuotationMarks || rawStringWithDoubleQuotationMarks +} + +fun String.isPattern(): Boolean { + return if (this.isRawString()) { + val stringContent = this.transformRawString() + return stringContent.isSupportedPattern() + } else false +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/SummaryGeneration.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/SummaryGeneration.kt new file mode 100644 index 0000000000..80475457e8 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/provider/utils/SummaryGeneration.kt @@ -0,0 +1,53 @@ +package org.utbot.python.fuzzing.provider.utils + +import org.utbot.fuzzing.seeds.BitVectorValue +import org.utbot.fuzzing.seeds.DefaultFloatBound +import org.utbot.fuzzing.seeds.IEEE754Value +import org.utbot.fuzzing.seeds.KnownValue +import org.utbot.fuzzing.seeds.Signed +import org.utbot.fuzzing.seeds.StringValue + +fun > T.valueToString(): String { + when (this) { + is BitVectorValue -> { + for (defaultBound in Signed.values()) { + if (defaultBound.test(this)) { + return defaultBound.name.lowercase() + } + } + return when (size) { + 1 -> get(0).toString().uppercase() + else -> toString(10) + } + } + is IEEE754Value -> { + for (defaultBound in DefaultFloatBound.values()) { + if (defaultBound.test(this)) { + return defaultBound.name.lowercase().replace("_", " ") + } + } + return when { + is32Float() -> toFloat().toString() + is64Float() -> toDouble().toString() + else -> toString() + } + } + is StringValue -> { + if (value.contains("\"\"\"")) { + val newValue = value.replace("\"", "\\\"") + return "'$newValue'" + } + return "'$value'" + } + else -> return toString() + } +} + +fun > T.generateSummary(): String { + return buildString { + append("%var% = ${valueToString()}") + if (mutatedFrom != null) { + append(" (mutated from ${mutatedFrom?.valueToString()})") + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/PreprocessedValueStorage.kt b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/PreprocessedValueStorage.kt new file mode 100644 index 0000000000..25f5a87e03 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/fuzzing/value/PreprocessedValueStorage.kt @@ -0,0 +1,37 @@ +package org.utbot.python.fuzzing.value + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory + +data class PreprocessedValueFromJSON( + val name: String, + val instances: List +) + +object TypesFromJSONStorage { + private val preprocessedTypes: List + + init { + val typesAsString = TypesFromJSONStorage::class.java.getResource("/preprocessed_values.json") + ?.readText(Charsets.UTF_8) + ?: error("Didn't find preprocessed_values.json") + val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build() + val typesAdapter: JsonAdapter> = moshi.adapter(Types.newParameterizedType(List::class.java, PreprocessedValueFromJSON::class.java)) + preprocessedTypes = typesAdapter.fromJson(typesAsString) ?: emptyList() + } + + private val typeNameMap: Map by lazy { + val result = mutableMapOf() + preprocessedTypes.forEach { type -> + result[type.name] = type + } + result + } + + fun getTypesFromJsonStorage(): Map { + return typeNameMap + } +} + diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/ArgBinding.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/ArgBinding.kt new file mode 100644 index 0000000000..fe43034ef3 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/ArgBinding.kt @@ -0,0 +1,18 @@ +package org.utbot.python.newtyping.ast + +import org.utpython.types.PythonSubtypeChecker +import org.utpython.types.PythonTypeHintsStorage +import org.utpython.types.general.FunctionType + +// TODO: consider different types of parameters +fun signaturesAreCompatible( + functionSignature: FunctionType, + callSignature: FunctionType, + storage: PythonTypeHintsStorage +): Boolean { + if (functionSignature.arguments.size != callSignature.arguments.size) + return false + return (functionSignature.arguments zip callSignature.arguments).all { (funcArg, callArg) -> + PythonSubtypeChecker.checkIfRightIsSubtypeOfLeft(funcArg, callArg, storage) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/ParseUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/ParseUtils.kt new file mode 100644 index 0000000000..47186a3366 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/ParseUtils.kt @@ -0,0 +1,238 @@ +package org.utbot.python.newtyping.ast + +import org.parsers.python.Node +import org.parsers.python.PythonConstants +import org.parsers.python.ast.AdditiveExpression +import org.parsers.python.ast.Argument +import org.parsers.python.ast.Assignment +import org.parsers.python.ast.Block +import org.parsers.python.ast.ClassDefinition +import org.parsers.python.ast.Comparison +import org.parsers.python.ast.Conjunction +import org.parsers.python.ast.Decorators +import org.parsers.python.ast.Delimiter +import org.parsers.python.ast.Disjunction +import org.parsers.python.ast.DotName +import org.parsers.python.ast.ForStatement +import org.parsers.python.ast.FunctionCall +import org.parsers.python.ast.FunctionDefinition +import org.parsers.python.ast.Group +import org.parsers.python.ast.IfStatement +import org.parsers.python.ast.Inversion +import org.parsers.python.ast.InvocationArguments +import org.parsers.python.ast.Keyword +import org.parsers.python.ast.MultiplicativeExpression +import org.parsers.python.ast.Name +import org.parsers.python.ast.Operator +import org.parsers.python.ast.Slice +import org.parsers.python.ast.SliceExpression +import org.parsers.python.ast.StarNamedExpressions +import org.parsers.python.ast.Tuple + +data class ParsedFunctionDefinition(val name: Name, val body: Block, val decorators: List) +data class ParsedClassDefinition(val name: Name, val body: Block) +data class ParsedForStatement(val forVariable: ForVariable, val iterable: Node) +sealed class ForVariable +data class SimpleForVariable(val variable: Name) : ForVariable() +data class TupleForVariable(val elems: List) : ForVariable() +data class ParsedSliceExpression(val head: Node, val slices: ParsedSlices) +sealed class ParsedSlices +data class SimpleSlice(val indexValue: Node) : ParsedSlices() +data class SlicedSlice(val start: Node?, val end: Node?, val step: Node?) : ParsedSlices() +data class TupleSlice(val elems: List) : ParsedSlices() +data class ParsedIfStatement(val condition: Node) +data class ParsedConjunction(val left: Node, val right: Node) +data class ParsedDisjunction(val left: Node, val right: Node) +data class ParsedInversion(val expr: Node) +data class ParsedGroup(val expr: Node) +data class ParsedList(val elems: List) +sealed class ParsedAssignment +data class SimpleAssign(val targets: List, val value: Node) : ParsedAssignment() +data class OpAssign(val target: Node, val op: Delimiter, val value: Node) : ParsedAssignment() +data class ParsedMultiplicativeExpression(val cases: List) +data class ParsedBinaryOperation(val left: Node, val op: Node, val right: Node) +data class ParsedDotName(val head: Node, val tail: Node) +data class ParsedFunctionCall(val function: Node, val args: List) +data class ParsedComparison(val cases: List) +data class PrimitiveComparison(val left: Node, val op: Delimiter, val right: Node) +data class ParsedDecorator(val name: Name, val arguments: InvocationArguments? = null) + +fun parseFunctionDefinition(node: FunctionDefinition): ParsedFunctionDefinition? { + val name = (node.children().first { it is Name } ?: return null) as Name + val body = (node.children().find { it is Block } ?: return null) as Block + val decoratorNodes = node.children().filterIsInstance().firstOrNull() + return ParsedFunctionDefinition(name, body, parseDecorators(decoratorNodes)) +} + +fun parseClassDefinition(node: ClassDefinition): ParsedClassDefinition? { + val name = (node.children().first { it is Name } ?: return null) as Name + val body = (node.children().find { it is Block } ?: return null) as Block + return ParsedClassDefinition(name, body) +} + +fun isIdentification(node: Node): Boolean { + val name = node as? Name ?: return false + val parent = name.parent as? DotName ?: return true + return parent.children().first() == node +} + +fun parseForVariable(node: Node): ForVariable? { + if (node is Name) + return SimpleForVariable(node) + if (node is Tuple) { + return TupleForVariable( + node.children().mapNotNull { child -> + if (child is Delimiter) + return@mapNotNull null + parseForVariable(child) + } + ) + } + return null +} + +fun parseForStatement(node: ForStatement): ParsedForStatement? { + val children = node.children() + val forVariable = parseForVariable(children[1]) ?: return null + val iterable = children[3] + return ParsedForStatement(forVariable, iterable) +} + +fun parseSliceExpression(node: SliceExpression): ParsedSliceExpression? { + val children = node.children() + if (children.size != 2) + return null + val slicesChildren = children[1].children() + val slices = parseSlices(slicesChildren) ?: return null + return ParsedSliceExpression(children[0], slices) +} + +fun parseSlices(children: List): ParsedSlices? { + if (children.any { it is Delimiter && it.toString() == "," }) { + var i = 1 + val slices = mutableListOf() + while (i < children.size) { + var j = children.drop(i).indexOfFirst { it is Delimiter && it.toString() == "," } + if (j == -1) + j = children.size - 1 + else + j += i + val child = parseSlices(children.subList(i, j)) ?: return null + slices.add(child) + i = j + 1 + } + return TupleSlice(slices) + } + val index = if (children.size == 3) 1 else if (children.size == 1) 0 else return null + if (children[index] is Delimiter && children[index].toString() == ":") { + return SlicedSlice(null, null, null) + } + if (children[index] is Slice) { + val sliceChildren = children[index].children() + val i = sliceChildren.indexOfFirst { it is Delimiter && it.toString() == ":" } + var j = sliceChildren.drop(i + 1).indexOfFirst { it is Delimiter && it.toString() == ":" } + if (j == -1) + j = sliceChildren.size + val start = if (i == 0) null else if (i == 1) sliceChildren[0] else return null + val end = if (j - i == 1) null else if (j - i == 2) sliceChildren[i + 1] else return null + val step = + if (j >= sliceChildren.size - 1) null else if (j == sliceChildren.size - 2) sliceChildren.last() else return null + return SlicedSlice(start, end, step) + } + return SimpleSlice(children[index]) +} + +fun parseIfStatement(node: IfStatement): ParsedIfStatement = + ParsedIfStatement(node.children().first { it !is Keyword }) + +fun parseConjunction(node: Conjunction): ParsedConjunction = + ParsedConjunction(node.children()[0], node.children()[2]) + +fun parseDisjunction(node: Disjunction): ParsedDisjunction = + ParsedDisjunction(node.children()[0], node.children()[2]) + +fun parseInversion(node: Inversion): ParsedInversion = + ParsedInversion(node.children()[1]) + +fun parseGroup(node: Group): ParsedGroup = + ParsedGroup(node.children().first { it !is Delimiter }) + +fun parseAdditiveExpression(node: AdditiveExpression): ParsedBinaryOperation? { // TODO + val op = (node.children()[1] as? Operator) ?: return null + return ParsedBinaryOperation(node.children()[0], op, node.children()[2]) +} + +fun parseMultiplicativeExpression(node: MultiplicativeExpression): ParsedMultiplicativeExpression { + val binaries: MutableList = mutableListOf() + val children = node.children() + for (i in 0 until children.size - 2) { + if (children[i + 1] !is Operator && children[i + 1] !is Delimiter) + continue + binaries.add(ParsedBinaryOperation(children[i], children[i + 1], children[i + 2])) + } + return ParsedMultiplicativeExpression(binaries) +} + +fun parseList(node: org.parsers.python.ast.List): ParsedList { + if (node.children().size <= 2) // only delimiters + return ParsedList(emptyList()) + val expr = node.children()[1] + return if (expr is StarNamedExpressions) + ParsedList(expr.children().filter { it !is Delimiter }) + else + ParsedList(listOf(expr)) +} + +fun parseAssignment(node: Assignment): ParsedAssignment? { + val op = node.children()[1] as? Delimiter ?: return null + if (op.type == PythonConstants.TokenType.ASSIGN) { + val targets = node.children().dropLast(1).filter { it !is Delimiter } + return SimpleAssign(targets, node.children().last()) + } + if (node.children().size != 3) + return null + return OpAssign(node.children()[0], op, node.children()[2]) +} + +fun parseDotName(node: DotName): ParsedDotName = + ParsedDotName(node.children()[0], node.children()[2]) + +fun parseFunctionCall(node: FunctionCall): ParsedFunctionCall? { + val function = node.children()[0] + val args = (node.children()[1] as? InvocationArguments ?: return null).children().filter { + if (it is Argument) // for now ignore function calls with different argument kinds + return null + it !is Delimiter + } + return ParsedFunctionCall(function, args) +} + +fun parseComparison(node: Comparison): ParsedComparison { + val primitives: MutableList = mutableListOf() + val children = node.children() + for (i in 0 until children.size - 2) { + if (children[i + 1] !is Delimiter) + continue + primitives.add(PrimitiveComparison(children[i], children[i + 1] as Delimiter, children[i + 2])) + } + return ParsedComparison(primitives) +} + +fun parseDecorators(decoratorNodes: Decorators?): List { + return decoratorNodes?.children()?.let { + val decoratorIndexes = + it.mapIndexedNotNull { index, node -> if (node is Delimiter && node.tokenType == PythonConstants.TokenType.AT) index + 1 else null } + decoratorIndexes.mapNotNull { index -> + val decorator = it[index] + when (decorator) { + is Name -> ParsedDecorator(decorator) + is FunctionCall -> { + val name = decorator.children().filterIsInstance().first() + val args = decorator.children().filterIsInstance().first() + ParsedDecorator(name, args) + } + else -> null + } + } + } ?: emptyList() +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/TypeUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/TypeUtils.kt new file mode 100644 index 0000000000..3fe668987a --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/TypeUtils.kt @@ -0,0 +1,17 @@ +package org.utbot.python.newtyping.ast + +import org.parsers.python.PythonConstants +import org.parsers.python.ast.NumericalLiteral +import org.utpython.types.PythonTypeHintsStorage +import org.utpython.types.general.UtType +import org.utpython.types.pythonAnyType + +fun typeOfNumericalLiteral(node: NumericalLiteral, storage: PythonTypeHintsStorage): UtType = + when (node.type) { + PythonConstants.TokenType.DECNUMBER, + PythonConstants.TokenType.HEXNUMBER, + PythonConstants.TokenType.OCTNUMBER -> storage.pythonInt + PythonConstants.TokenType.FLOAT -> storage.pythonFloat + PythonConstants.TokenType.COMPLEX -> storage.pythonComplex + else -> pythonAnyType + } \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/test.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/test.kt new file mode 100644 index 0000000000..4cf982e559 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/test.kt @@ -0,0 +1,18 @@ +package org.utbot.python.newtyping.ast + +import org.parsers.python.PythonParser +import org.utbot.python.PythonMethodHeader +import org.utbot.python.code.PythonCode + +/*fun main() { + val content = """ + class A: + @decorator + def func(x, y = 1, *args, **kwargs): + return 1 + """.trimIndent() + + val root = PythonParser(content).Module() + val y = PythonCode.findFunctionDefinition(root, PythonMethodHeader("func", "", null)) + val x = root +}*/ \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/Collector.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/Collector.kt new file mode 100644 index 0000000000..07f7ae3487 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/Collector.kt @@ -0,0 +1,9 @@ +package org.utbot.python.newtyping.ast.visitor + +import org.parsers.python.Node + +abstract class Collector { + open fun collectFromNodeBeforeRecursion(node: Node) = run { } + open fun collectFromNodeAfterRecursion(node: Node) = run { } + open fun finishCollection() = run { } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/Visitor.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/Visitor.kt new file mode 100644 index 0000000000..8261fe0941 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/Visitor.kt @@ -0,0 +1,29 @@ +package org.utbot.python.newtyping.ast.visitor + +import org.parsers.python.Node + +class Visitor(private val collectors: List) { + fun visit(node: Node) { + fixOffsets(node) // bug in parser? + innerVisit(node) + collectors.forEach { + it.finishCollection() + } + } + + private fun fixOffsets(node: Node) { + node.children().forEach { fixOffsets(it) } + if (node.children().isNotEmpty()) { + node.beginOffset = node.children().first().beginOffset + node.endOffset = node.children().last().endOffset + } + } + + private fun innerVisit(node: Node) { + collectors.forEach { it.collectFromNodeBeforeRecursion(node) } + node.children().forEach { + innerVisit(it) + } + collectors.forEach { it.collectFromNodeAfterRecursion(node) } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/constants/ConstantCollector.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/constants/ConstantCollector.kt new file mode 100644 index 0000000000..2bc3f65c35 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/constants/ConstantCollector.kt @@ -0,0 +1,85 @@ +package org.utbot.python.newtyping.ast.visitor.constants + +import org.parsers.python.Node +import org.parsers.python.PythonConstants +import org.parsers.python.ast.NumericalLiteral +import org.parsers.python.ast.SliceExpression +import org.parsers.python.ast.StringLiteral +import org.parsers.python.ast.UnaryExpression +import org.utpython.types.PythonTypeHintsStorage +import org.utbot.python.newtyping.ast.SimpleSlice +import org.utbot.python.newtyping.ast.TupleSlice +import org.utbot.python.newtyping.ast.parseSliceExpression +import org.utbot.python.newtyping.ast.typeOfNumericalLiteral +import org.utbot.python.newtyping.ast.visitor.Collector +import org.utpython.types.createPythonTupleType +import org.utpython.types.general.UtType +import java.math.BigDecimal +import java.math.BigInteger + +class ConstantCollector(private val storage: PythonTypeHintsStorage) : Collector() { + private val knownConstants = mutableMapOf, Pair>() + + private fun add(node: Node, type: UtType, value: Any) { + knownConstants[Pair(node.beginOffset, node.endOffset)] = Pair(type, value) + } + + private fun get(node: Node): Pair? { + return knownConstants[Pair(node.beginOffset, node.endOffset)] + } + + val result: List> + get() = knownConstants.map { it.value } + + override fun collectFromNodeAfterRecursion(node: Node) { + when (node) { + is NumericalLiteral -> processNumericalLiteral(node) + is StringLiteral -> processStringLiteral(node) + is SliceExpression -> processSliceExpression(node) + is UnaryExpression -> processUnaryExpression(node) + } + } + + private fun processNumericalLiteral(node: NumericalLiteral) { + val type = typeOfNumericalLiteral(node, storage) + val value = when (type) { + storage.pythonInt -> { + if (node.type == PythonConstants.TokenType.DECNUMBER) + node.toString().toBigInteger() + else return + } + storage.pythonFloat -> node.toString().toBigDecimal() + else -> return + } + add(node, type, value) + } + + private fun processStringLiteral(node: StringLiteral) { + if (node.toString().first() != 'f') + add(node, storage.pythonStr, node.toString()) + } + + private fun processSliceExpression(node: SliceExpression) { + val parsed = parseSliceExpression(node) ?: return + if (parsed.slices !is TupleSlice) + return + val elemNodes = parsed.slices.elems.map { it as? SimpleSlice ?: return } + val elems = elemNodes.map { get(it.indexValue) ?: return } + val type = createPythonTupleType(elems.map { it.first }) + knownConstants[Pair(elemNodes.first().indexValue.beginOffset, elemNodes.last().indexValue.endOffset)] = + Pair(type, elems) + } + + private fun processUnaryExpression(node: UnaryExpression) { + if (node.children().size != 2 || node.children().first().toString() != "-") + return + val child = get(node.children()[1]) ?: return + val value = child.second + if (value is BigInteger) { + add(node, storage.pythonInt, -value) + } + if (value is BigDecimal) { + add(node, storage.pythonFloat, -value) + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/hints/HintCollector.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/hints/HintCollector.kt new file mode 100644 index 0000000000..6dfaba253a --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/hints/HintCollector.kt @@ -0,0 +1,649 @@ +package org.utbot.python.newtyping.ast.visitor.hints + +import org.parsers.python.Node +import org.parsers.python.PythonConstants +import org.parsers.python.ast.AdditiveExpression +import org.parsers.python.ast.Argument +import org.parsers.python.ast.Assignment +import org.parsers.python.ast.Block +import org.parsers.python.ast.Comparison +import org.parsers.python.ast.Conjunction +import org.parsers.python.ast.DedentToken +import org.parsers.python.ast.Delimiter +import org.parsers.python.ast.Disjunction +import org.parsers.python.ast.DotName +import org.parsers.python.ast.ForStatement +import org.parsers.python.ast.FunctionCall +import org.parsers.python.ast.Group +import org.parsers.python.ast.IfStatement +import org.parsers.python.ast.IndentToken +import org.parsers.python.ast.Inversion +import org.parsers.python.ast.InvocationArguments +import org.parsers.python.ast.Keyword +import org.parsers.python.ast.MultiplicativeExpression +import org.parsers.python.ast.Name +import org.parsers.python.ast.Newline +import org.parsers.python.ast.NumericalLiteral +import org.parsers.python.ast.Operator +import org.parsers.python.ast.ReturnStatement +import org.parsers.python.ast.Slice +import org.parsers.python.ast.SliceExpression +import org.parsers.python.ast.Slices +import org.parsers.python.ast.Statement +import org.parsers.python.ast.StringLiteral +import org.utbot.python.PythonMethod +import org.utpython.types.PythonCallableTypeDescription +import org.utpython.types.PythonOverloadTypeDescription +import org.utpython.types.PythonTypeHintsStorage +import org.utbot.python.newtyping.ast.OpAssign +import org.utbot.python.newtyping.ast.ParsedFunctionCall +import org.utbot.python.newtyping.ast.SimpleAssign +import org.utbot.python.newtyping.ast.SimpleForVariable +import org.utbot.python.newtyping.ast.SimpleSlice +import org.utbot.python.newtyping.ast.SlicedSlice +import org.utbot.python.newtyping.ast.TupleSlice +import org.utbot.python.newtyping.ast.isIdentification +import org.utbot.python.newtyping.ast.parseAdditiveExpression +import org.utbot.python.newtyping.ast.parseAssignment +import org.utbot.python.newtyping.ast.parseComparison +import org.utbot.python.newtyping.ast.parseConjunction +import org.utbot.python.newtyping.ast.parseDisjunction +import org.utbot.python.newtyping.ast.parseDotName +import org.utbot.python.newtyping.ast.parseForStatement +import org.utbot.python.newtyping.ast.parseFunctionCall +import org.utbot.python.newtyping.ast.parseGroup +import org.utbot.python.newtyping.ast.parseIfStatement +import org.utbot.python.newtyping.ast.parseInversion +import org.utbot.python.newtyping.ast.parseList +import org.utbot.python.newtyping.ast.parseMultiplicativeExpression +import org.utbot.python.newtyping.ast.parseSliceExpression +import org.utbot.python.newtyping.ast.signaturesAreCompatible +import org.utbot.python.newtyping.ast.typeOfNumericalLiteral +import org.utbot.python.newtyping.ast.visitor.Collector +import org.utpython.types.createBinaryProtocol +import org.utpython.types.createCallableProtocol +import org.utpython.types.createIterableWithCustomReturn +import org.utpython.types.createProtocolWithAttribute +import org.utpython.types.createPythonCallableType +import org.utpython.types.general.DefaultSubstitutionProvider +import org.utpython.types.general.FunctionTypeCreator +import org.utpython.types.general.UtType +import org.utpython.types.getPythonAttributeByName +import org.utpython.types.inference.TypeInferenceEdgeWithBound +import org.utpython.types.inference.addEdge +import org.utpython.types.mypy.GlobalNamesStorage +import org.utpython.types.pythonAnyType +import org.utpython.types.pythonDescription +import org.utpython.types.pythonNoneType +import org.utpython.types.supportsBoolProtocol +import java.util.* + +class HintCollector( + private val function: PythonMethod, + private val storage: PythonTypeHintsStorage, + private val mypyTypes: Map, UtType>, + private val globalNamesStorage: GlobalNamesStorage, + private val moduleOfSources: String +) : Collector() { + private val parameterToNode: Map = + (function.argumentsNames zip function.methodType.arguments).associate { + it.first to HintCollectorNode(it.second) + } + private val astNodeToHintCollectorNode: MutableMap = mutableMapOf() + private val identificationToNode: MutableMap> = mutableMapOf() + private val blockStack = Stack() + + init { + val argNames = function.argumentsNames + assert(argNames.all { it != "" }) + identificationToNode[null] = mutableMapOf() + argNames.forEach { + identificationToNode[null]!![it] = parameterToNode[it]!! + } + } + + lateinit var result: HintCollectorResult + override fun finishCollection() { + val allNodes: MutableSet = mutableSetOf() + (parameterToNode + astNodeToHintCollectorNode).forEach { + if (!allNodes.contains(it.value)) + collectAllNodes(it.value, allNodes) + } + result = HintCollectorResult(parameterToNode, function.methodType, allNodes) + } + + private fun collectAllNodes(cur: HintCollectorNode, visited: MutableSet) { + visited.add(cur) + cur.outgoingEdges.forEach { + val to = it.to + if (!visited.contains(to)) + collectAllNodes(to, visited) + } + } + + override fun collectFromNodeBeforeRecursion(node: Node) { + if (node is Block) { + blockStack.add(node) + identificationToNode[node] = mutableMapOf() + } + } + + override fun collectFromNodeAfterRecursion(node: Node) { + when (node) { + is FunctionCall -> processFunctionCall(node) + is Keyword -> processKeyword(node) + is NumericalLiteral -> processNumericalLiteral(node) + is StringLiteral -> processStringLiteral(node) + is Block -> processBlock(node) + is Name -> processName(node) + is ForStatement -> processForStatement(node) + is IfStatement -> processIfStatement(node) + is Conjunction -> processConjunction(node) + is Disjunction -> processDisjunction(node) + is Inversion -> processInversion(node) + is Group -> processGroup(node) + is AdditiveExpression -> processAdditiveExpression(node) + is MultiplicativeExpression -> processMultiplicativeExpression(node) + is Comparison -> processComparison(node) + is org.parsers.python.ast.List -> processList(node) + is Assignment -> processAssignment(node) + is DotName -> processDotName(node) + is SliceExpression -> processSliceExpression(node) + // TODO: UnaryExpression, Power, ShiftExpression, BitwiseAnd, BitwiseOr, BitwiseXor + // TODO: Set, Dict, comprehensions + is Newline, is IndentToken, is Delimiter, is Operator, is DedentToken, is ReturnStatement, + is Statement, is InvocationArguments, is Argument, is Slice, is Slices -> Unit + else -> astNodeToHintCollectorNode[node] = HintCollectorNode(pythonAnyType) + } + } + + private fun processFunctionCall(node: FunctionCall) { + val parsed = parseFunctionCall(node) + if (parsed == null) { + astNodeToHintCollectorNode[node] = HintCollectorNode(pythonAnyType) + return + } + if (processIsinstanceCall(parsed, node)) + return + val rawType = mypyTypes[parsed.function.beginOffset to parsed.function.endOffset] + val attr = rawType?.getPythonAttributeByName( + storage, + "__call__" + ) + val type = attr?.type + val typeDescription = type?.pythonDescription() + val callType = createPythonCallableType( + parsed.args.size, + List(parsed.args.size) { PythonCallableTypeDescription.ArgKind.ARG_POS }, + List(parsed.args.size) { "" } + ) { + FunctionTypeCreator.InitializationData(List(parsed.args.size) { pythonAnyType }, pythonAnyType) + } + when (typeDescription) { + is PythonCallableTypeDescription -> { + astNodeToHintCollectorNode[node] = + HintCollectorNode(typeDescription.castToCompatibleTypeApi(type).returnValue) + if (parsed.args.size != typeDescription.numberOfArguments) + return + (parsed.args zip typeDescription.castToCompatibleTypeApi(type).arguments).forEach { (argNode, bound) -> + astNodeToHintCollectorNode[argNode]!!.upperBounds.add(bound) + } + } + is PythonOverloadTypeDescription -> { + val hintNode = HintCollectorNode(pythonAnyType) + astNodeToHintCollectorNode[node] = hintNode + typeDescription.getAnnotationParameters(type).forEach { typeCandidate -> + val descr = typeCandidate.pythonDescription() as? PythonCallableTypeDescription ?: return@forEach + val typeCandidateFunctionType = descr.castToCompatibleTypeApi(typeCandidate) + if (!signaturesAreCompatible(typeCandidateFunctionType, callType, storage)) + return@forEach + (parsed.args zip typeCandidateFunctionType.arguments).forEach { (argNode, argType) -> + astNodeToHintCollectorNode[argNode]!!.upperBounds.add(argType) + } + hintNode.upperBounds.add(typeCandidateFunctionType.returnValue) + } + } + else -> { + astNodeToHintCollectorNode[node] = HintCollectorNode(pythonAnyType) + val funcNode = parsed.function + astNodeToHintCollectorNode[funcNode]!!.upperBounds.add( + createCallableProtocol(List(parsed.args.size) { pythonAnyType }, pythonAnyType) + ) + } + } + } + + private fun processIsinstanceCall(parsedFunctionCall: ParsedFunctionCall, node: FunctionCall): Boolean { + if (parsedFunctionCall.function !is Name || parsedFunctionCall.function.toString() != "isinstance" || + parsedFunctionCall.args.size != 2) + return false + + val typeAsString = parsedFunctionCall.args[1].toString() + val type = globalNamesStorage.resolveTypeName(moduleOfSources, typeAsString) ?: return false + + astNodeToHintCollectorNode[node] = HintCollectorNode(storage.pythonBool) + val objNode = astNodeToHintCollectorNode[parsedFunctionCall.args[0]]!! + objNode.lowerBounds.add(type) + return true + } + + private fun processKeyword(node: Keyword) { + val type = when (node.type) { + PythonConstants.TokenType.TRUE, PythonConstants.TokenType.FALSE -> storage.pythonBool + PythonConstants.TokenType.NONE -> pythonNoneType + PythonConstants.TokenType.FOR, PythonConstants.TokenType.IF, + PythonConstants.TokenType.ELSE, PythonConstants.TokenType.ELIF, + PythonConstants.TokenType.WHILE, PythonConstants.TokenType.IN, + PythonConstants.TokenType.RETURN, PythonConstants.TokenType.OR, PythonConstants.TokenType.AND, + PythonConstants.TokenType.NOT -> return + else -> pythonAnyType + } + astNodeToHintCollectorNode[node] = HintCollectorNode(type) + } + + private fun processNumericalLiteral(node: NumericalLiteral) { + val type = typeOfNumericalLiteral(node, storage) + astNodeToHintCollectorNode[node] = HintCollectorNode(type) + } + + private fun processStringLiteral(node: StringLiteral) { + astNodeToHintCollectorNode[node] = HintCollectorNode(storage.pythonStr) + } + + private fun processBlock(node: Block) { + blockStack.pop() + val prevBlock = if (blockStack.isEmpty()) null else blockStack.peek() + identificationToNode[node]!!.forEach { (id, hintNode) -> + val prevHintNode = identificationToNode[prevBlock]!![id] ?: HintCollectorNode(pythonAnyType) + identificationToNode[prevBlock]!![id] = prevHintNode + val edgeFromPrev = HintEdgeWithBound( + from = prevHintNode, + to = hintNode, + source = EdgeSource.Identification, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { upperType -> listOf(upperType) } + val edgeToPrev = HintEdgeWithBound( + from = hintNode, + to = prevHintNode, + source = EdgeSource.Identification, + boundType = TypeInferenceEdgeWithBound.BoundType.Lower + ) { lowerType -> listOf(lowerType) } + + addEdge(edgeFromPrev) + addEdge(edgeToPrev) + } + } + + private fun processName(node: Name) { + if (!isIdentification(node)) + return + val name = node.toString() + val block = blockStack.peek() + if (identificationToNode[block]!![name] == null) { + val type = mypyTypes[node.beginOffset to node.endOffset] ?: pythonAnyType + identificationToNode[block]!![name] = HintCollectorNode(type) + } + astNodeToHintCollectorNode[node] = identificationToNode[block]!![name]!! + } + + private fun processForStatement(node: ForStatement) { + val parsed = parseForStatement(node) ?: return + // TODO: case of TupleForVariable + val iterableNode = astNodeToHintCollectorNode[parsed.iterable]!! + if (parsed.forVariable !is SimpleForVariable) { + addProtocol(iterableNode, createIterableWithCustomReturn(pythonAnyType)) + return + } + val variableNode = astNodeToHintCollectorNode[parsed.forVariable.variable]!! + val edgeFromVariableToIterable = HintEdgeWithBound( + from = variableNode, + to = iterableNode, + source = EdgeSource.ForStatement, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { varType -> listOf(createIterableWithCustomReturn(varType)) } + val edgeFromIterableToVariable = HintEdgeWithBound( + from = iterableNode, + to = variableNode, + source = EdgeSource.ForStatement, + boundType = TypeInferenceEdgeWithBound.BoundType.Lower + ) { iterType -> + val iterReturnType = iterType.getPythonAttributeByName(storage, "__iter__")?.type + ?: return@HintEdgeWithBound emptyList() + listOf(iterReturnType) + } + + addEdge(edgeFromVariableToIterable) + addEdge(edgeFromIterableToVariable) + } + + private fun processIfStatement(node: IfStatement) { + val parsed = parseIfStatement(node) + addProtocol(astNodeToHintCollectorNode[parsed.condition]!!, supportsBoolProtocol(storage)) + } + + private fun processConjunction(node: Conjunction) { + processBoolExpression(node) + val parsed = parseConjunction(node) + addProtocol(astNodeToHintCollectorNode[parsed.left]!!, supportsBoolProtocol(storage)) + addProtocol(astNodeToHintCollectorNode[parsed.right]!!, supportsBoolProtocol(storage)) + } + + private fun processDisjunction(node: Disjunction) { + processBoolExpression(node) + val parsed = parseDisjunction(node) + addProtocol(astNodeToHintCollectorNode[parsed.left]!!, supportsBoolProtocol(storage)) + addProtocol(astNodeToHintCollectorNode[parsed.right]!!, supportsBoolProtocol(storage)) + } + + private fun processInversion(node: Inversion) { + processBoolExpression(node) + val parsed = parseInversion(node) + addProtocol(astNodeToHintCollectorNode[parsed.expr]!!, supportsBoolProtocol(storage)) + } + + private fun processGroup(node: Group) { + val parsed = parseGroup(node) + val exprNode = astNodeToHintCollectorNode[parsed.expr]!! + astNodeToHintCollectorNode[node] = exprNode + } + + private fun processAdditiveExpression(node: AdditiveExpression) { + val curNode = HintCollectorNode(pythonAnyType) + astNodeToHintCollectorNode[node] = curNode + val parsed = parseAdditiveExpression(node) ?: return + processBinaryExpression( + curNode, + parsed.left, + parsed.right, + getOperationOfOperator(parsed.op.toString()) ?: return + ) + } + + private fun processMultiplicativeExpression(node: MultiplicativeExpression) { + val curNode = HintCollectorNode(pythonAnyType) + astNodeToHintCollectorNode[node] = curNode + val parsed = parseMultiplicativeExpression(node) + parsed.cases.forEach { + processBinaryExpression( + curNode, + it.left, + it.right, + getOperationOfOperator(it.op.toString()) ?: return, + source = EdgeSource.Multiplication + ) + } + } + + private fun processComparison(node: Comparison) { + val parsed = parseComparison(node) + parsed.cases.forEach { (left, op, right) -> + val comp = getComparison(op.toString()) ?: return@forEach + val leftNode = astNodeToHintCollectorNode[left]!! + val rightNode = astNodeToHintCollectorNode[right]!! + + val edgeFromLeftToRight = HintEdgeWithBound( + from = leftNode, + to = rightNode, + source = EdgeSource.Comparison, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { rightType -> listOf(createBinaryProtocol(comp.method, rightType, storage.pythonBool)) } + val edgeFromRightToLeft = HintEdgeWithBound( + from = rightNode, + to = leftNode, + source = EdgeSource.Comparison, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { leftType -> listOf(createBinaryProtocol(reverseComparison(comp).method, leftType, storage.pythonBool)) } + + addEdge(edgeFromLeftToRight) + addEdge(edgeFromRightToLeft) + } + processBoolExpression(node) + } + + private fun processList(node: org.parsers.python.ast.List) { + val parsed = parseList(node) + val partialType = DefaultSubstitutionProvider.substituteByIndex(storage.pythonList, 0, pythonAnyType) + val curNode = HintCollectorNode(partialType) + astNodeToHintCollectorNode[node] = curNode + val innerTypeNode = HintCollectorNode(pythonAnyType) + val edgeFromInnerToCur = HintEdgeWithValue( + from = innerTypeNode, + to = curNode, + annotationParameterId = 0 + ) + addEdge(edgeFromInnerToCur) + + parsed.elems.forEach { elem -> + val elemNode = astNodeToHintCollectorNode[elem]!! + val edge = HintEdgeWithBound( + from = elemNode, + to = innerTypeNode, + source = EdgeSource.CollectionElement, + boundType = TypeInferenceEdgeWithBound.BoundType.Lower + ) { listOf(it) } + addEdge(edge) + } + } + + private fun processAssignment(node: Assignment) { + val parsed = parseAssignment(node) ?: return + when (parsed) { + is SimpleAssign -> { + val targetNodes = parsed.targets.map { astNodeToHintCollectorNode[it]!! } + val valueNode = astNodeToHintCollectorNode[parsed.value]!! + targetNodes.forEach { target -> + val edgeFromValue = HintEdgeWithBound( + from = valueNode, + to = target, + source = EdgeSource.Assign, + boundType = TypeInferenceEdgeWithBound.BoundType.Lower + ) { listOf(it) } + val edgeFromTarget = HintEdgeWithBound( + from = target, + to = valueNode, + source = EdgeSource.Assign, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { listOf(it) } + addEdge(edgeFromValue) + addEdge(edgeFromTarget) + } + } + is OpAssign -> { + val targetNode = astNodeToHintCollectorNode[parsed.target]!! + val op = getOperationOfOpAssign(parsed.op.toString()) ?: return + val nodeForOpResult = HintCollectorNode(pythonAnyType) + processBinaryExpression(nodeForOpResult, parsed.target, parsed.value, op) + + val edgeToTarget = HintEdgeWithBound( + from = nodeForOpResult, + to = targetNode, + source = EdgeSource.OpAssign, + boundType = TypeInferenceEdgeWithBound.BoundType.Lower + ) { listOf(it) } + val edgeFromTarget = HintEdgeWithBound( + from = targetNode, + to = nodeForOpResult, + source = EdgeSource.OpAssign, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { listOf(it) } + + addEdge(edgeToTarget) + addEdge(edgeFromTarget) + } + } + } + + private fun processDotName(node: DotName) { + val parsed = parseDotName(node) + val type = mypyTypes[node.beginOffset to node.endOffset] ?: pythonAnyType + val curNode = HintCollectorNode(type) + astNodeToHintCollectorNode[node] = curNode + val headNode = astNodeToHintCollectorNode[parsed.head]!! + val attribute = parsed.tail.toString() + // addProtocol(headNode, createProtocolWithAttribute(attribute, pythonAnyType)) + + val edgeFromHead = HintEdgeWithBound( + from = headNode, + to = curNode, + source = EdgeSource.Attribute, + boundType = TypeInferenceEdgeWithBound.BoundType.Lower + ) { headType -> + headType.getPythonAttributeByName(storage, attribute)?.let { listOf(it.type) } ?: emptyList() + } + val edgeToHead = HintEdgeWithBound( + from = curNode, + to = headNode, + source = EdgeSource.Attribute, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { curType -> + listOf(createProtocolWithAttribute(attribute, curType)) + } + addEdge(edgeFromHead) + addEdge(edgeToHead) + } + + private fun processSliceExpression(node: SliceExpression) { + val curNode = HintCollectorNode(pythonAnyType) + astNodeToHintCollectorNode[node] = curNode + val parsed = parseSliceExpression(node) ?: return + val headNode = astNodeToHintCollectorNode[parsed.head]!! + when (parsed.slices) { + is SimpleSlice -> { + val indexNode = astNodeToHintCollectorNode[parsed.slices.indexValue]!! + val edgeFromIndexToHead = HintEdgeWithBound( + from = indexNode, + to = headNode, + source = EdgeSource.Slice, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { indexType -> + listOf(createBinaryProtocol("__getitem__", indexType, pythonAnyType)) + } + val edgeFromHeadToIndex = HintEdgeWithBound( + from = headNode, + to = indexNode, + source = EdgeSource.Slice, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { headType -> + val attr = headType.getPythonAttributeByName(storage, "__getitem__") + ?: return@HintEdgeWithBound emptyList() + // TODO: case of Overload + val descr = attr.type.pythonDescription() as? PythonCallableTypeDescription + ?: return@HintEdgeWithBound emptyList() + if (descr.numberOfArguments != 2) + return@HintEdgeWithBound emptyList() + listOf(attr.type.parameters[1]) + } + addEdge(edgeFromIndexToHead) + addEdge(edgeFromHeadToIndex) + } + is SlicedSlice -> { + addProtocol(headNode, createBinaryProtocol("__getitem__", storage.pythonSlice, pythonAnyType)) + // not necessary, but usually. TODO: remove if it spoils anything + for (slicePart in listOf(parsed.slices.start, parsed.slices.end, parsed.slices.step)) { + if (slicePart == null) + continue + val sliceNode = astNodeToHintCollectorNode[slicePart]!! + sliceNode.lowerBounds.add(storage.pythonInt) + } + } + is TupleSlice -> { + addProtocol(headNode, createBinaryProtocol("__getitem__", storage.tupleOfAny, pythonAnyType)) + } + } + val edgeFromCurToHead = HintEdgeWithBound( + from = curNode, + to = headNode, + source = EdgeSource.Slice, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { curType -> + listOf(createBinaryProtocol("__getitem__", pythonAnyType, curType)) + } + val edgeFromHeadToCur = HintEdgeWithBound( + from = headNode, + to = curNode, + source = EdgeSource.Slice, + boundType = TypeInferenceEdgeWithBound.BoundType.Lower + ) { headType -> + val attr = headType.getPythonAttributeByName(storage, "__getitem__") + ?: return@HintEdgeWithBound emptyList() + // TODO: case of Overload + val descr = attr.type.pythonDescription() as? PythonCallableTypeDescription + ?: return@HintEdgeWithBound emptyList() + listOf(descr.castToCompatibleTypeApi(attr.type).returnValue) + } + addEdge(edgeFromCurToHead) + addEdge(edgeFromHeadToCur) + } + + private fun processBoolExpression(node: Node) { + val hintCollectorNode = HintCollectorNode(storage.pythonBool) + astNodeToHintCollectorNode[node] = hintCollectorNode + } + + private fun addProtocol(node: HintCollectorNode, protocol: UtType) { + node.upperBounds.add(protocol) + } + + private fun processBinaryExpression( + curNode: HintCollectorNode, + left: Node, + right: Node, + op: Operation, + source: EdgeSource = EdgeSource.Operation + ) { + val leftNode = astNodeToHintCollectorNode[left]!! + val rightNode = astNodeToHintCollectorNode[right]!! + val methodName = op.method + + val edgeFromLeftToCur = HintEdgeWithBound( + from = leftNode, + to = curNode, + source = source, + boundType = TypeInferenceEdgeWithBound.BoundType.Lower + ) { leftType -> + listOf( + (leftType.getPythonAttributeByName(storage, methodName) + ?: return@HintEdgeWithBound emptyList()).type + ) + } + val edgeFromRightToCur = HintEdgeWithBound( + from = rightNode, + to = curNode, + source = source, + boundType = TypeInferenceEdgeWithBound.BoundType.Lower + ) { rightType -> + listOf( + (rightType.getPythonAttributeByName(storage, methodName) + ?: return@HintEdgeWithBound emptyList()).type + ) + } + addEdge(edgeFromLeftToCur) + addEdge(edgeFromRightToCur) + + val edgeFromLeftToRight = HintEdgeWithBound( + from = leftNode, + to = rightNode, + source = source, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { leftType -> listOf(createBinaryProtocol(methodName, leftType, pythonAnyType)) } + val edgeFromRightToLeft = HintEdgeWithBound( + from = rightNode, + to = leftNode, + source = source, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { rightType -> listOf(createBinaryProtocol(methodName, rightType, pythonAnyType)) } + addEdge(edgeFromLeftToRight) + addEdge(edgeFromRightToLeft) + + listOf(leftNode, rightNode).forEach { + val edgeFromCurToUp = HintEdgeWithBound( + from = curNode, + to = it, + source = source, + boundType = TypeInferenceEdgeWithBound.BoundType.Upper + ) { curType -> listOf(createBinaryProtocol(methodName, pythonAnyType, curType)) } + addEdge(edgeFromCurToUp) + } + + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/hints/HintCollectorStructures.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/hints/HintCollectorStructures.kt new file mode 100644 index 0000000000..f7fa189a3d --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/hints/HintCollectorStructures.kt @@ -0,0 +1,53 @@ +package org.utbot.python.newtyping.ast.visitor.hints + +import org.utpython.types.general.FunctionType +import org.utpython.types.general.UtType +import org.utpython.types.inference.TypeInferenceEdge +import org.utpython.types.inference.TypeInferenceEdgeWithBound +import org.utpython.types.inference.TypeInferenceEdgeWithValue +import org.utpython.types.inference.TypeInferenceNode + +sealed class HintEdge( + override val from: HintCollectorNode, + override val to: HintCollectorNode, +): TypeInferenceEdge + +class HintEdgeWithBound( + from: HintCollectorNode, + to: HintCollectorNode, + val source: EdgeSource, + override val boundType: TypeInferenceEdgeWithBound.BoundType, + override val dependency: (UtType) -> List +): HintEdge(from, to), TypeInferenceEdgeWithBound + +class HintEdgeWithValue( + from: HintCollectorNode, + to: HintCollectorNode, + override val annotationParameterId: Int +): HintEdge(from, to), TypeInferenceEdgeWithValue + +class HintCollectorNode(override val partialType: UtType): TypeInferenceNode { + val lowerBounds: MutableList = mutableListOf() + val upperBounds: MutableList = mutableListOf() + override val outgoingEdges: MutableSet = mutableSetOf() + override val ingoingEdges: MutableSet = mutableSetOf() +} + +class HintCollectorResult( + val parameterToNode: Map, + val initialSignature: FunctionType, + val allNodes: Set +) + +enum class EdgeSource { + ForStatement, + Identification, + Operation, + Multiplication, + CollectionElement, + Assign, + OpAssign, + Attribute, + Comparison, + Slice +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/hints/Protocols.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/hints/Protocols.kt new file mode 100644 index 0000000000..2c2e888487 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/ast/visitor/hints/Protocols.kt @@ -0,0 +1,63 @@ +package org.utbot.python.newtyping.ast.visitor.hints + +enum class Operation(val method: String) { + Add("__add__"), + Sub("__sub__"), + Mul("__mul__"), + TrueDiv("__truediv__"), + FloorDiv("__floordiv__"), + Mod("__mod__"), + Pow("__pow__"), + MatMul("__matmul__"), + And("__and__") +} + +fun getOperationOfOperator(op: String): Operation? = + when (op) { + "+" -> Operation.Add + "-" -> Operation.Sub + "*" -> Operation.Mul + "/" -> Operation.TrueDiv + "//" -> Operation.FloorDiv + "%" -> Operation.Mod + "**" -> Operation.Pow + "@" -> Operation.MatMul + "&" -> Operation.And + else -> null + } + +enum class ComparisonSign(val method: String) { + Lt("__lt__"), + Le("__le__"), + Eq("__eq__"), + Ne("__ne__"), + Gt("__gt__"), + Ge("__ge__") +} + +fun getComparison(op: String): ComparisonSign? = + when (op) { + "<" -> ComparisonSign.Lt + "<=" -> ComparisonSign.Le + "==" -> ComparisonSign.Eq + "!=" -> ComparisonSign.Ne + ">" -> ComparisonSign.Gt + ">=" -> ComparisonSign.Ge + else -> null + } + +fun reverseComparison(comp: ComparisonSign): ComparisonSign = + when (comp) { + ComparisonSign.Lt -> ComparisonSign.Gt + ComparisonSign.Le -> ComparisonSign.Ge + ComparisonSign.Eq -> ComparisonSign.Eq + ComparisonSign.Ne -> ComparisonSign.Ne + ComparisonSign.Gt -> ComparisonSign.Lt + ComparisonSign.Ge -> ComparisonSign.Le + } + +fun getOperationOfOpAssign(op: String): Operation? { + if (op.last() != '=') + return null + return getOperationOfOperator(op.dropLast(1)) +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/HintCollectorResultUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/HintCollectorResultUtils.kt new file mode 100644 index 0000000000..e6ff936f70 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/HintCollectorResultUtils.kt @@ -0,0 +1,46 @@ +package org.utbot.python.newtyping.inference + +import org.utbot.python.newtyping.ast.visitor.hints.HintCollectorNode +import org.utbot.python.newtyping.ast.visitor.hints.HintEdge +import org.utbot.python.newtyping.ast.visitor.hints.HintEdgeWithBound +import org.utpython.types.general.UtType +import org.utpython.types.inference.TypeInferenceEdgeWithBound +import org.utpython.types.pythonAnyType + +fun visitNodesByReverseEdges( + node: HintCollectorNode, + visited: MutableSet, + edgePredicate: (HintEdge) -> Boolean +) { + visited.add(node) + node.ingoingEdges.forEach { edge -> + if (edgePredicate(edge) && !visited.contains(edge.from)) + visitNodesByReverseEdges(edge.from, visited, edgePredicate) + } +} + +fun collectBoundsFromEdges(node: HintCollectorNode): Pair, List> { + val lowerBounds: MutableList = mutableListOf() + val upperBounds: MutableList = mutableListOf() + node.ingoingEdges.forEach { edge -> + if (edge !is HintEdgeWithBound) + return@forEach + val hints = edge.dependency(pythonAnyType) + when (edge.boundType) { + TypeInferenceEdgeWithBound.BoundType.Lower -> lowerBounds.addAll(hints) + TypeInferenceEdgeWithBound.BoundType.Upper -> upperBounds.addAll(hints) + } + } + return Pair(lowerBounds, upperBounds) +} + +fun collectBoundsFromComponent(component: Collection): Pair, List> { + val lowerBounds: MutableList = mutableListOf() + val upperBounds: MutableList = mutableListOf() + component.forEach { visitedNode -> + val (lowerFromEdges, upperFromEdges) = collectBoundsFromEdges(visitedNode) + lowerBounds.addAll(visitedNode.lowerBounds + lowerFromEdges + visitedNode.partialType) + upperBounds.addAll(visitedNode.upperBounds + upperFromEdges + visitedNode.partialType) + } + return Pair(lowerBounds, upperBounds) +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/TypeInferenceAlgorithmAPI.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/TypeInferenceAlgorithmAPI.kt new file mode 100644 index 0000000000..6ed0686157 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/TypeInferenceAlgorithmAPI.kt @@ -0,0 +1,15 @@ +package org.utbot.python.newtyping.inference + +import org.utpython.types.general.UtType + +abstract class TypeInferenceAlgorithm { + abstract suspend fun run( + isCancelled: () -> Boolean, + annotationHandler: suspend (UtType) -> InferredTypeFeedback, + ): Int +} + +sealed interface InferredTypeFeedback + +object SuccessFeedback : InferredTypeFeedback +object InvalidTypeFeedback : InferredTypeFeedback \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/TypeInferenceProcessor.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/TypeInferenceProcessor.kt new file mode 100644 index 0000000000..24bcfd0b12 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/TypeInferenceProcessor.kt @@ -0,0 +1,187 @@ +package org.utbot.python.newtyping.inference + +import kotlinx.coroutines.runBlocking +import org.parsers.python.PythonParser +import org.parsers.python.ast.ClassDefinition +import org.parsers.python.ast.FunctionDefinition +import org.utbot.python.PythonBaseMethod +import org.utpython.types.PythonFunctionDefinition +import org.utpython.types.PythonTypeHintsStorage +import org.utbot.python.newtyping.ast.parseClassDefinition +import org.utbot.python.newtyping.ast.parseFunctionDefinition +import org.utbot.python.newtyping.ast.visitor.Visitor +import org.utbot.python.newtyping.ast.visitor.hints.HintCollector +import org.utpython.types.general.CompositeType +import org.utpython.types.general.UtType +import org.utpython.types.getPythonAttributeByName +import org.utbot.python.newtyping.inference.baseline.BaselineAlgorithm +import org.utbot.python.newtyping.inference.baseline.MethodAndVars +import org.utbot.python.newtyping.mypy.dropInitFile +import org.utpython.types.mypy.GlobalNamesStorage +import org.utpython.types.mypy.MypyBuildDirectory +import org.utpython.types.mypy.MypyInfoBuild +import org.utpython.types.mypy.getErrorNumber +import org.utpython.types.mypy.readMypyAnnotationStorageAndInitialErrors +import org.utpython.types.pythonTypeRepresentation +import org.utpython.types.utils.getOffsetLine +import org.utbot.python.utils.Cleaner +import org.utbot.python.utils.Fail +import org.utbot.python.utils.Optional +import org.utbot.python.utils.RequirementsUtils +import org.utbot.python.utils.Success +import org.utbot.python.utils.TemporaryFileManager +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths + +class TypeInferenceProcessor( + private val pythonPath: String, + private val directoriesForSysPath: Set, + sourceFile: String, + private val moduleOfSourceFile: String, + private val functionName: String, + private val className: String? = null +) { + + private val path: Path = Paths.get(File(sourceFile).canonicalPath) + private val sourceFileContent = File(sourceFile).readText() + private val parsedFile = PythonParser(sourceFileContent).Module() + + fun inferTypes( + cancel: () -> Boolean, + processSignature: (UtType) -> Unit = {}, + checkRequirementsAction: () -> Unit = {}, + missingRequirementsAction: () -> Unit = {}, + loadingInfoAboutTypesAction: () -> Unit = {}, + analyzingCodeAction: () -> Unit = {}, + pythonMethodExtractionFailAction: (String) -> Unit = {}, + startingTypeInferenceAction: () -> Unit = {} + ): List { + Cleaner.restart() + try { + checkRequirementsAction() + + if (!RequirementsUtils.requirementsAreInstalled(pythonPath)) { + missingRequirementsAction() + return emptyList() + } + + loadingInfoAboutTypesAction() + + val (mypyBuild, report) = readMypyAnnotationStorageAndInitialErrors( + pythonPath, + path.toString(), + moduleOfSourceFile.dropInitFile(), + MypyBuildDirectory( + TemporaryFileManager.assignTemporaryFile(tag = "mypyBuildRoot"), + directoriesForSysPath + ) + ) + + val namesInModule = mypyBuild.names[moduleOfSourceFile]!!.map { it.name }.filter { + it.length < 4 || !it.startsWith("__") || !it.endsWith("__") + } + + analyzingCodeAction() + + val typeStorage = PythonTypeHintsStorage.get(mypyBuild) + val pythonMethodOpt = getPythonMethod(mypyBuild, typeStorage) + if (pythonMethodOpt is Fail) { + pythonMethodExtractionFailAction(pythonMethodOpt.message) + return emptyList() + } + + val pythonMethod = (pythonMethodOpt as Success).value + + val mypyExpressionTypes = mypyBuild.exprTypes[moduleOfSourceFile]!!.associate { + Pair(it.startOffset.toInt(), it.endOffset.toInt() + 1) to it.type.asUtBotType + } + val namesStorage = GlobalNamesStorage(mypyBuild) + val collector = + HintCollector(pythonMethod, typeStorage, mypyExpressionTypes, namesStorage, moduleOfSourceFile) + val visitor = Visitor(listOf(collector)) + visitor.visit(pythonMethod.ast) + + val algo = BaselineAlgorithm( + typeStorage, + collector.result, + pythonPath, + MethodAndVars(pythonMethod, ""), + emptyList(), + directoriesForSysPath, + moduleOfSourceFile, + namesInModule, + getErrorNumber( + report, + path.toString(), + getOffsetLine(sourceFileContent, pythonMethod.ast.beginOffset), + getOffsetLine(sourceFileContent, pythonMethod.ast.endOffset) + ), + mypyBuild.buildRoot.configFile, + dMypyTimeout = null + ) + + startingTypeInferenceAction() + val annotations = emptyList().toMutableList() + runBlocking { + algo.run(cancel) { + annotations.add(it) + processSignature(it) + SuccessFeedback + } + } + return annotations + } finally { + Cleaner.doCleaning() + } + } + + private fun getPythonMethod(mypyInfoBuild: MypyInfoBuild, typeStorage: PythonTypeHintsStorage): Optional { + if (className == null) { + val funcDef = parsedFile.children().firstNotNullOfOrNull { node -> + val res = (node as? FunctionDefinition)?.let { parseFunctionDefinition(it) } + if (res?.name?.toString() == functionName) res else null + } ?: return Fail("Couldn't find top-level function $functionName") + + val def = + mypyInfoBuild.definitions[moduleOfSourceFile]!![functionName]!!.getUtBotDefinition() as? PythonFunctionDefinition + ?: return Fail("$functionName is not a function") + + val result = PythonBaseMethod( + functionName, + path.toString(), + null, + sourceFileContent.substring(funcDef.body.beginOffset, funcDef.body.endOffset).trimIndent(), + def, + funcDef.body + ) + return Success(result) + } + val classDef = parsedFile.children().firstNotNullOfOrNull { node -> + val res = (node as? ClassDefinition)?.let { parseClassDefinition(it) } + if (res?.name?.toString() == className) res else null + } ?: return Fail("Couldn't find top-level class $className") + val funcDef = classDef.body.children().firstNotNullOfOrNull { node -> + val res = (node as? FunctionDefinition)?.let { parseFunctionDefinition(it) } + if (res?.name?.toString() == functionName) res else null + } ?: return Fail("Couldn't find method $functionName in class $className") + + val typeOfClass = mypyInfoBuild.definitions[moduleOfSourceFile]!![className]!!.getUtBotType() + as? CompositeType ?: return Fail("$className is not a class") + + val defOfFunc = typeOfClass.getPythonAttributeByName(typeStorage, functionName) as? PythonFunctionDefinition + ?: return Fail("$functionName is not a function") + + println(defOfFunc.type.pythonTypeRepresentation()) + + val result = PythonBaseMethod( + functionName, + path.toString(), + typeOfClass, + sourceFileContent.substring(funcDef.body.beginOffset, funcDef.body.endOffset).trimIndent(), + defOfFunc, + funcDef.body + ) + return Success(result) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/BaselineAlgorithm.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/BaselineAlgorithm.kt new file mode 100644 index 0000000000..1c709c3150 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/BaselineAlgorithm.kt @@ -0,0 +1,269 @@ +package org.utbot.python.newtyping.inference.baseline + +import mu.KotlinLogging +import org.utbot.python.PythonMethod +import org.utpython.types.PythonTypeHintsStorage +import org.utbot.python.newtyping.ast.visitor.hints.EdgeSource +import org.utbot.python.newtyping.ast.visitor.hints.HintCollectorNode +import org.utbot.python.newtyping.ast.visitor.hints.HintCollectorResult +import org.utbot.python.newtyping.ast.visitor.hints.HintEdgeWithBound +import org.utpython.types.createPythonUnionType +import org.utpython.types.general.FunctionType +import org.utpython.types.general.UtType +import org.utbot.python.newtyping.inference.InferredTypeFeedback +import org.utbot.python.newtyping.inference.InvalidTypeFeedback +import org.utbot.python.newtyping.inference.SuccessFeedback +import org.utbot.python.newtyping.inference.TypeInferenceAlgorithm +import org.utpython.types.inference.addEdge +import org.utbot.python.newtyping.inference.collectBoundsFromComponent +import org.utbot.python.newtyping.inference.visitNodesByReverseEdges +import org.utbot.python.newtyping.mypy.checkSuggestedSignatureWithDMypy +import org.utpython.types.pythonTypeRepresentation +import org.utpython.types.typesAreEqual +import org.utbot.python.newtyping.utils.weightedRandom +import org.utbot.python.utils.TemporaryFileManager +import java.io.File +import kotlin.random.Random +import org.utpython.types.pythonTypeName + +private val EDGES_TO_LINK = listOf( + EdgeSource.Identification, + EdgeSource.Assign, + EdgeSource.OpAssign, + EdgeSource.Operation, + EdgeSource.Comparison +) + +private val logger = KotlinLogging.logger {} + +data class MethodAndVars( + val method: PythonMethod, + val additionalVars: String, +) + +class BaselineAlgorithm( + private val storage: PythonTypeHintsStorage, + hintCollectorResult: HintCollectorResult, + private val pythonPath: String, + private val pythonMethod: MethodAndVars, + methodModifications: List = emptyList(), + private val directoriesForSysPath: Set, + private val moduleToImport: String, + private val namesInModule: Collection, + private val initialErrorNumber: Int, + private val configFile: File, + private val randomTypeFrequency: Int = 0, + private val dMypyTimeout: Long? +) : TypeInferenceAlgorithm() { + private val random = Random(0) + + private val initialPythonMethod: PythonMethod = pythonMethod.method + + private val generalRating = createGeneralTypeRating(hintCollectorResult, storage) + private val initialStates = methodModifications.ifEmpty { listOf(pythonMethod) } .map { + getInitialState(hintCollectorResult, generalRating, it.method.argumentsNames, it.method.methodType, it.additionalVars) + } + private val initialState = initialStates.first() + private val states: MutableList = initialStates.toMutableList() + private val fileForMypyRuns = TemporaryFileManager.assignTemporaryFile(tag = "mypy.py") + private var iterationCounter = 0 + private var randomTypeCounter = 0 + + private val simpleTypes = simplestTypes(storage) + private val mixtureType = createPythonUnionType(simpleTypes) + + private val expandedStates: MutableMap> = mutableMapOf() + private val statistic: MutableMap = mutableMapOf() + + private val checkedSignatures: MutableSet = mutableSetOf() + + private fun getRandomType(): UtType? { // TODO: Remove random type when ndarray + val weights = states.map { 1.0 / (it.anyNodes.size * it.anyNodes.size + 1) } + val state = weightedRandom(states, weights, random) + val newState = expandState(state, storage, state.anyNodes.map { mixtureType }) + if (newState != null) { + logger.info("Random type: ${newState.signature.pythonTypeRepresentation()}") + expandedStates[newState.signature] = newState to state + return newState.signature + } + return null + } + + private fun getLaudedType(): UtType? { + if (statistic.isEmpty()) return null + val sum = statistic.values.sum() + val weights = statistic.values.map { it.toDouble() / sum } + val newType = weightedRandom(statistic.keys.toList(), weights, random) + logger.info("Lauded type: ${newType.pythonTypeRepresentation()}") + return newType + } + + fun expandState(): UtType? { + if (states.isEmpty()) return null + + logger.debug("State number: ${states.size}") + iterationCounter++ + + if (randomTypeFrequency > 0 && iterationCounter % randomTypeFrequency == 0) { + randomTypeCounter++ + if (randomTypeCounter % 2 == 0) { + val laudedType = getLaudedType() + if (laudedType != null) return laudedType + } + val isNDArray = pythonMethod.method.methodType.arguments.any{ it.pythonTypeName() == "numpy.ndarray" } + val randomType = if (!isNDArray) { getRandomType() } else { null } + if (randomType != null) return randomType + } + + val state = chooseState(states) + val newState = expandState(state, storage) + if (newState != null) { + logger.info("Checking new state ${newState.signature.pythonTypeRepresentation()}") + if (checkSignature(newState.signature as FunctionType, newState.additionalVars, fileForMypyRuns, configFile)) { + logger.debug("Found new state!") + expandedStates[newState.signature] = newState to state + return newState.signature + } + } else if (state.anyNodes.isEmpty()) { + if (state.signature in checkedSignatures) { + logger.debug("Good type ${state.signature.pythonTypeRepresentation()}") + return state.signature + } + logger.debug("Checking ${state.signature.pythonTypeRepresentation()}") + if (checkSignature(state.signature as FunctionType, state.additionalVars, fileForMypyRuns, configFile)) { + logger.debug("${state.signature.pythonTypeRepresentation()} is good") + checkedSignatures.add(state.signature) + return state.signature + } else { + states.remove(state) + } + } else { + logger.debug("Remove ${state.signature.pythonTypeRepresentation()} because of any nodes") + states.remove(state) + } + return expandState() + } + + fun feedbackState(signature: UtType, feedback: InferredTypeFeedback) { + val stateInfo = expandedStates[signature] + val lauded = statistic[signature] != 0 + if (stateInfo != null) { + val (newState, parent) = stateInfo + when { + feedback is SuccessFeedback || lauded -> { + states.add(newState) + parent.children += 1 + } + + feedback is InvalidTypeFeedback -> { + states.remove(newState) + } + } + expandedStates.remove(signature) + } else if (feedback is InvalidTypeFeedback && !lauded) { + initialStates.forEach { + if (typesAreEqual(signature, it.signature)) { + states.remove(it) + } + } + } + } + + + override suspend fun run( + isCancelled: () -> Boolean, + annotationHandler: suspend (UtType) -> InferredTypeFeedback, + ): Int { + run breaking@ { + while (states.isNotEmpty()) { + if (isCancelled()) + return@breaking + logger.debug("State number: ${states.size}") + iterationCounter++ + + if (randomTypeFrequency > 0 && iterationCounter % randomTypeFrequency == 0) { + val weights = states.map { 1.0 / (it.anyNodes.size * it.anyNodes.size + 1) } + val state = weightedRandom(states, weights, random) + val newState = expandState(state, storage, state.anyNodes.map { mixtureType }) + if (newState != null) { + logger.info("Random type: ${newState.signature.pythonTypeRepresentation()}") + annotationHandler(newState.signature) + } + } + + val state = chooseState(states) + val newState = expandState(state, storage) + if (newState != null) { + if (iterationCounter == 1) { + annotationHandler(initialState.signature) + } + logger.info("Checking ${newState.signature.pythonTypeRepresentation()}") + if (checkSignature(newState.signature as FunctionType, newState.additionalVars, fileForMypyRuns, configFile)) { + logger.debug("Found new state!") + when (annotationHandler(newState.signature)) { + SuccessFeedback -> { + states.add(newState) + state.children += 1 + } + InvalidTypeFeedback -> {} + } + } + } else { + states.remove(state) + } + } + } + return iterationCounter + } + + private fun checkSignature(signature: FunctionType, newAdditionalVars: String, fileForMypyRuns: File, configFile: File): Boolean { + val methodCopy = initialPythonMethod.makeCopyWithNewType(signature) + return checkSuggestedSignatureWithDMypy( + methodCopy, + directoriesForSysPath, + moduleToImport, + namesInModule, + fileForMypyRuns, + pythonPath, + configFile, + initialErrorNumber, + newAdditionalVars, + timeout = dMypyTimeout + ) + } + + private fun chooseState(states: List): BaselineAlgorithmState { + val weights = states.map { 1.0 / (it.children * it.children + 1) } + return weightedRandom(states, weights, random) + } + + private fun getInitialState( + hintCollectorResult: HintCollectorResult, + generalRating: List, + paramNames: List, + methodType: FunctionType, + additionalVars: String = "", + ): BaselineAlgorithmState { + val root = PartialTypeNode(methodType, true) + val allNodes: MutableSet = mutableSetOf(root) + val argumentRootNodes = paramNames.map { hintCollectorResult.parameterToNode[it]!! } + argumentRootNodes.forEachIndexed { index, node -> + val visited: MutableSet = mutableSetOf() + visitNodesByReverseEdges(node, visited) { it is HintEdgeWithBound && EDGES_TO_LINK.contains(it.source) } + val (lowerBounds, upperBounds) = collectBoundsFromComponent(visited) + val decomposed = decompose(node.partialType, lowerBounds, upperBounds, 1, storage) + allNodes.addAll(decomposed.nodes) + val edge = BaselineAlgorithmEdge( + from = decomposed.root, + to = root, + annotationParameterId = index + ) + addEdge(edge) + } + return BaselineAlgorithmState(allNodes, generalRating, storage, additionalVars) + } + + fun laudType(type: FunctionType) { + statistic[type] = statistic[type]?.plus(1) ?: 1 + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/StateExpansion.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/StateExpansion.kt new file mode 100644 index 0000000000..33f364152a --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/StateExpansion.kt @@ -0,0 +1,88 @@ +package org.utbot.python.newtyping.inference.baseline + +import org.utpython.types.PythonTypeHintsStorage +import org.utpython.types.general.UtType +import org.utpython.types.inference.addEdge +import org.utpython.types.pythonAnnotationParameters +import org.utpython.types.pythonDescription +import java.util.* + +fun expandState(state: BaselineAlgorithmState, typeStorage: PythonTypeHintsStorage): BaselineAlgorithmState? { + if (state.anyNodes.isEmpty()) + return null + val types = state.candidateGraph.getNext() ?: return null + return expandState(state, typeStorage, types) +} + +fun expandState(state: BaselineAlgorithmState, typeStorage: PythonTypeHintsStorage, types: List): BaselineAlgorithmState? { + if (types.isEmpty()) + return null + val substitution = (state.anyNodes zip types).associate { it } + return expandNodes(state, substitution, state.generalRating, typeStorage) +} + +private fun expandNodes( + state: BaselineAlgorithmState, + substitution: Map, + generalRating: List, + storage: PythonTypeHintsStorage +): BaselineAlgorithmState { + val (newAnyNodeMap, allNewNodes) = substitution.entries.fold( + Pair(emptyMap(), emptySet()) + ) { (map, allNodeSet), (node, newType) -> + val (newNodeForAny, additionalNodes) = + decompose(newType, node.lowerBounds, node.upperBounds, node.nestedLevel, storage) + Pair(map + (node to newNodeForAny), allNodeSet + additionalNodes) + } + val newNodeMap = expansionBFS(substitution, newAnyNodeMap).toMutableMap() + state.nodes.forEach { cur -> newNodeMap[cur] = newNodeMap[cur] ?: cur.copy() } + state.nodes.forEach { cur -> + val new = newNodeMap[cur]!! + cur.outgoingEdges.forEach { edge -> + val newEdge = BaselineAlgorithmEdge( + from = new, + to = newNodeMap[edge.to]!!, + annotationParameterId = edge.annotationParameterId + ) + addEdge(newEdge) + } + } + return BaselineAlgorithmState(newNodeMap.values.toSet() + allNewNodes, generalRating, storage, state.additionalVars) +} + +private fun expansionBFS( + substitution: Map, + nodeMap: Map +): Map { + val queue: Queue> = LinkedList(substitution.keys.map { Pair(it, 0) }) + val newParams: MutableMap> = mutableMapOf() + val newNodes: MutableMap = nodeMap.toMutableMap() + val lastModified: MutableMap = mutableMapOf() + var timer = 0 + while (!queue.isEmpty()) { + timer++ + val (oldNode, putTime) = queue.remove() + if ((lastModified[oldNode] ?: 0) != putTime) + continue + if (!newNodes.keys.contains(oldNode)) { + val oldType = oldNode.partialType + val newType = if (substitution.keys.contains(oldNode)) + substitution[oldNode]!! + else + oldType.pythonDescription().createTypeWithNewAnnotationParameters( + oldType, + newParams[oldNode] ?: oldType.pythonAnnotationParameters() + ) + newNodes[oldNode] = PartialTypeNode(newType, oldNode.isRoot) + } + val newType = newNodes[oldNode]!!.partialType + oldNode.outgoingEdges.forEach { edge -> + val params = newParams[edge.to] ?: edge.to.partialType.pythonAnnotationParameters().toMutableList() + params[edge.annotationParameterId] = newType + newParams[edge.to] = params + lastModified[edge.to] = timer + queue.add(Pair(edge.to, timer)) + } + } + return newNodes +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/Structures.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/Structures.kt new file mode 100644 index 0000000000..4a01e45a09 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/Structures.kt @@ -0,0 +1,42 @@ +package org.utbot.python.newtyping.inference.baseline + +import org.utpython.types.PythonTypeHintsStorage +import org.utpython.types.general.UtType +import org.utpython.types.inference.TypeInferenceEdgeWithValue +import org.utpython.types.inference.TypeInferenceNode +import org.utpython.types.pythonAnyType + +sealed class BaselineAlgorithmNode(val isRoot: Boolean) : TypeInferenceNode { + override val ingoingEdges: MutableList = mutableListOf() + override val outgoingEdges: MutableList = mutableListOf() + abstract fun copy(): BaselineAlgorithmNode +} + +class PartialTypeNode(override val partialType: UtType, isRoot: Boolean) : BaselineAlgorithmNode(isRoot) { + override fun copy(): BaselineAlgorithmNode = PartialTypeNode(partialType, isRoot) +} + +class AnyTypeNode(val lowerBounds: List, val upperBounds: List, val nestedLevel: Int) : + BaselineAlgorithmNode(false) { + override val partialType: UtType = pythonAnyType + override fun copy(): BaselineAlgorithmNode = AnyTypeNode(lowerBounds, upperBounds, nestedLevel) +} + +class BaselineAlgorithmEdge( + override val from: BaselineAlgorithmNode, + override val to: BaselineAlgorithmNode, + override val annotationParameterId: Int +) : TypeInferenceEdgeWithValue + +class BaselineAlgorithmState( + val nodes: Set, + val generalRating: List, + typeStorage: PythonTypeHintsStorage, + val additionalVars: String = "", +) { + val signature: UtType + get() = nodes.find { it.isRoot }!!.partialType + val anyNodes: List = nodes.mapNotNull { it as? AnyTypeNode } + val candidateGraph = CandidateGraph(anyNodes, generalRating, typeStorage) + var children: Int = 0 +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/TypeCandidateGeneration.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/TypeCandidateGeneration.kt new file mode 100644 index 0000000000..9bd132b357 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/TypeCandidateGeneration.kt @@ -0,0 +1,195 @@ +package org.utbot.python.newtyping.inference.baseline + +import mu.KotlinLogging +import org.utpython.types.* +import org.utbot.python.newtyping.ast.visitor.hints.HintCollectorResult +import org.utpython.types.general.DefaultSubstitutionProvider +import org.utpython.types.general.UtType +import org.utpython.types.general.getBoundedParameters +import org.utpython.types.general.getOrigin +import org.utbot.python.newtyping.inference.collectBoundsFromEdges +import org.utpython.types.PythonTypeHintsStorage + +private val logger = KotlinLogging.logger {} + +class TypeRating(scores: List>) { + val typesInOrder: List> = scores.map { Pair(it.second, it.first) }.sortedBy { -it.first } + val types: List = typesInOrder.map { it.second } +} + +data class TypeRatingPosition( + val typeRating: TypeRating, + val pos: Int +) { + val hasNext: Boolean + get() = pos < typeRating.types.size - 1 + val curPenalty: Double + get() = typeRating.typesInOrder.first().first - typeRating.typesInOrder[pos].first + val type: UtType + get() = typeRating.types[pos] + + fun makeMove(): TypeRatingPosition { + assert(hasNext) + return TypeRatingPosition(typeRating, pos + 1) + } +} + +// TODO: memory usage can be reduced +class CandidateGraph( + anyNodes: List, + initialRating: List, + storage: PythonTypeHintsStorage, +) { + private val typeRatings: List = + anyNodes.map { createTypeRating(initialRating, it.lowerBounds, it.upperBounds, storage, it.nestedLevel) } + private val vertices: MutableList = + mutableListOf(CandidateGraphVertex(typeRatings.map { TypeRatingPosition(it, 0) })) + private var lastUsed: Int = -1 + private var lastExpanded: Int = -1 + + fun getNext(): List? { + if (lastUsed < lastExpanded) { + lastUsed += 1 + return vertices[lastUsed].types + } + if (lastExpanded == vertices.size - 1) + return null + + lastExpanded += 1 + vertices[lastExpanded].generateSuccessors().forEach(::insertVertexIfNotAlready) + + return getNext() + } + + private fun insertVertexIfNotAlready(vertex: CandidateGraphVertex) { + for (i in 0 until vertices.size) { + if (vertices[i] == vertex) + return + if (vertices[i].penalty > vertex.penalty) { + vertices.add(i, vertex) + return + } + } + vertices.add(vertex) + } + + init { + logger.debug("Created new CandidateGraph") + } +} + +data class CandidateGraphVertex(val positions: List) { + val penalty by lazy { + positions.fold(0.0) { acc, typeRatingPosition -> acc + typeRatingPosition.curPenalty } + } + + fun generateSuccessors(): List = + positions.mapNotNull { pos -> + CandidateGraphVertex( + positions.map { + if (it == pos) { + if (!pos.hasNext) + return@mapNotNull null + pos.makeMove() + } else + it + } + ) + } + + val types by lazy { + positions.map { it.type } + } +} + +private const val MAX_NESTING = 3 + +private fun changeScores( + initialRating: List, + storage: PythonTypeHintsStorage, + bounds: List, + hintScores: MutableMap, + withPenalty: Boolean, + isUpper: Boolean +) { + bounds.forEach { constraint -> + val (fitting, notFitting) = initialRating.partition { typeFromList -> + val type = DefaultSubstitutionProvider.substitute( + typeFromList, + typeFromList.getBoundedParameters().associateWith { pythonAnyType } + ) + if (isUpper) + PythonSubtypeChecker.checkIfRightIsSubtypeOfLeft(constraint, type, storage) + else + PythonSubtypeChecker.checkIfRightIsSubtypeOfLeft(type, constraint, storage) + } + if (withPenalty) + notFitting.forEach { + val wrapper = PythonTypeWrapperForEqualityCheck(it) + hintScores[wrapper] = (hintScores[wrapper] ?: 0.0) - 1 + } + fitting.forEach { + val wrapper = PythonTypeWrapperForEqualityCheck(it) + hintScores[wrapper] = (hintScores[wrapper] ?: 0.0) + 1.0 / fitting.size + } + } +} + +fun createTypeRating( + initialRating: List, + lowerBounds: List, + upperBounds: List, + storage: PythonTypeHintsStorage, + level: Int, + withPenalty: Boolean = true +): TypeRating { + val hintScores = mutableMapOf() + changeScores(initialRating, storage, lowerBounds, hintScores, withPenalty, isUpper = false) + changeScores(initialRating, storage, upperBounds, hintScores, withPenalty, isUpper = true) + val scores: List> = initialRating.mapNotNull { typeFromList -> + if (level == MAX_NESTING && typeFromList.getBoundedParameters().isNotEmpty()) + return@mapNotNull null + val type = DefaultSubstitutionProvider.substitute( + typeFromList, + typeFromList.getBoundedParameters().associateWith { pythonAnyType } + ) + val wrapper = PythonTypeWrapperForEqualityCheck(type) + Pair(type, hintScores[wrapper] ?: 0.0) + } + return TypeRating(scores) +} + +fun simplestTypes(storage: PythonTypeHintsStorage): List { + val int = storage.pythonInt + val listOfAny = DefaultSubstitutionProvider.substituteAll(storage.pythonList, listOf(pythonAnyType)) + val str = storage.pythonStr + val bool = storage.pythonBool + val float = storage.pythonFloat + val dictOfAny = DefaultSubstitutionProvider.substituteAll(storage.pythonDict, listOf(pythonAnyType, pythonAnyType)) + return listOf(int, listOfAny, str, bool, float, dictOfAny) +} + +fun createGeneralTypeRating(hintCollectorResult: HintCollectorResult, storage: PythonTypeHintsStorage): List { + val allLowerBounds: MutableList = mutableListOf() + val allUpperBounds: MutableList = mutableListOf() + hintCollectorResult.allNodes.forEach { node -> + val (lowerFromEdges, upperFromEdges) = collectBoundsFromEdges(node) + allLowerBounds.addAll((lowerFromEdges + node.lowerBounds + node.partialType).filter { + !typesAreEqual(it, pythonAnyType) + }) + allUpperBounds.addAll((upperFromEdges + node.upperBounds + node.partialType).filter { + !typesAreEqual(it, pythonAnyType) + }) + } + val prefix = simplestTypes(storage) + val rating = createTypeRating( + storage.simpleTypes.filter { !prefix.any { type -> typesAreEqual(type.getOrigin(), it) } }, + allLowerBounds, + allUpperBounds, + storage, + 1, + withPenalty = false + ) + val prefixRating = createTypeRating(prefix, allLowerBounds, allUpperBounds, storage, 1, withPenalty = false) + return prefixRating.types + rating.types +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/TypeDecomposition.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/TypeDecomposition.kt new file mode 100644 index 0000000000..90e049ba7f --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/baseline/TypeDecomposition.kt @@ -0,0 +1,70 @@ +package org.utbot.python.newtyping.inference.baseline + +import org.utpython.types.* +import org.utpython.types.general.UtType +import org.utpython.types.inference.addEdge + +data class DecompositionResult( + val root: BaselineAlgorithmNode, + val nodes: Set +) + +fun decompose( + partialType: UtType, + lowerBounds: List, + upperBounds: List, + level: Int, + storage: PythonTypeHintsStorage +): DecompositionResult { + if (typesAreEqual(partialType, pythonAnyType)) { + val root = AnyTypeNode( + lowerBounds.filter { !typesAreEqual(it, pythonAnyType) }, + upperBounds.filter { !typesAreEqual(it, pythonAnyType) }, + level + ) + return DecompositionResult(root, setOf(root)) + } + val newNodes: MutableSet = mutableSetOf() + val root = PartialTypeNode(partialType, false) + newNodes.add(root) + val children = partialType.pythonAnnotationParameters() + val constraints: Map> = + List(children.size) { it }.associateWith { mutableListOf() } + lowerBounds.forEach { boundType -> + val cur = propagateConstraint(partialType, TypeConstraint(boundType, ConstraintKind.LowerBound), storage) + cur.forEach { constraints[it.key]!!.add(it.value) } + } + upperBounds.forEach { boundType -> + val cur = propagateConstraint(partialType, TypeConstraint(boundType, ConstraintKind.UpperBound), storage) + cur.forEach { constraints[it.key]!!.add(it.value) } + } + constraints.forEach { (index, constraintList) -> + val childLowerBounds: MutableList = mutableListOf() + val childUpperBounds: MutableList = mutableListOf() + constraintList.forEach { constraint -> + when (constraint.kind) { + ConstraintKind.LowerBound -> childLowerBounds.add(constraint.type) + ConstraintKind.UpperBound -> childUpperBounds.add(constraint.type) + ConstraintKind.BothSided -> { + childLowerBounds.add(constraint.type) + childUpperBounds.add(constraint.type) + } + } + } + val (childBaselineAlgorithmNode, nodes) = decompose( + children[index], + childLowerBounds, + childUpperBounds, + level + 1, + storage + ) + newNodes.addAll(nodes) + val edge = BaselineAlgorithmEdge( + from = childBaselineAlgorithmNode, + to = root, + annotationParameterId = index + ) + addEdge(edge) + } + return DecompositionResult(root, newNodes) +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/test.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/test.kt new file mode 100644 index 0000000000..83d9df08cd --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/inference/test.kt @@ -0,0 +1,16 @@ +package org.utbot.python.newtyping.inference + +import org.utpython.types.pythonTypeRepresentation + +fun main() { + TypeInferenceProcessor( + "python3.9", + directoriesForSysPath = setOf("/home/tochilinak/Documents/projects/utbot/UTBotJava/utbot-python/samples"), + "/home/tochilinak/Documents/projects/utbot/UTBotJava/utbot-python/samples/easy_samples/generics.py", + moduleOfSourceFile = "easy_samples.generics", + "set", + className = "LoggedVar" + ).inferTypes(cancel = { false }).forEach { + println(it.pythonTypeRepresentation()) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/RunDMypy.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/RunDMypy.kt new file mode 100644 index 0000000000..d9320d1364 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/RunDMypy.kt @@ -0,0 +1,59 @@ +package org.utbot.python.newtyping.mypy + +import mu.KotlinLogging +import org.utbot.python.PythonMethod +import org.utbot.python.code.PythonCodeGenerator.generateMypyCheckCode +import org.utbot.python.utils.TemporaryFileManager +import org.utpython.types.mypy.getErrorNumber +import org.utpython.types.mypy.getErrorsAndNotes +import org.utbot.python.utils.runCommand +import java.io.File + +private val logger = KotlinLogging.logger {} + +fun checkWithDMypy(pythonPath: String, fileWithCodePath: String, configFile: File, timeout: Long? = null): String? { + val result = runCommand( + listOf( + pythonPath, + "-m", + "mypy.dmypy", + "run", + "--", + fileWithCodePath, + "--config-file", + configFile.path + ), + timeout = timeout + ) + if (result.terminatedByTimeout) + return null + return result.stdout +} + +fun checkSuggestedSignatureWithDMypy( + method: PythonMethod, + directoriesForSysPath: Set, + moduleToImport: String, + namesInModule: Collection, + fileForMypyCode: File, + pythonPath: String, + configFile: File, + initialErrorNumber: Int, + additionalVars: String, + timeout: Long? = null +): Boolean { + val annotationMap = + (method.argumentsNames zip method.methodType.arguments).associate { + Pair(it.first, it.second) + } + val mypyCode = generateMypyCheckCode(method, annotationMap, directoriesForSysPath, moduleToImport, namesInModule, additionalVars) + // logger.debug(mypyCode) + TemporaryFileManager.writeToAssignedFile(fileForMypyCode, mypyCode) + val mypyOutput = checkWithDMypy(pythonPath, fileForMypyCode.canonicalPath, configFile, timeout = timeout) + ?: return true + val report = getErrorsAndNotes(mypyOutput) + val errorNumber = getErrorNumber(report, fileForMypyCode.canonicalPath, 0, mypyCode.length) + if (errorNumber > initialErrorNumber) + logger.debug(mypyOutput) + return errorNumber <= initialErrorNumber +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/Utils.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/Utils.kt new file mode 100644 index 0000000000..96ced37886 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/mypy/Utils.kt @@ -0,0 +1,11 @@ +package org.utbot.python.newtyping.mypy + +/** + * Remove .__init__ suffix if it exists. +It resolves problem with duplication module names in mypy, e.g. mod.__init__ and mod + */ +fun String.dropInitFile(): String { + return if (this.endsWith(".__init__")) { + this.dropLast(9) + } else this +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/newtyping/utils/Utils.kt b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/utils/Utils.kt new file mode 100644 index 0000000000..ce9da99af0 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/newtyping/utils/Utils.kt @@ -0,0 +1,10 @@ +package org.utbot.python.newtyping.utils + +import org.utbot.fuzzing.utils.chooseOne +import kotlin.random.Random + + +fun weightedRandom(elems: List, weights: List, random: Random): T { + val index = random.chooseOne(weights.toDoubleArray()) + return elems[index] +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/Cleaner.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/Cleaner.kt new file mode 100644 index 0000000000..cc29c36562 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/Cleaner.kt @@ -0,0 +1,22 @@ +package org.utbot.python.utils + +object Cleaner { + private var clean: () -> Unit = {} + + fun addFunction(f: () -> Unit) { + val oldClean = clean + val newClean = { + f() + oldClean() + } + clean = newClean + } + + fun restart() { + clean = {} + } + + fun doCleaning() { + clean() + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/FindCurrentPythonModule.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/FindCurrentPythonModule.kt new file mode 100644 index 0000000000..7852d3de36 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/FindCurrentPythonModule.kt @@ -0,0 +1,18 @@ +package org.utbot.python.utils + +import java.io.File + +fun findCurrentPythonModule( + directoriesForSysPath: Collection, + sourceFile: String +): Optional { + directoriesForSysPath.forEach { path -> + val module = getModuleName(path.toAbsolutePath(), sourceFile.toAbsolutePath()) + if (module != null) + return Success(module) + } + return Fail("Couldn't find path for $sourceFile in --sys-path option. Please, specify it.") +} + +fun String.toAbsolutePath(): String = + File(this).canonicalPath diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/Optional.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/Optional.kt new file mode 100644 index 0000000000..40e5330760 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/Optional.kt @@ -0,0 +1,25 @@ +package org.utbot.python.utils + +sealed class Optional +class Fail(val message: String) : Optional() +class Success(val value: A) : Optional() + +fun bind( + value: Optional, + f: (A) -> Optional +): Optional = + when (value) { + is Fail -> Fail(value.message) + is Success -> f(value.value) + } + +fun pack(vararg values: Optional): Optional> { + val result = mutableListOf() + for (elem in values) { + when (elem) { + is Fail -> return Fail(elem.message) + is Success -> result.add(elem.value) + } + } + return Success(result) +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/PriorityCartesianProduct.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/PriorityCartesianProduct.kt new file mode 100644 index 0000000000..5cd6ca501f --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/PriorityCartesianProduct.kt @@ -0,0 +1,41 @@ +package org.utbot.python.utils + +import java.lang.Integer.min + +class PriorityCartesianProduct(private val lists: List>) { + + private fun generateFixedSumRepresentation( + sum: Int, + index: Int = 0, + curRepr: List = emptyList() + ): Sequence> { + val itemNumber = lists.size + var result = emptySequence>() + if (index == itemNumber && sum == 0) { + return sequenceOf(curRepr) + } else if (index < itemNumber && sum >= 0) { + for (i in 0..min(sum, lists[index].size - 1)) { + result += generateFixedSumRepresentation( + sum - i, + index + 1, + curRepr + listOf(i) + ) + } + } + return result + } + + fun getSequence(): Sequence> { + var curSum = 0 + val maxSum = lists.fold(0) { acc, elem -> acc + elem.size } + val combinations = generateSequence { + if (curSum > maxSum) + null + else + generateFixedSumRepresentation(curSum++) + } + return combinations.flatten().map { combination: List -> + combination.mapIndexed { element, value -> lists[element][value] } + } + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/ProcessUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/ProcessUtils.kt new file mode 100644 index 0000000000..379cfc0f7d --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/ProcessUtils.kt @@ -0,0 +1,50 @@ +package org.utbot.python.utils + +import java.io.BufferedReader +import java.io.InputStreamReader +import java.util.concurrent.TimeUnit + +data class CmdResult( + val stdout: String, + val stderr: String, + val exitValue: Int, + val terminatedByTimeout: Boolean = false +) + +fun startProcess( + command: List, + environmentVariables: Map = emptyMap() +): Process { + val pb = ProcessBuilder(command) + val env = pb.environment() + env += environmentVariables + return pb.start() +} + +fun getResult(process: Process, timeout: Long? = null): CmdResult { + if (timeout != null) { + if (!process.waitFor(timeout, TimeUnit.MILLISECONDS)) { + process.destroy() + return CmdResult("", "", 1, terminatedByTimeout = true) + } + } + + val reader = BufferedReader(InputStreamReader(process.inputStream)) + var stdout = "" + var line: String? = "" + while (line != null) { + stdout += "$line\n" + line = reader.readLine() + } + + if (timeout == null) + process.waitFor() + + val stderr = process.errorStream.readBytes().decodeToString().trimIndent() + return CmdResult(stdout.trimIndent(), stderr, process.exitValue()) +} + +fun runCommand(command: List, timeout: Long? = null, environmentVariables: Map = emptyMap()): CmdResult { + val process = startProcess(command, environmentVariables) + return getResult(process, timeout) +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/PythonVersionChecker.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/PythonVersionChecker.kt new file mode 100644 index 0000000000..a0e81e7a07 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/PythonVersionChecker.kt @@ -0,0 +1,22 @@ +package org.utbot.python.utils + +object PythonVersionChecker { + private val minimalPythonVersion = Triple(3, 10, 0) + + fun checkPythonVersion(pythonPath: String): Boolean { + try { + val version = runCommand(listOf( + pythonPath, + "-c", + "\"import sys; print('.'.join(map(str, sys.version_info[:3])))\"" + )) + if (version.exitValue == 0) { + val (major, minor, patch) = version.stdout.split(".") + return (major.toInt() >= minimalPythonVersion.first && minor.toInt() >= minimalPythonVersion.second && patch.toInt() >= minimalPythonVersion.third) + } + return false + } catch (_: Exception) { + return false + } + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsInstaller.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsInstaller.kt new file mode 100644 index 0000000000..2aace97dec --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsInstaller.kt @@ -0,0 +1,18 @@ +package org.utbot.python.utils + +interface RequirementsInstaller { + fun checkRequirements(pythonPath: String, requirements: List): Boolean + fun installRequirements(pythonPath: String, requirements: List) + + companion object { + fun checkRequirements(requirementsInstaller: RequirementsInstaller, pythonPath: String, additionalRequirements: List): Boolean { + val requirements = RequirementsUtils.requirements + additionalRequirements + if (!requirementsInstaller.checkRequirements(pythonPath, requirements)) { + requirementsInstaller.installRequirements(pythonPath, requirements) + return requirementsInstaller.checkRequirements(pythonPath, requirements) + } + return true + } + + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt new file mode 100644 index 0000000000..2e955a5b92 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt @@ -0,0 +1,62 @@ +package org.utbot.python.utils + +import org.utbot.python.UtbotExecutor +import org.utpython.types.mypy.MypyInfoBuild + +object RequirementsUtils { + private val utbotMypyRunnerVersion = // local_pip_setup should be used only for debugging + RequirementsUtils::class.java.getResource("/local_pip_setup/local_utbot_mypy_version")?.readText() + ?: MypyInfoBuild::class.java.getResource("/utbot_mypy_runner_version")!!.readText() + private val utbotExecutorVersion = + UtbotExecutor::class.java.getResource("/utbot_executor_version")!!.readText() + private val useLocalPythonPackages = // "true" must be set only for debugging + this::class.java.getResource("/local_pip_setup/use_local_python_packages")?.readText()?.toBoolean() ?: false + private val localMypyRunnerPath = + this::class.java.getResource("/local_pip_setup/local_utbot_mypy_path")?.readText() + private val localExecutorPath = + this::class.java.getResource("/local_pip_setup/local_utbot_executor_path")?.readText() + private val pipFindLinks: List = + if (useLocalPythonPackages) listOfNotNull(localMypyRunnerPath, localExecutorPath) else emptyList() + val requirements: List = listOf( + "utbot-mypy-runner==$utbotMypyRunnerVersion", + "utbot-executor==$utbotExecutorVersion", + ) + + private val requirementsScriptContent: String = + RequirementsUtils::class.java.getResource("/check_requirements.py") + ?.readText() + ?: error("Didn't find /check_requirements.py") + + fun requirementsAreInstalled(pythonPath: String): Boolean { + return requirementsAreInstalled(pythonPath, requirements) + } + + fun requirementsAreInstalled(pythonPath: String, requirementList: List): Boolean { + val requirementsScript = + TemporaryFileManager.createTemporaryFile(requirementsScriptContent, tag = "requirements") + val result = runCommand( + listOf( + pythonPath, + requirementsScript.path + ) + requirementList + ) + requirementsScript.delete() + return result.exitValue == 0 + } + + fun installRequirements(pythonPath: String): CmdResult { + return installRequirements(pythonPath, requirements) + } + + fun installRequirements(pythonPath: String, moduleNames: List): CmdResult { + return runCommand( + listOf( + pythonPath, + "-m", + "pip", + "install" + ) + moduleNames, + environmentVariables = mapOf("PIP_FIND_LINKS" to pipFindLinks.joinToString(" ")) + ) + } +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/StringUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/StringUtils.kt new file mode 100644 index 0000000000..2daa636802 --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/StringUtils.kt @@ -0,0 +1,47 @@ +package org.utbot.python.utils + +import org.utbot.common.PathUtil.toPath +import java.io.File +import java.nio.file.Paths + +// numeration from zero +fun getLineNumber(content: String, pos: Int) = + content.substring(0, pos).count { it == '\n' } + +fun getLineOfFunction(code: String, functionName: String? = null): Int? { + val regex = + if (functionName != null) + """(?m)^def +$functionName\(""".toRegex() + else + """(?m)^def""".toRegex() + + val trimmedCode = code.replaceIndent() + return regex.find(trimmedCode)?.range?.first?.let { getLineNumber(trimmedCode, it) } +} + +fun String.camelToSnakeCase(): String { + val camelRegex = "(?<=[a-zA-Z])[\\dA-Z]".toRegex() + return camelRegex.replace(this) { + "_${it.value}" + }.lowercase() +} + +fun moduleOfType(typeName: String): String? { + val lastIndex = typeName.lastIndexOf('.') + return if (lastIndex == -1) null else typeName.substring(0, lastIndex) +} + +fun checkIfFileLiesInPath(path: String, fileWithClassPath: String): Boolean { + val parentPath = Paths.get(path).toAbsolutePath() + val childPath = Paths.get(fileWithClassPath).toAbsolutePath() + return childPath.startsWith(parentPath) +} + +fun getModuleNameWithoutCheck(path: File, fileWithClass: File): String = + path.toURI().relativize(fileWithClass.toURI()).path.removeSuffix(".py").toPath().joinToString(".") + +fun getModuleName(path: String, fileWithClassPath: String): String? { + if (checkIfFileLiesInPath(path, fileWithClassPath)) + return getModuleNameWithoutCheck(File(path), File(fileWithClassPath)) + return null +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/TemporaryFileManager.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/TemporaryFileManager.kt new file mode 100644 index 0000000000..7dcc6c382f --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/TemporaryFileManager.kt @@ -0,0 +1,47 @@ +package org.utbot.python.utils + +import org.utbot.common.FileUtil +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths + +object TemporaryFileManager { + private var tmpDirectory: Path + private var nextId = 0 + + init { + tmpDirectory = initialize() + } + + fun initialize(): Path { + tmpDirectory = FileUtil.createTempDirectory("python-test-generation-${nextId++}") + Cleaner.addFunction { tmpDirectory.toFile().deleteRecursively() } + return tmpDirectory + } + + fun assignTemporaryFile(fileName_: String? = null, tag: String? = null, addToCleaner: Boolean = true): File { + val fileName = fileName_ ?: ("tmp_${nextId++}_" + (tag ?: "")) + val fullpath = Paths.get(tmpDirectory.toString(), fileName) + val result = fullpath.toFile() + if (addToCleaner) + Cleaner.addFunction { result.delete() } + return result + } + + fun writeToAssignedFile(file: File, content: String) { + file.writeText(content) + file.parentFile?.mkdirs() + file.createNewFile() + } + + fun createTemporaryFile( + content: String, + fileName: String? = null, + tag: String? = null, + addToCleaner: Boolean = true + ): File { + val file = assignTemporaryFile(fileName, tag, addToCleaner) + writeToAssignedFile(file, content) + return file + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/TestGenerationLimitManager.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/TestGenerationLimitManager.kt new file mode 100644 index 0000000000..a1b89262de --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/TestGenerationLimitManager.kt @@ -0,0 +1,89 @@ +package org.utbot.python.utils + +class TestGenerationLimitManager( + // global settings + var mode: LimitManagerMode, + val until: Long, + + // local settings: one type inference iteration + var executions: Int = 150, + var invalidExecutions: Int = 10, + var cacheNodeExecutions: Int = 20, + var fakeNodeExecutions: Int = 40, + var missedLines: Int? = null, + val isRootManager: Boolean = false, +) { + private val initExecution = executions + private val initInvalidExecutions = invalidExecutions + private val initCacheNodeExecutions = cacheNodeExecutions + private val initFakeNodeExecutions = fakeNodeExecutions + private val initMissedLines = missedLines + + fun restart() { + executions = initExecution + invalidExecutions = initInvalidExecutions + cacheNodeExecutions = initCacheNodeExecutions + fakeNodeExecutions = initFakeNodeExecutions + missedLines = initMissedLines + } + + fun addSuccessExecution() { + executions -= 1 + } + + fun addInvalidExecution() { + invalidExecutions -= 1 + } + + fun addFakeNodeExecutions() { + fakeNodeExecutions -= 1 + } + + fun restartFakeNode() { + fakeNodeExecutions = initFakeNodeExecutions + } + + fun isCancelled(): Boolean { + return mode.isCancelled(this) + } +} + +interface LimitManagerMode { + fun isCancelled(manager: TestGenerationLimitManager): Boolean +} + +object MaxCoverageMode : LimitManagerMode { + override fun isCancelled(manager: TestGenerationLimitManager): Boolean { + return manager.missedLines?.equals(0) == true + } +} + +object TimeoutMode : LimitManagerMode { + override fun isCancelled(manager: TestGenerationLimitManager): Boolean { + return System.currentTimeMillis() >= manager.until + } +} + +object ExecutionMode : LimitManagerMode { + override fun isCancelled(manager: TestGenerationLimitManager): Boolean { + return manager.invalidExecutions <= 0 || manager.executions <= 0 || manager.fakeNodeExecutions <= 0 || manager.cacheNodeExecutions <= 0 + } +} + +object MaxCoverageWithTimeoutMode : LimitManagerMode { + override fun isCancelled(manager: TestGenerationLimitManager): Boolean { + return MaxCoverageMode.isCancelled(manager) || TimeoutMode.isCancelled(manager) + } +} + +object ExecutionWithTimeoutMode : LimitManagerMode { + override fun isCancelled(manager: TestGenerationLimitManager): Boolean { + return ExecutionMode.isCancelled(manager) || TimeoutMode.isCancelled(manager) + } +} + +object FakeWithTimeoutMode : LimitManagerMode { + override fun isCancelled(manager: TestGenerationLimitManager): Boolean { + return manager.fakeNodeExecutions <= 0 || TimeoutMode.isCancelled(manager) + } +} \ No newline at end of file diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/TimeoutUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/TimeoutUtils.kt new file mode 100644 index 0000000000..8bcb1b796a --- /dev/null +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/TimeoutUtils.kt @@ -0,0 +1,31 @@ +package org.utbot.python.utils + +import java.text.SimpleDateFormat +import java.util.* +import kotlin.math.max + +fun separateUntil(until: Long, currentIndex: Int, itemsCount: Int): Long { + return when (val itemsLeft = itemsCount - currentIndex) { + 0 -> 0 + 1 -> until + else -> { + val now = System.currentTimeMillis() + max((until - now) / itemsLeft + now, 0) + } + } +} + +fun separateTimeout(timeout: Long, itemsCount: Int): Long { + return when (itemsCount) { + 0 -> 0 + else -> { + timeout / itemsCount + } + } +} + +fun Long.convertToTime(): String { + val date = Date(this) + val format = SimpleDateFormat("HH:mm:ss.SSS") + return format.format(date) +} diff --git a/utbot-python/src/main/resources/check_requirements.py b/utbot-python/src/main/resources/check_requirements.py new file mode 100644 index 0000000000..e1b466d627 --- /dev/null +++ b/utbot-python/src/main/resources/check_requirements.py @@ -0,0 +1,7 @@ +import pkg_resources +import sys + + +if __name__ == "__main__": + dependencies = sys.argv[1:] + pkg_resources.require(dependencies) diff --git a/utbot-python/src/main/resources/example_code/__init__.py b/utbot-python/src/main/resources/example_code/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/src/main/resources/example_code/arithmetic.py b/utbot-python/src/main/resources/example_code/arithmetic.py new file mode 100644 index 0000000000..6f26aa94f4 --- /dev/null +++ b/utbot-python/src/main/resources/example_code/arithmetic.py @@ -0,0 +1,17 @@ +import math + + +def calculate_function_value(x, y): + """ + Calculate value `f` + | sqrt(x - 2y) , x > 100 + f(x, y) = | (3x^2 - 2xy + y^2) / sin(x) , -100 < x <= 100 + | (0.01 * x) ^ log2(y) , x < -100 + """ + + if x > 100: + return math.sqrt(x - 2 * y) + elif -100 < x <= 100: + return (3*x**2 - 2*x*y + y**2) / math.sin(x) + else: + return (0.01 * x) ** math.log2(y) diff --git a/utbot-python/src/main/resources/example_code/inner_dir/__init__.py b/utbot-python/src/main/resources/example_code/inner_dir/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python/src/main/resources/example_code/inner_dir/inner_module.py b/utbot-python/src/main/resources/example_code/inner_dir/inner_module.py new file mode 100644 index 0000000000..8ab929bcd8 --- /dev/null +++ b/utbot-python/src/main/resources/example_code/inner_dir/inner_module.py @@ -0,0 +1,8 @@ +class InnerClass: + x: int + + def __init__(self, x: int): + self.x = x + + def f(self, y: int): + return y**2 + self.x*y + 1 diff --git a/utbot-python/src/main/resources/preprocessed_values.json b/utbot-python/src/main/resources/preprocessed_values.json new file mode 100644 index 0000000000..19f47bdf4d --- /dev/null +++ b/utbot-python/src/main/resources/preprocessed_values.json @@ -0,0 +1,429 @@ +[ + { + "name": "builtins.float", + "instances": [ + "float('-inf')", + "float('inf')", + "float('nan')" + ] + }, + { + "name": "builtins.BaseException", + "instances": [ + "BaseException()" + ] + }, + { + "name": "builtins.object", + "instances": [ + "object()" + ] + }, + { + "name": "builtins.type", + "instances": [ + "type(len)", + "type('123')", + "type(1.0)", + "type({})", + "type('foo', (), {})", + "type('A', (), {'__qualname__': 'B.C'})", + "type(lambda x: x)", + "type((True).real)", + "type(list.append)", + "type('blah', (), {})", + "type(())", + "type('A', (), {})", + "type(list)", + "type({}.items())", + "type({}.values())", + "type('NewClass', (object,), {})", + "type(list.__add__)", + "type(classmethod(lambda c: None))", + "type(range(0))", + "type(None)", + "type(iter(range(0)))", + "type('C', (object,), {'__hash__': None})", + "type(complex('1' * 500))", + "type(staticmethod(lambda : None))", + "type(iter(range(1 << 1000)))", + "type('C', (), {})", + "type({}.keys())" + ] + }, + { + "name": "datetime.date", + "instances": [ + "datetime.date(1, 1, 1)", + "datetime.date(1995, 4, 12)", + "datetime.date(2011, 1, 1)", + "datetime.date(2002, 3, 4)", + "datetime.date(1993, 8, 26)", + "datetime.date(2000, 1, 2)", + "datetime.date(1970, 1, 1)" + ] + }, + { + "name": "datetime.datetime", + "instances": [ + "datetime.datetime(2011, 1, 1)", + "datetime.datetime(1970, 1, 1)", + "datetime.datetime(2015, 4, 5, 1, 45)", + "datetime.datetime(1, 1, 1)", + "datetime.datetime(2014, 11, 2, 1, 30)", + "datetime.datetime(1, 2, 3, 4, 5, 6, 7)", + "datetime.datetime(2002, 4, 7, 2)", + "datetime.datetime(1, 1, 1, fold=1)", + "datetime.datetime(2010, 1, 1)", + "datetime.datetime(2011, 1, 1, 12, 30)", + "datetime.datetime(1993, 8, 26, 22, 12, 55, 99999)", + "datetime.datetime(1, 4, 1, 2)", + "datetime.datetime(1995, 4, 12)", + "datetime.datetime(1, 10, 25, 1)", + "datetime.datetime(10, 10, 10, 10, 10, 10, 10)", + "datetime.datetime(2002, 10, 27, 1)" + ] + }, + { + "name": "datetime.time", + "instances": [ + "datetime.time(fold=1)", + "datetime.time()", + "datetime.time(0, fold=1)", + "datetime.time(22, 12, 55, 99999)", + "datetime.time(0)", + "datetime.time(microsecond=40)", + "datetime.time(18, 45, 3, 1234)", + "datetime.time(12, 0)", + "datetime.time(12, 30)" + ] + }, + { + "name": "datetime.timedelta", + "instances": [ + "datetime.timedelta(days=100, weeks=-7, hours=-24 * (100 - 49), minutes=-3, seconds=12, microseconds=(3 * 60 - 12) * 1000000)", + "datetime.timedelta(hours=24)", + "datetime.timedelta(hours=23, minutes=59)", + "datetime.timedelta(0, 4000, 1)", + "datetime.timedelta(days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999)", + "datetime.timedelta(minutes=1440)", + "datetime.timedelta(microseconds=1)", + "datetime.timedelta(minutes=24)", + "datetime.timedelta(minutes=60)", + "datetime.timedelta(weeks=13)", + "datetime.timedelta(minutes=2, seconds=1, microseconds=3)", + "datetime.timedelta(26, 55, 99999)", + "datetime.timedelta(seconds=0.5)", + "datetime.timedelta(minutes=-200)", + "datetime.timedelta(seconds=30)", + "datetime.timedelta(days=-999999999)", + "datetime.timedelta(minutes=-2)", + "datetime.timedelta(minutes=2 * 1439)", + "datetime.timedelta(hours=12, minutes=32, seconds=30)", + "datetime.timedelta(hours=-5)", + "datetime.timedelta(hours=5)", + "datetime.timedelta(42)", + "datetime.timedelta(minutes=23)", + "datetime.timedelta(minutes=3)", + "datetime.timedelta(microseconds=-1)", + "datetime.timedelta(0, 0, 1000)", + "datetime.timedelta(minutes=1)", + "datetime.timedelta(days=365)", + "datetime.timedelta(minutes=0)", + "datetime.timedelta(microseconds=-81)", + "datetime.timedelta(hours=9.5)", + "datetime.timedelta(minutes=2, seconds=30)", + "datetime.timedelta(hours=-4)", + "datetime.timedelta(minutes=-300)", + "datetime.timedelta(days=1, seconds=2, microseconds=3)", + "datetime.timedelta(hours=2)", + "datetime.timedelta(0)" + ] + }, + { + "name": "decimal.Decimal", + "instances": [ + "decimal.Decimal('22.2')", + "decimal.Decimal('1.234e7')", + "decimal.Decimal('sNaN')", + "decimal.Decimal(3)", + "decimal.Decimal('45.34')", + "decimal.Decimal('580')", + "decimal.Decimal((0, (0,), 0))", + "decimal.Decimal('3.4e200')", + "decimal.Decimal('1e2')", + "decimal.Decimal('2.59')", + "decimal.Decimal((1, (0, 0, 0), 37))", + "decimal.Decimal(10000)", + "decimal.Decimal('10e99999')", + "decimal.Decimal(7.5)", + "decimal.Decimal('1.1')", + "decimal.Decimal(10 ** (19 * 24))", + "decimal.Decimal('-inf')", + "decimal.Decimal(' 3.45679 ')", + "decimal.Decimal('1.0e-20')", + "decimal.Decimal('-0.8')", + "decimal.Decimal('1652.9E100')", + "decimal.Decimal('-10')", + "decimal.Decimal('0E10')", + "decimal.Decimal('2.54')", + "decimal.Decimal('15.32')", + "decimal.Decimal('-0')", + "decimal.Decimal('0.00390625')", + "decimal.Decimal(5)", + "decimal.Decimal((1, (0, 0, 0), 'N'))", + "decimal.Decimal('-3.141590000')", + "decimal.Decimal('0')", + "decimal.Decimal(45)", + "decimal.Decimal('1234e9999')", + "decimal.Decimal(2)", + "decimal.Decimal('1.634E100')", + "decimal.Decimal('4.125')", + "decimal.Decimal('4.2084')", + "decimal.Decimal('56531E100')", + "decimal.Decimal((1, [4, 3, 4, 9, 1, 3, 5, 3, 4], -25))", + "decimal.Decimal('9.99')", + "decimal.Decimal('45')", + "decimal.Decimal('100.0')", + "decimal.Decimal('7')", + "decimal.Decimal('1.01')", + "decimal.Decimal('111')", + "decimal.Decimal(2 ** 16)", + "decimal.Decimal('0.0012885819')", + "decimal.Decimal('1')", + "decimal.Decimal(-12)", + "decimal.Decimal('1.12345')", + "decimal.Decimal('-0.5')", + "decimal.Decimal((0, (4, 5, 3, 4), -2))", + "decimal.Decimal('-0.4')", + "decimal.Decimal('-33.3')", + "decimal.Decimal(2 ** 578)", + "decimal.Decimal('1.00000001e-20')", + "decimal.Decimal('10001111111')", + "decimal.Decimal([0, [0], 0])", + "decimal.Decimal('INF')", + "decimal.Decimal(2 ** 64 + 2 ** 32 - 1)", + "decimal.Decimal('4.9712')", + "decimal.Decimal('8.392')", + "decimal.Decimal('1e-9999')", + "decimal.Decimal('9.123')", + "decimal.Decimal('1e99')", + "decimal.Decimal('1.47e5')", + "decimal.Decimal(23)", + "decimal.Decimal('3.0')", + "decimal.Decimal('152587890625')", + "decimal.Decimal('3.1234')", + "decimal.Decimal(+45)", + "decimal.Decimal(float('nan'))", + "decimal.Decimal('32')", + "decimal.Decimal('snan123')", + "decimal.Decimal(10101)", + "decimal.Decimal('28.5')", + "decimal.Decimal('0.00')", + "decimal.Decimal('11.68')", + "decimal.Decimal('99999999999999999999999999.9')", + "decimal.Decimal('1_0_0_0')", + "decimal.Decimal('5')", + "decimal.Decimal('1.00000001e-100')", + "decimal.Decimal('0.28')", + "decimal.Decimal('NaN')", + "decimal.Decimal((0, (), 'F'))", + "decimal.Decimal('-NaN')", + "decimal.Decimal('9.87654321')", + "decimal.Decimal('10912837129')", + "decimal.Decimal('1.00000001')", + "decimal.Decimal('2E+1')", + "decimal.Decimal('-1')", + "decimal.Decimal('Nan891287828')", + "decimal.Decimal('-11.1')", + "decimal.Decimal((1, (), 37))", + "decimal.Decimal('1e1')", + "decimal.Decimal('NAN')", + "decimal.Decimal('9.8765e-12')", + "decimal.Decimal(99)", + "decimal.Decimal('625')", + "decimal.Decimal(200)", + "decimal.Decimal(123)", + "decimal.Decimal('1.00')", + "decimal.Decimal('81.3971')", + "decimal.Decimal('123456789.1')", + "decimal.Decimal('0.372')", + "decimal.Decimal('1.23')", + "decimal.Decimal(1234)", + "decimal.Decimal('-21.1')", + "decimal.Decimal('10901935')", + "decimal.Decimal('-0E12')", + "decimal.Decimal('Inf')", + "decimal.Decimal((0, (0,), 'F'))", + "decimal.Decimal('-0.6')", + "decimal.Decimal('9e2')", + "decimal.Decimal('35.719')", + "decimal.Decimal('390625')", + "decimal.Decimal(10 ** (19 * 25))", + "decimal.Decimal('-2.5')", + "decimal.Decimal('3.571')", + "decimal.Decimal('1e797')", + "decimal.Decimal('1e-425000000')", + "decimal.Decimal('188.83E100')", + "decimal.Decimal('0.001')", + "decimal.Decimal((1, (4, 3, 4, 9, 1, 3, 5, 3, 4), -25))", + "decimal.Decimal('100E-425000010')", + "decimal.Decimal('1e100000')", + "decimal.Decimal('3.5e-2')", + "decimal.Decimal('100000000000000000000000000')", + "decimal.Decimal('-5')", + "decimal.Decimal(11)", + "decimal.Decimal('1.0e20')", + "decimal.Decimal('1e4')", + "decimal.Decimal(1001)", + "decimal.Decimal('-4.34913534E-17')", + "decimal.Decimal('-23.00000')", + "decimal.Decimal('-25e55')", + "decimal.Decimal('456789')", + "decimal.Decimal('10')", + "decimal.Decimal('999.9')", + "decimal.Decimal([1, (4, 3, 4, 9, 1, 3, 5, 3, 4), -25])", + "decimal.Decimal(0)", + "decimal.Decimal('-0.0625')", + "decimal.Decimal('9.99e-5')", + "decimal.Decimal('256e7')", + "decimal.Decimal('1.3E4 \\n')", + "decimal.Decimal()", + "decimal.Decimal('10.0')", + "decimal.Decimal('3.1415926')", + "decimal.Decimal(0.1)", + "decimal.Decimal('0.25')", + "decimal.Decimal('9.8182731e181273')", + "decimal.Decimal('16.1')", + "decimal.Decimal('nan')", + "decimal.Decimal('-3.217160342717258261933904529E-7')", + "decimal.Decimal('1e9999')", + "decimal.Decimal('0.05')", + "decimal.Decimal('25')", + "decimal.Decimal(100)", + "decimal.Decimal(-2)", + "decimal.Decimal('NaN12345')", + "decimal.Decimal('1e-99')", + "decimal.Decimal('0.' + '9' * 30)", + "decimal.Decimal(1221 ** 1271)", + "decimal.Decimal('32.9714')", + "decimal.Decimal((1, (0, 2, 7, 1), 'F'))", + "decimal.Decimal((1, (4, 5), 0))", + "decimal.Decimal(50)", + "decimal.Decimal([1, [4, 3, 4, 9, 1, 3, 5, 3, 4], -25])", + "decimal.Decimal('45e2')", + "decimal.Decimal('2.234e2000')", + "decimal.Decimal(1000)", + "decimal.Decimal('7.33')", + "decimal.Decimal('5e3')", + "decimal.Decimal('-0.0')", + "decimal.Decimal('1.0e-100')", + "decimal.Decimal('5.5')", + "decimal.Decimal('-75')", + "decimal.Decimal('1.2')", + "decimal.Decimal('-0.625')", + "decimal.Decimal((0, (0, 0, 4, 0, 5, 3, 4), 'n'))", + "decimal.Decimal('33.3')", + "decimal.Decimal(9)", + "decimal.Decimal(4)", + "decimal.Decimal('1230E100')", + "decimal.Decimal('.000e20')", + "decimal.Decimal((0, (0, 0, 4, 0, 5, 3, 4), -2))", + "decimal.Decimal(67)", + "decimal.Decimal('sNAN')", + "decimal.Decimal(float('-inf'))", + "decimal.Decimal(123456789000)", + "decimal.Decimal('NaN123')", + "decimal.Decimal(True)", + "decimal.Decimal('5e-3')", + "decimal.Decimal('-6.1')", + "decimal.Decimal('-25')", + "decimal.Decimal('2')", + "decimal.Decimal('-1.25')", + "decimal.Decimal('0.1')", + "decimal.Decimal('1.0')", + "decimal.Decimal('90.697E100')", + "decimal.Decimal(152587890625)", + "decimal.Decimal(10 ** (9 * 24))", + "decimal.Decimal('12.7')", + "decimal.Decimal('66')", + "decimal.Decimal(-100)", + "decimal.Decimal('.1')", + "decimal.Decimal('7.34')", + "decimal.Decimal(float('inf'))", + "decimal.Decimal('7.335')", + "decimal.Decimal('1.2345')", + "decimal.Decimal('0.2')", + "decimal.Decimal((0, (4, 5, 3, 4), 'F'))", + "decimal.Decimal('Infinity')", + "decimal.Decimal('0.871831e800')", + "decimal.Decimal('1.00000001e20')", + "decimal.Decimal('3.1415')", + "decimal.Decimal('-Inf')", + "decimal.Decimal('-nan')", + "decimal.Decimal(456)", + "decimal.Decimal('194')", + "decimal.Decimal('0.025')", + "decimal.Decimal('snan')", + "decimal.Decimal(567)", + "decimal.Decimal('9.8765e12')", + "decimal.Decimal('3.1')", + "decimal.Decimal('-1.5')", + "decimal.Decimal('inf')", + "decimal.Decimal('1.3')", + "decimal.Decimal('-4.5678E50')", + "decimal.Decimal(5 ** 2659)", + "decimal.Decimal('33e+33')", + "decimal.Decimal('1e-100000')", + "decimal.Decimal(float('-0.0'))", + "decimal.Decimal('1.23456789')", + "decimal.Decimal('1e-10')", + "decimal.Decimal(12)", + "decimal.Decimal('nan123')", + "decimal.Decimal('0.0')", + "decimal.Decimal('-0.000')", + "decimal.Decimal('152587890625e7')", + "decimal.Decimal('-16.1')", + "decimal.Decimal('0.333333333333333333')", + "decimal.Decimal(-1)", + "decimal.Decimal('43.24')", + "decimal.Decimal('9.9')", + "decimal.Decimal('3.1416')", + "decimal.Decimal('2.1')", + "decimal.Decimal('16807')", + "decimal.Decimal('62.4802')", + "decimal.Decimal('-15')", + "decimal.Decimal(500000123)", + "decimal.Decimal('-38.3')", + "decimal.Decimal('0.333333333333333333333333')", + "decimal.Decimal('1e425000000')", + "decimal.Decimal('1.5')", + "decimal.Decimal(768)", + "decimal.Decimal(10 ** (9 * 25))", + "decimal.Decimal((1, (), 'n'))", + "decimal.Decimal('11.1')", + "decimal.Decimal('0.01')", + "decimal.Decimal(-45)", + "decimal.Decimal('9.99e10')", + "decimal.Decimal('1e-3')", + "decimal.Decimal('100000000.123')", + "decimal.Decimal('0.5')", + "decimal.Decimal('3')", + "decimal.Decimal(1)", + "decimal.Decimal(10)", + "decimal.Decimal('20')", + "decimal.Decimal('0.1234')", + "decimal.Decimal('-Infinity')", + "decimal.Decimal('.01')", + "decimal.Decimal('23.42')", + "decimal.Decimal(' -7.89')", + "decimal.Decimal(False)", + "decimal.Decimal('1_3.3e4_0')", + "decimal.Decimal('2.234e-2000')", + "decimal.Decimal('-1E+1')", + "decimal.Decimal('1.50001')", + "decimal.Decimal('8.71E+799')", + "decimal.Decimal('20.686')" + ] + } +] diff --git a/utbot-python/src/test/java/org/utbot/python/framework/external/PythonUtBotJavaApiTest.java b/utbot-python/src/test/java/org/utbot/python/framework/external/PythonUtBotJavaApiTest.java new file mode 100644 index 0000000000..0d79f03c60 --- /dev/null +++ b/utbot-python/src/test/java/org/utbot/python/framework/external/PythonUtBotJavaApiTest.java @@ -0,0 +1,114 @@ +package org.utbot.python.framework.external; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.utbot.python.PythonTestSet; +import org.utbot.python.framework.codegen.model.Unittest; +import org.utbot.python.utils.Cleaner; +import org.utbot.python.utils.TemporaryFileManager; + +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +public class PythonUtBotJavaApiTest { + private final String pythonPath = ""; // Set your path to Python before testing + + @BeforeEach + public void setUp() { + TemporaryFileManager.INSTANCE.initialize(); + Cleaner.INSTANCE.restart(); + } + + @AfterEach + public void tearDown() { + Cleaner.INSTANCE.doCleaning(); + } + private File loadResource(String name) { + URL resource = getClass().getClassLoader().getResource(name); + if (resource == null) { + throw new IllegalArgumentException("file not found!"); + } else { + try { + return new File(resource.toURI()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + } + + @Test + public void testSimpleFunction() { + File fileWithCode = loadResource("example_code/arithmetic.py"); + String pythonRunRoot = fileWithCode.getParentFile().getAbsolutePath(); + String moduleFilename = fileWithCode.getAbsolutePath(); + PythonObjectName testMethodName = new PythonObjectName("arithmetic", "calculate_function_value"); + PythonTestMethodInfo methodInfo = new PythonTestMethodInfo(testMethodName, moduleFilename, null); + ArrayList testMethods = new ArrayList<>(1); + testMethods.add(methodInfo); + ArrayList directoriesForSysPath = new ArrayList<>(); + directoriesForSysPath.add(pythonRunRoot); + String testCode = PythonUtBotJavaApi.generate( + testMethods, + pythonPath, + pythonRunRoot, + directoriesForSysPath, + 10_000, + 1_000, + Unittest.INSTANCE + ); + Assertions.assertFalse(testCode.isEmpty()); + } + + @Test + public void testSimpleFunctionTestCase() { + File fileWithCode = loadResource("example_code/arithmetic.py"); + String pythonRunRoot = fileWithCode.getParentFile().getAbsolutePath(); + String moduleFilename = fileWithCode.getAbsolutePath(); + PythonObjectName testMethodName = new PythonObjectName("arithmetic", "calculate_function_value"); + PythonTestMethodInfo methodInfo = new PythonTestMethodInfo(testMethodName, moduleFilename, null); + ArrayList testMethods = new ArrayList<>(1); + testMethods.add(methodInfo); + ArrayList directoriesForSysPath = new ArrayList<>(); + directoriesForSysPath.add(pythonRunRoot); + List testCase = PythonUtBotJavaApi.generateTestSets( + testMethods, + pythonPath, + pythonRunRoot, + directoriesForSysPath, + 10_000, + 1_000 + ); + Assertions.assertFalse(testCase.isEmpty()); + Assertions.assertFalse(testCase.get(0).component2().isEmpty()); + } + + @Test + public void testSimpleClassMethod() { + File fileWithCode = loadResource("example_code/inner_dir/inner_module.py"); + File projectRoot = loadResource("example_code/"); + String pythonRunRoot = projectRoot.getAbsolutePath(); + String moduleFilename = fileWithCode.getAbsolutePath(); + PythonObjectName containingClassName = new PythonObjectName("inner_dir.inner_module", "InnerClass"); + PythonObjectName testMethodName = new PythonObjectName("inner_dir.inner_module", "f"); + PythonTestMethodInfo methodInfo = new PythonTestMethodInfo(testMethodName, moduleFilename, containingClassName); + ArrayList testMethods = new ArrayList<>(1); + testMethods.add(methodInfo); + ArrayList directoriesForSysPath = new ArrayList<>(); + directoriesForSysPath.add(pythonRunRoot); + String testCode = PythonUtBotJavaApi.generate( + testMethods, + pythonPath, + pythonRunRoot, + directoriesForSysPath, + 10_000, + 1_000, + Unittest.INSTANCE + ); + Assertions.assertFalse(testCode.isEmpty()); + } +} diff --git a/utbot-python/src/test/kotlin/org/utbot/python/framework/api/python/utils/PythonTreeComparatorTest.kt b/utbot-python/src/test/kotlin/org/utbot/python/framework/api/python/utils/PythonTreeComparatorTest.kt new file mode 100644 index 0000000000..77b2177eac --- /dev/null +++ b/utbot-python/src/test/kotlin/org/utbot/python/framework/api/python/utils/PythonTreeComparatorTest.kt @@ -0,0 +1,380 @@ +package org.utbot.python.framework.api.python.utils + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import org.utbot.python.framework.api.python.PythonClassId +import org.utbot.python.framework.api.python.PythonTree +import org.utbot.python.framework.api.python.util.comparePythonTree +import org.utbot.python.framework.api.python.util.pythonIntClassId +import org.utbot.python.framework.api.python.util.pythonStrClassId + +internal class PythonTreeComparatorTest { + @Test + fun testEqualPrimitive() { + val left = PythonTree.PrimitiveNode(pythonIntClassId, "1") + + assertTrue(comparePythonTree(left, left)) + } + + @Test + fun testEqualList() { + val left = PythonTree.ListNode(mapOf( + 0 to PythonTree.PrimitiveNode(pythonIntClassId, "1"), + 1 to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + + assertTrue(comparePythonTree(left, left)) + } + + @Test + fun testEqualTuple() { + val left = PythonTree.TupleNode(mapOf( + 0 to PythonTree.PrimitiveNode(pythonIntClassId, "1"), + 1 to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + + assertTrue(comparePythonTree(left, left)) + } + + @Test + fun testEqualDict() { + val left = PythonTree.DictNode(mapOf( + PythonTree.PrimitiveNode(pythonStrClassId, "'a'") to PythonTree.PrimitiveNode(pythonIntClassId, "1"), + PythonTree.PrimitiveNode(pythonStrClassId, "'b'") to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + + assertTrue(comparePythonTree(left, left)) + } + + @Test + fun testEqualSet() { + val left = PythonTree.SetNode(setOf( + PythonTree.PrimitiveNode(pythonIntClassId, "1"), + PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableSet()) + + assertTrue(comparePythonTree(left, left)) + } + + @Test + fun testEqualEmptyReduce() { + val left = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(PythonTree.PrimitiveNode(pythonIntClassId, "2")), + ) + + assertTrue(comparePythonTree(left, left)) + } + + @Test + fun testEqualNotEmptyReduce() { + val left = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(PythonTree.PrimitiveNode(pythonIntClassId, "2")), + ) + left.state["my_field"] = PythonTree.PrimitiveNode(pythonIntClassId, "2") + left.state["my_field_1"] = PythonTree.PrimitiveNode(pythonIntClassId, "1") + + assertTrue(comparePythonTree(left, left)) + } + + @Test + fun testEqualReduceWithListItems() { + val left = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(PythonTree.PrimitiveNode(pythonIntClassId, "2")), + ) + left.listitems = listOf( + PythonTree.PrimitiveNode(pythonIntClassId, "1"), + PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ) + + assertTrue(comparePythonTree(left, left)) + } + + @Test + fun testEqualReduceWithDictItems() { + val left = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(PythonTree.PrimitiveNode(pythonIntClassId, "2")), + ) + left.dictitems = mapOf( + PythonTree.PrimitiveNode(pythonStrClassId, "'a'") to PythonTree.PrimitiveNode(pythonIntClassId, "1"), + PythonTree.PrimitiveNode(pythonStrClassId, "'b'") to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ) + + assertTrue(comparePythonTree(left, left)) + } + + @Test + fun testEqualRecursiveReduce() { + val left = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + val child = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + val child2 = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + left.state["children"] = PythonTree.ListNode( + mapOf(0 to child).toMutableMap() + ) + child.state["children"] = PythonTree.ListNode( + mapOf(0 to child2).toMutableMap() + ) + child2.state["children"] = PythonTree.ListNode( + mapOf(0 to left).toMutableMap() + ) + + assertTrue(comparePythonTree(left, left)) + } + + @Test + fun testEqualHardRecursiveReduce() { + val left = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + val child = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + val child2 = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + val child3 = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + val child4 = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + val child5 = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + val child6 = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + left.state["children"] = PythonTree.ListNode(mapOf( + 0 to child, + 1 to child2, + 2 to child3, + ).toMutableMap()) + child.state["children"] = PythonTree.ListNode(mapOf( + 0 to child2, + 1 to child3, + ).toMutableMap()) + child2.state["children"] = PythonTree.ListNode(mapOf( + 0 to child2, + 1 to child3, + 2 to left, + 3 to child4, + ).toMutableMap()) + child3.state["children"] = PythonTree.ListNode(mapOf( + 0 to left, + ).toMutableMap()) + child4.state["children"] = PythonTree.ListNode(mapOf( + 0 to left, + 1 to child5, + ).toMutableMap()) + child5.state["children"] = PythonTree.ListNode(mapOf( + 0 to child2, + ).toMutableMap()) + child6.state["children"] = PythonTree.ListNode(mapOf( + 0 to child2, + ).toMutableMap()) + + assertTrue(comparePythonTree(left, left)) + } + + @Test + fun testNotEqualPrimitive() { + val left = PythonTree.PrimitiveNode(pythonIntClassId, "1") + val right = PythonTree.PrimitiveNode(pythonIntClassId, "2") + + assertFalse(comparePythonTree(left, right)) + } + + @Test + fun testNotEqualList() { + val left = PythonTree.ListNode(mapOf( + 0 to PythonTree.PrimitiveNode(pythonIntClassId, "1"), + 1 to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + val right = PythonTree.ListNode(mapOf( + 0 to PythonTree.PrimitiveNode(pythonIntClassId, "0"), + 1 to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + + assertFalse(comparePythonTree(left, right)) + } + + @Test + fun testNotEqualTuple() { + val left = PythonTree.TupleNode(mapOf( + 0 to PythonTree.PrimitiveNode(pythonIntClassId, "1"), + 1 to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + val right = PythonTree.TupleNode(mapOf( + 0 to PythonTree.PrimitiveNode(pythonIntClassId, "0"), + 1 to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + + assertFalse(comparePythonTree(left, right)) + } + + @Test + fun testNotEqualDict() { + val left = PythonTree.DictNode(mapOf( + PythonTree.PrimitiveNode(pythonStrClassId, "'c'") to PythonTree.PrimitiveNode(pythonIntClassId, "1"), + PythonTree.PrimitiveNode(pythonStrClassId, "'b'") to PythonTree.PrimitiveNode(pythonIntClassId, "0"), + ).toMutableMap()) + val right = PythonTree.DictNode(mapOf( + PythonTree.PrimitiveNode(pythonStrClassId, "'a'") to PythonTree.PrimitiveNode(pythonIntClassId, "1"), + PythonTree.PrimitiveNode(pythonStrClassId, "'b'") to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + + assertFalse(comparePythonTree(left, right)) + } + + @Test + fun testNotEqualSet() { + val left = PythonTree.SetNode(setOf( + PythonTree.PrimitiveNode(pythonIntClassId, "1"), + PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableSet()) + val right = PythonTree.SetNode(setOf( + PythonTree.PrimitiveNode(pythonIntClassId, "0"), + PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableSet()) + + assertFalse(comparePythonTree(left, right)) + } + + @Test + fun testNotEqualListDiffSize() { + val left = PythonTree.ListNode(mapOf( + 0 to PythonTree.PrimitiveNode(pythonIntClassId, "1"), + 1 to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + val right = PythonTree.ListNode(mapOf( + 1 to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + + assertFalse(comparePythonTree(left, right)) + } + + @Test + fun testNotEqualTupleDiffSize() { + val left = PythonTree.TupleNode(mapOf( + 0 to PythonTree.PrimitiveNode(pythonIntClassId, "1"), + 1 to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + val right = PythonTree.TupleNode(mapOf( + 1 to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + + assertFalse(comparePythonTree(left, right)) + } + + @Test + fun testNotEqualDictDiffSize() { + val left = PythonTree.DictNode(mapOf( + PythonTree.PrimitiveNode(pythonStrClassId, "'c'") to PythonTree.PrimitiveNode(pythonIntClassId, "1"), + PythonTree.PrimitiveNode(pythonStrClassId, "'b'") to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + val right = PythonTree.DictNode(mapOf( + PythonTree.PrimitiveNode(pythonStrClassId, "'b'") to PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableMap()) + + assertFalse(comparePythonTree(left, right)) + } + + @Test + fun testNotEqualSetDiffSize() { + val left = PythonTree.SetNode(setOf( + PythonTree.PrimitiveNode(pythonIntClassId, "1"), + PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableSet()) + val right = PythonTree.SetNode(setOf( + PythonTree.PrimitiveNode(pythonIntClassId, "2"), + ).toMutableSet()) + + assertFalse(comparePythonTree(left, right)) + } + + @Test + fun testNotEqualRecursiveReduce() { + val left = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + val leftChild1 = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + val leftChild2 = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + left.state["children"] = PythonTree.ListNode( + mapOf(0 to leftChild1).toMutableMap() + ) + leftChild1.state["children"] = PythonTree.ListNode( + mapOf(0 to leftChild2).toMutableMap() + ) + leftChild2.state["children"] = PythonTree.ListNode( + mapOf(0 to left).toMutableMap() + ) + + val right = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + val rightChild1 = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + val rightChild2 = PythonTree.ReduceNode( + PythonClassId("my_module", "MyClass"), + PythonClassId("my_module.MyClass"), + listOf(), + ) + right.state["children"] = PythonTree.ListNode(mapOf( + 0 to rightChild1, + 1 to rightChild2, + ).toMutableMap()) + rightChild1.state["children"] = PythonTree.ListNode(mapOf( + 0 to rightChild2, + ).toMutableMap()) + + assertFalse(comparePythonTree(left, right)) + } +} \ No newline at end of file diff --git a/utbot-rd/build.gradle b/utbot-rd/build.gradle new file mode 100644 index 0000000000..f09e7cdf81 --- /dev/null +++ b/utbot-rd/build.gradle @@ -0,0 +1,307 @@ +plugins { + id 'com.jetbrains.rdgen' version "2023.2.0" +} + +import com.jetbrains.rd.generator.gradle.RdGenExtension +import com.jetbrains.rd.generator.gradle.RdGenTask + +if (includeRiderInBuild.toBoolean()) { + def utbotRider = project.rootProject.childProjects["utbot-rider"] + evaluationDependsOn(utbotRider.path) + tasks.getByName("classes").dependsOn(utbotRider.tasks.getByName("addRiderModelsToUtbotModels")) +} + +compileKotlin { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } +} + +compileTestKotlin { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +configurations { + lifetimedProcessMockCompileClasspath.extendsFrom configurations.compileClasspath + processWithRdServerMockCompileClasspath.extendsFrom configurations.compileClasspath + rdgenModelsCompileClasspath.extendsFrom configurations.compileClasspath + if (includeRiderInBuild.toBoolean()) + riderRdgenModelsCompileClasspath.extendsFrom configurations.rdgenModelsCompileClasspath +} + +sourceSets { + lifetimedProcessMock { + kotlin { + srcDirs = ["src/main/lifetimedProcessMock"] + } + } + processWithRdServerMock { + kotlin { + srcDirs = ["src/main/processWithRdServerMock"] + } + } + rdgenModels { + kotlin { + srcDirs = ["src/main/rdgen"] + } + } + if (includeRiderInBuild.toBoolean()) { + riderRdgenModels { + kotlin { + srcDirs = ["src/main/riderRdgenModels"] + } + } + } +} + +def riderModelJar = new File(project.buildDir, "libs/rider-model.jar") + +dependencies { + implementation project(':utbot-core') + implementation group: 'com.jetbrains.rd', name: 'rd-framework', version: rdVersion + implementation group: 'com.jetbrains.rd', name: 'rd-core', version: rdVersion + + implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlinLoggingVersion + + processWithRdServerMockImplementation project(':utbot-rd') + + rdgenModelsCompileClasspath group: 'com.jetbrains.rd', name: 'rd-gen', version: rdVersion + + if (includeRiderInBuild.toBoolean()) { + riderRdgenModelsCompileClasspath files(riderModelJar) + } +} + +task lifetimedProcessMockJar(type: Jar) { + dependsOn lifetimedProcessMockClasses + archiveAppendix.set("lifetimedProcessMock") + + manifest { + attributes( + 'Main-Class': 'org.utbot.rd.tests.LifetimedProcessMockKt' + ) + } + + from configurations.lifetimedProcessMockCompileClasspath.collect { + (it.isDirectory() || !it.exists()) ? it : zipTree(it) + } + sourceSets.lifetimedProcessMock.output + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +task processWithRdServerMockJar(type: Jar) { + dependsOn processWithRdServerMockClasses + archiveAppendix.set("processWithRdServerMock") + + manifest { + attributes( + 'Main-Class': 'org.utbot.rd.tests.ProcessWithRdServerMockKt' + ) + } + + from configurations.processWithRdServerMockCompileClasspath.collect { + (it.isDirectory() || !it.exists()) ? it : zipTree(it) + } + sourceSets.processWithRdServerMock.output + + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + +test { + dependsOn lifetimedProcessMockJar + dependsOn processWithRdServerMockJar + systemProperty("RD_MOCK_PROCESS", lifetimedProcessMockJar.archiveFile.get().getAsFile().canonicalPath) + systemProperty("PROCESS_WITH_RD_SERVER_MOCK", processWithRdServerMockJar.archiveFile.get().getAsFile().canonicalPath) +} + +if (includeRiderInBuild.toBoolean()) { + def currentProjectDir = project.projectDir + def riderProject = project.rootProject.childProjects["utbot-rider"] + def riderTask = riderProject.tasks.getByName("addRiderModelsToUtbotModels") + // !!!! IMPORTANT !!!! + // after generation you should MANUALLY correct kotlin generated code as it is incorrectly references some rider model + // mandatory steps: + // 1. In UtBotRiderModel.Generated.kt change package to `package org.utbot.rider.generated` + // 2. then import all unreferenced classes + // otherwise you will have broken plugin initialization and ClassNotFoundException + task generateRiderModels(type: RdGenTask) { + dependsOn(riderTask) + + def riderProjectDir = riderProject.projectDir + def generatedOutputDir = new File(riderProjectDir, "src/main/kotlin/org/utbot/rider/generated") + def hashDir = generatedOutputDir + def sourcesDir = new File(currentProjectDir, "src/main/riderRdgenModels/org/utbot/rider/rd/models") + def rdParams = extensions.getByName("params") as RdGenExtension + + group = "rdgen" + rdParams.verbose = true + rdParams.sources(sourcesDir) + rdParams.hashFolder = hashDir.canonicalPath + rdParams.packages = "org.utbot.rider.rd.models" + rdParams.classpath(riderModelJar) + + rdParams.generator { + language = "kotlin" + transform = "symmetric" + root = "com.jetbrains.rider.model.nova.ide.IdeRoot" + directory = generatedOutputDir.canonicalPath + namespace = "org.utbot.rider.generated" + } + + rdParams.generator { + language = "csharp" + transform = "symmetric" + root = "com.jetbrains.rider.model.nova.ide.IdeRoot" + namespace = "UtBot" + directory = new File(riderProjectDir, "src/dotnet/UtBot/UtBot/Generated") + } + } +} + +task generateInstrumentedProcessModels(type: RdGenTask) { + def currentProjectDir = project.projectDir + def instrumentationProjectDir = project.rootProject.childProjects["utbot-instrumentation"].projectDir + def generatedOutputDir = new File(instrumentationProjectDir, "src/main/kotlin/org/utbot/instrumentation/rd/generated") + def hashDir = generatedOutputDir + def sourcesDir = new File(currentProjectDir, "src/main/rdgen/org/utbot/rd/models") + def rdParams = extensions.getByName("params") as RdGenExtension + + group = "rdgen" + rdParams.verbose = true + rdParams.sources(sourcesDir) + rdParams.hashFolder = hashDir.canonicalPath + // where to search roots + rdParams.packages = "org.utbot.rd.models" + + rdParams.generator { + language = "kotlin" + transform = "symmetric" + root = "org.utbot.rd.models.InstrumentedProcessRoot" + + directory = generatedOutputDir.canonicalPath + namespace = "org.utbot.instrumentation.process.generated" + } +} + +task generateEngineProcessModels(type: RdGenTask) { + def currentProjectDir = project.projectDir + def ideaPluginProjectDir = project.rootProject.childProjects["utbot-framework"].projectDir + def generatedOutputDir = new File(ideaPluginProjectDir, "src/main/kotlin/org/utbot/framework/process/generated") + def hashDir = generatedOutputDir + def sourcesDir = new File(currentProjectDir, "src/main/rdgen/org/utbot/rd/models") + def rdParams = extensions.getByName("params") as RdGenExtension + + group = "rdgen" + rdParams.verbose = true + rdParams.sources(sourcesDir) + rdParams.hashFolder = hashDir.canonicalPath + // where to search roots + rdParams.packages = "org.utbot.rd.models" + + rdParams.generator { + language = "kotlin" + transform = "symmetric" + root = "org.utbot.rd.models.EngineProcessRoot" + + directory = generatedOutputDir.canonicalPath + namespace = "org.utbot.framework.process.generated" + } +} + +task generateCommonModels(type: RdGenTask) { + def currentProjectDir = project.projectDir + def ideaPluginProjectDir = project.rootProject.childProjects["utbot-rd"].projectDir + def generatedOutputDir = new File(ideaPluginProjectDir, "src/main/kotlin/org/utbot/rd/generated") + def hashDir = generatedOutputDir + def sourcesDir = new File(currentProjectDir, "src/main/rdgen/org/utbot/rd/models") + def rdParams = extensions.getByName("params") as RdGenExtension + + group = "rdgen" + rdParams.verbose = true + rdParams.sources(sourcesDir) + rdParams.hashFolder = hashDir.canonicalPath + // where to search roots + rdParams.packages = "org.utbot.rd.models" + + rdParams.generator { + language = "kotlin" + transform = "symmetric" + root = "org.utbot.rd.models.SynchronizationRoot" + + directory = generatedOutputDir.canonicalPath + namespace = "org.utbot.rd.generated" + } + + rdParams.generator { + language = "kotlin" + transform = "symmetric" + root = "org.utbot.rd.models.SettingsRoot" + + directory = generatedOutputDir.canonicalPath + namespace = "org.utbot.rd.generated" + } + + rdParams.generator { + language = "kotlin" + transform = "symmetric" + root = "org.utbot.rd.models.LoggerRoot" + + directory = generatedOutputDir.canonicalPath + namespace = "org.utbot.rd.generated" + } +} + +task generateSpringModels(type: RdGenTask) { + def currentProjectDir = project.projectDir + def ideaPluginProjectDir = project.rootProject.childProjects["utbot-spring-analyzer"].projectDir + def generatedOutputDir = new File(ideaPluginProjectDir, "src/main/kotlin/org/utbot/spring/generated") + def hashDir = generatedOutputDir + def sourcesDir = new File(currentProjectDir, "src/main/rdgen/org/utbot/rd/models") + def rdParams = extensions.getByName("params") as RdGenExtension + + group = "rdgen" + rdParams.verbose = true + rdParams.sources(sourcesDir) + rdParams.hashFolder = hashDir.canonicalPath + // where to search roots + rdParams.packages = "org.utbot.rd.models" + + rdParams.generator { + language = "kotlin" + transform = "symmetric" + root = "org.utbot.rd.models.SpringAnalyzerRoot" + + directory = generatedOutputDir.canonicalPath + namespace = "org.utbot.spring.generated" + } +} + +task generateCSharpModels(type: RdGenTask) { + def currentProjectDir = project.projectDir + def riderPluginProjectDir = project.rootProject.projectDir.toPath().resolve("utbot-rider").toFile() + def generatedOutputDir = new File (riderPluginProjectDir, "src/dotnet/UtBot/UtBot.Rd/Generated") + def hashDir = generatedOutputDir + def sourcesDir = new File(currentProjectDir, "src/main/rdgen/org/utbot/rd/models") + def rdParams = extensions.getByName("params") as RdGenExtension + + group = "rdgen" + rdParams.verbose = true + rdParams.sources(sourcesDir) + rdParams.hashFolder = hashDir.canonicalPath + rdParams.packages = "org.utbot.rd.models" + + rdParams.generator { + language = "csharp" + transform = "symmetric" + root = "org.utbot.rd.models.CSharpRoot" + + directory = generatedOutputDir.canonicalPath + namespace = "UtBot.Rd.Generated" + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/ClientProcessUtil.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/ClientProcessUtil.kt new file mode 100644 index 0000000000..133437c11c --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/ClientProcessUtil.kt @@ -0,0 +1,234 @@ +package org.utbot.rd + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.impl.RdCall +import com.jetbrains.rd.framework.util.launch +import com.jetbrains.rd.util.LogLevel +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.lifetime.LifetimeDefinition +import com.jetbrains.rd.util.lifetime.isAlive +import com.jetbrains.rd.util.lifetime.plusAssign +import com.jetbrains.rd.util.reactive.adviseEternal +import com.jetbrains.rd.util.threading.SingleThreadScheduler +import com.jetbrains.rd.util.trace +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.withTimeoutOrNull +import org.utbot.common.* +import org.utbot.rd.generated.synchronizationModel +import org.utbot.rd.loggers.withLevel +import java.io.File +import java.io.OutputStream +import java.io.PrintStream +import kotlin.time.Duration + +const val rdProcessDirName = "rdProcessSync" +val processSyncDirectory = File(utBotTempDirectory.toFile(), rdProcessDirName) +const val rdPortProcessArgumentTag = "rdPort" +internal const val fileWaitTimeoutMillis = 10L +private val logger = getLogger() + +internal fun childCreatedFileName(port: Int): String { + return "$port.created" +} + +internal fun signalProcessReady(port: Int) { + processSyncDirectory.mkdirs() + + val signalFile = File(processSyncDirectory, childCreatedFileName(port)) + + if (signalFile.exists()) { + signalFile.delete() + } + + val created = signalFile.createNewFile() + + if (!created) { + throw IllegalStateException("cannot create signal file") + } +} + +fun rdPortArgument(port: Int): String { + return "$rdPortProcessArgumentTag=$port" +} + +fun findRdPort(args: Array): Int { + return args.find { it.startsWith(rdPortProcessArgumentTag) } + ?.run { split("=").last().toInt().coerceIn(1..65535) } + ?: throw IllegalArgumentException("No port provided") +} + +/** + * Traces when process is idle for too much time, then terminates it. + */ +class IdleWatchdog(private val ldef: LifetimeDefinition, val timeout: Duration) { + private enum class State { + STARTED, + ENDED + } + + private val synchronizer: Channel = Channel(1) + + init { + ldef.onTermination { synchronizer.close(CancellationException("Client terminated")) } + } + + var suspendTimeout = false + + /** + * Execute block indicating that during this activity process should not die. + * After block ended - idle timer restarts + */ + fun wrapActive(block: () -> T): T { + try { + synchronizer.trySendBlocking(State.STARTED) + return block() + } finally { + synchronizer.trySendBlocking(State.ENDED) + } + } + + /** + * Adds callback to RdCall with indicating that during this activity process should not die. + * After block ended - idle timer restarts + */ + fun wrapActiveCall(call: RdCall, block: (T) -> R) { + call.set { it -> + wrapActive { + block(it) + } + } + } + + /** + * Adds callback to RdCall with indicating that during this activity process should not die. + * After block ended - idle timer restarts. + * Also additonally logs + */ + fun measureTimeForActiveCall(call: RdCall, requestName: String, level: LogLevel = LogLevel.Debug, block: (T) -> R) { + call.set { it -> + logger.withLevel(level).measureTime({ requestName }) { + wrapActive { + block(it) + } + } + } + } + + suspend fun setupTimeout() { + ldef.launch { + var lastState = State.ENDED + while (ldef.isAlive) { + val current: State? = + withTimeoutOrNull(timeout) { + synchronizer.receive() + } + if (current == null) { + if (lastState == State.ENDED && !suspendTimeout) { + // process is waiting for command more than expected, better die + logger.info { "terminating lifetime by timeout" } + stopProtocol() + break + } + } else { + lastState = current + } + } + } + } + + fun stopProtocol() { + ldef.terminate() + } +} + +class ClientProtocolBuilder { + private var timeout = Duration.INFINITE + + private fun silentlyCloseStandardStreams() { + // we should change out/err streams as not to spend time on user output + // and also because rd default logging system writes some initial values to stdout, polluting it as well + val tmpStream = PrintStream(object : OutputStream() { + override fun write(b: Int) {} + }) + val prevOut = System.out + val prevError = System.err + System.setOut(tmpStream) + System.setErr(tmpStream) + // stdin/stderr should be closed as not to leave hanging descriptors + // and we cannot log any exceptions here as rd remote logging is still not configured + // so we pass any exceptions + silent { prevOut.close() } + silent { prevError.close() } + } + + suspend fun start(args: Array, parent: Lifetime? = null, block: Protocol.(IdleWatchdog) -> Unit) { + silentlyCloseStandardStreams() + + val port = findRdPort(args) + + UtRdCoroutineScope.initialize() + val pid = currentProcessPid.toInt() + val ldef = parent?.createNested() ?: LifetimeDefinition() + ldef.terminateOnException { _ -> + ldef += { logger.info { "lifetime terminated" } } + ldef += { + val syncFile = File(processSyncDirectory, childCreatedFileName(port)) + + if (syncFile.exists()) { + logger.info { "sync file existed" } + syncFile.delete() + } + } + logger.info { "pid - $pid, port - $port" } + logger.info { "isJvm8 - $isJvm8, isJvm9Plus - $isJvm9Plus, isWindows - $isWindows" } + + val name = "Client$port" + val rdClientProtocolScheduler = SingleThreadScheduler(ldef, "Scheduler for $name") + val clientProtocol = Protocol( + name, + Serializers(), + Identities(IdKind.Client), + rdClientProtocolScheduler, + SocketWire.Client(ldef, rdClientProtocolScheduler, port), + ldef + ) + val watchdog = IdleWatchdog(ldef, timeout) + + watchdog.setupTimeout() + rdClientProtocolScheduler.pump(ldef) { + clientProtocol.synchronizationModel.suspendTimeoutTimer.set { param -> + watchdog.suspendTimeout = param + } + clientProtocol.synchronizationModel.stopProcess.adviseEternal { _ -> watchdog.stopProtocol() } + clientProtocol.block(watchdog) + } + + signalProcessReady(port) + logger.info { "signalled" } + clientProtocol.synchronizationModel.synchronizationSignal.let { sync -> + val answerFromMainProcess = sync.adviseForConditionAsync(ldef) { + if (it == "main") { + logger.trace { "received from main" } + watchdog.wrapActive { + sync.fire("child") + } + true + } else { + false + } + } + answerFromMainProcess.await() + } + ldef.awaitTermination() + } + } + + fun withProtocolTimeout(duration: Duration): ClientProtocolBuilder { + timeout = duration + return this + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/LifetimedProcess.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/LifetimedProcess.kt new file mode 100644 index 0000000000..9d0b38b94d --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/LifetimedProcess.kt @@ -0,0 +1,103 @@ +package org.utbot.rd + +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.lifetime.LifetimeDefinition +import com.jetbrains.rd.util.lifetime.throwIfNotAlive +import kotlinx.coroutines.delay +import java.util.concurrent.TimeUnit +import kotlin.concurrent.thread + +/** + * Creates LifetimedProcess. + * If provided lifetime already terminated - throws CancellationException and process is not started. + */ +fun startLifetimedProcess(cmd: List, lifetime: Lifetime? = null): LifetimedProcess { + lifetime?.throwIfNotAlive() + + return ProcessBuilder(cmd).start().toLifetimedProcess(lifetime) +} + +/** + * Creates LifetimedProcess from already running process. + * + * Process will be terminated if provided lifetime is terminated. + */ +fun Process.toLifetimedProcess(lifetime: Lifetime? = null): LifetimedProcess { + return LifetimedProcessIml(this, lifetime) +} + +/** + * Main class goals + * 1. if process terminates - lifetime terminates + * 2. if lifetime terminates - process terminates + */ +interface LifetimedProcess { + val lifetime: Lifetime + val process: Process + fun terminate() +} + +inline fun R.use(block: (R) -> T): T { + try { + return block(this) + } + finally { + terminate() + } +} + +inline fun R.terminateOnException(block: (R) -> T): T { + try { + return block(this) + } + catch(e: Throwable) { + terminate() + throw e + } +} + +private const val processKillTimeoutMillis = 100L +private const val checkProcessAliveDelayMillis = 100L + +class LifetimedProcessIml(override val process: Process, lifetime: Lifetime? = null): LifetimedProcess { + private val ldef: LifetimeDefinition + + override val lifetime + get() = ldef.lifetime + + init { + ldef = lifetime?.createNested() ?: LifetimeDefinition() + allProcessList.add(this) + ldef.onTermination { + process.destroy() + + if (!process.waitFor(processKillTimeoutMillis, TimeUnit.MILLISECONDS)) + process.destroyForcibly() + + allProcessList.remove(this) + } + UtRdCoroutineScope.current.launch(ldef) { + while (process.isAlive) { + delay(checkProcessAliveDelayMillis) + } + + ldef.terminate() + } + } + + override fun terminate() { + ldef.terminate() + } + + companion object { + private val allProcessList = mutableListOf() + + init { + Runtime.getRuntime().addShutdownHook(thread(start = false) { + for (proc in allProcessList.reversed()) { + proc.terminate() + } + }) + } + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/ProcessWithRdServer.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/ProcessWithRdServer.kt new file mode 100644 index 0000000000..a6321f1e18 --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/ProcessWithRdServer.kt @@ -0,0 +1,154 @@ +package org.utbot.rd + +import com.jetbrains.rd.framework.IProtocol +import com.jetbrains.rd.framework.Protocol +import com.jetbrains.rd.framework.serverPort +import com.jetbrains.rd.framework.util.NetUtils +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.lifetime.isAlive +import com.jetbrains.rd.util.lifetime.throwIfNotAlive +import com.jetbrains.rd.util.trace +import kotlinx.coroutines.delay +import org.utbot.common.getPid +import org.utbot.common.silent +import org.utbot.rd.generated.synchronizationModel +import java.io.File +import java.nio.file.Files + +/** + * Process will be terminated if lifetime is not alive + */ +suspend fun Process.withRdServer( + lifetime: Lifetime? = null, + serverFactory: (Lifetime) -> Protocol +): ProcessWithRdServer { + return ProcessWithRdServerImpl(toLifetimedProcess(lifetime)) { + serverFactory(it) + } +} + +suspend fun LifetimedProcess.withRdServer( + serverFactory: (Lifetime) -> Protocol +): ProcessWithRdServer { + return ProcessWithRdServerImpl(this) { + serverFactory(it) + } +} + +/** + * Process will not be started if lifetime is not alive + */ +suspend fun startProcessWithRdServer( + cmd: List, + lifetime: Lifetime? = null, + serverFactory: (Lifetime) -> Protocol +): ProcessWithRdServer { + lifetime?.throwIfNotAlive() + + val child = startLifetimedProcess(cmd, lifetime) + + return child.withRdServer { + serverFactory(it) + } +} + +/** + * Process will not be started if lifetime is not alive + */ +suspend fun startProcessWithRdServer( + processFactory: (Int) -> Process, + lifetime: Lifetime? = null, + serverFactory: (Int, Lifetime) -> Protocol +): ProcessWithRdServer { + lifetime?.throwIfNotAlive() + + val port = NetUtils.findFreePort(0) + + return processFactory(port).withRdServer(lifetime) { + serverFactory(port, it) + } +} + +/** + * Main goals of this class: + * 1. start rd server protocol with child process + * 2. protocol should be bound to process lifetime + */ +interface ProcessWithRdServer : LifetimedProcess { + val protocol: IProtocol + val port: Int + get() = protocol.wire.serverPort + + suspend fun awaitProcessReady(): ProcessWithRdServer +} + +private val logger = getLogger() + +class ProcessWithRdServerImpl private constructor( + private val child: LifetimedProcess, + serverFactory: (Lifetime) -> Protocol +) : ProcessWithRdServer, LifetimedProcess by child { + override val protocol = serverFactory(lifetime) + + override fun terminate() { + silent { + protocol.synchronizationModel.stopProcess.fire(Unit) + } + child.terminate() + } + + companion object { + suspend operator fun invoke( + child: LifetimedProcess, serverFactory: (Lifetime) -> Protocol + ): ProcessWithRdServerImpl = ProcessWithRdServerImpl(child, serverFactory).terminateOnException { + it.apply { protocol.wire.connected.adviseForConditionAsync(lifetime).await() } + } + } + + override suspend fun awaitProcessReady(): ProcessWithRdServer { + protocol.scheduler.pump(lifetime) { + protocol.synchronizationModel + } + processSyncDirectory.mkdirs() + + // there 2 stages at rd protocol initialization: + // 1. we need to bind all entities - for ex. generated model and custom signal + // because we cannot operate with unbound + // 2. we need to wait when all that entities bound on the other side + // because when we fire something that is not bound on another side - we will lose this call + // to guarantee 2nd stage success - there is custom simple synchronization: + // 1. child process will create file "${port}.created" - this indicates that child process is ready to receive messages + // 2. we will test the connection via sync RdSignal + // only then we can successfully start operating + val pid = process.getPid.toInt() + val syncFile = File(processSyncDirectory, childCreatedFileName(port)) + + while (lifetime.isAlive) { + if (Files.deleteIfExists(syncFile.toPath())) { + logger.trace { "process $pid for port $port: signal file deleted connecting" } + break + } + + delay(fileWaitTimeoutMillis) + } + + protocol.synchronizationModel.synchronizationSignal.let { sync -> + val messageFromChild = sync.adviseForConditionAsync(lifetime) { it == "child" } + + while (messageFromChild.isActive) { + sync.fire("main") + delay(10) + } + } + + lifetime.onTermination { + if (syncFile.exists()) { + logger.trace { "process $pid for port $port: on terminating syncFile existed" } + syncFile.delete() + } + } + + return this + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/RdSettingsContainer.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/RdSettingsContainer.kt new file mode 100644 index 0000000000..420a24374a --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/RdSettingsContainer.kt @@ -0,0 +1,56 @@ +package org.utbot.rd + +import mu.KLogger +import org.utbot.common.SettingsContainer +import org.utbot.common.SettingsContainerFactory +import org.utbot.rd.generated.SettingForArgument +import org.utbot.rd.generated.SettingsModel +import kotlin.properties.PropertyDelegateProvider +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + +class RdSettingsContainerFactory(private val settingsModel: SettingsModel) : SettingsContainerFactory { + override fun createSettingsContainer( + logger: KLogger, + defaultKeyForSettingsPath: String, + defaultSettingsPath: String? + ): SettingsContainer { + return RdSettingsContainer(logger, defaultKeyForSettingsPath, settingsModel) + } +} + +class RdSettingsContainer(val logger: KLogger, val key: String, val settingsModel: SettingsModel) : SettingsContainer { + + override fun settingFor( + defaultValue: T, + range : Triple>?, + converter: (String) -> T + ): PropertyDelegateProvider> { + return PropertyDelegateProvider { _, _ -> + object : ReadWriteProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + val params = SettingForArgument(key, property.name) + return settingsModel.settingFor.startBlocking(params).value?.let { + try { + return range?.run { + // Coerce parsed value into the specified range + minOf( + range.second, + maxOf(converter(it), range.first, range.third), + range.third + ) + } ?: converter(it) + } catch (e: Exception) { + logger.warn("Cannot parse value for $key, default value [$defaultValue] will be used instead") { e } + defaultValue + } + } ?: defaultValue + } + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + throw IllegalStateException("Setting properties allowed only from plugin process") + } + } + } + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/UtRdCoroutineScope.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/UtRdCoroutineScope.kt new file mode 100644 index 0000000000..5d46179e04 --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/UtRdCoroutineScope.kt @@ -0,0 +1,23 @@ +package org.utbot.rd + +import com.jetbrains.rd.framework.util.RdCoroutineScope +import com.jetbrains.rd.framework.util.asCoroutineDispatcher +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.threading.SingleThreadScheduler + +private val coroutineDispatcher = SingleThreadScheduler(Lifetime.Eternal, "UtCoroutineScheduler").asCoroutineDispatcher + +class UtRdCoroutineScope(lifetime: Lifetime) : RdCoroutineScope(lifetime) { + companion object { + val current = UtRdCoroutineScope(Lifetime.Eternal) + fun initialize() { + // only to load and initialize class + } + } + + init { + override(lifetime, this) + } + + override val defaultDispatcher = coroutineDispatcher +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/UtRdUtil.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/UtRdUtil.kt new file mode 100644 index 0000000000..4dabe27c7b --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/UtRdUtil.kt @@ -0,0 +1,127 @@ +package org.utbot.rd + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.util.NetUtils +import com.jetbrains.rd.framework.util.synchronizeWith +import com.jetbrains.rd.util.* +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.lifetime.LifetimeDefinition +import com.jetbrains.rd.util.lifetime.throwIfNotAlive +import com.jetbrains.rd.util.reactive.IScheduler +import com.jetbrains.rd.util.reactive.ISource +import com.jetbrains.rd.util.threading.SingleThreadScheduler +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.runBlocking +import org.utbot.common.LoggerWithLogMethod + +suspend fun ProcessWithRdServer.onScheduler(block: () -> T): T { + val deffered = CompletableDeferred() + protocol.scheduler.invokeOrQueue { deffered.complete(block()) } + return deffered.await() +} + +fun ProcessWithRdServer.onSchedulerBlocking(block: () -> T): T = runBlocking { onScheduler(block) } + +fun IRdCall.startBlocking(req: TReq): TRes { + val call = this + // We do not use RdCall.sync because it requires timeouts for execution, after which request will be stopped. + // Some requests, for example test generation, might be either really long, or have their own timeouts. + // To honour their timeout logic we do not use RdCall.sync. + return runBlocking { call.startSuspending(req) } +} + +/** + * Terminates lifetime if exception occurs. + * Useful when initializing classes, for ex. if an exception occurs while some parts already bound to lifetime, + * and you need to terminate those parts + */ +inline fun LifetimeDefinition.terminateOnException(block: (Lifetime) -> T): T { + try { + return block(this) + } catch (e: Throwable) { + this.terminate() + throw e + } +} + +/** + * Suspend until provided lifetime terminates or coroutine cancelles + */ +suspend fun Lifetime.awaitTermination() { + val deferred = CompletableDeferred() + this.onTermination { deferred.complete(Unit) } + deferred.await() +} + +/** + * Executes block on IScheduler and suspends until completed + * @param lifetime indicates whether it's operation is still required + * @throws CancellationException if coroutine was cancelled or lifetime was terminated before block completed +*/ +suspend fun IScheduler.pump(lifetime: Lifetime, block: (Lifetime) -> T): T { + val ldef = lifetime.createNested() + val deferred = CompletableDeferred() + + ldef.synchronizeWith(deferred) + + this.invokeOrQueue { + deferred.complete(block(ldef)) + } + + return deferred.await() +} + +suspend fun IScheduler.pump(block: (Lifetime) -> T): T = this.pump(Lifetime.Eternal, block) + +/** + * Asynchronously checks the condition. + * The condition can be satisfied or canceled. + * As soon as the condition is checked, the lifetime is terminated. + * In order to cancel the calculation, use the function return value of Deferred type. + * + * @see kotlinx.coroutines.withTimeout coroutine builder in case you need cancel the calculation by timeout. + */ +fun ISource.adviseForConditionAsync(lifetime: Lifetime, condition: (T) -> Boolean): Deferred { + val ldef = lifetime.createNested() + val deferred = CompletableDeferred() + + ldef.synchronizeWith(deferred) + + this.advise(ldef) { + if(condition(it)) { + deferred.complete(Unit) + } + } + + return deferred +} + +fun ISource.adviseForConditionAsync(lifetime: Lifetime): Deferred { + return this.adviseForConditionAsync(lifetime) { it } +} + +/** + * Process will not be started if lifetime is not alive + */ +suspend fun startUtProcessWithRdServer( + lifetime: Lifetime? = null, + factory: (Int) -> Process +): ProcessWithRdServer { + lifetime?.throwIfNotAlive() + + val port = NetUtils.findFreePort(0) + + return factory(port).withRdServer(lifetime) { + val name = "Server$port" + val rdServerProtocolScheduler = SingleThreadScheduler(it, "Scheduler for $name") + Protocol( + "Server", + Serializers(), + Identities(IdKind.Server), + rdServerProtocolScheduler, + SocketWire.Server(it, rdServerProtocolScheduler, port, "ServerSocket"), + it + ) + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/exceptions/InstantProcessDeathException.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/exceptions/InstantProcessDeathException.kt new file mode 100644 index 0000000000..015abbf0b0 --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/exceptions/InstantProcessDeathException.kt @@ -0,0 +1,16 @@ +package org.utbot.rd.exceptions + +/** + * This exception is designed to be thrown any time you start child process with rd, + * but it dies before rd initiated, implicating that the problem probably in CLI arguments + */ +abstract class InstantProcessDeathException(private val debugPort: Int, private val isProcessDebug: Boolean) : Exception() { + override val message: String? + get() { + var text = "Process died before any request was executed, check process log file." + if (isProcessDebug) { + text += " Process requested debug on port - $debugPort, check if port is open." + } + return text + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/generated/LoggerModel.Generated.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/LoggerModel.Generated.kt new file mode 100644 index 0000000000..d1dbc4deeb --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/LoggerModel.Generated.kt @@ -0,0 +1,185 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.rd.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [LoggerModel.kt:8] + */ +class LoggerModel private constructor( + private val _initRemoteLogging: RdSignal, + private val _log: RdSignal, + private val _getCategoryMinimalLogLevel: RdOptionalProperty +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + serializers.register(LogArguments) + } + + + @JvmStatic + @JvmName("internalCreateModel") + @Deprecated("Use create instead", ReplaceWith("create(lifetime, protocol)")) + internal fun createModel(lifetime: Lifetime, protocol: IProtocol): LoggerModel { + @Suppress("DEPRECATION") + return create(lifetime, protocol) + } + + @JvmStatic + @Deprecated("Use protocol.loggerModel or revise the extension scope instead", ReplaceWith("protocol.loggerModel")) + fun create(lifetime: Lifetime, protocol: IProtocol): LoggerModel { + LoggerRoot.register(protocol.serializers) + + return LoggerModel() + } + + + const val serializationHash = -4262122198555578601L + + } + override val serializersOwner: ISerializersOwner get() = LoggerModel + override val serializationHash: Long get() = LoggerModel.serializationHash + + //fields + val initRemoteLogging: IAsyncSignal get() = _initRemoteLogging + val log: IAsyncSignal get() = _log + + /** + * Property value - integer for com.jetbrains.rd.util.LogLevel. + */ + val getCategoryMinimalLogLevel: IOptProperty get() = _getCategoryMinimalLogLevel + //methods + //initializer + init { + _getCategoryMinimalLogLevel.optimizeNested = true + } + + init { + _initRemoteLogging.async = true + _log.async = true + _getCategoryMinimalLogLevel.async = true + } + + init { + bindableChildren.add("initRemoteLogging" to _initRemoteLogging) + bindableChildren.add("log" to _log) + bindableChildren.add("getCategoryMinimalLogLevel" to _getCategoryMinimalLogLevel) + } + + //secondary constructor + private constructor( + ) : this( + RdSignal(FrameworkMarshallers.Void), + RdSignal(LogArguments), + RdOptionalProperty(FrameworkMarshallers.Int) + ) + + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("LoggerModel (") + printer.indent { + print("initRemoteLogging = "); _initRemoteLogging.print(printer); println() + print("log = "); _log.print(printer); println() + print("getCategoryMinimalLogLevel = "); _getCategoryMinimalLogLevel.print(printer); println() + } + printer.print(")") + } + //deepClone + override fun deepClone(): LoggerModel { + return LoggerModel( + _initRemoteLogging.deepClonePolymorphic(), + _log.deepClonePolymorphic(), + _getCategoryMinimalLogLevel.deepClonePolymorphic() + ) + } + //contexts +} +val IProtocol.loggerModel get() = getOrCreateExtension(LoggerModel::class) { @Suppress("DEPRECATION") LoggerModel.create(lifetime, this) } + + + +/** + * @property logLevelOrdinal Integer value for com.jetbrains.rd.util.LogLevel + * #### Generated from [LoggerModel.kt:9] + */ +data class LogArguments ( + val category: String, + val logLevelOrdinal: Int, + val message: String +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = LogArguments::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): LogArguments { + val category = buffer.readString() + val logLevelOrdinal = buffer.readInt() + val message = buffer.readString() + return LogArguments(category, logLevelOrdinal, message) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: LogArguments) { + buffer.writeString(value.category) + buffer.writeInt(value.logLevelOrdinal) + buffer.writeString(value.message) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as LogArguments + + if (category != other.category) return false + if (logLevelOrdinal != other.logLevelOrdinal) return false + if (message != other.message) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + category.hashCode() + __r = __r*31 + logLevelOrdinal.hashCode() + __r = __r*31 + message.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("LogArguments (") + printer.indent { + print("category = "); category.print(printer); println() + print("logLevelOrdinal = "); logLevelOrdinal.print(printer); println() + print("message = "); message.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/generated/LoggerRoot.Generated.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/LoggerRoot.Generated.kt new file mode 100644 index 0000000000..ed678d075d --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/LoggerRoot.Generated.kt @@ -0,0 +1,59 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.rd.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [LoggerModel.kt:7] + */ +class LoggerRoot private constructor( +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + LoggerRoot.register(serializers) + LoggerModel.register(serializers) + } + + + + + + const val serializationHash = -3743703762234585836L + + } + override val serializersOwner: ISerializersOwner get() = LoggerRoot + override val serializationHash: Long get() = LoggerRoot.serializationHash + + //fields + //methods + //initializer + //secondary constructor + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("LoggerRoot (") + printer.print(")") + } + //deepClone + override fun deepClone(): LoggerRoot { + return LoggerRoot( + ) + } + //contexts +} diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SettingsModel.Generated.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SettingsModel.Generated.kt new file mode 100644 index 0000000000..13dffb1dca --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SettingsModel.Generated.kt @@ -0,0 +1,214 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.rd.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [SettingsModel.kt:9] + */ +class SettingsModel private constructor( + private val _settingFor: RdCall +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + serializers.register(SettingForArgument) + serializers.register(SettingForResult) + } + + + @JvmStatic + @JvmName("internalCreateModel") + @Deprecated("Use create instead", ReplaceWith("create(lifetime, protocol)")) + internal fun createModel(lifetime: Lifetime, protocol: IProtocol): SettingsModel { + @Suppress("DEPRECATION") + return create(lifetime, protocol) + } + + @JvmStatic + @Deprecated("Use protocol.settingsModel or revise the extension scope instead", ReplaceWith("protocol.settingsModel")) + fun create(lifetime: Lifetime, protocol: IProtocol): SettingsModel { + SettingsRoot.register(protocol.serializers) + + return SettingsModel() + } + + + const val serializationHash = 5155891414073322635L + + } + override val serializersOwner: ISerializersOwner get() = SettingsModel + override val serializationHash: Long get() = SettingsModel.serializationHash + + //fields + val settingFor: RdCall get() = _settingFor + //methods + //initializer + init { + _settingFor.async = true + } + + init { + bindableChildren.add("settingFor" to _settingFor) + } + + //secondary constructor + private constructor( + ) : this( + RdCall(SettingForArgument, SettingForResult) + ) + + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SettingsModel (") + printer.indent { + print("settingFor = "); _settingFor.print(printer); println() + } + printer.print(")") + } + //deepClone + override fun deepClone(): SettingsModel { + return SettingsModel( + _settingFor.deepClonePolymorphic() + ) + } + //contexts +} +val IProtocol.settingsModel get() = getOrCreateExtension(SettingsModel::class) { @Suppress("DEPRECATION") SettingsModel.create(lifetime, this) } + + + +/** + * #### Generated from [SettingsModel.kt:10] + */ +data class SettingForArgument ( + val key: String, + val propertyName: String +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = SettingForArgument::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SettingForArgument { + val key = buffer.readString() + val propertyName = buffer.readString() + return SettingForArgument(key, propertyName) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SettingForArgument) { + buffer.writeString(value.key) + buffer.writeString(value.propertyName) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as SettingForArgument + + if (key != other.key) return false + if (propertyName != other.propertyName) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + key.hashCode() + __r = __r*31 + propertyName.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SettingForArgument (") + printer.indent { + print("key = "); key.print(printer); println() + print("propertyName = "); propertyName.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [SettingsModel.kt:14] + */ +data class SettingForResult ( + val value: String? +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = SettingForResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SettingForResult { + val value = buffer.readNullable { buffer.readString() } + return SettingForResult(value) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SettingForResult) { + buffer.writeNullable(value.value) { buffer.writeString(it) } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as SettingForResult + + if (value != other.value) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + if (value != null) value.hashCode() else 0 + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SettingForResult (") + printer.indent { + print("value = "); value.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SettingsRoot.Generated.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SettingsRoot.Generated.kt new file mode 100644 index 0000000000..4c17349868 --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SettingsRoot.Generated.kt @@ -0,0 +1,59 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.rd.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [SettingsModel.kt:7] + */ +class SettingsRoot private constructor( +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + SettingsRoot.register(serializers) + SettingsModel.register(serializers) + } + + + + + + const val serializationHash = -414203168893339225L + + } + override val serializersOwner: ISerializersOwner get() = SettingsRoot + override val serializationHash: Long get() = SettingsRoot.serializationHash + + //fields + //methods + //initializer + //secondary constructor + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SettingsRoot (") + printer.print(")") + } + //deepClone + override fun deepClone(): SettingsRoot { + return SettingsRoot( + ) + } + //contexts +} diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationModel.Generated.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationModel.Generated.kt new file mode 100644 index 0000000000..a42b46fb13 --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationModel.Generated.kt @@ -0,0 +1,110 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.rd.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [SynchronizationModel.kt:8] + */ +class SynchronizationModel private constructor( + private val _suspendTimeoutTimer: RdCall, + private val _synchronizationSignal: RdSignal, + private val _stopProcess: RdSignal +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + } + + + @JvmStatic + @JvmName("internalCreateModel") + @Deprecated("Use create instead", ReplaceWith("create(lifetime, protocol)")) + internal fun createModel(lifetime: Lifetime, protocol: IProtocol): SynchronizationModel { + @Suppress("DEPRECATION") + return create(lifetime, protocol) + } + + @JvmStatic + @Deprecated("Use protocol.synchronizationModel or revise the extension scope instead", ReplaceWith("protocol.synchronizationModel")) + fun create(lifetime: Lifetime, protocol: IProtocol): SynchronizationModel { + SynchronizationRoot.register(protocol.serializers) + + return SynchronizationModel() + } + + + const val serializationHash = -8851121542976813112L + + } + override val serializersOwner: ISerializersOwner get() = SynchronizationModel + override val serializationHash: Long get() = SynchronizationModel.serializationHash + + //fields + val suspendTimeoutTimer: RdCall get() = _suspendTimeoutTimer + val synchronizationSignal: IAsyncSignal get() = _synchronizationSignal + + /** + * This command tells the instrumented process to stop + */ + val stopProcess: IAsyncSignal get() = _stopProcess + //methods + //initializer + init { + _suspendTimeoutTimer.async = true + _synchronizationSignal.async = true + _stopProcess.async = true + } + + init { + bindableChildren.add("suspendTimeoutTimer" to _suspendTimeoutTimer) + bindableChildren.add("synchronizationSignal" to _synchronizationSignal) + bindableChildren.add("stopProcess" to _stopProcess) + } + + //secondary constructor + private constructor( + ) : this( + RdCall(FrameworkMarshallers.Bool, FrameworkMarshallers.Void), + RdSignal(FrameworkMarshallers.String), + RdSignal(FrameworkMarshallers.Void) + ) + + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SynchronizationModel (") + printer.indent { + print("suspendTimeoutTimer = "); _suspendTimeoutTimer.print(printer); println() + print("synchronizationSignal = "); _synchronizationSignal.print(printer); println() + print("stopProcess = "); _stopProcess.print(printer); println() + } + printer.print(")") + } + //deepClone + override fun deepClone(): SynchronizationModel { + return SynchronizationModel( + _suspendTimeoutTimer.deepClonePolymorphic(), + _synchronizationSignal.deepClonePolymorphic(), + _stopProcess.deepClonePolymorphic() + ) + } + //contexts +} +val IProtocol.synchronizationModel get() = getOrCreateExtension(SynchronizationModel::class) { @Suppress("DEPRECATION") SynchronizationModel.create(lifetime, this) } + diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationRoot.Generated.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationRoot.Generated.kt new file mode 100644 index 0000000000..faf8b2a2ce --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationRoot.Generated.kt @@ -0,0 +1,59 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.rd.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [SynchronizationModel.kt:6] + */ +class SynchronizationRoot private constructor( +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + SynchronizationRoot.register(serializers) + SynchronizationModel.register(serializers) + } + + + + + + const val serializationHash = -8945393054954668256L + + } + override val serializersOwner: ISerializersOwner get() = SynchronizationRoot + override val serializationHash: Long get() = SynchronizationRoot.serializationHash + + //fields + //methods + //initializer + //secondary constructor + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SynchronizationRoot (") + printer.print(")") + } + //deepClone + override fun deepClone(): SynchronizationRoot { + return SynchronizationRoot( + ) + } + //contexts +} diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdConsoleLogger.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdConsoleLogger.kt new file mode 100644 index 0000000000..b1e5dfd01e --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdConsoleLogger.kt @@ -0,0 +1,31 @@ +package org.utbot.rd.loggers + +import com.jetbrains.rd.util.* +import org.utbot.common.timeFormatter +import java.io.PrintStream +import java.time.LocalDateTime + +class UtRdConsoleLogger( + private val loggersLevel: LogLevel, + private val streamToWrite: PrintStream, + private val category: String +) : Logger { + override fun isEnabled(level: LogLevel): Boolean { + return level >= loggersLevel + } + + private fun format(category: String, level: LogLevel, message: Any?, throwable: Throwable?) : String { + val throwableToPrint = if (level < LogLevel.Error) throwable else throwable ?: Exception("No exception was actually thrown, this exception is used purely to log trace") + val rdCategory = if (category.isNotEmpty()) "RdCategory: ${category.substringAfterLast('.').padEnd(25)} | " else "" + return "${LocalDateTime.now().format(timeFormatter)} | ${level.toString().uppercase().padEnd(5)} | $rdCategory${message?.toString()?:""} ${throwableToPrint?.getThrowableText()?.let { "| $it" }?:""}" + } + + override fun log(level: LogLevel, message: Any?, throwable: Throwable?) { + if (!isEnabled(level)) + return + + val msg = format(category, level, message, throwable) + + streamToWrite.println(msg) + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdConsoleLoggerFactory.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdConsoleLoggerFactory.kt new file mode 100644 index 0000000000..ad2d02a8a1 --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdConsoleLoggerFactory.kt @@ -0,0 +1,19 @@ +package org.utbot.rd.loggers + +import com.jetbrains.rd.util.ILoggerFactory +import com.jetbrains.rd.util.LogLevel +import com.jetbrains.rd.util.Logger +import java.io.PrintStream + +/** + * Creates loggers with predefined log level, that writes to provided stream. + * Create logger category is added to message. + */ +class UtRdConsoleLoggerFactory( + private val loggersLevel: LogLevel, + private val streamToWrite: PrintStream +) : ILoggerFactory { + override fun getLogger(category: String): Logger { + return UtRdConsoleLogger(loggersLevel, streamToWrite, category) + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdKLogger.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdKLogger.kt new file mode 100644 index 0000000000..4016eb2b36 --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdKLogger.kt @@ -0,0 +1,55 @@ +package org.utbot.rd.loggers + +import com.jetbrains.rd.util.* +import mu.KLogger + +/** + * Adapter from RD Logger to KLogger + */ +class UtRdKLogger( + private val realLogger: KLogger, + val category: String, + private val logTraceOnError: Boolean = true +) : Logger { + val logLevel: LogLevel + get() { + return when { + realLogger.isTraceEnabled -> LogLevel.Trace + realLogger.isDebugEnabled -> LogLevel.Debug + realLogger.isInfoEnabled -> LogLevel.Info + realLogger.isWarnEnabled -> LogLevel.Warn + realLogger.isErrorEnabled -> LogLevel.Error + else -> LogLevel.Fatal + } + } + + override fun isEnabled(level: LogLevel): Boolean { + return level >= logLevel + } + + private fun format(level: LogLevel, message: Any?, throwable: Throwable?): String { + val throwableToPrint = + if (logTraceOnError && level >= LogLevel.Error) + throwable ?: Exception("No exception was actually thrown, this exception is used purely to log trace") + else + throwable + val rdCategory = if (category.isNotEmpty()) "RdCategory: ${category.substringAfterLast('.').padEnd(25)} | " else "" + return "$rdCategory${message?.toString() ?: ""} ${throwableToPrint?.getThrowableText()?.let { "| $it" } ?: ""}" + } + + override fun log(level: LogLevel, message: Any?, throwable: Throwable?) { + if (!isEnabled(level) || (message == null && throwable == null)) + return + + val msg = format(level, message, throwable) + + when (level) { + LogLevel.Trace -> realLogger.trace(msg) + LogLevel.Debug -> realLogger.debug(msg) + LogLevel.Info -> realLogger.info(msg) + LogLevel.Warn -> realLogger.warn(msg) + LogLevel.Error -> realLogger.error(msg) + LogLevel.Fatal -> realLogger.error(msg) + } + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdKLoggerFactory.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdKLoggerFactory.kt new file mode 100644 index 0000000000..7982c963f3 --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdKLoggerFactory.kt @@ -0,0 +1,16 @@ +package org.utbot.rd.loggers + +import com.jetbrains.rd.util.ILoggerFactory +import com.jetbrains.rd.util.Logger +import mu.KLogger + +/** + * Creates RD loggers that writes to provided KLogger. + * + * Created logger category is added to message. + */ +class UtRdKLoggerFactory(private val realLogger: KLogger) : ILoggerFactory { + override fun getLogger(category: String): Logger { + return UtRdKLogger(realLogger, category) + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdLogUtil.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdLogUtil.kt new file mode 100644 index 0000000000..bc32202782 --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdLogUtil.kt @@ -0,0 +1,45 @@ +package org.utbot.rd.loggers + +import com.jetbrains.rd.util.* +import com.jetbrains.rd.util.lifetime.Lifetime +import mu.KLogger +import org.utbot.common.LoggerWithLogMethod +import org.utbot.rd.generated.LoggerModel + +fun Logger.withLevel(logLevel: LogLevel): LoggerWithLogMethod = LoggerWithLogMethod { + this.log(logLevel, it) +} + +fun Logger.info(): LoggerWithLogMethod = LoggerWithLogMethod { + this.info(it) +} +fun Logger.debug(): LoggerWithLogMethod = LoggerWithLogMethod { + this.debug(it) +} +fun Logger.trace(): LoggerWithLogMethod = LoggerWithLogMethod { + this.trace(it) +} + +fun overrideDefaultRdLoggerFactoryWithKLogger(logger: KLogger) { + if (Statics().get() !is UtRdKLoggerFactory) { + Logger.set(Lifetime.Eternal, UtRdKLoggerFactory(logger)) + } +} + +fun LoggerModel.setup(logger: KLogger, processLifetime: Lifetime) { + // we do not log trace on error here because it's a job of clint-side process logger + // server-side process logger can only log trace of where it was set up from which isn't particularly useful + val rdLogger = UtRdKLogger(logger, "", logTraceOnError = false) + // currently we do not specify log level for different categories + // though it is possible with some additional map on categories -> consider performance + // this logLevel is obtained from KotlinLogger + getCategoryMinimalLogLevel.set(rdLogger.logLevel.ordinal) + + log.advise(processLifetime) { + val logLevel = UtRdRemoteLogger.logLevelValues[it.logLevelOrdinal] + // assume throwable already in message + rdLogger.log(logLevel, it.message, null) + } + + initRemoteLogging.fire(Unit) +} diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdRemoteLogger.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdRemoteLogger.kt new file mode 100644 index 0000000000..648f32ab35 --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdRemoteLogger.kt @@ -0,0 +1,63 @@ +package org.utbot.rd.loggers + +import com.jetbrains.rd.util.LogLevel +import com.jetbrains.rd.util.Logger +import com.jetbrains.rd.util.collections.CountingSet +import com.jetbrains.rd.util.getThrowableText +import com.jetbrains.rd.util.reactive.valueOrThrow +import com.jetbrains.rd.util.threadLocalWithInitial +import org.utbot.rd.generated.LogArguments +import org.utbot.rd.generated.LoggerModel +import org.utbot.rd.startBlocking + +class UtRdRemoteLogger( + private val loggerModel: LoggerModel, + private val category: String +) : Logger { + private val logLevel: LogLevel by lazy { logLevelValues[loggerModel.getCategoryMinimalLogLevel.valueOrThrow] } + + companion object { + val logLevelValues = LogLevel.values() + private val threadLocalExecutingBackingFiled: ThreadLocal> = + threadLocalWithInitial { CountingSet() } + + internal val threadLocalExecuting get() = threadLocalExecutingBackingFiled.get() + } + + override fun isEnabled(level: LogLevel): Boolean { + // On every protocol sends/receives event RD to its own loggers. + // They will be redirected here, and then sent via RD to another process, + // producing new log event again thus causing infinite recursion. + // The solution is to prohibit writing any logs from inside logger. + // This is implemented via thread local counter per logger, + // which will be incremented when this logger fires event to another process, + // and deny all following log events until previous log event is delivered. + if (threadLocalExecuting[this] > 0) + return false + + return level >= logLevel + } + + private fun format(message: Any?, throwable: Throwable?): String { + val rdCategory = if (category.isNotEmpty()) "RdCategory: ${category.substringAfterLast('.').padEnd(25)} | " else "" + return "$rdCategory${message?.toString() ?: ""}${ + throwable?.getThrowableText()?.let { "${message?.let { " | " } ?: ""}$it" } ?: "" + }" + } + + override fun log(level: LogLevel, message: Any?, throwable: Throwable?) { + if (!isEnabled(level) || message == null && throwable == null) + return + + threadLocalExecuting.add(this, +1) + try { + val renderedMsg = format(message, throwable) + val args = LogArguments(category, level.ordinal, renderedMsg) + + loggerModel.log.fire(args) + } finally { + threadLocalExecuting.add(this, -1) + } + } + +} \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdRemoteLoggerFactory.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdRemoteLoggerFactory.kt new file mode 100644 index 0000000000..e2024376a3 --- /dev/null +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/loggers/UtRdRemoteLoggerFactory.kt @@ -0,0 +1,17 @@ +package org.utbot.rd.loggers + +import com.jetbrains.rd.util.ILoggerFactory +import com.jetbrains.rd.util.Logger +import org.utbot.rd.generated.LoggerModel + +/** + * Creates loggers that are mapped to the remote counter-part. + * Category is added to message +*/ +class UtRdRemoteLoggerFactory( + private val loggerModel: LoggerModel +) : ILoggerFactory { + override fun getLogger(category: String): Logger { + return UtRdRemoteLogger(loggerModel, category) + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/lifetimedProcessMock/org/utbot/rd/tests/LifetimedProcessMock.kt b/utbot-rd/src/main/lifetimedProcessMock/org/utbot/rd/tests/LifetimedProcessMock.kt new file mode 100644 index 0000000000..f1f472fc7a --- /dev/null +++ b/utbot-rd/src/main/lifetimedProcessMock/org/utbot/rd/tests/LifetimedProcessMock.kt @@ -0,0 +1,12 @@ +package org.utbot.rd.tests + +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking + +fun main(args: Array) { + val length = args.single().toLong() + + runBlocking { + delay(length * 1000) + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/processWithRdServerMock/org/utbot/rd/tests/ProcessWithRdServerMock.kt b/utbot-rd/src/main/processWithRdServerMock/org/utbot/rd/tests/ProcessWithRdServerMock.kt new file mode 100644 index 0000000000..304add54ce --- /dev/null +++ b/utbot-rd/src/main/processWithRdServerMock/org/utbot/rd/tests/ProcessWithRdServerMock.kt @@ -0,0 +1,40 @@ +package org.utbot.rd.tests + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.util.lifetime.LifetimeDefinition +import com.jetbrains.rd.util.reactive.IScheduler +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking + +fun main(args: Array): Unit { + val def = LifetimeDefinition() + val length = args.first().toLong() + val shouldstart = args.last().toBoolean() + val port = args[1].toInt() + val scheduler = object : IScheduler { + override val isActive = true + + override fun flush() {} + + override fun queue(action: () -> Unit) { + action() + } + } + + if (shouldstart) { + val protocol = Protocol( + "TestClient", + Serializers(), + Identities(IdKind.Client), + scheduler, + SocketWire.Client(def, scheduler, port), + def + ) + println(protocol.name) + } + + runBlocking { + delay(length * 1000) + } + def.terminate() +} \ No newline at end of file diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/CSharpModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/CSharpModel.kt new file mode 100644 index 0000000000..cc75a8ff12 --- /dev/null +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/CSharpModel.kt @@ -0,0 +1,44 @@ +@file:Suppress("unused") +package org.utbot.rd.models + +import com.jetbrains.rd.generator.nova.* + +object CSharpRoot: Root() + +object VSharpModel: Ext(CSharpRoot) { + val methodDescriptor = structdef { + field("methodName", PredefinedType.string) + field("typeName", PredefinedType.string) + field("hasNoOverloads", PredefinedType.bool) + field("parameters", immutableList(PredefinedType.string)) + } + + val mapEntry = structdef { + field("key", PredefinedType.string) + field("value", PredefinedType.string) + } + + val generateArguments = structdef { + field("assemblyPath", PredefinedType.string) + field("projectCsprojPath", PredefinedType.string) + field("solutionFilePath", PredefinedType.string) + field("methods", immutableList(methodDescriptor)) + field("generationTimeoutInSeconds", PredefinedType.int) + field("targetFramework", PredefinedType.string.nullable) + field("assembliesFullNameToTheirPath", immutableList(mapEntry)) + } + + val generateResults = structdef { + field("generatedProjectPath", PredefinedType.string.nullable) + field("generatedFilesPaths", immutableList(PredefinedType.string)) + field("exceptionMessage", PredefinedType.string.nullable) + field("testsCount", PredefinedType.int) + field("errorsCount", PredefinedType.int) + } + + init { + call("generate", generateArguments, generateResults).async + signal("ping", PredefinedType.string).async + signal("log", PredefinedType.string).async + } +} diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt new file mode 100644 index 0000000000..bc8ffcabfb --- /dev/null +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt @@ -0,0 +1,150 @@ +@file:Suppress("unused") +package org.utbot.rd.models + +import com.jetbrains.rd.generator.nova.* + +object EngineProcessRoot : Root() + +object RdInstrumenterAdapter: Ext(EngineProcessRoot) { + val computeSourceFileByClassArguments = structdef { + field("canonicalClassName", PredefinedType.string) + } + init { + call("computeSourceFileByClass", computeSourceFileByClassArguments, PredefinedType.string.nullable).async + } +} + +object RdSourceFindingStrategy : Ext(EngineProcessRoot) { + val sourceStrategyMethodArgs = structdef { + field("testSetId", PredefinedType.long) + field("classFqn", PredefinedType.string) + field("extension", PredefinedType.string.nullable) + } + + init { + call("testsRelativePath", PredefinedType.long, PredefinedType.string).async + call("getSourceRelativePath", sourceStrategyMethodArgs, PredefinedType.string).async + call("getSourceFile", sourceStrategyMethodArgs, PredefinedType.string.nullable).async + } +} + +object EngineProcessModel : Ext(EngineProcessRoot) { + val jdkInfo = structdef { + field("path", PredefinedType.string) + field("version", PredefinedType.int) + } + val testGeneratorParams = structdef { + field("buildDir", array(PredefinedType.string)) + field("classpath", PredefinedType.string.nullable) + field("dependencyPaths", PredefinedType.string) + field("jdkInfo", jdkInfo) + field("applicationContext", array(PredefinedType.byte)) + } + val testClassNameParams = structdef { + field("classUnderTest", array(PredefinedType.byte)) + } + val testClassNameResult = structdef { + field("testClassName", PredefinedType.string) + } + val generateParams = structdef { + // generate + field("methods", array(PredefinedType.byte)) + field("mockStrategy", PredefinedType.string) + field("chosenClassesToMockAlways", array(PredefinedType.byte)) + field("timeout", PredefinedType.long) + // testflow + field("generationTimeout", PredefinedType.long) + field("isSymbolicEngineEnabled", PredefinedType.bool) + field("isFuzzingEnabled", PredefinedType.bool) + field("fuzzingValue", PredefinedType.double) + // method filters + field("searchDirectory", PredefinedType.string) + // taint analysis + field("taintConfigPath", PredefinedType.string.nullable) + } + val generateResult = structdef { + field("notEmptyCases", PredefinedType.int) + field("testSetsId", PredefinedType.long) + } + val renderParams = structdef { + field("testSetsId", PredefinedType.long) + field("classUnderTest", array(PredefinedType.byte)) + field("projectType", PredefinedType.string) + field("paramNames", array(PredefinedType.byte)) + field("generateUtilClassFile", PredefinedType.bool) + field("testFramework", PredefinedType.string) + field("mockFramework", PredefinedType.string) + field("codegenLanguage", PredefinedType.string) + field("parameterizedTestSource", PredefinedType.string) + field("staticsMocking", PredefinedType.string) + field("forceStaticMocking", array(PredefinedType.byte)) + field("generateWarningsForStaticMocking", PredefinedType.bool) + field("runtimeExceptionTestsBehaviour", PredefinedType.string) + field("hangingTestsTimeout", PredefinedType.long) + field("enableTestsTimeout", PredefinedType.bool) + field("testClassPackageName", PredefinedType.string) + } + val renderResult = structdef { + field("generatedCode", PredefinedType.string) + field("utilClassKind", PredefinedType.string.nullable) + } + val setupContextParams = structdef { + field("classpathForUrlsClassloader", immutableList(PredefinedType.string)) + } + val methodDescription = structdef { + field("name", PredefinedType.string) + field("containingClass", PredefinedType.string.nullable) + field("parametersTypes", immutableList(PredefinedType.string.nullable)) + } + val findMethodsInClassMatchingSelectedArguments = structdef { + field("classId", array(PredefinedType.byte)) + field("methodDescriptions", immutableList(methodDescription)) + } + val findMethodsInClassMatchingSelectedResult = structdef { + field("executableIds", array(PredefinedType.byte)) + } + val findMethodParamNamesArguments = structdef { + field("classId", array(PredefinedType.byte)) + field("bySignature", array(PredefinedType.byte)) + } + val findMethodParamNamesResult = structdef { + field("paramNames", array(PredefinedType.byte)) + } + val writeSarifReportArguments = structdef { + field("testSetsId", PredefinedType.long) + field("reportFilePath", PredefinedType.string) + field("generatedTestsCode", PredefinedType.string) + } + val generateTestReportArgs = structdef { + field("eventLogMessage", PredefinedType.string.nullable) + field("testPackageName", PredefinedType.string.nullable) + field("isMultiPackage", PredefinedType.bool) + field("forceMockWarning", PredefinedType.string.nullable) + field("forceStaticMockWarnings", PredefinedType.string.nullable) + field("testFrameworkWarning", PredefinedType.string.nullable) + field("hasInitialWarnings", PredefinedType.bool) + } + val generateTestReportResult = structdef { + field("notifyMessage", PredefinedType.string) + field("statistics", PredefinedType.string.nullable) + field("hasWarnings", PredefinedType.bool) + } + val performParams = structdef { + field("engineProcessTask", array(PredefinedType.byte)) + } + + init { + call("setupUtContext", setupContextParams, PredefinedType.void).async + call("createTestGenerator", testGeneratorParams, PredefinedType.void).async + call("isCancelled", PredefinedType.void, PredefinedType.bool).async + call("findTestClassName", testClassNameParams, testClassNameResult).async + call("generate", generateParams, generateResult).async + call("render", renderParams, renderResult).async + call("obtainClassId", PredefinedType.string, array(PredefinedType.byte)).async + call("findMethodsInClassMatchingSelected", findMethodsInClassMatchingSelectedArguments, findMethodsInClassMatchingSelectedResult).async + call("findMethodParamNames", findMethodParamNamesArguments, findMethodParamNamesResult).async + call("writeSarifReport", writeSarifReportArguments, PredefinedType.string).async + call("generateTestReport", generateTestReportArgs, generateTestReportResult).async + call("perform", performParams, array(PredefinedType.byte)).async + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/InstrumentedProcessModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/InstrumentedProcessModel.kt new file mode 100644 index 0000000000..f9d3d76f50 --- /dev/null +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/InstrumentedProcessModel.kt @@ -0,0 +1,103 @@ +@file:Suppress("unused") +package org.utbot.rd.models + +import com.jetbrains.rd.generator.nova.* + +object InstrumentedProcessRoot : Root() + +object InstrumentedProcessModel : Ext(InstrumentedProcessRoot) { + val AddPathsParams = structdef { + field("pathsToUserClasses", PredefinedType.string) + } + + val SetInstrumentationParams = structdef { + field("instrumentation", array(PredefinedType.byte)) + field("useBytecodeTransformation", PredefinedType.bool) + } + + val InvokeMethodCommandParams = structdef { + field("classname", PredefinedType.string) + field("signature", PredefinedType.string) + field("arguments", array(PredefinedType.byte)) + field("parameters", array(PredefinedType.byte)) + } + + val InvokeMethodCommandResult = structdef { + field("result", array(PredefinedType.byte)) + } + + val CollectCoverageParams = structdef { + field("clazz", array(PredefinedType.byte)) + } + + val CollectCoverageResult = structdef { + field("coverageInfo", array(PredefinedType.byte)) + } + + val ComputeStaticFieldParams = structdef { + field("fieldId", array(PredefinedType.byte)) + } + + val ComputeStaticFieldResult = structdef { + field("result", array(PredefinedType.byte)) + } + + val GetSpringRepositoriesParams = structdef { + field("classId", array(PredefinedType.byte)) + } + + val GetSpringRepositoriesResult = structdef { + field("springRepositoryIds", array(PredefinedType.byte)) + } + + val TryLoadingSpringContextResult = structdef { + field("springContextLoadingResult", array(PredefinedType.byte)) + } + + init { + call("AddPaths", AddPathsParams, PredefinedType.void).apply { + async + documentation = + "The main process tells where the instrumented process should search for the classes" + } + call("Warmup", PredefinedType.void, PredefinedType.void).apply { + async + documentation = + "Load classes from classpath and instrument them" + } + call("SetInstrumentation", SetInstrumentationParams, PredefinedType.void).apply { + async + documentation = + "The main process sends [instrumentation] to the instrumented process" + } + call("InvokeMethodCommand", InvokeMethodCommandParams, InvokeMethodCommandResult).apply { + async + documentation = + "The main process requests the instrumented process to execute a method with the given [signature],\n" + + "which declaring class's name is [className].\n" + + "@property parameters are the parameters needed for an execution, e.g. static environment" + } + call("CollectCoverage", CollectCoverageParams, CollectCoverageResult).apply { + async + documentation = + "This command is sent to the instrumented process from the [ConcreteExecutor] if user wants to collect coverage for the\n" + + "[clazz]" + } + call("ComputeStaticField", ComputeStaticFieldParams, ComputeStaticFieldResult).apply { + async + documentation = + "This command is sent to the instrumented process from the [ConcreteExecutor] if user wants to get value of static field\n" + + "[fieldId]" + } + call("getRelevantSpringRepositories", GetSpringRepositoriesParams, GetSpringRepositoriesResult).apply { + async + documentation = "Gets a list of [SpringRepositoryId]s that class specified by the [ClassId]" + + " (possibly indirectly) depends on (requires Spring instrumentation)" + } + call("tryLoadingSpringContext", PredefinedType.void, TryLoadingSpringContextResult).apply { + async + documentation = "This command is sent to the instrumented process from the [ConcreteExecutor]\n" + + "if the user wants to determine whether or not Spring application context can load" + } + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/LoggerModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/LoggerModel.kt new file mode 100644 index 0000000000..cdcd148727 --- /dev/null +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/LoggerModel.kt @@ -0,0 +1,23 @@ +@file:Suppress("unused") + +package org.utbot.rd.models + +import com.jetbrains.rd.generator.nova.* + +object LoggerRoot : Root() +object LoggerModel : Ext(LoggerRoot) { + val logArguments = structdef { + field("category", PredefinedType.string) + field("logLevelOrdinal", PredefinedType.int).doc("Integer value for com.jetbrains.rd.util.LogLevel") + field("message", PredefinedType.string) + } + + init { + signal("initRemoteLogging", PredefinedType.void).async + signal("log", logArguments).async + property( + "getCategoryMinimalLogLevel", + PredefinedType.int + ).async.doc("Property value - integer for com.jetbrains.rd.util.LogLevel.") + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/SettingsModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/SettingsModel.kt new file mode 100644 index 0000000000..7cef4ed4b4 --- /dev/null +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/SettingsModel.kt @@ -0,0 +1,20 @@ +@file:Suppress("unused") + +package org.utbot.rd.models + +import com.jetbrains.rd.generator.nova.* + +object SettingsRoot: Root() + +object SettingsModel : Ext(SettingsRoot) { + val settingForArgument = structdef { + field("key", PredefinedType.string) + field("propertyName", PredefinedType.string) + } + val settingForResult = structdef { + field("value", PredefinedType.string.nullable) + } + init { + call("settingFor", settingForArgument, settingForResult).async + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/SpringAnalyzerModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/SpringAnalyzerModel.kt new file mode 100644 index 0000000000..e5474cae67 --- /dev/null +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/SpringAnalyzerModel.kt @@ -0,0 +1,32 @@ +@file:Suppress("unused") +package org.utbot.rd.models + +import com.jetbrains.rd.generator.nova.* + +object SpringAnalyzerRoot : Root() + +object SpringAnalyzerProcessModel : Ext(SpringAnalyzerRoot) { + val springAnalyzerParams = structdef { + field("springSettings", array(PredefinedType.byte)) + } + + val beanAdditionalData = structdef { + field("factoryMethodName", PredefinedType.string) + field("parameterTypes", immutableList(PredefinedType.string)) + field("configClassFqn", PredefinedType.string) + } + + val beanDefinitionData = structdef { + field("beanName", PredefinedType.string) + field("beanTypeFqn", PredefinedType.string) + field("additionalData", beanAdditionalData.nullable) + } + + val springAnalyzerResult = structdef { + field("beanDefinitions", array(beanDefinitionData)) + } + + init { + call("analyze", springAnalyzerParams, springAnalyzerResult).async + } +} diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/SynchronizationModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/SynchronizationModel.kt new file mode 100644 index 0000000000..f0d86d23fa --- /dev/null +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/SynchronizationModel.kt @@ -0,0 +1,18 @@ +@file:Suppress("unused") +package org.utbot.rd.models + +import com.jetbrains.rd.generator.nova.* + +object SynchronizationRoot: Root() + +object SynchronizationModel: Ext(SynchronizationRoot) { + init { + call("suspendTimeoutTimer", PredefinedType.bool, PredefinedType.void).async + signal("synchronizationSignal", PredefinedType.string).async + signal("StopProcess", PredefinedType.void).apply { + async + documentation = + "This command tells the instrumented process to stop" + } + } +} \ No newline at end of file diff --git a/utbot-rd/src/main/riderRdgenModels/org/utbot/rider/rd/models/UtBotRiderModel.kt b/utbot-rd/src/main/riderRdgenModels/org/utbot/rider/rd/models/UtBotRiderModel.kt new file mode 100644 index 0000000000..17b6e52ccb --- /dev/null +++ b/utbot-rd/src/main/riderRdgenModels/org/utbot/rider/rd/models/UtBotRiderModel.kt @@ -0,0 +1,25 @@ +@file:Suppress("unused") + +package org.utbot.rider.rd.models + +import com.jetbrains.rd.generator.nova.* +import com.jetbrains.rider.model.nova.ide.SolutionModel + +object UtBotRiderModel : Ext(SolutionModel.Solution) { + val startPublishArgs = structdef { + field("fileName", PredefinedType.string) + field("arguments", PredefinedType.string) + field("workingDirectory", PredefinedType.string) + } + + init { + signal("startPublish", startPublishArgs).async + signal("logPublishOutput", PredefinedType.string).async + signal("logPublishError", PredefinedType.string).async + signal("stopPublish", PredefinedType.int).async + + signal("startVSharp", PredefinedType.void).async + signal("logVSharp", PredefinedType.string).async + signal("stopVSharp", PredefinedType.int).async + } +} \ No newline at end of file diff --git a/utbot-rd/src/test/kotlin/org/utbot/rd/tests/LifetimedProcessTest.kt b/utbot-rd/src/test/kotlin/org/utbot/rd/tests/LifetimedProcessTest.kt new file mode 100644 index 0000000000..265413cac7 --- /dev/null +++ b/utbot-rd/src/test/kotlin/org/utbot/rd/tests/LifetimedProcessTest.kt @@ -0,0 +1,100 @@ +package org.utbot.rd.tests + +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.lifetime.LifetimeDefinition +import com.jetbrains.rd.util.lifetime.isAlive +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.utbot.rd.LifetimedProcess +import org.utbot.rd.startLifetimedProcess +import java.io.File + +class LifetimedProcessTest { + lateinit var def: LifetimeDefinition + private val parent: Lifetime + get() = def.lifetime + + @BeforeEach + fun initLifetimes() { + def = LifetimeDefinition() + } + + @AfterEach + fun terminateLifetimes() { + def.terminate() + } + + private fun processMockCmd(delayInSeconds: Long): List { + val jar = System.getProperty("RD_MOCK_PROCESS") + val javaHome = System.getProperty("java.home") + val java = File(javaHome, "bin").resolve("java") + + return listOf(java.canonicalPath, "-ea", "-jar", jar, delayInSeconds.toString()) + } + + private fun List.startLifetimedProcessWithAssertion(block: (LifetimedProcess) -> Unit) { + val proc = startLifetimedProcess(this, parent) + + assertProcessAlive(proc) + block(proc) + assertProcessDead(proc) + } + + private fun assertProcessAlive(proc: LifetimedProcess) = runBlocking { + delay(1000) // if proc not started in 1 seconds - something is bad + Assertions.assertTrue(proc.lifetime.isAlive) + Assertions.assertTrue(proc.process.isAlive) + } + + private fun assertProcessDead(proc: LifetimedProcess) = runBlocking { + delay(1000) // if proc is not dead in 1 second - something is bad + Assertions.assertFalse(proc.lifetime.isAlive) + Assertions.assertFalse(proc.process.isAlive) + } + + @Test + fun testProcessLifetimeTermination() { + val cmds = processMockCmd(10) + + cmds.startLifetimedProcessWithAssertion { + it.terminate() + } + Assertions.assertTrue(parent.isAlive) + } + + @Test + fun testParentLifetimeTermination() { + val cmds = processMockCmd(10) + + cmds.startLifetimedProcessWithAssertion { + terminateLifetimes() + } + Assertions.assertFalse(parent.isAlive) + } + + @Test + fun testProcessDeath() { + val cmds = processMockCmd(3) + + cmds.startLifetimedProcessWithAssertion { + runBlocking { + delay(5000) + } + } + Assertions.assertTrue(parent.isAlive) + } + + @Test + fun testProcessKill() { + val cmds = processMockCmd(10) + + cmds.startLifetimedProcessWithAssertion { + it.process.destroyForcibly() + } + Assertions.assertTrue(parent.isAlive) + } +} \ No newline at end of file diff --git a/utbot-rd/src/test/kotlin/org/utbot/rd/tests/ProcessWithRdServerTest.kt b/utbot-rd/src/test/kotlin/org/utbot/rd/tests/ProcessWithRdServerTest.kt new file mode 100644 index 0000000000..58f5d77cef --- /dev/null +++ b/utbot-rd/src/test/kotlin/org/utbot/rd/tests/ProcessWithRdServerTest.kt @@ -0,0 +1,105 @@ +package org.utbot.rd.tests + +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.lifetime.LifetimeDefinition +import com.jetbrains.rd.util.lifetime.isAlive +import kotlinx.coroutines.* +import org.junit.jupiter.api.* +import org.utbot.rd.* +import java.io.File + +class ProcessWithRdServerTest { + lateinit var def: LifetimeDefinition + private val parent: Lifetime + get() = def.lifetime + + @BeforeEach + fun initLifetimes() { + def = LifetimeDefinition() + } + + @AfterEach + fun terminateLifetimes() { + def.terminate() + } + + private fun processMockCmd(delayInSeconds: Long, port: Int, shouldStartProtocol: Boolean): List { + val jar = System.getProperty("PROCESS_WITH_RD_SERVER_MOCK") + val javaHome = System.getProperty("java.home") + val java = File(javaHome, "bin").resolve("java") + + return listOf(java.canonicalPath, "-ea", "-jar", jar, delayInSeconds.toString(), port.toString(), shouldStartProtocol.toString()) + } + + private fun assertProcessAlive(proc: LifetimedProcess) = runBlocking { + delay(1000) // if proc not started in 1 seconds - something is bad + Assertions.assertTrue(proc.lifetime.isAlive) + Assertions.assertTrue(proc.process.isAlive) + } + + private fun assertProcessDead(proc: LifetimedProcess) = runBlocking { + delay(1000) // if proc is not dead in 1 second - something is bad + Assertions.assertFalse(proc.lifetime.isAlive) + Assertions.assertFalse(proc.process.isAlive) + } + + @Test + fun testParentOnly() = runBlocking { + var lifetimedProcess: LifetimedProcess? = null + val exception = assertThrows { + withTimeout(5000) { + startUtProcessWithRdServer(parent) { + val cmds = processMockCmd(3, it, false) + val proc = ProcessBuilder(cmds).start() + val lfProc = proc.toLifetimedProcess(parent) + + assertProcessAlive(lfProc) + lifetimedProcess = lfProc + proc + } + } + } + + Assertions.assertFalse(exception is TimeoutCancellationException) + Assertions.assertTrue(parent.isAlive) + assertProcessDead(lifetimedProcess!!) + } + + @Test + fun testParentWithChild() = runBlocking { + val child = startUtProcessWithRdServer(parent) { + val cmds = processMockCmd(3, it, true) + + ProcessBuilder(cmds).start() + } + + assertProcessAlive(child) + delay(3000) + assertProcessDead(child) + + Assertions.assertTrue(parent.isAlive) + Assertions.assertFalse(child.protocol.lifetime.isAlive) + } + + @Test + fun testCancellation() = runBlocking { + var lifetimedProcess: LifetimedProcess? = null + val exception = assertThrows { + withTimeout(1000) { + startUtProcessWithRdServer(parent) { + val cmds = processMockCmd(3, it, false) + val proc = ProcessBuilder(cmds).start() + val lfProc = proc.toLifetimedProcess(parent) + + assertProcessAlive(lfProc) + lifetimedProcess = lfProc + proc + } + } + } + + Assertions.assertTrue(exception is TimeoutCancellationException) + Assertions.assertTrue(parent.isAlive) + assertProcessDead(lifetimedProcess!!) + } +} \ No newline at end of file diff --git a/utbot-rider/.gitignore b/utbot-rider/.gitignore new file mode 100644 index 0000000000..347c41ffef --- /dev/null +++ b/utbot-rider/.gitignore @@ -0,0 +1,233 @@ +bin/ +obj/ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# Rider +.idea +*.iml + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +## TODO: Comment the next line if you want to checkin your +## web deploy settings but do note that will include unencrypted +## passwords +#*.pubxml + +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# Custom files +VSharp.Test/Tests/VSharp.CSharpUtils/VSharp.CSharpUtils.dll + +*DS_Store + +# CLR interaction files +VSharp.ClrInteraction/sdk/* +VSharp.ClrInteraction/packs/* +VSharp.ClrInteraction/shared/* +VSharp.ClrInteraction/host/* +**/cmake-build-debug/** + +# Rendered tests +VSharp.Test/GeneratedTests/** diff --git a/utbot-rider/build.gradle.kts b/utbot-rider/build.gradle.kts new file mode 100644 index 0000000000..e88eef51bf --- /dev/null +++ b/utbot-rider/build.gradle.kts @@ -0,0 +1,86 @@ +import org.apache.tools.ant.taskdefs.condition.Os + +val semVer: String? by rootProject +val rdVersion: String? by rootProject + +plugins { + id("org.jetbrains.intellij") version "1.13.1" +} + +intellij { + type.set("RD") + version.set("2023.1") +} + +tasks { + register("addRiderModelsToUtbotModels") { + val rdLibDirectory = File(project.tasks.setupDependencies.get().idea.get().classes, "lib/rd/rider-model.jar") + from(rdLibDirectory) + val utbotRd = project.rootProject.childProjects["utbot-rd"]!! + val targetDir = utbotRd.buildDir.resolve("libs") + into(targetDir) + } + val dotNetSdkCmdPath = projectDir.resolve("dotnet-sdk.cmd").toString() + + compileKotlin { + kotlinOptions { + jvmTarget = "17" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class") + allWarningsAsErrors = false + } + } + + java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + runIde { + jvmArgs("-Xmx2048m") + } + + patchPluginXml { + sinceBuild.set("231") + version.set(semVer) + } + + buildSearchableOptions { + enabled = false + } + + val chmodDotnet = create("chmodDotnet") { + group = "build" + doLast { + exec { + commandLine = listOf( + "chmod", + "+x", + dotNetSdkCmdPath + ) + } + } + } + + val publishDotNet = create("publishDotNet") { + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + dependsOn(chmodDotnet) + } + group = "build" + doLast { + exec { + commandLine = listOf( + dotNetSdkCmdPath, + "publish", + projectDir.resolve("src/dotnet/UtBot/UtBot.sln").toString() + ) + } + } + } + + prepareSandbox { + dependsOn(publishDotNet) + from("src/dotnet/UtBot/UtBot/bin/Debug/net6.0/publish") { + into("${intellij.pluginName.get()}/dotnet") } + } + +} diff --git a/utbot-rider/dotnet-sdk.cmd b/utbot-rider/dotnet-sdk.cmd new file mode 100644 index 0000000000..9a08efc811 --- /dev/null +++ b/utbot-rider/dotnet-sdk.cmd @@ -0,0 +1,266 @@ +:<<"::CMDLITERAL" +@ECHO OFF +GOTO :CMDSCRIPT +::CMDLITERAL + +set -eu + +SCRIPT_VERSION=dotnet-cmd-v2 +COMPANY_DIR="UtBot" +TARGET_DIR="${TEMPDIR:-$HOME/.local/share}/$COMPANY_DIR/dotnet-cmd" +KEEP_ROSETTA2=false +export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true +export DOTNET_CLI_TELEMETRY_OPTOUT=true + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +retry_on_error () { + local n="$1" + shift + + for i in $(seq 2 "$n"); do + "$@" 2>&1 && return || echo "WARNING: Command '$1' returned non-zero exit status $?, try again" + done + "$@" +} + +is_linux_musl () { + (ldd --version 2>&1 || true) | grep -q musl +} + +case $(uname) in +Darwin) + DOTNET_ARCH=$(uname -m) + if ! $KEEP_ROSETTA2 && [ "$(sysctl -n sysctl.proc_translated 2>/dev/null || true)" = "1" ]; then + DOTNET_ARCH=arm64 + fi + case $DOTNET_ARCH in + x86_64) + DOTNET_HASH_URL=cf3e1c73-a9a9-4e08-8607-8f9edae5f3f2/40a021a98a6b6e430a1f170037735f6f + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-osx-x64 + ;; + arm64) + DOTNET_HASH_URL=3859fff3-f8a9-4e05-87cd-bd6db75833f5/64ec1099d45f85d14099da3c1f92a5c3 + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-osx-arm64 + ;; + *) echo "Unknown architecture $DOTNET_ARCH" >&2; exit 1;; + esac;; +Linux) + DOTNET_ARCH=$(linux$(getconf LONG_BIT) uname -m) + case $DOTNET_ARCH in + x86_64) + if is_linux_musl; then + DOTNET_HASH_URL=206aebda-126f-484f-af02-051a17c1ec54/2ec559cb69cec83ffa64dba5441a1b2d + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-linux-musl-x64 + else + DOTNET_HASH_URL=77d472e5-194c-421e-992d-e4ca1d08e6cc/56c61ac303ddf1b12026151f4f000a2b + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-linux-x64 + fi + ;; + aarch64) + if is_linux_musl; then + DOTNET_HASH_URL=4bd2399a-e0e9-43a6-9767-ac15dd430b1c/3dd4307a1ce811e31943d80eee638bc1 + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-linux-musl-arm64 + else + DOTNET_HASH_URL=06c4ee8e-bf2c-4e46-ab1c-e14dd72311c1/f7bc6c9677eaccadd1d0e76c55d361ea + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-linux-arm64 + fi + ;; + armv7l | armv8l) + if is_linux_musl; then + DOTNET_HASH_URL=952c468c-ac70-46b0-9274-4cb9c270950c/f0cd4c8392158547c2fa38674bfd56fd + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-linux-musl-arm + else + DOTNET_HASH_URL=a218e3b9-941b-43be-bfb1-615862777457/80954de34ab68729981ed372a8d25b46 + DOTNET_FILE_NAME=dotnet-sdk-6.0.301-linux-arm + fi + ;; + *) echo "Unknown architecture $DOTNET_ARCH" >&2; exit 1;; + esac;; +*) echo "Unknown platform: $(uname)" >&2; exit 1;; +esac + +DOTNET_URL=https://cache-redirector.jetbrains.com/download.visualstudio.microsoft.com/download/pr/$DOTNET_HASH_URL/$DOTNET_FILE_NAME.tar.gz +DOTNET_TARGET_DIR=$TARGET_DIR/$DOTNET_FILE_NAME-$SCRIPT_VERSION +DOTNET_TEMP_FILE=$TARGET_DIR/dotnet-sdk-temp.tar.gz + +if grep -q -x "$DOTNET_URL" "$DOTNET_TARGET_DIR/.flag" 2>/dev/null; then + # Everything is up-to-date in $DOTNET_TARGET_DIR, do nothing + true +else +while true; do # Note(k15tfu): for goto + mkdir -p "$TARGET_DIR" + + LOCK_FILE="$TARGET_DIR/.dotnet-cmd-lock.pid" + TMP_LOCK_FILE="$TARGET_DIR/.tmp.$$.pid" + echo $$ >"$TMP_LOCK_FILE" + + while ! ln "$TMP_LOCK_FILE" "$LOCK_FILE" 2>/dev/null; do + LOCK_OWNER=$(cat "$LOCK_FILE" 2>/dev/null || true) + while [ -n "$LOCK_OWNER" ] && ps -p $LOCK_OWNER >/dev/null; do + warn "Waiting for the process $LOCK_OWNER to finish bootstrap dotnet.cmd" + sleep 1 + LOCK_OWNER=$(cat "$LOCK_FILE" 2>/dev/null || true) + + # Hurry up, bootstrap is ready.. + if grep -q -x "$DOTNET_URL" "$DOTNET_TARGET_DIR/.flag" 2>/dev/null; then + break 3 # Note(k15tfu): goto out of the outer if-else block. + fi + done + + if [ -n "$LOCK_OWNER" ] && grep -q -x $LOCK_OWNER "$LOCK_FILE" 2>/dev/null; then + die "ERROR: The lock file $LOCK_FILE still exists on disk after the owner process $LOCK_OWNER exited" + fi + done + + trap "rm -f \"$LOCK_FILE\"" EXIT + rm "$TMP_LOCK_FILE" + + if ! grep -q -x "$DOTNET_URL" "$DOTNET_TARGET_DIR/.flag" 2>/dev/null; then + warn "Downloading $DOTNET_URL to $DOTNET_TEMP_FILE" + + rm -f "$DOTNET_TEMP_FILE" + if command -v curl >/dev/null 2>&1; then + if [ -t 1 ]; then CURL_PROGRESS="--progress-bar"; else CURL_PROGRESS="--silent --show-error"; fi + retry_on_error 5 curl -L $CURL_PROGRESS --output "${DOTNET_TEMP_FILE}" "$DOTNET_URL" + elif command -v wget >/dev/null 2>&1; then + if [ -t 1 ]; then WGET_PROGRESS=""; else WGET_PROGRESS="-nv"; fi + retry_on_error 5 wget $WGET_PROGRESS -O "${DOTNET_TEMP_FILE}" "$DOTNET_URL" + else + die "ERROR: Please install wget or curl" + fi + + warn "Extracting $DOTNET_TEMP_FILE to $DOTNET_TARGET_DIR" + rm -rf "$DOTNET_TARGET_DIR" + mkdir -p "$DOTNET_TARGET_DIR" + + tar -x -f "$DOTNET_TEMP_FILE" -C "$DOTNET_TARGET_DIR" + rm -f "$DOTNET_TEMP_FILE" + + echo "$DOTNET_URL" >"$DOTNET_TARGET_DIR/.flag" + fi + + rm "$LOCK_FILE" + break +done +fi + +if [ ! -x "$DOTNET_TARGET_DIR/dotnet" ]; then + die "Unable to find dotnet under $DOTNET_TARGET_DIR" +fi + +exec "$DOTNET_TARGET_DIR/dotnet" "$@" + +:CMDSCRIPT + +setlocal +set SCRIPT_VERSION=v2 +set COMPANY_NAME=UtBot +set TARGET_DIR=%LOCALAPPDATA%\%COMPANY_NAME%\dotnet-cmd\ + +for /f "tokens=3 delims= " %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "PROCESSOR_ARCHITECTURE"') do set ARCH=%%a + +if "%ARCH%"=="ARM64" ( + set DOTNET_HASH_URL=656c8345-6661-409e-871d-00ca93cec542/cae3dcdc5c668c0e0abcf12d838348f1 + set DOTNET_FILE_NAME=dotnet-sdk-6.0.301-win-arm64 +) else ( + +if "%ARCH%"=="AMD64" ( + set DOTNET_HASH_URL=333eba0c-3242-48f3-a923-fdac5f219f77/342a4595101e3b4616360a7666459236 + set DOTNET_FILE_NAME=dotnet-sdk-6.0.301-win-x64 +) else ( + +if "%ARCH%"=="x86" ( + set DOTNET_HASH_URL=0a9cabcb-cb52-4f5e-bb79-1298f9ff9e22/c306c5cc940a9bb9a39ffe6619a255e6 + set DOTNET_FILE_NAME=dotnet-sdk-6.0.301-win-x86 +) else ( + +echo Unknown Windows architecture +goto fail + +))) + +set DOTNET_URL=https://cache-redirector.jetbrains.com/download.visualstudio.microsoft.com/download/pr/%DOTNET_HASH_URL%/%DOTNET_FILE_NAME%.zip +set DOTNET_TARGET_DIR=%TARGET_DIR%%DOTNET_FILE_NAME%-%SCRIPT_VERSION%\ +set DOTNET_TEMP_FILE=%TARGET_DIR%dotnet-sdk-temp.zip +set DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=true +set DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true +set DOTNET_CLI_TELEMETRY_OPTOUT=true + +set POWERSHELL=%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe + +if not exist "%DOTNET_TARGET_DIR%.flag" goto downloadAndExtractDotNet + +set /p CURRENT_FLAG=<"%DOTNET_TARGET_DIR%.flag" +if "%CURRENT_FLAG%" == "%DOTNET_URL%" goto continueWithDotNet + +:downloadAndExtractDotNet + +set DOWNLOAD_AND_EXTRACT_DOTNET_PS1= ^ +Set-StrictMode -Version 3.0; ^ +$ErrorActionPreference = 'Stop'; ^ + ^ +$createdNew = $false; ^ +$lock = New-Object System.Threading.Mutex($true, 'Global\dotnet-cmd-lock', [ref]$createdNew); ^ +if (-not $createdNew) { ^ + Write-Host 'Waiting for the other process to finish bootstrap dotnet.cmd'; ^ + [void]$lock.WaitOne(); ^ +} ^ + ^ +try { ^ + if ((Get-Content '%DOTNET_TARGET_DIR%.flag' -ErrorAction Ignore) -ne '%DOTNET_URL%') { ^ + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; ^ + Write-Host 'Downloading %DOTNET_URL% to %DOTNET_TEMP_FILE%'; ^ + [void](New-Item '%TARGET_DIR%' -ItemType Directory -Force); ^ + (New-Object Net.WebClient).DownloadFile('%DOTNET_URL%', '%DOTNET_TEMP_FILE%'); ^ + ^ + Write-Host 'Extracting %DOTNET_TEMP_FILE% to %DOTNET_TARGET_DIR%'; ^ + if (Test-Path '%DOTNET_TARGET_DIR%') { ^ + Remove-Item '%DOTNET_TARGET_DIR%' -Recurse; ^ + } ^ + Add-Type -A 'System.IO.Compression.FileSystem'; ^ + [IO.Compression.ZipFile]::ExtractToDirectory('%DOTNET_TEMP_FILE%', '%DOTNET_TARGET_DIR%'); ^ + Remove-Item '%DOTNET_TEMP_FILE%'; ^ + ^ + Set-Content '%DOTNET_TARGET_DIR%.flag' -Value '%DOTNET_URL%'; ^ + } ^ +} ^ +finally { ^ + $lock.ReleaseMutex(); ^ +} + +"%POWERSHELL%" -nologo -noprofile -Command %DOWNLOAD_AND_EXTRACT_DOTNET_PS1% +if errorlevel 1 goto fail + +:continueWithDotNet + +if not exist "%DOTNET_TARGET_DIR%\dotnet.exe" ( + echo Unable to find dotnet.exe under %DOTNET_TARGET_DIR% + goto fail +) + +REM Prevent globally installed .NET Core from leaking into this runtime's lookup +SET DOTNET_MULTILEVEL_LOOKUP=0 +SET DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false + +for /f "tokens=2 delims=:." %%c in ('chcp') do set /a PREV_CODE_PAGE=%%c +call "%DOTNET_TARGET_DIR%\dotnet.exe" %* +set /a DOTNET_EXIT_CODE=%ERRORLEVEL% +chcp %PREV_CODE_PAGE% >nul + +exit /B %DOTNET_EXIT_CODE% +endlocal + +:fail +echo "FAIL" +exit /b 1 \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot.Rd/Generated/CSharpRoot.Generated.cs b/utbot-rider/src/dotnet/UtBot/UtBot.Rd/Generated/CSharpRoot.Generated.cs new file mode 100644 index 0000000000..390b86080f --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot.Rd/Generated/CSharpRoot.Generated.cs @@ -0,0 +1,94 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a RdGen v1.11. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using JetBrains.Annotations; + +using JetBrains.Core; +using JetBrains.Diagnostics; +using JetBrains.Collections; +using JetBrains.Collections.Viewable; +using JetBrains.Lifetimes; +using JetBrains.Serialization; +using JetBrains.Rd; +using JetBrains.Rd.Base; +using JetBrains.Rd.Impl; +using JetBrains.Rd.Tasks; +using JetBrains.Rd.Util; +using JetBrains.Rd.Text; + + +// ReSharper disable RedundantEmptyObjectCreationArgumentList +// ReSharper disable InconsistentNaming +// ReSharper disable RedundantOverflowCheckingContext + + +namespace UtBot.Rd.Generated +{ + + + /// + ///

    Generated from: CSharpModel.kt:6

    + ///
    + public class CSharpRoot : RdExtBase + { + //fields + //public fields + + //private fields + //primary constructor + private CSharpRoot( + ) + { + } + //secondary constructor + //deconstruct trait + //statics + + + + protected override long SerializationHash => -3743965577225156277L; + + protected override Action Register => RegisterDeclaredTypesSerializers; + public static void RegisterDeclaredTypesSerializers(ISerializers serializers) + { + + serializers.RegisterToplevelOnce(typeof(CSharpRoot), CSharpRoot.RegisterDeclaredTypesSerializers); + serializers.RegisterToplevelOnce(typeof(VSharpModel), VSharpModel.RegisterDeclaredTypesSerializers); + } + + public CSharpRoot(Lifetime lifetime, IProtocol protocol) : this() + { + Identify(protocol.Identities, RdId.Root.Mix("CSharpRoot")); + Bind(lifetime, protocol, "CSharpRoot"); + } + + //constants + + //custom body + //methods + //equals trait + //hash code trait + //pretty print + public override void Print(PrettyPrinter printer) + { + printer.Println("CSharpRoot ("); + printer.Print(")"); + } + //toString + public override string ToString() + { + var printer = new SingleLinePrettyPrinter(); + Print(printer); + return printer.ToString(); + } + } +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot.Rd/Generated/VSharpModel.Generated.cs b/utbot-rider/src/dotnet/UtBot/UtBot.Rd/Generated/VSharpModel.Generated.cs new file mode 100644 index 0000000000..1fb98174ac --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot.Rd/Generated/VSharpModel.Generated.cs @@ -0,0 +1,598 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a RdGen v1.11. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using JetBrains.Annotations; + +using JetBrains.Core; +using JetBrains.Diagnostics; +using JetBrains.Collections; +using JetBrains.Collections.Viewable; +using JetBrains.Lifetimes; +using JetBrains.Serialization; +using JetBrains.Rd; +using JetBrains.Rd.Base; +using JetBrains.Rd.Impl; +using JetBrains.Rd.Tasks; +using JetBrains.Rd.Util; +using JetBrains.Rd.Text; + + +// ReSharper disable RedundantEmptyObjectCreationArgumentList +// ReSharper disable InconsistentNaming +// ReSharper disable RedundantOverflowCheckingContext + + +namespace UtBot.Rd.Generated +{ + + + /// + ///

    Generated from: CSharpModel.kt:8

    + ///
    + public class VSharpModel : RdExtBase + { + //fields + //public fields + [NotNull] public RdCall Generate => _Generate; + [NotNull] public ISignal Ping => _Ping; + [NotNull] public ISignal Log => _Log; + + //private fields + [NotNull] private readonly RdCall _Generate; + [NotNull] private readonly RdSignal _Ping; + [NotNull] private readonly RdSignal _Log; + + //primary constructor + private VSharpModel( + [NotNull] RdCall generate, + [NotNull] RdSignal ping, + [NotNull] RdSignal log + ) + { + if (generate == null) throw new ArgumentNullException("generate"); + if (ping == null) throw new ArgumentNullException("ping"); + if (log == null) throw new ArgumentNullException("log"); + + _Generate = generate; + _Ping = ping; + _Log = log; + _Generate.Async = true; + _Ping.Async = true; + _Log.Async = true; + BindableChildren.Add(new KeyValuePair("generate", _Generate)); + BindableChildren.Add(new KeyValuePair("ping", _Ping)); + BindableChildren.Add(new KeyValuePair("log", _Log)); + } + //secondary constructor + private VSharpModel ( + ) : this ( + new RdCall(GenerateArguments.Read, GenerateArguments.Write, GenerateResults.Read, GenerateResults.Write), + new RdSignal(JetBrains.Rd.Impl.Serializers.ReadString, JetBrains.Rd.Impl.Serializers.WriteString), + new RdSignal(JetBrains.Rd.Impl.Serializers.ReadString, JetBrains.Rd.Impl.Serializers.WriteString) + ) {} + //deconstruct trait + //statics + + + + protected override long SerializationHash => 9120359939503061610L; + + protected override Action Register => RegisterDeclaredTypesSerializers; + public static void RegisterDeclaredTypesSerializers(ISerializers serializers) + { + + serializers.RegisterToplevelOnce(typeof(CSharpRoot), CSharpRoot.RegisterDeclaredTypesSerializers); + } + + public VSharpModel(Lifetime lifetime, IProtocol protocol) : this() + { + Identify(protocol.Identities, RdId.Root.Mix("VSharpModel")); + Bind(lifetime, protocol, "VSharpModel"); + } + + //constants + + //custom body + //methods + //equals trait + //hash code trait + //pretty print + public override void Print(PrettyPrinter printer) + { + printer.Println("VSharpModel ("); + using (printer.IndentCookie()) { + printer.Print("generate = "); _Generate.PrintEx(printer); printer.Println(); + printer.Print("ping = "); _Ping.PrintEx(printer); printer.Println(); + printer.Print("log = "); _Log.PrintEx(printer); printer.Println(); + } + printer.Print(")"); + } + //toString + public override string ToString() + { + var printer = new SingleLinePrettyPrinter(); + Print(printer); + return printer.ToString(); + } + } + + + /// + ///

    Generated from: CSharpModel.kt:21

    + ///
    + public sealed class GenerateArguments : IPrintable, IEquatable + { + //fields + //public fields + [NotNull] public string AssemblyPath {get; private set;} + [NotNull] public string ProjectCsprojPath {get; private set;} + [NotNull] public string SolutionFilePath {get; private set;} + [NotNull] public List Methods {get; private set;} + public int GenerationTimeoutInSeconds {get; private set;} + [CanBeNull] public string TargetFramework {get; private set;} + [NotNull] public List AssembliesFullNameToTheirPath {get; private set;} + + //private fields + //primary constructor + public GenerateArguments( + [NotNull] string assemblyPath, + [NotNull] string projectCsprojPath, + [NotNull] string solutionFilePath, + [NotNull] List methods, + int generationTimeoutInSeconds, + [CanBeNull] string targetFramework, + [NotNull] List assembliesFullNameToTheirPath + ) + { + if (assemblyPath == null) throw new ArgumentNullException("assemblyPath"); + if (projectCsprojPath == null) throw new ArgumentNullException("projectCsprojPath"); + if (solutionFilePath == null) throw new ArgumentNullException("solutionFilePath"); + if (methods == null) throw new ArgumentNullException("methods"); + if (assembliesFullNameToTheirPath == null) throw new ArgumentNullException("assembliesFullNameToTheirPath"); + + AssemblyPath = assemblyPath; + ProjectCsprojPath = projectCsprojPath; + SolutionFilePath = solutionFilePath; + Methods = methods; + GenerationTimeoutInSeconds = generationTimeoutInSeconds; + TargetFramework = targetFramework; + AssembliesFullNameToTheirPath = assembliesFullNameToTheirPath; + } + //secondary constructor + //deconstruct trait + public void Deconstruct([NotNull] out string assemblyPath, [NotNull] out string projectCsprojPath, [NotNull] out string solutionFilePath, [NotNull] out List methods, out int generationTimeoutInSeconds, [CanBeNull] out string targetFramework, [NotNull] out List assembliesFullNameToTheirPath) + { + assemblyPath = AssemblyPath; + projectCsprojPath = ProjectCsprojPath; + solutionFilePath = SolutionFilePath; + methods = Methods; + generationTimeoutInSeconds = GenerationTimeoutInSeconds; + targetFramework = TargetFramework; + assembliesFullNameToTheirPath = AssembliesFullNameToTheirPath; + } + //statics + + public static CtxReadDelegate Read = (ctx, reader) => + { + var assemblyPath = reader.ReadString(); + var projectCsprojPath = reader.ReadString(); + var solutionFilePath = reader.ReadString(); + var methods = ReadMethodDescriptorList(ctx, reader); + var generationTimeoutInSeconds = reader.ReadInt(); + var targetFramework = ReadStringNullable(ctx, reader); + var assembliesFullNameToTheirPath = ReadMapEntryList(ctx, reader); + var _result = new GenerateArguments(assemblyPath, projectCsprojPath, solutionFilePath, methods, generationTimeoutInSeconds, targetFramework, assembliesFullNameToTheirPath); + return _result; + }; + public static CtxReadDelegate> ReadMethodDescriptorList = MethodDescriptor.Read.List(); + public static CtxReadDelegate ReadStringNullable = JetBrains.Rd.Impl.Serializers.ReadString.NullableClass(); + public static CtxReadDelegate> ReadMapEntryList = MapEntry.Read.List(); + + public static CtxWriteDelegate Write = (ctx, writer, value) => + { + writer.Write(value.AssemblyPath); + writer.Write(value.ProjectCsprojPath); + writer.Write(value.SolutionFilePath); + WriteMethodDescriptorList(ctx, writer, value.Methods); + writer.Write(value.GenerationTimeoutInSeconds); + WriteStringNullable(ctx, writer, value.TargetFramework); + WriteMapEntryList(ctx, writer, value.AssembliesFullNameToTheirPath); + }; + public static CtxWriteDelegate> WriteMethodDescriptorList = MethodDescriptor.Write.List(); + public static CtxWriteDelegate WriteStringNullable = JetBrains.Rd.Impl.Serializers.WriteString.NullableClass(); + public static CtxWriteDelegate> WriteMapEntryList = MapEntry.Write.List(); + + //constants + + //custom body + //methods + //equals trait + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((GenerateArguments) obj); + } + public bool Equals(GenerateArguments other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return AssemblyPath == other.AssemblyPath && ProjectCsprojPath == other.ProjectCsprojPath && SolutionFilePath == other.SolutionFilePath && Methods.SequenceEqual(other.Methods) && GenerationTimeoutInSeconds == other.GenerationTimeoutInSeconds && Equals(TargetFramework, other.TargetFramework) && AssembliesFullNameToTheirPath.SequenceEqual(other.AssembliesFullNameToTheirPath); + } + //hash code trait + public override int GetHashCode() + { + unchecked { + var hash = 0; + hash = hash * 31 + AssemblyPath.GetHashCode(); + hash = hash * 31 + ProjectCsprojPath.GetHashCode(); + hash = hash * 31 + SolutionFilePath.GetHashCode(); + hash = hash * 31 + Methods.ContentHashCode(); + hash = hash * 31 + GenerationTimeoutInSeconds.GetHashCode(); + hash = hash * 31 + (TargetFramework != null ? TargetFramework.GetHashCode() : 0); + hash = hash * 31 + AssembliesFullNameToTheirPath.ContentHashCode(); + return hash; + } + } + //pretty print + public void Print(PrettyPrinter printer) + { + printer.Println("GenerateArguments ("); + using (printer.IndentCookie()) { + printer.Print("assemblyPath = "); AssemblyPath.PrintEx(printer); printer.Println(); + printer.Print("projectCsprojPath = "); ProjectCsprojPath.PrintEx(printer); printer.Println(); + printer.Print("solutionFilePath = "); SolutionFilePath.PrintEx(printer); printer.Println(); + printer.Print("methods = "); Methods.PrintEx(printer); printer.Println(); + printer.Print("generationTimeoutInSeconds = "); GenerationTimeoutInSeconds.PrintEx(printer); printer.Println(); + printer.Print("targetFramework = "); TargetFramework.PrintEx(printer); printer.Println(); + printer.Print("assembliesFullNameToTheirPath = "); AssembliesFullNameToTheirPath.PrintEx(printer); printer.Println(); + } + printer.Print(")"); + } + //toString + public override string ToString() + { + var printer = new SingleLinePrettyPrinter(); + Print(printer); + return printer.ToString(); + } + } + + + /// + ///

    Generated from: CSharpModel.kt:31

    + ///
    + public sealed class GenerateResults : IPrintable, IEquatable + { + //fields + //public fields + [CanBeNull] public string GeneratedProjectPath {get; private set;} + [NotNull] public List GeneratedFilesPaths {get; private set;} + [CanBeNull] public string ExceptionMessage {get; private set;} + public int TestsCount {get; private set;} + public int ErrorsCount {get; private set;} + + //private fields + //primary constructor + public GenerateResults( + [CanBeNull] string generatedProjectPath, + [NotNull] List generatedFilesPaths, + [CanBeNull] string exceptionMessage, + int testsCount, + int errorsCount + ) + { + if (generatedFilesPaths == null) throw new ArgumentNullException("generatedFilesPaths"); + + GeneratedProjectPath = generatedProjectPath; + GeneratedFilesPaths = generatedFilesPaths; + ExceptionMessage = exceptionMessage; + TestsCount = testsCount; + ErrorsCount = errorsCount; + } + //secondary constructor + //deconstruct trait + public void Deconstruct([CanBeNull] out string generatedProjectPath, [NotNull] out List generatedFilesPaths, [CanBeNull] out string exceptionMessage, out int testsCount, out int errorsCount) + { + generatedProjectPath = GeneratedProjectPath; + generatedFilesPaths = GeneratedFilesPaths; + exceptionMessage = ExceptionMessage; + testsCount = TestsCount; + errorsCount = ErrorsCount; + } + //statics + + public static CtxReadDelegate Read = (ctx, reader) => + { + var generatedProjectPath = ReadStringNullable(ctx, reader); + var generatedFilesPaths = ReadStringList(ctx, reader); + var exceptionMessage = ReadStringNullable(ctx, reader); + var testsCount = reader.ReadInt(); + var errorsCount = reader.ReadInt(); + var _result = new GenerateResults(generatedProjectPath, generatedFilesPaths, exceptionMessage, testsCount, errorsCount); + return _result; + }; + public static CtxReadDelegate ReadStringNullable = JetBrains.Rd.Impl.Serializers.ReadString.NullableClass(); + public static CtxReadDelegate> ReadStringList = JetBrains.Rd.Impl.Serializers.ReadString.List(); + + public static CtxWriteDelegate Write = (ctx, writer, value) => + { + WriteStringNullable(ctx, writer, value.GeneratedProjectPath); + WriteStringList(ctx, writer, value.GeneratedFilesPaths); + WriteStringNullable(ctx, writer, value.ExceptionMessage); + writer.Write(value.TestsCount); + writer.Write(value.ErrorsCount); + }; + public static CtxWriteDelegate WriteStringNullable = JetBrains.Rd.Impl.Serializers.WriteString.NullableClass(); + public static CtxWriteDelegate> WriteStringList = JetBrains.Rd.Impl.Serializers.WriteString.List(); + + //constants + + //custom body + //methods + //equals trait + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((GenerateResults) obj); + } + public bool Equals(GenerateResults other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Equals(GeneratedProjectPath, other.GeneratedProjectPath) && GeneratedFilesPaths.SequenceEqual(other.GeneratedFilesPaths) && Equals(ExceptionMessage, other.ExceptionMessage) && TestsCount == other.TestsCount && ErrorsCount == other.ErrorsCount; + } + //hash code trait + public override int GetHashCode() + { + unchecked { + var hash = 0; + hash = hash * 31 + (GeneratedProjectPath != null ? GeneratedProjectPath.GetHashCode() : 0); + hash = hash * 31 + GeneratedFilesPaths.ContentHashCode(); + hash = hash * 31 + (ExceptionMessage != null ? ExceptionMessage.GetHashCode() : 0); + hash = hash * 31 + TestsCount.GetHashCode(); + hash = hash * 31 + ErrorsCount.GetHashCode(); + return hash; + } + } + //pretty print + public void Print(PrettyPrinter printer) + { + printer.Println("GenerateResults ("); + using (printer.IndentCookie()) { + printer.Print("generatedProjectPath = "); GeneratedProjectPath.PrintEx(printer); printer.Println(); + printer.Print("generatedFilesPaths = "); GeneratedFilesPaths.PrintEx(printer); printer.Println(); + printer.Print("exceptionMessage = "); ExceptionMessage.PrintEx(printer); printer.Println(); + printer.Print("testsCount = "); TestsCount.PrintEx(printer); printer.Println(); + printer.Print("errorsCount = "); ErrorsCount.PrintEx(printer); printer.Println(); + } + printer.Print(")"); + } + //toString + public override string ToString() + { + var printer = new SingleLinePrettyPrinter(); + Print(printer); + return printer.ToString(); + } + } + + + /// + ///

    Generated from: CSharpModel.kt:16

    + ///
    + public sealed class MapEntry : IPrintable, IEquatable + { + //fields + //public fields + [NotNull] public string Key {get; private set;} + [NotNull] public string Value {get; private set;} + + //private fields + //primary constructor + public MapEntry( + [NotNull] string key, + [NotNull] string value + ) + { + if (key == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException("value"); + + Key = key; + Value = value; + } + //secondary constructor + //deconstruct trait + public void Deconstruct([NotNull] out string key, [NotNull] out string value) + { + key = Key; + value = Value; + } + //statics + + public static CtxReadDelegate Read = (ctx, reader) => + { + var key = reader.ReadString(); + var value = reader.ReadString(); + var _result = new MapEntry(key, value); + return _result; + }; + + public static CtxWriteDelegate Write = (ctx, writer, value) => + { + writer.Write(value.Key); + writer.Write(value.Value); + }; + + //constants + + //custom body + //methods + //equals trait + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((MapEntry) obj); + } + public bool Equals(MapEntry other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Key == other.Key && Value == other.Value; + } + //hash code trait + public override int GetHashCode() + { + unchecked { + var hash = 0; + hash = hash * 31 + Key.GetHashCode(); + hash = hash * 31 + Value.GetHashCode(); + return hash; + } + } + //pretty print + public void Print(PrettyPrinter printer) + { + printer.Println("MapEntry ("); + using (printer.IndentCookie()) { + printer.Print("key = "); Key.PrintEx(printer); printer.Println(); + printer.Print("value = "); Value.PrintEx(printer); printer.Println(); + } + printer.Print(")"); + } + //toString + public override string ToString() + { + var printer = new SingleLinePrettyPrinter(); + Print(printer); + return printer.ToString(); + } + } + + + /// + ///

    Generated from: CSharpModel.kt:9

    + ///
    + public sealed class MethodDescriptor : IPrintable, IEquatable + { + //fields + //public fields + [NotNull] public string MethodName {get; private set;} + [NotNull] public string TypeName {get; private set;} + public bool HasNoOverloads {get; private set;} + [NotNull] public List Parameters {get; private set;} + + //private fields + //primary constructor + public MethodDescriptor( + [NotNull] string methodName, + [NotNull] string typeName, + bool hasNoOverloads, + [NotNull] List parameters + ) + { + if (methodName == null) throw new ArgumentNullException("methodName"); + if (typeName == null) throw new ArgumentNullException("typeName"); + if (parameters == null) throw new ArgumentNullException("parameters"); + + MethodName = methodName; + TypeName = typeName; + HasNoOverloads = hasNoOverloads; + Parameters = parameters; + } + //secondary constructor + //deconstruct trait + public void Deconstruct([NotNull] out string methodName, [NotNull] out string typeName, out bool hasNoOverloads, [NotNull] out List parameters) + { + methodName = MethodName; + typeName = TypeName; + hasNoOverloads = HasNoOverloads; + parameters = Parameters; + } + //statics + + public static CtxReadDelegate Read = (ctx, reader) => + { + var methodName = reader.ReadString(); + var typeName = reader.ReadString(); + var hasNoOverloads = reader.ReadBool(); + var parameters = ReadStringList(ctx, reader); + var _result = new MethodDescriptor(methodName, typeName, hasNoOverloads, parameters); + return _result; + }; + public static CtxReadDelegate> ReadStringList = JetBrains.Rd.Impl.Serializers.ReadString.List(); + + public static CtxWriteDelegate Write = (ctx, writer, value) => + { + writer.Write(value.MethodName); + writer.Write(value.TypeName); + writer.Write(value.HasNoOverloads); + WriteStringList(ctx, writer, value.Parameters); + }; + public static CtxWriteDelegate> WriteStringList = JetBrains.Rd.Impl.Serializers.WriteString.List(); + + //constants + + //custom body + //methods + //equals trait + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((MethodDescriptor) obj); + } + public bool Equals(MethodDescriptor other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return MethodName == other.MethodName && TypeName == other.TypeName && HasNoOverloads == other.HasNoOverloads && Parameters.SequenceEqual(other.Parameters); + } + //hash code trait + public override int GetHashCode() + { + unchecked { + var hash = 0; + hash = hash * 31 + MethodName.GetHashCode(); + hash = hash * 31 + TypeName.GetHashCode(); + hash = hash * 31 + HasNoOverloads.GetHashCode(); + hash = hash * 31 + Parameters.ContentHashCode(); + return hash; + } + } + //pretty print + public void Print(PrettyPrinter printer) + { + printer.Println("MethodDescriptor ("); + using (printer.IndentCookie()) { + printer.Print("methodName = "); MethodName.PrintEx(printer); printer.Println(); + printer.Print("typeName = "); TypeName.PrintEx(printer); printer.Println(); + printer.Print("hasNoOverloads = "); HasNoOverloads.PrintEx(printer); printer.Println(); + printer.Print("parameters = "); Parameters.PrintEx(printer); printer.Println(); + } + printer.Print(")"); + } + //toString + public override string ToString() + { + var printer = new SingleLinePrettyPrinter(); + Print(printer); + return printer.ToString(); + } + } +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot.Rd/RdUtil.cs b/utbot-rider/src/dotnet/UtBot/UtBot.Rd/RdUtil.cs new file mode 100644 index 0000000000..d069e66524 --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot.Rd/RdUtil.cs @@ -0,0 +1,7 @@ +namespace UtBot.Rd; + +public static class RdUtil +{ + public static readonly string MainProcessName = "UtBot"; + public static readonly string DefaultTestProjectTarget = "net6.0"; +} \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot.Rd/TypeDescriptor.cs b/utbot-rider/src/dotnet/UtBot/UtBot.Rd/TypeDescriptor.cs new file mode 100644 index 0000000000..7f370c5cde --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot.Rd/TypeDescriptor.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace UtBot.Rd; + +// Type descriptors are separately serialized to json because +// they are recursive (have type descriptor Parameters) +public class TypeDescriptor +{ + public int? ArrayRank { get; set; } + public string Name { get; set; } + public int? MethodParameterPosition { get; set; } + public int? TypeParameterPosition { get; set; } + public List Parameters { get; set; } +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot.Rd/UtBot.Rd.csproj b/utbot-rider/src/dotnet/UtBot/UtBot.Rd/UtBot.Rd.csproj new file mode 100644 index 0000000000..a037083c98 --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot.Rd/UtBot.Rd.csproj @@ -0,0 +1,14 @@ + + + + net6.0 + disable + UtBot.Rd + + + + + + + + diff --git a/utbot-rider/src/dotnet/UtBot/UtBot.VSharp/UtBot.VSharp.csproj b/utbot-rider/src/dotnet/UtBot/UtBot.VSharp/UtBot.VSharp.csproj new file mode 100644 index 0000000000..a8cab38432 --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot.VSharp/UtBot.VSharp.csproj @@ -0,0 +1,18 @@ + + + + Exe + net6.0 + disable + UtBot.VSharp + UtBot.VSharp.VSharpMain + + + + + + + + + + diff --git a/utbot-rider/src/dotnet/UtBot/UtBot.VSharp/VSharpMain.cs b/utbot-rider/src/dotnet/UtBot/UtBot.VSharp/VSharpMain.cs new file mode 100644 index 0000000000..662f2e9a1a --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot.VSharp/VSharpMain.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; +using JetBrains.Collections.Viewable; +using JetBrains.Lifetimes; +using JetBrains.Rd; +using JetBrains.Rd.Impl; +using JetBrains.Rd.Tasks; +using UtBot.Rd; +using UtBot.Rd.Generated; +using VSharp; +using VSharp.TestRenderer; + +namespace UtBot.VSharp; + +public static class VSharpMain +{ + public static readonly string VSharpProcessName = "VSharp"; + + private class SignalWriter : TextWriter + { + private readonly ISignal _signal; + public SignalWriter(ISignal signal) + { + _signal = signal; + } + + public override void Write(string value) + { + _signal.Fire(value); + } + + public override void WriteLine(string value) + { + Write(value); + } + + public override Encoding Encoding => Encoding.Default; + } + + private static GenerateResults GenerateImpl(GenerateArguments arguments, IReadOnlyDictionary assembliesFullNameToTheirPath) + { + var (assemblyPath, projectCsprojPath, solutionFilePath, + methodDescriptors, generationTimeout, targetFramework, _) = arguments; + + string AssemblyResolveFunc(string fullAssemblyName) + { + return !assembliesFullNameToTheirPath.TryGetValue(fullAssemblyName, out var found) ? null : found; + } + + var assemblyLoadContext = new AssemblyLoadContext(VSharpProcessName); + assemblyLoadContext.Resolving += (context, assemblyName) => + { + var found = AssemblyResolveFunc(assemblyName.FullName); + if (found == null) + { + return null; + } + return context.LoadFromAssemblyPath(found); + }; + + using (new DependencyResolver(AssemblyResolveFunc)) + { + var assembly = assemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + var methods = methodDescriptors.Select(d => d.ToMethodInfo(assembly)).ToList(); + var declaringType = methods.Select(m => m.DeclaringType).Distinct().SingleOrDefault(); + var stat = TestGenerator.Cover(methods, generationTimeout, verbosity: Verbosity.Info); + + var testedProject = new FileInfo(projectCsprojPath); + var solution = new FileInfo(solutionFilePath); + + var (generatedProject, renderedFiles) = + Renderer.Render(stat.Results(), testedProject, declaringType, solution, targetFramework); + + return new GenerateResults( + generatedProject.FullName, + renderedFiles, + null, + (int)stat.TestsCount, + (int)stat.ErrorsCount); + } + } + + private static bool MatchesType(TypeDescriptor typeDescriptor, Type typ) + { + if (typ.IsGenericMethodParameter) + { + return typ.GenericParameterPosition == typeDescriptor.MethodParameterPosition; + } + + if (typ.IsGenericTypeParameter) + { + return typ.GenericParameterPosition == typeDescriptor.TypeParameterPosition; + } + + if (typ.IsArray) + { + return typ.GetArrayRank() == typeDescriptor.ArrayRank && + MatchesType(typeDescriptor.Parameters[0], typ.GetElementType()); + } + + var name = typ.IsGenericType ? typ.GetGenericTypeDefinition().FullName : typ.FullName; + + if (name != typeDescriptor.Name) + { + Logger.printLogString(Logger.Error, $"{typ.FullName} != {typeDescriptor.Name}"); + return false; + } + + var genericArguments = typ.GetGenericArguments(); + + if (genericArguments.Length != typeDescriptor.Parameters.Count) + { + return false; + } + + for (var i = 0; i < genericArguments.Length; ++i) + { + if (!MatchesType(typeDescriptor.Parameters[i], genericArguments[i])) + { + return false; + } + } + + return true; + } + + private static bool MatchesMethod(MethodDescriptor descriptor, MethodInfo methodInfo) + { + var targetParameters = descriptor.Parameters.Select(p => JsonSerializer.Deserialize(p)).ToArray(); + + if (methodInfo.Name != descriptor.MethodName) + { + return false; + } + + var parameters = methodInfo.GetParameters(); + + if (parameters.Length != targetParameters.Length) + { + return false; + } + + for (var i = 0; i < parameters.Length; ++i) + { + if (!MatchesType(targetParameters[i], parameters[i].ParameterType)) + { + return false; + } + } + + return true; + } + + private static MethodInfo ToMethodInfo(this MethodDescriptor descriptor, Assembly assembly) + { + var type = assembly.GetType(descriptor.TypeName, throwOnError: false); + + if (type?.FullName != descriptor.TypeName) + throw new InvalidDataException($"Cannot find type {descriptor.TypeName}, found: {type?.Name}"); + + MethodInfo methodInfo; + var bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public; + + if (descriptor.HasNoOverloads) + { + methodInfo = type.GetMethod(descriptor.MethodName, bindingFlags); + } + else + { + methodInfo = type.GetMethods() + .FirstOrDefault(m => MatchesMethod(descriptor, m)); + } + + if (methodInfo?.Name != descriptor.MethodName) + throw new InvalidDataException( + $"Cannot find method ${descriptor.MethodName} for type ${descriptor.TypeName}"); + + return methodInfo; + } + + public static void Main(string[] args) + { + using var blockingQueue = new BlockingCollection(1); + var port = int.Parse(args[0]); + var ldef = new LifetimeDefinition(); + SingleThreadScheduler.RunOnSeparateThread(ldef.Lifetime, VSharpProcessName, scheduler => + { + var wire = new SocketWire.Client(ldef.Lifetime, scheduler, port); + var serializers = new Serializers(); + var identities = new Identities(IdKind.Client); + var protocol = new Protocol(VSharpProcessName, serializers, identities, scheduler, wire, ldef.Lifetime); + scheduler.Queue(() => + { + var vSharpModel = new VSharpModel(ldef.Lifetime, protocol); + // Configuring V# logger: messages will be send via RD to UTBot plugin process + Logger.configureWriter(new SignalWriter(vSharpModel.Log)); + vSharpModel.Generate.Set((_, arguments) => + { + try + { + var assemblyToPath = arguments.AssembliesFullNameToTheirPath.ToDictionary(it => it.Key, it => it.Value); + return GenerateImpl(arguments, assemblyToPath); + } + catch (Exception e) + { + return new GenerateResults(null, new(), e.ToString(), 0, 0); + } + finally + { + var pathToCsProject = arguments.ProjectCsprojPath; + var csProjFile = new FileInfo(pathToCsProject); + if (csProjFile.Exists) + { + var csProjDir = csProjFile.Directory; + var vSharpDirs = csProjDir!.GetDirectories(); + foreach(var dir in vSharpDirs) + { + if (Regex.IsMatch(dir.Name, @"^VSharp\.tests\..+$")) + { + dir.Delete(recursive: true); + } + } + } + scheduler.Queue(() => { blockingQueue.Add("End"); }); + } + }); + vSharpModel.Ping.Advise(ldef.Lifetime, s => + { + if (s == RdUtil.MainProcessName) + { + vSharpModel.Ping.Fire(VSharpProcessName); + } + }); + }); + }); + blockingQueue.Take(); + ldef.Terminate(); + } +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot.sln b/utbot-rider/src/dotnet/UtBot/UtBot.sln new file mode 100644 index 0000000000..c9234f0b98 --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtBot", "UtBot\UtBot.csproj", "{573A2CF1-56F0-4350-A018-E25A52A86E63}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtBot.VSharp", "UtBot.VSharp\UtBot.VSharp.csproj", "{C076EF37-4DCA-4852-9D5D-04B18E662DBF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtBot.Rd", "UtBot.Rd\UtBot.Rd.csproj", "{B8B9FF73-A630-4476-82BD-C0BBD8C8A9E5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {573A2CF1-56F0-4350-A018-E25A52A86E63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {573A2CF1-56F0-4350-A018-E25A52A86E63}.Debug|Any CPU.Build.0 = Debug|Any CPU + {573A2CF1-56F0-4350-A018-E25A52A86E63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {573A2CF1-56F0-4350-A018-E25A52A86E63}.Release|Any CPU.Build.0 = Release|Any CPU + {C076EF37-4DCA-4852-9D5D-04B18E662DBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C076EF37-4DCA-4852-9D5D-04B18E662DBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C076EF37-4DCA-4852-9D5D-04B18E662DBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C076EF37-4DCA-4852-9D5D-04B18E662DBF}.Release|Any CPU.Build.0 = Release|Any CPU + {B8B9FF73-A630-4476-82BD-C0BBD8C8A9E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8B9FF73-A630-4476-82BD-C0BBD8C8A9E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8B9FF73-A630-4476-82BD-C0BBD8C8A9E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8B9FF73-A630-4476-82BD-C0BBD8C8A9E5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestAction.cs b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestAction.cs new file mode 100644 index 0000000000..6c9d8c290f --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestAction.cs @@ -0,0 +1,12 @@ +using JetBrains.Application.UI.ActionsRevised.Menu; +using JetBrains.ReSharper.Feature.Services.Generate.Actions; +using JetBrains.ReSharper.Resources.Resources.Icons; +using JetBrains.UI.RichText; + +namespace UtBot; + +[Action("Generate.UnitTest", "Generate Unit Test", Icon = typeof(PsiFeaturesUnsortedThemedIcons.FuncZoneGenerate))] +internal class GenerateUnitTestAction : GenerateActionBase +{ + protected override RichText Caption => "Generate Unit Test"; +} \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestElementProvider.cs b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestElementProvider.cs new file mode 100644 index 0000000000..c9ca0341e7 --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestElementProvider.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using JetBrains.Annotations; +using JetBrains.ReSharper.Feature.Services.CSharp.Generate; +using JetBrains.ReSharper.Feature.Services.Generate; +using JetBrains.ReSharper.Psi; +using JetBrains.ReSharper.Psi.CSharp; +using JetBrains.ReSharper.Psi.Resolve; +using JetBrains.ReSharper.Psi.Tree; + +namespace UtBot; + +internal class TimeoutGeneratorOption : IGeneratorOption +{ + public static readonly string Id = "TimeoutGeneratorOption"; + private const string InitialValue = "10"; + + public IReadOnlyList GetPossibleValues() => new string[] { }; + + public bool IsValidValue(string value) => + int.TryParse(value, out var intValue) && intValue > 0; + + public string ID => Id; + + public string Title => "Timeout (s) for all selected methods"; + + public GeneratorOptionKind Kind => GeneratorOptionKind.Text; + + public bool Persist => false; + + public string Value { get; set; } = InitialValue; + + public bool OverridesGlobalOption { get; set; } = false; + + public bool HasDependentOptions => false; +} + +[GeneratorElementProvider(GenerateUnitTestWorkflow.Kind, typeof(CSharpLanguage))] +internal class GenerateUnitTestElementProvider : GeneratorProviderBase +{ + public override void Populate(CSharpGeneratorContext context) + { + context.Options.Add(new TimeoutGeneratorOption()); + + var memberSource = context.ExternalElementsSource?.GetTypeElement() ?? context.ClassDeclaration.DeclaredElement; + if (memberSource == null) return; + + var substitution = context.ExternalElementsSource?.GetSubstitution() ?? memberSource.IdSubstitution; + + var usageContext = (ITreeNode)context.ClassDeclaration.Body ?? context.ClassDeclaration; + + foreach (var method in memberSource.Methods) + { + if (MethodFilter(method, substitution, usageContext)) + { + var element = new GeneratorDeclaredElement(method, substitution); + context.ProvidedElements.Add(element); + context.InputElements.Add(element); + } + } + } + + protected virtual bool MethodFilter([NotNull] IMethod method, ISubstitution substitution, + [NotNull] ITreeNode context) + { + if (method.IsSynthetic()) return false; + if (method.GetAccessRights() != AccessRights.PUBLIC) return false; + return true; + } +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflow.cs b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflow.cs new file mode 100644 index 0000000000..0daa5f1237 --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflow.cs @@ -0,0 +1,23 @@ +using JetBrains.ReSharper.Feature.Services.Generate.Actions; +using JetBrains.ReSharper.Feature.Services.Generate.Workflows; +using JetBrains.ReSharper.Psi.Resources; + +namespace UtBot; + +public class GenerateUnitTestWorkflow : GenerateCodeWorkflowBase +{ + public const string Kind = "UnitTest"; + + public GenerateUnitTestWorkflow() : base( + Kind, + PsiSymbolsThemedIcons.SymbolUnitTest.Id, + "Tests with UnitTestBot", + GenerateActionGroup.CLR_LANGUAGE, + "Generate tests with UnitTestBot", + "Select methods for generation", + "Generate.UnitTest") + { + } + + public override double Order => 10; +} \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflowProvider.cs b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflowProvider.cs new file mode 100644 index 0000000000..661a6d8e4f --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/GenerateUnitTestWorkflowProvider.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using JetBrains.Application.DataContext; +using JetBrains.ReSharper.Feature.Services.Generate.Actions; + +namespace UtBot; + +[GenerateProvider] +public class GenerateUnitTestWorkflowProvider : IGenerateWorkflowProvider +{ + public IEnumerable CreateWorkflow(IDataContext dataContext) + { + return new[] { new GenerateUnitTestWorkflow() }; + } +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/Generated/UtBotRiderModel.Generated.cs b/utbot-rider/src/dotnet/UtBot/UtBot/Generated/UtBotRiderModel.Generated.cs new file mode 100644 index 0000000000..cf5bfd3ebb --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/Generated/UtBotRiderModel.Generated.cs @@ -0,0 +1,268 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a RdGen v1.10. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using JetBrains.Annotations; + +using JetBrains.Core; +using JetBrains.Diagnostics; +using JetBrains.Collections; +using JetBrains.Collections.Viewable; +using JetBrains.Lifetimes; +using JetBrains.Serialization; +using JetBrains.Rd; +using JetBrains.Rd.Base; +using JetBrains.Rd.Impl; +using JetBrains.Rd.Tasks; +using JetBrains.Rd.Util; +using JetBrains.Rd.Text; + + +// ReSharper disable RedundantEmptyObjectCreationArgumentList +// ReSharper disable InconsistentNaming +// ReSharper disable RedundantOverflowCheckingContext + + +namespace JetBrains.Rider.Model +{ + + + /// + ///

    Generated from: UtBotRiderModel.kt:8

    + ///
    + public class UtBotRiderModel : RdExtBase + { + //fields + //public fields + [NotNull] public ISignal StartPublish => _StartPublish; + [NotNull] public ISignal LogPublishOutput => _LogPublishOutput; + [NotNull] public ISignal LogPublishError => _LogPublishError; + [NotNull] public ISignal StopPublish => _StopPublish; + [NotNull] public ISignal StartVSharp => _StartVSharp; + [NotNull] public ISignal LogVSharp => _LogVSharp; + [NotNull] public ISignal StopVSharp => _StopVSharp; + + //private fields + [NotNull] private readonly RdSignal _StartPublish; + [NotNull] private readonly RdSignal _LogPublishOutput; + [NotNull] private readonly RdSignal _LogPublishError; + [NotNull] private readonly RdSignal _StopPublish; + [NotNull] private readonly RdSignal _StartVSharp; + [NotNull] private readonly RdSignal _LogVSharp; + [NotNull] private readonly RdSignal _StopVSharp; + + //primary constructor + private UtBotRiderModel( + [NotNull] RdSignal startPublish, + [NotNull] RdSignal logPublishOutput, + [NotNull] RdSignal logPublishError, + [NotNull] RdSignal stopPublish, + [NotNull] RdSignal startVSharp, + [NotNull] RdSignal logVSharp, + [NotNull] RdSignal stopVSharp + ) + { + if (startPublish == null) throw new ArgumentNullException("startPublish"); + if (logPublishOutput == null) throw new ArgumentNullException("logPublishOutput"); + if (logPublishError == null) throw new ArgumentNullException("logPublishError"); + if (stopPublish == null) throw new ArgumentNullException("stopPublish"); + if (startVSharp == null) throw new ArgumentNullException("startVSharp"); + if (logVSharp == null) throw new ArgumentNullException("logVSharp"); + if (stopVSharp == null) throw new ArgumentNullException("stopVSharp"); + + _StartPublish = startPublish; + _LogPublishOutput = logPublishOutput; + _LogPublishError = logPublishError; + _StopPublish = stopPublish; + _StartVSharp = startVSharp; + _LogVSharp = logVSharp; + _StopVSharp = stopVSharp; + _StartPublish.Async = true; + _LogPublishOutput.Async = true; + _LogPublishError.Async = true; + _StopPublish.Async = true; + _StartVSharp.Async = true; + _LogVSharp.Async = true; + _StopVSharp.Async = true; + BindableChildren.Add(new KeyValuePair("startPublish", _StartPublish)); + BindableChildren.Add(new KeyValuePair("logPublishOutput", _LogPublishOutput)); + BindableChildren.Add(new KeyValuePair("logPublishError", _LogPublishError)); + BindableChildren.Add(new KeyValuePair("stopPublish", _StopPublish)); + BindableChildren.Add(new KeyValuePair("startVSharp", _StartVSharp)); + BindableChildren.Add(new KeyValuePair("logVSharp", _LogVSharp)); + BindableChildren.Add(new KeyValuePair("stopVSharp", _StopVSharp)); + } + //secondary constructor + internal UtBotRiderModel ( + ) : this ( + new RdSignal(StartPublishArgs.Read, StartPublishArgs.Write), + new RdSignal(JetBrains.Rd.Impl.Serializers.ReadString, JetBrains.Rd.Impl.Serializers.WriteString), + new RdSignal(JetBrains.Rd.Impl.Serializers.ReadString, JetBrains.Rd.Impl.Serializers.WriteString), + new RdSignal(JetBrains.Rd.Impl.Serializers.ReadInt, JetBrains.Rd.Impl.Serializers.WriteInt), + new RdSignal(JetBrains.Rd.Impl.Serializers.ReadVoid, JetBrains.Rd.Impl.Serializers.WriteVoid), + new RdSignal(JetBrains.Rd.Impl.Serializers.ReadString, JetBrains.Rd.Impl.Serializers.WriteString), + new RdSignal(JetBrains.Rd.Impl.Serializers.ReadInt, JetBrains.Rd.Impl.Serializers.WriteInt) + ) {} + //deconstruct trait + //statics + + + + protected override long SerializationHash => 6014484928290881L; + + protected override Action Register => RegisterDeclaredTypesSerializers; + public static void RegisterDeclaredTypesSerializers(ISerializers serializers) + { + + serializers.RegisterToplevelOnce(typeof(IdeRoot), IdeRoot.RegisterDeclaredTypesSerializers); + } + + + //constants + + //custom body + //methods + //equals trait + //hash code trait + //pretty print + public override void Print(PrettyPrinter printer) + { + printer.Println("UtBotRiderModel ("); + using (printer.IndentCookie()) { + printer.Print("startPublish = "); _StartPublish.PrintEx(printer); printer.Println(); + printer.Print("logPublishOutput = "); _LogPublishOutput.PrintEx(printer); printer.Println(); + printer.Print("logPublishError = "); _LogPublishError.PrintEx(printer); printer.Println(); + printer.Print("stopPublish = "); _StopPublish.PrintEx(printer); printer.Println(); + printer.Print("startVSharp = "); _StartVSharp.PrintEx(printer); printer.Println(); + printer.Print("logVSharp = "); _LogVSharp.PrintEx(printer); printer.Println(); + printer.Print("stopVSharp = "); _StopVSharp.PrintEx(printer); printer.Println(); + } + printer.Print(")"); + } + //toString + public override string ToString() + { + var printer = new SingleLinePrettyPrinter(); + Print(printer); + return printer.ToString(); + } + } + public static class SolutionUtBotRiderModelEx + { + public static UtBotRiderModel GetUtBotRiderModel(this Solution solution) + { + return solution.GetOrCreateExtension("utBotRiderModel", () => new UtBotRiderModel()); + } + } + + + /// + ///

    Generated from: UtBotRiderModel.kt:9

    + ///
    + public sealed class StartPublishArgs : IPrintable, IEquatable + { + //fields + //public fields + [NotNull] public string FileName {get; private set;} + [NotNull] public string Arguments {get; private set;} + [NotNull] public string WorkingDirectory {get; private set;} + + //private fields + //primary constructor + public StartPublishArgs( + [NotNull] string fileName, + [NotNull] string arguments, + [NotNull] string workingDirectory + ) + { + if (fileName == null) throw new ArgumentNullException("fileName"); + if (arguments == null) throw new ArgumentNullException("arguments"); + if (workingDirectory == null) throw new ArgumentNullException("workingDirectory"); + + FileName = fileName; + Arguments = arguments; + WorkingDirectory = workingDirectory; + } + //secondary constructor + //deconstruct trait + public void Deconstruct([NotNull] out string fileName, [NotNull] out string arguments, [NotNull] out string workingDirectory) + { + fileName = FileName; + arguments = Arguments; + workingDirectory = WorkingDirectory; + } + //statics + + public static CtxReadDelegate Read = (ctx, reader) => + { + var fileName = reader.ReadString(); + var arguments = reader.ReadString(); + var workingDirectory = reader.ReadString(); + var _result = new StartPublishArgs(fileName, arguments, workingDirectory); + return _result; + }; + + public static CtxWriteDelegate Write = (ctx, writer, value) => + { + writer.Write(value.FileName); + writer.Write(value.Arguments); + writer.Write(value.WorkingDirectory); + }; + + //constants + + //custom body + //methods + //equals trait + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((StartPublishArgs) obj); + } + public bool Equals(StartPublishArgs other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return FileName == other.FileName && Arguments == other.Arguments && WorkingDirectory == other.WorkingDirectory; + } + //hash code trait + public override int GetHashCode() + { + unchecked { + var hash = 0; + hash = hash * 31 + FileName.GetHashCode(); + hash = hash * 31 + Arguments.GetHashCode(); + hash = hash * 31 + WorkingDirectory.GetHashCode(); + return hash; + } + } + //pretty print + public void Print(PrettyPrinter printer) + { + printer.Println("StartPublishArgs ("); + using (printer.IndentCookie()) { + printer.Print("fileName = "); FileName.PrintEx(printer); printer.Println(); + printer.Print("arguments = "); Arguments.PrintEx(printer); printer.Println(); + printer.Print("workingDirectory = "); WorkingDirectory.PrintEx(printer); printer.Println(); + } + printer.Print(")"); + } + //toString + public override string ToString() + { + var printer = new SingleLinePrettyPrinter(); + Print(printer); + return printer.ToString(); + } + } +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/ProcessWithRdServer.cs b/utbot-rider/src/dotnet/UtBot/UtBot/ProcessWithRdServer.cs new file mode 100644 index 0000000000..0d7f3616ff --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/ProcessWithRdServer.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Net; +using JetBrains.Annotations; +using JetBrains.Application.Threading; +using JetBrains.Collections.Viewable; +using JetBrains.Lifetimes; +using JetBrains.Rd; +using JetBrains.Rd.Impl; +using JetBrains.Rider.Model; +using JetBrains.Threading; +using JetBrains.Util; +using JetBrains.Util.Logging; +using UtBot.Rd; +using UtBot.Rd.Generated; + +namespace UtBot; + +[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] +public class ProcessWithRdServer +{ + public Lifetime Lifetime => _ldef.Lifetime; + public Protocol Protocol; + [CanBeNull] public VSharpModel VSharpModel { get; private set; } + + private readonly LifetimeDefinition _ldef; + public readonly Process Proc = new(); + + public ProcessWithRdServer(string name, string workingDir, int port, string exePath, IShellLocks shellLocks, UtBotRiderModel riderModel, Lifetime? parent = null, [CanBeNull] ILogger logger = null) + { + logger ??= Logger.GetLogger(); + using var blockingCollection = new BlockingCollection(2); + shellLocks.AssertNonMainThread(); + _ldef = (parent ?? Lifetime.Eternal).CreateNested(); + var pingLdef = _ldef.Lifetime.CreateNested(); + try + { + SingleThreadScheduler.RunOnSeparateThread(Lifetime, name, scheduler => + { + var endPoint = new IPEndPoint(IPAddress.Loopback, port); + var socket = SocketWire.Server.CreateServerSocket(endPoint); + var wire = new SocketWire.Server(Lifetime, scheduler, socket); + var serializers = new Serializers(); + var identities = new Identities(IdKind.Server); + var startInfo = new ProcessStartInfo("dotnet", $"--roll-forward LatestMajor \"{exePath}\" {port}") + { + WorkingDirectory = workingDir + }; + riderModel.StartVSharp.Fire(); + Protocol = new Protocol(name, serializers, identities, scheduler, wire, Lifetime); + scheduler.Queue(() => + { + VSharpModel = new VSharpModel(Lifetime, Protocol); + VSharpModel.Ping.Advise(pingLdef.Lifetime, s => + { + if (s == name) + { + blockingCollection.TryAdd(s); + } + }); + VSharpModel.Log.Advise(Lifetime, s => + { + logger.Info($"V#: {s}"); + riderModel.LogVSharp.Fire(s); + }); + }); + Proc.StartInfo = startInfo; + Lifetime.OnTermination(() => Proc.Kill(entireProcessTree: true)); + if (Proc.Start()) + Proc.Exited += (_, _) => _ldef.Terminate(); + else + _ldef.Terminate(); + }); + + if (Proc?.HasExited == true) return; + + SpinWaitEx.SpinUntil(pingLdef.Lifetime, () => + { + if (Proc?.HasExited == true) + { + VSharpModel = null; + _ldef.Terminate(); + } + + VSharpModel?.Ping.Fire(RdUtil.MainProcessName); + return blockingCollection.TryTake(out _); + }); + pingLdef.Terminate(); + } + catch (Exception) + { + _ldef.Terminate(); + throw; + } + } +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/UnitTestBuilder.cs b/utbot-rider/src/dotnet/UtBot/UtBot/UnitTestBuilder.cs new file mode 100644 index 0000000000..e0cc0a98e2 --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/UnitTestBuilder.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Timers; +using JetBrains.Application.Notifications; +using JetBrains.Application.Progress; +using JetBrains.Application.Threading; +using JetBrains.Application.Threading.Tasks; +using JetBrains.Application.UI.Controls; +using JetBrains.Lifetimes; +using JetBrains.ProjectModel; +using JetBrains.ProjectModel.ProjectsHost; +using JetBrains.Rd.Tasks; +using JetBrains.RdBackend.Common.Features; +using JetBrains.ReSharper.Feature.Services.CSharp.Generate; +using JetBrains.ReSharper.Feature.Services.Generate; +using JetBrains.ReSharper.Psi; +using JetBrains.ReSharper.Psi.CSharp; +using JetBrains.ReSharper.Psi.Util; +using JetBrains.Rider.Model; +using JetBrains.Util; +using JetBrains.Util.Threading; +using UtBot.Rd; +using UtBot.Rd.Generated; +using UtBot.Utils; +using UtBot.VSharp; + +namespace UtBot; + +[GeneratorBuilder(GenerateUnitTestWorkflow.Kind, typeof(CSharpLanguage))] +internal sealed class UnitTestBuilder : GeneratorBuilderBase +{ + private const string PublishDirName = "utbot-publish"; + + private readonly IBackgroundProgressIndicatorManager _backgroundProgressIndicatorManager; + private readonly Lifetime _lifetime; + private readonly ILogger _logger; + private readonly IShellLocks _shellLocks; + private readonly UtBotRiderModel _riderModel; + private readonly Notifications _notifications; + + public UnitTestBuilder( + Lifetime lifetime, + ISolution solution, + IShellLocks shellLocks, + IBackgroundProgressIndicatorManager backgroundProgressIndicatorManager, + ILogger logger, + Notifications notifications) + { + _lifetime = lifetime; + _shellLocks = shellLocks; + _backgroundProgressIndicatorManager = backgroundProgressIndicatorManager; + _logger = logger; + _notifications = notifications; + _riderModel = solution.GetProtocolSolution().GetUtBotRiderModel(); + } + + protected override void Process(CSharpGeneratorContext context, IProgressIndicator progress) + { + _notifications.Refresh(); + + var timeoutString = context.GetOption(TimeoutGeneratorOption.Id); + + if (!int.TryParse(timeoutString, out var timeout) || timeout <= 0) + { + _notifications.ShowError("Invalid timeout value. Timeout should be an integer number greater than zero"); + return; + } + + if (context.PsiModule.ContainingProjectModule is not IProject project) return; + + if (!DotNetVersionUtils.CanRunVSharp(project.GetSolution())) + { + _notifications.ShowError($"At least .NET {DotNetVersionUtils.MinCompatibleSdkMajor} SDK is required for UnitTestBot.NET"); + return; + } + + var typeElement = context.ClassDeclaration.DeclaredElement; + if (typeElement == null) return; + if (typeElement is not IClass && typeElement is not IStruct) return; + var testProjectTfm = DotNetVersionUtils.GetTestProjectFramework(project); + + var jsonOptions = new JsonSerializerOptions + { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + var descriptors = new List(); + foreach (var inputElement in context.InputElements.WithProgress(progress, "Generating unit tests") + .OfType>()) + { + var methodName = inputElement.DeclaredElement.ShortName; + var hasNoOverLoads = typeElement.GetAllClassMembers().Count(m => m.Member.ShortName == methodName) == 1; + + var parameterDescriptors = new List(); + + if (!hasNoOverLoads) + { + foreach (var parameter in inputElement.DeclaredElement.Parameters) + { + var typeDescriptor = ToTypeDescriptor(parameter.Type, inputElement.DeclaredElement, typeElement); + parameterDescriptors.Add(JsonSerializer.Serialize(typeDescriptor, jsonOptions)); + } + } + + descriptors.Add(new MethodDescriptor(methodName, typeElement.GetClrName().FullName, hasNoOverLoads, parameterDescriptors)); + } + + var progressLifetimeDef = _lifetime.CreateNested(); + var indicator = + _backgroundProgressIndicatorManager.CreateIndicator(progressLifetimeDef.Lifetime, true, true, + "Generating unit tests"); + + _shellLocks.Tasks.StartNew(_lifetime, Scheduling.FreeThreaded, () => + { + try + { + Generate(indicator, project, descriptors, testProjectTfm, timeout); + } + finally + { + progressLifetimeDef.Terminate(); + } + }); + } + + private TypeDescriptor ToTypeDescriptor(IType typ, IMethod declMethod, ITypeElement declTyp) + { + var par = typ.GetTypeParameterType(); + + if (declMethod.TypeParameters.Contains(par)) + { + return new TypeDescriptor + { + ArrayRank = null, + Name = null, + MethodParameterPosition = declMethod.TypeParameters.IndexOf(par), + TypeParameterPosition = null, + Parameters = new() + }; + } + + if (declTyp.TypeParameters.Contains(par)) + { + return new TypeDescriptor + { + ArrayRank = null, + Name = null, + MethodParameterPosition = null, + TypeParameterPosition = declTyp.TypeParameters.IndexOf(par), + Parameters = new() + }; + } + + if (typ is IArrayType arr) + { + var elementType = ToTypeDescriptor(typ.GetScalarType(), declMethod, declTyp); + + return new TypeDescriptor + { + ArrayRank = arr.Rank, + Name = null, + MethodParameterPosition = null, + TypeParameterPosition = null, + Parameters = new List { elementType } + }; + } + + var subst = typ.GetScalarType().GetSubstitution(); + var pars = typ.GetTypeElement().TypeParameters.Select(p => ToTypeDescriptor(subst[p], declMethod, declTyp)); + + return new TypeDescriptor + { + ArrayRank = null, + Name = typ.GetScalarType()?.GetClrName().FullName, + MethodParameterPosition = null, + TypeParameterPosition = null, + Parameters = pars.ToList() + }; + } + + private void Generate(IBackgroundProgressIndicator progressIndicator, IProject project, + List descriptors, TestProjectTargetFramework testProjectFramework, int timeout) + { + var solution = project.GetSolution(); + var solutionMark = solution.GetSolutionMark(); + if (solutionMark == null) return; + + var solutionFilePath = solutionMark.Location.FullPath; + _logger.Verbose($"Solution path: {solutionFilePath}"); + + project.Locks.AssertNonMainThread(); + + var config = project.ProjectProperties.ActiveConfigurations.Configurations.First(); + var outputDir = project.GetOutputDirectory(config.TargetFrameworkId).Combine(PublishDirName); + progressIndicator.Header.SetValue($"Publishing dependencies to {PublishDirName}..."); + + if (!ProjectPublisher.PublishSync(_logger, progressIndicator, project, config, outputDir, _riderModel)) { + var title = $"Cannot publish project {project.Name}"; + _notifications.ShowError(title); + return; + } + + var assemblyFileName = project.GetOutputFilePath(config.TargetFrameworkId).Name; + var assemblyPath = Directory.GetFiles(outputDir.FullPath, assemblyFileName, SearchOption.AllDirectories).FirstOrDefault(); + if (assemblyPath is null) + { + _notifications.ShowError($"Cannot build project {project.Name}"); + return; + } + + var typeName = descriptors.Select(m => m.TypeName).Distinct().SingleOrDefault(); + + _logger.Verbose($"Start Generation for {typeName}"); + progressIndicator.Lifetime.ThrowIfNotAlive(); + progressIndicator.Header.SetValue(typeName); + + var pluginPath = FileSystemPath.Parse(Assembly.GetExecutingAssembly().Location).Parent; + var vsharpRunner = pluginPath.Combine("UtBot.VSharp.dll"); + + var methodNames = descriptors.Select(m => $"{m.TypeName}.{m.MethodName}").ToArray(); + var intervalS = (double)timeout / methodNames.Length; + var intervalMs = intervalS > 0 ? intervalS * 1000 : 500; + using var methodProgressTimer = new Timer(intervalMs); + var i = 0; + void ChangeMethodName() + { + progressIndicator.Header.SetValue(methodNames[i]); + i = (i + 1) % methodNames.Length; + } + methodProgressTimer.Elapsed += (_, _) => ChangeMethodName(); + _logger.Catch(() => + { + var name = VSharpMain.VSharpProcessName; + var workingDir = project.ProjectFileLocation.Directory.FullPath; + var port = NetworkUtil.GetFreePort(); + var runnerPath = vsharpRunner.FullPath; + var proc = new ProcessWithRdServer(name, workingDir, port, runnerPath, project.Locks, _riderModel, _lifetime, _logger); + var projectCsprojPath = project.ProjectFileLocation.FullPath; + List allAssemblies; + using (_shellLocks.UsingReadLock()) + { + allAssemblies = solution.GetAllAssemblies() + .Where(it => it.Location.AssemblyPhysicalPath is not null) + .Select(it => new MapEntry(it.FullAssemblyName, it.Location.AssemblyPhysicalPath.FullPath)) + .DistinctBy(it => it.Key) + .ToList(); + } + var args = new GenerateArguments(assemblyPath, projectCsprojPath, solutionFilePath, descriptors, + timeout, testProjectFramework.FrameworkMoniker.Name, allAssemblies); + var vSharpTimeout = TimeSpan.FromSeconds(timeout); + var rpcTimeout = new RpcTimeouts(vSharpTimeout + TimeSpan.FromSeconds(1), vSharpTimeout + TimeSpan.FromSeconds(30)); + ChangeMethodName(); + methodProgressTimer.Start(); + var result = proc.VSharpModel?.Generate.Sync(args, rpcTimeout); + methodProgressTimer.Stop(); + proc.Proc.WaitForExit(); + _riderModel.StopVSharp.Fire(proc.Proc.ExitCode); + _logger.Info("Result acquired"); + if (result is { GeneratedProjectPath: not null }) + { + _shellLocks.ExecuteOrQueue(_lifetime, "UnitTestBuilder::Generate", () => + { + if (solution.IsValid()) + { + solution.GetProtocolSolution().GetFileSystemModel().RefreshPaths + .Start(_lifetime, + new RdFsRefreshRequest(new List { result.GeneratedProjectPath }, true)); + + } + }); + + _notifications.ShowInfo( + $"Generated {result.TestsCount} tests and found {result.ErrorsCount} errors for {typeName}"); + + if (testProjectFramework.IsDefault) + { + _notifications.ShowWarning( + $"Generated test project targets {testProjectFramework.FrameworkMoniker}, which is not directly targeted by {project.Name}. " + + "Test project may fail to compile due to reference errors"); + } + } + else + { + var ex = result == null ? "Could not start V#" : result.ExceptionMessage; + _logger.Info($"Could not generate tests for ${typeName}, exception - {ex}"); + + var title = $"Could not generate tests for {typeName}"; + var openExceptionMessageCommand = new UserNotificationCommand( + "Show error info", + () => MessageBox.ShowError(ex ?? "Cannot get error info", title)); + _notifications.ShowError(title, command: openExceptionMessageCommand); + } + }); + + methodProgressTimer.Stop(); + _logger.Verbose($"Generation finished for {typeName}"); + + _shellLocks.ExecuteOrQueue(_lifetime, "UnitTestBuilder::Generate", () => + { + if (project.IsValid()) + solution.GetProtocolSolution().GetFileSystemModel().RefreshPaths + .Start(_lifetime, + new RdFsRefreshRequest(new List { solutionMark.Location.FullPath }, true)); + }); + } +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/UtBot.csproj b/utbot-rider/src/dotnet/UtBot/UtBot/UtBot.csproj new file mode 100644 index 0000000000..83a35b478b --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/UtBot.csproj @@ -0,0 +1,24 @@ + + + + net6.0 + UtBot + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/Utils/DotNetVersionUtils.cs b/utbot-rider/src/dotnet/UtBot/UtBot/Utils/DotNetVersionUtils.cs new file mode 100644 index 0000000000..48aa3380f9 --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/Utils/DotNetVersionUtils.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; +using JetBrains.ProjectModel; +using JetBrains.Util; + +namespace UtBot.Utils; + +internal readonly record struct TestProjectTargetFramework(FrameworkMoniker FrameworkMoniker, bool IsDefault); + +internal static class DotNetVersionUtils +{ + public const int MinCompatibleSdkMajor = 7; + private const string NUnitProjectMinTfm = "net6.0"; + + public static bool CanRunVSharp(ISolution solution) => GetCanRunVSharp(solution.SolutionDirectory.FullPath); + + public static TestProjectTargetFramework GetTestProjectFramework(IProject project) + { + var path = project.ProjectFileLocation.Directory.FullPath; + + var nUnitNewInfo = RunProcess(new ProcessStartInfo + { + FileName = "dotnet", + Arguments = "new nunit -f", + WorkingDirectory = path + }, returnError: true); + + if (nUnitNewInfo == null) + throw new Exception("Could not get new NUnit info"); + + var matches = DotNetRegex.Any.Matches(nUnitNewInfo); + + if (matches.IsEmpty()) + throw new Exception("Could not parse TFMs from new NUnit info"); + + // Code in Allocator.cs uses methods which were added in .NET 6 + var testProjectRequiredFramework = new FrameworkMoniker(NUnitProjectMinTfm); + + var availableTfms = matches + .Select(m => new FrameworkMoniker(m.Value)) + .Distinct() + .Where(s => s.CompareTo(testProjectRequiredFramework) >= 0) + .OrderBy() + .ToList(); + + var projectTfms = GetTfms(project).ToList(); + + TestProjectTargetFramework framework; + + var exactMatch = availableTfms.FirstOrDefault(t => projectTfms.Contains(t)); + + if (exactMatch is not null) + { + return new(exactMatch, false); + } + + var projectStandards = projectTfms.Where(t => t.Kind is DotNetKind.NetStandard); + var standardMatch = + availableTfms.FirstOrDefault(t => projectStandards.Any(t.ImplementsStandard)); + + if (standardMatch is not null) + { + return new(standardMatch, false); + } + + var defaultTfm = availableTfms.First(); + return new(defaultTfm, true); + } + + private static bool GetCanRunVSharp(string workingDir) + { + var sdksInfo = RunProcess(new ProcessStartInfo + { + FileName = "dotnet", + Arguments = "--list-sdks", + WorkingDirectory = workingDir + }); + + if (sdksInfo is null) + { + return false; + } + + var matches = Regex.Matches(sdksInfo, @"(\d+)\.(\d+)\.(\d+)"); + + if (matches.Count < 1) + { + return false; + } + + for (var i = 0; i < matches.Count; ++i) + { + if (!int.TryParse(matches[i].Groups[1].Value, out var majorVersion)) + { + continue; + } + + if (majorVersion >= MinCompatibleSdkMajor) + { + return true; + } + } + + return false; + } + + private static IEnumerable GetTfms(IProject project) => + project.TargetFrameworkIds + .Select(i => new FrameworkMoniker(i.TryGetShortIdentifier())); + + private static string RunProcess(ProcessStartInfo startInfo, bool returnError = false) + { + startInfo.RedirectStandardError = true; + startInfo.RedirectStandardOutput = true; + + var pi = Process.Start(startInfo); + var s = returnError ? pi?.StandardError.ReadToEnd() : pi?.StandardOutput.ReadToEnd(); + pi?.WaitForExit(); + return s; + } +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/Utils/FrameworkMoniker.cs b/utbot-rider/src/dotnet/UtBot/UtBot/Utils/FrameworkMoniker.cs new file mode 100644 index 0000000000..0d7dce61ae --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/Utils/FrameworkMoniker.cs @@ -0,0 +1,148 @@ +using System; +using System.Text.RegularExpressions; +using JetBrains.Util; + +namespace UtBot.Utils; + +internal static class DotNetRegex +{ + public static readonly Regex ModernNet = new(@"net\d+\.\d+"); + public static readonly Regex NetFramework = new(@"net\d+"); + public static readonly Regex NetCore = new(@"netcoreapp(\d+)\.(\d+)"); + public static readonly Regex NetStandard = new(@"netstandard\d+\.\d+"); + public static readonly Regex Any = new(@"net\d+\.\d+|netcoreapp\d+\.\d+|net\d+"); +} + +internal enum DotNetKind +{ + NetFramework, + NetCore, + Net, + NetStandard, + Unknown +} + +internal class FrameworkMoniker : IComparable +{ + public string Name { get; } + public DotNetKind Kind { get; } + + public FrameworkMoniker(string name) + { + // Checking 'netcoreapp6.0' etc. cases --- look like .NET Core, but in fact .NET + var matches = DotNetRegex.NetCore.Matches(name); + if (matches.IsEmpty()) + { + Name = name; + Kind = GetKind(name); + return; + } + + var major = int.Parse(matches[0].Groups[1].Value); + if (major >= 5) + { + var minor = int.Parse(matches[0].Groups[2].Value); + Name = $"net{major}.{minor}"; + Kind = DotNetKind.Net; + return; + } + + Name = name; + Kind = DotNetKind.NetCore; + } + + private const string NetStandard10 = "netstandard1.0"; + private const string NetStandard11 = "netstandard1.1"; + private const string NetStandard12 = "netstandard1.2"; + private const string NetStandard13 = "netstandard1.3"; + private const string NetStandard14 = "netstandard1.4"; + private const string NetStandard20 = "netstandard2.0"; + private const string NetStandard21 = "netstandard2.1"; + + private static DotNetKind GetKind(string tfm) + { + if (DotNetRegex.ModernNet.IsMatch(tfm)) + { + return DotNetKind.Net; + } + + if (DotNetRegex.NetFramework.IsMatch(tfm)) + { + return DotNetKind.NetFramework; + } + + if (DotNetRegex.NetStandard.IsMatch(tfm)) + { + return DotNetKind.NetStandard; + } + + return DotNetRegex.NetCore.IsMatch(tfm) ? DotNetKind.NetCore : DotNetKind.Unknown; + } + + public int CompareTo(FrameworkMoniker other) => + (Kind, other.Kind) switch + { + (DotNetKind.NetStandard, not DotNetKind.NetStandard) or + (not DotNetKind.NetStandard, DotNetKind.NetStandard) => + throw new InvalidOperationException("Cannot compare .NET Standard and specific .NET"), + + (DotNetKind.NetFramework, DotNetKind.NetCore) or + (DotNetKind.NetFramework, DotNetKind.Net) or + (DotNetKind.NetCore, DotNetKind.Net) => -1, + + (DotNetKind.NetCore, DotNetKind.NetFramework) or + (DotNetKind.Net, DotNetKind.NetFramework) or + (DotNetKind.Net, DotNetKind.NetCore) => 1, + + _ => string.Compare(Name, other.Name, StringComparison.InvariantCulture) + }; + + // https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-1-5#select-net-standard-version + public bool ImplementsStandard(FrameworkMoniker standardFrameworkMoniker) + { + if (standardFrameworkMoniker.Kind is not DotNetKind.NetStandard) + { + throw new ArgumentException("standardTfm is not a .NET Standard TFM"); + } + + var standardName = standardFrameworkMoniker.Name; + + return Kind switch + { + DotNetKind.Net => true, + DotNetKind.NetFramework => + Name switch + { + "net45" => standardName is NetStandard10 or NetStandard11, + "net451" or "net452" => standardName is NetStandard10 or NetStandard11 or NetStandard12, + "net46" => standardName is NetStandard10 or NetStandard11 or NetStandard12 or NetStandard13, + "net461" => standardName is NetStandard10 or NetStandard11 or NetStandard12 or NetStandard13 + or NetStandard14, + _ => standardName is not NetStandard21 + }, + DotNetKind.NetCore => + Name switch + { + "netcoreapp1.0" or "netcoreapp1.1" => standardName is not (NetStandard20 or NetStandard21), + "netcoreapp2.0" or "netcoreapp2.1" or "netcoreapp2.2" => standardName is not NetStandard21, + _ => true + }, + DotNetKind.NetStandard => CompareTo(standardFrameworkMoniker) >= 0, + _ => false + }; + } + + public override bool Equals(object obj) + { + if (obj is not FrameworkMoniker another) + { + return false; + } + + return Name.Equals(another.Name); + } + + public override int GetHashCode() => Name.GetHashCode(); + + public override string ToString() => Name; +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/Utils/Notifications.cs b/utbot-rider/src/dotnet/UtBot/UtBot/Utils/Notifications.cs new file mode 100644 index 0000000000..140f9d5eac --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/Utils/Notifications.cs @@ -0,0 +1,98 @@ +using JetBrains.Annotations; +using JetBrains.Application.Notifications; +using JetBrains.Application.Threading; +using JetBrains.Lifetimes; +using JetBrains.ProjectModel; +using JetBrains.Util; + +namespace UtBot.Utils; + +[SolutionComponent] +internal class Notifications +{ + private Lifetime _lifetime; + private readonly SequentialLifetimes _sequentialLifetimes; + private readonly UserNotifications _userNotifications; + private readonly IShellLocks _shellLocks; + private readonly ILogger _logger; + + private const string Title = "UnitTestBot.NET"; + + public Notifications( + Lifetime lifetime, + UserNotifications userNotifications, + IShellLocks shellLocks, + ILogger logger) + { + _sequentialLifetimes = new SequentialLifetimes(lifetime); + _lifetime = _sequentialLifetimes.Next(); + _userNotifications = userNotifications; + _shellLocks = shellLocks; + _logger = logger; + } + + private void ShowNotification( + NotificationSeverity severity, + string title, + string body, + bool closeAfterExecution = true, + UserNotificationCommand command = null) + { + void NotificationAction() + { + _userNotifications.CreateNotification( + _lifetime, + severity, + title, + body, + closeAfterExecution: closeAfterExecution, + executed: command); + } + + _shellLocks.ExecuteOrQueueEx( + _lifetime, + "UtBot::Notification::Show", + NotificationAction); + } + + public void ShowError( + [NotNull] string body, + bool closeAfterExecution = true, + UserNotificationCommand command = null) + { + ShowNotification( + NotificationSeverity.CRITICAL, + Title, + body, + closeAfterExecution: closeAfterExecution, + command: command); + } + + public void ShowInfo( + [NotNull] string body, + bool closeAfterExecution = true, + UserNotificationCommand command = null) + { + ShowNotification( + NotificationSeverity.INFO, + Title, + body, + closeAfterExecution: closeAfterExecution, + command: command); + } + + public void ShowWarning( + [NotNull] string body, + bool closeAfterExecution = true, + UserNotificationCommand command = null) + { + ShowNotification( + NotificationSeverity.WARNING, + Title, + body, + closeAfterExecution: closeAfterExecution, + command: command); + } + + public void Refresh() => _lifetime = _sequentialLifetimes.Next(); +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/Utils/ProjectPublisher.cs b/utbot-rider/src/dotnet/UtBot/UtBot/Utils/ProjectPublisher.cs new file mode 100644 index 0000000000..8358bc2c02 --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/Utils/ProjectPublisher.cs @@ -0,0 +1,105 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using JetBrains.Application.Threading.Tasks; +using JetBrains.Application.UI.Controls; +using JetBrains.Collections.Viewable; +using JetBrains.Lifetimes; +using JetBrains.ProjectModel; +using JetBrains.ProjectModel.Properties; +using JetBrains.Rider.Model; +using JetBrains.Threading; +using JetBrains.Util; +using UtBot.Rd.Generated; + +namespace UtBot.Utils; + +public static class ProjectPublisher +{ + public static bool PublishSync(ILogger logger, IBackgroundProgressIndicator indicator, IProject project, IProjectConfiguration config, VirtualFileSystemPath outputDir, UtBotRiderModel model) + { + var publishLifetimeDef = indicator.Lifetime.CreateNested(); + indicator.Cancel.Advise(Lifetime.Eternal, canceled => {if (canceled) publishLifetimeDef.Terminate();}); + + try + { + Directory.CreateDirectory(outputDir.FullPath); + var projectName = project.ProjectFileLocation.Name; + var architecture = GetArchitecture(); + var tfm = config.TargetFrameworkId.TryGetShortIdentifier(); + var command = config.TargetFrameworkId.IsNetFramework ? "build" : "publish"; + + if (tfm is null) + { + throw new ArgumentException("Cannot get framework moniker from project config"); + } + + var processInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = + $"{command} \"{projectName}\" --sc -c {config.Name} -a {architecture} -o {outputDir.FullPath} -f {tfm}", + WorkingDirectory = project.ProjectFileLocation.Directory.FullPath, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using var process = new Process(); + process.StartInfo = processInfo; + process.OutputDataReceived += (_, args) => + { + model.LogPublishOutput.Fire(args.Data ?? "\n"); + }; + process.ErrorDataReceived += (_, args) => + { + model.LogPublishError.Fire(args.Data ?? "\n"); + }; + process.Exited += (_, a) => + { + publishLifetimeDef.Terminate(); + }; + model.StartPublish.Fire(new StartPublishArgs(processInfo.FileName, processInfo.Arguments, processInfo.WorkingDirectory)); + if (process.Start()) + { + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + SpinWaitEx.SpinUntil(publishLifetimeDef.Lifetime, () => process.HasExited); + if (process.ExitCode != 0) + { + logger.Warn($"Publish process exited with code {process.ExitCode}: {process.StandardOutput.ReadToEnd()}"); + } + model.StopPublish.Fire(process.ExitCode); + } + else + { + model.StopPublish.Fire(-1); + } + return process.HasExited && process.ExitCode == 0; + } + catch (Exception e) + { + logger.Warn(e, comment: "Could not publish project for VSharp, exception occured"); + return false; + } + finally + { + publishLifetimeDef.Terminate(); + } + } + + private static string GetArchitecture() + { + var arch = RuntimeInformation.OSArchitecture; + return arch switch + { + Architecture.X86 => "x86", + Architecture.X64 => "x64", + Architecture.Arm => "arm", + Architecture.Arm64 => "arm64", + Architecture.Wasm or Architecture.S390x => + throw new InvalidOperationException($"Unsupported architecture: {arch}"), + _ => throw new ArgumentOutOfRangeException() + }; + } +} diff --git a/utbot-rider/src/dotnet/UtBot/UtBot/ZoneMarker.cs b/utbot-rider/src/dotnet/UtBot/UtBot/ZoneMarker.cs new file mode 100644 index 0000000000..927016ff1c --- /dev/null +++ b/utbot-rider/src/dotnet/UtBot/UtBot/ZoneMarker.cs @@ -0,0 +1,22 @@ +using JetBrains.Application.BuildScript.Application.Zones; +using JetBrains.ProjectModel; +using JetBrains.ReSharper.Psi.CSharp; +using JetBrains.ReSharper.UnitTestFramework; +using JetBrains.Rider.Model; + +namespace UtBot; + +[ZoneDefinition(ZoneFlags.AutoEnable)] +public interface IUtBotPluginZone : + IZone, + IRequire, + IRequire, + IRequire, + IRequire +{ +} + +[ZoneMarker] +public class ZoneMarker : IRequire +{ +} \ No newline at end of file diff --git a/utbot-rider/src/main/kotlin/org/utbot/rider/UtBotConsoleViewComponent.kt b/utbot-rider/src/main/kotlin/org/utbot/rider/UtBotConsoleViewComponent.kt new file mode 100644 index 0000000000..25b3c7c61b --- /dev/null +++ b/utbot-rider/src/main/kotlin/org/utbot/rider/UtBotConsoleViewComponent.kt @@ -0,0 +1,118 @@ +package org.utbot.rider + +import com.intellij.execution.executors.DefaultRunExecutor +import com.intellij.execution.filters.TextConsoleBuilderFactory +import com.intellij.execution.ui.ConsoleView +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.execution.ui.RunContentDescriptor +import com.intellij.execution.ui.RunContentManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.rd.createLifetime +import com.jetbrains.rd.platform.util.idea.ProtocolSubscribedProjectComponent +import com.jetbrains.rd.platform.util.lifetime +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.reactive.ISignal +import com.jetbrains.rider.projectView.solution +import org.utbot.rider.generated.UtBotRiderModel +import org.utbot.rider.generated.utBotRiderModel +import kotlin.random.Random + +private fun ConsoleView.printYellowLine(any: Any) { + print("$any\n", ConsoleViewContentType.LOG_INFO_OUTPUT) +} + +private fun ConsoleView.printNormalLine(any: Any) { + print("$any\n", ConsoleViewContentType.NORMAL_OUTPUT) +} + +private fun ConsoleView.printErrorLine(any: Any) { + print("$any\n", ConsoleViewContentType.ERROR_OUTPUT) +} + +private fun ISignal.advisePrintExitCode(lifetime: Lifetime, consoleView: ConsoleView) { + advise(lifetime) { exitCode -> + val msg = "Process exited with code: $exitCode" + if (exitCode == 0) + consoleView.printNormalLine(msg) + else + consoleView.printErrorLine(msg) + } +} + +private fun ISignal.advisePrintNormal(lifetime: Lifetime, consoleView: ConsoleView) { + advise(lifetime) { output -> + consoleView.printNormalLine(output) + } +} + +private fun ISignal.advisePrintError(lifetime: Lifetime, consoleView: ConsoleView) { + advise(lifetime) { output -> + consoleView.printErrorLine(output) + } +} + +class UtBotConsoleViewComponent(project: Project) : ProtocolSubscribedProjectComponent(project) { + private var currentExecutionId: Long = -1 + private val model: UtBotRiderModel + + private fun update( + firstLine: String, + displayName: String, + previous: RunContentDescriptor? + ): Triple { + val textConsoleBuilderFactory = TextConsoleBuilderFactory.getInstance() + val consoleView = textConsoleBuilderFactory.createBuilder(project).console + val runContentDescriptor = + RunContentDescriptor(consoleView, null, consoleView.component, displayName).apply { + executionId = currentExecutionId + } + val runContentManager = RunContentManager.getInstance(project) + + runContentManager.showRunContent( + DefaultRunExecutor.getRunExecutorInstance(), + runContentDescriptor, + previous + ) + consoleView.printYellowLine(firstLine) + + return Triple(runContentDescriptor, consoleView, consoleView.createLifetime()) + } + + private fun initPublish() { + var previousPublish: RunContentDescriptor? = null + model.startPublish.advise(project.lifetime) { publishArgs -> + currentExecutionId = Random.nextLong() + val (fileName, arguments, workingDirectory) = publishArgs + val firstLine = "$workingDirectory .> $fileName $arguments" + val (currentPublish, consoleView, consoleLifetime) = update( + firstLine, + "Project publish for UtBot", + previousPublish + ) + previousPublish = currentPublish + model.logPublishOutput.advisePrintNormal(consoleLifetime, consoleView) + model.logPublishError.advisePrintError(consoleLifetime, consoleView) + model.stopPublish.advisePrintExitCode(consoleLifetime, consoleView) + } + } + + init { + model = project.solution.utBotRiderModel + initPublish() + initVSharp() + } + + private fun initVSharp() { + var previousVSharp: RunContentDescriptor? = null + model.startVSharp.advise(project.lifetime) { + val (currentVSharp, consoleView, consoleLifetime) = update( + "Started VSharp Engine", + "Running VSharp Engine", + previousVSharp + ) + previousVSharp = currentVSharp + model.logVSharp.advisePrintNormal(consoleLifetime, consoleView) + model.stopVSharp.advisePrintExitCode(consoleLifetime, consoleView) + } + } +} \ No newline at end of file diff --git a/utbot-rider/src/main/kotlin/org/utbot/rider/generated/UtBotRiderModel.Generated.kt b/utbot-rider/src/main/kotlin/org/utbot/rider/generated/UtBotRiderModel.Generated.kt new file mode 100644 index 0000000000..f1dac3d1f2 --- /dev/null +++ b/utbot-rider/src/main/kotlin/org/utbot/rider/generated/UtBotRiderModel.Generated.kt @@ -0,0 +1,191 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.rider.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* +import com.jetbrains.rd.ide.model.Solution + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [UtBotRiderModel.kt:8] + */ +class UtBotRiderModel private constructor( + private val _startPublish: RdSignal, + private val _logPublishOutput: RdSignal, + private val _logPublishError: RdSignal, + private val _stopPublish: RdSignal, + private val _startVSharp: RdSignal, + private val _logVSharp: RdSignal, + private val _stopVSharp: RdSignal +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + serializers.register(StartPublishArgs) + } + + + + + + const val serializationHash = 6014484928290881L + + } + override val serializersOwner: ISerializersOwner get() = UtBotRiderModel + override val serializationHash: Long get() = UtBotRiderModel.serializationHash + + //fields + val startPublish: IAsyncSignal get() = _startPublish + val logPublishOutput: IAsyncSignal get() = _logPublishOutput + val logPublishError: IAsyncSignal get() = _logPublishError + val stopPublish: IAsyncSignal get() = _stopPublish + val startVSharp: IAsyncSignal get() = _startVSharp + val logVSharp: IAsyncSignal get() = _logVSharp + val stopVSharp: IAsyncSignal get() = _stopVSharp + //methods + //initializer + init { + _startPublish.async = true + _logPublishOutput.async = true + _logPublishError.async = true + _stopPublish.async = true + _startVSharp.async = true + _logVSharp.async = true + _stopVSharp.async = true + } + + init { + bindableChildren.add("startPublish" to _startPublish) + bindableChildren.add("logPublishOutput" to _logPublishOutput) + bindableChildren.add("logPublishError" to _logPublishError) + bindableChildren.add("stopPublish" to _stopPublish) + bindableChildren.add("startVSharp" to _startVSharp) + bindableChildren.add("logVSharp" to _logVSharp) + bindableChildren.add("stopVSharp" to _stopVSharp) + } + + //secondary constructor + internal constructor( + ) : this( + RdSignal(StartPublishArgs), + RdSignal(FrameworkMarshallers.String), + RdSignal(FrameworkMarshallers.String), + RdSignal(FrameworkMarshallers.Int), + RdSignal(FrameworkMarshallers.Void), + RdSignal(FrameworkMarshallers.String), + RdSignal(FrameworkMarshallers.Int) + ) + + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("UtBotRiderModel (") + printer.indent { + print("startPublish = "); _startPublish.print(printer); println() + print("logPublishOutput = "); _logPublishOutput.print(printer); println() + print("logPublishError = "); _logPublishError.print(printer); println() + print("stopPublish = "); _stopPublish.print(printer); println() + print("startVSharp = "); _startVSharp.print(printer); println() + print("logVSharp = "); _logVSharp.print(printer); println() + print("stopVSharp = "); _stopVSharp.print(printer); println() + } + printer.print(")") + } + //deepClone + override fun deepClone(): UtBotRiderModel { + return UtBotRiderModel( + _startPublish.deepClonePolymorphic(), + _logPublishOutput.deepClonePolymorphic(), + _logPublishError.deepClonePolymorphic(), + _stopPublish.deepClonePolymorphic(), + _startVSharp.deepClonePolymorphic(), + _logVSharp.deepClonePolymorphic(), + _stopVSharp.deepClonePolymorphic() + ) + } + //contexts +} +val Solution.utBotRiderModel get() = getOrCreateExtension("utBotRiderModel", ::UtBotRiderModel) + + + +/** + * #### Generated from [UtBotRiderModel.kt:9] + */ +data class StartPublishArgs ( + val fileName: String, + val arguments: String, + val workingDirectory: String +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = StartPublishArgs::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): StartPublishArgs { + val fileName = buffer.readString() + val arguments = buffer.readString() + val workingDirectory = buffer.readString() + return StartPublishArgs(fileName, arguments, workingDirectory) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: StartPublishArgs) { + buffer.writeString(value.fileName) + buffer.writeString(value.arguments) + buffer.writeString(value.workingDirectory) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as StartPublishArgs + + if (fileName != other.fileName) return false + if (arguments != other.arguments) return false + if (workingDirectory != other.workingDirectory) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + fileName.hashCode() + __r = __r*31 + arguments.hashCode() + __r = __r*31 + workingDirectory.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("StartPublishArgs (") + printer.indent { + print("fileName = "); fileName.print(printer); println() + print("arguments = "); arguments.print(printer); println() + print("workingDirectory = "); workingDirectory.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} diff --git a/utbot-rider/src/main/resources/META-INF/plugin.xml b/utbot-rider/src/main/resources/META-INF/plugin.xml new file mode 100644 index 0000000000..1b60aceb7d --- /dev/null +++ b/utbot-rider/src/main/resources/META-INF/plugin.xml @@ -0,0 +1,43 @@ + + + + org.utbot.rider.plugin.id + UnitTestBot.NET + utbot.org + com.intellij.modules.rider + + + +
  • Generating ready-to-use test cases with method bodies and inputs
  • +
  • Maximizing branch coverage in regression suite while keeping the number of tests at minimum
  • +
  • Capable to find deeply hidden code defects and express them as tests
  • +
  • Applicable to LINQ syntax and complex generics
  • +
  • Supporting .NET Framework, .NET Core, .NET 5, and .NET 6
  • +
  • Powered by V# — the custom symbolic execution engine
  • + +
    + Try UnitTestBot .NET online demo to see how it generates tests for your code in real time. + ]]> + + + + + org.utbot.rider.UtBotConsoleViewComponent + + + + + +
  • Test generation timeout option to control test generation duration
  • +
  • Balloon notifications informing about the test generation process state
  • +
  • Console output for publish and test generation processes
  • +
  • .NET 7 projects support
  • +
  • Bugfixes
  • + + ]]> +
    + diff --git a/utbot-rider/src/main/resources/META-INF/pluginIcon.svg b/utbot-rider/src/main/resources/META-INF/pluginIcon.svg new file mode 100644 index 0000000000..d24574d6dd --- /dev/null +++ b/utbot-rider/src/main/resources/META-INF/pluginIcon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/utbot-sample/build.gradle b/utbot-sample/build.gradle index 7a5d55d38b..20c963ec5c 100644 --- a/utbot-sample/build.gradle +++ b/utbot-sample/build.gradle @@ -1,35 +1,15 @@ plugins { - id 'java' + id 'java-library' } dependencies { - compile group: 'org.jetbrains', name: 'annotations', version: '16.0.2' - compile group: 'com.github.stephenc.findbugs', name: 'findbugs-annotations', version: '1.3.9-1' - compileOnly 'org.projectlombok:lombok:1.18.20' + implementation group: 'org.jetbrains', name: 'annotations', version: '16.0.2' + implementation group: 'com.github.stephenc.findbugs', name: 'findbugs-annotations', version: '1.3.9-1' + implementation 'org.projectlombok:lombok:1.18.20' + testImplementation group: 'org.mockito', name:'mockito-core', version: mockitoVersion annotationProcessor 'org.projectlombok:lombok:1.18.20' implementation(project(":utbot-api")) implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2' implementation group: 'javax.validation', name: 'validation-api', version: '2.0.0.Final' - implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' - -// To use JUnit4, comment out JUnit5 and uncomment JUnit4 dependencies here. Please also check "test" section -// testImplementation group: 'junit', name: 'junit', version: '4.13.1' - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.7.0' - testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0' - -// testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.5.13' - - testImplementation group: 'org.mockito', name: 'mockito-inline', version: '4.0.0' - testCompile "org.mockito:mockito-inline:+" + implementation group: 'org.slf4j', name: 'slf4j-api', version: slf4jVersion } - -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -test { -// To use JUnit4, comment out useJUnitPlatform and uncomment useJUnit. Please also check "dependencies" section - //useJUnit() - useJUnitPlatform() -} \ No newline at end of file diff --git a/utbot-sample/src/main/java/org/utbot/examples/algorithms/ArraysQuickSort.java b/utbot-sample/src/main/java/org/utbot/examples/algorithms/ArraysQuickSort.java index 048253b99c..9a925957b7 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/algorithms/ArraysQuickSort.java +++ b/utbot-sample/src/main/java/org/utbot/examples/algorithms/ArraysQuickSort.java @@ -9,11 +9,11 @@ public static int[] runDefaultQuickSort(int value) { } // implementation from Arrays.sort. All static constants inlined, because we don't support them for now - static void sort(int[] a, int left, int right, + public static void sort(int[] a, int left, int right, int[] work, int workBase, int workLen) { // Use Quicksort on small arrays if (right - left < 286) { //QUICKSORT_THRESHOLD - sort(a, left, right, true); + internalSort(a, left, right, true); return; } @@ -54,7 +54,7 @@ static void sort(int[] a, int left, int right, * use Quicksort instead of merge sort. */ if (++count == 67) { // MAX_RUN_COUNT - sort(a, left, right, true); + internalSort(a, left, right, true); return; } } @@ -134,7 +134,7 @@ static void sort(int[] a, int left, int right, } } - private static void sort(int[] a, int left, int right, boolean leftmost) { + private static void internalSort(int[] a, int left, int right, boolean leftmost) { int length = right - left + 1; // Use insertion sort on tiny arrays @@ -350,8 +350,8 @@ private static void sort(int[] a, int left, int right, boolean leftmost) { a[great + 1] = pivot2; // Sort left and right parts recursively, excluding known pivots - sort(a, left, less - 2, leftmost); - sort(a, great + 2, right, false); + internalSort(a, left, less - 2, leftmost); + internalSort(a, great + 2, right, false); /* * If center part is too large (comprises > 4/7 of the array), @@ -423,7 +423,7 @@ private static void sort(int[] a, int left, int right, boolean leftmost) { } // Sort center part recursively - sort(a, less, great, false); + internalSort(a, less, great, false); } else { // Partitioning with one pivot /* @@ -490,8 +490,8 @@ private static void sort(int[] a, int left, int right, boolean leftmost) { * All elements from center part are equal * and, therefore, already sorted. */ - sort(a, left, less - 1, leftmost); - sort(a, great + 1, right, false); + internalSort(a, left, less - 1, leftmost); + internalSort(a, great + 1, right, false); } } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/arrays/ArrayStoreExceptionExamples.java b/utbot-sample/src/main/java/org/utbot/examples/arrays/ArrayStoreExceptionExamples.java new file mode 100644 index 0000000000..fe201e0cbb --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/arrays/ArrayStoreExceptionExamples.java @@ -0,0 +1,163 @@ +package org.utbot.examples.arrays; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeSet; + +public class ArrayStoreExceptionExamples { + + public boolean correctAssignmentSamePrimitiveType(int[] data) { + if (data == null || data.length == 0) return false; + data[0] = 1; + return true; + } + + public boolean correctAssignmentIntToIntegerArray(Integer[] data) { + if (data == null || data.length == 0) return false; + data[0] = 1; + return true; + } + + public boolean correctAssignmentSubtype(Number[] data) { + if (data == null || data.length == 0) return false; + data[0] = 15; + return true; + } + + public boolean correctAssignmentToObjectArray(Object[] data) { + if (data == null || data.length < 2) return false; + data[0] = 1; + data[1] = new ArrayList(); + return true; + } + + public void wrongAssignmentUnrelatedType(Integer[] data) { + if (data == null || data.length == 0) return; + ((Object[]) data)[0] = "x"; + } + + public void wrongAssignmentSupertype(Integer[] data) { + if (data == null || data.length == 0) return; + Number x = 1.2; + ((Number[]) data)[0] = x; + } + + public void checkGenericAssignmentWithCorrectCast() { + Number[] data = new Number[3]; + genericAssignmentWithCast(data, 5); + } + + public void checkGenericAssignmentWithWrongCast() { + Number[] data = new Number[3]; + genericAssignmentWithCast(data, "x"); + } + + public void checkGenericAssignmentWithExtendsSubtype() { + Number[] data = new Number[3]; + genericAssignmentWithExtends(data, 7); + } + + public void checkGenericAssignmentWithExtendsUnrelated() { + Number[] data = new Number[3]; + genericAssignmentWithExtends(data, "x"); + } + + public void checkObjectAssignment() { + Object[] data = new Object[3]; + data[0] = "x"; + } + + public void checkAssignmentToObjectArray() { + Object[] data = new Object[3]; + data[0] = 1; + data[1] = "a"; + data[2] = data; + } + + public void checkWrongAssignmentOfItself() { + Number[] data = new Number[2]; + genericAssignmentWithCast(data, data); + } + + public void checkGoodAssignmentOfItself() { + Object[] data = new Object[2]; + genericAssignmentWithCast(data, data); + } + + public int[] arrayCopyForIncompatiblePrimitiveTypes(long[] data) { + if (data == null) + return null; + + int[] result = new int[data.length]; + if (data.length != 0) { + System.arraycopy(data, 0, result, 0, data.length); + } + + return result; + } + + public int[][] fill2DPrimitiveArray() { + int[][] data = new int[2][3]; + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 3; j++) { + data[i][j] = i * 3 + j; + } + } + return data; + } + + public Object[] fillObjectArrayWithList(List list) { + if (list == null) + return null; + + Object[] result = new Object[1]; + result[0] = list; + return result; + } + + public Object[] fillWithTreeSet(TreeSet treeSet) { + if (treeSet == null) + return null; + + Object[] result = new Object[1]; + result[0] = treeSet; + return result; + } + + public SomeInterface[] fillSomeInterfaceArrayWithSomeInterface(SomeInterface impl) { + if (impl == null) + return null; + + SomeInterface[] result = new SomeInterface[1]; + result[0] = impl; + return result; + } + + public Object[] fillObjectArrayWithSomeInterface(SomeInterface impl) { + if (impl == null) + return null; + + Object[] result = new Object[1]; + result[0] = impl; + return result; + } + + public Object[] fillWithSomeImplementation(SomeImplementation impl) { + if (impl == null) + return null; + + Object[] result = new Object[1]; + result[0] = impl; + return result; + } + + private void genericAssignmentWithCast(T[] data, E element) { + if (data == null || data.length == 0) return; + data[0] = (T) element; + } + + private void genericAssignmentWithExtends(T[] data, E element) { + if (data == null || data.length == 0) return; + data[0] = element; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/arrays/CopyOfExample.java b/utbot-sample/src/main/java/org/utbot/examples/arrays/CopyOfExample.java new file mode 100644 index 0000000000..cf1316e294 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/arrays/CopyOfExample.java @@ -0,0 +1,45 @@ +package org.utbot.examples.arrays; + +import org.utbot.api.mock.UtMock; + +import java.util.Arrays; + +public class CopyOfExample { + @SuppressWarnings("IfStatementWithIdenticalBranches") + Integer[] copyOfExample(Integer[] values, int newLength) { + UtMock.assume(values != null); + + if (values.length == 0) { + if (newLength <= 0) { + return Arrays.copyOf(values, newLength); + } else { + return Arrays.copyOf(values, newLength); + } + } else { + if (newLength <= 0) { + return Arrays.copyOf(values, newLength); + } else { + return Arrays.copyOf(values, newLength); + } + } + } + + @SuppressWarnings("IfStatementWithIdenticalBranches") + Integer[] copyOfRangeExample(Integer[] values, int from, int to) { + UtMock.assume(values != null); + + if (from < 0) { + return Arrays.copyOfRange(values, from, to); + } + + if (from > to) { + return Arrays.copyOfRange(values, from, to); + } + + if (from > values.length) { + return Arrays.copyOfRange(values, from, to); + } + + return Arrays.copyOfRange(values, from, to); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/arrays/SomeImplementation.java b/utbot-sample/src/main/java/org/utbot/examples/arrays/SomeImplementation.java new file mode 100644 index 0000000000..d67592ff91 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/arrays/SomeImplementation.java @@ -0,0 +1,8 @@ +package org.utbot.examples.arrays; + +public class SomeImplementation implements SomeInterface { + @Override + public int foo() { + return 0; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/arrays/SomeInterface.java b/utbot-sample/src/main/java/org/utbot/examples/arrays/SomeInterface.java new file mode 100644 index 0000000000..35b7c5a38a --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/arrays/SomeInterface.java @@ -0,0 +1,5 @@ +package org.utbot.examples.arrays; + +public interface SomeInterface { + int foo(); +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/codegen/ClassWithStaticAndInnerClasses.java b/utbot-sample/src/main/java/org/utbot/examples/codegen/ClassWithStaticAndInnerClasses.java index 0d40009771..11c20f54cb 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/codegen/ClassWithStaticAndInnerClasses.java +++ b/utbot-sample/src/main/java/org/utbot/examples/codegen/ClassWithStaticAndInnerClasses.java @@ -3,6 +3,9 @@ public class ClassWithStaticAndInnerClasses { public int z = 0; + // public field that exposes private type PrivateInnerClassWithPublicField + public PrivateInnerClassWithPublicField publicFieldWithPrivateType = new PrivateInnerClassWithPublicField(0); + private static class PrivateStaticClassWithPublicField { public int x; @@ -52,6 +55,18 @@ public PublicStaticClassWithPublicField createFromConst(int x) { } public static class PublicStaticClassWithPrivateField { + + public static class DeepNestedStatic { + public int g(int x) { + return x + 1; + } + } + + public class DeepNested { + public int h(int x) { + return x + 2; + } + } private int x; public PublicStaticClassWithPrivateField(int x) { @@ -227,4 +242,8 @@ PackagePrivateFinalInnerClassWithPackagePrivateField usePackagePrivateFinalInner return innerClass.createFromIncrement(x); } + + int getValueFromPublicFieldWithPrivateType() { + return publicFieldWithPrivateType.x; + } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/codegen/FileWithTopLevelFunctionsReflectHelper.java b/utbot-sample/src/main/java/org/utbot/examples/codegen/FileWithTopLevelFunctionsReflectHelper.java new file mode 100644 index 0000000000..d5803f72bc --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/codegen/FileWithTopLevelFunctionsReflectHelper.java @@ -0,0 +1,6 @@ +package org.utbot.examples.codegen; + +// We can't access FileWithTopLevelFunctionsKt::class from Kotlin, so we use this class to get reflection from Java +public class FileWithTopLevelFunctionsReflectHelper { + static Class clazz = FileWithTopLevelFunctionsKt.class; +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/codegen/JavaAssert.java b/utbot-sample/src/main/java/org/utbot/examples/codegen/JavaAssert.java new file mode 100644 index 0000000000..fb5dc8b622 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/codegen/JavaAssert.java @@ -0,0 +1,8 @@ +package org.utbot.examples.codegen; + +public class JavaAssert { + public int assertPositive(int value) { + assert value > 0; + return value; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/codegen/deepequals/ClassWithCrossReferenceRelationship.java b/utbot-sample/src/main/java/org/utbot/examples/codegen/deepequals/ClassWithCrossReferenceRelationship.java new file mode 100644 index 0000000000..2f2b1c5590 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/codegen/deepequals/ClassWithCrossReferenceRelationship.java @@ -0,0 +1,30 @@ +package org.utbot.examples.codegen.deepequals; + +class FirstClass { + SecondClass secondClass; + + FirstClass(SecondClass second) { + this.secondClass = second; + } +} + +class SecondClass { + FirstClass firstClass; + + SecondClass(FirstClass first) { + this.firstClass = first; + } +} + +public class ClassWithCrossReferenceRelationship { + public FirstClass returnFirstClass(int value) { + if (value == 0) { + return new FirstClass(new SecondClass(null)); + } else { + FirstClass first = new FirstClass(null); + first.secondClass = new SecondClass(first); + + return first; + } + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/codegen/deepequals/ClassWithNullableField.java b/utbot-sample/src/main/java/org/utbot/examples/codegen/deepequals/ClassWithNullableField.java new file mode 100644 index 0000000000..cfb2ca5c65 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/codegen/deepequals/ClassWithNullableField.java @@ -0,0 +1,34 @@ +package org.utbot.examples.codegen.deepequals; + +class Component { + int a = 1; +} + +class Compound { + Component component; + + Compound(Component component) { + this.component = component; + } +} + +class GreatCompound { + Compound compound; + + GreatCompound(Compound compound) { + this.compound = compound; + } +} + +public class ClassWithNullableField { + public Compound returnCompoundWithNullableField(int value) { + if (value > 0) return new Compound(null); + else return new Compound(new Component()); + } + + public GreatCompound returnGreatCompoundWithNullableField(int value) { + if (value > 0) return new GreatCompound(null); + else if (value == 0) return new GreatCompound(new Compound(new Component())); + else return new GreatCompound(new Compound(null)); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/codegen/modifiers/ClassWithPrivateMutableFieldOfPrivateType.java b/utbot-sample/src/main/java/org/utbot/examples/codegen/modifiers/ClassWithPrivateMutableFieldOfPrivateType.java new file mode 100644 index 0000000000..938f6f863d --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/codegen/modifiers/ClassWithPrivateMutableFieldOfPrivateType.java @@ -0,0 +1,16 @@ +package org.utbot.examples.codegen.modifiers; + +public class ClassWithPrivateMutableFieldOfPrivateType { + @SuppressWarnings({"FieldCanBeLocal", "unused"}) + private PrivateClass privateMutableField = null; + + public int changePrivateMutableFieldWithPrivateType() { + privateMutableField = new PrivateClass(); + + return privateMutableField.x; + } + + private static class PrivateClass { + int x = 0; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/collections/ListIterators.java b/utbot-sample/src/main/java/org/utbot/examples/collections/ListIterators.java index ce55011cea..8bdd6219ed 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/collections/ListIterators.java +++ b/utbot-sample/src/main/java/org/utbot/examples/collections/ListIterators.java @@ -1,11 +1,34 @@ package org.utbot.examples.collections; +import org.utbot.api.mock.UtMock; + import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ListIterator; public class ListIterators { + @SuppressWarnings({"IfStatementWithIdenticalBranches", "RedundantOperationOnEmptyContainer"}) + Iterator returnIterator(List list) { + UtMock.assume(list != null); + + if (list.isEmpty()) { + return list.iterator(); + } else { + return list.iterator(); + } + } + + @SuppressWarnings("IfStatementWithIdenticalBranches") + ListIterator returnListIterator(List list) { + UtMock.assume(list != null); + + if (list.isEmpty()) { + return list.listIterator(); + } else { + return list.listIterator(); + } + } List iterate(List list) { Iterator iterator = list.iterator(); diff --git a/utbot-sample/src/main/java/org/utbot/examples/collections/Lists.java b/utbot-sample/src/main/java/org/utbot/examples/collections/Lists.java index 3c66dd0c69..99d5c1c3b3 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/collections/Lists.java +++ b/utbot-sample/src/main/java/org/utbot/examples/collections/Lists.java @@ -3,6 +3,7 @@ import org.utbot.api.mock.UtMock; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; @@ -159,4 +160,15 @@ List addAllByIndex(List list, int i) { } return list; } + + @SuppressWarnings("IfStatementWithIdenticalBranches") + List asListExample(String[] values) { + UtMock.assume(values != null); + + if (values.length == 0) { + return Arrays.asList(values); + } else { + return Arrays.asList(values); + } + } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/collections/Maps.java b/utbot-sample/src/main/java/org/utbot/examples/collections/Maps.java index 118001e487..0be06782f5 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/collections/Maps.java +++ b/utbot-sample/src/main/java/org/utbot/examples/collections/Maps.java @@ -1,7 +1,11 @@ package org.utbot.examples.collections; +import org.utbot.api.mock.UtMock; + +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; public class Maps { @@ -23,6 +27,36 @@ String mapToString(long startTime, int pageSize, int pageNum) { return params.toString(); } + @SuppressWarnings("OverwrittenKey") + Integer mapPutAndGet() { + Map values = new HashMap<>(); + + values.put(1L, 2); + values.put(1L, 3); + + return values.get(1L); + } + + @SuppressWarnings("OverwrittenKey") + Integer putInMapFromParameters(Map values) { + values.put(1L, 2); + values.put(1L, 3); + + return values.get(1L); + } + + @SuppressWarnings("OverwrittenKey") + Integer containsKeyAndPuts(Map values) { + UtMock.assume(!values.containsKey(1L)); + + values.put(1L, 2); + values.put(1L, 3); + + UtMock.assume(values.get(1L).equals(3)); + + return values.get(1L); + } + Map countChars(String s) { Map map = new LinkedHashMap<>(); for (int i = 0; i < s.length(); i++) { @@ -245,4 +279,44 @@ CustomClass removeCustomObject(Map map, int i) { return removed; } } + + public List mapOperator(Map map) { + List result = new ArrayList<>(); + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().equals("key")) { + result.add(entry.getKey()); + } + } + if (result.size() > 1) { + return result; + } else { + return new ArrayList<>(map.values()); + } + } + + public Map createMapWithString() { + Map map = new HashMap<>(); + map.put("tuesday", 354); + map.remove("tuesday"); + + return map; + } + + public Map createMapWithEnum() { + Map map = new HashMap<>(); + map.put(WorkDays.Monday, 112); + map.put(WorkDays.Tuesday, 354); + map.put(WorkDays.Friday, 567); + map.remove(WorkDays.Tuesday); + + return map; + } + + public enum WorkDays { + Monday, + Tuesday, + Wednesday, + Thursday, + Friday + } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/collections/QueueUsages.java b/utbot-sample/src/main/java/org/utbot/examples/collections/QueueUsages.java new file mode 100644 index 0000000000..5a2586e844 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/collections/QueueUsages.java @@ -0,0 +1,92 @@ +package org.utbot.examples.collections; + +import org.utbot.examples.objects.WrappedInt; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.LinkedBlockingDeque; + +public class QueueUsages { + public int createArrayDeque(WrappedInt init, WrappedInt next) { + Queue q = new ArrayDeque<>(Collections.singletonList(init)); + q.add(next); + return q.size(); + } + + public int createLinkedList(WrappedInt init, WrappedInt next) { + Queue q = new LinkedList<>(Collections.singletonList(init)); + q.add(next); + return q.size(); + } + + public int createLinkedBlockingDeque(WrappedInt init, WrappedInt next) { + Queue q = new LinkedBlockingDeque<>(Collections.singletonList(init)); + q.add(next); + return q.size(); + } + + public int containsQueue(Queue q, int x) { + if (q.contains(x)) { + return 1; + } else { + return 0; + } + } + + public Queue addQueue(Queue q, WrappedInt x) { + q.add(x); + return q; + } + + public Queue addAllQueue(Queue q, WrappedInt x) { + Collection lst = Arrays.asList(new WrappedInt(1), x); + q.addAll(lst); + return q; + } + + public Deque castQueueToDeque(Queue q) { + if (q instanceof Deque) { + return (Deque)q; + } else { + return null; + } + } + + public int checkSubtypesOfQueue(Queue q) { + if (q == null) { + return 0; + } + if (q instanceof LinkedList) { + return 1; + } else if (q instanceof ArrayDeque) { + return 2; + } else { + return 3; + } + } + + public int checkSubtypesOfQueueWithUsage(Queue q) { + if (q == null) { + return 0; + } + q.add(1); + if (q instanceof LinkedList) { + return 1; + } else if (q instanceof ArrayDeque) { + return 2; + } else { + return 3; + } + } + + public ConcurrentLinkedQueue addConcurrentLinkedQueue(ConcurrentLinkedQueue q, WrappedInt o) { + q.add(o); + return q; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/collections/SetIterators.java b/utbot-sample/src/main/java/org/utbot/examples/collections/SetIterators.java index 91ed859aae..0f332e0a85 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/collections/SetIterators.java +++ b/utbot-sample/src/main/java/org/utbot/examples/collections/SetIterators.java @@ -1,9 +1,22 @@ package org.utbot.examples.collections; +import org.utbot.api.mock.UtMock; + import java.util.Iterator; import java.util.Set; public class SetIterators { + @SuppressWarnings({"IfStatementWithIdenticalBranches", "RedundantOperationOnEmptyContainer"}) + Iterator returnIterator(Set set) { + UtMock.assume(set != null); + + if (set.isEmpty()) { + return set.iterator(); + } else { + return set.iterator(); + } + } + int iteratorHasNext(Set s) { Iterator iterator = s.iterator(); if (!iterator.hasNext()) { diff --git a/utbot-sample/src/main/java/org/utbot/examples/controlflow/Conditions.java b/utbot-sample/src/main/java/org/utbot/examples/controlflow/Conditions.java index c5ba5c8aab..d13d9d6282 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/controlflow/Conditions.java +++ b/utbot-sample/src/main/java/org/utbot/examples/controlflow/Conditions.java @@ -1,6 +1,21 @@ package org.utbot.examples.controlflow; public class Conditions { + + /** + * This Doc is here in order to check whether the summaries and display names are rendered correctly. + * Had not in hours of peace, + * It learned to lightly look on life. + * + * @param a some long value + * @param b some int value + * @return the result you won't expect. + */ + public int returnCastFromTernaryOperator(long a, int b) { + a = a % b; + return (int) (a < 0 ? a + b : a); + } + public int simpleCondition(boolean condition) { if (condition) { return 1; @@ -16,4 +31,10 @@ public void emptyBranches(boolean condition) { // do nothing } } + + public int elseIf(int id) throws RuntimeException { + if (id > 0) return 0; + else if (id == 0) throw new RuntimeException("Exception message"); + else return 1; + } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/controlflow/Switch.java b/utbot-sample/src/main/java/org/utbot/examples/controlflow/Switch.java index d4948828a4..63f9f0b8f5 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/controlflow/Switch.java +++ b/utbot-sample/src/main/java/org/utbot/examples/controlflow/Switch.java @@ -46,6 +46,32 @@ public int enumSwitch(RoundingMode m) { return -1; } + public int charToIntSwitch(char c) { + switch (c) { + case 'I': return 1; + case 'V': return 5; + case 'X': return 10; + case 'L': return 50; + case 'C': return 100; + case 'D': return 500; + case 'M': return 1000; + default: throw new IllegalArgumentException("Unrecognized symbol: " + c); + } + } + + public int throwExceptionInSwitchArgument() { + switch (getChar()) { + case 'I': + return 1; + default: + return 100; + } + } + + private char getChar() throws RuntimeException { + throw new RuntimeException("Exception message"); + } + //TODO: String switch // public int stringSwitch(String s) { // switch (s) { @@ -59,4 +85,3 @@ public int enumSwitch(RoundingMode m) { // } // } } - diff --git a/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnum.java b/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnum.java index 14a0ded3fc..61cc9c4a83 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnum.java +++ b/utbot-sample/src/main/java/org/utbot/examples/enums/ClassWithEnum.java @@ -98,9 +98,28 @@ public boolean changingStaticWithEnumInit() { return true; } + public int virtualFunction(StatusEnum parameter) { + int value = parameter.virtualFunction(); + if (value > 0) { + return value; + } + + return Math.abs(value); + } + enum StatusEnum { - READY(0, 10, "200"), - ERROR(-1, -10, null); + READY(0, 10, "200") { + @Override + public int virtualFunction() { + return 0; + } + }, + ERROR(-1, -10, null) { + @Override + int virtualFunction() { + return 1; + } + }; int mutableInt; final int code; @@ -137,6 +156,8 @@ static StatusEnum fromIsReady(boolean isReady) { int publicGetCode() { return this == READY ? 10 : -10; } + + abstract int virtualFunction(); } enum ManyConstantsEnum { @@ -228,7 +249,7 @@ boolean affectSystemStaticAndInitEnumFromItAndGetItFromEnumFun() { enum OuterStaticUsageEnum { A; - int y; + final int y; OuterStaticUsageEnum() { y = staticInt; @@ -237,5 +258,11 @@ enum OuterStaticUsageEnum { int getOuterStatic() { return staticInt; } + + + @Override + public String toString() { + return String.format("%s(y = %d)", name(), y); + } } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/enums/ComplexEnumExamples.java b/utbot-sample/src/main/java/org/utbot/examples/enums/ComplexEnumExamples.java new file mode 100644 index 0000000000..42ecf5d810 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/enums/ComplexEnumExamples.java @@ -0,0 +1,113 @@ +package org.utbot.examples.enums; + +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class ComplexEnumExamples { + + public enum Color { + RED, + GREEN, + BLUE + } + + public int countEqualColors(@NotNull Color a, @NotNull Color b, @NotNull Color c) { + int equalToA = 1; + if (b == a) { + equalToA++; + } + + if (c == a) { + equalToA++; + } + + int equalToB = 1; + if (a == b) { + equalToB++; + } + + if (c == b) { + equalToB++; + } + + if (equalToA > equalToB) { + return equalToA; + } else { + return equalToB; + } + } + + public int countNullColors(Color a, Color b) { + int nullCount = 0; + if (a == null) { + nullCount++; + } + + if (b == null) { + nullCount++; + } + + return nullCount; + } + + public int enumToEnumMapCountValues(@NotNull Map map) { + int count = 0; + for (Color color: map.values()) { + if (color == Color.RED) { + count++; + } + } + return count; + } + + public int enumToEnumMapCountKeys(@NotNull Map map) { + int count = 0; + for (Color key: map.keySet()) { + if (key == Color.GREEN || Color.BLUE.equals(key)) { + count++; + } else { + // Do nothing + } + } + return count; + } + + public int enumToEnumMapCountMatches(@NotNull Map map) { + int count = 0; + for (Map.Entry entry: map.entrySet()) { + if (entry.getKey() == entry.getValue() && entry.getKey() != null) { + count++; + } + } + return count; + } + + public State findState(int code) { + return State.findStateByCode(code); + } + + public Map countValuesInArray(Color @NotNull [] colors) { + HashMap counters = new HashMap<>(); + for (Color c : colors) { + if (c != null) { + Integer value = counters.getOrDefault(c, 0); + counters.put(c, value + 1); + } + } + return counters; + } + + public int countRedInArray(@NotNull Color @NotNull [] colors) { + int count = 0; + for (Color c : colors) { + if (c == Color.RED) { + count++; + } + } + return count; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/enums/State.java b/utbot-sample/src/main/java/org/utbot/examples/enums/State.java new file mode 100644 index 0000000000..c8d0af1e3f --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/enums/State.java @@ -0,0 +1,41 @@ +package org.utbot.examples.enums; + +public enum State { + OPEN(255) { + @Override + public String toString() { + return ""; + } + }, + CLOSED(127) { + @Override + public String toString() { + return ""; + } + }, + UNKNOWN(0) { + @Override + public String toString() { + return ""; + } + }; + + private final int code; + + State(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public static State findStateByCode(int code) { + for (State state: values()) { + if (state.getCode() == code) { + return state; + } + } + return UNKNOWN; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/exceptions/ExceptionExamples.java b/utbot-sample/src/main/java/org/utbot/examples/exceptions/ExceptionExamples.java index cf259389fc..efc9e9eca1 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/exceptions/ExceptionExamples.java +++ b/utbot-sample/src/main/java/org/utbot/examples/exceptions/ExceptionExamples.java @@ -86,10 +86,29 @@ public int catchDeepNestedThrow(int i) { } } + public int catchExceptionAfterOtherPossibleException(int i) { + int x = 15; + x /= i + 1; + + try { + x /= i; + } catch (RuntimeException e) { + return 2; + } + return 1; + } + public IllegalArgumentException createException() { return new IllegalArgumentException("Here we are: " + Math.sqrt(10)); } + public int hangForSeconds(int seconds) throws InterruptedException { + for (int i = 0; i < seconds; i++) { + Thread.sleep(1000); + } + return seconds; + } + public int dontCatchDeepNestedThrow(int i) { return callNestedWithThrow(i); } @@ -104,4 +123,8 @@ private int nestedWithThrow(int i) { } return i; } -} \ No newline at end of file + + public int throwExceptionInMethodUnderTest() throws RuntimeException { + throw new RuntimeException("Exception message"); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/exceptions/JvmCrashExamples.java b/utbot-sample/src/main/java/org/utbot/examples/exceptions/JvmCrashExamples.java index a753e86751..7ecba1ea68 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/exceptions/JvmCrashExamples.java +++ b/utbot-sample/src/main/java/org/utbot/examples/exceptions/JvmCrashExamples.java @@ -1,6 +1,9 @@ package org.utbot.examples.exceptions; import java.lang.reflect.Field; +import java.security.AccessController; +import java.security.PrivilegedAction; + import sun.misc.Unsafe; public class JvmCrashExamples { @@ -13,7 +16,6 @@ public int exit(int i) { return i; } - // this method crashes JVM public int crash(int i) throws Exception { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); @@ -26,4 +28,25 @@ public int crash(int i) throws Exception { return 1; } + + // this method crashes JVM and uses [AccessController.doPrivileged] to obtain privileges + public int crashPrivileged(int i) { + return AccessController.doPrivileged((PrivilegedAction) () -> { + Field f; + try { + f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + Unsafe unsafe = (Unsafe) f.get(null); + unsafe.putAddress(0, 0); + + if (i == 0) { + return i; + } + + return 1; + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/CustomPredicateExample.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/CustomPredicateExample.java new file mode 100644 index 0000000000..bb1d417cd6 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/CustomPredicateExample.java @@ -0,0 +1,65 @@ +package org.utbot.examples.lambda; + +public class CustomPredicateExample { + static int someStaticField = 5; + int someNonStaticField = 10; + + public boolean noCapturedValuesPredicateCheck(PredicateNoCapturedValues predicate, int x) { + //noinspection RedundantIfStatement + if (predicate.test(x)) { + return true; + } else { + return false; + } + } + + public boolean capturedLocalVariablePredicateCheck(PredicateCapturedLocalVariable predicate, int x) { + //noinspection RedundantIfStatement + if (predicate.test(x)) { + return true; + } else { + return false; + } + } + + public boolean capturedParameterPredicateCheck(PredicateCapturedParameter predicate, int x) { + //noinspection RedundantIfStatement + if (predicate.test(x)) { + return true; + } else { + return false; + } + } + + public boolean capturedStaticFieldPredicateCheck(PredicateCapturedStaticField predicate, int x) { + //noinspection RedundantIfStatement + if (predicate.test(x)) { + return true; + } else { + return false; + } + } + + public boolean capturedNonStaticFieldPredicateCheck(PredicateCapturedNonStaticField predicate, int x) { + //noinspection RedundantIfStatement + if (predicate.test(x)) { + return true; + } else { + return false; + } + } + + // this method contains implementation of functional interface 'CustomPredicate' + void someLambdas(int someParameter) { + PredicateNoCapturedValues predicate1 = (x) -> x == 5; + + int localVariable = 10; + PredicateCapturedLocalVariable predicate2 = (x) -> x + localVariable == 5; + + PredicateCapturedParameter predicate3 = (x) -> x + someParameter == 5; + + PredicateCapturedStaticField predicate4 = (x) -> x + someStaticField == 5; + + PredicateCapturedNonStaticField predicate5 = (x) -> x + someNonStaticField == 5; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedLocalVariable.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedLocalVariable.java new file mode 100644 index 0000000000..04277c9647 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedLocalVariable.java @@ -0,0 +1,18 @@ +package org.utbot.examples.lambda; + +/** + * This functional interface is implemented via one lambda in {@link CustomPredicateExample#someLambdas}. + * + * DO NOT implement it anymore, because test on {@link CustomPredicateExample#capturedLocalVariablePredicateCheck} + * relies on the fact that there is only one implementation and that implementation is lambda. + * In addition, in this case we want the implementing lambda to capture some local variable. + * + * It is important because we want to test how we generate tests when the only available implementation is lambda, + * and we want to check different cases: with or without captured values. Note that lambdas may capture + * local variables, method parameters, static and non-static fields. That is why we have multiple functional interfaces + * in this package: one for each case. + */ +@FunctionalInterface +public interface PredicateCapturedLocalVariable { + boolean test(T value); +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedNonStaticField.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedNonStaticField.java new file mode 100644 index 0000000000..16a352f7bc --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedNonStaticField.java @@ -0,0 +1,18 @@ +package org.utbot.examples.lambda; + +/** + * This functional interface is implemented via one lambda in {@link CustomPredicateExample#someLambdas}. + * + * DO NOT implement it anymore, because test on {@link CustomPredicateExample#capturedNonStaticFieldPredicateCheck} + * relies on the fact that there is only one implementation and that implementation is lambda. + * In addition, in this case we want the implementing lambda to capture some non-static field of a class. + * + * It is important because we want to test how we generate tests when the only available implementation is lambda, + * and we want to check different cases: with or without captured values. Note that lambdas may capture + * local variables, method parameters, static and non-static fields. That is why we have multiple functional interfaces + * in this package: one for each case. + */ +@FunctionalInterface +public interface PredicateCapturedNonStaticField { + boolean test(T value); +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedParameter.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedParameter.java new file mode 100644 index 0000000000..c0f38c7d62 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedParameter.java @@ -0,0 +1,18 @@ +package org.utbot.examples.lambda; + +/** + * This functional interface is implemented via one lambda in {@link CustomPredicateExample#someLambdas}. + * + * DO NOT implement it anymore, because test on {@link CustomPredicateExample#capturedParameterPredicateCheck} + * relies on the fact that there is only one implementation and that implementation is lambda. + * In addition, in this case we want the implementing lambda to capture some method parameter. + * + * It is important because we want to test how we generate tests when the only available implementation is lambda, + * and we want to check different cases: with or without captured values. Note that lambdas may capture + * local variables, method parameters, static and non-static fields. That is why we have multiple functional interfaces + * in this package: one for each case. + */ +@FunctionalInterface +public interface PredicateCapturedParameter { + boolean test(T value); +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedStaticField.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedStaticField.java new file mode 100644 index 0000000000..9dfaaaffe8 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateCapturedStaticField.java @@ -0,0 +1,18 @@ +package org.utbot.examples.lambda; + +/** + * This functional interface is implemented via one lambda in {@link CustomPredicateExample#someLambdas}. + * + * DO NOT implement it anymore, because test on {@link CustomPredicateExample#capturedStaticFieldPredicateCheck} + * relies on the fact that there is only one implementation and that implementation is lambda. + * In addition, in this case we want the implementing lambda to capture some static field of some class. + * + * It is important because we want to test how we generate tests when the only available implementation is lambda, + * and we want to check different cases: with or without captured values. Note that lambdas may capture + * local variables, method parameters, static and non-static fields. That is why we have multiple functional interfaces + * in this package: one for each case. + */ +@FunctionalInterface +public interface PredicateCapturedStaticField { + boolean test(T value); +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateNoCapturedValues.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateNoCapturedValues.java new file mode 100644 index 0000000000..57d3974e29 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateNoCapturedValues.java @@ -0,0 +1,18 @@ +package org.utbot.examples.lambda; + +/** + * This functional interface is implemented via one lambda in {@link CustomPredicateExample#someLambdas}. + * + * DO NOT implement it anymore, because test on {@link CustomPredicateExample#noCapturedValuesPredicateCheck} + * relies on the fact that there is only one implementation and that implementation is lambda. + * In addition, in this case we want the implementing lambda to not capture any values. + * + * It is important because we want to test how we generate tests when the only available implementation is lambda, + * and we want to check different cases: with or without captured values. Note that lambdas may capture + * local variables, method parameters, static and non-static fields. That is why we have multiple functional interfaces + * in this package: one for each case. + */ +@FunctionalInterface +public interface PredicateNoCapturedValues { + boolean test(T value); +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateNotExample.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateNotExample.java new file mode 100644 index 0000000000..3339750760 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/PredicateNotExample.java @@ -0,0 +1,13 @@ +package org.utbot.examples.lambda; + +import java.util.function.*; + +public class PredicateNotExample { + public boolean predicateNotExample(int a) { + if (Predicate.not(i -> i.equals(5)).test(a)) { + return true; + } else { + return false; + } + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/lambda/ThrowingWithLambdaExample.java b/utbot-sample/src/main/java/org/utbot/examples/lambda/ThrowingWithLambdaExample.java new file mode 100644 index 0000000000..acbc77ceb3 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/lambda/ThrowingWithLambdaExample.java @@ -0,0 +1,27 @@ +package org.utbot.examples.lambda; + +import org.utbot.api.mock.UtMock; + +public class ThrowingWithLambdaExample { + // This example mostly checks that we can construct non-static lambda even if it's init section was not analyzed + // (e.g., an exception was thrown before it). + boolean anyExample(int[] values, IntPredicate predicate) { + UtMock.assume(predicate != null); + + for (int value : values) { + if (predicate.test(value)) { + return true; + } + } + + return false; + } + + // To make this lambda non-static, we need to make it use `this` instance. + @SuppressWarnings({"unused", "ConstantConditions"}) + IntPredicate nonStaticIntPredicate = x -> this != null && x == 42; + + interface IntPredicate { + boolean test(int value); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/math/OverflowExamples.java b/utbot-sample/src/main/java/org/utbot/examples/math/OverflowExamples.java index b1cc7fd4f7..271139ab2e 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/math/OverflowExamples.java +++ b/utbot-sample/src/main/java/org/utbot/examples/math/OverflowExamples.java @@ -4,6 +4,9 @@ public class OverflowExamples { public byte byteAddOverflow(byte x, byte y) { return (byte) (x + y); } + public byte byteWithIntOverflow(byte x, int y) { + return (byte) (x + y); + } public byte byteMulOverflow(byte x, byte y) { return (byte) (x * y); } diff --git a/utbot-sample/src/main/java/org/utbot/examples/mixed/SerializableExample.java b/utbot-sample/src/main/java/org/utbot/examples/mixed/SerializableExample.java new file mode 100644 index 0000000000..b5456054be --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/mixed/SerializableExample.java @@ -0,0 +1,13 @@ +package org.utbot.examples.mixed; + +import java.io.File; + +public class SerializableExample { + public void example() { + join("string", File.separator, System.currentTimeMillis()); + } + + public static String join(T... elements) { + return null; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/mock/CommonMocksExample.java b/utbot-sample/src/main/java/org/utbot/examples/mock/CommonMocksExample.java index 873ea187d2..d65209b356 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/mock/CommonMocksExample.java +++ b/utbot-sample/src/main/java/org/utbot/examples/mock/CommonMocksExample.java @@ -1,5 +1,7 @@ package org.utbot.examples.mock; +import org.utbot.api.mock.UtMock; +import org.utbot.examples.mock.others.Random; import org.utbot.examples.mock.service.impl.ExampleClass; import org.utbot.examples.objects.ObjectWithFinalStatic; import org.utbot.examples.objects.RecursiveTypeClass; @@ -35,4 +37,11 @@ public int clinitMockExample() { return -ObjectWithFinalStatic.keyValue; } } + + public int mocksForNullOfDifferentTypes(Integer intValue, Random random) { + UtMock.assume(intValue == null); + UtMock.assume(random == null); + + return 0; + } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/mock/MockStaticFieldExample.java b/utbot-sample/src/main/java/org/utbot/examples/mock/MockStaticFieldExample.java index 916f6a2759..4de83e353f 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/mock/MockStaticFieldExample.java +++ b/utbot-sample/src/main/java/org/utbot/examples/mock/MockStaticFieldExample.java @@ -1,5 +1,6 @@ package org.utbot.examples.mock; +import org.utbot.examples.mock.others.ClassWithStaticField; import org.utbot.examples.mock.others.Generator; public class MockStaticFieldExample { @@ -7,6 +8,9 @@ public class MockStaticFieldExample { private static Generator privateGenerator; public static Generator publicGenerator; + public static final ClassWithStaticField staticFinalField = new ClassWithStaticField(); + public static ClassWithStaticField staticField = new ClassWithStaticField(); + public int calculate(int threshold) { int a = privateGenerator.generateInt(); int b = publicGenerator.generateInt(); @@ -15,4 +19,20 @@ public int calculate(int threshold) { } return a + b + 1; } + + // This test is associated with https://github.com/UnitTestBot/UTBotJava/issues/1533 + public int checkMocksInLeftAndRightAssignPartFinalField() { + staticFinalField.intField = 5; + staticFinalField.anotherIntField = staticFinalField.foo(); + + return staticFinalField.anotherIntField; + } + + // This test is associated with https://github.com/UnitTestBot/UTBotJava/issues/1533 + public int checkMocksInLeftAndRightAssignPart() { + staticField.intField = 5; + staticField.anotherIntField = staticField.foo(); + + return staticField.anotherIntField; + } } \ No newline at end of file diff --git a/utbot-sample/src/main/java/org/utbot/examples/mock/MockStaticMethodExample.java b/utbot-sample/src/main/java/org/utbot/examples/mock/MockStaticMethodExample.java index 9cb42fef6c..63e5902e87 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/mock/MockStaticMethodExample.java +++ b/utbot-sample/src/main/java/org/utbot/examples/mock/MockStaticMethodExample.java @@ -11,4 +11,8 @@ public int useStaticMethod() { return 0; } + + public void mockStaticMethodFromAlwaysMockClass() { + System.out.println("example"); + } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/mock/fields/ClassUsingClassWithRandomField.java b/utbot-sample/src/main/java/org/utbot/examples/mock/fields/ClassUsingClassWithRandomField.java new file mode 100644 index 0000000000..7e611f3b50 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/mock/fields/ClassUsingClassWithRandomField.java @@ -0,0 +1,9 @@ +package org.utbot.examples.mock.fields; + +public class ClassUsingClassWithRandomField { + public int useClassWithRandomField() { + ClassWithRandomField classWithRandomField = new ClassWithRandomField(); + + return classWithRandomField.nextInt(); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/mock/fields/ClassWithRandomField.java b/utbot-sample/src/main/java/org/utbot/examples/mock/fields/ClassWithRandomField.java new file mode 100644 index 0000000000..cfb9ae137d --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/mock/fields/ClassWithRandomField.java @@ -0,0 +1,11 @@ +package org.utbot.examples.mock.fields; + +import java.util.Random; + +public class ClassWithRandomField { + public Random random = new Random(); + + public int nextInt() { + return random.nextInt(); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/mock/others/ClassWithStaticField.java b/utbot-sample/src/main/java/org/utbot/examples/mock/others/ClassWithStaticField.java new file mode 100644 index 0000000000..fb6d8bbb06 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/mock/others/ClassWithStaticField.java @@ -0,0 +1,10 @@ +package org.utbot.examples.mock.others; + +public class ClassWithStaticField { + public int intField; + public int anotherIntField; + + public int foo() { + return 5; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/nested/DeepNested.java b/utbot-sample/src/main/java/org/utbot/examples/nested/DeepNested.java new file mode 100644 index 0000000000..0c9ba5e61d --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/nested/DeepNested.java @@ -0,0 +1,14 @@ +package org.utbot.examples.nested; + +public class DeepNested { + public class Nested1 { + public class Nested2 { + public int f(int i) { + if (i > 0) { + return 10; + } + return 0; + } + } + } +} \ No newline at end of file diff --git a/utbot-sample/src/main/java/org/utbot/examples/objects/AbstractAnonymousClass.java b/utbot-sample/src/main/java/org/utbot/examples/objects/AbstractAnonymousClass.java index 3a5777f724..d09da1136b 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/objects/AbstractAnonymousClass.java +++ b/utbot-sample/src/main/java/org/utbot/examples/objects/AbstractAnonymousClass.java @@ -4,6 +4,22 @@ abstract class AbstractAnonymousClass { abstract int constValue(); abstract int add(int x); + public int methodWithoutOverrides(int x, int y) { + return y + addFortyTwo(x); + } + + public int methodWithOverride(int x, int y) { + return y + addNumber(x); + } + + public int addFortyTwo(int x) { + return x + 42; + } + + public int addNumber(int x) { + return x + 27; + } + public static AbstractAnonymousClass getInstance(int x) { if (x % 2 == 0) { return new AnonymousClassAlternative(); @@ -19,6 +35,11 @@ int constValue() { int add(int x) { return x + 15; } + + @Override + public int methodWithOverride(int x, int y) { + return x + 37; + } }; } } @@ -33,4 +54,9 @@ int constValue() { int add(int x) { return x + 1; } + + @Override + public int methodWithOverride(int x, int y) { + return x + 17; + } } diff --git a/utbot-sample/src/main/java/org/utbot/examples/objects/AnonymousClassesExample.java b/utbot-sample/src/main/java/org/utbot/examples/objects/AnonymousClassesExample.java index 36e705bc01..8128872408 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/objects/AnonymousClassesExample.java +++ b/utbot-sample/src/main/java/org/utbot/examples/objects/AnonymousClassesExample.java @@ -1,9 +1,9 @@ package org.utbot.examples.objects; public class AnonymousClassesExample { - private static final AbstractAnonymousClass staticAnonymousClass = AbstractAnonymousClass.getInstance(1); + static final AbstractAnonymousClass staticAnonymousClass = AbstractAnonymousClass.getInstance(1); @SuppressWarnings("FieldMayBeFinal") - private static AbstractAnonymousClass nonFinalAnonymousStatic = AbstractAnonymousClass.getInstance(1); + static AbstractAnonymousClass nonFinalAnonymousStatic = AbstractAnonymousClass.getInstance(1); public int anonymousClassAsParam(AbstractAnonymousClass abstractAnonymousClass) { return abstractAnonymousClass.constValue(); diff --git a/utbot-sample/src/main/java/org/utbot/examples/objects/ClassForTestClinitSections.java b/utbot-sample/src/main/java/org/utbot/examples/objects/ClassForTestClinitSections.java new file mode 100644 index 0000000000..f037e88283 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/objects/ClassForTestClinitSections.java @@ -0,0 +1,13 @@ +package org.utbot.examples.objects; + +public class ClassForTestClinitSections { + private static int x = 5; + + public int resultDependingOnStaticSection() { + if (x == 5) { + return -1; + } + + return 1; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/objects/HiddenFieldAccessModifiersExample.java b/utbot-sample/src/main/java/org/utbot/examples/objects/HiddenFieldAccessModifiersExample.java new file mode 100644 index 0000000000..16cf7f50b9 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/objects/HiddenFieldAccessModifiersExample.java @@ -0,0 +1,7 @@ +package org.utbot.examples.objects; + +public class HiddenFieldAccessModifiersExample { + public boolean checkSuperFieldEqualsOne(HiddenFieldAccessModifiersSucc b) { + return b.getF() == 1; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/objects/HiddenFieldAccessModifiersSucc.java b/utbot-sample/src/main/java/org/utbot/examples/objects/HiddenFieldAccessModifiersSucc.java new file mode 100644 index 0000000000..ba200f176f --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/objects/HiddenFieldAccessModifiersSucc.java @@ -0,0 +1,5 @@ +package org.utbot.examples.objects; + +public class HiddenFieldAccessModifiersSucc extends HiddenFieldAccessModifiersSuper { + private int f; +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/objects/HiddenFieldAccessModifiersSuper.java b/utbot-sample/src/main/java/org/utbot/examples/objects/HiddenFieldAccessModifiersSuper.java new file mode 100644 index 0000000000..1d819d711d --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/objects/HiddenFieldAccessModifiersSuper.java @@ -0,0 +1,11 @@ +package org.utbot.examples.objects; + +public class HiddenFieldAccessModifiersSuper { + public int f; + public int getF() { + return this.f; + } + public void setF(int val) { + this.f = val; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/objects/LocalClassExample.java b/utbot-sample/src/main/java/org/utbot/examples/objects/LocalClassExample.java new file mode 100644 index 0000000000..6c6ddcc560 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/objects/LocalClassExample.java @@ -0,0 +1,17 @@ +package org.utbot.examples.objects; + +public class LocalClassExample { + int localClassFieldExample(int y) { + class LocalClass { + final int x; + + public LocalClass(int x) { + this.x = x; + } + } + + LocalClass localClass = new LocalClass(42); + + return localClass.x + y; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/reflection/NewInstanceExample.java b/utbot-sample/src/main/java/org/utbot/examples/reflection/NewInstanceExample.java new file mode 100644 index 0000000000..21f4c1f6ed --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/reflection/NewInstanceExample.java @@ -0,0 +1,16 @@ +package org.utbot.examples.reflection; + +public class NewInstanceExample { + @SuppressWarnings("deprecation") + int createWithReflectionExample() throws ClassNotFoundException, InstantiationException, IllegalAccessException { + Class cls = Class.forName("org.utbot.examples.reflection.ClassWithDefaultConstructor"); + ClassWithDefaultConstructor classWithDefaultConstructor = (ClassWithDefaultConstructor) cls.newInstance(); + + return classWithDefaultConstructor.x; + } +} + +class ClassWithDefaultConstructor { + + int x; +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/stdlib/DateExample.java b/utbot-sample/src/main/java/org/utbot/examples/stdlib/DateExample.java new file mode 100644 index 0000000000..4f334c5aca --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/stdlib/DateExample.java @@ -0,0 +1,9 @@ +package org.utbot.examples.stdlib; + +import java.util.Date; + +public class DateExample { + public boolean getTime(Date date) { + return date.getTime() == 100; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/stdlib/JavaIOFileInputStreamCheck.java b/utbot-sample/src/main/java/org/utbot/examples/stdlib/JavaIOFileInputStreamCheck.java new file mode 100644 index 0000000000..968295ef2a --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/stdlib/JavaIOFileInputStreamCheck.java @@ -0,0 +1,12 @@ +package org.utbot.examples.stdlib; + +import java.io.FileInputStream; +import java.io.IOException; + +public class JavaIOFileInputStreamCheck { + public int read(String s) throws IOException { + java.io.FileInputStream fis = new java.io.FileInputStream(s); + byte[] b = new byte[1000]; + return fis.read(b); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/stdlib/StaticsPathDiversion.java b/utbot-sample/src/main/java/org/utbot/examples/stdlib/StaticsPathDiversion.java new file mode 100644 index 0000000000..a83791d1d1 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/stdlib/StaticsPathDiversion.java @@ -0,0 +1,21 @@ +package org.utbot.examples.stdlib; + +import org.utbot.api.mock.UtMock; + +import java.io.File; + +public class StaticsPathDiversion { + @SuppressWarnings({"IfStatementWithIdenticalBranches"}) + // In this test we check that the symbolic engine does not change the static field `File.separator` + public String separatorEquality(String s) { + // Ignore this case to make sure we will have not more than 2 executions even without minimization + UtMock.assume(s != null); + + // We use if-else here instead of a simple return to get executions for both return values + if (File.separator.equals(s)) { + return File.separator; + } else { + return File.separator; + } + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java b/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java index 326f3b12b9..b636394152 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java +++ b/utbot-sample/src/main/java/org/utbot/examples/stream/BaseStreamExample.java @@ -13,21 +13,14 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.ToDoubleFunction; +import java.util.function.ToIntFunction; +import java.util.function.ToLongFunction; import java.util.stream.Collectors; import java.util.stream.Stream; @SuppressWarnings({"IfStatementWithIdenticalBranches", "RedundantOperationOnEmptyContainer"}) public class BaseStreamExample { - Stream returningStreamExample(List list) { - UtMock.assume(list != null); - - if (list.isEmpty()) { - return list.stream(); - } else { - return list.stream(); - } - } - Stream returningStreamAsParameterExample(Stream s) { UtMock.assume(s != null); return s; @@ -55,7 +48,35 @@ Integer[] mapExample(List list) { } } - // TODO mapToInt, etc https://github.com/UnitTestBot/UTBotJava/issues/146 + int[] mapToIntExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + if (list.contains(null)) { + return list.stream().mapToInt(Short::intValue).toArray(); + } else { + return list.stream().mapToInt(Short::intValue).toArray(); + } + } + + long[] mapToLongExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + if (list.contains(null)) { + return list.stream().mapToLong(Short::longValue).toArray(); + } else { + return list.stream().mapToLong(Short::longValue).toArray(); + } + } + + double[] mapToDoubleExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + if (list.contains(null)) { + return list.stream().mapToDouble(Short::doubleValue).toArray(); + } else { + return list.stream().mapToDouble(Short::doubleValue).toArray(); + } + } Object[] flatMapExample(List list) { UtMock.assume(list != null && !list.isEmpty()); @@ -63,12 +84,57 @@ Object[] flatMapExample(List list) { return list.stream().flatMap(value -> Arrays.stream(new Object[]{value, value})).toArray(Object[]::new); } + int[] flatMapToIntExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + + return list + .stream() + .flatMapToInt(value -> + Arrays.stream(new int[]{ + shortToIntFunction.applyAsInt(value), + shortToIntFunction.applyAsInt(value)} + ) + ) + .toArray(); + } + + long[] flatMapToLongExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + + return list + .stream() + .flatMapToLong(value -> + Arrays.stream(new long[]{ + shortToLongFunction.applyAsLong(value), + shortToLongFunction.applyAsLong(value)} + ) + ) + .toArray(); + } + + double[] flatMapToDoubleExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + + return list + .stream() + .flatMapToDouble(value -> + Arrays.stream(new double[]{ + shortToDoubleFunction.applyAsDouble(value), + shortToDoubleFunction.applyAsDouble(value)} + ) + ) + .toArray(); + } + boolean distinctExample(List list) { UtMock.assume(list != null && !list.isEmpty()); int prevSize = list.size(); - int newSize = list.stream().distinct().toArray().length; + int newSize = (int) list.stream().distinct().count(); return prevSize != newSize; } @@ -97,12 +163,18 @@ int peekExample(List list) { int beforeStaticValue = x; final Consumer action = value -> x += value; + final Stream stream = list.stream(); + + Stream afterPeek; if (list.contains(null)) { - list.stream().peek(action); + afterPeek = stream.peek(action); } else { - list.stream().peek(action); + afterPeek = stream.peek(action); } + // use terminal operation to force peek action + afterPeek.count(); + return beforeStaticValue; } @@ -368,14 +440,16 @@ Optional findFirstExample(List list) { } } + @SuppressWarnings("DuplicatedCode") Integer iteratorSumExample(List list) { - UtMock.assume(list != null && !list.isEmpty()); + UtMock.assume(list != null); int sum = 0; Iterator streamIterator = list.stream().iterator(); if (list.isEmpty()) { while (streamIterator.hasNext()) { + // unreachable Integer value = streamIterator.next(); sum += value; } @@ -410,21 +484,16 @@ long closedStreamExample(List values) { } @SuppressWarnings({"ReplaceInefficientStreamCount", "ConstantConditions"}) + // TODO wrong generic type for data field https://github.com/UnitTestBot/UTBotJava/issues/730 long customCollectionStreamExample(CustomCollection customCollection) { UtMock.assume(customCollection != null && customCollection.data != null); - if (customCollection.isEmpty()) { - return customCollection.stream().count(); + final Stream stream = customCollection.stream(); - // simplified example, does not generate branch too - /*customCollection.removeIf(Objects::isNull); - return customCollection.toArray().length;*/ + if (customCollection.isEmpty()) { + return stream.count(); } else { - return customCollection.stream().count(); - - // simplified example, does not generate branch too - /*customCollection.removeIf(Objects::isNull); - return customCollection.toArray().length;*/ + return stream.count(); } } @@ -524,7 +593,7 @@ public Iterator iterator() { @SuppressWarnings({"ManualArrayCopy", "unchecked"}) @NotNull @Override - public Object[] toArray() { + public Object @NotNull [] toArray() { final int size = size(); E[] arr = (E[]) new Object[size]; for (int i = 0; i < size; i++) { @@ -534,9 +603,9 @@ public Object[] toArray() { return arr; } - @NotNull + @SuppressWarnings({"SuspiciousToArrayCall"}) @Override - public T[] toArray(@NotNull T[] a) { + public T[] toArray(T @NotNull [] a) { return Arrays.asList(data).toArray(a); } @@ -560,6 +629,7 @@ public boolean remove(Object o) { return removed; } + @SuppressWarnings("SlowListContainsAll") @Override public boolean containsAll(@NotNull Collection c) { return Arrays.asList(data).containsAll(c); diff --git a/utbot-sample/src/main/java/org/utbot/examples/stream/DoubleStreamExample.java b/utbot-sample/src/main/java/org/utbot/examples/stream/DoubleStreamExample.java new file mode 100644 index 0000000000..dc8801f498 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/stream/DoubleStreamExample.java @@ -0,0 +1,508 @@ +package org.utbot.examples.stream; + +import org.utbot.api.mock.UtMock; + +import java.util.Arrays; +import java.util.DoubleSummaryStatistics; +import java.util.List; +import java.util.OptionalDouble; +import java.util.PrimitiveIterator; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleFunction; +import java.util.function.DoublePredicate; +import java.util.function.DoubleToIntFunction; +import java.util.function.DoubleToLongFunction; +import java.util.function.DoubleUnaryOperator; +import java.util.function.ToDoubleFunction; +import java.util.stream.DoubleStream; + +@SuppressWarnings("IfStatementWithIdenticalBranches") +public class DoubleStreamExample { + DoubleStream returningStreamAsParameterExample(DoubleStream s) { + UtMock.assume(s != null); + + return s; + } + + int useParameterStream(DoubleStream s) { + UtMock.assume(s != null); + + final double[] values = s.toArray(); + + if (values.length == 0) { + return 0; + } else { + return values.length; + } + } + + boolean filterExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int prevSize = list.size(); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + int newSize = list.stream().mapToDouble(shortToDoubleFunction).filter(x -> x != 0).toArray().length; + + return prevSize != newSize; + } + + double[] mapExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final DoubleUnaryOperator mapper = value -> value * 2; + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.contains(null)) { + return doubles.map(mapper).toArray(); + } else { + return doubles.map(mapper).toArray(); + } + } + + Object[] mapToObjExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final DoubleFunction mapper = value -> new double[]{value, value}; + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.contains(null)) { + return doubles.mapToObj(mapper).toArray(); + } else { + return doubles.mapToObj(mapper).toArray(); + } + } + + int[] mapToIntExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final DoubleToIntFunction mapper = value -> (int) value; + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.contains(null)) { + return doubles.mapToInt(mapper).toArray(); + } else { + return doubles.mapToInt(mapper).toArray(); + } + } + + long[] mapToLongExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final DoubleToLongFunction mapper = value -> (long) value; + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.contains(null)) { + return doubles.mapToLong(mapper).toArray(); + } else { + return doubles.mapToLong(mapper).toArray(); + } + } + + double[] flatMapExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + return doubles.flatMap(x -> Arrays.stream(new double[]{x, x})).toArray(); + } + + boolean distinctExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int prevSize = list.size(); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + int newSize = list.stream().mapToDouble(shortToDoubleFunction).distinct().toArray().length; + + return prevSize != newSize; + } + + double[] sortedExample(List list) { + UtMock.assume(list != null && list.size() >= 2); + + Short first = list.get(0); + + int lastIndex = list.size() - 1; + Short last = list.get(lastIndex); + + UtMock.assume(last < first); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + return doubles.sorted().toArray(); + } + + static int x = 0; + + @SuppressWarnings("ResultOfMethodCallIgnored") + int peekExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int beforeStaticValue = x; + + final DoubleConsumer action = value -> x += value; + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + DoubleStream afterPeek; + if (list.contains(null)) { + afterPeek = doubles.peek(action); + } else { + afterPeek = doubles.peek(action); + } + + // use terminal operation to force peek action + afterPeek.count(); + + return beforeStaticValue; + } + + double[] limitExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.size() <= 2) { + return doubles.limit(2).toArray(); + } else { + return doubles.limit(2).toArray(); + } + } + + double[] skipExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.size() <= 2) { + return doubles.skip(2).toArray(); + } else { + return doubles.skip(2).toArray(); + } + } + + int forEachExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int beforeStaticValue = x; + + final DoubleConsumer action = value -> x += value; + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.contains(null)) { + doubles.forEach(action); + } else { + doubles.forEach(action); + } + + return beforeStaticValue; + } + + double[] toArrayExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (size <= 1) { + return doubles.toArray(); + } else { + return doubles.toArray(); + } + } + + double reduceExample(List list) { + UtMock.assume(list != null); + + final double identity = 42; + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.isEmpty()) { + return doubles.reduce(identity, Double::sum); + } else { + return doubles.reduce(identity, Double::sum); + } + } + + OptionalDouble optionalReduceExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (size == 0) { + return doubles.reduce(Double::sum); + } + + return doubles.reduce(Double::sum); + } + + // TODO collect example + + double sumExample(List list) { + UtMock.assume(list != null); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.isEmpty()) { + return doubles.sum(); + } else { + return doubles.sum(); + } + } + + OptionalDouble minExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (size == 0) { + return doubles.min(); + } + + return doubles.min(); + } + + OptionalDouble maxExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (size == 0) { + return doubles.max(); + } + + return doubles.max(); + } + + long countExample(List list) { + UtMock.assume(list != null); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.isEmpty()) { + return doubles.count(); + } else { + return doubles.count(); + } + } + + OptionalDouble averageExample(List list) { + UtMock.assume(list != null); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.isEmpty()) { + return doubles.average(); + } else { + return doubles.average(); + } + } + + DoubleSummaryStatistics summaryStatisticsExample(List list) { + UtMock.assume(list != null); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.isEmpty()) { + return doubles.summaryStatistics(); + } else { + return doubles.summaryStatistics(); + } + } + + boolean anyMatchExample(List list) { + UtMock.assume(list != null); + + final DoublePredicate predicate = value -> value != 0; + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + if (list.isEmpty()) { + return doubles.anyMatch(predicate); + } + + UtMock.assume(list.size() == 2); + + Short first = list.get(0); + Short second = list.get(1); + + if ((first == null || first == 0) && (second == null || second == 0)) { + return doubles.anyMatch(predicate); + } + + if (first == null || first == 0) { + return doubles.anyMatch(predicate); + } + + if (second == null || second == 0) { + return doubles.anyMatch(predicate); + } + + return doubles.anyMatch(predicate); + } + + boolean allMatchExample(List list) { + UtMock.assume(list != null); + + final DoublePredicate predicate = value -> value != 0; + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + if (list.isEmpty()) { + return doubles.allMatch(predicate); + } + + UtMock.assume(list.size() == 2); + + Short first = list.get(0); + Short second = list.get(1); + + if ((first == null || first == 0) && (second == null || second == 0)) { + return doubles.allMatch(predicate); + } + + if (first == null || first == 0) { + return doubles.allMatch(predicate); + } + + if (second == null || second == 0) { + return doubles.allMatch(predicate); + } + + return doubles.allMatch(predicate); + } + + boolean noneMatchExample(List list) { + UtMock.assume(list != null); + + final DoublePredicate predicate = value -> value != 0; + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + if (list.isEmpty()) { + return doubles.noneMatch(predicate); + } + + UtMock.assume(list.size() == 2); + + Short first = list.get(0); + Short second = list.get(1); + + if ((first == null || first == 0) && (second == null || second == 0)) { + return doubles.noneMatch(predicate); + } + + if (first == null || first == 0) { + return doubles.noneMatch(predicate); + } + + if (second == null || second == 0) { + return doubles.noneMatch(predicate); + } + + return doubles.noneMatch(predicate); + } + + OptionalDouble findFirstExample(List list) { + UtMock.assume(list != null); + + final ToDoubleFunction shortToDoubleFunction = value -> value == null ? 0 : value.doubleValue(); + final DoubleStream doubles = list.stream().mapToDouble(shortToDoubleFunction); + + if (list.isEmpty()) { + return doubles.findFirst(); + } + + if (list.get(0) == null) { + return doubles.findFirst(); + } else { + return doubles.findFirst(); + } + } + + Object[] boxedExample(List list) { + UtMock.assume(list != null); + + return list.stream().mapToDouble(value -> value == null ? 0 : value.doubleValue()).boxed().toArray(); + } + + double iteratorSumExample(List list) { + UtMock.assume(list != null); + + double sum = 0; + PrimitiveIterator.OfDouble streamIterator = list.stream().mapToDouble(value -> value == null ? 0 : value.doubleValue()).iterator(); + + if (list.isEmpty()) { + while (streamIterator.hasNext()) { + // unreachable + Double value = streamIterator.next(); + sum += value; + } + } else { + while (streamIterator.hasNext()) { + Double value = streamIterator.next(); + sum += value; + } + } + + return sum; + } + + DoubleStream streamOfExample(double[] values) { + UtMock.assume(values != null); + + if (values.length == 0) { + return DoubleStream.empty(); + } else { + return DoubleStream.of(values); + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + long closedStreamExample(List values) { + UtMock.assume(values != null); + + DoubleStream doubleStream = values.stream().mapToDouble(value -> value == null ? 0 : value.doubleValue()); + doubleStream.count(); + + return doubleStream.count(); + } + + double[] generateExample() { + return DoubleStream.generate(() -> 42).limit(10).toArray(); + } + + double[] iterateExample() { + return DoubleStream.iterate(42, x -> x + 1).limit(10).toArray(); + } + + double[] concatExample() { + final double identity = 42; + DoubleStream first = DoubleStream.generate(() -> identity).limit(10); + DoubleStream second = DoubleStream.iterate(identity, x -> x + 1).limit(10); + + return DoubleStream.concat(first, second).toArray(); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/stream/IntStreamExample.java b/utbot-sample/src/main/java/org/utbot/examples/stream/IntStreamExample.java new file mode 100644 index 0000000000..2e3e33f122 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/stream/IntStreamExample.java @@ -0,0 +1,532 @@ +package org.utbot.examples.stream; + +import org.utbot.api.mock.UtMock; + +import java.util.Arrays; +import java.util.IntSummaryStatistics; +import java.util.List; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.PrimitiveIterator; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; +import java.util.function.IntPredicate; +import java.util.function.IntToDoubleFunction; +import java.util.function.IntToLongFunction; +import java.util.function.IntUnaryOperator; +import java.util.function.ToIntFunction; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; + +@SuppressWarnings("IfStatementWithIdenticalBranches") +public class IntStreamExample { + IntStream returningStreamAsParameterExample(IntStream s) { + UtMock.assume(s != null); + + return s; + } + + int useParameterStream(IntStream s) { + UtMock.assume(s != null); + + final int[] values = s.toArray(); + + if (values.length == 0) { + return 0; + } else { + return values.length; + } + } + + boolean filterExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int prevSize = list.size(); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + int newSize = list.stream().mapToInt(shortToIntFunction).filter(x -> x != 0).toArray().length; + + return prevSize != newSize; + } + + int[] mapExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final IntUnaryOperator mapper = value -> value * 2; + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.contains(null)) { + return ints.map(mapper).toArray(); + } else { + return ints.map(mapper).toArray(); + } + } + + Object[] mapToObjExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final IntFunction mapper = value -> new int[]{value, value}; + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.contains(null)) { + return ints.mapToObj(mapper).toArray(); + } else { + return ints.mapToObj(mapper).toArray(); + } + } + + long[] mapToLongExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final IntToLongFunction mapper = value -> value * 2L; + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.contains(null)) { + return ints.mapToLong(mapper).toArray(); + } else { + return ints.mapToLong(mapper).toArray(); + } + } + + double[] mapToDoubleExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final IntToDoubleFunction mapper = value -> (double) value / 2; + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.contains(null)) { + return ints.mapToDouble(mapper).toArray(); + } else { + return ints.mapToDouble(mapper).toArray(); + } + } + + int[] flatMapExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + return ints.flatMap(x -> Arrays.stream(new int[]{x, x})).toArray(); + } + + boolean distinctExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int prevSize = list.size(); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + int newSize = list.stream().mapToInt(shortToIntFunction).distinct().toArray().length; + + return prevSize != newSize; + } + + int[] sortedExample(List list) { + UtMock.assume(list != null && list.size() >= 2); + + Short first = list.get(0); + + int lastIndex = list.size() - 1; + Short last = list.get(lastIndex); + + UtMock.assume(last < first); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + return ints.sorted().toArray(); + } + + static int x = 0; + + @SuppressWarnings("ResultOfMethodCallIgnored") + int peekExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int beforeStaticValue = x; + + final IntConsumer action = value -> x += value; + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + IntStream afterPeek; + if (list.contains(null)) { + afterPeek = ints.peek(action); + } else { + afterPeek = ints.peek(action); + } + + // use terminal operation to force peek action + afterPeek.count(); + + return beforeStaticValue; + } + + int[] limitExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.size() <= 2) { + return ints.limit(2).toArray(); + } else { + return ints.limit(2).toArray(); + } + } + + int[] skipExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.size() <= 2) { + return ints.skip(2).toArray(); + } else { + return ints.skip(2).toArray(); + } + } + + int forEachExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int beforeStaticValue = x; + + final IntConsumer action = value -> x += value; + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.contains(null)) { + ints.forEach(action); + } else { + ints.forEach(action); + } + + return beforeStaticValue; + } + + int[] toArrayExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (size <= 1) { + return ints.toArray(); + } else { + return ints.toArray(); + } + } + + int reduceExample(List list) { + UtMock.assume(list != null); + + final int identity = 42; + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.isEmpty()) { + return ints.reduce(identity, Integer::sum); + } else { + return ints.reduce(identity, Integer::sum); + } + } + + OptionalInt optionalReduceExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (size == 0) { + return ints.reduce(Integer::sum); + } + + return ints.reduce(Integer::sum); + } + + // TODO collect example + + int sumExample(List list) { + UtMock.assume(list != null); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.isEmpty()) { + return ints.sum(); + } else { + return ints.sum(); + } + } + + OptionalInt minExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (size == 0) { + return ints.min(); + } + + return ints.min(); + } + + OptionalInt maxExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (size == 0) { + return ints.max(); + } + + return ints.max(); + } + + long countExample(List list) { + UtMock.assume(list != null); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.isEmpty()) { + return ints.count(); + } else { + return ints.count(); + } + } + + OptionalDouble averageExample(List list) { + UtMock.assume(list != null); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.isEmpty()) { + return ints.average(); + } else { + return ints.average(); + } + } + + IntSummaryStatistics summaryStatisticsExample(List list) { + UtMock.assume(list != null); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.isEmpty()) { + return ints.summaryStatistics(); + } else { + return ints.summaryStatistics(); + } + } + + boolean anyMatchExample(List list) { + UtMock.assume(list != null); + + final IntPredicate predicate = value -> value != 0; + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + if (list.isEmpty()) { + return ints.anyMatch(predicate); + } + + UtMock.assume(list.size() == 2); + + Short first = list.get(0); + Short second = list.get(1); + + if ((first == null || first == 0) && (second == null || second == 0)) { + return ints.anyMatch(predicate); + } + + if (first == null || first == 0) { + return ints.anyMatch(predicate); + } + + if (second == null || second == 0) { + return ints.anyMatch(predicate); + } + + return ints.anyMatch(predicate); + } + + boolean allMatchExample(List list) { + UtMock.assume(list != null); + + final IntPredicate predicate = value -> value != 0; + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + if (list.isEmpty()) { + return ints.allMatch(predicate); + } + + UtMock.assume(list.size() == 2); + + Short first = list.get(0); + Short second = list.get(1); + + if ((first == null || first == 0) && (second == null || second == 0)) { + return ints.allMatch(predicate); + } + + if (first == null || first == 0) { + return ints.allMatch(predicate); + } + + if (second == null || second == 0) { + return ints.allMatch(predicate); + } + + return ints.allMatch(predicate); + } + + boolean noneMatchExample(List list) { + UtMock.assume(list != null); + + final IntPredicate predicate = value -> value != 0; + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + if (list.isEmpty()) { + return ints.noneMatch(predicate); + } + + UtMock.assume(list.size() == 2); + + Short first = list.get(0); + Short second = list.get(1); + + if ((first == null || first == 0) && (second == null || second == 0)) { + return ints.noneMatch(predicate); + } + + if (first == null || first == 0) { + return ints.noneMatch(predicate); + } + + if (second == null || second == 0) { + return ints.noneMatch(predicate); + } + + return ints.noneMatch(predicate); + } + + OptionalInt findFirstExample(List list) { + UtMock.assume(list != null); + + final ToIntFunction shortToIntFunction = value -> value == null ? 0 : value.intValue(); + final IntStream ints = list.stream().mapToInt(shortToIntFunction); + + if (list.isEmpty()) { + return ints.findFirst(); + } + + if (list.get(0) == null) { + return ints.findFirst(); + } else { + return ints.findFirst(); + } + } + + LongStream asLongStreamExample(List list) { + UtMock.assume(list != null); + + return list.stream().mapToInt(value -> value == null ? 0 : value.intValue()).asLongStream(); + } + + DoubleStream asDoubleStreamExample(List list) { + UtMock.assume(list != null); + + return list.stream().mapToInt(value -> value == null ? 0 : value.intValue()).asDoubleStream(); + } + + Object[] boxedExample(List list) { + UtMock.assume(list != null); + + return list.stream().mapToInt(value -> value == null ? 0 : value.intValue()).boxed().toArray(); + } + + @SuppressWarnings("DuplicatedCode") + int iteratorSumExample(List list) { + UtMock.assume(list != null); + + int sum = 0; + PrimitiveIterator.OfInt streamIterator = list.stream().mapToInt(value -> value == null ? 0 : value.intValue()).iterator(); + + if (list.isEmpty()) { + while (streamIterator.hasNext()) { + // unreachable + Integer value = streamIterator.next(); + sum += value; + } + } else { + while (streamIterator.hasNext()) { + Integer value = streamIterator.next(); + sum += value; + } + } + + return sum; + } + + IntStream streamOfExample(int[] values) { + UtMock.assume(values != null); + + if (values.length == 0) { + return IntStream.empty(); + } else { + return IntStream.of(values); + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + long closedStreamExample(List values) { + UtMock.assume(values != null); + + IntStream intStream = values.stream().mapToInt(value -> value == null ? 0 : value.intValue()); + intStream.count(); + + return intStream.count(); + } + + int[] generateExample() { + return IntStream.generate(() -> 42).limit(10).toArray(); + } + + int[] iterateExample() { + return IntStream.iterate(42, x -> x + 1).limit(10).toArray(); + } + + int[] concatExample() { + final int identity = 42; + IntStream first = IntStream.generate(() -> identity).limit(10); + IntStream second = IntStream.iterate(identity, x -> x + 1).limit(10); + + return IntStream.concat(first, second).toArray(); + } + + int[] rangeExample() { + return IntStream.range(0, 10).toArray(); + } + + int[] rangeClosedExample() { + return IntStream.rangeClosed(0, 10).toArray(); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/stream/LongStreamExample.java b/utbot-sample/src/main/java/org/utbot/examples/stream/LongStreamExample.java new file mode 100644 index 0000000000..c821714069 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/stream/LongStreamExample.java @@ -0,0 +1,524 @@ +package org.utbot.examples.stream; + +import org.utbot.api.mock.UtMock; + +import java.util.Arrays; +import java.util.List; +import java.util.LongSummaryStatistics; +import java.util.OptionalDouble; +import java.util.OptionalLong; +import java.util.PrimitiveIterator; +import java.util.function.LongConsumer; +import java.util.function.LongFunction; +import java.util.function.LongPredicate; +import java.util.function.LongToDoubleFunction; +import java.util.function.LongToIntFunction; +import java.util.function.LongUnaryOperator; +import java.util.function.ToLongFunction; +import java.util.stream.DoubleStream; +import java.util.stream.LongStream; + +@SuppressWarnings("IfStatementWithIdenticalBranches") +public class LongStreamExample { + LongStream returningStreamAsParameterExample(LongStream s) { + UtMock.assume(s != null); + + return s; + } + + int useParameterStream(LongStream s) { + UtMock.assume(s != null); + + final long[] values = s.toArray(); + + if (values.length == 0) { + return 0; + } else { + return values.length; + } + } + + boolean filterExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int prevSize = list.size(); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + int newSize = list.stream().mapToLong(shortToLongFunction).filter(x -> x != 0).toArray().length; + + return prevSize != newSize; + } + + long[] mapExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final LongUnaryOperator mapper = value -> value * 2; + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.contains(null)) { + return longs.map(mapper).toArray(); + } else { + return longs.map(mapper).toArray(); + } + } + + Object[] mapToObjExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final LongFunction mapper = value -> new long[]{value, value}; + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.contains(null)) { + return longs.mapToObj(mapper).toArray(); + } else { + return longs.mapToObj(mapper).toArray(); + } + } + + int[] mapToIntExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final LongToIntFunction mapper = value -> (int) value; + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.contains(null)) { + return longs.mapToInt(mapper).toArray(); + } else { + return longs.mapToInt(mapper).toArray(); + } + } + + double[] mapToDoubleExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final LongToDoubleFunction mapper = value -> (double) value / 2; + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.contains(null)) { + return longs.mapToDouble(mapper).toArray(); + } else { + return longs.mapToDouble(mapper).toArray(); + } + } + + long[] flatMapExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + return longs.flatMap(x -> Arrays.stream(new long[]{x, x})).toArray(); + } + + boolean distinctExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int prevSize = list.size(); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + int newSize = list.stream().mapToLong(shortToLongFunction).distinct().toArray().length; + + return prevSize != newSize; + } + + long[] sortedExample(List list) { + UtMock.assume(list != null && list.size() >= 2); + + Short first = list.get(0); + + int lastIndex = list.size() - 1; + Short last = list.get(lastIndex); + + UtMock.assume(last < first); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + return longs.sorted().toArray(); + } + + static int x = 0; + + @SuppressWarnings("ResultOfMethodCallIgnored") + int peekExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int beforeStaticValue = x; + + final LongConsumer action = value -> x += value; + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + LongStream afterPeek; + if (list.contains(null)) { + afterPeek = longs.peek(action); + } else { + afterPeek = longs.peek(action); + } + + // use terminal operation to force peek action + afterPeek.count(); + + return beforeStaticValue; + } + + long[] limitExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.size() <= 2) { + return longs.limit(2).toArray(); + } else { + return longs.limit(2).toArray(); + } + } + + long[] skipExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.size() <= 2) { + return longs.skip(2).toArray(); + } else { + return longs.skip(2).toArray(); + } + } + + int forEachExample(List list) { + UtMock.assume(list != null && !list.isEmpty()); + + int beforeStaticValue = x; + + final LongConsumer action = value -> x += value; + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.contains(null)) { + longs.forEach(action); + } else { + longs.forEach(action); + } + + return beforeStaticValue; + } + + long[] toArrayExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (size <= 1) { + return longs.toArray(); + } else { + return longs.toArray(); + } + } + + long reduceExample(List list) { + UtMock.assume(list != null); + + final long identity = 42; + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.isEmpty()) { + return longs.reduce(identity, Long::sum); + } else { + return longs.reduce(identity, Long::sum); + } + } + + OptionalLong optionalReduceExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (size == 0) { + return longs.reduce(Long::sum); + } + + return longs.reduce(Long::sum); + } + + // TODO collect example + + long sumExample(List list) { + UtMock.assume(list != null); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.isEmpty()) { + return longs.sum(); + } else { + return longs.sum(); + } + } + + OptionalLong minExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (size == 0) { + return longs.min(); + } + + return longs.min(); + } + + OptionalLong maxExample(List list) { + UtMock.assume(list != null); + + int size = list.size(); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (size == 0) { + return longs.max(); + } + + return longs.max(); + } + + long countExample(List list) { + UtMock.assume(list != null); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.isEmpty()) { + return longs.count(); + } else { + return longs.count(); + } + } + + OptionalDouble averageExample(List list) { + UtMock.assume(list != null); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.isEmpty()) { + return longs.average(); + } else { + return longs.average(); + } + } + + LongSummaryStatistics summaryStatisticsExample(List list) { + UtMock.assume(list != null); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.isEmpty()) { + return longs.summaryStatistics(); + } else { + return longs.summaryStatistics(); + } + } + + boolean anyMatchExample(List list) { + UtMock.assume(list != null); + + final LongPredicate predicate = value -> value != 0; + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + if (list.isEmpty()) { + return longs.anyMatch(predicate); + } + + UtMock.assume(list.size() == 2); + + Short first = list.get(0); + Short second = list.get(1); + + if ((first == null || first == 0) && (second == null || second == 0)) { + return longs.anyMatch(predicate); + } + + if (first == null || first == 0) { + return longs.anyMatch(predicate); + } + + if (second == null || second == 0) { + return longs.anyMatch(predicate); + } + + return longs.anyMatch(predicate); + } + + boolean allMatchExample(List list) { + UtMock.assume(list != null); + + final LongPredicate predicate = value -> value != 0; + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + if (list.isEmpty()) { + return longs.allMatch(predicate); + } + + UtMock.assume(list.size() == 2); + + Short first = list.get(0); + Short second = list.get(1); + + if ((first == null || first == 0) && (second == null || second == 0)) { + return longs.allMatch(predicate); + } + + if (first == null || first == 0) { + return longs.allMatch(predicate); + } + + if (second == null || second == 0) { + return longs.allMatch(predicate); + } + + return longs.allMatch(predicate); + } + + boolean noneMatchExample(List list) { + UtMock.assume(list != null); + + final LongPredicate predicate = value -> value != 0; + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + if (list.isEmpty()) { + return longs.noneMatch(predicate); + } + + UtMock.assume(list.size() == 2); + + Short first = list.get(0); + Short second = list.get(1); + + if ((first == null || first == 0) && (second == null || second == 0)) { + return longs.noneMatch(predicate); + } + + if (first == null || first == 0) { + return longs.noneMatch(predicate); + } + + if (second == null || second == 0) { + return longs.noneMatch(predicate); + } + + return longs.noneMatch(predicate); + } + + OptionalLong findFirstExample(List list) { + UtMock.assume(list != null); + + final ToLongFunction shortToLongFunction = value -> value == null ? 0 : value.longValue(); + final LongStream longs = list.stream().mapToLong(shortToLongFunction); + + if (list.isEmpty()) { + return longs.findFirst(); + } + + if (list.get(0) == null) { + return longs.findFirst(); + } else { + return longs.findFirst(); + } + } + + DoubleStream asDoubleStreamExample(List list) { + UtMock.assume(list != null); + + return list.stream().mapToLong(value -> value == null ? 0 : value.longValue()).asDoubleStream(); + } + + Object[] boxedExample(List list) { + UtMock.assume(list != null); + + return list.stream().mapToLong(value -> value == null ? 0 : value.longValue()).boxed().toArray(); + } + + long iteratorSumExample(List list) { + UtMock.assume(list != null); + + long sum = 0; + PrimitiveIterator.OfLong streamIterator = list.stream().mapToLong(value -> value == null ? 0 : value.longValue()).iterator(); + + if (list.isEmpty()) { + while (streamIterator.hasNext()) { + // unreachable + Long value = streamIterator.next(); + sum += value; + } + } else { + while (streamIterator.hasNext()) { + Long value = streamIterator.next(); + sum += value; + } + } + + return sum; + } + + LongStream streamOfExample(long[] values) { + UtMock.assume(values != null); + + if (values.length == 0) { + return LongStream.empty(); + } else { + return LongStream.of(values); + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + long closedStreamExample(List values) { + UtMock.assume(values != null); + + LongStream intStream = values.stream().mapToLong(value -> value == null ? 0 : value.longValue()); + intStream.count(); + + return intStream.count(); + } + + long[] generateExample() { + return LongStream.generate(() -> 42).limit(10).toArray(); + } + + long[] iterateExample() { + return LongStream.iterate(42, x -> x + 1).limit(10).toArray(); + } + + long[] concatExample() { + final long identity = 42; + LongStream first = LongStream.generate(() -> identity).limit(10); + LongStream second = LongStream.iterate(identity, x -> x + 1).limit(10); + + return LongStream.concat(first, second).toArray(); + } + + long[] rangeExample() { + return LongStream.range(0, 10).toArray(); + } + + long[] rangeClosedExample() { + return LongStream.rangeClosed(0, 10).toArray(); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/stream/StreamsAsMethodResultExample.java b/utbot-sample/src/main/java/org/utbot/examples/stream/StreamsAsMethodResultExample.java new file mode 100644 index 0000000000..566a57f091 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/stream/StreamsAsMethodResultExample.java @@ -0,0 +1,83 @@ +package org.utbot.examples.stream; + +import org.utbot.api.mock.*; + +import java.util.List; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +@SuppressWarnings({"IfStatementWithIdenticalBranches", "RedundantOperationOnEmptyContainer"}) +public class StreamsAsMethodResultExample { + Stream returningStreamExample(List list) { + UtMock.assume(list != null); + + if (list.isEmpty()) { + return list.stream(); + } + + return list.stream(); + } + + IntStream returningIntStreamExample(List list) { + UtMock.assume(list != null); + + final int size = list.size(); + + if (size == 0) { + return list.stream().mapToInt(value -> value); + } + + UtMock.assume(size == 1); + + final Integer integer = list.get(0); + + if (integer == null) { + return list.stream().mapToInt(value -> value); + } + + return list.stream().mapToInt(value -> value); + } + + LongStream returningLongStreamExample(List list) { + UtMock.assume(list != null); + + final int size = list.size(); + + if (size == 0) { + return list.stream().mapToLong(value -> value); + } + + UtMock.assume(size == 1); + + final Integer integer = list.get(0); + + if (integer == null) { + return list.stream().mapToLong(value -> value); + } + + return list.stream().mapToLong(value -> value); + } + + DoubleStream returningDoubleStreamExample(List list) { + UtMock.assume(list != null); + + final int size = list.size(); + + if (size == 0) { + return list.stream().mapToDouble(value -> value); + } + + UtMock.assume(size == 1); + + final Integer integer = list.get(0); + + if (integer == null) { + return list.stream().mapToDouble(value -> value); + } + + return list.stream().mapToDouble(value -> value); + } +} + diff --git a/utbot-sample/src/main/java/org/utbot/examples/strings/GenericExamples.java b/utbot-sample/src/main/java/org/utbot/examples/strings/GenericExamples.java new file mode 100644 index 0000000000..55f0be4196 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/strings/GenericExamples.java @@ -0,0 +1,11 @@ +package org.utbot.examples.strings; + +public class GenericExamples { + public boolean containsOk(T obj) { + return obj.toString().contains("ok"); + } + + public boolean containsOkExample() { + return new GenericExamples().containsOk("Elders have spoken"); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/strings/StringExamples.java b/utbot-sample/src/main/java/org/utbot/examples/strings/StringExamples.java index 73c624ae92..ffdec57810 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/strings/StringExamples.java +++ b/utbot-sample/src/main/java/org/utbot/examples/strings/StringExamples.java @@ -1,5 +1,8 @@ package org.utbot.examples.strings; +import org.jetbrains.annotations.NotNull; +import org.utbot.api.mock.UtMock; + import java.util.Arrays; import static java.lang.Boolean.valueOf; @@ -44,6 +47,16 @@ public String intToString(int a, int b) { } } + public String[] intToStringWithConstants() { + return new String[]{ + Integer.toString(Integer.MIN_VALUE), + Integer.toString(Integer.MIN_VALUE + 100), + Integer.toString(0), + Integer.toString(Integer.MAX_VALUE - 100), + Integer.toString(Integer.MAX_VALUE) + }; + } + public String longToString(long a, long b) { if (a > b) { return Long.toString(a); @@ -52,6 +65,16 @@ public String longToString(long a, long b) { } } + public String[] longToStringWithConstants() { + return new String[]{ + Long.toString(Long.MIN_VALUE), + Long.toString(Long.MIN_VALUE + 100L), + Long.toString(0), + Long.toString(Long.MAX_VALUE - 100L), + Long.toString(Long.MAX_VALUE) + }; + } + public String startsWithLiteral(String str) { if (str.startsWith("1234567890")) { str = str.replace("3", "A"); @@ -74,6 +97,16 @@ public String byteToString(byte a, byte b) { } } + public String[] byteToStringWithConstants() { + return new String[]{ + Byte.toString(Byte.MIN_VALUE), + Byte.toString((byte) (Byte.MIN_VALUE + 100)), + Byte.toString((byte) 0), + Byte.toString((byte) (Byte.MAX_VALUE - 100)), + Byte.toString(Byte.MAX_VALUE) + }; + } + public String replace(String a, String b) { return a.replace("abc", b); } @@ -94,6 +127,16 @@ public String shortToString(short a, short b) { } } + public String[] shortToStringWithConstants() { + return new String[]{ + Short.toString(Short.MIN_VALUE), + Short.toString((short) (Short.MIN_VALUE + 100)), + Short.toString((short) 0), + Short.toString((short) (Short.MAX_VALUE - 100)), + Short.toString(Short.MAX_VALUE) + }; + } + public String booleanToString(boolean a, boolean b) { if (a ^ b) { return Boolean.toString(a ^ b); @@ -166,6 +209,11 @@ public String useStringBuffer(String fst, String snd) { return buffer.toString(); } + // This test checks StringBuilder can be correctly constructed + public void stringBuilderAsParameterExample(StringBuilder sb) { + UtMock.assume(sb != null); + } + public String nullableStringBuffer(StringBuffer buffer, int i) { if (i >= 0) { buffer.append("Positive"); @@ -175,6 +223,16 @@ public String nullableStringBuffer(StringBuffer buffer, int i) { return buffer.toString(); } + @SuppressWarnings("RedundantIfStatement") + public boolean isStringBuilderEmpty(@NotNull StringBuilder stringBuilder) { + String content = stringBuilder.toString(); + if (content.length() == 0) { + return true; + } + + return false; + } + public boolean isValidUuid(String uuid) { return isNotBlank(uuid) && uuid .matches("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); @@ -184,6 +242,50 @@ public boolean isValidUuidShortVersion(String uuid) { return uuid != null && uuid.matches("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); } + @SuppressWarnings("IfStatementWithIdenticalBranches") + public int splitExample(String s) { + UtMock.assume(s != null); + UtMock.assume(s.length() == 3); + + final char firstChar = s.charAt(0); + final char secondChar = s.charAt(1); + final char thirdChar = s.charAt(2); + + final boolean isFirstWhitespace = Character.isWhitespace(firstChar); + final boolean isSecondWhitespace = Character.isWhitespace(secondChar); + final boolean isThirdWhitespace = Character.isWhitespace(thirdChar); + + if (isFirstWhitespace) { + if (isSecondWhitespace) { + if (isThirdWhitespace) { + return s.split("\\s+").length; + } else { + return s.split("\\s+").length; + } + } else { + if (isThirdWhitespace) { + return s.split("\\s+").length; + } else { + return s.split("\\s+").length; + } + } + } else { + if (isSecondWhitespace) { + if (isThirdWhitespace) { + return s.split("\\s+").length; + } else { + return s.split("\\s+").length; + } + } else { + if (isThirdWhitespace) { + return s.split("\\s+").length; + } else { + return s.split("\\s+").length; + } + } + } + } + public boolean isNotBlank(CharSequence cs) { return !isBlank(cs); } diff --git a/utbot-sample/src/main/java/org/utbot/examples/taint/TaintBranching.java b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintBranching.java new file mode 100644 index 0000000000..4ab1923ce1 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintBranching.java @@ -0,0 +1,33 @@ +package org.utbot.examples.taint; + +/** + * Config: ./utbot-sample/src/main/resources/taint/TaintBranchingConfig.yaml + */ +public class TaintBranching { + + public String source() { + return "t"; + } + + public void sink(String s) { + // + } + + public void bad(boolean cond) { + String s = source(); + if (cond) { + sink(s); + } else { + // + } + } + + public void good(boolean cond) { + String s = source(); + if (cond) { + sink("n"); + } else { + // + } + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/taint/TaintCleanerConditions.java b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintCleanerConditions.java new file mode 100644 index 0000000000..0312f9acb6 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintCleanerConditions.java @@ -0,0 +1,63 @@ +package org.utbot.examples.taint; + +/** + * Config: ./utbot-sample/src/main/resources/taint/TaintCleanerConditionsConfig.yaml + */ +public class TaintCleanerConditions { + + public String source() { + return "t"; + } + + public String sourceEmpty() { + return ""; + } + + public void sink(String s) { + // + } + + public void cleanerArgCondition(String s) { + // + } + + public boolean cleanerReturnCondition(String s) { + return s.isEmpty(); + } + + public void badArg() { + String s = source(); + cleanerArgCondition(s); + sink(s); + } + + public void badReturn() { + String s = source(); + boolean isClean = cleanerReturnCondition(s); + sink(s); + } + + public void badThis() { + String s = source(); + boolean res = s.isEmpty(); + sink(s); + } + + public void goodArg() { + String s = sourceEmpty(); + cleanerArgCondition(s); + sink(s); + } + + public void goodReturn() { + String s = sourceEmpty(); + boolean isClean = cleanerReturnCondition(s); + sink(s); + } + + public void goodThis() { + String s = sourceEmpty(); + boolean res = s.isEmpty(); + sink(s); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/taint/TaintCleanerSimple.java b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintCleanerSimple.java new file mode 100644 index 0000000000..b9f727ea69 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintCleanerSimple.java @@ -0,0 +1,35 @@ +package org.utbot.examples.taint; + +/** + * Config: ./utbot-sample/src/main/resources/taint/TaintCleanerSimpleConfig.yaml + */ +public class TaintCleanerSimple { + + public String source() { + return "t"; + } + + public void cleaner(String s) { + // + } + + public void notCleaner(String s) { + // + } + + public void sink(String s) { + // + } + + public void bad() { + String s = source(); + notCleaner(s); + sink(s); + } + + public void good() { + String s = source(); + cleaner(s); + sink(s); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/taint/TaintLongPath.java b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintLongPath.java new file mode 100644 index 0000000000..1262130534 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintLongPath.java @@ -0,0 +1,33 @@ +package org.utbot.examples.taint; + +/** + * Config: ./utbot-sample/src/main/resources/taint/TaintLongPathConfig.yaml + */ +public class TaintLongPath { + + public String source() { + return "t"; + } + + public void sink(String s) { + // + } + + public void bad() { + String s = source(); + bad2(s); + } + + public void bad2(String s) { + bad3(s); + } + + public void bad3(String s) { + sink(s); + } + + public void good() { + String s = source(); + sink("n"); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/taint/TaintOtherClass.java b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintOtherClass.java new file mode 100644 index 0000000000..a33076f67f --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintOtherClass.java @@ -0,0 +1,37 @@ +package org.utbot.examples.taint; + +public class TaintOtherClass { + + public void bad() { + Inner inner = new Inner(); + String s = inner.source(); + String sp = inner.pass(s); + inner.sink(sp); + } + + public void good() { + Inner inner = new Inner(); + String s = inner.source(); + inner.cleaner(s); + inner.sink(s); + } +} + +class Inner { + + public String source() { + return "t"; + } + + public String pass(String s) { + return s + "p"; + } + + public void cleaner(String s) { + // + } + + public void sink(String s) { + // + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/taint/TaintPassConditions.java b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintPassConditions.java new file mode 100644 index 0000000000..7f5b38bf44 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintPassConditions.java @@ -0,0 +1,71 @@ +package org.utbot.examples.taint; + +/** + * Config: ./utbot-sample/src/main/resources/taint/TaintPassConditionsConfig.yaml + */ +public class TaintPassConditions { + + public String source() { + return "t"; + } + + public String sourceEmpty() { + return ""; + } + + public void sink(String s) { + // + } + + public String passArgCondition(String s, boolean isPass) { + if (isPass) { + return s + "p"; + } else { + return ""; + } + } + + public String passReturnCondition(String s, boolean isPass) { + if (isPass) { + return s + "p"; + } else { + return ""; + } + } + + public void badArg() { + String s = source(); + String sp = passArgCondition(s, true); + sink(sp); + } + + public void badReturn() { + String s = source(); + String sp = passReturnCondition(s, true); + sink(sp); + } + + public void badThis() { + String s = source(); + String sp = s.concat("#"); + sink(sp); + } + + public void goodArg() { + String s = source(); + String sp = passArgCondition(s, false); + sink(sp); + } + + public void goodReturn() { + String s = source(); + String sp = passReturnCondition(s, false); + sink(sp); + } + + public void goodThis() { + String s = sourceEmpty(); + String sp = s.concat("#"); + sink(sp); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/taint/TaintPassSimple.java b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintPassSimple.java new file mode 100644 index 0000000000..c76bf76b06 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintPassSimple.java @@ -0,0 +1,42 @@ +package org.utbot.examples.taint; + +/** + * Config: ./utbot-sample/src/main/resources/taint/TaintPassSimpleConfig.yaml + */ +public class TaintPassSimple { + + public String source() { + return "t"; + } + + public String pass(String s) { + return s + "p"; + } + + public String notPass(String s) { + return s + "p"; + } + + public void sink(String s) { + // + } + + public void bad() { + String s = source(); + String sp = pass(s); + sink(sp); + } + + public void badDoublePass() { + String s = source(); + String sp = pass(s); + String spp = pass(sp); + sink(spp); + } + + public void good() { + String s = source(); + String sp = notPass(s); + sink(sp); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSeveralMarks.java b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSeveralMarks.java new file mode 100644 index 0000000000..52da4847ed --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSeveralMarks.java @@ -0,0 +1,131 @@ +package org.utbot.examples.taint; + +/** + * Config: ./utbot-sample/src/main/resources/taint/TaintSeveralMarksConfig.yaml + */ +public class TaintSeveralMarks { + + public String source1() { + return "1"; + } + + public String source2() { + return "2"; + } + + public String source12() { + return "12"; + } + + public String pass1(String s) { + return s + "1"; + } + + public String pass2(String s) { + return s + "2"; + } + + + public void cleaner1(String s) { + // + } + + public void cleaner2(String s) { + // + } + + public void sink1(String s) { + // + } + + public void sink2(String s) { + // + } + + public void sink13(String s) { + // + } + + public void sink123(String s) { + // + } + + public void sinkAll(String s) { + // + } + + public void bad1() { + String s = source1(); + String sp = pass1(s); + sink1(sp); + } + + public void bad2() { + String s = source2(); + String sp = pass2(s); + sink2(sp); + } + + public void bad13() { + String s = source1(); + sink13(s); + } + + public void bad123() { + String s12 = source12(); + sink123(s12); + } + + public void badSourceAll() { + String s = source12(); + sink1(s); + sink2(s); + sinkAll(s); + } + + public void badSinkAll() { + String s1 = source1(); + String s2 = source2(); + sinkAll(s1); + sinkAll(s2); + } + + public void badWrongCleaner() { + String s = source1(); + cleaner2(s); + sink1(s); + } + + public void good1() { + String s = source1(); + cleaner1(s); + sink1(s); + } + + public void good2() { + String s = source2(); + cleaner2(s); + sink2(s); + } + + public void good13() { + String s = source2(); + sink13(s); + } + + public void goodWrongSource() { + String s = source1(); + sink2(s); + } + + public void goodWrongSink() { + String s = source2(); + sink1(s); + } + + public void goodWrongPass() { + String s = source1(); + String sp = pass2(s); + sink1(sp); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSignature.java b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSignature.java new file mode 100644 index 0000000000..39f973fdbe --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSignature.java @@ -0,0 +1,83 @@ +package org.utbot.examples.taint; + +/** + * Config: ./utbot-sample/src/main/resources/taint/TaintSignatureConfig.yaml + */ +public class TaintSignature { + + // sources + + public String source() { + return "t"; + } + + public String source(boolean cond) { // fake + return "t"; + } + + public String source(int a, int b) { // fake + return "t"; + } + + // passes + + public String pass(String s) { + return s + "p"; + } + + public String pass(String s, int b) { // fake + return s + "p"; + } + + // cleaners + + public void cleaner(String s) { + // + } + + public void cleaner(String s, String t) { // fake + // + } + + // sinks + + public void sink(String s) { + // + } + + public void sink(String s, int a) { // fake + // + } + + public void badFakeCleaner() { + String s = source(); + String sp = pass(s); + cleaner(sp, s); // fake + sink(sp); + } + + public void goodCleaner() { + String s = source(); + String sp = pass(s); + cleaner(sp); + sink(sp); + } + + public void goodFakeSources() { + String s = source(true); + String t = source(1, 2); + sink(s); + sink(t); + } + + public void goodFakePass() { + String s = source(); + String sp = pass(s, 1); // fake + sink(sp); + } + + public void goodFakeSink() { + String s = source(); + sink(s, 1); // fake + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSimple.java b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSimple.java new file mode 100644 index 0000000000..e02c2ef10c --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSimple.java @@ -0,0 +1,25 @@ +package org.utbot.examples.taint; + +/** + * Config: ./utbot-sample/src/main/resources/taint/TaintSimpleConfig.yaml + */ +public class TaintSimple { + + public String source() { + return "t"; + } + + public void sink(String s) { + // + } + + public void bad() { + String s = source(); + sink(s); + } + + public void good() { + String s = source(); + sink("n"); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSinkConditions.java b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSinkConditions.java new file mode 100644 index 0000000000..786b73f14d --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSinkConditions.java @@ -0,0 +1,39 @@ +package org.utbot.examples.taint; + +/** + * Config: ./utbot-sample/src/main/resources/taint/TaintSinkConditionsConfig.yaml + */ +public class TaintSinkConditions { + + public String source() { + return "t"; + } + + public String sourceEmpty() { + return ""; + } + + public void sink(String s, boolean isSink) { + // + } + + public void badArg() { + String s = source(); + sink(s, true); + } + + public void badThis() { + String s = source(); + byte[] res = s.getBytes(); + } + + public void goodArg() { + String s = source(); + sink(s, false); + } + + public void goodThis() { + String s = sourceEmpty(); + byte[] res = s.getBytes(); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSourceConditions.java b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSourceConditions.java new file mode 100644 index 0000000000..0da33cbfa1 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/taint/TaintSourceConditions.java @@ -0,0 +1,59 @@ +package org.utbot.examples.taint; + +/** + * Config: ./utbot-sample/src/main/resources/taint/TaintSourceConditionsConfig.yaml + */ +public class TaintSourceConditions { + + public String sourceArgCondition(boolean isSource) { + if (isSource) { + return "t"; + } else { + return ""; + } + } + + public String sourceReturnCondition(boolean isSource) { + if (isSource) { + return "t"; + } else { + return ""; + } + } + + public void sink(String s) { + // + } + + public void badArg() { + String s = sourceArgCondition(true); + sink(s); + } + + public void badReturn() { + String s = sourceReturnCondition(true); + sink(s); + } + + public void badThis() { + String s = "t"; + String res = s.toLowerCase(); + sink(res); + } + + public void goodArg() { + String s = sourceArgCondition(false); + sink(s); + } + + public void goodReturn() { + String s = sourceArgCondition(false); + sink(s); + } + + public void goodThis() { + String s = ""; + String res = s.toLowerCase(); + sink(res); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/CountDownLatchExamples.java b/utbot-sample/src/main/java/org/utbot/examples/threads/CountDownLatchExamples.java new file mode 100644 index 0000000000..b1ff95af84 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/CountDownLatchExamples.java @@ -0,0 +1,38 @@ +package org.utbot.examples.threads; + +import org.utbot.api.mock.UtMock; + +import java.util.concurrent.CountDownLatch; + +public class CountDownLatchExamples { + long getAndDown(CountDownLatch countDownLatch) { + UtMock.assume(countDownLatch != null); + + final long count = countDownLatch.getCount(); + + if (count < 0) { + // Unreachable + return -1; + } + + countDownLatch.countDown(); + final long nextCount = countDownLatch.getCount(); + + if (nextCount < 0) { + // Unreachable + return -2; + } + + if (count == 0) { + // Could not differs from 0 too + return nextCount; + } + + if (count - nextCount != 1) { + // Unreachable + return -3; + } + + return nextCount; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/ExecutorServiceExamples.java b/utbot-sample/src/main/java/org/utbot/examples/threads/ExecutorServiceExamples.java new file mode 100644 index 0000000000..c55005343e --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/ExecutorServiceExamples.java @@ -0,0 +1,23 @@ +package org.utbot.examples.threads; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; + +public class ExecutorServiceExamples { + public void throwingInExecute() { + Executors.newSingleThreadExecutor().execute(() -> { + throw new IllegalStateException(); + }); + } + + public int changingCollectionInExecute() { + List list = new ArrayList<>(); + + Executors.newSingleThreadExecutor().execute(() -> { + list.add(42); + }); + + return list.get(0); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java b/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java new file mode 100644 index 0000000000..2b233f239d --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/FutureExamples.java @@ -0,0 +1,43 @@ +package org.utbot.examples.threads; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class FutureExamples { + public void throwingRunnableExample() throws ExecutionException, InterruptedException { + final CompletableFuture future = CompletableFuture.runAsync(() -> { + throw new IllegalStateException(); + }); + + future.get(); + } + + public int resultFromGet() throws ExecutionException, InterruptedException { + final CompletableFuture future = CompletableFuture.supplyAsync(() -> 42); + + return future.get(); + } + + public int changingCollectionInFuture() throws ExecutionException, InterruptedException { + List values = new ArrayList<>(); + + final CompletableFuture future = CompletableFuture.runAsync(() -> values.add(42)); + + future.get(); + + return values.get(0); + } + + // NOTE: this tests looks similar as the test above BUT it is important to check correctness of the wrapper + // for CompletableFuture - an actions is executed regardless of invoking `get` method. + @SuppressWarnings("unused") + public int changingCollectionInFutureWithoutGet() { + List values = new ArrayList<>(); + + final CompletableFuture future = CompletableFuture.runAsync(() -> values.add(42)); + + return values.get(0); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/threads/ThreadExamples.java b/utbot-sample/src/main/java/org/utbot/examples/threads/ThreadExamples.java new file mode 100644 index 0000000000..21cc1bbfe1 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/threads/ThreadExamples.java @@ -0,0 +1,29 @@ +package org.utbot.examples.threads; + +import java.util.ArrayList; +import java.util.List; + +public class ThreadExamples { + public void explicitExceptionInStart() { + new Thread(() -> { + throw new IllegalStateException(); + }).start(); + } + + public int changingCollectionInThread() { + List values = new ArrayList<>(); + + new Thread(() -> values.add(42)).start(); + + return values.get(0); + } + + @SuppressWarnings("unused") + public int changingCollectionInThreadWithoutStart() { + List values = new ArrayList<>(); + + final Thread thread = new Thread(() -> values.add(42)); + + return values.get(0); + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/types/CollectionAsField.java b/utbot-sample/src/main/java/org/utbot/examples/types/CollectionAsField.java new file mode 100644 index 0000000000..157f34f63c --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/types/CollectionAsField.java @@ -0,0 +1,10 @@ +package org.utbot.examples.types; + +import java.util.HashMap; +import java.util.Map; + +public class CollectionAsField { + public static Map staticMap = new HashMap<>(); + public Map nonStaticMap = new HashMap<>(); + public T field; +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/types/Generics.java b/utbot-sample/src/main/java/org/utbot/examples/types/Generics.java new file mode 100644 index 0000000000..5272a4d391 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/types/Generics.java @@ -0,0 +1,57 @@ +package org.utbot.examples.types; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class Generics { + public boolean genericAsField(CollectionAsField object) { + if (object != null && object.field != null) { + return object.field.equals("abc"); + } + + return false; + } + + public String mapAsStaticField() { + CollectionAsField.staticMap.put("key", "value"); + return CollectionAsField.staticMap.get("key"); + } + + public String mapAsParameter(Map map) { + map.put("key", "value"); + return map.get("key"); + } + + public String mapAsNonStaticField(CollectionAsField object) { + object.nonStaticMap.put("key", "value"); + return object.nonStaticMap.get("key"); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public String methodWithRawType(Map map) { + nestedMethodWithGenericInfo(map); + map.put("key", "value"); + + return (String) map.get("key"); + } + + @SuppressWarnings("UnusedReturnValue") + private Map nestedMethodWithGenericInfo(Map map) { + return map; + } + + public int methodWithArrayTypeBoundary() { + return new ArrayTypeParameters().methodWithArrayTypeBoundary(null); + } +} + +class ArrayTypeParameters { + public int methodWithArrayTypeBoundary(List list) { + if (list.isEmpty()) { + return -1; + } + + return 1; + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/types/PathDependentGenericsExample.java b/utbot-sample/src/main/java/org/utbot/examples/types/PathDependentGenericsExample.java new file mode 100644 index 0000000000..e0861705b6 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/types/PathDependentGenericsExample.java @@ -0,0 +1,59 @@ +package org.utbot.examples.types; + +import org.utbot.api.mock.UtMock; + +import java.util.List; + +public class PathDependentGenericsExample { + public int pathDependentGenerics(GenericParent element) { + if (element instanceof ClassWithOneGeneric) { + functionWithOneGeneric((ClassWithOneGeneric) element); + return 1; + } + + if (element instanceof ClassWithTwoGenerics) { + functionWithTwoGenerics((ClassWithTwoGenerics) element); + return 2; + } + + return 3; + } + + public int functionWithSeveralTypeConstraintsForTheSameObject(Object element) { + if (element instanceof List) { + functionWithSeveralGenerics((List) element, (List) element); + + UtMock.assume(!((List) element).isEmpty()); + Object value = ((List) element).get(0); + UtMock.assume(value != null); + + if (value instanceof Number) { + return 1; + } else { + return 2; // unreachable + } + } + + return 3; + } + + private void functionWithSeveralGenerics(List firstValue, List anotherValue) { + } + + private void functionWithOneGeneric(ClassWithOneGeneric value) { + System.out.println(); + } + + private void functionWithTwoGenerics(ClassWithTwoGenerics value) { + System.out.println(); + } +} + +abstract class GenericParent { +} + +class ClassWithOneGeneric extends GenericParent { +} + +class ClassWithTwoGenerics extends GenericParent { +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/unsafe/UnsafeOperations.java b/utbot-sample/src/main/java/org/utbot/examples/unsafe/UnsafeOperations.java new file mode 100644 index 0000000000..7b76b0ba13 --- /dev/null +++ b/utbot-sample/src/main/java/org/utbot/examples/unsafe/UnsafeOperations.java @@ -0,0 +1,20 @@ +package org.utbot.examples.unsafe; + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; +import java.security.AccessController; +import java.security.PrivilegedAction; + +public class UnsafeOperations { + public int getAddressSizeOrZero() { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + Unsafe unsafe = (Unsafe) f.get(null); + return unsafe.addressSize(); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Reflection failed"); + } + } +} diff --git a/utbot-sample/src/main/java/org/utbot/examples/unsafe/UnsafeWithField.java b/utbot-sample/src/main/java/org/utbot/examples/unsafe/UnsafeWithField.java index bac70b2dcc..83c3bf3df9 100644 --- a/utbot-sample/src/main/java/org/utbot/examples/unsafe/UnsafeWithField.java +++ b/utbot-sample/src/main/java/org/utbot/examples/unsafe/UnsafeWithField.java @@ -5,7 +5,7 @@ public class UnsafeWithField { Field field; - Field setField(Field f) { + public Field setField(Field f) { field = f; return Field.INTEGER; } diff --git a/utbot-sample/src/main/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctions.kt b/utbot-sample/src/main/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctions.kt new file mode 100644 index 0000000000..f9b281a5ce --- /dev/null +++ b/utbot-sample/src/main/kotlin/org/utbot/examples/codegen/FileWithTopLevelFunctions.kt @@ -0,0 +1,15 @@ +package org.utbot.examples.codegen + +class CustomClass + +fun topLevelSum(a: Int, b: Int): Int { + return a + b +} + +fun Int.extensionOnBasicType(other: Int): Int { + return this + other +} + +fun CustomClass.extensionOnCustomClass(other: CustomClass): Boolean { + return this === other +} \ No newline at end of file diff --git a/utbot-sample/src/main/resources/taint/TaintBranchingConfig.yaml b/utbot-sample/src/main/resources/taint/TaintBranchingConfig.yaml new file mode 100644 index 0000000000..70ad529cad --- /dev/null +++ b/utbot-sample/src/main/resources/taint/TaintBranchingConfig.yaml @@ -0,0 +1,9 @@ +sources: + - org.utbot.examples.taint.TaintBranching.source: + add-to: return + marks: bad + +sinks: + - org.utbot.examples.taint.TaintBranching.sink: + check: arg1 + marks: bad diff --git a/utbot-sample/src/main/resources/taint/TaintCleanerConditionsConfig.yaml b/utbot-sample/src/main/resources/taint/TaintCleanerConditionsConfig.yaml new file mode 100644 index 0000000000..1594025397 --- /dev/null +++ b/utbot-sample/src/main/resources/taint/TaintCleanerConditionsConfig.yaml @@ -0,0 +1,29 @@ +sources: + - org.utbot.examples.taint.TaintCleanerConditions.source: + add-to: return + marks: bad + - org.utbot.examples.taint.TaintCleanerConditions.sourceEmpty: + add-to: return + marks: bad + +cleaners: + - org.utbot.examples.taint.TaintCleanerConditions.cleanerArgCondition: + remove-from: arg1 + marks: bad + conditions: + arg1: "" + - org.utbot.examples.taint.TaintCleanerConditions.cleanerReturnCondition: + remove-from: arg1 + marks: bad + conditions: + return: true + - java.lang.String.isEmpty: + remove-from: this + marks: bad + conditions: + this: "" + +sinks: + - org.utbot.examples.taint.TaintCleanerConditions.sink: + check: arg1 + marks: bad diff --git a/utbot-sample/src/main/resources/taint/TaintCleanerSimpleConfig.yaml b/utbot-sample/src/main/resources/taint/TaintCleanerSimpleConfig.yaml new file mode 100644 index 0000000000..9761b1ad1f --- /dev/null +++ b/utbot-sample/src/main/resources/taint/TaintCleanerSimpleConfig.yaml @@ -0,0 +1,14 @@ +sources: + - org.utbot.examples.taint.TaintCleanerSimple.source: + add-to: return + marks: bad + +cleaners: + - org.utbot.examples.taint.TaintCleanerSimple.cleaner: + remove-from: arg1 + marks: bad + +sinks: + - org.utbot.examples.taint.TaintCleanerSimple.sink: + check: arg1 + marks: bad diff --git a/utbot-sample/src/main/resources/taint/TaintLongPathConfig.yaml b/utbot-sample/src/main/resources/taint/TaintLongPathConfig.yaml new file mode 100644 index 0000000000..813971f19b --- /dev/null +++ b/utbot-sample/src/main/resources/taint/TaintLongPathConfig.yaml @@ -0,0 +1,9 @@ +sources: + - org.utbot.examples.taint.TaintLongPath.source: + add-to: return + marks: bad + +sinks: + - org.utbot.examples.taint.TaintLongPath.sink: + check: arg1 + marks: bad diff --git a/utbot-sample/src/main/resources/taint/TaintOtherClassConfig.yaml b/utbot-sample/src/main/resources/taint/TaintOtherClassConfig.yaml new file mode 100644 index 0000000000..ec02e98452 --- /dev/null +++ b/utbot-sample/src/main/resources/taint/TaintOtherClassConfig.yaml @@ -0,0 +1,20 @@ +sources: + - org.utbot.examples.taint.Inner.source: + add-to: return + marks: bad + +passes: + - org.utbot.examples.taint.Inner.pass: + get-from: arg1 + add-to: return + marks: bad + +cleaners: + - org.utbot.examples.taint.Inner.cleaner: + remove-from: arg1 + marks: bad + +sinks: + - org.utbot.examples.taint.Inner.sink: + check: arg1 + marks: bad diff --git a/utbot-sample/src/main/resources/taint/TaintPassConditionsConfig.yaml b/utbot-sample/src/main/resources/taint/TaintPassConditionsConfig.yaml new file mode 100644 index 0000000000..86ba0d173d --- /dev/null +++ b/utbot-sample/src/main/resources/taint/TaintPassConditionsConfig.yaml @@ -0,0 +1,34 @@ +sources: + - org.utbot.examples.taint.TaintPassConditions.source: + add-to: return + marks: bad + - org.utbot.examples.taint.TaintPassConditions.sourceEmpty: + add-to: return + marks: bad + +passes: + - org.utbot.examples.taint.TaintPassConditions.passArgCondition: + get-from: arg1 + add-to: return + marks: bad + conditions: + arg2: true + - org.utbot.examples.taint.TaintPassConditions.passReturnCondition: + get-from: arg1 + add-to: return + marks: bad + conditions: + return: + not: "" + - java.lang.String.concat: + get-from: [ this, arg1 ] + add-to: return + marks: bad + conditions: + this: + not: "" + +sinks: + - org.utbot.examples.taint.TaintPassConditions.sink: + check: arg1 + marks: bad diff --git a/utbot-sample/src/main/resources/taint/TaintPassSimpleConfig.yaml b/utbot-sample/src/main/resources/taint/TaintPassSimpleConfig.yaml new file mode 100644 index 0000000000..f2995edfc9 --- /dev/null +++ b/utbot-sample/src/main/resources/taint/TaintPassSimpleConfig.yaml @@ -0,0 +1,15 @@ +sources: + - org.utbot.examples.taint.TaintPassSimple.source: + add-to: return + marks: bad + +passes: + - org.utbot.examples.taint.TaintPassSimple.pass: + get-from: arg1 + add-to: return + marks: bad + +sinks: + - org.utbot.examples.taint.TaintPassSimple.sink: + check: arg1 + marks: bad diff --git a/utbot-sample/src/main/resources/taint/TaintSeveralMarksConfig.yaml b/utbot-sample/src/main/resources/taint/TaintSeveralMarksConfig.yaml new file mode 100644 index 0000000000..4b2bd7cce9 --- /dev/null +++ b/utbot-sample/src/main/resources/taint/TaintSeveralMarksConfig.yaml @@ -0,0 +1,45 @@ +sources: + - org.utbot.examples.taint.TaintSeveralMarks.source1: + add-to: return + marks: mark1 + - org.utbot.examples.taint.TaintSeveralMarks.source2: + add-to: return + marks: mark2 + - org.utbot.examples.taint.TaintSeveralMarks.source12: + add-to: return + marks: [ mark1, mark2 ] + +passes: + - org.utbot.examples.taint.TaintSeveralMarks.pass1: + get-from: arg1 + add-to: return + marks: mark1 + - org.utbot.examples.taint.TaintSeveralMarks.pass2: + get-from: arg1 + add-to: return + marks: mark2 + +cleaners: + - org.utbot.examples.taint.TaintSeveralMarks.cleaner1: + remove-from: arg1 + marks: mark1 + - org.utbot.examples.taint.TaintSeveralMarks.cleaner2: + remove-from: arg1 + marks: mark2 + +sinks: + - org.utbot.examples.taint.TaintSeveralMarks.sink1: + check: arg1 + marks: mark1 + - org.utbot.examples.taint.TaintSeveralMarks.sink2: + check: arg1 + marks: mark2 + - org.utbot.examples.taint.TaintSeveralMarks.sink13: + check: arg1 + marks: [ mark1, mark3 ] + - org.utbot.examples.taint.TaintSeveralMarks.sink123: + check: arg1 + marks: [ mark1, mark2, mark3 ] + - org.utbot.examples.taint.TaintSeveralMarks.sinkAll: + check: arg1 + marks: [] diff --git a/utbot-sample/src/main/resources/taint/TaintSignatureConfig.yaml b/utbot-sample/src/main/resources/taint/TaintSignatureConfig.yaml new file mode 100644 index 0000000000..73ef64c550 --- /dev/null +++ b/utbot-sample/src/main/resources/taint/TaintSignatureConfig.yaml @@ -0,0 +1,24 @@ +sources: + - org.utbot.examples.taint.TaintSignature.source: + signature: [ ] + add-to: return + marks: bad + +passes: + - org.utbot.examples.taint.TaintSignature.pass: + signature: [ ] + get-from: arg1 + add-to: return + marks: bad + +cleaners: + - org.utbot.examples.taint.TaintSignature.cleaner: + signature: [ ] + remove-from: arg1 + marks: bad + +sinks: + - org.utbot.examples.taint.TaintSignature.sink: + signature: [ ] + check: arg1 + marks: bad diff --git a/utbot-sample/src/main/resources/taint/TaintSimpleConfig.yaml b/utbot-sample/src/main/resources/taint/TaintSimpleConfig.yaml new file mode 100644 index 0000000000..a501b40751 --- /dev/null +++ b/utbot-sample/src/main/resources/taint/TaintSimpleConfig.yaml @@ -0,0 +1,9 @@ +sources: + - org.utbot.examples.taint.TaintSimple.source: + add-to: return + marks: bad + +sinks: + - org.utbot.examples.taint.TaintSimple.sink: + check: arg1 + marks: bad diff --git a/utbot-sample/src/main/resources/taint/TaintSinkConditionsConfig.yaml b/utbot-sample/src/main/resources/taint/TaintSinkConditionsConfig.yaml new file mode 100644 index 0000000000..75392fc7c9 --- /dev/null +++ b/utbot-sample/src/main/resources/taint/TaintSinkConditionsConfig.yaml @@ -0,0 +1,21 @@ +sources: + - org.utbot.examples.taint.TaintSinkConditions.source: + add-to: return + marks: bad + - org.utbot.examples.taint.TaintSinkConditions.sourceEmpty: + add-to: return + marks: bad + +sinks: + - org.utbot.examples.taint.TaintSinkConditions.sink: + check: arg1 + marks: bad + conditions: + arg2: true + - java.lang.String.getBytes: + check: this + marks: bad + conditions: + this: + not: + "" diff --git a/utbot-sample/src/main/resources/taint/TaintSourceConditionsConfig.yaml b/utbot-sample/src/main/resources/taint/TaintSourceConditionsConfig.yaml new file mode 100644 index 0000000000..0fc1fc4177 --- /dev/null +++ b/utbot-sample/src/main/resources/taint/TaintSourceConditionsConfig.yaml @@ -0,0 +1,24 @@ +sources: + - org.utbot.examples.taint.TaintSourceConditions.sourceArgCondition: + add-to: return + marks: bad + conditions: + arg1: true + - org.utbot.examples.taint.TaintSourceConditions.sourceReturnCondition: + add-to: return + marks: bad + conditions: + return: + not: "" + - java.lang.String.toLowerCase: + add-to: return + marks: bad + conditions: + this: + not: + "" + +sinks: + - org.utbot.examples.taint.TaintSourceConditions.sink: + check: arg1 + marks: bad diff --git a/utbot-sample/src/main/resources/utbot-api.jar b/utbot-sample/src/main/resources/utbot-api.jar index 8398d3ed07..a29bf950b9 100644 Binary files a/utbot-sample/src/main/resources/utbot-api.jar and b/utbot-sample/src/main/resources/utbot-api.jar differ diff --git a/utbot-spring-analyzer/build.gradle.kts b/utbot-spring-analyzer/build.gradle.kts new file mode 100644 index 0000000000..9f12641120 --- /dev/null +++ b/utbot-spring-analyzer/build.gradle.kts @@ -0,0 +1,61 @@ +import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer + +val springBootVersion: String by rootProject +val rdVersion: String by rootProject +val commonsLoggingVersion: String by rootProject +val kotlinLoggingVersion: String by rootProject +val commonsIOVersion: String by rootProject + +plugins { + id("com.github.johnrengelman.shadow") version "7.1.2" + id("java") + application +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +val shadowJarConfiguration: Configuration by configurations.creating {} +configurations.implementation.get().extendsFrom(shadowJarConfiguration) + +dependencies { + // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot + compileOnly("org.springframework.boot:spring-boot:$springBootVersion") + compileOnly("io.github.microutils:kotlin-logging:$kotlinLoggingVersion") + + fun ModuleDependency.excludeSlf4jApi() = exclude(group = "org.slf4j", module = "slf4j-api") + + // TODO stop putting dependencies that are only used in SpringAnalyzerProcess into shadow jar + implementation(project(":utbot-rd")) { excludeSlf4jApi() } + implementation(project(":utbot-core")) { excludeSlf4jApi() } + implementation(project(":utbot-framework-api")) { excludeSlf4jApi() } + + runtimeOnly(project(":utbot-spring-commons")) { excludeSlf4jApi() } + implementation(project(":utbot-spring-commons-api")) { excludeSlf4jApi() } + + implementation("com.jetbrains.rd:rd-framework:$rdVersion") { excludeSlf4jApi() } + implementation("com.jetbrains.rd:rd-core:$rdVersion") { excludeSlf4jApi() } + implementation("commons-logging:commons-logging:$commonsLoggingVersion") { excludeSlf4jApi() } +} + +application { + mainClass.set("org.utbot.spring.process.SpringAnalyzerProcessMainKt") +} + +tasks.shadowJar { + isZip64 = true + + transform(Log4j2PluginsCacheFileTransformer::class.java) + archiveFileName.set("utbot-spring-analyzer-shadow.jar") +} + +val springAnalyzerJar: Configuration by configurations.creating { + isCanBeResolved = false + isCanBeConsumed = true +} + +artifacts { + add(springAnalyzerJar.name, tasks.shadowJar) +} diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzer/SpringApplicationAnalyzer.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzer/SpringApplicationAnalyzer.kt new file mode 100644 index 0000000000..ba81bff3c1 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzer/SpringApplicationAnalyzer.kt @@ -0,0 +1,28 @@ +package org.utbot.spring.analyzer + +import org.utbot.spring.api.provider.InstantiationSettings +import org.utbot.spring.api.ApplicationData +import org.utbot.spring.api.provider.SpringApiProviderFacade +import org.utbot.spring.exception.UtBotSpringShutdownException +import org.utbot.spring.generated.BeanDefinitionData +import org.utbot.spring.utils.SourceFinder + +class SpringApplicationAnalyzer { + + fun getBeanDefinitions(applicationData: ApplicationData): Array { + // TODO: get rid of SourceFinder + val configurationClasses = SourceFinder(applicationData).findSources() + val instantiationSettings = InstantiationSettings( + configurationClasses, + applicationData.springSettings.profiles.toTypedArray(), + ) + + return SpringApiProviderFacade.getInstance(this::class.java.classLoader) + .useMostSpecificNonFailingApi(instantiationSettings) { springApi -> + UtBotSpringShutdownException + .catch { springApi.getOrLoadSpringApplicationContext() } + .beanDefinitions + .toTypedArray() + }.result.getOrThrow() + } +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/api/ApplicationData.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/api/ApplicationData.kt new file mode 100644 index 0000000000..b776eb9c76 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/api/ApplicationData.kt @@ -0,0 +1,7 @@ +package org.utbot.spring.api + +import org.utbot.framework.plugin.api.SpringSettings.* + +class ApplicationData( + val springSettings: PresentSpringSettings +) diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/config/TestApplicationConfiguration.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/config/TestApplicationConfiguration.kt new file mode 100644 index 0000000000..5fdee855f6 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/config/TestApplicationConfiguration.kt @@ -0,0 +1,15 @@ +package org.utbot.spring.config + +import org.springframework.beans.factory.config.BeanFactoryPostProcessor +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.ImportResource +import org.utbot.spring.postProcessors.UtBotBeanFactoryPostProcessor + +@Configuration +@ImportResource +open class TestApplicationConfiguration { + + @Bean + open fun utBotBeanFactoryPostProcessor(): BeanFactoryPostProcessor = UtBotBeanFactoryPostProcessor +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/configurators/ApplicationConfigurationType.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/configurators/ApplicationConfigurationType.kt new file mode 100644 index 0000000000..e48be8751c --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/configurators/ApplicationConfigurationType.kt @@ -0,0 +1,11 @@ +package org.utbot.spring.configurators + +enum class ApplicationConfigurationType { + + XmlConfiguration, + + /** + * Any Java-based configuration, including both simple @Configuration and @SpringBootApplication + */ + JavaConfiguration, +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/configurators/XmlConfigurationParser.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/configurators/XmlConfigurationParser.kt new file mode 100644 index 0000000000..71c75c0833 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/configurators/XmlConfigurationParser.kt @@ -0,0 +1,43 @@ +package org.utbot.spring.configurators + +import org.w3c.dom.Document +import org.w3c.dom.Element +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult + +class XmlConfigurationParser(private val userXmlFilePath: String, private val fakeXmlFilePath: String) { + + fun fillFakeApplicationXml() { + val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder() + val doc = builder.parse(userXmlFilePath) + + // Property placeholders may contain file names relative to user project, + // they will not be found in ours. We import all properties using another approach. + deletePropertyPlaceholders(doc) + writeXmlFile(doc) + } + + private fun deletePropertyPlaceholders(doc: Document) { + val elements = doc.getElementsByTagName("context:property-placeholder") + val elementsCount = elements.length + + // Xml file may contain several property placeholders: + // see https://stackoverflow.com/questions/26618400/how-to-use-multiple-property-placeholder-in-a-spring-xml-file + for (i in 0 until elementsCount) { + val element = elements.item(i) as Element + element.parentNode.removeChild(element) + } + + doc.normalize() + } + + private fun writeXmlFile(doc: Document) { + val tFormer = TransformerFactory.newInstance().newTransformer() + val source = DOMSource(doc) + val destination = StreamResult(fakeXmlFilePath) + + tFormer.transform(source, destination) + } +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/exception/UtBotSpringShutdownException.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/exception/UtBotSpringShutdownException.kt new file mode 100644 index 0000000000..6d80ba9166 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/exception/UtBotSpringShutdownException.kt @@ -0,0 +1,34 @@ +package org.utbot.spring.exception + +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import org.utbot.spring.generated.BeanDefinitionData + +private val logger = getLogger() + +/** + * Use this exception to shutdown the application + * when all required analysis actions are completed. + */ +class UtBotSpringShutdownException( + message: String, + val beanDefinitions: List +): RuntimeException(message) { + companion object { + fun catch(block: () -> Unit): UtBotSpringShutdownException { + try { + block() + throw IllegalStateException("UtBotSpringShutdownException has not been thrown") + } catch (e: Throwable) { + // Spring sometimes wraps exceptions in other exceptions, so we go over + // all the causes to determine if UtBotSpringShutdownException was thrown + for(cause in generateSequence(e) { it.cause }) + if (cause is UtBotSpringShutdownException) { + logger.info { "UtBotSpringShutdownException has been successfully caught" } + return cause + } + throw e + } + } + } +} diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerProcessModel.Generated.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerProcessModel.Generated.kt new file mode 100644 index 0000000000..066048b61e --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerProcessModel.Generated.kt @@ -0,0 +1,348 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.spring.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [SpringAnalyzerModel.kt:8] + */ +class SpringAnalyzerProcessModel private constructor( + private val _analyze: RdCall +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + serializers.register(SpringAnalyzerParams) + serializers.register(BeanAdditionalData) + serializers.register(BeanDefinitionData) + serializers.register(SpringAnalyzerResult) + } + + + @JvmStatic + @JvmName("internalCreateModel") + @Deprecated("Use create instead", ReplaceWith("create(lifetime, protocol)")) + internal fun createModel(lifetime: Lifetime, protocol: IProtocol): SpringAnalyzerProcessModel { + @Suppress("DEPRECATION") + return create(lifetime, protocol) + } + + @JvmStatic + @Deprecated("Use protocol.springAnalyzerProcessModel or revise the extension scope instead", ReplaceWith("protocol.springAnalyzerProcessModel")) + fun create(lifetime: Lifetime, protocol: IProtocol): SpringAnalyzerProcessModel { + SpringAnalyzerRoot.register(protocol.serializers) + + return SpringAnalyzerProcessModel() + } + + + const val serializationHash = 8094902537230457267L + + } + override val serializersOwner: ISerializersOwner get() = SpringAnalyzerProcessModel + override val serializationHash: Long get() = SpringAnalyzerProcessModel.serializationHash + + //fields + val analyze: RdCall get() = _analyze + //methods + //initializer + init { + _analyze.async = true + } + + init { + bindableChildren.add("analyze" to _analyze) + } + + //secondary constructor + private constructor( + ) : this( + RdCall(SpringAnalyzerParams, SpringAnalyzerResult) + ) + + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SpringAnalyzerProcessModel (") + printer.indent { + print("analyze = "); _analyze.print(printer); println() + } + printer.print(")") + } + //deepClone + override fun deepClone(): SpringAnalyzerProcessModel { + return SpringAnalyzerProcessModel( + _analyze.deepClonePolymorphic() + ) + } + //contexts +} +val IProtocol.springAnalyzerProcessModel get() = getOrCreateExtension(SpringAnalyzerProcessModel::class) { @Suppress("DEPRECATION") SpringAnalyzerProcessModel.create(lifetime, this) } + + + +/** + * #### Generated from [SpringAnalyzerModel.kt:13] + */ +data class BeanAdditionalData ( + val factoryMethodName: String, + val parameterTypes: List, + val configClassFqn: String +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = BeanAdditionalData::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): BeanAdditionalData { + val factoryMethodName = buffer.readString() + val parameterTypes = buffer.readList { buffer.readString() } + val configClassFqn = buffer.readString() + return BeanAdditionalData(factoryMethodName, parameterTypes, configClassFqn) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: BeanAdditionalData) { + buffer.writeString(value.factoryMethodName) + buffer.writeList(value.parameterTypes) { v -> buffer.writeString(v) } + buffer.writeString(value.configClassFqn) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as BeanAdditionalData + + if (factoryMethodName != other.factoryMethodName) return false + if (parameterTypes != other.parameterTypes) return false + if (configClassFqn != other.configClassFqn) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + factoryMethodName.hashCode() + __r = __r*31 + parameterTypes.hashCode() + __r = __r*31 + configClassFqn.hashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("BeanAdditionalData (") + printer.indent { + print("factoryMethodName = "); factoryMethodName.print(printer); println() + print("parameterTypes = "); parameterTypes.print(printer); println() + print("configClassFqn = "); configClassFqn.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [SpringAnalyzerModel.kt:19] + */ +data class BeanDefinitionData ( + val beanName: String, + val beanTypeFqn: String, + val additionalData: BeanAdditionalData? +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = BeanDefinitionData::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): BeanDefinitionData { + val beanName = buffer.readString() + val beanTypeFqn = buffer.readString() + val additionalData = buffer.readNullable { BeanAdditionalData.read(ctx, buffer) } + return BeanDefinitionData(beanName, beanTypeFqn, additionalData) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: BeanDefinitionData) { + buffer.writeString(value.beanName) + buffer.writeString(value.beanTypeFqn) + buffer.writeNullable(value.additionalData) { BeanAdditionalData.write(ctx, buffer, it) } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as BeanDefinitionData + + if (beanName != other.beanName) return false + if (beanTypeFqn != other.beanTypeFqn) return false + if (additionalData != other.additionalData) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + beanName.hashCode() + __r = __r*31 + beanTypeFqn.hashCode() + __r = __r*31 + if (additionalData != null) additionalData.hashCode() else 0 + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("BeanDefinitionData (") + printer.indent { + print("beanName = "); beanName.print(printer); println() + print("beanTypeFqn = "); beanTypeFqn.print(printer); println() + print("additionalData = "); additionalData.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [SpringAnalyzerModel.kt:9] + */ +data class SpringAnalyzerParams ( + val springSettings: ByteArray +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = SpringAnalyzerParams::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SpringAnalyzerParams { + val springSettings = buffer.readByteArray() + return SpringAnalyzerParams(springSettings) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SpringAnalyzerParams) { + buffer.writeByteArray(value.springSettings) + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as SpringAnalyzerParams + + if (!(springSettings contentEquals other.springSettings)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + springSettings.contentHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SpringAnalyzerParams (") + printer.indent { + print("springSettings = "); springSettings.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} + + +/** + * #### Generated from [SpringAnalyzerModel.kt:25] + */ +data class SpringAnalyzerResult ( + val beanDefinitions: Array +) : IPrintable { + //companion + + companion object : IMarshaller { + override val _type: KClass = SpringAnalyzerResult::class + + @Suppress("UNCHECKED_CAST") + override fun read(ctx: SerializationCtx, buffer: AbstractBuffer): SpringAnalyzerResult { + val beanDefinitions = buffer.readArray {BeanDefinitionData.read(ctx, buffer)} + return SpringAnalyzerResult(beanDefinitions) + } + + override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: SpringAnalyzerResult) { + buffer.writeArray(value.beanDefinitions) { BeanDefinitionData.write(ctx, buffer, it) } + } + + + } + //fields + //methods + //initializer + //secondary constructor + //equals trait + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || other::class != this::class) return false + + other as SpringAnalyzerResult + + if (!(beanDefinitions contentDeepEquals other.beanDefinitions)) return false + + return true + } + //hash code trait + override fun hashCode(): Int { + var __r = 0 + __r = __r*31 + beanDefinitions.contentDeepHashCode() + return __r + } + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SpringAnalyzerResult (") + printer.indent { + print("beanDefinitions = "); beanDefinitions.print(printer); println() + } + printer.print(")") + } + //deepClone + //contexts +} diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerRoot.Generated.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerRoot.Generated.kt new file mode 100644 index 0000000000..4a4e0649e0 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/generated/SpringAnalyzerRoot.Generated.kt @@ -0,0 +1,59 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") +package org.utbot.spring.generated + +import com.jetbrains.rd.framework.* +import com.jetbrains.rd.framework.base.* +import com.jetbrains.rd.framework.impl.* + +import com.jetbrains.rd.util.lifetime.* +import com.jetbrains.rd.util.reactive.* +import com.jetbrains.rd.util.string.* +import com.jetbrains.rd.util.* +import kotlin.time.Duration +import kotlin.reflect.KClass +import kotlin.jvm.JvmStatic + + + +/** + * #### Generated from [SpringAnalyzerModel.kt:6] + */ +class SpringAnalyzerRoot private constructor( +) : RdExtBase() { + //companion + + companion object : ISerializersOwner { + + override fun registerSerializersCore(serializers: ISerializers) { + SpringAnalyzerRoot.register(serializers) + SpringAnalyzerProcessModel.register(serializers) + } + + + + + + const val serializationHash = -4315357569975275049L + + } + override val serializersOwner: ISerializersOwner get() = SpringAnalyzerRoot + override val serializationHash: Long get() = SpringAnalyzerRoot.serializationHash + + //fields + //methods + //initializer + //secondary constructor + //equals trait + //hash code trait + //pretty print + override fun print(printer: PrettyPrinter) { + printer.println("SpringAnalyzerRoot (") + printer.print(")") + } + //deepClone + override fun deepClone(): SpringAnalyzerRoot { + return SpringAnalyzerRoot( + ) + } + //contexts +} diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/loggers/RDApacheCommonsLogFactory.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/loggers/RDApacheCommonsLogFactory.kt new file mode 100644 index 0000000000..0c78cb87a1 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/loggers/RDApacheCommonsLogFactory.kt @@ -0,0 +1,46 @@ +package org.utbot.spring.loggers + +import com.jetbrains.rd.util.LogLevel +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import org.apache.commons.logging.Log +import org.apache.commons.logging.impl.LogFactoryImpl +import org.utbot.spring.exception.UtBotSpringShutdownException + +@Suppress("unused") // used via -Dorg.apache.commons.logging.LogFactory=org.utbot.spring.loggers.RDApacheCommonsLogFactory +class RDApacheCommonsLogFactory : LogFactoryImpl() { + override fun getInstance(name: String): Log { + val logger = getLogger(category = name) + return object : Log { + private fun log(level: LogLevel, message: Any?, throwable: Throwable?) { + if (throwable is UtBotSpringShutdownException) { + // avoid polluting logs with stack trace of expected exception + logger.info { message } + logger.info { "${throwable::class.java.name}: ${throwable.message}" } + } else + logger.log(level, message, throwable) + } + private fun isEnabled(level: LogLevel) = logger.isEnabled(level) + + override fun trace(message: Any?) = log(LogLevel.Trace, message, throwable = null) + override fun trace(message: Any?, t: Throwable?) = log(LogLevel.Trace, message, throwable = t) + override fun debug(message: Any?) = log(LogLevel.Debug, message, throwable = null) + override fun debug(message: Any?, t: Throwable?) = log(LogLevel.Debug, message, throwable = t) + override fun info(message: Any?) = log(LogLevel.Info, message, throwable = null) + override fun info(message: Any?, t: Throwable?) = log(LogLevel.Info, message, throwable = t) + override fun warn(message: Any?) = log(LogLevel.Warn, message, throwable = null) + override fun warn(message: Any?, t: Throwable?) = log(LogLevel.Warn, message, throwable = t) + override fun error(message: Any?) = log(LogLevel.Error, message, throwable = null) + override fun error(message: Any?, t: Throwable?) = log(LogLevel.Error, message, throwable = t) + override fun fatal(message: Any?) = log(LogLevel.Fatal, message, throwable = null) + override fun fatal(message: Any?, t: Throwable?) = log(LogLevel.Fatal, message, throwable = t) + + override fun isTraceEnabled() = isEnabled(LogLevel.Trace) + override fun isDebugEnabled() = isEnabled(LogLevel.Debug) + override fun isInfoEnabled() = isEnabled(LogLevel.Info) + override fun isErrorEnabled() = isEnabled(LogLevel.Error) + override fun isFatalEnabled() = isEnabled(LogLevel.Fatal) + override fun isWarnEnabled() = isEnabled(LogLevel.Warn) + } + } +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt new file mode 100644 index 0000000000..705d0503f4 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt @@ -0,0 +1,92 @@ +package org.utbot.spring.postProcessors + +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import com.jetbrains.rd.util.warn +import org.springframework.beans.factory.BeanCreationException +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition +import org.springframework.beans.factory.config.BeanFactoryPostProcessor +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory +import org.springframework.beans.factory.support.BeanDefinitionRegistry +import org.springframework.core.PriorityOrdered +import org.springframework.core.type.StandardMethodMetadata +import org.utbot.spring.exception.UtBotSpringShutdownException +import org.utbot.spring.generated.BeanAdditionalData +import org.utbot.spring.generated.BeanDefinitionData + +val logger = getLogger() + +object UtBotBeanFactoryPostProcessor : BeanFactoryPostProcessor, PriorityOrdered { + /** + * Sets the priority of post processor to highest to avoid side effects from others. + */ + override fun getOrder(): Int = PriorityOrdered.HIGHEST_PRECEDENCE + + override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) { + logger.info { "Started post-processing bean factory in UtBot" } + + val beanDefinitions = findBeanDefinitions(beanFactory) + logger.info { "Detected ${beanDefinitions.size} bean qualified names" } + + logger.info { "Finished post-processing bean factory in UtBot" } + + destroyBeanDefinitions(beanFactory) + throw UtBotSpringShutdownException("Finished post-processing bean factory in UtBot", beanDefinitions) + } + + private fun findBeanDefinitions(beanFactory: ConfigurableListableBeanFactory): List = + beanFactory.beanDefinitionNames + .mapNotNull { getBeanDefinitionData(beanFactory, it) } + .filterNot { + it.beanTypeFqn.startsWith("org.utbot.spring") || + UtBotBeanFactoryPostProcessor.javaClass.simpleName.equals(it.beanName, ignoreCase = true) + } + + private fun getBeanDefinitionData(beanFactory: ConfigurableListableBeanFactory, beanName: String): BeanDefinitionData? { + val beanDefinition = beanFactory.getBeanDefinition(beanName) + + var beanAdditionalData: BeanAdditionalData? = null + val beanTypeFqn = if (beanDefinition is AnnotatedBeanDefinition) { + if (beanDefinition.factoryMethodMetadata == null) { + // there's no factoryMethod so bean is defined with @Component-like annotation rather than @Bean annotation + // same approach isn't applicable for @Bean beans, because for them, it returns name of @Configuration class + beanDefinition.metadata.className + .also { fqn -> + logger.info { "Got $fqn as metadata.className for @Component-like bean: $beanName" } + } + } else try { + val parameterTypes = + (beanDefinition.factoryMethodMetadata as? StandardMethodMetadata) + ?.introspectedMethod + ?.parameterTypes + ?.map { it.name } + ?: emptyList() + + beanAdditionalData = BeanAdditionalData( + factoryMethodName = beanDefinition.factoryMethodMetadata.methodName, + parameterTypes = parameterTypes, + configClassFqn = beanDefinition.factoryMethodMetadata.declaringClassName + ) + + // we determine a real return type later in [UtTestsDialogProcessor.createTests] + beanDefinition.factoryMethodMetadata.returnTypeName + } catch (e: BeanCreationException) { + logger.warn { "Failed to get bean: $beanName" } + null + } + } else { + beanDefinition.beanClassName.also { fqn -> + logger.info { "Got $fqn as beanClassName for XML-like bean: $beanName" } + } + } + + return beanTypeFqn?.let { BeanDefinitionData(beanName, beanTypeFqn, beanAdditionalData) } + } + + private fun destroyBeanDefinitions(beanFactory: ConfigurableListableBeanFactory) { + for (beanDefinitionName in beanFactory.beanDefinitionNames) { + val beanRegistry = beanFactory as BeanDefinitionRegistry + beanRegistry.removeBeanDefinition(beanDefinitionName) + } + } +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt new file mode 100644 index 0000000000..044ef0c4a9 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt @@ -0,0 +1,99 @@ +package org.utbot.spring.process + +import com.jetbrains.rd.util.lifetime.LifetimeDefinition +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +import org.utbot.common.JarUtils +import org.utbot.common.getPid +import org.utbot.framework.UtSettings +import org.utbot.framework.plugin.services.WorkingDirService +import org.utbot.framework.process.AbstractRDProcessCompanion +import org.utbot.rd.ProcessWithRdServer +import org.utbot.rd.exceptions.InstantProcessDeathException +import org.utbot.rd.generated.LoggerModel +import org.utbot.rd.generated.loggerModel +import org.utbot.rd.loggers.setup +import org.utbot.rd.onSchedulerBlocking +import org.utbot.rd.startBlocking +import org.utbot.rd.startUtProcessWithRdServer +import org.utbot.rd.terminateOnException +import org.utbot.spring.generated.SpringAnalyzerParams +import org.utbot.spring.generated.SpringAnalyzerProcessModel +import org.utbot.spring.generated.SpringAnalyzerResult +import org.utbot.spring.generated.springAnalyzerProcessModel +import java.io.File +import org.utbot.framework.plugin.api.SpringSettings.* +import org.utbot.framework.process.kryo.KryoHelper + +class SpringAnalyzerProcessInstantDeathException : + InstantProcessDeathException( + UtSettings.springAnalyzerProcessDebugPort, + UtSettings.runSpringAnalyzerProcessWithDebug + ) + +private val logger = KotlinLogging.logger {} + +private var classpathArgs = listOf() + +private const val SPRING_ANALYZER_JAR_FILENAME = "utbot-spring-analyzer-shadow.jar" + +private val springAnalyzerJarFile = + JarUtils.extractJarFileFromResources( + jarFileName = SPRING_ANALYZER_JAR_FILENAME, + jarResourcePath = "lib/$SPRING_ANALYZER_JAR_FILENAME", + targetDirectoryName = "spring-analyzer" + ) + +class SpringAnalyzerProcess private constructor( + rdProcess: ProcessWithRdServer +) : ProcessWithRdServer by rdProcess { + + companion object : AbstractRDProcessCompanion( + debugPort = UtSettings.springAnalyzerProcessDebugPort, + runWithDebug = UtSettings.runSpringAnalyzerProcessWithDebug, + suspendExecutionInDebugMode = UtSettings.suspendSpringAnalyzerProcessExecutionInDebugMode, + processSpecificCommandLineArgs = { + listOf("-Dorg.apache.commons.logging.LogFactory=org.utbot.spring.loggers.RDApacheCommonsLogFactory") + classpathArgs + } + ) { + fun createBlocking(classpath: List) = runBlocking { SpringAnalyzerProcess(classpath) } + + suspend operator fun invoke(classpathItems: List): SpringAnalyzerProcess = + LifetimeDefinition().terminateOnException { lifetime -> + val extendedClasspath = listOf(springAnalyzerJarFile.path) + classpathItems + + val rdProcess = startUtProcessWithRdServer(lifetime) { port -> + classpathArgs = listOf( + "-cp", + extendedClasspath.joinToString(File.pathSeparator), + "org.utbot.spring.process.SpringAnalyzerProcessMainKt" + ) + val cmd = obtainProcessCommandLine(port) + val process = ProcessBuilder(cmd) + .directory(WorkingDirService.provide().toFile()) + .start() + + logger.info { "Spring Analyzer process started with PID = ${process.getPid}" } + + if (!process.isAlive) throw SpringAnalyzerProcessInstantDeathException() + + process + } + rdProcess.awaitProcessReady() + val proc = SpringAnalyzerProcess(rdProcess) + proc.loggerModel.setup(logger, proc.lifetime) + return proc + } + } + + private val springAnalyzerModel: SpringAnalyzerProcessModel = onSchedulerBlocking { protocol.springAnalyzerProcessModel } + private val loggerModel: LoggerModel = onSchedulerBlocking { protocol.loggerModel } + private val kryoHelper = KryoHelper(lifetime) + + fun getBeanDefinitions( + springSettings: PresentSpringSettings + ): SpringAnalyzerResult { + val params = SpringAnalyzerParams(kryoHelper.writeObject(springSettings)) + return springAnalyzerModel.analyze.startBlocking(params) + } +} diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcessMain.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcessMain.kt new file mode 100644 index 0000000000..8a1d72f8d2 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcessMain.kt @@ -0,0 +1,55 @@ +package org.utbot.spring.process + +import com.jetbrains.rd.framework.IProtocol +import com.jetbrains.rd.util.Logger +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.reactive.adviseOnce +import org.utbot.common.AbstractSettings +import org.utbot.framework.process.kryo.KryoHelper +import org.utbot.rd.ClientProtocolBuilder +import org.utbot.rd.IdleWatchdog +import org.utbot.rd.RdSettingsContainerFactory +import org.utbot.rd.generated.loggerModel +import org.utbot.rd.generated.settingsModel +import org.utbot.rd.loggers.UtRdRemoteLoggerFactory +import org.utbot.spring.analyzer.SpringApplicationAnalyzer +import org.utbot.spring.api.ApplicationData +import org.utbot.spring.generated.SpringAnalyzerProcessModel +import org.utbot.spring.generated.SpringAnalyzerResult +import org.utbot.spring.generated.springAnalyzerProcessModel +import java.io.File +import kotlin.time.Duration.Companion.seconds + +private val messageFromMainTimeoutMillis = 120.seconds +private val logger = getLogger() + +@Suppress("unused") +object SpringAnalyzerProcessMain + +suspend fun main(args: Array) = + ClientProtocolBuilder().withProtocolTimeout(messageFromMainTimeoutMillis).start(args) { + loggerModel.initRemoteLogging.adviseOnce(lifetime) { + Logger.set(Lifetime.Eternal, UtRdRemoteLoggerFactory(loggerModel)) + logger.info { "-----------------------------------------------------------------------" } + logger.info { "------------------NEW SPRING ANALYZER PROCESS STARTED------------------" } + logger.info { "-----------------------------------------------------------------------" } + } + AbstractSettings.setupFactory(RdSettingsContainerFactory(protocol.settingsModel)) + springAnalyzerProcessModel.setup(it, protocol) + } + +private fun SpringAnalyzerProcessModel.setup(watchdog: IdleWatchdog, realProtocol: IProtocol) { + val kryoHelper = KryoHelper(realProtocol.lifetime) + + watchdog.measureTimeForActiveCall(analyze, "Analyzing Spring Application") { params -> + val applicationData = ApplicationData( + kryoHelper.readObject(params.springSettings) + ) + + SpringAnalyzerResult( + SpringApplicationAnalyzer().getBeanDefinitions(applicationData) + ) + } +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/PathsUtils.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/PathsUtils.kt new file mode 100644 index 0000000000..7fe7dfba42 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/PathsUtils.kt @@ -0,0 +1,9 @@ +package org.utbot.spring.utils + +import kotlin.io.path.Path + +object PathsUtils { + const val EMPTY_PATH = "" + + fun createFakeFilePath(fileName: String): String = "fake_${Path(fileName).fileName}" +} diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/SourceFinder.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/SourceFinder.kt new file mode 100644 index 0000000000..027dabbdc5 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/SourceFinder.kt @@ -0,0 +1,46 @@ +package org.utbot.spring.utils + +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import org.springframework.context.annotation.ImportResource +import org.utbot.common.patchAnnotation +import org.utbot.framework.plugin.api.SpringConfiguration.* +import org.utbot.spring.api.ApplicationData +import org.utbot.spring.config.TestApplicationConfiguration +import java.nio.file.Path +import kotlin.io.path.Path + +private val logger = getLogger() + +class SourceFinder( + private val applicationData: ApplicationData +) { + private val classLoader: ClassLoader = this::class.java.classLoader + + fun findSources(): Array> = + when (val config = applicationData.springSettings.configuration) { + is JavaBasedConfiguration -> { + logger.info { "Using java Spring configuration" } + arrayOf( + TestApplicationConfiguration::class.java, + classLoader.loadClass(config.configBinaryName) + ) + } + + is XMLConfiguration -> { + logger.info { "Using xml Spring configuration" } + + // Put `applicationData.configurationFile` in `@ImportResource` of `TestApplicationConfiguration` + patchImportResourceAnnotation(Path(config.absolutePath).fileName) + + arrayOf(TestApplicationConfiguration::class.java) + } + } + + private fun patchImportResourceAnnotation(userXmlFilePath: Path) = + patchAnnotation( + annotation = TestApplicationConfiguration::class.java.getAnnotation(ImportResource::class.java), + property = "value", + newValue = arrayOf(String.format("classpath:%s", "$userXmlFilePath")) + ) +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/TempFileManager.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/TempFileManager.kt new file mode 100644 index 0000000000..14c8e96a85 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/TempFileManager.kt @@ -0,0 +1,37 @@ +package org.utbot.spring.utils + +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import java.io.File +import java.io.IOException + +private val logger = getLogger() + +class TempFileManager(private val fakeFilesList: List) { + + fun createTempFiles() { + for (fileName in fakeFilesList) { + val fakeXmlFileAbsolutePath = PathsUtils.createFakeFilePath(fileName) + + try { + File(fakeXmlFileAbsolutePath).createNewFile() + } catch (e: IOException) { + logger.info { "Fake xml file creation failed with exception $e" } + } + + } + } + + fun deleteTempFiles() { + for (fileName in fakeFilesList) { + val fakeXmlFileAbsolutePath = PathsUtils.createFakeFilePath(fileName) + + try { + File(fakeXmlFileAbsolutePath).delete() + } catch (e: IOException) { + logger.info { "Fake xml file deletion failed with exception $e" } + } + + } + } +} \ No newline at end of file diff --git a/utbot-spring-commons-api/build.gradle.kts b/utbot-spring-commons-api/build.gradle.kts new file mode 100644 index 0000000000..76786b8cb9 --- /dev/null +++ b/utbot-spring-commons-api/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("java") +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} \ No newline at end of file diff --git a/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/SpringApi.kt b/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/SpringApi.kt new file mode 100644 index 0000000000..c856b00c92 --- /dev/null +++ b/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/SpringApi.kt @@ -0,0 +1,52 @@ +package org.utbot.spring.api + +import java.net.URLClassLoader + +//TODO: `userSourcesClassLoader` must not be passed as a method argument, requires refactoring +interface SpringApi { + /** + * NOTE! [Any] return type is used here because Spring itself may not be on the classpath of the API user class loader + * + * @throws [UTSpringContextLoadingException] as a wrapper for all runtime exceptions + */ + fun getOrLoadSpringApplicationContext(): Any + + /** + * NOTE! [Any] return type is used here because Spring itself may not be on the classpath of the API user class loader + * + * Besides that `entityManager` depending on libraries used by user may be either `javax.persistence.EntityManager` + * or `jakarta.persistence.EntityManager` and they don't have common super type other than `Object`. + */ + fun getEntityManager(): Any + + fun getBean(beanName: String): Any + + fun getDependenciesForBean(beanName: String, userSourcesClassLoader: URLClassLoader): Set + + fun resetBean(beanName: String) + + fun resolveRepositories(beanNames: Set, userSourcesClassLoader: URLClassLoader): Set + + /** + * NOTE! Should be called on one thread with method under test and value constructor, + * because transactions are bound to threads + */ + fun beforeTestMethod() + + /** + * NOTE! Should be called on one thread with method under test and value constructor, + * because transactions are bound to threads + */ + fun afterTestMethod() + + /** + * Time-consuming operation that fully resets Spring context + */ + fun resetContext() +} + +data class RepositoryDescription( + val beanName: String, + val repositoryName: String, + val entityName: String, +) \ No newline at end of file diff --git a/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/UTSpringContextLoadingException.kt b/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/UTSpringContextLoadingException.kt new file mode 100644 index 0000000000..d8bbf2b716 --- /dev/null +++ b/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/UTSpringContextLoadingException.kt @@ -0,0 +1,11 @@ +package org.utbot.spring.api + +/** + * Used primarily to let code generation distinguish between + * parts of stack trace inside UTBot (including RD, etc.) + * and parts of stack trace inside Spring and user application. + */ +class UTSpringContextLoadingException(override val cause: Throwable) : Exception( + "Failed to load Spring application context", + cause +) diff --git a/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/provider/InstantiationSettings.kt b/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/provider/InstantiationSettings.kt new file mode 100644 index 0000000000..ea2146a327 --- /dev/null +++ b/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/provider/InstantiationSettings.kt @@ -0,0 +1,9 @@ +package org.utbot.spring.api.provider + +class InstantiationSettings( + val configurationClasses: Array>, + val profiles: Array, +) { + override fun toString(): String = + "InstantiationSettings(configurationClasses=${configurationClasses.contentToString()}, profiles=${profiles.contentToString()})" +} \ No newline at end of file diff --git a/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/provider/SpringApiProviderFacade.kt b/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/provider/SpringApiProviderFacade.kt new file mode 100644 index 0000000000..49b90d2aef --- /dev/null +++ b/utbot-spring-commons-api/src/main/kotlin/org/utbot/spring/api/provider/SpringApiProviderFacade.kt @@ -0,0 +1,41 @@ +package org.utbot.spring.api.provider + +import org.utbot.spring.api.SpringApi + +/** + * Stateless provider of independent [SpringApi] instances that do not have shared state, + * meaning each [SpringApi] instance will when needed start its own Spring Application. + */ +interface SpringApiProviderFacade { + fun provideMostSpecificAvailableApi(instantiationSettings: InstantiationSettings): ProviderResult + + /** + * [apiUser] is consequently invoked on all available (on the classpath) + * [SpringApi] types from most specific (e.g. Spring Boot) to least specific (e.g. Pure Spring) + * until it executes without throwing exception, then obtained result is returned. + * + * All exceptions are collected into [ProviderResult.exceptions]. + */ + fun useMostSpecificNonFailingApi( + instantiationSettings: InstantiationSettings, + apiUser: (SpringApi) -> T + ): ProviderResult + + companion object { + fun getInstance(classLoader: ClassLoader): SpringApiProviderFacade = + classLoader + .loadClass("org.utbot.spring.provider.SpringApiProviderFacadeImpl") + .getConstructor() + .newInstance() as SpringApiProviderFacade + } + + /** + * [result] can be a [Result.success] while [exceptions] is not empty, + * if we failed to use most specific [SpringApi] available (e.g. SpringBoot), but + * were able to successfully fall back to less specific [SpringApi] (e.g. PureSpring). + */ + class ProviderResult( + val result: Result, + val exceptions: List + ) +} \ No newline at end of file diff --git a/utbot-spring-commons/build.gradle.kts b/utbot-spring-commons/build.gradle.kts new file mode 100644 index 0000000000..da6f100f1d --- /dev/null +++ b/utbot-spring-commons/build.gradle.kts @@ -0,0 +1,58 @@ +import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer + +val springVersion: String by rootProject +val springSecurityVersion: String by rootProject +val springBootVersion: String by rootProject +val javaxVersion: String by rootProject +val jakartaVersion: String by rootProject +val rdVersion: String by rootProject + +plugins { + id("com.github.johnrengelman.shadow") version "7.1.2" + id("java") +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +dependencies { + implementation(project(":utbot-spring-commons-api")) + + + // these dependencies are compilieOnly, because they should + // already be present on the classpath in all utbot processes + compileOnly(project(":utbot-core")) + compileOnly("com.jetbrains.rd:rd-core:$rdVersion") { exclude(group = "org.slf4j", module = "slf4j-api") } + + + // these dependencies are compilieOnly, because they should + // be picked up from user classpath if they are present there + compileOnly("org.springframework.boot:spring-boot:$springBootVersion") + compileOnly("org.springframework.boot:spring-boot-test-autoconfigure:$springBootVersion") + compileOnly("org.springframework:spring-test:$springVersion") + compileOnly("org.springframework:spring-tx:$springVersion") + compileOnly("org.springframework:spring-web:$springVersion") + compileOnly("org.springframework.security:spring-security-test:$springSecurityVersion") + compileOnly("org.springframework.data:spring-data-commons:$springBootVersion") + + compileOnly("javax.persistence:javax.persistence-api:$javaxVersion") + compileOnly("jakarta.persistence:jakarta.persistence-api:$jakartaVersion") +} + +tasks.shadowJar { + isZip64 = true + + transform(Log4j2PluginsCacheFileTransformer::class.java) + archiveFileName.set("utbot-spring-commons-shadow.jar") +} + +val springCommonsJar: Configuration by configurations.creating { + isCanBeResolved = false + isCanBeConsumed = true +} + +artifacts { + add(springCommonsJar.name, tasks.shadowJar) +} diff --git a/utbot-spring-commons/src/main/kotlin/org/utbot/spring/SpringApiImpl.kt b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/SpringApiImpl.kt new file mode 100644 index 0000000000..9bf806e7bb --- /dev/null +++ b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/SpringApiImpl.kt @@ -0,0 +1,181 @@ +package org.utbot.spring + +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.warn +import org.springframework.beans.factory.support.BeanDefinitionRegistry +import org.springframework.context.ConfigurableApplicationContext +import org.springframework.data.repository.Repository +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.TestContextManager +import org.utbot.common.hasOnClasspath +import org.utbot.common.patchAnnotation +import org.utbot.spring.api.SpringApi +import org.utbot.spring.api.RepositoryDescription +import org.utbot.spring.api.UTSpringContextLoadingException +import org.utbot.spring.api.provider.InstantiationSettings +import org.utbot.spring.dummy.DummySpringIntegrationTestClass +import org.utbot.spring.utils.DependencyUtils.isSpringDataOnClasspath +import org.utbot.spring.utils.RepositoryUtils +import java.lang.reflect.Method +import java.net.URLClassLoader +import kotlin.reflect.jvm.javaMethod + +private val logger = getLogger() + +class SpringApiImpl( + instantiationSettings: InstantiationSettings, + dummyTestClass: Class, +) : SpringApi { + private lateinit var dummyTestClassInstance: DummySpringIntegrationTestClass + private val dummyTestClass = dummyTestClass.also { + patchAnnotation( + annotation = it.getAnnotation(ActiveProfiles::class.java), + property = "value", + newValue = instantiationSettings.profiles + ) + patchAnnotation( + annotation = it.getAnnotation(ContextConfiguration::class.java), + property = "classes", + newValue = instantiationSettings.configurationClasses + ) + } + private val dummyTestMethod: Method = DummySpringIntegrationTestClass::dummyTestMethod.javaMethod!! + private val testContextManager: TestContextManager = TestContextManager(this.dummyTestClass) + + private val context get() = getOrLoadSpringApplicationContext() + + override fun getEntityManager(): Any = dummyTestClassInstance.entityManager + + override fun getOrLoadSpringApplicationContext() = try { + testContextManager.testContext.applicationContext as ConfigurableApplicationContext + } catch (e: Throwable) { + throw UTSpringContextLoadingException(e) + } + + override fun getBean(beanName: String): Any = context.getBean(beanName) + + override fun getDependenciesForBean(beanName: String, userSourcesClassLoader: URLClassLoader): Set { + val analyzedBeanNames = mutableSetOf() + collectDependenciesForBeanRecursively(beanName, analyzedBeanNames, userSourcesClassLoader) + return analyzedBeanNames + } + + private fun collectDependenciesForBeanRecursively( + beanName: String, + analyzedBeanNames: MutableSet, + userSourcesClassLoader: URLClassLoader, + ) { + if (beanName in analyzedBeanNames) return + + analyzedBeanNames.add(beanName) + + val dependencyBeanNames = context.beanFactory + .getDependenciesForBean(beanName) + // this filtering is applied to avoid inner beans + .filter { it in context.beanDefinitionNames } + .filter { name -> + val clazz = getBean(name)::class.java + // here immediate hierarchy is enough because proxies are inherited directly + val immediateClazzHierarchy = clazz.interfaces + clazz.superclass + clazz + immediateClazzHierarchy.any { clazz -> userSourcesClassLoader.hasOnClasspath(clazz.name) } + } + .toSet() + + dependencyBeanNames.forEach { collectDependenciesForBeanRecursively(it, analyzedBeanNames, userSourcesClassLoader) } + } + + override fun resetBean(beanName: String) { + val beanDefinitionRegistry = context.beanFactory as BeanDefinitionRegistry + + val beanDefinition = context.beanFactory.getBeanDefinition(beanName) + beanDefinitionRegistry.removeBeanDefinition(beanName) + beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition) + } + + override fun resolveRepositories(beanNames: Set, userSourcesClassLoader: URLClassLoader): Set { + if (!isSpringDataOnClasspath) return emptySet() + val repositoryBeans = beanNames + .map { beanName -> SimpleBeanDefinition(beanName, getBean(beanName)) } + .filter { beanDef -> describesRepository(beanDef.bean) } + .toSet() + + val descriptions = mutableSetOf() + + for (repositoryBean in repositoryBeans) { + val repositoryClass = repositoryBean.bean::class.java + val repositoryClassName = repositoryClass + .interfaces + .filter { clazz -> userSourcesClassLoader.hasOnClasspath(clazz.name) } + .filter { Repository::class.java.isAssignableFrom(it) } + .map { it.name } + .firstOrNull() ?: Repository::class.java.name + + val entity = RepositoryUtils.getEntityClass(repositoryClass) + + if (entity != null) { + descriptions += RepositoryDescription( + beanName = repositoryBean.beanName, + repositoryName = repositoryClassName, + entityName = entity.name, + ) + } else { + logger.warn { + "Failed to get entity class for bean ${repositoryBean.beanName} " + + "that was recognised as a repository of type $repositoryClassName" + } + } + } + + return descriptions + } + + private var beforeTestClassCalled = false + private var isInsideTestMethod = false + + private fun beforeTestClass() { + beforeTestClassCalled = true + testContextManager.beforeTestClass() + dummyTestClassInstance = dummyTestClass.getConstructor().newInstance() + testContextManager.prepareTestInstance(dummyTestClassInstance) + } + + override fun beforeTestMethod() { + if (!beforeTestClassCalled) + beforeTestClass() + if (isInsideTestMethod) { + logger.warn { "afterTestMethod() wasn't called for previous test method, calling it from beforeTestMethod()" } + afterTestMethod() + } + testContextManager.beforeTestMethod(dummyTestClassInstance, dummyTestMethod) + testContextManager.beforeTestExecution(dummyTestClassInstance, dummyTestMethod) + isInsideTestMethod = true + } + + override fun afterTestMethod() { + if (!isInsideTestMethod) { + logger.warn { "afterTestMethod() was probably called twice, ignoring second call" } + return + } + testContextManager.afterTestExecution(dummyTestClassInstance, dummyTestMethod, null) + testContextManager.afterTestMethod(dummyTestClassInstance, dummyTestMethod, null) + isInsideTestMethod = false + } + + override fun resetContext() { + testContextManager.testContext.markApplicationContextDirty(null) + getOrLoadSpringApplicationContext() + } + + private fun describesRepository(bean: Any): Boolean = + try { + bean is Repository<*, *> + } catch (e: ClassNotFoundException) { + false + } + + data class SimpleBeanDefinition( + val beanName: String, + val bean: Any, + ) +} \ No newline at end of file diff --git a/utbot-spring-commons/src/main/kotlin/org/utbot/spring/dummy/DummyPureSpringIntegrationTestClass.kt b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/dummy/DummyPureSpringIntegrationTestClass.kt new file mode 100644 index 0000000000..351472d26f --- /dev/null +++ b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/dummy/DummyPureSpringIntegrationTestClass.kt @@ -0,0 +1,8 @@ +package org.utbot.spring.dummy + +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase + +open class DummyPureSpringIntegrationTestClass : DummySpringIntegrationTestClass() + +@AutoConfigureTestDatabase +class DummyPureSpringIntegrationTestClassAutoconfigTestDB : DummyPureSpringIntegrationTestClass() diff --git a/utbot-spring-commons/src/main/kotlin/org/utbot/spring/dummy/DummySpringBootIntegrationTestClass.kt b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/dummy/DummySpringBootIntegrationTestClass.kt new file mode 100644 index 0000000000..0337e5a835 --- /dev/null +++ b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/dummy/DummySpringBootIntegrationTestClass.kt @@ -0,0 +1,21 @@ +package org.utbot.spring.dummy + +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.SpringBootTestContextBootstrapper +import org.springframework.test.context.BootstrapWith + +@SpringBootTest +@BootstrapWith(SpringBootTestContextBootstrapper::class) +open class DummySpringBootIntegrationTestClass : DummySpringIntegrationTestClass() + +@AutoConfigureTestDatabase +class DummySpringBootIntegrationTestClassAutoconfigTestDB : DummySpringBootIntegrationTestClass() + +@AutoConfigureMockMvc +class DummySpringBootIntegrationTestClassAutoconfigMockMvc : DummySpringBootIntegrationTestClass() + +@AutoConfigureMockMvc +@AutoConfigureTestDatabase +class DummySpringBootIntegrationTestClassAutoconfigMockMvcAndTestDB : DummySpringBootIntegrationTestClass() diff --git a/utbot-spring-commons/src/main/kotlin/org/utbot/spring/dummy/DummySpringIntegrationTestClass.kt b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/dummy/DummySpringIntegrationTestClass.kt new file mode 100644 index 0000000000..43ad33bda5 --- /dev/null +++ b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/dummy/DummySpringIntegrationTestClass.kt @@ -0,0 +1,19 @@ +package org.utbot.spring.dummy + +import org.springframework.security.test.context.support.WithMockUser +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.ContextConfiguration +import org.springframework.transaction.annotation.Isolation +import org.springframework.transaction.annotation.Transactional + +@ActiveProfiles(/* fills dynamically */) +@ContextConfiguration(/* fills dynamically */) +@Transactional(isolation = Isolation.SERIALIZABLE) +@WithMockUser +abstract class DummySpringIntegrationTestClass { + @javax.persistence.PersistenceContext + @jakarta.persistence.PersistenceContext + lateinit var entityManager: Any + + fun dummyTestMethod() {} +} \ No newline at end of file diff --git a/utbot-spring-commons/src/main/kotlin/org/utbot/spring/provider/PureSpringApiProvider.kt b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/provider/PureSpringApiProvider.kt new file mode 100644 index 0000000000..371bb93a93 --- /dev/null +++ b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/provider/PureSpringApiProvider.kt @@ -0,0 +1,19 @@ +package org.utbot.spring.provider + +import org.utbot.spring.api.provider.InstantiationSettings +import org.utbot.spring.SpringApiImpl +import org.utbot.spring.dummy.DummyPureSpringIntegrationTestClass +import org.utbot.spring.dummy.DummyPureSpringIntegrationTestClassAutoconfigTestDB +import org.utbot.spring.utils.DependencyUtils.isSpringDataOnClasspath + +class PureSpringApiProvider : SpringApiProvider { + + override fun isAvailable() = true + + override fun provideAPI(instantiationSettings: InstantiationSettings) = + SpringApiImpl( + instantiationSettings, + if (isSpringDataOnClasspath) DummyPureSpringIntegrationTestClassAutoconfigTestDB::class.java + else DummyPureSpringIntegrationTestClass::class.java + ) +} \ No newline at end of file diff --git a/utbot-spring-commons/src/main/kotlin/org/utbot/spring/provider/SpringApiProvider.kt b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/provider/SpringApiProvider.kt new file mode 100644 index 0000000000..66b30109a0 --- /dev/null +++ b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/provider/SpringApiProvider.kt @@ -0,0 +1,11 @@ +package org.utbot.spring.provider + +import org.utbot.spring.api.SpringApi +import org.utbot.spring.api.provider.InstantiationSettings + +interface SpringApiProvider { + + fun isAvailable(): Boolean + + fun provideAPI(instantiationSettings: InstantiationSettings): SpringApi +} diff --git a/utbot-spring-commons/src/main/kotlin/org/utbot/spring/provider/SpringApiProviderFacadeImpl.kt b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/provider/SpringApiProviderFacadeImpl.kt new file mode 100644 index 0000000000..519115a310 --- /dev/null +++ b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/provider/SpringApiProviderFacadeImpl.kt @@ -0,0 +1,53 @@ +package org.utbot.spring.provider + +import com.jetbrains.rd.util.error +import org.utbot.spring.api.provider.InstantiationSettings + +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import org.springframework.boot.SpringBootVersion +import org.springframework.core.SpringVersion +import org.utbot.spring.api.SpringApi +import org.utbot.spring.api.provider.SpringApiProviderFacade +import org.utbot.spring.api.provider.SpringApiProviderFacade.ProviderResult + +private val logger = getLogger() + +class SpringApiProviderFacadeImpl : SpringApiProviderFacade { + + override fun provideMostSpecificAvailableApi(instantiationSettings: InstantiationSettings): ProviderResult = + useMostSpecificNonFailingApi(instantiationSettings) { api -> + api.getOrLoadSpringApplicationContext() + api + } + + override fun useMostSpecificNonFailingApi( + instantiationSettings: InstantiationSettings, + apiUser: (SpringApi) -> T + ): ProviderResult { + logger.info { "Current Java version is: " + System.getProperty("java.version") } + logger.info { "Current Spring version is: " + runCatching { SpringVersion.getVersion() }.getOrNull() } + logger.info { "Current Spring Boot version is: " + runCatching { SpringBootVersion.getVersion() }.getOrNull() } + logger.info { "InstantiationSettings: $instantiationSettings" } + + val exceptions = mutableListOf() + + val apiProviders = sequenceOf(SpringBootApiProvider(), PureSpringApiProvider()) + + val result = apiProviders + .filter { apiProvider -> apiProvider.isAvailable() } + .map { apiProvider -> + logger.info { "Using Spring API from $apiProvider" } + val result = runCatching { apiUser(apiProvider.provideAPI(instantiationSettings)) } + result.onFailure { e -> + exceptions.add(e) + logger.error("Using Spring API from $apiProvider failed", e) + } + result + } + .firstOrNull { it.isSuccess } + ?: Result.failure(exceptions.first()) + + return ProviderResult(result, exceptions) + } +} diff --git a/utbot-spring-commons/src/main/kotlin/org/utbot/spring/provider/SpringBootApiProvider.kt b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/provider/SpringBootApiProvider.kt new file mode 100644 index 0000000000..ed28bcac4c --- /dev/null +++ b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/provider/SpringBootApiProvider.kt @@ -0,0 +1,27 @@ +package org.utbot.spring.provider + +import org.utbot.spring.api.provider.InstantiationSettings +import org.utbot.spring.dummy.DummySpringBootIntegrationTestClass +import org.utbot.spring.SpringApiImpl +import org.utbot.spring.dummy.DummySpringBootIntegrationTestClassAutoconfigMockMvc +import org.utbot.spring.dummy.DummySpringBootIntegrationTestClassAutoconfigMockMvcAndTestDB +import org.utbot.spring.dummy.DummySpringBootIntegrationTestClassAutoconfigTestDB +import org.utbot.spring.utils.DependencyUtils.isSpringBootTestOnClasspath +import org.utbot.spring.utils.DependencyUtils.isSpringDataOnClasspath +import org.utbot.spring.utils.DependencyUtils.isSpringWebOnClasspath + +class SpringBootApiProvider : SpringApiProvider { + + override fun isAvailable(): Boolean = isSpringBootTestOnClasspath + + override fun provideAPI(instantiationSettings: InstantiationSettings) = + SpringApiImpl( + instantiationSettings, + when { + isSpringDataOnClasspath && isSpringWebOnClasspath -> DummySpringBootIntegrationTestClassAutoconfigMockMvcAndTestDB::class + isSpringDataOnClasspath && !isSpringWebOnClasspath -> DummySpringBootIntegrationTestClassAutoconfigTestDB::class + !isSpringDataOnClasspath && isSpringWebOnClasspath -> DummySpringBootIntegrationTestClassAutoconfigMockMvc::class + else -> DummySpringBootIntegrationTestClass::class + }.java + ) +} \ No newline at end of file diff --git a/utbot-spring-commons/src/main/kotlin/org/utbot/spring/utils/DependencyUtils.kt b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/utils/DependencyUtils.kt new file mode 100644 index 0000000000..c945d1b4bd --- /dev/null +++ b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/utils/DependencyUtils.kt @@ -0,0 +1,28 @@ +package org.utbot.spring.utils + +import org.springframework.boot.test.context.SpringBootTestContextBootstrapper +import org.springframework.data.repository.Repository +import org.springframework.web.bind.annotation.RequestMapping + +object DependencyUtils { + val isSpringDataOnClasspath = try { + Repository::class.java.name + true + } catch (e: Throwable) { + false + } + + val isSpringWebOnClasspath = try { + RequestMapping::class.java.name + true + } catch (e: Throwable) { + false + } + + val isSpringBootTestOnClasspath = try { + SpringBootTestContextBootstrapper::class.java.name + true + } catch (e: Throwable) { + false + } +} diff --git a/utbot-spring-commons/src/main/kotlin/org/utbot/spring/utils/RepositoryUtils.kt b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/utils/RepositoryUtils.kt new file mode 100644 index 0000000000..487f3f7690 --- /dev/null +++ b/utbot-spring-commons/src/main/kotlin/org/utbot/spring/utils/RepositoryUtils.kt @@ -0,0 +1,29 @@ +package org.utbot.spring.utils + +import org.springframework.core.GenericTypeResolver +import org.springframework.data.repository.Repository + +/** + * This util class allows to obtain some data from Spring repository. + * For example, information about entity it is working with. + * + * Private methods implementation is taken from https://stackoverflow.com/a/76229273. + */ +object RepositoryUtils { + + fun getEntityClass(repositoryClass: Class<*>): Class<*>? = + getGenericType(repositoryClass, Repository::class.java, 0) + + private fun getGenericType(classInstance: Class<*>, classToGetGenerics: Class<*>, genericPosition: Int): Class<*>? { + val typeArguments = getGenericType(classInstance, classToGetGenerics) + if (typeArguments != null && typeArguments.size >= genericPosition) { + return typeArguments[genericPosition] + } + + return null + } + + private fun getGenericType(classInstance: Class<*>, classToGetGenerics: Class<*>): Array?>? { + return GenericTypeResolver.resolveTypeArguments(classInstance, classToGetGenerics) + } +} \ No newline at end of file diff --git a/utbot-spring-framework/build.gradle.kts b/utbot-spring-framework/build.gradle.kts new file mode 100644 index 0000000000..0852abd8aa --- /dev/null +++ b/utbot-spring-framework/build.gradle.kts @@ -0,0 +1,39 @@ +val kotlinLoggingVersion: String by rootProject +val rdVersion: String by rootProject +val sootVersion: String by rootProject + +val fetchSpringCommonsJar: Configuration by configurations.creating { + isCanBeResolved = true + isCanBeConsumed = false +} + +val fetchSpringAnalyzerJar: Configuration by configurations.creating { + isCanBeResolved = true + isCanBeConsumed = false +} + +dependencies { + implementation(project(":utbot-framework")) + implementation(project(":utbot-spring-commons-api")) + implementation(project(":utbot-spring-analyzer")) + + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) + implementation(group = "com.jetbrains.rd", name = "rd-core", version = rdVersion) + + implementation("org.unittestbot.soot:soot-utbot-fork:${sootVersion}") { + exclude(group = "com.google.guava", module = "guava") + } + + fetchSpringCommonsJar(project(":utbot-spring-commons", configuration = "springCommonsJar")) + fetchSpringAnalyzerJar(project(":utbot-spring-analyzer", configuration = "springAnalyzerJar")) +} + +tasks.processResources { + from(fetchSpringCommonsJar) { + into("lib") + } + + from(fetchSpringAnalyzerJar) { + into("lib") + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/external/api/UtBotSpringApi.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/external/api/UtBotSpringApi.kt new file mode 100644 index 0000000000..0442ef1092 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/external/api/UtBotSpringApi.kt @@ -0,0 +1,66 @@ +package org.utbot.external.api + +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.context.simple.SimpleApplicationContext +import org.utbot.framework.context.spring.SpringApplicationContext +import org.utbot.framework.context.spring.SpringApplicationContextImpl +import org.utbot.framework.plugin.api.SpringConfiguration +import org.utbot.framework.plugin.api.SpringSettings +import org.utbot.framework.plugin.api.SpringTestType +import org.utbot.framework.process.SpringAnalyzerTask +import java.io.File + +object UtBotSpringApi { + private val springBootConfigAnnotations = setOf( + "org.springframework.boot.autoconfigure.SpringBootApplication", + "org.springframework.boot.SpringBootConfiguration" + ) + + /** + * NOTE: [classpath] should include project under test classpath (with all dependencies) as well as + * `spring-test`, `spring-boot-test`, and `spring-security-test` if respectively `spring-beans`, + * `spring-boot`, and `spring-security-core` are dependencies of project under test. + * + * UtBot doesn't add Spring test modules to classpath automatically to let API users control their versions. + */ + @JvmOverloads + @JvmStatic + fun createSpringApplicationContext( + springSettings: SpringSettings, + springTestType: SpringTestType, + classpath: List, + delegateContext: ApplicationContext = SimpleApplicationContext() + ): SpringApplicationContext { + if (springTestType == SpringTestType.INTEGRATION_TEST) { + require(springSettings is SpringSettings.PresentSpringSettings) { + "Integration tests can't be generated without Spring settings" + } + val configuration = springSettings.configuration + require(configuration !is SpringConfiguration.XMLConfiguration) { + "Integration tests aren't supported for XML configurations, consider using Java " + + "configuration that imports your XML configuration with @ImportResource" + } + } + return SpringApplicationContextImpl.internalCreate( + delegateContext = delegateContext, + beanDefinitions = when (springSettings) { + SpringSettings.AbsentSpringSettings -> listOf() + is SpringSettings.PresentSpringSettings -> SpringAnalyzerTask(classpath, springSettings).perform() + }, + springTestType = springTestType, + springSettings = springSettings, + ) + } + + @JvmStatic + fun createXmlSpringConfiguration(xmlConfig: File): SpringConfiguration.XMLConfiguration = + SpringConfiguration.XMLConfiguration(xmlConfig.absolutePath) + + @JvmStatic + fun createJavaSpringConfiguration(javaConfig: Class<*>): SpringConfiguration.JavaBasedConfiguration = + if (javaConfig.annotations.any { it.annotationClass.java.name in springBootConfigAnnotations }) { + SpringConfiguration.SpringBootConfiguration(javaConfig.name, isDefinitelyUnique = false) + } else { + SpringConfiguration.JavaConfiguration(javaConfig.name) + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/domain/SpringModule.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/domain/SpringModule.kt new file mode 100644 index 0000000000..8ada81ee7f --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/domain/SpringModule.kt @@ -0,0 +1,27 @@ +package org.utbot.framework.codegen.domain + +enum class SpringModule( + val testFrameworkDisplayName: String, +) { + SPRING_BEANS( + testFrameworkDisplayName = "spring-test", + ), + SPRING_BOOT( + testFrameworkDisplayName = "spring-boot-test", + ), + SPRING_SECURITY( + testFrameworkDisplayName = "spring-security-test" + ); + + var isInstalled = false + /** + * Generation Spring specific tests requires special spring test framework being installed, + * so we can use `TestContextManager` from `spring-test` to configure test context in + * spring-analyzer and to run integration tests. + */ + var testFrameworkInstalled: Boolean = false + + companion object { + val installedItems get() = values().filter { it.isInstalled } + } +} diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/MockitoAnnotationBuiltins.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/MockitoAnnotationBuiltins.kt new file mode 100644 index 0000000000..0f6bac180e --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/MockitoAnnotationBuiltins.kt @@ -0,0 +1,18 @@ +package org.utbot.framework.codegen.domain.builtin + +import org.utbot.framework.plugin.api.BuiltinClassId + +internal val mockClassId = BuiltinClassId( + canonicalName = "org.mockito.Mock", + simpleName = "Mock", +) + +internal val spyClassId = BuiltinClassId( + canonicalName = "org.mockito.Spy", + simpleName = "Spy" +) + +internal val injectMocksClassId = BuiltinClassId( + canonicalName = "org.mockito.InjectMocks", + simpleName = "InjectMocks", +) \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/generator/SpringCodeGenerator.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/generator/SpringCodeGenerator.kt new file mode 100644 index 0000000000..7aba0c939a --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/generator/SpringCodeGenerator.kt @@ -0,0 +1,69 @@ +package org.utbot.framework.codegen.generator + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.builders.SimpleTestClassModelBuilder +import org.utbot.framework.codegen.services.language.CgLanguageAssistant +import org.utbot.framework.codegen.tree.CgCustomAssertConstructor +import org.utbot.framework.codegen.tree.CgMethodConstructor +import org.utbot.framework.codegen.tree.CgSpringIntegrationTestClassConstructor +import org.utbot.framework.codegen.tree.CgSpringMethodConstructor +import org.utbot.framework.codegen.tree.CgSpringUnitTestClassConstructor +import org.utbot.framework.codegen.tree.CgSpringVariableConstructor +import org.utbot.framework.codegen.tree.CgVariableConstructor +import org.utbot.framework.codegen.tree.ututils.UtilClassKind +import org.utbot.framework.codegen.tree.withCustomAssertForMockMvcResultActions +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConcreteContextLoadingResult +import org.utbot.framework.plugin.api.SpringSettings +import org.utbot.framework.plugin.api.SpringSettings.AbsentSpringSettings +import org.utbot.framework.plugin.api.SpringSettings.PresentSpringSettings +import org.utbot.framework.plugin.api.SpringTestType + +class SpringCodeGenerator( + private val springTestType: SpringTestType, + private val springSettings: SpringSettings, + private val concreteContextLoadingResult: ConcreteContextLoadingResult?, + params: CodeGeneratorParams +) : AbstractCodeGenerator( + params.copy( + cgLanguageAssistant = object : CgLanguageAssistant by params.cgLanguageAssistant { + override fun getVariableConstructorBy(context: CgContext): CgVariableConstructor = + // TODO decorate original `params.cgLanguageAssistant.getVariableConstructorBy(context)` + CgSpringVariableConstructor(context) + + override fun getMethodConstructorBy(context: CgContext): CgMethodConstructor = + CgSpringMethodConstructor(context) + + override fun getCustomAssertConstructorBy(context: CgContext): CgCustomAssertConstructor = + params.cgLanguageAssistant.getCustomAssertConstructorBy(context) + .withCustomAssertForMockMvcResultActions() + } + ) +) { + private val classUnderTest: ClassId = params.classUnderTest + + override fun generate(testSets: List): CodeGeneratorResult { + val testClassModel = SimpleTestClassModelBuilder().createTestClassModel(classUnderTest, testSets) + + logger.info { "Code generation phase started at ${now()}" } + val astConstructor = when (springTestType) { + SpringTestType.UNIT_TEST -> CgSpringUnitTestClassConstructor(context) + SpringTestType.INTEGRATION_TEST -> + when (val settings = springSettings) { + is PresentSpringSettings -> CgSpringIntegrationTestClassConstructor(context, concreteContextLoadingResult, settings) + is AbsentSpringSettings -> error("No Spring settings were provided for Spring integration test generation.") + } + } + val testClassFile = astConstructor.construct(testClassModel) + logger.info { "Code generation phase finished at ${now()}" } + + val generatedCode = renderToString(testClassFile) + + return CodeGeneratorResult( + generatedCode = generatedCode, + utilClassKind = UtilClassKind.fromCgContextOrNull(context), + testsGenerationReport = astConstructor.testsGenerationReport, + ) + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/SpyFrameworkManager.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/SpyFrameworkManager.kt new file mode 100644 index 0000000000..841c045f07 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/services/framework/SpyFrameworkManager.kt @@ -0,0 +1,12 @@ +package org.utbot.framework.codegen.services.framework + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.plugin.api.UtAssembleModel + +class SpyFrameworkManager(context: CgContext) : CgVariableConstructorComponent(context) { + + fun spyForVariable(model: UtAssembleModel){ + variableConstructor.constructAssembleForVariable(model) + } + +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgAbstractSpringTestClassConstructor.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgAbstractSpringTestClassConstructor.kt new file mode 100644 index 0000000000..9f46e6abef --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgAbstractSpringTestClassConstructor.kt @@ -0,0 +1,131 @@ +package org.utbot.framework.codegen.tree + +import org.utbot.framework.codegen.domain.builtin.TestClassUtilMethodProvider +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.AnnotationTarget.Method +import org.utbot.framework.codegen.domain.models.CgClassBody +import org.utbot.framework.codegen.domain.models.CgFieldDeclaration +import org.utbot.framework.codegen.domain.models.CgFrameworkUtilMethod +import org.utbot.framework.codegen.domain.models.CgMethod +import org.utbot.framework.codegen.domain.models.CgMethodTestSet +import org.utbot.framework.codegen.domain.models.CgMethodsCluster +import org.utbot.framework.codegen.domain.models.CgRegion +import org.utbot.framework.codegen.domain.models.CgStatement +import org.utbot.framework.codegen.domain.models.CgStaticsRegion +import org.utbot.framework.codegen.domain.models.SimpleTestClassModel +import org.utbot.framework.codegen.tree.fieldmanager.ClassFieldManagerFacade +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.util.id + +abstract class CgAbstractSpringTestClassConstructor(context: CgContext) : + CgAbstractTestClassConstructor(context) { + + protected val variableConstructor: CgSpringVariableConstructor = + CgComponents.getVariableConstructorBy(context) as CgSpringVariableConstructor + + override fun constructTestClassBody(testClassModel: SimpleTestClassModel): CgClassBody { + return buildClassBody(currentTestClass) { + + // TODO: support inner classes here + + fields += constructClassFields(testClassModel) + clearUnwantedVariableModels() + + // constructClassFields may mark fields as initialized, while they are in + // fact not initialized, so we clearAlreadyInitializedFieldModels() + variableConstructor.clearAlreadyInitializedFieldModels() + + constructAdditionalTestMethods()?.let { methodRegions += it } + + for ((testSetIndex, testSet) in testClassModel.methodTestSets.withIndex()) { + updateExecutableUnderTest(testSet.executableUnderTest) + withTestSetIdScope(testSetIndex) { + val currentMethodUnderTestRegions = constructTestSet(testSet) ?: return@withTestSetIdScope + val executableUnderTestCluster = CgMethodsCluster( + "Test suites for executable $currentExecutableUnderTest", + currentMethodUnderTestRegions + ) + methodRegions += executableUnderTestCluster + } + } + + constructAdditionalUtilMethods()?.let { methodRegions += it } + + if (currentTestClass == outerMostTestClass) { + val utilEntities = collectUtilEntities() + // If utilMethodProvider is TestClassUtilMethodProvider, then util entities should be declared + // in the test class. Otherwise, util entities will be located elsewhere (e.g. another class). + if (utilMethodProvider is TestClassUtilMethodProvider && utilEntities.isNotEmpty()) { + staticDeclarationRegions += CgStaticsRegion("Util methods", utilEntities) + } + } + } + } + + override fun constructTestSet(testSet: CgMethodTestSet): List>? { + val regions = mutableListOf>() + + if (testSet.executions.any()) { + runCatching { + createTest(testSet, regions) + }.onFailure { e -> processFailure(testSet, e) } + } + + val errors = testSet.allErrors + if (errors.isNotEmpty()) { + regions += methodConstructor.errorMethod(testSet, errors) + testsGenerationReport.addMethodErrors(testSet, errors) + } + + return if (regions.any()) regions else null + } + + abstract fun constructClassFields(testClassModel: SimpleTestClassModel): List + + /** + * Here "additional" means that these tests are not obtained from + * [UtExecution]s generated by engine or fuzzer, but have another origin. + */ + open fun constructAdditionalTestMethods(): CgMethodsCluster? = null + + open fun constructAdditionalUtilMethods(): CgMethodsCluster? = null + + + /** + * Clears the results of variable instantiations that occurred + * when we create class variables with specific annotations. + * Actually, only mentioned variables should be stored in `valueByModelId`. + * + * This is a kind of HACK. + * It is better to distinguish creating variable by model with all + * related side effects and just creating a variable definition, + * but it will take very long time to do it now. + */ + private fun clearUnwantedVariableModels() { + val trustedListOfModels = ClassFieldManagerFacade(context).findTrustedModels() + + valueByUtModelWrapper + .filterNot { it.key in trustedListOfModels } + .forEach { valueByUtModelWrapper.remove(it.key) } + } + + protected fun constructBeforeMethod(statements: List): CgFrameworkUtilMethod { + val beforeAnnotation = addAnnotation(context.testFramework.beforeMethodId, Method) + return CgFrameworkUtilMethod( + name = "setUp", + statements = statements, + exceptions = emptySet(), + annotations = listOf(beforeAnnotation), + ) + } + + protected fun constructAfterMethod(statements: List): CgFrameworkUtilMethod { + val afterAnnotation = addAnnotation(context.testFramework.afterMethodId, Method) + return CgFrameworkUtilMethod( + name = "tearDown", + statements = statements, + exceptions = setOf(Exception::class.id), + annotations = listOf(afterAnnotation), + ) + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMockMvcResultActionsAssertConstructor.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMockMvcResultActionsAssertConstructor.kt new file mode 100644 index 0000000000..963eabdf3b --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMockMvcResultActionsAssertConstructor.kt @@ -0,0 +1,51 @@ +package org.utbot.framework.codegen.tree + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.services.access.CgCallableAccessManager +import org.utbot.framework.codegen.services.access.CgCallableAccessManagerImpl +import org.utbot.framework.codegen.tree.CgComponents.getStatementConstructorBy +import org.utbot.framework.plugin.api.UtCustomModel +import org.utbot.framework.plugin.api.UtSpringMockMvcResultActionsModel +import org.utbot.framework.plugin.api.util.SpringModelUtils.contentMatchersStringMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.mockMvcResultHandlersClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.mockMvcResultMatchersClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.resultActionsAndDoMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.resultActionsAndExpectMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.resultHandlersPrintMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.resultMatchersContentMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.resultMatchersStatusMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.resultMatchersViewMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.statusMatchersIsMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.viewMatchersNameMethodId + +fun CgCustomAssertConstructor.withCustomAssertForMockMvcResultActions() = + CgMockMvcResultActionsAssertConstructor(context, this) + +class CgMockMvcResultActionsAssertConstructor( + context: CgContext, + private val delegateAssertConstructor: CgCustomAssertConstructor +) : CgCustomAssertConstructor by delegateAssertConstructor, + CgContextOwner by context, + CgStatementConstructor by getStatementConstructorBy(context), + CgCallableAccessManager by CgCallableAccessManagerImpl(context) { + override fun tryConstructCustomAssert(expected: UtCustomModel, actual: CgVariable): Boolean { + if (expected is UtSpringMockMvcResultActionsModel) { + +actual[resultActionsAndDoMethodId](mockMvcResultHandlersClassId[resultHandlersPrintMethodId]()) + +actual[resultActionsAndExpectMethodId]( + mockMvcResultMatchersClassId[resultMatchersStatusMethodId]()[statusMatchersIsMethodId](expected.status) + ) + expected.viewName?.let { viewName -> + +actual[resultActionsAndExpectMethodId]( + mockMvcResultMatchersClassId[resultMatchersViewMethodId]()[viewMatchersNameMethodId](viewName) + ) + } + +actual[resultActionsAndExpectMethodId]( + mockMvcResultMatchersClassId[resultMatchersContentMethodId]()[contentMatchersStringMethodId](expected.contentAsString) + ) + return true + } else + return delegateAssertConstructor.tryConstructCustomAssert(expected, actual) + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringIntegrationTestClassConstructor.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringIntegrationTestClassConstructor.kt new file mode 100644 index 0000000000..7dbcfce6ac --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringIntegrationTestClassConstructor.kt @@ -0,0 +1,247 @@ +package org.utbot.framework.codegen.tree + +import mu.KotlinLogging +import org.utbot.common.tryLoadClass +import org.utbot.framework.codegen.domain.Junit4 +import org.utbot.framework.codegen.domain.Junit5 +import org.utbot.framework.codegen.domain.TestNg +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.* +import org.utbot.framework.codegen.domain.models.AnnotationTarget.* +import org.utbot.framework.codegen.domain.models.CgTestMethodType.FAILING +import org.utbot.framework.codegen.domain.models.CgTestMethodType.SUCCESSFUL +import org.utbot.framework.codegen.tree.fieldmanager.CgAutowiredFieldsManager +import org.utbot.framework.codegen.tree.fieldmanager.CgPersistenceContextFieldsManager +import org.utbot.framework.codegen.util.escapeControlChars +import org.utbot.framework.codegen.util.resolve +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConcreteContextLoadingResult +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.SpringSettings.* +import org.utbot.framework.plugin.api.SpringConfiguration.* +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtSpringEntityManagerModel +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper.Companion.collectAllModels +import org.utbot.framework.plugin.api.util.IndentUtil.TAB +import org.utbot.framework.plugin.api.util.SpringModelUtils +import org.utbot.framework.plugin.api.util.SpringModelUtils.activeProfilesClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.autoConfigureTestDbClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.bootstrapWithClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.contextConfigurationClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.dirtiesContextClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.dirtiesContextClassModeClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.extendWithClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.flushMethodIdOrNull +import org.utbot.framework.plugin.api.util.SpringModelUtils.mockMvcClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.repositoryClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.runWithClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.springBootTestClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.springBootTestContextBootstrapperClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.springExtensionClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.springRunnerClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.transactionalClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.withMockUserClassId +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.spring.api.UTSpringContextLoadingException + +class CgSpringIntegrationTestClassConstructor( + context: CgContext, + private val concreteContextLoadingResult: ConcreteContextLoadingResult?, + private val springSettings: PresentSpringSettings, +) : CgAbstractSpringTestClassConstructor(context) { + + private val autowiredFieldManager = CgAutowiredFieldsManager(context) + private val persistenceContextFieldsManager = CgPersistenceContextFieldsManager.createIfPossible(context) + + companion object { + private val logger = KotlinLogging.logger {} + } + + override fun construct(testClassModel: SimpleTestClassModel): CgClassFile = super.construct( + flushMethodIdOrNull?.let { flushMethodId -> + testClassModel.mapStateBeforeModels { UtModelDeepMapper { model -> + shallowlyAddFlushes(model, flushMethodId) + } } + } ?: testClassModel + ) + + private fun shallowlyAddFlushes(model: UtModel, flushMethodId: MethodId): UtModel = + when (model) { + is UtAssembleModel -> model.copy( + modificationsChain = model.modificationsChain.flatMap { modification -> + if (modification.instance is UtSpringEntityManagerModel) + listOf( + modification, + UtExecutableCallModel( + instance = UtSpringEntityManagerModel(), + executable = flushMethodId, + params = emptyList() + ) + ) + else listOf(modification) + } + ) + else -> model + } + + override fun constructTestClass(testClassModel: SimpleTestClassModel): CgClass { + addNecessarySpringSpecificAnnotations(testClassModel) + return super.constructTestClass(testClassModel) + } + + override fun constructClassFields(testClassModel: SimpleTestClassModel): List { + val autowiredFields = autowiredFieldManager.createFieldDeclarations(testClassModel) + val persistentContextFields = persistenceContextFieldsManager?.createFieldDeclarations(testClassModel) + .orEmpty() + + return autowiredFields + persistentContextFields + } + + override fun constructAdditionalTestMethods() = + CgMethodsCluster.withoutDocs( + listOfNotNull(constructContextLoadsMethod()) + ) + + private fun constructContextLoadsMethod() : CgTestMethod { + if (concreteContextLoadingResult == null) + logger.error { "Missing contextLoadingResult" } + val exception = concreteContextLoadingResult?.exceptions?.firstOrNull() + return CgTestMethod( + name = "contextLoads", + statements = listOfNotNull( + exception?.let { e -> constructFailedContextLoadingTraceComment(e) }, + if (concreteContextLoadingResult == null) CgSingleLineComment("Error: context loading result from concrete execution is missing") else null + ), + annotations = listOf(addAnnotation(context.testFramework.testAnnotationId, Method)), + documentation = CgDocumentationComment(listOf( + CgDocRegularLineStmt("This sanity check test fails if the application context cannot start.") + ) + exception?.let { constructFailedContextLoadingDocComment() }.orEmpty()), + type = if (concreteContextLoadingResult != null && exception == null) SUCCESSFUL else FAILING + ) + } + + private fun constructFailedContextLoadingDocComment() = listOf( + CgDocRegularLineStmt("

    "), + CgDocRegularLineStmt("Context loading throws an exception."), + CgDocRegularLineStmt("Please try to fix your context or environment configuration."), + CgDocRegularLineStmt("Spring configuration applied: ${springSettings.configuration.fullDisplayName}."), + ) + + private fun constructFailedContextLoadingTraceComment(exception: Throwable) = CgMultilineComment( + exception + .stackTraceToString() + .lines() + .let { lines -> + if (exception is UTSpringContextLoadingException) lines.dropWhile { !it.contains("Caused") } + else lines + } + .mapIndexed { i, line -> + if (i == 0) "Failure ${line.replace("Caused", "caused")}" + else TAB + line + } + .map { it.escapeControlChars() } + ) + + private fun addNecessarySpringSpecificAnnotations(testClassModel: SimpleTestClassModel) { + val isSpringBootTestAccessible = utContext.classLoader.tryLoadClass(springBootTestClassId.name) != null + if (isSpringBootTestAccessible) { + addAnnotation(springBootTestClassId, Class) + } + + val (testFrameworkExtension, springExtension) = when (testFramework) { + Junit4 -> runWithClassId to springRunnerClassId + Junit5 -> extendWithClassId to springExtensionClassId + TestNg -> error("Spring extension is not implemented in TestNg") + else -> error("Trying to generate tests for Spring project with non-JVM framework") + } + + // @SpringBootTest contains @ExtendWith(SpringExtension.class), no need to add it manually + if (!isSpringBootTestAccessible || testFrameworkExtension != extendWithClassId) { + addAnnotation( + classId = testFrameworkExtension, + argument = createGetClassExpression(springExtension, codegenLanguage), + target = Class, + ) + } + + if (utContext.classLoader.tryLoadClass(springBootTestContextBootstrapperClassId.name) != null) + // TODO in somewhat new versions of Spring Boot, @SpringBootTest + // already includes @BootstrapWith(SpringBootTestContextBootstrapper.class), + // so we should avoid adding it manually to reduce number of annotations + addAnnotation( + classId = bootstrapWithClassId, + argument = createGetClassExpression(springBootTestContextBootstrapperClassId, codegenLanguage), + target = Class, + ) + + val defaultProfileIsUsed = springSettings.profiles.singleOrNull() == "default" + if (!defaultProfileIsUsed) { + addAnnotation( + classId = activeProfilesClassId, + namedArguments = listOf( + CgNamedAnnotationArgument( + name = "profiles", + value = CgArrayAnnotationArgument(springSettings.profiles.map { profile -> profile.resolve() }) + ) + ), + target = Class, + ) + } + + // TODO: we support only JavaBasedConfiguration in integration tests. + // Adapt for XMLConfigurations when supported. + val configClass = springSettings.configuration as JavaBasedConfiguration + if (configClass is JavaConfiguration || configClass is SpringBootConfiguration && !configClass.isDefinitelyUnique) { + addAnnotation( + classId = contextConfigurationClassId, + namedArguments = listOf( + CgNamedAnnotationArgument( + name = "classes", + value = CgArrayAnnotationArgument( + listOf( + createGetClassExpression( + ClassId(configClass.configBinaryName), + codegenLanguage + ) + ) + ) + ) + ), + target = Class, + ) + } + + + addAnnotation( + classId = dirtiesContextClassId, + namedArguments = listOf( + CgNamedAnnotationArgument( + name = "classMode", + value = CgEnumConstantAccess(dirtiesContextClassModeClassId, "BEFORE_EACH_TEST_METHOD") + ), + ), + target = Class, + ) + + if (utContext.classLoader.tryLoadClass(transactionalClassId.name) != null) + addAnnotation(transactionalClassId, Class) + + // `@AutoConfigureTestDatabase` can itself be on the classpath, while spring-data + // (i.e. module containing `Repository`) is not. + // + // If we add `@AutoConfigureTestDatabase` without having spring-data, + // generated tests will fail with `ClassNotFoundException: org.springframework.dao.DataAccessException`. + if (utContext.classLoader.tryLoadClass(repositoryClassId.name) != null) + addAnnotation(autoConfigureTestDbClassId, Class) + + val allStateBeforeModels = collectAllModels { collector -> testClassModel.mapStateBeforeModels { collector } } + if (allStateBeforeModels.any { it.classId == mockMvcClassId }) + addAnnotation(SpringModelUtils.autoConfigureMockMvcClassId, Class) + + if (utContext.classLoader.tryLoadClass(withMockUserClassId.name) != null) + addAnnotation(withMockUserClassId, Class) + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringMethodConstructor.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringMethodConstructor.kt new file mode 100644 index 0000000000..60aa678b41 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringMethodConstructor.kt @@ -0,0 +1,27 @@ +package org.utbot.framework.codegen.tree + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.util.SpringModelUtils.mockMvcPerformMethodId +import org.utbot.framework.plugin.api.util.SpringModelUtils.nestedServletExceptionClassIds +import org.utbot.framework.plugin.api.util.id + +class CgSpringMethodConstructor(context: CgContext) : CgMethodConstructor(context) { + override fun shouldTestPassWithException(execution: UtExecution, exception: Throwable): Boolean = + !isNestedServletException(exception) && super.shouldTestPassWithException(execution, exception) + + override fun collectNeededStackTraceLines( + exception: Throwable, + executableToStartCollectingFrom: ExecutableId + ): List = + // `mockMvc.perform` wraps exceptions from user code into NestedServletException, so we unwrap them back + exception.takeIf { + executableToStartCollectingFrom == mockMvcPerformMethodId && isNestedServletException(it) + }?.cause?.let { cause -> + super.collectNeededStackTraceLines(cause, currentExecutableUnderTest!!) + } ?: super.collectNeededStackTraceLines(exception, executableToStartCollectingFrom) + + private fun isNestedServletException(exception: Throwable): Boolean = + exception::class.java.id in nestedServletExceptionClassIds +} diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringUnitTestClassConstructor.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringUnitTestClassConstructor.kt new file mode 100644 index 0000000000..f60627035e --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringUnitTestClassConstructor.kt @@ -0,0 +1,109 @@ +package org.utbot.framework.codegen.tree + +import org.utbot.framework.codegen.domain.builtin.closeMethodId +import org.utbot.framework.codegen.domain.builtin.openMocksMethodId +import org.utbot.framework.codegen.domain.builtin.clearMethodId +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgAssignment +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgFieldDeclaration +import org.utbot.framework.codegen.domain.models.CgMethodCall +import org.utbot.framework.codegen.domain.models.CgMethodsCluster +import org.utbot.framework.codegen.domain.models.CgStatementExecutableCall +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.domain.models.SimpleTestClassModel +import org.utbot.framework.codegen.tree.fieldmanager.CgInjectingMocksFieldsManager +import org.utbot.framework.codegen.tree.fieldmanager.CgMockedFieldsManager +import org.utbot.framework.codegen.tree.fieldmanager.CgSpiedFieldsManager +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.objectClassId + +class CgSpringUnitTestClassConstructor(context: CgContext) : CgAbstractSpringTestClassConstructor(context) { + + private var additionalMethodsRequired: Boolean = false + private lateinit var mockitoCloseableVariable: CgValue + private lateinit var spyClearVariables: List + + private val mocksFieldsManager = CgMockedFieldsManager(context) + private val spiesFieldsManager = CgSpiedFieldsManager(context) + private val injectingMocksFieldsManager = + CgInjectingMocksFieldsManager(context, mocksFieldsManager, spiesFieldsManager) + + override fun constructClassFields(testClassModel: SimpleTestClassModel): List { + val fields = mutableListOf() + + val spiesFields = spiesFieldsManager.createFieldDeclarations(testClassModel) + val mockedFields = mocksFieldsManager.createFieldDeclarations(testClassModel) + + if ((spiesFields + mockedFields).isNotEmpty()) { + val injectingMocksFields = injectingMocksFieldsManager.createFieldDeclarations(testClassModel) + + fields += injectingMocksFields + fields += mockedFields + fields += spiesFields + fields += constructMockitoCloseables() + + additionalMethodsRequired = true + spyClearVariables = spiesFields.map { it.declaration.variable } + } + + return fields + } + + override fun constructAdditionalUtilMethods(): CgMethodsCluster? { + if (!additionalMethodsRequired) return null + + importIfNeeded(openMocksMethodId) + + val openMocksCall = CgMethodCall( + caller = null, + executableId = openMocksMethodId, + //TODO: this is a hack of this + arguments = listOf(CgVariable("this", objectClassId)) + ) + + val closeCall = CgMethodCall( + caller = mockitoCloseableVariable, + executableId = closeMethodId, + arguments = emptyList(), + ) + + val clearSpyModelCalls = spyClearVariables.map { spyVariable -> + CgMethodCall( + caller = spyVariable, + executableId = clearMethodId(spyVariable.type.jClass), + arguments = emptyList() + ) + } + + val openMocksStatement = CgAssignment(mockitoCloseableVariable, openMocksCall) + val closeStatement = CgStatementExecutableCall(closeCall) + val clearSpyModels = clearSpyModelCalls.map { CgStatementExecutableCall(it) } + + return CgMethodsCluster.withoutDocs( + listOf( + constructBeforeMethod(listOf(openMocksStatement)), + constructAfterMethod(clearSpyModels + listOf(closeStatement)), + ) + ) + } + + private fun constructMockitoCloseables(): CgFieldDeclaration { + val mockitoCloseableVarName = "mockitoCloseable" + val mockitoCloseableVarType = java.lang.AutoCloseable::class.id + + val mockitoCloseableModel = UtCompositeModel( + id = null, + classId = mockitoCloseableVarType, + isMock = false, + ) + + mockitoCloseableVariable = + variableConstructor.getOrCreateVariable(mockitoCloseableModel, mockitoCloseableVarName) + val mockitoCloseableDeclaration = CgDeclaration(mockitoCloseableVarType, mockitoCloseableVarName, initializer = null) + return CgFieldDeclaration(ownerClassId = currentTestClass, mockitoCloseableDeclaration) + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringVariableConstructor.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringVariableConstructor.kt new file mode 100644 index 0000000000..2ca542e6c6 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgSpringVariableConstructor.kt @@ -0,0 +1,44 @@ +package org.utbot.framework.codegen.tree + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgLiteral +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.tree.fieldmanager.ClassFieldManagerFacade +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtSpringContextModel +import org.utbot.framework.plugin.api.UtSpringEntityManagerModel +import org.utbot.framework.plugin.api.util.stringClassId + +class CgSpringVariableConstructor(context: CgContext) : CgVariableConstructor(context) { + + private val fieldManagerFacade = ClassFieldManagerFacade(context) + + fun clearAlreadyInitializedFieldModels() { + fieldManagerFacade.clearAlreadyInitializedModels() + } + + override fun getOrCreateVariable(model: UtModel, name: String?): CgValue { + val variable = fieldManagerFacade.constructVariableForField(model) + + variable?.let { return it } + + return when (model) { + is UtSpringContextModel, is UtSpringEntityManagerModel -> createDummyVariable(model) + else -> super.getOrCreateVariable(model, name) + } + } + + private fun createDummyVariable(model: UtModel): CgVariable { + // This is a kind of HACK + // Actually, this value is not supposed to be used in the generated code. + // However, this value existence is required for fields declaration process. + val dummyVariable = newVar(model.classId) { + CgLiteral(stringClassId, "dummy") + } + + valueByUtModelWrapper[model.wrap()] = dummyVariable + + return dummyVariable + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgAbstractClassFieldManager.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgAbstractClassFieldManager.kt new file mode 100644 index 0000000000..4a7accda5e --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgAbstractClassFieldManager.kt @@ -0,0 +1,100 @@ +package org.utbot.framework.codegen.tree.fieldmanager + +import org.utbot.framework.codegen.domain.UtModelWrapper +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.AnnotationTarget +import org.utbot.framework.codegen.domain.models.CgDeclaration +import org.utbot.framework.codegen.domain.models.CgFieldDeclaration +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.domain.models.builders.TypedModelWrappers +import org.utbot.framework.codegen.tree.CgComponents +import org.utbot.framework.codegen.tree.CgSpringVariableConstructor +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel + +abstract class CgAbstractClassFieldManager(context: CgContext) : + CgClassFieldManager, + CgContextOwner by context { + + val annotatedModels: MutableSet = mutableSetOf() + protected val modelGroupsProvider = ModelGroupsProvider(context) + + fun findCgValueByModel(model: UtModel, setOfModels: Set?): CgValue? { + val key = setOfModels?.find { it == model.wrap() } ?: return null + return valueByUtModelWrapper[key] + } + + protected abstract fun fieldWithAnnotationIsRequired(classId: ClassId): Boolean + + protected fun constructFieldsWithAnnotation(modelWrappers: Set): List { + val groupedModelsByClassId = modelWrappers.groupByClassId() + val annotation = statementConstructor.addAnnotation(annotationType, AnnotationTarget.Field) + + val constructedDeclarations = mutableListOf() + for ((classId, modelWrappers) in groupedModelsByClassId) { + + val modelWrapper = modelWrappers.firstOrNull() ?: continue + val model = modelWrapper.model + + val fieldWithAnnotationIsRequired = fieldWithAnnotationIsRequired(model.classId) + if (!fieldWithAnnotationIsRequired) { + continue + } + + val baseVarName = constructBaseVarName(model) + + /* + * `withNameScope` is used to avoid saving names for sub-models of model. + * + * Different models from different executions may have the same id. + * Therefore, when creating a variable for a new class field and using existing `currentTestSetId` and `currentExecutionId`, + * field`s `model.wrap()` may match one of the values in `annotatedModels`. + * To avoid false matches when creating a variable for a new class field, `withTestSetIdScope(-1)` and `withExecutionIdScope(-1)` are used. + */ + val createdVariable = withNameScope { + withTestSetIdScope(-1) { + withExecutionIdScope(-1) { + variableConstructor.getOrCreateVariable(model, baseVarName) as? CgVariable + ?: error("`CgVariable` cannot be constructed from a $model model") + } + } + } + existingVariableNames.add(createdVariable.name) + + val declaration = CgDeclaration(classId, variableName = createdVariable.name, initializer = null) + + constructedDeclarations += CgFieldDeclaration( + ownerClassId = currentTestClass, + declaration, + annotation + ) + + modelWrappers.forEach { modelWrapper -> + valueByUtModelWrapper[modelWrapper] = createdVariable + annotatedModels += modelWrapper + } + } + + return constructedDeclarations + } + + protected open fun constructBaseVarName(model: UtModel): String? = nameGenerator.nameFrom(model.classId) + + private fun Set.groupByClassId(): TypedModelWrappers { + val classModels = mutableMapOf>() + + for (modelGroup in this.groupBy { it.model.classId }) { + classModels[modelGroup.key] = modelGroup.value.toSet() + } + + return classModels + } + + protected val variableConstructor: CgSpringVariableConstructor by lazy { + CgComponents.getVariableConstructorBy(context) as CgSpringVariableConstructor + } + protected val nameGenerator = CgComponents.getNameGeneratorBy(context) + protected val statementConstructor = CgComponents.getStatementConstructorBy(context) +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgAutowiredFieldsManager.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgAutowiredFieldsManager.kt new file mode 100644 index 0000000000..be9c29e1d6 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgAutowiredFieldsManager.kt @@ -0,0 +1,42 @@ +package org.utbot.framework.codegen.tree.fieldmanager + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgFieldDeclaration +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.TestClassModel +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.util.SpringModelUtils +import org.utbot.framework.plugin.api.util.SpringModelUtils.getBeanNameOrNull +import org.utbot.framework.plugin.api.util.SpringModelUtils.isAutowiredFromContext + +class CgAutowiredFieldsManager(context: CgContext) : CgAbstractClassFieldManager(context) { + init { + relevantFieldManagers += this + } + + override val annotationType = SpringModelUtils.autowiredClassId + override fun createFieldDeclarations(testClassModel: TestClassModel): List { + val modelsByOrigin = modelGroupsProvider.collectModelsByOrigin(testClassModel) + val autowiredFromContextModels = modelsByOrigin + .stateBeforeDependentModels + .filterTo(HashSet()) { it.model.isAutowiredFromContext() } + + return constructFieldsWithAnnotation(autowiredFromContextModels) + } + + override fun useVariableForModel(model: UtModel, variable: CgValue) { + when { + model.isAutowiredFromContext() -> { + variableConstructor.constructAssembleForVariable(model as UtAssembleModel) + } + + else -> error("Trying to autowire model $model but it is not appropriate") + } + } + + override fun constructBaseVarName(model: UtModel): String? = model.getBeanNameOrNull() + + override fun fieldWithAnnotationIsRequired(classId: ClassId): Boolean = true +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgClassFieldManager.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgClassFieldManager.kt new file mode 100644 index 0000000000..8212d57d24 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgClassFieldManager.kt @@ -0,0 +1,18 @@ +package org.utbot.framework.codegen.tree.fieldmanager + +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.CgFieldDeclaration +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.TestClassModel +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel + +interface CgClassFieldManager : CgContextOwner { + + val annotationType: ClassId + + fun createFieldDeclarations(testClassModel: TestClassModel): List + + fun useVariableForModel(model: UtModel, variable: CgValue) +} + diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgInjectingMocksFieldsManager.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgInjectingMocksFieldsManager.kt new file mode 100644 index 0000000000..2e8a14740d --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgInjectingMocksFieldsManager.kt @@ -0,0 +1,57 @@ +package org.utbot.framework.codegen.tree.fieldmanager + +import org.utbot.framework.codegen.domain.builtin.injectMocksClassId +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgFieldDeclaration +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.TestClassModel +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtModelWithCompositeOrigin +import org.utbot.framework.plugin.api.isMockModel + +class CgInjectingMocksFieldsManager( + val context: CgContext, + private val mocksFieldsManager: CgMockedFieldsManager, + private val spiesFieldsManager: CgSpiedFieldsManager, + ) : CgAbstractClassFieldManager(context) { + init { + relevantFieldManagers += this + } + + override val annotationType = injectMocksClassId + + override fun createFieldDeclarations(testClassModel: TestClassModel): List { + val modelsByOrigin = modelGroupsProvider.collectModelsByOrigin(testClassModel) + return constructFieldsWithAnnotation(modelsByOrigin.thisInstanceModels) + } + + override fun useVariableForModel(model: UtModel, variable: CgValue) { + val modelFields = when (model) { + is UtCompositeModel -> model.fields + is UtModelWithCompositeOrigin -> model.origin?.fields + else -> null + } + + modelFields?.forEach { (fieldId, fieldModel) -> + // creating variables for modelVariable fields + val variableForField = variableConstructor.getOrCreateVariable(fieldModel) + + // is variable mocked by @Mock annotation + val isMocked = findCgValueByModel(fieldModel, mocksFieldsManager.annotatedModels) != null + + // is variable spied by @Spy annotation + val isSpied = findCgValueByModel(fieldModel, spiesFieldsManager.annotatedModels) != null + + // If field model is a mock model and is mocked by @Mock annotation in classFields or is spied by @Spy annotation, + // it is set in the connected with instance under test automatically via @InjectMocks. + // Otherwise, we need to set this field manually. + if ((!fieldModel.isMockModel() || !isMocked) && !isSpied) { + variableConstructor.setFieldValue(variable, fieldId, variableForField) + } + } + } + + override fun fieldWithAnnotationIsRequired(classId: ClassId): Boolean = true +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgMockedFieldsManager.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgMockedFieldsManager.kt new file mode 100644 index 0000000000..b9a3e81dc5 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgMockedFieldsManager.kt @@ -0,0 +1,53 @@ +package org.utbot.framework.codegen.tree.fieldmanager + +import org.utbot.framework.codegen.domain.builtin.mockClassId +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgFieldDeclaration +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.CgVariable +import org.utbot.framework.codegen.domain.models.TestClassModel +import org.utbot.framework.codegen.tree.CgComponents +import org.utbot.framework.codegen.tree.fieldmanager.MockitoInjectionUtils.canBeInjectedByTypeInto +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.isMockModel + +class CgMockedFieldsManager(context: CgContext) : CgAbstractClassFieldManager(context) { + init { + relevantFieldManagers += this + } + + override val annotationType = mockClassId + override fun createFieldDeclarations(testClassModel: TestClassModel): List { + val modelsByOrigin = modelGroupsProvider.collectModelsByOrigin(testClassModel) + + val dependentMockModels = + modelsByOrigin.thisInstanceDependentModels + .filterTo(mutableSetOf()) { cgModel -> + cgModel.model.isMockModel() && cgModel !in modelsByOrigin.thisInstanceModels + } + + return constructFieldsWithAnnotation(dependentMockModels) + } + + private val mockFrameworkManager = CgComponents.getMockFrameworkManagerBy(context) + override fun useVariableForModel(model: UtModel, variable: CgValue) { + if (!model.isMockModel()) { + error("$model does not represent a mock") + } + + mockFrameworkManager.createMockForVariable( + model as UtCompositeModel, + variable as CgVariable, + ) + + for ((fieldId, fieldModel) in model.fields) { + val variableForField = variableConstructor.getOrCreateVariable(fieldModel) + variableConstructor.setFieldValue(variable, fieldId, variableForField) + } + } + + override fun fieldWithAnnotationIsRequired(classId: ClassId): Boolean = + classId.canBeInjectedByTypeInto(classUnderTest) +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgPersistenceContextFieldsManager.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgPersistenceContextFieldsManager.kt new file mode 100644 index 0000000000..83bd944dad --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgPersistenceContextFieldsManager.kt @@ -0,0 +1,43 @@ +package org.utbot.framework.codegen.tree.fieldmanager + +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgFieldDeclaration +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.TestClassModel +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtSpringEntityManagerModel +import org.utbot.framework.plugin.api.util.SpringModelUtils + +class CgPersistenceContextFieldsManager private constructor( + context: CgContext, + override val annotationType: ClassId, +) : CgAbstractClassFieldManager(context) { + + init { + relevantFieldManagers += this + } + + companion object { + fun createIfPossible(context: CgContext): CgPersistenceContextFieldsManager? = SpringModelUtils.persistenceContextClassIds.firstOrNull() + ?.let { persistenceContextClassId -> CgPersistenceContextFieldsManager(context, persistenceContextClassId) } + } + + override fun createFieldDeclarations(testClassModel: TestClassModel): List { + val modelsByOrigin = modelGroupsProvider.collectModelsByOrigin(testClassModel) + val entityManagerModels = modelsByOrigin + .stateBeforeDependentModels + .filterTo(HashSet()) { it.model is UtSpringEntityManagerModel } + + return constructFieldsWithAnnotation(entityManagerModels) + } + + override fun useVariableForModel(model: UtModel, variable: CgValue) { + return when(model) { + is UtSpringEntityManagerModel -> {} + else -> error("Trying to use @PersistenceContext for model $model but it is not appropriate") + } + } + + override fun fieldWithAnnotationIsRequired(classId: ClassId): Boolean = true +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt new file mode 100644 index 0000000000..6c37ff3602 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/CgSpiedFieldsManager.kt @@ -0,0 +1,88 @@ +package org.utbot.framework.codegen.tree.fieldmanager + +import org.utbot.framework.codegen.domain.UtModelWrapper +import org.utbot.framework.codegen.domain.builtin.spyClassId +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.models.CgFieldDeclaration +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.codegen.domain.models.TestClassModel +import org.utbot.framework.codegen.services.framework.SpyFrameworkManager +import org.utbot.framework.codegen.tree.fieldmanager.MockitoInjectionUtils.canBeInjectedByTypeInto +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.canBeSpied +import org.utbot.framework.plugin.api.isMockModel +import org.utbot.framework.plugin.api.spiedTypes +import org.utbot.framework.plugin.api.util.jClass + +class CgSpiedFieldsManager(context: CgContext) : CgAbstractClassFieldManager(context) { + + init { + relevantFieldManagers += this + } + + override val annotationType = spyClassId + + override fun createFieldDeclarations(testClassModel: TestClassModel): List { + val modelsByOrigin = modelGroupsProvider.collectModelsByOrigin(testClassModel) + + val dependentMockModels = + modelsByOrigin.thisInstanceDependentModels + .filterTo(mutableSetOf()) { cgModel -> + cgModel.model.isMockModel() && cgModel !in modelsByOrigin.thisInstanceModels + } + + val dependentSpyModels = + modelsByOrigin.thisInstanceDependentModels + .filterTo(mutableSetOf()) { cgModel -> + cgModel.model.canBeSpied() && + cgModel !in modelsByOrigin.thisInstanceModels && + cgModel !in dependentMockModels + } + + val suitableSpyModels = getSuitableSpyModels(dependentSpyModels) + return constructFieldsWithAnnotation(suitableSpyModels) + } + + private val spyFrameworkManager = SpyFrameworkManager(context) + + override fun useVariableForModel(model: UtModel, variable: CgValue) { + if (!model.canBeSpied()) { + error("$model does not represent a spy") + } + spyFrameworkManager.spyForVariable( + model as UtAssembleModel, + ) + } + + override fun fieldWithAnnotationIsRequired(classId: ClassId): Boolean = + classId.canBeInjectedByTypeInto(classUnderTest) + + override fun constructBaseVarName(model: UtModel): String = super.constructBaseVarName(model) + "Spy" + + private fun getSuitableSpyModels(potentialSpyModels: MutableSet): Set = + spiedTypes.fold(setOf()) { spyModels, type -> + spyModels + getSuitableSpyModelsOfType(type, potentialSpyModels) + } + + /* + * Filters out cases when different tests use different [clazz] + * implementations and hence we need to inject different types. + * + * This limitation is reasoned by @InjectMocks behaviour. + * Otherwise, injection may be misleading: + * for example, several spies may be injected into one field. + */ + private fun getSuitableSpyModelsOfType( + clazz: Class<*>, + potentialSpyModels: MutableSet + ): Set { + val spyModelsAssignableFrom = potentialSpyModels + .filter { clazz.isAssignableFrom(it.model.classId.jClass) } + .toSet() + val spyModelsTypesCount = spyModelsAssignableFrom.map { it.model.classId }.toSet().size + + return if (spyModelsTypesCount == 1) spyModelsAssignableFrom else emptySet() + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/ClassFieldManagerFacade.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/ClassFieldManagerFacade.kt new file mode 100644 index 0000000000..3b61b4a403 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/ClassFieldManagerFacade.kt @@ -0,0 +1,51 @@ +package org.utbot.framework.codegen.tree.fieldmanager + +import org.utbot.common.getOrPut +import org.utbot.framework.codegen.domain.UtModelWrapper +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.context.CgContextProperty +import org.utbot.framework.codegen.domain.models.CgValue +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtSpringContextModel + +object RelevantFieldManagersProperty : CgContextProperty> + +/** + * Managers to process annotated fields of the class under test + * relevant for the current generation type. + */ +val CgContextOwner.relevantFieldManagers: MutableList + get() = properties.getOrPut(RelevantFieldManagersProperty) { mutableListOf() } + +class ClassFieldManagerFacade(context: CgContext) : CgContextOwner by context { + + private val alreadyInitializedModels = mutableSetOf() + + fun clearAlreadyInitializedModels() { + alreadyInitializedModels.clear() + } + + fun constructVariableForField(model: UtModel): CgValue? { + relevantFieldManagers.forEach { manager -> + val alreadyCreatedVariable = manager.findCgValueByModel(model, manager.annotatedModels) + if (alreadyCreatedVariable != null) { + if (alreadyInitializedModels.add(model.wrap())) + manager.useVariableForModel(model, alreadyCreatedVariable) + return alreadyCreatedVariable + } + } + + return null + } + + fun findTrustedModels(): List { + val trustedModels = mutableListOf(UtSpringContextModel.wrap()) + + relevantFieldManagers.forEach { manager -> + trustedModels += manager.annotatedModels + } + + return trustedModels + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/MockitoInjectionUtils.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/MockitoInjectionUtils.kt new file mode 100644 index 0000000000..5f5176257b --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/MockitoInjectionUtils.kt @@ -0,0 +1,14 @@ +package org.utbot.framework.codegen.tree.fieldmanager + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.util.allDeclaredFieldIds +import org.utbot.framework.plugin.api.util.isSubtypeOf + +object MockitoInjectionUtils { + /* + * If count of fields of the same type is 1, then we mock/spy variable by @Mock/@Spy annotation, + * otherwise we will create this variable by simple variable constructor. + */ + fun ClassId.canBeInjectedByTypeInto(classToInjectInto: ClassId): Boolean = + classToInjectInto.allDeclaredFieldIds.filter { isSubtypeOf(it.type) }.toList().size == 1 +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/ModelGroupsProvider.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/ModelGroupsProvider.kt new file mode 100644 index 0000000000..7b42558f12 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/codegen/tree/fieldmanager/ModelGroupsProvider.kt @@ -0,0 +1,128 @@ +package org.utbot.framework.codegen.tree.fieldmanager + +import org.utbot.framework.codegen.domain.UtModelWrapper +import org.utbot.framework.codegen.domain.context.CgContext +import org.utbot.framework.codegen.domain.context.CgContextOwner +import org.utbot.framework.codegen.domain.models.TestClassModel +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtClassRefModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtCustomModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtLambdaModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtStatementCallModel +import org.utbot.framework.plugin.api.UtVoidModel + +data class ModelsByOrigin( + val thisInstanceModels: Set, + val thisInstanceDependentModels: Set, + val stateBeforeDependentModels: Set, +) + +class ModelGroupsProvider(val context: CgContext): CgContextOwner by context { + + private val modelsCache = mutableMapOf() + + fun collectModelsByOrigin(testClassModel: TestClassModel): ModelsByOrigin { + if (testClassModel in modelsCache) { + return modelsCache[testClassModel]!! + } + + val thisInstanceModels = mutableSetOf() + val thisInstancesDependentModels = mutableSetOf() + val stateBeforeDependentModels = mutableSetOf() + + for ((testSetIndex, testSet) in testClassModel.methodTestSets.withIndex()) { + withTestSetIdScope(testSetIndex) { + for ((executionIndex, execution) in testSet.executions.withIndex()) { + withExecutionIdScope(executionIndex) { + setOf(execution.stateBefore.thisInstance, execution.stateAfter.thisInstance) + .filterNotNull() + .forEach { model -> + thisInstanceModels += model.wrap() + thisInstancesDependentModels += collectImmediateDependentModels( + model, + skipModificationChains = true + ) + } + + (execution.stateBefore.parameters + execution.stateBefore.thisInstance) + .filterNotNull() + .forEach { model -> stateBeforeDependentModels += collectRecursively(model) } + } + } + } + } + + val modelsByOrigin = ModelsByOrigin(thisInstanceModels, thisInstancesDependentModels, stateBeforeDependentModels) + modelsCache[testClassModel] = modelsByOrigin + + return modelsByOrigin + } + + private fun collectImmediateDependentModels(model: UtModel, skipModificationChains: Boolean): Set { + val dependentModels = mutableSetOf() + + when (model) { + is UtNullModel, + is UtPrimitiveModel, + is UtClassRefModel, + is UtVoidModel, + is UtEnumConstantModel, + is UtCustomModel -> {} + is UtLambdaModel -> { + model.capturedValues.forEach { dependentModels.add(it.wrap()) } + } + is UtArrayModel -> { + model.stores.values.forEach { dependentModels.add(it.wrap()) } + if (model.stores.count() < model.length) { + dependentModels.add(model.constModel.wrap()) + } + } + is UtCompositeModel -> { + // Here we traverse fields only. + // Traversing mocks as well will result in wrong models playing + // a role of class fields with @Mock annotation. + model.fields.forEach { (_, model) -> dependentModels.add(model.wrap()) } + } + is UtAssembleModel -> { + model.instantiationCall.instance?.let { dependentModels.add(it.wrap()) } + model.instantiationCall.params.forEach { dependentModels.add(it.wrap()) } + + if(!skipModificationChains) { + model.modificationsChain.forEach { stmt -> + stmt.instance?.let { dependentModels.add(it.wrap()) } + when (stmt) { + is UtStatementCallModel -> stmt.params.forEach { dependentModels.add(it.wrap()) } + is UtDirectSetFieldModel -> dependentModels.add(stmt.fieldModel.wrap()) + } + } + } + } + } + + return dependentModels + } + + private fun collectRecursively(model: UtModel): Set { + val allDependentModels = mutableSetOf() + + collectRecursively(model, allDependentModels) + + return allDependentModels + } + + private fun collectRecursively(model: UtModel, allDependentModels: MutableSet){ + if(!allDependentModels.add(model.wrap())){ + return + } + collectImmediateDependentModels(model, skipModificationChains = false).forEach { + collectRecursively(it.model, allDependentModels) + } + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContext.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContext.kt new file mode 100644 index 0000000000..458c9db3f6 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContext.kt @@ -0,0 +1,25 @@ +package org.utbot.framework.context.spring + +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.plugin.api.BeanDefinitionData +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConcreteContextLoadingResult +import org.utbot.framework.plugin.api.SpringSettings + +/** + * Data we get from Spring application context + * to manage engine and code generator behaviour. + */ +interface SpringApplicationContext : ApplicationContext { + val springSettings: SpringSettings + + /** + * Describes bean definitions (bean name, type, some optional additional data) + */ + val beanDefinitions: List + val injectedTypes: Set + val allInjectedSuperTypes: Set + + var concreteContextLoadingResult: ConcreteContextLoadingResult? + fun getBeansAssignableTo(classId: ClassId): List +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt new file mode 100644 index 0000000000..bec479112e --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringApplicationContextImpl.kt @@ -0,0 +1,211 @@ +package org.utbot.framework.context.spring + +import mu.KotlinLogging +import org.utbot.common.dynamicPropertiesOf +import org.utbot.common.isAbstract +import org.utbot.common.isStatic +import org.utbot.common.withValue +import org.utbot.external.api.UtBotSpringApi +import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.generator.AbstractCodeGenerator +import org.utbot.framework.codegen.generator.CodeGeneratorParams +import org.utbot.framework.codegen.generator.SpringCodeGenerator +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.context.NonNullSpeculator +import org.utbot.framework.context.TypeReplacer +import org.utbot.framework.context.custom.CoverageFilteringConcreteExecutionContext +import org.utbot.framework.context.custom.RerunningConcreteExecutionContext +import org.utbot.framework.context.custom.useMocks +import org.utbot.framework.context.utils.transformInstrumentationFactory +import org.utbot.framework.context.utils.transformJavaFuzzingContext +import org.utbot.framework.context.utils.transformValueProvider +import org.utbot.framework.plugin.api.BeanDefinitionData +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConcreteContextLoadingResult +import org.utbot.framework.plugin.api.SpringSettings +import org.utbot.framework.plugin.api.SpringTestType +import org.utbot.framework.plugin.api.util.SpringModelUtils.entityClassIds +import org.utbot.framework.plugin.api.util.allSuperTypes +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.fuzzing.spring.JavaLangObjectValueProvider +import org.utbot.fuzzing.spring.FuzzedTypeFlag +import org.utbot.fuzzing.spring.addProperties +import org.utbot.fuzzing.spring.decorators.replaceTypes +import org.utbot.fuzzing.spring.properties +import org.utbot.fuzzing.spring.unit.InjectMockValueProvider +import org.utbot.fuzzing.toFuzzerType +import org.utbot.instrumentation.instrumentation.execution.RemovingConstructFailsUtExecutionInstrumentation + +class SpringApplicationContextImpl private constructor( + private val delegateContext: ApplicationContext, + override val beanDefinitions: List, + private val springTestType: SpringTestType, + override val springSettings: SpringSettings, +): ApplicationContext by delegateContext, SpringApplicationContext { + companion object { + private val logger = KotlinLogging.logger {} + + /** + * Used internally by UtBot to create an instance of [SpringApplicationContextImpl] + * when [beanDefinitions] are already known. + * + * NOTE: Bean definitions defined in config from [springSettings] are IGNORED. + * + * API users should use [UtBotSpringApi.createSpringApplicationContext] + */ + fun internalCreate( + delegateContext: ApplicationContext, + beanDefinitions: List, + springTestType: SpringTestType, + springSettings: SpringSettings, + ) = SpringApplicationContextImpl(delegateContext, beanDefinitions, springTestType, springSettings) + } + + private object ReplacedFuzzedTypeFlag : FuzzedTypeFlag + + override val typeReplacer: TypeReplacer = SpringTypeReplacer(delegateContext.typeReplacer, this) + override val nonNullSpeculator: NonNullSpeculator = SpringNonNullSpeculator(delegateContext.nonNullSpeculator, this) + + override var concreteContextLoadingResult: ConcreteContextLoadingResult? = null + + override fun createConcreteExecutionContext( + fullClasspath: String, + classpathWithoutDependencies: String + ): ConcreteExecutionContext { + var delegateConcreteExecutionContext = delegateContext.createConcreteExecutionContext( + fullClasspath, + classpathWithoutDependencies + ).transformValueProvider { valueProvider -> + valueProvider.with(JavaLangObjectValueProvider( + classesToTryUsingAsJavaLangObject = listOf(objectClassId, classUnderTest) + )) + } + + // to avoid filtering out all coverage, we only filter + // coverage when `classpathWithoutDependencies` is provided + // (e.g. when we are launched from IDE plugin) + if (classpathWithoutDependencies.isNotEmpty()) + delegateConcreteExecutionContext = CoverageFilteringConcreteExecutionContext( + delegateContext = delegateConcreteExecutionContext, + classpathToIncludeCoverageFrom = classpathWithoutDependencies, + annotationsToIgnoreCoverage = entityClassIds.toSet(), + keepOriginalCoverageOnEmptyFilteredCoverage = true + ) + + return when (springTestType) { + SpringTestType.UNIT_TEST -> delegateConcreteExecutionContext.transformJavaFuzzingContext { fuzzingContext -> + fuzzingContext + .useMocks { type -> + ReplacedFuzzedTypeFlag !in type.properties && + mockStrategy.eligibleToMock( + classToMock = type.classId, + classUnderTest = fuzzingContext.classUnderTest + ) + } + .transformValueProvider { origValueProvider -> + InjectMockValueProvider( + idGenerator = fuzzingContext.idGenerator, + classUnderTest = fuzzingContext.classUnderTest, + isFieldNonNull = { fieldId -> + nonNullSpeculator.speculativelyCannotProduceNullPointerException(fieldId, classUnderTest) + }, + ) + .withFallback(origValueProvider) + .replaceTypes { description, type -> + typeReplacer.replaceTypeIfNeeded(type.classId) + ?.let { replacementClassId -> + // TODO infer generic type of replacement + val replacement = + if (type.classId == replacementClassId) type + else toFuzzerType(replacementClassId.jClass, description.typeCache) + replacement.addProperties( + dynamicPropertiesOf(ReplacedFuzzedTypeFlag.withValue(Unit)) + ) + } ?: type + } + } + } + SpringTestType.INTEGRATION_TEST -> + RerunningConcreteExecutionContext( + SpringIntegrationTestConcreteExecutionContext( + delegateConcreteExecutionContext, + classpathWithoutDependencies, + springApplicationContext = this + ), + maxRerunsPerMethod = UtSettings.maxSpringContextResetsPerMethod + ) + }.transformInstrumentationFactory { delegateInstrumentationFactory -> + RemovingConstructFailsUtExecutionInstrumentation.Factory(delegateInstrumentationFactory) + } + } + + override fun createCodeGenerator(params: CodeGeneratorParams): AbstractCodeGenerator = + // TODO decorate original `delegateContext.createCodeGenerator(params)` + SpringCodeGenerator( + springTestType = springTestType, + springSettings = springSettings, + concreteContextLoadingResult = concreteContextLoadingResult, + params = params, + ) + + override fun getBeansAssignableTo(classId: ClassId): List = beanDefinitions.filter { beanDef -> + // some bean classes may fail to load + runCatching { + val beanClass = ClassId(beanDef.beanTypeName).jClass + classId.jClass.isAssignableFrom(beanClass) + }.getOrElse { false } + } + + // Classes representing concrete types that are actually used in Spring application + override val injectedTypes: Set + get() { + if (!areAllInjectedSuperTypesInitialized) { + for (beanTypeName in beanDefinitions.map { it.beanTypeName }) { + try { + val beanClass = utContext.classLoader.loadClass(beanTypeName) + if (!beanClass.isAbstract && !beanClass.isInterface && + !beanClass.isLocalClass && (!beanClass.isMemberClass || beanClass.isStatic)) { + _injectedTypes += beanClass.id + } + } catch (e: Throwable) { + // For some Spring beans (e.g. with anonymous classes) + // it is possible to have problems with classes loading. + when (e) { + is ClassNotFoundException, is NoClassDefFoundError, is IllegalAccessError -> + logger.warn { "Failed to load bean class for $beanTypeName (${e.message})" } + + else -> throw e + } + } + } + + // This is done to be sure that this storage is not empty after the first class loading iteration. + // So, even if all loaded classes were filtered out, we will not try to load them again. + areAllInjectedSuperTypesInitialized = true + } + + return _injectedTypes + } + + override val allInjectedSuperTypes: Set + get() { + if (!areInjectedTypesInitialized) { + _allInjectedSuperTypes = injectedTypes.flatMap { it.allSuperTypes() }.toSet() + areInjectedTypesInitialized = true + } + + return _allInjectedSuperTypes + } + + // the following properties help to imitate `by lazy` behaviour, do not use them directly + // (we can't use actual `by lazy` because communication via RD breaks it) + private var _allInjectedSuperTypes: Set = emptySet() + private var areAllInjectedSuperTypesInitialized : Boolean = false + + private val _injectedTypes = mutableSetOf() + private var areInjectedTypesInitialized: Boolean = false +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestConcreteExecutionContext.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestConcreteExecutionContext.kt new file mode 100644 index 0000000000..12fc787d9b --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestConcreteExecutionContext.kt @@ -0,0 +1,94 @@ +package org.utbot.framework.context.spring + +import mu.KotlinLogging +import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.context.ConcreteExecutionContext.FuzzingContextParams +import org.utbot.framework.context.JavaFuzzingContext +import org.utbot.framework.plugin.api.ConcreteContextLoadingResult +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.SpringSettings +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.isSuccess +import org.utbot.framework.plugin.api.util.SpringModelUtils +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.getRelevantSpringRepositories +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation +import org.utbot.instrumentation.instrumentation.spring.SpringUtExecutionInstrumentation +import org.utbot.instrumentation.tryLoadingSpringContext +import java.io.File + +class SpringIntegrationTestConcreteExecutionContext( + private val delegateContext: ConcreteExecutionContext, + classpathWithoutDependencies: String, + private val springApplicationContext: SpringApplicationContext, +) : ConcreteExecutionContext by delegateContext { + private val springSettings = (springApplicationContext.springSettings as? SpringSettings.PresentSpringSettings) ?: + error("Integration tests cannot be generated without Spring configuration") + + companion object { + private val logger = KotlinLogging.logger {} + } + + override val instrumentationFactory: UtExecutionInstrumentation.Factory<*> = + SpringUtExecutionInstrumentation.Factory( + delegateContext.instrumentationFactory, + springSettings, + springApplicationContext.beanDefinitions, + buildDirs = classpathWithoutDependencies.split(File.pathSeparator) + .map { File(it).toURI().toURL() } + .toTypedArray(), + ) + + override fun loadContext( + concreteExecutor: ConcreteExecutor, + ): ConcreteContextLoadingResult = + delegateContext.loadContext(concreteExecutor).andThen { + springApplicationContext.concreteContextLoadingResult ?: concreteExecutor.tryLoadingSpringContext().also { + springApplicationContext.concreteContextLoadingResult = it + } + } + + override fun tryCreateFuzzingContext(params: FuzzingContextParams): JavaFuzzingContext { + if (springApplicationContext.getBeansAssignableTo(params.classUnderTest).isEmpty()) + error( + "No beans of type ${params.classUnderTest} are found. " + + "Try choosing different Spring configuration or adding beans to " + + springSettings.configuration.fullDisplayName + ) + + val relevantRepositories = params.concreteExecutor.getRelevantSpringRepositories(params.classUnderTest) + logger.info { "Detected relevant repositories for class ${params.classUnderTest}: $relevantRepositories" } + + return SpringIntegrationTestJavaFuzzingContext( + delegateContext = delegateContext.tryCreateFuzzingContext(params), + relevantRepositories = relevantRepositories, + springApplicationContext = springApplicationContext, + fuzzingStartTimeMillis = params.fuzzingStartTimeMillis, + fuzzingEndTimeMillis = params.fuzzingEndTimeMillis, + ) + } + + override fun transformExecutionsBeforeMinimization( + executions: List, + methodUnderTest: ExecutableId + ): List { + val (mockMvcExecutions, regularExecutions) = + delegateContext.transformExecutionsBeforeMinimization(executions, methodUnderTest) + .partition { it.executableToCall == SpringModelUtils.mockMvcPerformMethodId } + + val classUnderTestName = methodUnderTest.classId.name + val methodUnderTestSignature = methodUnderTest.signature + + fun UtExecution.getMethodUnderTestCoverage(): List? = + coverage?.coveredInstructions?.filter { + it.className == classUnderTestName && it.methodSignature == methodUnderTestSignature + }?.map { it.id } + + fun UtExecution.getKey() = getMethodUnderTestCoverage()?.let { it to result.isSuccess } + + val mockMvcExecutionKeys = mockMvcExecutions.mapNotNullTo(mutableSetOf()) { it.getKey() } + + return mockMvcExecutions + regularExecutions.filter { it.getKey() !in mockMvcExecutionKeys } + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestJavaFuzzingContext.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestJavaFuzzingContext.kt new file mode 100644 index 0000000000..0b22966f15 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringIntegrationTestJavaFuzzingContext.kt @@ -0,0 +1,144 @@ +package org.utbot.framework.context.spring + +import mu.KotlinLogging +import org.utbot.common.tryLoadClass +import org.utbot.framework.context.JavaFuzzingContext +import org.utbot.framework.fuzzer.IdentityPreservingIdGenerator +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.SpringRepositoryId +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtSpringMockMvcResultActionsModel +import org.utbot.framework.plugin.api.util.SpringModelUtils +import org.utbot.framework.plugin.api.util.SpringModelUtils.allControllerParametersAreSupported +import org.utbot.framework.plugin.api.util.allDeclaredFieldIds +import org.utbot.framework.plugin.api.util.jField +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.fuzzing.JavaValueProvider +import org.utbot.fuzzing.ValueProvider +import org.utbot.fuzzing.providers.AnyDepthNullValueProvider +import org.utbot.fuzzing.spring.decorators.ModifyingWithMethodsProviderWrapper +import org.utbot.fuzzing.providers.ObjectValueProvider +import org.utbot.fuzzing.spring.GeneratedFieldValueProvider +import org.utbot.fuzzing.spring.SpringBeanValueProvider +import org.utbot.fuzzing.spring.decorators.preserveProperties +import org.utbot.fuzzing.spring.valid.EmailValueProvider +import org.utbot.fuzzing.spring.valid.NotBlankStringValueProvider +import org.utbot.fuzzing.spring.valid.NotEmptyStringValueProvider +import org.utbot.fuzzing.spring.valid.ValidEntityValueProvider +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult + +class SpringIntegrationTestJavaFuzzingContext( + private val delegateContext: JavaFuzzingContext, + relevantRepositories: Set, + springApplicationContext: SpringApplicationContext, + private val fuzzingStartTimeMillis: Long, + private val fuzzingEndTimeMillis: Long, +) : JavaFuzzingContext by delegateContext { + companion object { + private val logger = KotlinLogging.logger {} + } + + private val springBeanValueProvider: JavaValueProvider = + SpringBeanValueProvider( + idGenerator, + beanNameProvider = { classId -> + springApplicationContext.getBeansAssignableTo(classId).map { it.beanName } + }, + relevantRepositories = relevantRepositories + ) + + override val valueProvider: JavaValueProvider = + springBeanValueProvider.withModifyingMethodsBuddy() + .withFallback(ValidEntityValueProvider(idGenerator, onlyAcceptWhenValidIsRequired = true).withModifyingMethodsBuddy()) + .withFallback(EmailValueProvider()) + .withFallback(NotBlankStringValueProvider()) + .withFallback(NotEmptyStringValueProvider()) + .withFallback( + delegateContext.valueProvider + .with(ObjectValueProvider(idGenerator).withModifyingMethodsBuddy()) + .with(ValidEntityValueProvider(idGenerator, onlyAcceptWhenValidIsRequired = false).withModifyingMethodsBuddy()) + .with(createGeneratedFieldValueProviders(relevantRepositories, idGenerator)) + .withFallback(AnyDepthNullValueProvider) + ) + .preserveProperties() + + private fun JavaValueProvider.withModifyingMethodsBuddy(): JavaValueProvider = + with(modifyingMethodsBuddy(this)) + + private fun modifyingMethodsBuddy(provider: JavaValueProvider): JavaValueProvider = + ModifyingWithMethodsProviderWrapper(classUnderTest, provider) + + + private fun createGeneratedFieldValueProviders( + relevantRepositories: Set, + idGenerator: IdentityPreservingIdGenerator + ): JavaValueProvider { + val generatedValueAnnotationClasses = SpringModelUtils.generatedValueClassIds.mapNotNull { + @Suppress("UNCHECKED_CAST") // type system fails to understand that @GeneratedValue is indeed an annotation + utContext.classLoader.tryLoadClass(it.name) as Class? + } + + val generatedValueFields = + relevantRepositories + .flatMap { springRepositoryId -> + val entityClassId = springRepositoryId.entityClassId + entityClassId.allDeclaredFieldIds + .filter { fieldId -> generatedValueAnnotationClasses.any { fieldId.jField.isAnnotationPresent(it) } } + .map { entityClassId to it } + } + + logger.info { "Detected @GeneratedValue fields: $generatedValueFields" } + + return ValueProvider.of(generatedValueFields.map { (entityClassId, fieldId) -> + GeneratedFieldValueProvider(idGenerator, entityClassId, fieldId) + }) + } + + private val methodsSuccessfullyCalledViaMockMvc = mutableSetOf() + + override fun createStateBefore( + thisInstance: UtModel?, + parameters: List, + statics: Map, + executableToCall: ExecutableId + ): EnvironmentModels { + val delegateStateBefore = delegateContext.createStateBefore(thisInstance, parameters, statics, executableToCall) + return when (executableToCall) { + is ConstructorId -> delegateStateBefore + is MethodId -> { + val requestBuilderModel = SpringModelUtils.createRequestBuilderModelOrNull( + methodId = executableToCall, + arguments = parameters, + idGenerator = { idGenerator.createId() } + )?.takeIf { + val halfOfFuzzingTimePassed = System.currentTimeMillis() > (fuzzingStartTimeMillis + fuzzingEndTimeMillis) / 2 + val allParamsSupported = allControllerParametersAreSupported(executableToCall) + val successfullyCalledViaMockMvc = executableToCall in methodsSuccessfullyCalledViaMockMvc + !halfOfFuzzingTimePassed || allParamsSupported && successfullyCalledViaMockMvc + } ?: return delegateStateBefore + + delegateStateBefore.copy( + thisInstance = SpringModelUtils.createMockMvcModel(controller = thisInstance) { idGenerator.createId() }, + parameters = listOf(requestBuilderModel), + executableToCall = SpringModelUtils.mockMvcPerformMethodId, + ) + } + } + } + + override fun handleFuzzedConcreteExecutionResult( + methodUnderTest: ExecutableId, + concreteExecutionResult: UtConcreteExecutionResult + ) { + delegateContext.handleFuzzedConcreteExecutionResult(methodUnderTest, concreteExecutionResult) + ((concreteExecutionResult.result as? UtExecutionSuccess)?.model as? UtSpringMockMvcResultActionsModel)?.let { + if (it.status < 400) + methodsSuccessfullyCalledViaMockMvc.add(methodUnderTest) + } + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringNonNullSpeculator.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringNonNullSpeculator.kt new file mode 100644 index 0000000000..4bd44f01a2 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringNonNullSpeculator.kt @@ -0,0 +1,114 @@ +package org.utbot.framework.context.spring + +import mu.KotlinLogging +import org.utbot.framework.context.NonNullSpeculator +import org.utbot.framework.context.spring.SpringNonNullSpeculator.InjectionKind.* +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.util.SpringModelUtils.autowiredClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.componentClassId +import org.utbot.framework.plugin.api.util.SpringModelUtils.injectClassIds +import org.utbot.framework.plugin.api.util.allDeclaredFieldIds +import org.utbot.framework.plugin.api.util.executable +import org.utbot.framework.plugin.api.util.fieldId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.jField +import org.utbot.framework.plugin.api.util.jFieldOrNull +import org.utbot.modifications.ExecutableAnalyzer +import soot.SootField +import java.lang.reflect.AnnotatedElement + +class SpringNonNullSpeculator( + private val delegateNonNullSpeculator: NonNullSpeculator, + private val springApplicationContext: SpringApplicationContext +) : NonNullSpeculator { + private val executableAnalyzer = ExecutableAnalyzer() + + companion object { + private val logger = KotlinLogging.logger {} + } + + override fun speculativelyCannotProduceNullPointerException( + field: SootField, + classUnderTest: ClassId, + ): Boolean = + ((field.jFieldOrNull?.let { it.fieldId in getInjectedFields(it.declaringClass.id) } ?: false) || + delegateNonNullSpeculator.speculativelyCannotProduceNullPointerException(field, classUnderTest)) + + override fun speculativelyCannotProduceNullPointerException(field: FieldId, classUnderTest: ClassId): Boolean = + (field in getInjectedFields(field.declaringClass) || + delegateNonNullSpeculator.speculativelyCannotProduceNullPointerException(field, classUnderTest)) + + private val injectedFieldsCache = mutableMapOf>() + + /** + * As described in [this issue](https://github.com/UnitTestBot/UTBotJava/issues/2589) we should consider + * `FieldType fieldName` to be non-`null`, iff one of the following statements holds: + * - the field itself is annotated with `@Autowired(required=true)`, `@Autowired` or `@Inject` + * - all the following statements hold: + * - one of the following statements hold: + * - there’s a constructor or a method annotated with `@Autowired(required=true)`, `@Autowired` or `@Inject` + * - there’s only one constructor defined in the class, said constructor is not annotated with + * `@Autowired(required=false)`, while the class itself is annotated with `@Component`-like annotation + * (either `@Component` itself or other annotation that is itself annotated with `@Component`) + * - said constructor/method accepts a parameter of type `FieldType` (or its subtype) + * - said parameter is not annotated with `@Nullable` nor with `@Autowired(required=false)` + * - said constructor/method contains an assignment of said parameter to `fieldName` + */ + private fun getInjectedFields(classId: ClassId): Set = injectedFieldsCache.getOrPut(classId) { + try { + (classId.allDeclaredFieldIds.filter { it.jField.getInjectionAnnotationKind() == INJECTED_AND_REQUIRED } + + classId.getInjectingExecutables().flatMap { injectingExecutable -> + executableAnalyzer.analyze(injectingExecutable).params + .map { (paramIdx, fieldId) -> injectingExecutable.executable.parameters[paramIdx] to fieldId } + .filter { (param, fieldId) -> + fieldId.type.jClass.isAssignableFrom(param.type) && + param.annotations.none { + it.annotationClass.simpleName?.endsWith("Nullable") ?: false + } && + param.getInjectionAnnotationKind() != INJECTED_BUT_NOT_REQUIRED + } + .map { (_, fieldId) -> fieldId } + }).toSet().also { + logger.debug { "Injected fields for $classId: $it" } + } + } catch (e: Throwable) { + logger.warn(e) { "Failed to determine injected fields for class $classId" } + emptySet() + } + } + + private fun ClassId.getInjectingExecutables(): Sequence { + return (allConstructors + allMethods).filter { + it.executable.getInjectionAnnotationKind() == INJECTED_AND_REQUIRED + } + listOfNotNull(allConstructors.singleOrNull()?.takeIf { constructor -> + isBeanDefiningClass() && constructor.executable.getInjectionAnnotationKind() == NOT_INJECTED + }) + } + + private fun ClassId.isBeanDefiningClass(): Boolean = + this in springApplicationContext.injectedTypes || jClass.annotations.any { it.isComponentLike() } + + private fun Annotation.isComponentLike() = + annotationClass.id == componentClassId || + annotationClass.annotations.any { it.annotationClass.id == componentClassId } + + private fun AnnotatedElement.getInjectionAnnotationKind(): InjectionKind { + if(annotations.any { it.annotationClass.id in injectClassIds }) + return INJECTED_AND_REQUIRED + val autowiredAnnotation = annotations.firstOrNull { it.annotationClass.id == autowiredClassId } + ?: return NOT_INJECTED + return if (autowiredAnnotation.annotationClass.java.getMethod("required").invoke(autowiredAnnotation) as Boolean) + INJECTED_AND_REQUIRED + else + INJECTED_BUT_NOT_REQUIRED + } + + private enum class InjectionKind { + NOT_INJECTED, + INJECTED_BUT_NOT_REQUIRED, + INJECTED_AND_REQUIRED, + } +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringTypeReplacer.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringTypeReplacer.kt new file mode 100644 index 0000000000..b8a9e7de77 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/context/spring/SpringTypeReplacer.kt @@ -0,0 +1,23 @@ +package org.utbot.framework.context.spring + +import org.utbot.framework.context.TypeReplacer +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.TypeReplacementMode +import org.utbot.framework.plugin.api.util.isAbstract +import org.utbot.framework.plugin.api.util.isSubtypeOf + +class SpringTypeReplacer( + private val delegateTypeReplacer: TypeReplacer, + private val springApplicationContext: SpringApplicationContext +) : TypeReplacer { + override val typeReplacementMode: TypeReplacementMode = + if (springApplicationContext.beanDefinitions.isNotEmpty() || + delegateTypeReplacer.typeReplacementMode == TypeReplacementMode.KnownImplementor) + TypeReplacementMode.KnownImplementor + else + TypeReplacementMode.NoImplementors + + override fun replaceTypeIfNeeded(classId: ClassId): ClassId? = + if (classId.isAbstract) springApplicationContext.injectedTypes.singleOrNull { it.isSubtypeOf(classId) } + else delegateTypeReplacer.replaceTypeIfNeeded(classId) +} \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/plugin/api/utils/SpringDependencyPatterns.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/plugin/api/utils/SpringDependencyPatterns.kt new file mode 100644 index 0000000000..8f18fda134 --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/plugin/api/utils/SpringDependencyPatterns.kt @@ -0,0 +1,77 @@ +package org.utbot.framework.plugin.api.utils + +import org.utbot.framework.codegen.domain.SpringModule +import org.utbot.framework.codegen.domain.SpringModule.* + +fun SpringModule.patterns(): Patterns { + val moduleLibraryPatterns = when (this) { + SPRING_BOOT -> springBootModulePatterns + SPRING_BEANS -> springBeansModulePatterns + SPRING_SECURITY -> springSecurityModulePatterns + } + val libraryPatterns = when (this) { + SPRING_BOOT -> springBootPatterns + SPRING_BEANS -> springBeansPatterns + SPRING_SECURITY -> springSecurityPatterns + } + + return Patterns(moduleLibraryPatterns, libraryPatterns) +} + +fun SpringModule.testPatterns(): Patterns { + val moduleLibraryPatterns = when (this) { + SPRING_BOOT -> springBootTestModulePatterns + SPRING_BEANS -> springBeansTestModulePatterns + SPRING_SECURITY -> springSecurityTestModulePatterns + } + val libraryPatterns = when (this) { + SPRING_BOOT -> springBootTestPatterns + SPRING_BEANS -> springBeansTestPatterns + SPRING_SECURITY -> springSecurityTestPatterns + } + + return Patterns(moduleLibraryPatterns, libraryPatterns) +} + +val SPRING_BEANS_JAR_PATTERN = Regex("spring-beans-([0-9]+)(\\.[0-9]+){1,2}") +val SPRING_BEANS_MVN_PATTERN = Regex("org\\.springframework:spring-beans:([0-9]+)(\\.[0-9]+){1,2}") +val springBeansPatterns = listOf(SPRING_BEANS_JAR_PATTERN, SPRING_BEANS_MVN_PATTERN) + +val SPRING_BEANS_BASIC_MODULE_PATTERN = Regex("spring-beans") +val springBeansModulePatterns = listOf(SPRING_BEANS_BASIC_MODULE_PATTERN) + +val SPRING_BEANS_TEST_JAR_PATTERN = Regex("spring-test-([0-9]+)(\\.[0-9]+){1,2}") +val SPRING_BEANS_TEST_MVN_PATTERN = Regex("org\\.springframework:spring-test:([0-9]+)(\\.[0-9]+){1,2}") +val springBeansTestPatterns = listOf(SPRING_BEANS_TEST_JAR_PATTERN, SPRING_BEANS_TEST_MVN_PATTERN) + +val SPRING_BEANS_TEST_BASIC_MODULE_PATTERN = Regex("spring-test") +val springBeansTestModulePatterns = listOf(SPRING_BEANS_TEST_BASIC_MODULE_PATTERN) + +val SPRING_BOOT_JAR_PATTERN = Regex("spring-boot-([0-9]+)(\\.[0-9]+){1,2}") +val SPRING_BOOT_MVN_PATTERN = Regex("org\\.springframework\\.boot:spring-boot:([0-9]+)(\\.[0-9]+){1,2}") +val springBootPatterns = listOf(SPRING_BOOT_JAR_PATTERN, SPRING_BOOT_MVN_PATTERN) + +val SPRING_BOOT_BASIC_MODULE_PATTERN = Regex("spring-boot") +val springBootModulePatterns = listOf(SPRING_BOOT_BASIC_MODULE_PATTERN) + +val SPRING_BOOT_TEST_JAR_PATTERN = Regex("spring-boot-test-([0-9]+)(\\.[0-9]+){1,2}") +val SPRING_BOOT_TEST_MVN_PATTERN = Regex("org\\.springframework\\.boot:spring-boot-test:([0-9]+)(\\.[0-9]+){1,2}") + +val springBootTestPatterns = listOf(SPRING_BOOT_TEST_JAR_PATTERN, SPRING_BOOT_TEST_MVN_PATTERN) + +val SPRING_BOOT_TEST_BASIC_MODULE_PATTERN = Regex("spring-boot-test") +val springBootTestModulePatterns = listOf(SPRING_BOOT_TEST_BASIC_MODULE_PATTERN) + +val SPRING_SECURITY_JAR_PATTERN = Regex("spring-security-core-([0-9]+)(\\.[0-9]+){1,2}") +val SPRING_SECURITY_MVN_PATTERN = Regex("org\\.springframework\\.security:spring-security-core:([0-9]+)(\\.[0-9]+){1,2}") +val springSecurityPatterns = listOf(SPRING_SECURITY_JAR_PATTERN, SPRING_SECURITY_MVN_PATTERN) + +val SPRING_SECURITY_BASIC_MODULE_PATTERN = Regex("spring-security-core") +val springSecurityModulePatterns = listOf(SPRING_SECURITY_BASIC_MODULE_PATTERN) + +val SPRING_SECURITY_TEST_JAR_PATTERN = Regex("spring-security-test-([0-9]+)(\\.[0-9]+){1,2}") +val SPRING_SECURITY_TEST_MVN_PATTERN = Regex("org\\.springframework\\.security:spring-security-test:([0-9]+)(\\.[0-9]+){1,2}") +val springSecurityTestPatterns = listOf(SPRING_SECURITY_TEST_JAR_PATTERN, SPRING_SECURITY_TEST_MVN_PATTERN) + +val SPRING_SECURITY_TEST_BASIC_MODULE_PATTERN = Regex("spring-security-test") +val springSecurityTestModulePatterns = listOf(SPRING_SECURITY_TEST_BASIC_MODULE_PATTERN) \ No newline at end of file diff --git a/utbot-spring-framework/src/main/kotlin/org/utbot/framework/process/SpringAnalyzerTask.kt b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/process/SpringAnalyzerTask.kt new file mode 100644 index 0000000000..b4f3ea2a2f --- /dev/null +++ b/utbot-spring-framework/src/main/kotlin/org/utbot/framework/process/SpringAnalyzerTask.kt @@ -0,0 +1,41 @@ +package org.utbot.framework.process + +import mu.KotlinLogging +import org.utbot.framework.plugin.api.BeanAdditionalData +import org.utbot.framework.plugin.api.BeanDefinitionData +import org.utbot.framework.plugin.api.SpringSettings.PresentSpringSettings +import org.utbot.rd.use +import org.utbot.spring.process.SpringAnalyzerProcess + +class SpringAnalyzerTask( + private val classpath: List, + private val settings: PresentSpringSettings, +) : EngineProcessTask> { + companion object { + private val logger = KotlinLogging.logger {} + } + + override fun perform(): List = try { + SpringAnalyzerProcess.createBlocking(classpath).use { + it.getBeanDefinitions(settings) + }.beanDefinitions + .map { data -> + // mapping RD models to API models + val additionalData = data.additionalData?.let { + BeanAdditionalData( + it.factoryMethodName, + it.parameterTypes, + it.configClassFqn + ) + } + BeanDefinitionData( + data.beanName, + data.beanTypeFqn, + additionalData + ) + } + } catch (e: Exception) { + logger.error(e) { "Spring Analyzer crashed, resorting to using empty bean list" } + emptyList() + } +} \ No newline at end of file diff --git a/utbot-spring-sample/build.gradle.kts b/utbot-spring-sample/build.gradle.kts new file mode 100644 index 0000000000..bd0b3b4ea3 --- /dev/null +++ b/utbot-spring-sample/build.gradle.kts @@ -0,0 +1,45 @@ +import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer +import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer + +plugins { + id("com.github.johnrengelman.shadow") version "7.1.2" + id("java") +} + +val springBootVersion: String by rootProject + +dependencies { + implementation("org.projectlombok:lombok:1.18.20") + annotationProcessor("org.projectlombok:lombok:1.18.20") + + implementation(group = "org.springframework.boot", name = "spring-boot-starter-web", version = springBootVersion) + implementation(group = "org.springframework.boot", name = "spring-boot-starter-data-jpa", version = springBootVersion) + implementation(group = "org.springframework.boot", name = "spring-boot-starter-test", version = springBootVersion) +} + +tasks.shadowJar { + isZip64 = true + + transform(Log4j2PluginsCacheFileTransformer::class.java) + archiveFileName.set("utbot-spring-sample-shadow.jar") + + // Required for Spring to run properly when using shadowJar + // More details: https://github.com/spring-projects/spring-boot/issues/1828 + mergeServiceFiles() + append("META-INF/spring.handlers") + append("META-INF/spring.schemas") + append("META-INF/spring.tooling") + transform(PropertiesFileTransformer().apply { + paths = listOf("META-INF/spring.factories") + mergeStrategy = "append" + }) +} + +val springSampleJar: Configuration by configurations.creating { + isCanBeResolved = false + isCanBeConsumed = true +} + +artifacts { + add(springSampleJar.name, tasks.shadowJar) +} \ No newline at end of file diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/app/MyService.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/app/MyService.java new file mode 100644 index 0000000000..7e9f49b37e --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/app/MyService.java @@ -0,0 +1,5 @@ +package org.utbot.examples.spring.app; + +public interface MyService { + String getName(); +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/app/MyServiceImpl.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/app/MyServiceImpl.java new file mode 100644 index 0000000000..6f711df70b --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/app/MyServiceImpl.java @@ -0,0 +1,11 @@ +package org.utbot.examples.spring.app; + +import org.springframework.stereotype.Service; + +@Service +public class MyServiceImpl implements MyService { + @Override + public String getName() { + return "impl"; + } +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/app/MyServiceUser.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/app/MyServiceUser.java new file mode 100644 index 0000000000..d1db7260c2 --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/app/MyServiceUser.java @@ -0,0 +1,18 @@ +package org.utbot.examples.spring.app; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class MyServiceUser { + private final MyService myService; + + @Autowired + public MyServiceUser(MyService myService) { + this.myService = myService; + } + + public String useMyService() { + return myService.getName(); + } +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/app/SpringExampleApp.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/app/SpringExampleApp.java new file mode 100644 index 0000000000..32f4cd0c94 --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/app/SpringExampleApp.java @@ -0,0 +1,7 @@ +package org.utbot.examples.spring.app; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringExampleApp { +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/OrderRepository.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/OrderRepository.java new file mode 100644 index 0000000000..c4446c5924 --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/OrderRepository.java @@ -0,0 +1,7 @@ +package org.utbot.examples.spring.autowiring; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.utbot.examples.spring.autowiring.oneBeanForOneType.Order; + +public interface OrderRepository extends JpaRepository { +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/oneBeanForOneType/Order.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/oneBeanForOneType/Order.java new file mode 100644 index 0000000000..5a2f2529e1 --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/oneBeanForOneType/Order.java @@ -0,0 +1,24 @@ +package org.utbot.examples.spring.autowiring.oneBeanForOneType; + +import lombok.*; +import lombok.extern.jackson.Jacksonized; + +import javax.persistence.*; + +@Getter +@Setter +@Builder +@ToString +@Jacksonized +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "orders") +public class Order { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id; + String buyer; + Double price; + int qty; +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/oneBeanForOneType/ServiceWithInjectedAndNonInjectedField.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/oneBeanForOneType/ServiceWithInjectedAndNonInjectedField.java new file mode 100644 index 0000000000..89309ca35a --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/oneBeanForOneType/ServiceWithInjectedAndNonInjectedField.java @@ -0,0 +1,22 @@ +package org.utbot.examples.spring.autowiring.oneBeanForOneType; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.utbot.examples.spring.autowiring.OrderRepository; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class ServiceWithInjectedAndNonInjectedField { + + public List selectedOrders = new ArrayList<>(); + + @Autowired + private OrderRepository orderRepository; + + public Integer getOrdersSize() { + return orderRepository.findAll().size() + selectedOrders.size(); + } + +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/oneBeanForOneType/ServiceWithInjectedField.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/oneBeanForOneType/ServiceWithInjectedField.java new file mode 100644 index 0000000000..9b492c30d9 --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/oneBeanForOneType/ServiceWithInjectedField.java @@ -0,0 +1,22 @@ +package org.utbot.examples.spring.autowiring.oneBeanForOneType; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.utbot.examples.spring.autowiring.OrderRepository; + +import java.util.List; + +@Service +public class ServiceWithInjectedField { + + @Autowired + private OrderRepository orderRepository; + + public List getOrders() { + return orderRepository.findAll(); + } + + public Order createOrder(Order order) { + return orderRepository.save(order); + } +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/twoAndMoreBeansForOneType/Person.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/twoAndMoreBeansForOneType/Person.java new file mode 100644 index 0000000000..64c9cb9d91 --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/twoAndMoreBeansForOneType/Person.java @@ -0,0 +1,22 @@ +package org.utbot.examples.spring.autowiring.twoAndMoreBeansForOneType; + +public class Person { + private String firstName; + private String lastName; + + private Integer age; + + public Person(String firstName, String secondName, Integer age) { + this.firstName = firstName; + this.lastName = secondName; + this.age = age; + } + + public String getName() { + return firstName + " " + lastName; + } + + public Integer getAge(){ + return age; + } +} \ No newline at end of file diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/twoAndMoreBeansForOneType/ServiceOfBeansWithSameType.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/twoAndMoreBeansForOneType/ServiceOfBeansWithSameType.java new file mode 100644 index 0000000000..3c3c9bf239 --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/autowiring/twoAndMoreBeansForOneType/ServiceOfBeansWithSameType.java @@ -0,0 +1,31 @@ +package org.utbot.examples.spring.autowiring.twoAndMoreBeansForOneType; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class ServiceOfBeansWithSameType { + @Autowired + private Person personOne; + + @Autowired + private Person personTwo; + + // A method for testing both cases when the Engine produces + // - two models for two @Autowired fields of the same type + // - one model for two @Autowired fields of the same type + public Boolean checker() { + String name1 = personOne.getName();// shouldn't produce NPE because `personOne` is `@Autowired` + int length = name1.length(); // can produce NPE because `Person.name` isn't `@Autowired` + Integer age2 = personTwo.getAge(); // shouldn't produce NPE because `personTwo` is `@Autowired` + return personOne == personTwo; + } + + public Person getPersonOne() { + return personOne; + } + + public Person getPersonTwo() { + return personTwo; + } +} \ No newline at end of file diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/boot/ExampleSpringBootConfig.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/boot/ExampleSpringBootConfig.java new file mode 100644 index 0000000000..b437460c7d --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/boot/ExampleSpringBootConfig.java @@ -0,0 +1,7 @@ +package org.utbot.examples.spring.config.boot; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ExampleSpringBootConfig { +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/boot/ExampleSpringBootService.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/boot/ExampleSpringBootService.java new file mode 100644 index 0000000000..99e89e7b9d --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/boot/ExampleSpringBootService.java @@ -0,0 +1,11 @@ +package org.utbot.examples.spring.config.boot; + +import org.springframework.stereotype.Service; +import org.utbot.examples.spring.config.utils.SafetyUtils; + +@Service +public class ExampleSpringBootService { + public ExampleSpringBootService() { + SafetyUtils.shouldNeverBeCalled(); + } +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/pure/ExamplePureSpringConfig.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/pure/ExamplePureSpringConfig.java new file mode 100644 index 0000000000..e0fa208ee8 --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/pure/ExamplePureSpringConfig.java @@ -0,0 +1,34 @@ +package org.utbot.examples.spring.config.pure; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; +import org.utbot.examples.spring.config.utils.SafetyUtils; + +public class ExamplePureSpringConfig { + @Bean(name = "exampleService0") + public ExamplePureSpringService exampleService() { + SafetyUtils.shouldNeverBeCalled(); + return null; + } + + @Bean(name = "exampleServiceTest1") + @Profile("test1") + public ExamplePureSpringService exampleServiceTest1() { + SafetyUtils.shouldNeverBeCalled(); + return null; + } + + @Bean(name = "exampleServiceTest2") + @Profile("test2") + public ExamplePureSpringService exampleServiceTest2() { + SafetyUtils.shouldNeverBeCalled(); + return null; + } + + @Bean(name = "exampleServiceTest3") + @Profile("test3") + public ExamplePureSpringService exampleServiceTest3() { + SafetyUtils.shouldNeverBeCalled(); + return null; + } +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/pure/ExamplePureSpringService.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/pure/ExamplePureSpringService.java new file mode 100644 index 0000000000..bed899616d --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/pure/ExamplePureSpringService.java @@ -0,0 +1,4 @@ +package org.utbot.examples.spring.config.pure; + +public class ExamplePureSpringService { +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/utils/SafetyUtils.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/utils/SafetyUtils.java new file mode 100644 index 0000000000..94f12774bb --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/utils/SafetyUtils.java @@ -0,0 +1,15 @@ +package org.utbot.examples.spring.config.utils; + +public class SafetyUtils { + + private static final int UNEXPECTED_CALL_EXIT_STATUS = -1182; + + /** + * Bean constructors and factory methods should never be executed during bean analysis, + * hence call to this method is added into them to ensure they are actually never called. + */ + public static void shouldNeverBeCalled() { + System.err.println("shouldNeverBeCalled() is unexpectedly called"); + System.exit(UNEXPECTED_CALL_EXIT_STATUS); + } +} diff --git a/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/xml/ExampleXmlService.java b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/xml/ExampleXmlService.java new file mode 100644 index 0000000000..d2f57e7d32 --- /dev/null +++ b/utbot-spring-sample/src/main/java/org/utbot/examples/spring/config/xml/ExampleXmlService.java @@ -0,0 +1,9 @@ +package org.utbot.examples.spring.config.xml; + +import org.utbot.examples.spring.config.utils.SafetyUtils; + +public class ExampleXmlService { + public ExampleXmlService() { + SafetyUtils.shouldNeverBeCalled(); + } +} diff --git a/utbot-spring-sample/src/main/resources/xml-spring-config.xml b/utbot-spring-sample/src/main/resources/xml-spring-config.xml new file mode 100644 index 0000000000..5118152230 --- /dev/null +++ b/utbot-spring-sample/src/main/resources/xml-spring-config.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/utbot-spring-test/build.gradle b/utbot-spring-test/build.gradle new file mode 100644 index 0000000000..631e4a6d19 --- /dev/null +++ b/utbot-spring-test/build.gradle @@ -0,0 +1,46 @@ +configurations { + fetchSpringSampleJar +} + +dependencies { + testImplementation(project(":utbot-framework")) + testImplementation(project(":utbot-spring-framework")) + testImplementation project(':utbot-testing') + testImplementation project(':utbot-spring-sample') + + // To use JUnit4, comment out JUnit5 and uncomment JUnit4 dependencies here. Please also check "test" section + // testImplementation group: 'junit', name: 'junit', version: '4.13.1' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.8.1' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.1' + + // used for testing code generation + testImplementation group: 'junit', name: 'junit', version: junit4Version + testImplementation group: 'org.junit.platform', name: 'junit-platform-console-standalone', version: junit4PlatformVersion + testImplementation group: 'org.mockito', name: 'mockito-core', version: mockitoVersion + testImplementation group: 'org.mockito', name: 'mockito-inline', version: mockitoInlineVersion + testImplementation group: 'org.jacoco', name: 'org.jacoco.report', version: jacocoVersion + + testImplementation group: 'commons-io', name: 'commons-io', version: commonsIoVersion + + testImplementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: springBootVersion + + fetchSpringSampleJar project(path: ':utbot-spring-sample', configuration: 'springSampleJar') +} + +configurations { + all { + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' + } +} + +test { + if (System.getProperty('DEBUG', 'false') == 'true') { + jvmArgs '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=9009' + } +} + +processTestResources { + from(configurations.fetchSpringSampleJar) { + into "lib" + } +} diff --git a/utbot-spring-test/src/test/java/org/utbot/api/java/SpringUtBotJavaApiTest.java b/utbot-spring-test/src/test/java/org/utbot/api/java/SpringUtBotJavaApiTest.java new file mode 100644 index 0000000000..09a5594bc1 --- /dev/null +++ b/utbot-spring-test/src/test/java/org/utbot/api/java/SpringUtBotJavaApiTest.java @@ -0,0 +1,105 @@ +package org.utbot.api.java; + +import org.utbot.examples.spring.app.MyServiceImpl; +import org.junit.jupiter.api.Test; +import org.utbot.examples.spring.app.MyServiceUser; +import org.utbot.examples.spring.app.SpringExampleApp; +import org.utbot.examples.spring.config.pure.ExamplePureSpringConfig; +import org.utbot.external.api.UtBotJavaApi; +import org.utbot.external.api.UtBotSpringApi; +import org.utbot.framework.codegen.domain.ForceStaticMocking; +import org.utbot.framework.codegen.domain.Junit4; +import org.utbot.framework.codegen.domain.MockitoStaticMocking; +import org.utbot.framework.codegen.domain.ProjectType; +import org.utbot.framework.context.ApplicationContext; +import org.utbot.framework.context.spring.SpringApplicationContext; +import org.utbot.framework.plugin.api.*; +import org.utbot.framework.util.Snippet; + +import java.io.File; +import java.lang.reflect.Method; +import java.util.*; + +import static org.utbot.framework.plugin.api.MockFramework.MOCKITO; +import static org.utbot.framework.util.TestUtilsKt.compileClassFile; + +public class SpringUtBotJavaApiTest extends AbstractUtBotJavaApiTest { + + /** + * We are using the environment to check spring generation the only difference in the data supplied by the argument + * @param applicationContext returns Spring settings to run the generation with + */ + private void supplyConfigurationAndRunSpringTest(ApplicationContext applicationContext) { + + UtBotJavaApi.setStopConcreteExecutorOnExit(false); + + String classpath = getClassPath(MyServiceImpl.class); + String dependencyClassPath = getDependencyClassPath(); + + Method getNameMethod = getMethodByName( + MyServiceImpl.class, + "getName" + ); + + Method useMyServiceMethod = getMethodByName( + MyServiceUser.class, + "useMyService" + ); + + List myServiceUserTestSets = UtBotJavaApi.generateTestSetsForMethods( + Collections.singletonList(useMyServiceMethod), + MyServiceUser.class, + classpath, + dependencyClassPath, + MockStrategyApi.OTHER_PACKAGES, + 60000L, + applicationContext + ); + + String generationResult = UtBotJavaApi.generateTestCode( + Collections.emptyList(), + myServiceUserTestSets, + GENERATED_TEST_CLASS_NAME, + classpath, + dependencyClassPath, + MyServiceUser.class, + ProjectType.Spring, + Junit4.INSTANCE, + MOCKITO, + CodegenLanguage.JAVA, + MockitoStaticMocking.INSTANCE, + false, + ForceStaticMocking.DO_NOT_FORCE, + MyServiceUser.class.getPackage().getName(), + applicationContext + ); + + Snippet snippet = new Snippet(CodegenLanguage.JAVA, generationResult); + compileClassFile(GENERATED_TEST_CLASS_NAME, snippet); + } + + @Test + public void testUnitTestWithoutSettings() { + supplyConfigurationAndRunSpringTest(UtBotSpringApi.createSpringApplicationContext( + SpringSettings.AbsentSpringSettings, + SpringTestType.UNIT_TEST, + Collections.emptyList())); + } + + @Test + public void testUnitTestWithSettings() { + SpringConfiguration configuration = + UtBotSpringApi.createJavaSpringConfiguration(SpringExampleApp.class); + + SpringSettings springSettings = + new SpringSettings.PresentSpringSettings(configuration, Arrays.asList("test1", "test2")); + + ApplicationContext applicationContext = UtBotSpringApi.createSpringApplicationContext( + springSettings, + SpringTestType.UNIT_TEST, + Arrays.asList(getDependencyClassPath().split(File.pathSeparator)) + ); + + supplyConfigurationAndRunSpringTest(applicationContext); + } +} diff --git a/utbot-spring-test/src/test/java/org/utbot/examples/spring/manual/UtBotSpringApiTest.java b/utbot-spring-test/src/test/java/org/utbot/examples/spring/manual/UtBotSpringApiTest.java new file mode 100644 index 0000000000..7e318a3c03 --- /dev/null +++ b/utbot-spring-test/src/test/java/org/utbot/examples/spring/manual/UtBotSpringApiTest.java @@ -0,0 +1,178 @@ +package org.utbot.examples.spring.manual; + +import org.apache.commons.io.FileUtils; +import org.junit.Test; +import org.utbot.common.FileUtil; +import org.utbot.common.JarUtils; +import org.utbot.examples.spring.config.boot.ExampleSpringBootConfig; +import org.utbot.examples.spring.config.pure.ExamplePureSpringConfig; +import org.utbot.external.api.UtBotSpringApi; +import org.utbot.framework.context.spring.SpringApplicationContext; +import org.utbot.framework.plugin.api.*; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; + +public class UtBotSpringApiTest { + private static final List DEFAULT_PROFILES = Collections.singletonList("default"); + + @Test + public void testOnAbsentSpringSettings() { + SpringApplicationContext springApplicationContext = + UtBotSpringApi.createSpringApplicationContext( + SpringSettings.AbsentSpringSettings, + SpringTestType.UNIT_TEST, + new ArrayList<>() + ); + + assertEquals(new ArrayList<>(), springApplicationContext.getBeanDefinitions()); + } + + @Test + public void testOnSpringBootConfig() { + List actual = getBeansFromSampleProject( + UtBotSpringApi.createJavaSpringConfiguration(ExampleSpringBootConfig.class), + DEFAULT_PROFILES + ); + + List expected = new ArrayList<>(); + expected.add(new BeanDefinitionData( + "exampleSpringBootConfig", + "org.utbot.examples.spring.config.boot.ExampleSpringBootConfig", + null + )); + expected.add(new BeanDefinitionData( + "exampleSpringBootService", + "org.utbot.examples.spring.config.boot.ExampleSpringBootService", + null + )); + + assertEquals(expected, actual); + } + + @Test + public void testOnPureSpringConfig() { + List actual = getBeansFromSampleProject( + UtBotSpringApi.createJavaSpringConfiguration(ExamplePureSpringConfig.class), + DEFAULT_PROFILES + ); + + List expected = new ArrayList<>(); + expected.add(new BeanDefinitionData("examplePureSpringConfig", "org.utbot.examples.spring.config.pure.ExamplePureSpringConfig", null)); + expected.add(new BeanDefinitionData( + "exampleService0", + "org.utbot.examples.spring.config.pure.ExamplePureSpringService", + new BeanAdditionalData( + "exampleService", + Collections.emptyList(), + "org.utbot.examples.spring.config.pure.ExamplePureSpringConfig" + ) + )); + + assertEquals(expected, actual); + } + + @Test + public void testOnPureSpringConfigWithProfiles() { + List profiles = new ArrayList<>(); + profiles.add("test1"); + profiles.add("test3"); + + List actual = getBeansFromSampleProject( + UtBotSpringApi.createJavaSpringConfiguration(ExamplePureSpringConfig.class), + profiles + ); + + List expected = new ArrayList<>(); + expected.add(new BeanDefinitionData("examplePureSpringConfig", "org.utbot.examples.spring.config.pure.ExamplePureSpringConfig", null)); + expected.add(new BeanDefinitionData( + "exampleService0", + "org.utbot.examples.spring.config.pure.ExamplePureSpringService", + new BeanAdditionalData( + "exampleService", + Collections.emptyList(), + "org.utbot.examples.spring.config.pure.ExamplePureSpringConfig" + ) + )); + expected.add(new BeanDefinitionData( + "exampleServiceTest1", + "org.utbot.examples.spring.config.pure.ExamplePureSpringService", + new BeanAdditionalData( + "exampleServiceTest1", + Collections.emptyList(), + "org.utbot.examples.spring.config.pure.ExamplePureSpringConfig" + ) + )); + expected.add(new BeanDefinitionData( + "exampleServiceTest3", + "org.utbot.examples.spring.config.pure.ExamplePureSpringService", + new BeanAdditionalData( + "exampleServiceTest3", + Collections.emptyList(), + "org.utbot.examples.spring.config.pure.ExamplePureSpringConfig" + ) + )); + + assertEquals(expected, actual); + } + + @Test + public void testOnXmlConfig() throws IOException { + URL configUrl = Objects.requireNonNull(getClass().getClassLoader().getResource("xml-spring-config.xml")); + File configDir = FileUtil.INSTANCE.createTempDirectory("xml-config").toFile(); + File configFile = new File(configDir, "xml-spring-config.xml"); + FileUtils.copyURLToFile(configUrl, configFile); + List additionalClasspath = Collections.singletonList(configDir.getAbsolutePath()); + + List actual = getBeansFromSampleProject( + UtBotSpringApi.createXmlSpringConfiguration(configFile), + additionalClasspath + ); + + List expected = new ArrayList<>(); + expected.add(new BeanDefinitionData( + "xmlService", + "org.utbot.examples.spring.config.xml.ExampleXmlService", + null + )); + + assertEquals(expected, actual); + } + + static List getBeansFromSampleProject( + SpringConfiguration configuration, + List profiles + ) { + return getBeansFromSampleProject(configuration, profiles, new ArrayList<>()); + } + + static List getBeansFromSampleProject( + SpringConfiguration configuration, + List profiles, + List additionalClasspath + ) { + SpringSettings springSettings = new SpringSettings.PresentSpringSettings(configuration, profiles); + SpringTestType springTestType = SpringTestType.UNIT_TEST; + List classpath = new ArrayList<>(additionalClasspath); + classpath.add( + JarUtils.INSTANCE.extractJarFileFromResources( + "utbot-spring-sample-shadow.jar", + "lib/utbot-spring-sample-shadow.jar", + "spring-sample" + ).getAbsolutePath() + ); + SpringApplicationContext springApplicationContext = + UtBotSpringApi.createSpringApplicationContext(springSettings, springTestType, classpath); + return springApplicationContext.getBeanDefinitions().stream() + .filter(beanDef -> beanDef.getBeanTypeName().startsWith("org.utbot.examples")) + .collect(Collectors.toList()); + } +} diff --git a/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/autowiring/SpringNoConfigUtValueTestCaseChecker.kt b/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/autowiring/SpringNoConfigUtValueTestCaseChecker.kt new file mode 100644 index 0000000000..a0977e9e35 --- /dev/null +++ b/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/autowiring/SpringNoConfigUtValueTestCaseChecker.kt @@ -0,0 +1,24 @@ +package org.utbot.examples.spring.autowiring + +import org.utbot.examples.spring.utils.standardSpringTestingConfigurations +import org.utbot.framework.context.spring.SpringApplicationContextImpl +import org.utbot.framework.plugin.api.SpringSettings +import org.utbot.framework.plugin.api.SpringTestType +import org.utbot.testing.UtValueTestCaseChecker +import org.utbot.testing.defaultApplicationContext +import kotlin.reflect.KClass + +val springNoConfigApplicationContext = SpringApplicationContextImpl.internalCreate( + delegateContext = defaultApplicationContext, + springTestType = SpringTestType.UNIT_TEST, + springSettings = SpringSettings.AbsentSpringSettings, + beanDefinitions = emptyList() +) + +abstract class SpringNoConfigUtValueTestCaseChecker( + testClass: KClass<*> +) : UtValueTestCaseChecker( + testClass, + configurations = standardSpringTestingConfigurations, + applicationContext = springNoConfigApplicationContext +) \ No newline at end of file diff --git a/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/autowiring/oneBeanForOneType/ServiceWithInjectedAndNonInjectedFieldTests.kt b/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/autowiring/oneBeanForOneType/ServiceWithInjectedAndNonInjectedFieldTests.kt new file mode 100644 index 0000000000..f86ddf9101 --- /dev/null +++ b/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/autowiring/oneBeanForOneType/ServiceWithInjectedAndNonInjectedFieldTests.kt @@ -0,0 +1,35 @@ +package org.utbot.examples.spring.autowiring.oneBeanForOneType + +import org.junit.jupiter.api.Test +import org.utbot.examples.spring.autowiring.SpringNoConfigUtValueTestCaseChecker +import org.utbot.examples.spring.utils.findAllRepositoryCall +import org.utbot.examples.spring.utils.springAdditionalDependencies +import org.utbot.examples.spring.utils.springMockStrategy +import org.utbot.testing.DoNotCalculate +import org.utbot.testing.ignoreExecutionsNumber +import org.utbot.testing.isException +import org.utbot.testing.singleMock +import org.utbot.testing.value + +internal class ServiceWithInjectedAndNonInjectedFieldTests : SpringNoConfigUtValueTestCaseChecker( + testClass = ServiceWithInjectedAndNonInjectedField::class, +) { + @Test + fun testGetOrdersSize() { + checkThisMocksAndExceptions( + method = ServiceWithInjectedAndNonInjectedField::getOrdersSize, + // TODO: replace with `branches = eq(3)` + // after the fix of `speculativelyCannotProduceNullPointerException` in SpringApplicationContext + branches = ignoreExecutionsNumber, + { thisInstance, mocks, r: Result -> + val orderRepository = mocks.singleMock("orderRepository", findAllRepositoryCall) + val repositorySize = orderRepository.value?>()!!.size + repositorySize + thisInstance.selectedOrders.size == r.getOrNull() + }, + { _, _, r: Result -> r.isException() }, + coverage = DoNotCalculate, + mockStrategy = springMockStrategy, + additionalDependencies = springAdditionalDependencies, + ) + } +} \ No newline at end of file diff --git a/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/autowiring/oneBeanForOneType/ServiceWithInjectedFieldTests.kt b/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/autowiring/oneBeanForOneType/ServiceWithInjectedFieldTests.kt new file mode 100644 index 0000000000..c123d7f2bd --- /dev/null +++ b/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/autowiring/oneBeanForOneType/ServiceWithInjectedFieldTests.kt @@ -0,0 +1,45 @@ +package org.utbot.examples.spring.autowiring.oneBeanForOneType + +import org.junit.jupiter.api.Test +import org.utbot.examples.spring.autowiring.SpringNoConfigUtValueTestCaseChecker +import org.utbot.examples.spring.utils.findAllRepositoryCall +import org.utbot.examples.spring.utils.saveRepositoryCall +import org.utbot.examples.spring.utils.springAdditionalDependencies +import org.utbot.examples.spring.utils.springMockStrategy +import org.utbot.testcheckers.eq +import org.utbot.testing.* + +internal class ServiceWithInjectedFieldTests : SpringNoConfigUtValueTestCaseChecker( + testClass = ServiceWithInjectedField::class, +) { + @Test + fun testGetOrders() { + checkMocks( + method = ServiceWithInjectedField::getOrders, + branches = eq(1), + { mocks, r -> + val orderRepository = mocks.singleMock("orderRepository", findAllRepositoryCall) + orderRepository.value?>() == r + }, + coverage = DoNotCalculate, + mockStrategy = springMockStrategy, + additionalDependencies = springAdditionalDependencies, + ) + } + + @Test + fun testCreateOrder() { + checkThisMocksAndExceptions( + method = ServiceWithInjectedField::createOrder, + // TODO: replace with `branches = eq(1)` after fix of https://github.com/UnitTestBot/UTBotJava/issues/2367 + branches = ignoreExecutionsNumber, + { _, _, mocks, r: Result -> + val orderRepository = mocks.singleMock("orderRepository", saveRepositoryCall) + orderRepository.value() == r.getOrNull() + }, + coverage = DoNotCalculate, + mockStrategy = springMockStrategy, + additionalDependencies = springAdditionalDependencies, + ) + } +} \ No newline at end of file diff --git a/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/autowiring/twoAndMoreBeansForOneType/ServiceOfBeansWithSameTypeTest.kt b/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/autowiring/twoAndMoreBeansForOneType/ServiceOfBeansWithSameTypeTest.kt new file mode 100644 index 0000000000..aa748579c4 --- /dev/null +++ b/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/autowiring/twoAndMoreBeansForOneType/ServiceOfBeansWithSameTypeTest.kt @@ -0,0 +1,59 @@ +package org.utbot.examples.spring.autowiring.twoAndMoreBeansForOneType + +import org.junit.jupiter.api.Test +import org.utbot.examples.spring.autowiring.SpringNoConfigUtValueTestCaseChecker +import org.utbot.examples.spring.utils.* +import org.utbot.testcheckers.eq +import org.utbot.testing.* + +class ServiceOfBeansWithSameTypeTest : SpringNoConfigUtValueTestCaseChecker( + testClass = ServiceOfBeansWithSameType::class, +) { + + /** + * In this test, we check both cases when the Engine produces + * - two models for two @Autowired fields of the same type + * - one model for two @Autowired fields of the same type + */ + @Test + fun testChecker() { + checkThisMocksAndExceptions( + method = ServiceOfBeansWithSameType::checker, + branches = eq(3), + {_, mocks, r -> + val personOne = mocks.singleMock("personOne", namePersonCall) + + val personOneName = personOne.value() + + val r1 = personOneName == null + + r1 && r.isException() + }, + {_, mocks, r -> + val personOne = mocks.singleMock("personOne", namePersonCall) + val personTwo = mocks.singleMockOrNull("personTwo", namePersonCall) + + val personOneName = personOne.value() + + val r1 = personOneName != null + val r2 = personTwo == null // `personTwo.getName()` isn't mocked, meaning `personOne != personTwo` + + r1 && r2 && (r.getOrNull() == false) + }, + {_, mocks, r -> + val personOne = mocks.singleMock("personOne", namePersonCall) + val personTwo = mocks.singleMockOrNull("personTwo", namePersonCall) + + val personOneName = personOne.value() + + val r1 = personOneName != null + val r2 = personTwo != null // `personTwo.getName()` is mocked, meaning `personOne == personTwo` + + r1 && r2 && (r.getOrNull() == true) + }, + coverage = DoNotCalculate, + mockStrategy = springMockStrategy, + additionalDependencies = springAdditionalDependencies, + ) + } +} diff --git a/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/utils/CallUtils.kt b/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/utils/CallUtils.kt new file mode 100644 index 0000000000..86cb0648e4 --- /dev/null +++ b/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/utils/CallUtils.kt @@ -0,0 +1,38 @@ +package org.utbot.examples.spring.utils + +import org.utbot.examples.spring.autowiring.OrderRepository +import org.utbot.examples.spring.autowiring.oneBeanForOneType.Order +import org.utbot.examples.spring.autowiring.twoAndMoreBeansForOneType.Person +import kotlin.reflect.KFunction1 +import kotlin.reflect.KFunction2 +import kotlin.reflect.full.functions + +@Suppress("UNCHECKED_CAST") +val findAllRepositoryCall: KFunction1?> = + OrderRepository::class + .functions + .single { it.name == "findAll" && it.parameters.size == 1 } + as KFunction1?> + + +@Suppress("UNCHECKED_CAST") +val saveRepositoryCall: KFunction2 = + OrderRepository::class + .functions + .single { it.name == "save" && it.parameters.size == 2 } + as KFunction2 + + +@Suppress("UNCHECKED_CAST") +val namePersonCall: KFunction1 = + Person::class + .functions + .single { it.name == "getName" && it.parameters.size == 1 } + as KFunction1 + +@Suppress("UNCHECKED_CAST") +val agePersonCall: KFunction1 = + Person::class + .functions + .single { it.name == "getAge" && it.parameters.size == 1 } + as KFunction1 \ No newline at end of file diff --git a/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/utils/SpringTestingConfiguration.kt b/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/utils/SpringTestingConfiguration.kt new file mode 100644 index 0000000000..3a967775a1 --- /dev/null +++ b/utbot-spring-test/src/test/kotlin/org/utbot/examples/spring/utils/SpringTestingConfiguration.kt @@ -0,0 +1,20 @@ +package org.utbot.examples.spring.utils + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.data.repository.PagingAndSortingRepository +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.SpringConfiguration +import org.utbot.testing.TestExecution + +val standardSpringTestingConfigurations: List = listOf( + SpringConfiguration(CodegenLanguage.JAVA, ParametrizedTestSource.DO_NOT_PARAMETRIZE, TestExecution) +) + +val springMockStrategy = MockStrategyApi.OTHER_CLASSES + +val springAdditionalDependencies: Array> = arrayOf( + JpaRepository::class.java, + PagingAndSortingRepository::class.java, +) \ No newline at end of file diff --git a/utbot-spring-test/src/test/resources/log4j2.xml b/utbot-spring-test/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..ac3d2f2abf --- /dev/null +++ b/utbot-spring-test/src/test/resources/log4j2.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/utbot-summary-tests/build.gradle b/utbot-summary-tests/build.gradle index cb460cbd1d..8f356f6e58 100644 --- a/utbot-summary-tests/build.gradle +++ b/utbot-summary-tests/build.gradle @@ -1,5 +1,6 @@ -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" -apply plugin: "java" +plugins { + id 'java-library' +} evaluationDependsOn(':utbot-framework') compileTestJava.dependsOn tasks.getByPath(':utbot-framework:testClasses') @@ -8,10 +9,7 @@ dependencies { implementation(project(":utbot-framework")) implementation(project(':utbot-instrumentation')) testImplementation project(':utbot-sample') - testImplementation group: 'junit', name: 'junit', version: junit4_version - testCompile project(':utbot-framework').sourceSets.test.output + testImplementation group: 'junit', name: 'junit', version: junit4Version + testImplementation project(':utbot-testing') + testImplementation project(':utbot-framework').sourceSets.test.output } - -test { - useJUnitPlatform() -} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/CustomJavaDocTagsEnabler.kt b/utbot-summary-tests/src/test/kotlin/examples/CustomJavaDocTagsEnabler.kt new file mode 100644 index 0000000000..5929caad89 --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/CustomJavaDocTagsEnabler.kt @@ -0,0 +1,25 @@ +package examples + +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext +import org.utbot.framework.UtSettings + +/** + * Controls the value of useCustomJavaDocTags global variable. + * + * Should be used in summary tests containing custom JavaDoc tags. + * To use it, add an annotation @ExtendWith(CustomJavaDocTagsEnabler::class) under test class. + */ +class CustomJavaDocTagsEnabler(private val enable: Boolean = true) : BeforeEachCallback, AfterEachCallback { + private var previousValue = false + + override fun beforeEach(context: ExtensionContext?) { + previousValue = UtSettings.useCustomJavaDocTags + UtSettings.useCustomJavaDocTags = enable + } + + override fun afterEach(context: ExtensionContext?) { + UtSettings.useCustomJavaDocTags = previousValue + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/SummaryTestCaseGeneratorTest.kt b/utbot-summary-tests/src/test/kotlin/examples/SummaryTestCaseGeneratorTest.kt index 5ba1fb5dab..4de641132b 100644 --- a/utbot-summary-tests/src/test/kotlin/examples/SummaryTestCaseGeneratorTest.kt +++ b/utbot-summary-tests/src/test/kotlin/examples/SummaryTestCaseGeneratorTest.kt @@ -3,37 +3,29 @@ package examples import org.junit.jupiter.api.* import org.utbot.common.WorkaroundReason import org.utbot.common.workaround -import org.utbot.examples.AbstractTestCaseGeneratorTest -import org.utbot.examples.CoverageMatcher -import org.utbot.examples.DoNotCalculate import org.utbot.framework.UtSettings.checkNpeInNestedMethods import org.utbot.framework.UtSettings.checkNpeInNestedNotPrivateMethods import org.utbot.framework.UtSettings.checkSolverTimeoutMillis -import org.utbot.framework.codegen.TestExecution -import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.MockStrategyApi -import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtMethod +import org.utbot.framework.plugin.api.* import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.executableId import org.utbot.summary.comment.nextSynonyms -import org.utbot.summary.summarize +import org.utbot.summary.summarizeAll +import org.utbot.testing.CoverageMatcher +import org.utbot.testing.TestExecution +import org.utbot.testing.UtValueTestCaseChecker import kotlin.reflect.KClass import kotlin.reflect.KFunction -import kotlin.reflect.KFunction1 -import kotlin.reflect.KFunction2 -import kotlin.reflect.KFunction3 -import kotlin.reflect.KFunction4 +private const val NEW_LINE = "\n" +private const val POINT_IN_THE_LIST = " * " +private const val COMMENT_SEPARATOR = "-------------------------------------------------------------" @Disabled open class SummaryTestCaseGeneratorTest( testClass: KClass<*>, testCodeGeneration: Boolean = false, - languagePipelines: List = listOf( - CodeGenerationLanguageLastStage(CodegenLanguage.JAVA), - CodeGenerationLanguageLastStage(CodegenLanguage.KOTLIN, TestExecution) - ) -) : AbstractTestCaseGeneratorTest(testClass, testCodeGeneration, languagePipelines) { +) : UtValueTestCaseChecker(testClass, testCodeGeneration) { private lateinit var cookie: AutoCloseable @BeforeEach @@ -46,49 +38,15 @@ open class SummaryTestCaseGeneratorTest( cookie.close() } - protected inline fun checkNoArguments( - method: KFunction1<*, R>, - coverage: CoverageMatcher = DoNotCalculate, - mockStrategy: MockStrategyApi = MockStrategyApi.NO_MOCKS, - summaryKeys: List, - methodNames: List = listOf(), - displayNames: List = listOf() - ) = check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - - protected inline fun checkOneArgument( - method: KFunction2<*, T, R>, - coverage: CoverageMatcher = DoNotCalculate, - mockStrategy: MockStrategyApi = MockStrategyApi.NO_MOCKS, - summaryKeys: List, - methodNames: List = listOf(), - displayNames: List = listOf() - ) = check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - - protected inline fun checkTwoArguments( - method: KFunction3<*, T1, T2, R>, - coverage: CoverageMatcher = DoNotCalculate, - mockStrategy: MockStrategyApi = MockStrategyApi.NO_MOCKS, - summaryKeys: List, - methodNames: List = listOf(), - displayNames: List = listOf() - ) = check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - - protected inline fun checkThreeArguments( - method: KFunction4<*, T1, T2, T3, R>, - coverage: CoverageMatcher = DoNotCalculate, - mockStrategy: MockStrategyApi = MockStrategyApi.NO_MOCKS, - summaryKeys: List, - methodNames: List = listOf(), - displayNames: List = listOf() - ) = check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - - inline fun check( + inline fun summaryCheck( method: KFunction, mockStrategy: MockStrategyApi, coverageMatcher: CoverageMatcher, summaryKeys: List, - methodNames: List, - displayNames: List + methodNames: List = listOf(), + displayNames: List = listOf(), + clusterInfo: List> = listOf(), + additionalMockAlwaysClasses: Set = emptySet() ) { workaround(WorkaroundReason.HACK) { // @todo change to the constructor parameter @@ -96,13 +54,17 @@ open class SummaryTestCaseGeneratorTest( checkNpeInNestedMethods = true checkNpeInNestedNotPrivateMethods = true } - val utMethod = UtMethod.from(method) - val testCase = executionsModel(utMethod, mockStrategy) - testCase.summarize(searchDirectory) + val testSet = executionsModel( + method.executableId, + mockStrategy, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + val testSetWithSummarization = listOf(testSet).summarizeAll(searchDirectory, sourceFile = null).single() - testCase.executions.checkMatchersWithTextSummary(summaryKeys) - testCase.executions.checkMatchersWithMethodNames(methodNames) - testCase.executions.checkMatchersWithDisplayNames(displayNames) + testSetWithSummarization.executions.checkMatchersWithTextSummary(summaryKeys) + testSetWithSummarization.executions.checkMatchersWithMethodNames(methodNames) + testSetWithSummarization.executions.checkMatchersWithDisplayNames(displayNames) + testSetWithSummarization.checkClusterInfo(clusterInfo) } /** @@ -120,18 +82,82 @@ open class SummaryTestCaseGeneratorTest( return result } + // TODO: if next synonyms and normalize function will be removed, this method could be moved as overridden equals to the dataClass [UtClusterInfo] + private fun UtClusterInfo.normalizedAndEquals(other: UtClusterInfo): Boolean { + if (header != other.header) return false + + return if (content == null) { + other.content == null + } else { + if (other.content == null) false + else { + content!!.normalize() == other.content!!.normalize() + } + } + } + + /** + * Verifies that there are the same number of clusters, its content and number of included tests in each cluster. + */ + fun UtMethodTestSet.checkClusterInfo(clusterInfo: List>) { + if (clusterInfo.isEmpty()) { + return + } + + Assertions.assertEquals(this.clustersInfo.size, clusterInfo.size) + + this.clustersInfo.forEachIndexed { index, it -> + Assertions.assertTrue(it.first!!.normalizedAndEquals(clusterInfo[index].first)) + Assertions.assertEquals(it.second.count(), clusterInfo[index].second) + } + } + fun List.checkMatchersWithTextSummary( - summaryTextKeys: List, + comments: List, ) { - if (summaryTextKeys.isEmpty()) { + if (comments.isEmpty()) { return } val notMatchedExecutions = this.filter { execution -> - summaryTextKeys.none { summaryKey -> val normalize = execution.summary?.toString()?.normalize() - normalize?.contains(summaryKey.normalize()) == true } + comments.none { comment -> + val normalize = execution.summary?.toString()?.normalize() + normalize?.contains(comment.normalize()) == true + } + } + + val notMatchedComments = comments.filter { comment -> + this.none { execution -> + val normalize = execution.summary?.toString()?.normalize() + normalize?.contains(comment.normalize()) == true + } + } + + Assertions.assertTrue(notMatchedExecutions.isEmpty() && notMatchedComments.isEmpty()) { + buildString { + if (notMatchedExecutions.isNotEmpty()) { + append( + "\nThe following comments were produced by the UTBot, " + + "but were not found in the list of comments passed in the check() method:\n\n${ + commentsFromExecutions( + notMatchedExecutions + ) + }" + ) + } + + if (notMatchedComments.isNotEmpty()) { + append( + "\nThe following comments were passed in the check() method, " + + "but were not found in the list of comments produced by the UTBot:\n\n${ + comments( + notMatchedComments + ) + }" + ) + } + } } - Assertions.assertTrue(notMatchedExecutions.isEmpty()) { "Not matched comments ${summaries(notMatchedExecutions)}" } } fun List.checkMatchersWithMethodNames( @@ -143,7 +169,36 @@ open class SummaryTestCaseGeneratorTest( val notMatchedExecutions = this.filter { execution -> methodNames.none { methodName -> execution.testMethodName?.equals(methodName) == true } } - Assertions.assertTrue(notMatchedExecutions.isEmpty()) { "Not matched test names ${summaries(notMatchedExecutions)}" } + + val notMatchedMethodNames = methodNames.filter { methodName -> + this.none { execution -> execution.testMethodName?.equals(methodName) == true } + } + + Assertions.assertTrue(notMatchedExecutions.isEmpty() && notMatchedMethodNames.isEmpty()) { + buildString { + if (notMatchedExecutions.isNotEmpty()) { + append( + "\nThe following method names were produced by the UTBot, " + + "but were not found in the list of method names passed in the check() method:\n\n${ + methodNamesFromExecutions( + notMatchedExecutions + ) + }" + ) + } + + if (notMatchedMethodNames.isNotEmpty()) { + append( + "\nThe following method names were passed in the check() method, " + + "but were not found in the list of method names produced by the UTBot:\n\n${ + methodNames( + notMatchedMethodNames + ) + }" + ) + } + } + } } fun List.checkMatchersWithDisplayNames( @@ -155,14 +210,108 @@ open class SummaryTestCaseGeneratorTest( val notMatchedExecutions = this.filter { execution -> displayNames.none { displayName -> execution.displayName?.equals(displayName) == true } } - Assertions.assertTrue(notMatchedExecutions.isEmpty()) { "Not matched display names ${summaries(notMatchedExecutions)}" } + + val notMatchedDisplayNames = displayNames.filter { displayName -> + this.none { execution -> execution.displayName?.equals(displayName) == true } + } + + Assertions.assertTrue(notMatchedExecutions.isEmpty() && notMatchedDisplayNames.isEmpty()) { + buildString { + if (notMatchedExecutions.isNotEmpty()) { + append( + "\nThe following display names were produced by the UTBot, " + + "but were not found in the list of display names passed in the check() method:\n\n${ + displayNamesFromExecutions( + notMatchedExecutions + ) + }" + ) + } + + if (notMatchedDisplayNames.isNotEmpty()) { + append( + "\nThe following display names were passed in the check() method, " + + "but were not found in the list of display names produced by the UTBot:\n\n${ + displayNames( + notMatchedDisplayNames + ) + }" + ) + } + } + } } - private fun summaries(executions: List): String { - var result = ""; - executions.forEach { - result += it.summary?.joinToString(separator = "", postfix = "\n") + private fun commentsFromExecutions(executions: List): String { + return buildString { + append(COMMENT_SEPARATOR) + executions.forEach { + append(NEW_LINE) + append(NEW_LINE) + append(it.summary?.joinToString(separator = "", postfix = NEW_LINE)) + append(COMMENT_SEPARATOR) + append(NEW_LINE) + } + append(NEW_LINE) + } + } + + private fun comments(comments: List): String { + return buildString { + append(COMMENT_SEPARATOR) + comments.forEach { + append(NEW_LINE) + append(NEW_LINE) + append(it) + append(NEW_LINE) + append(COMMENT_SEPARATOR) + append(NEW_LINE) + } + append(NEW_LINE) + } + } + + private fun displayNamesFromExecutions(executions: List): String { + return buildString { + executions.forEach { + append(POINT_IN_THE_LIST) + append(it.displayName) + append(NEW_LINE) + } + append(NEW_LINE) + } + } + + private fun displayNames(displayNames: List): String { + return buildString { + displayNames.forEach { + append(POINT_IN_THE_LIST) + append(it) + append(NEW_LINE) + } + append(NEW_LINE) + } + } + + private fun methodNamesFromExecutions(executions: List): String { + return buildString { + executions.forEach { + append(POINT_IN_THE_LIST) + append(it.testMethodName) + append(NEW_LINE) + } + append(NEW_LINE) + } + } + + private fun methodNames(methodNames: List): String { + return buildString { + methodNames.forEach { + append(POINT_IN_THE_LIST) + append(it) + append(NEW_LINE) + } + append(NEW_LINE) } - return result } } \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryArrayQuickSortExampleTest.kt b/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryArrayQuickSortExampleTest.kt new file mode 100644 index 0000000000..45c4717763 --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryArrayQuickSortExampleTest.kt @@ -0,0 +1,593 @@ +package examples.algorithms + +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test +import org.utbot.examples.algorithms.ArraysQuickSort +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.UtClusterInfo +import org.utbot.testing.DoNotCalculate + +@Tag("slow") +class SummaryArrayQuickSortExampleTest : SummaryTestCaseGeneratorTest( + ArraysQuickSort::class, +) { + @Test + fun testSort() { + val summary1 = "Test does not iterate for(int k = left; k < right; run[count] = k), for(int lo = run[count] - 1, hi = k; ++lo < --hi; ), while(++k <= right && a[k - 1] >= a[k]), while(++k <= right && a[k - 1] <= a[k]), while(k < right && a[k] == a[k + 1]), executes conditions:\n" + + " (right - left < 286): False,\n" + + " (count == 0): True\n" + + "returns from: return;\n" + val summary2 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): True,\n" + + " (leftmost): True\n" + + " returns from: return;\n" + + " \n" + + "Test further returns from: return;\n" + val summary3 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it executes conditions:\n" + + " (length < 47): True,\n" + + " (leftmost): True\n" + + " iterates the loop for(int i = left, j = i; i < right; j = ++i) once. \n" + + " Test further does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), returns from: return;\n" + + " \n" + + "Test further returns from: return;\n" + val summary4 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it executes conditions:\n" + + " (length < 47): True,\n" + + " (leftmost): True\n" + + " iterates the loop for(int i = left, j = i; i < right; j = ++i) once,\n" + + " inside this loop, the test executes conditions:\n" + + " (j-- == left): True\n" + + " Test then does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), returns from: return;\n" + + " \n" + + "Test later returns from: return;\n" + val summary5 = "Test executes conditions:\n" + + " (right - left < 286): False\n" + + "iterates the loop while(k < right && a[k] == a[k + 1]) once. \n" + + "Test throws ArrayIndexOutOfBoundsException in: while(k < right && a[k] == a[k + 1])\n" + val summary6 = "Test executes conditions:\n" + + " (right - left < 286): False\n" + + "iterates the loop while(k < right && a[k] == a[k + 1]) once. \n" + + "Test throws NullPointerException in: while(k < right && a[k] == a[k + 1])\n" + val summary7 = "Test executes conditions:\n" + + " (right - left < 286): False\n" + + "iterates the loop while(k < right && a[k] == a[k + 1]) once. \n" + + "Test throws ArrayIndexOutOfBoundsException in: while(k < right && a[k] == a[k + 1])\n" + val summary8 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary9 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary10 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False\n" + + " \n" + + "Test throws NullPointerException in: internalSort(a, left, right, true);\n" + val summary11 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it executes conditions:\n" + + " (length < 47): True,\n" + + " (leftmost): True\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary12 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it executes conditions:\n" + + " (length < 47): True,\n" + + " (leftmost): True\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary13 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it executes conditions:\n" + + " (length < 47): True,\n" + + " (leftmost): True\n" + + " \n" + + "Test throws NullPointerException in: internalSort(a, left, right, true);\n" + val summary14 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): False,\n" + + " (a[e3] < a[e2]): False,\n" + + " (a[e4] < a[e3]): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary15 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): False,\n" + + " (a[e3] < a[e2]): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary16 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate for(int k = less - 1; ++k <= great; ), while(a[great] == pivot2), while(a[less] == pivot1), for(int k = less - 1; ++k <= great; ), while(a[--great] > pivot2), while(a[++less] < pivot1), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): False,\n" + + " (a[e3] < a[e2]): False,\n" + + " (a[e4] < a[e3]): False,\n" + + " (a[e5] < a[e4]): True,\n" + + " (t < a[e3]): True,\n" + + " (if (t < a[e2]) {\n" + + " a[e3] = a[e2];\n" + + " a[e2] = t;\n" + + " if (t < a[e1]) {\n" + + " a[e2] = a[e1];\n" + + " a[e1] = t;\n" + + " }\n" + + "}): False,\n" + + " (a[e1] != a[e2]): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary17 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate for(int k = less - 1; ++k <= great; ), while(a[great] == pivot2), while(a[less] == pivot1), for(int k = less - 1; ++k <= great; ), while(a[--great] > pivot2), while(a[++less] < pivot1), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): False,\n" + + " (a[e3] < a[e2]): False,\n" + + " (a[e4] < a[e3]): False,\n" + + " (a[e5] < a[e4]): True,\n" + + " (t < a[e3]): True,\n" + + " (if (t < a[e2]) {\n" + + " a[e3] = a[e2];\n" + + " a[e2] = t;\n" + + " if (t < a[e1]) {\n" + + " a[e2] = a[e1];\n" + + " a[e1] = t;\n" + + " }\n" + + "}): True,\n" + + " (if (t < a[e1]) {\n" + + " a[e2] = a[e1];\n" + + " a[e1] = t;\n" + + "}): False,\n" + + " (a[e1] != a[e2]): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary18 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): False,\n" + + " (a[e3] < a[e2]): False,\n" + + " (a[e4] < a[e3]): False,\n" + + " (a[e5] < a[e4]): True,\n" + + " (t < a[e3]): True,\n" + + " (if (t < a[e2]) {\n" + + " a[e3] = a[e2];\n" + + " a[e2] = t;\n" + + " if (t < a[e1]) {\n" + + " a[e2] = a[e1];\n" + + " a[e1] = t;\n" + + " }\n" + + "}): True,\n" + + " (if (t < a[e1]) {\n" + + " a[e2] = a[e1];\n" + + " a[e1] = t;\n" + + "}): True,\n" + + " (a[e1] != a[e2]): True,\n" + + " (a[e2] != a[e3]): True,\n" + + " (a[e3] != a[e4]): True,\n" + + " (a[e4] != a[e5]): True\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary19 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate for(int k = less - 1; ++k <= great; ), while(a[great] == pivot2), while(a[less] == pivot1), for(int k = less - 1; ++k <= great; ), while(a[--great] > pivot2), while(a[++less] < pivot1), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): False,\n" + + " (a[e3] < a[e2]): False,\n" + + " (a[e4] < a[e3]): False,\n" + + " (a[e5] < a[e4]): True,\n" + + " (t < a[e3]): False,\n" + + " (a[e1] != a[e2]): True,\n" + + " (a[e2] != a[e3]): False,\n" + + " (a[k] == pivot): True,\n" + + " (a[k] == pivot): False,\n" + + " (ak < pivot): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary20 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate for(int k = less - 1; ++k <= great; ), while(a[great] == pivot2), while(a[less] == pivot1), for(int k = less - 1; ++k <= great; ), while(a[--great] > pivot2), while(a[++less] < pivot1), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): False,\n" + + " (a[e3] < a[e2]): False,\n" + + " (a[e4] < a[e3]): True,\n" + + " (t < a[e2]): True,\n" + + " (if (t < a[e1]) {\n" + + " a[e2] = a[e1];\n" + + " a[e1] = t;\n" + + "}): True,\n" + + " (a[e5] < a[e4]): False,\n" + + " (a[e1] != a[e2]): True,\n" + + " (a[e2] != a[e3]): True,\n" + + " (a[e3] != a[e4]): False,\n" + + " (ak < pivot): True,\n" + + " (ak < pivot): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary21 = "Test executes conditions:\n" + + " (right - left < 286): False\n" + + "iterates the loop while(k < right && a[k] == a[k + 1]) twice. \n" + + "Test throws ArrayIndexOutOfBoundsException in: while(k < right && a[k] == a[k + 1])\n" + val summary22 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): True,\n" + + " (a[e3] < a[e2]): True,\n" + + " (t < a[e1]): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary23 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): True,\n" + + " (a[e3] < a[e2]): True,\n" + + " (t < a[e1]): False,\n" + + " (a[e4] < a[e3]): True,\n" + + " (t < a[e2]): True,\n" + + " (if (t < a[e1]) {\n" + + " a[e2] = a[e1];\n" + + " a[e1] = t;\n" + + "}): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary24 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): True,\n" + + " (a[e3] < a[e2]): True,\n" + + " (t < a[e1]): False,\n" + + " (a[e4] < a[e3]): True,\n" + + " (t < a[e2]): True,\n" + + " (if (t < a[e1]) {\n" + + " a[e2] = a[e1];\n" + + " a[e1] = t;\n" + + "}): True\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary25 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary26 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): False,\n" + + " (a[e3] < a[e2]): True,\n" + + " (t < a[e1]): True\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary27 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): False,\n" + + " (a[e3] < a[e2]): True,\n" + + " (t < a[e1]): False,\n" + + " (a[e4] < a[e3]): False,\n" + + " (a[e5] < a[e4]): False,\n" + + " (a[e1] != a[e2]): True,\n" + + " (a[e2] != a[e3]): True,\n" + + " (a[e3] != a[e4]): True,\n" + + " (a[e4] != a[e5]): True\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary28 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): False,\n" + + " (a[e3] < a[e2]): True,\n" + + " (t < a[e1]): False,\n" + + " (a[e4] < a[e3]): False,\n" + + " (a[e5] < a[e4]): True,\n" + + " (t < a[e3]): False,\n" + + " (a[e1] != a[e2]): True,\n" + + " (a[e2] != a[e3]): True,\n" + + " (a[e3] != a[e4]): True,\n" + + " (a[e4] != a[e5]): True\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary29 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate while(last < a[--right]), for(int k = left; ++left <= right; k = ++left), while(a2 < a[--k]), while(a1 < a[--k]), for(int i = left, j = i; i < right; j = ++i), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): True,\n" + + " (a[e3] < a[e2]): False,\n" + + " (a[e4] < a[e3]): True,\n" + + " (t < a[e2]): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary30 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate for(int k = less - 1; ++k <= great; ), while(a[great] == pivot2), while(a[less] == pivot1), for(int k = less - 1; ++k <= great; ), while(a[--great] > pivot2), while(a[++less] < pivot1), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): True,\n" + + " (a[e3] < a[e2]): False,\n" + + " (a[e4] < a[e3]): False,\n" + + " (a[e5] < a[e4]): False,\n" + + " (a[e1] != a[e2]): True,\n" + + " (a[e2] != a[e3]): True,\n" + + " (a[e3] != a[e4]): False,\n" + + " (a[k] == pivot): False,\n" + + " (ak < pivot): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary31 = "Test executes conditions:\n" + + " (right - left < 286): True\n" + + "calls {@link org.utbot.examples.algorithms.ArraysQuickSort#internalSort(int[],int,int,boolean)},\n" + + " there it does not iterate for(int k = less - 1; ++k <= great; ), while(a[great] == pivot2), while(a[less] == pivot1), for(int k = less - 1; ++k <= great; ), while(a[--great] > pivot2), while(a[++less] < pivot1), executes conditions:\n" + + " (length < 47): False,\n" + + " (a[e2] < a[e1]): True,\n" + + " (a[e3] < a[e2]): False,\n" + + " (a[e4] < a[e3]): False,\n" + + " (a[e5] < a[e4]): False,\n" + + " (a[e1] != a[e2]): True,\n" + + " (a[e2] != a[e3]): True,\n" + + " (a[e3] != a[e4]): True,\n" + + " (a[e4] != a[e5]): False,\n" + + " (a[k] == pivot): False,\n" + + " (ak < pivot): False\n" + + " \n" + + "Test throws ArrayIndexOutOfBoundsException in: internalSort(a, left, right, true);\n" + val summary32 = "Test executes conditions:\n" + + " (right - left < 286): False\n" + + "iterates the loop for(int k = left; k < right; run[count] = k) once,\n" + + " inside this loop, the test iterates the loop while(k < right && a[k] == a[k + 1]) once. \n" + + "Test then executes conditions:\n" + + " (k == right): False,\n" + + " (a[k] < a[k + 1]): True\n" + + "iterates the loop while(++k <= right && a[k - 1] <= a[k])\n" + + "Test throws ArrayIndexOutOfBoundsException in: while(++k <= right && a[k - 1] <= a[k])\n" + val summary33 = "Test executes conditions:\n" + + " (right - left < 286): False\n" + + "iterates the loop for(int k = left; k < right; run[count] = k) once,\n" + + " inside this loop, the test iterates the loop while(k < right && a[k] == a[k + 1]) twice. \n" + + "Test then does not iterate while(++k <= right && a[k - 1] <= a[k]), executes conditions:\n" + + " (k == right): False\n" + + " (a[k] < a[k + 1]): False\n" + + " (a[k] > a[k + 1]): True\n" + + "iterates the loop while(++k <= right && a[k - 1] >= a[k])\n" + + "Test throws ArrayIndexOutOfBoundsException in: while(++k <= right && a[k - 1] >= a[k])\n" + + val methodName1 = "testSort_CountEqualsZero" + val methodName2 = "testSort_Leftmost" + val methodName3 = "testSort_AiGreaterOrEqualJOfA" + val methodName4 = "testSort_PostfixDecrementJEqualsLeft" + val methodName5 = "testSort_ThrowArrayIndexOutOfBoundsException" + val methodName6 = "testSort_ThrowNullPointerException" + val methodName7 = "testSort_ThrowArrayIndexOutOfBoundsException_1" + val methodName8 = "testSort_ThrowArrayIndexOutOfBoundsException_2" + val methodName9 = "testSort_ThrowArrayIndexOutOfBoundsException_3" + val methodName10 = "testSort_ThrowNullPointerException_1" + val methodName11 = "testSort_ThrowArrayIndexOutOfBoundsException_4" + val methodName12 = "testSort_ThrowArrayIndexOutOfBoundsException_5" + val methodName13 = "testSort_ThrowNullPointerException_2" + val methodName14 = "testSort_ThrowArrayIndexOutOfBoundsException_6" + val methodName15 = "testSort_ThrowArrayIndexOutOfBoundsException_7" + val methodName16 = "testSort_TGreaterOrEqualE2OfA" + val methodName17 = "testSort_TGreaterOrEqualE1OfA" + val methodName18 = "testSort_TLessThanE1OfA" + val methodName19 = "testSort_KOfAEqualsPivot" + val methodName20 = "testSort_AkLessThanPivot" + val methodName21 = "testSort_ThrowArrayIndexOutOfBoundsException_8" + val methodName22 = "testSort_ThrowArrayIndexOutOfBoundsException_9" + val methodName23 = "testSort_TGreaterOrEqualE1OfA_1" + val methodName24 = "testSort_ThrowArrayIndexOutOfBoundsException_10" + val methodName25 = "testSort_ThrowArrayIndexOutOfBoundsException_11" + val methodName26 = "testSort_TLessThanE1OfA_1" + val methodName27 = "testSort_ThrowArrayIndexOutOfBoundsException_12" + val methodName28 = "testSort_ThrowArrayIndexOutOfBoundsException_13" + val methodName29 = "testSort_TGreaterOrEqualE2OfA_1" + val methodName30 = "testSort_ThrowArrayIndexOutOfBoundsException_14" + val methodName31 = "testSort_E4OfAEqualsE5OfA" + val methodName32 = "testSort_PrefixIncrementKLessOrEqualRightAndK1OfALessOrEqualKOfA" + val methodName33 = "testSort_PrefixIncrementKLessOrEqualRightAndK1OfAGreaterOrEqualKOfA" + + val displayName1 = "right - left < 286 : False -> return" + val displayName2 = "length < 47 : True -> return" + val displayName3 = "while(ai < a[j]) -> return" + val displayName4 = "while(ai < a[j]) -> return" + val displayName5 = "while(k < right && a[k] == a[k + 1]) -> ThrowArrayIndexOutOfBoundsException" + val displayName6 = "while(k < right && a[k] == a[k + 1]) -> ThrowNullPointerException" + val displayName7 = "while(k < right && a[k] == a[k + 1]) -> ThrowArrayIndexOutOfBoundsException" + val displayName8 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName9 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName10 = "internalSort(a, left, right, true) : True -> ThrowNullPointerException" + val displayName11 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName12 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName13 = "internalSort(a, left, right, true) : True -> ThrowNullPointerException" + val displayName14 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName15 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName16 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName17 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName18 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName19 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName20 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName21 = "while(k < right && a[k] == a[k + 1]) -> ThrowArrayIndexOutOfBoundsException" + val displayName22 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName23 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName24 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName25 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName26 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName27 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName28 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName29 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName30 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName31 = "internalSort(a, left, right, true) : True -> ThrowArrayIndexOutOfBoundsException" + val displayName32 = "while(++k <= right && a[k - 1] <= a[k]) -> ThrowArrayIndexOutOfBoundsException" + val displayName33 = "while(++k <= right && a[k - 1] >= a[k]) -> ThrowArrayIndexOutOfBoundsException" + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4, + summary5, + summary6, + summary7, + summary8, + summary9, + summary10, + summary11, + summary12, + summary13, + summary14, + summary15, + summary16, + summary17, + summary18, + summary19, + summary20, + summary21, + summary22, + summary23, + summary24, + summary25, + summary26, + summary27, + summary28, + summary29, + summary30, + summary31, + summary32, + summary33 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4, + displayName5, + displayName6, + displayName7, + displayName8, + displayName9, + displayName10, + displayName11, + displayName12, + displayName13, + displayName14, + displayName15, + displayName16, + displayName17, + displayName18, + displayName19, + displayName20, + displayName21, + displayName22, + displayName23, + displayName24, + displayName25, + displayName26, + displayName27, + displayName28, + displayName29, + displayName30, + displayName31, + displayName32, + displayName33 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4, + methodName5, + methodName6, + methodName7, + methodName8, + methodName9, + methodName10, + methodName11, + methodName12, + methodName13, + methodName14, + methodName15, + methodName16, + methodName17, + methodName18, + methodName19, + methodName20, + methodName21, + methodName22, + methodName23, + methodName24, + methodName25, + methodName26, + methodName27, + methodName28, + methodName29, + methodName30, + methodName31, + methodName32, + methodName33 + ) + + val clusterInfo = listOf( + Pair(UtClusterInfo("SYMBOLIC EXECUTION ENGINE: SUCCESSFUL EXECUTIONS for method sort(int[], int, int, int[], int, int)", null), 4), + Pair( + UtClusterInfo( + "SYMBOLIC EXECUTION ENGINE: ERROR SUITE for method sort(int[], int, int, int[], int, int)", null + ), 29 + ) + ) + + + val method = ArraysQuickSort::sort + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames, clusterInfo) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryBinarySearchTest.kt b/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryBinarySearchTest.kt new file mode 100644 index 0000000000..a9175c1b9c --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryBinarySearchTest.kt @@ -0,0 +1,94 @@ +package examples.algorithms + +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.utbot.examples.algorithms.BinarySearch +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate + +class SummaryBinarySearchTest : SummaryTestCaseGeneratorTest( + BinarySearch::class, +) { + @Test + fun testLeftBinSearch() { + val summary1 = "Test does not iterate while(left < right - 1), executes conditions:\n" + + " (found): False\n" + + "returns from: return -1;\n" + val summary2 = "Test iterates the loop while(left < right - 1) once,\n" + + " inside this loop, the test executes conditions:\n" + + " (array[middle] == key): False,\n" + + " (array[middle] < key): True\n" + + "Test later executes conditions:\n" + + " (found): False\n" + + "returns from: return -1;" + val summary3 = "Test iterates the loop while(left < right - 1) once,\n" + + " inside this loop, the test executes conditions:\n" + + " (array[middle] == key): True,\n" + + " (array[middle] < key): False\n" + + "Test later executes conditions:\n" + + " (found): True\n" + + "returns from: return right + 1;\n" + val summary4 = "Test iterates the loop while(left < right - 1) once,\n" + + " inside this loop, the test executes conditions:\n" + + " (array[middle] == key): True,\n" + + " (array[middle] < key): False\n" + + "Test afterwards executes conditions:\n" + + " (found): True\n" + + "returns from: return right + 1;\n" + val summary5 = "Test invokes:\n" + + " org.utbot.examples.algorithms.BinarySearch#isUnsorted(long[]) once\n" + + "throws NullPointerException when: isUnsorted(array)\n" + val summary6 = "Test invokes:\n" + + " org.utbot.examples.algorithms.BinarySearch#isUnsorted(long[]) once\n" + + "executes conditions:\n" + + " (isUnsorted(array)): True\n" + + "throws IllegalArgumentException when: isUnsorted(array)\n" + + val methodName1 = "testLeftBinSearch_NotFound" + val methodName2 = "testLeftBinSearch_MiddleOfArrayLessThanKey" + val methodName3 = "testLeftBinSearch_Found" + val methodName4 = "testLeftBinSearch_Found_1" + val methodName5 = "testLeftBinSearch_ThrowNullPointerException" + val methodName6 = "testLeftBinSearch_ThrowIllegalArgumentException" + + val displayName1 = "found : False -> return -1" + val displayName2 = "array[middle] == key : False -> return -1" + val displayName3 = "while(left < right - 1) -> return right + 1" + val displayName4 = "while(left < right - 1) -> return right + 1" + val displayName5 = "isUnsorted(array) -> ThrowNullPointerException" + val displayName6 = "isUnsorted(array) -> ThrowIllegalArgumentException" + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4, + summary5, + summary6 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4, + displayName5, + displayName6 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4, + methodName5, + methodName6 + ) + + val method = BinarySearch::leftBinSearch + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryCorrectBracketSequences.kt b/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryCorrectBracketSequences.kt new file mode 100644 index 0000000000..9fb1ef82da --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryCorrectBracketSequences.kt @@ -0,0 +1,52 @@ +package examples.algorithms + +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.utbot.examples.algorithms.CorrectBracketSequences +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate + +class SummaryCorrectBracketSequences : SummaryTestCaseGeneratorTest( + CorrectBracketSequences::class, +) { + @Test + fun testIsTheSameType() { + val commonSummary = "Test returns from: return a == '(' && b == ')' || a == '{' && b == '}' || a == '[' && b == ']';\n" + + val methodName1 = "testIsTheSameType_ANotEqualsCharAndBNotEqualsCharOrANotEqualsCharAndBNotEqualsCharOrANotEqualsCharAndBNotEqualsChar" + val methodName2 = "testIsTheSameType_ANotEqualsCharAndBNotEqualsCharOrANotEqualsCharAndBNotEqualsCharOrANotEqualsCharAndBNotEqualsChar_1" + val methodName3 = "testIsTheSameType_AEqualsCharAndBEqualsCharOrAEqualsCharAndBEqualsCharOrAEqualsCharAndBEqualsChar" + val methodName4 = "testIsTheSameType_ANotEqualsCharAndBNotEqualsCharOrANotEqualsCharAndBNotEqualsCharOrANotEqualsCharAndBNotEqualsChar_2" + val methodName5 = "testIsTheSameType_ANotEqualsCharAndBNotEqualsCharOrANotEqualsCharAndBNotEqualsCharOrANotEqualsCharAndBNotEqualsChar_3" + val methodName6 = "testIsTheSameType_AEqualsCharAndBEqualsCharOrAEqualsCharAndBEqualsCharOrAEqualsCharAndBEqualsChar_1" + val methodName7 = "testIsTheSameType_AEqualsCharAndBEqualsCharOrAEqualsCharAndBEqualsCharOrAEqualsCharAndBEqualsChar_2" + + val commonDiplayName1 = "return a == '(' && b == ')' || a == '{' && b == '}' || a == '[' && b == ']' : False -> return a == '(' && b == ')' || a == '{' && b == '}' || a == '[' && b == ']'" + val commonDisplayName2 = "return a == '(' && b == ')' || a == '{' && b == '}' || a == '[' && b == ']' : True -> return a == '(' && b == ')' || a == '{' && b == '}' || a == '[' && b == ']'" + + val summaryKeys = listOf( + commonSummary + ) + + val displayNames = listOf( + commonDiplayName1, + commonDisplayName2 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4, + methodName5, + methodName6, + methodName7 + ) + + val method = CorrectBracketSequences::isTheSameType + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryReturnExampleTest.kt b/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryReturnExampleTest.kt index a12c2854e6..ac60b7d8ea 100644 --- a/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryReturnExampleTest.kt +++ b/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummaryReturnExampleTest.kt @@ -3,8 +3,8 @@ package examples.algorithms import examples.SummaryTestCaseGeneratorTest import org.utbot.examples.algorithms.ReturnExample import org.junit.jupiter.api.Test -import org.utbot.examples.DoNotCalculate import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( ReturnExample::class, @@ -94,7 +94,7 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( val mockStrategy = MockStrategyApi.NO_MOCKS val coverage = DoNotCalculate - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test @@ -126,9 +126,9 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( " 2nd return statement: return a;\n" val methodName1 = "testCompareChars_NLessThan1" - val methodName2 = "testCompareChars_0OfCharactertoCharsiEqualsA" // TODO: a weird unclear naming - val methodName3 = "testCompareChars_0OfCharactertoCharsiEqualsB" - val methodName4 = "testCompareChars_0OfCharactertoCharsiNotEqualsB" // TODO: si -> is + val methodName2 = "testCompareChars_0OfCharacterToCharsIEqualsA" // TODO: a weird unclear naming + val methodName3 = "testCompareChars_0OfCharacterToCharsIEqualsB" + val methodName4 = "testCompareChars_0OfCharacterToCharsINotEqualsB" val displayName1 = "n < 1 : True -> return ' '" val displayName2 = "Character.toChars(i)[0] == a : True -> return b" @@ -160,24 +160,24 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( val mockStrategy = MockStrategyApi.NO_MOCKS val coverage = DoNotCalculate - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testInnerVoidCompareChars() { - val summary1 = "Test calls ReturnExample::compareChars,\n" + + val summary1 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compareChars(char,char,int)},\n" + " there it executes conditions:\n" + " (n < 1): True\n" + " returns from: return ' ';\n" + " " // TODO: generates empty String or \n a the end - val summary2 = "Test calls ReturnExample::compareChars,\n" + + val summary2 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compareChars(char,char,int)},\n" + " there it executes conditions:\n" + " (n < 1): False\n" + " iterates the loop for(int i = 0; i < n; i++) once,\n" + " inside this loop, the test executes conditions:\n" + " (Character.toChars(i)[0] == a): True\n" + " returns from: return b;" - val summary3 = "Test calls ReturnExample::compareChars,\n" + + val summary3 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compareChars(char,char,int)},\n" + " there it executes conditions:\n" + " (n < 1): False\n" + " iterates the loop for(int i = 0; i < n; i++) once,\n" + @@ -186,7 +186,7 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( " (Character.toChars(i)[0] == b): False\n" + " Test then returns from: return a;\n" + " " // TODO: generates empty String or \n a the end - val summary4 = "Test calls ReturnExample::compareChars,\n" + + val summary4 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compareChars(char,char,int)},\n" + " there it executes conditions:\n" + " (n < 1): False\n" + " iterates the loop for(int i = 0; i < n; i++) once,\n" + @@ -196,9 +196,9 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( " returns from: return a;" val methodName1 = "testInnerVoidCompareChars_NLessThan1" - val methodName2 = "testInnerVoidCompareChars_0OfCharactertoCharsiEqualsA" // TODO: a weird unclear naming - val methodName3 = "testInnerVoidCompareChars_0OfCharactertoCharsiNotEqualsB" - val methodName4 = "testInnerVoidCompareChars_0OfCharactertoCharsiEqualsB" // TODO: si -> is + val methodName2 = "testInnerVoidCompareChars_0OfCharacterToCharsIEqualsA" // TODO: a weird unclear naming + val methodName3 = "testInnerVoidCompareChars_0OfCharacterToCharsINotEqualsB" + val methodName4 = "testInnerVoidCompareChars_0OfCharacterToCharsIEqualsB" val displayName1 = "n < 1 : True -> return ' '" val displayName2 = "Character.toChars(i)[0] == a : True -> return b" @@ -230,18 +230,18 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( val mockStrategy = MockStrategyApi.NO_MOCKS val coverage = DoNotCalculate - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testInnerReturnCompareChars() { - val summary1 = "Test calls ReturnExample::compareChars,\n" + + val summary1 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compareChars(char,char,int)},\n" + " there it executes conditions:\n" + " (n < 1): True\n" + " returns from: return ' ';\n" + " \n" + "Test later returns from: return compareChars(a, b, n);\n" - val summary2 = "Test calls ReturnExample::compareChars,\n" + + val summary2 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compareChars(char,char,int)},\n" + " there it executes conditions:\n" + " (n < 1): False\n" + " iterates the loop for(int i = 0; i < n; i++) once,\n" + @@ -249,7 +249,7 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( " (Character.toChars(i)[0] == a): True\n" + " returns from: return b;\n" + "Test later returns from: return compareChars(a, b, n);\n" - val summary3 = "Test calls ReturnExample::compareChars,\n" + + val summary3 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compareChars(char,char,int)},\n" + " there it executes conditions:\n" + " (n < 1): False\n" + " iterates the loop for(int i = 0; i < n; i++) once,\n" + @@ -259,7 +259,7 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( " Test then returns from: return a;\n" + " \n" + // "Test afterwards returns from: return compareChars(a, b, n);\n" - val summary4 = "Test calls ReturnExample::compareChars,\n" + + val summary4 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compareChars(char,char,int)},\n" + " there it executes conditions:\n" + " (n < 1): False\n" + " iterates the loop for(int i = 0; i < n; i++) once,\n" + @@ -270,9 +270,9 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( "Test afterwards returns from: return compareChars(a, b, n);\n" val methodName1 = "testInnerReturnCompareChars_NLessThan1" - val methodName2 = "testInnerReturnCompareChars_0OfCharactertoCharsiEqualsA" // TODO: a weird unclear naming - val methodName3 = "testInnerReturnCompareChars_0OfCharactertoCharsiNotEqualsB" - val methodName4 = "testInnerReturnCompareChars_0OfCharactertoCharsiEqualsB" // TODO: si -> is + val methodName2 = "testInnerReturnCompareChars_0OfCharacterToCharsIEqualsA" // TODO: a weird unclear naming + val methodName3 = "testInnerReturnCompareChars_0OfCharacterToCharsINotEqualsB" + val methodName4 = "testInnerReturnCompareChars_0OfCharacterToCharsIEqualsB" val displayName1 = "n < 1 : True -> return ' '" val displayName2 = "Character.toChars(i)[0] == a : True -> return b" @@ -304,18 +304,18 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( val mockStrategy = MockStrategyApi.NO_MOCKS val coverage = DoNotCalculate - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testInnerVoidCompare() { - val summary1 = "Test calls ReturnExample::compare,\n" + + val summary1 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compare(int,int)},\n" + " there it executes conditions:\n" + " (a < 0): False,\n" + " (b < 0): True\n" + " returns from: return a;\n" + " " // TODO: remove blank line - val summary2 = "Test calls ReturnExample::compare,\n" + + val summary2 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compare(int,int)},\n" + " there it executes conditions:\n" + " (a < 0): False,\n" + " (b < 0): False,\n" + @@ -323,19 +323,19 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( " (a > b): True\n" + " returns from: return b;\n" + " " // TODO: remove blank line - val summary3 = "Test calls ReturnExample::compare,\n" + + val summary3 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compare(int,int)},\n" + " there it executes conditions:\n" + " (a < 0): False,\n" + " (b < 0): False,\n" + " (b == 10): True\n" + " returns from: return c;\n" + " " // TODO: remove blank line - val summary4 = "Test calls ReturnExample::compare,\n" + + val summary4 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compare(int,int)},\n" + " there it executes conditions:\n" + " (a < 0): True\n" + " returns from: return a;\n" + " " // TODO: remove blank line - val summary5 = "Test calls ReturnExample::compare,\n" + + val summary5 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compare(int,int)},\n" + " there it executes conditions:\n" + " (a < 0): False,\n" + " (b < 0): False,\n" + @@ -344,7 +344,7 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( " (a < b): True\n" + " returns from: return a;\n" + " " // TODO: remove blank line - val summary6 = "Test calls ReturnExample::compare,\n" + + val summary6 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compare(int,int)},\n" + " there it executes conditions:\n" + " (a < 0): False,\n" + " (b < 0): False,\n" + @@ -399,19 +399,19 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( val mockStrategy = MockStrategyApi.NO_MOCKS val coverage = DoNotCalculate - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testInnerReturnCompare() { - val summary1 = "Test calls ReturnExample::compare,\n" + + val summary1 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compare(int,int)},\n" + " there it executes conditions:\n" + " (a < 0): False,\n" + " (b < 0): True\n" + " returns from: return a;\n" + " \n" + "Test then returns from: return compare(a, b);\n" - val summary2 = "Test calls ReturnExample::compare,\n" + + val summary2 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compare(int,int)},\n" + " there it executes conditions:\n" + " (a < 0): False,\n" + " (b < 0): False,\n" + @@ -420,7 +420,7 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( " returns from: return b;\n" + " \n" + "Test afterwards returns from: return compare(a, b);\n" - val summary3 = "Test calls ReturnExample::compare,\n" + + val summary3 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compare(int,int)},\n" + " there it executes conditions:\n" + " (a < 0): False,\n" + " (b < 0): False,\n" + @@ -428,13 +428,13 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( " returns from: return c;\n" + " \n" + "Test then returns from: return compare(a, b);\n" - val summary4 = "Test calls ReturnExample::compare,\n" + + val summary4 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compare(int,int)},\n" + " there it executes conditions:\n" + " (a < 0): True\n" + " returns from: return a;\n" + " \n" + "Test next returns from: return compare(a, b);\n" - val summary5 = "Test calls ReturnExample::compare,\n" + + val summary5 = "Test calls{@link org.utbot.examples.algorithms.ReturnExample#compare(int,int)},\n" + " there it executes conditions:\n" + " (a < 0): False,\n" + " (b < 0): False,\n" + @@ -444,7 +444,7 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( " returns from: return a;\n" + " \n" + "Test afterwards returns from: return compare(a, b);\n" - val summary6 = "Test calls ReturnExample::compare,\n" + + val summary6 = "Test calls {@link org.utbot.examples.algorithms.ReturnExample#compare(int,int)},\n" + " there it executes conditions:\n" + " (a < 0): False,\n" + " (b < 0): False,\n" + @@ -501,6 +501,6 @@ class SummaryReturnExampleTest : SummaryTestCaseGeneratorTest( val mockStrategy = MockStrategyApi.NO_MOCKS val coverage = DoNotCalculate - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } } \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummarySortTest.kt b/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummarySortTest.kt new file mode 100644 index 0000000000..416c868587 --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/algorithms/SummarySortTest.kt @@ -0,0 +1,57 @@ +package examples.algorithms + +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.utbot.examples.algorithms.Sort +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate + +class SummarySortTest : SummaryTestCaseGeneratorTest( + Sort::class, +) { + @Test + fun testDefaultSort() { + val summary1 = "Test \n" + + "throws NullPointerException when: array.length < 4\n" + val summary2 = "Test executes conditions:\n" + + " (array.length < 4): True\n" + + "throws IllegalArgumentException when: array.length < 4\n" + val summary3 = "Test executes conditions:\n" + + " (array.length < 4): False\n" + + "invokes:\n" + + " {@link java.util.Arrays#sort(int[])} once\n" + + "returns from: return array;\n" + + val methodName1 = "testDefaultSort_ThrowNullPointerException" + val methodName2 = "testDefaultSort_ThrowIllegalArgumentException" + val methodName3 = "testDefaultSort_ArrayLengthGreaterOrEqual4" + + val displayName1 = "array.length < 4 -> ThrowNullPointerException" + val displayName2 = "array.length < 4 -> ThrowIllegalArgumentException" + val displayName3 = "array.length < 4 : False -> return array" + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + val method = Sort::defaultSort + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/collections/SummaryListWrapperReturnsVoidTest.kt b/utbot-summary-tests/src/test/kotlin/examples/collections/SummaryListWrapperReturnsVoidTest.kt new file mode 100644 index 0000000000..e95a5940f4 --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/collections/SummaryListWrapperReturnsVoidTest.kt @@ -0,0 +1,132 @@ +package examples.collections + +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.utbot.examples.collections.ListWrapperReturnsVoidExample +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate + +/** + * Tests verify that the previously discovered bug is not reproducible anymore. + * + * To get more details, see [issue-437](https://github.com/UnitTestBot/UTBotJava/issues/437) + */ +class SummaryListWrapperReturnsVoidTest : SummaryTestCaseGeneratorTest( + ListWrapperReturnsVoidExample::class, +) { + @Test + fun testRunForEach() { + val summary1 = "Test invokes:\n" + + " {@link java.util.List#forEach(java.util.function.Consumer)} once\n" + + "throws NullPointerException in: list.forEach(o -> {\n" + + " if (o == null)\n" + + " i[0]++;\n" + + "});\n" + val summary2 = "Test returns from: return i[0];" + val summary3 = "Test returns from: return i[0];" + val summary4 = "Test returns from: return i[0];" + + val methodName1 = "testRunForEach_ThrowNullPointerException" + val methodName2 = "testRunForEach_Return0OfI" + val methodName3 = "testRunForEach_Return0OfI_1" + val methodName4 = "testRunForEach_Return0OfI_2" + + val displayName1 = "list.forEach(o -> { if (o == null) i[0]++ }) : True -> ThrowNullPointerException" + val displayName2 = "-> return i[0]" + val displayName3 = "-> return i[0]" + val displayName4 = "-> return i[0]" + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4 + ) + + val method = ListWrapperReturnsVoidExample::runForEach + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testSumPositiveForEach() { + val summary1 = "Test throws NullPointerException in: list.forEach(i -> {\n" + + " if (i > 0) {\n" + + " sum[0] += i;\n" + + " }\n" + + "});" + val summary2 = "Test invokes: {@link java.util.List#forEach(java.util.function.Consumer)} once\n" + + "throws NullPointerException in: list.forEach(i -> {\n" + + " if (i > 0) {\n" + + " sum[0] += i;\n" + + " }\n" + + "});" + val summary3 = "Test executes conditions:\n" + + " (sum[0] == 0): True\n" + + "returns from: return 0;" + val summary4 = "Test executes conditions:\n" + + " (sum[0] == 0): True\n" + + "returns from: return 0;" + val summary5 = "Test executes conditions:\n" + + " (sum[0] == 0): False\n" + + "returns from: return sum[0];" + + val methodName1 = "testSumPositiveForEach_ThrowNullPointerException" + val methodName2 = "testSumPositiveForEach_ThrowNullPointerException_1" + val methodName3 = "testSumPositiveForEach_0OfSumEqualsZero" + val methodName4 = "testSumPositiveForEach_0OfSumEqualsZero_1" + val methodName5 = "testSumPositiveForEach_0OfSumNotEqualsZero" + + val displayName1 = "list.forEach(i -> { if (i > 0) { sum[0] += i } }) : True -> ThrowNullPointerException" + val displayName2 = "list.forEach(i -> { if (i > 0) { sum[0] += i } }) : True -> ThrowNullPointerException" + val displayName3 = "sum[0] == 0 : True -> return 0" + val displayName4 = "sum[0] == 0 : True -> return 0" + val displayName5 = "sum[0] == 0 : False -> return sum[0]" + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4, + summary5 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4, + displayName5 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4, + methodName5 + ) + + val method = ListWrapperReturnsVoidExample::sumPositiveForEach + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/controlflow/SummaryConditionsTest.kt b/utbot-summary-tests/src/test/kotlin/examples/controlflow/SummaryConditionsTest.kt new file mode 100644 index 0000000000..39f854f67e --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/controlflow/SummaryConditionsTest.kt @@ -0,0 +1,151 @@ +package examples.controlflow + +import examples.CustomJavaDocTagsEnabler +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.utbot.testing.DoNotCalculate +import org.utbot.examples.controlflow.Conditions +import org.utbot.framework.plugin.api.MockStrategyApi + +@ExtendWith(CustomJavaDocTagsEnabler::class) +class SummaryConditionsTest : SummaryTestCaseGeneratorTest( + Conditions::class +) { + @Test + fun testSimpleCondition() { + val summary1 = "@utbot.classUnderTest {@link Conditions}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Conditions#simpleCondition(boolean)}\n" + + "@utbot.executesCondition {@code (condition): False}\n" + + "@utbot.returnsFrom {@code return 0;}" + + val summary2 = "@utbot.classUnderTest {@link Conditions}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Conditions#simpleCondition(boolean)}\n" + + "@utbot.executesCondition {@code (condition): True}\n" + + "@utbot.returnsFrom {@code return 1;}" + + val methodName1 = "testSimpleCondition_NotCondition" + val methodName2 = "testSimpleCondition_Condition" + + val displayName1 = "condition : False -> return 0" + val displayName2 = "condition : True -> return 1" + + val summaryKeys = listOf( + summary1, + summary2 + ) + + val displayNames = listOf( + displayName1, + displayName2 + ) + + val methodNames = listOf( + methodName1, + methodName2 + ) + + val method = Conditions::simpleCondition + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testReturnCastFromTernaryOperator() { + val summary1 = "@utbot.classUnderTest {@link Conditions}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Conditions#returnCastFromTernaryOperator(long,int)}\n" + + "@utbot.returnsFrom {@code return (int) (a < 0 ? a + b : a);}\n" + val summary2 = "@utbot.classUnderTest {@link Conditions}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Conditions#returnCastFromTernaryOperator(long,int)}\n" + + "@utbot.returnsFrom {@code return (int) (a < 0 ? a + b : a);}\n" + val summary3 = "@utbot.classUnderTest {@link Conditions}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Conditions#returnCastFromTernaryOperator(long,int)}\n" + + "@utbot.throwsException {@link java.lang.ArithmeticException} in: a = a % b;\n" + + val methodName1 = "testReturnCastFromTernaryOperator_A0aba" + val methodName2 = "testReturnCastFromTernaryOperator_A0aba_1" + val methodName3 = "testReturnCastFromTernaryOperator_ThrowArithmeticException" + + val displayName1 = "return (int) (a < 0 ? a + b : a) : False -> return (int) (a < 0 ? a + b : a)" + val displayName2 = "return (int) (a < 0 ? a + b : a) : True -> return (int) (a < 0 ? a + b : a)" + val displayName3 = "a = a % b -> ThrowArithmeticException" + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + val method = Conditions::returnCastFromTernaryOperator + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testElseIf() { + val summary1 = "@utbot.classUnderTest {@link Conditions}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Conditions#elseIf(int)}\n" + + "@utbot.executesCondition {@code (id > 0): True}\n" + + "@utbot.returnsFrom {@code return 0;}" + + val summary2 = "@utbot.classUnderTest {@link Conditions}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Conditions#elseIf(int)}\n" + + "@utbot.executesCondition {@code (id > 0): False}\n" + + "@utbot.executesCondition {@code (id == 0): False}\n" + + "@utbot.returnsFrom {@code return 1;}" + + val summary3 = "@utbot.classUnderTest {@link Conditions}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Conditions#elseIf(int)}\n" + + "@utbot.executesCondition {@code (id > 0): False}\n" + + "@utbot.executesCondition {@code (id == 0): True}\n" + + "@utbot.throwsException {@link java.lang.RuntimeException} when: id == 0" + + val methodName1 = "testElseIf_IdGreaterThanZero" + val methodName2 = "testElseIf_IdNotEqualsZero" + val methodName3 = "testElseIf_ThrowRuntimeException" + + val displayName1 = "id > 0 : True -> id > 0" + val displayName2 = "id > 0 : False -> return 1" + val displayName3 = "id == 0 -> ThrowRuntimeException" + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + val method = Conditions::elseIf + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} diff --git a/utbot-summary-tests/src/test/kotlin/examples/controlflow/SummaryCycleTest.kt b/utbot-summary-tests/src/test/kotlin/examples/controlflow/SummaryCycleTest.kt index b4e3a6f3ba..63c2b84457 100644 --- a/utbot-summary-tests/src/test/kotlin/examples/controlflow/SummaryCycleTest.kt +++ b/utbot-summary-tests/src/test/kotlin/examples/controlflow/SummaryCycleTest.kt @@ -1,14 +1,10 @@ package examples.controlflow import examples.SummaryTestCaseGeneratorTest -import org.junit.Ignore -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Tag -import org.utbot.examples.controlflow.Cycles import org.junit.jupiter.api.Test -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.algorithms.ReturnExample +import org.utbot.examples.controlflow.Cycles import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate class SummaryCycleTest : SummaryTestCaseGeneratorTest( Cycles::class, @@ -19,7 +15,8 @@ class SummaryCycleTest : SummaryTestCaseGeneratorTest( " inside this loop, the test executes conditions:\n" + " (i < 0): True\n" + "returns from: return 2;" - val summary2 = "Test does not iterate for(int i = x - 5; i < x; i++), for(int j = i; j < x + i; j++), returns from: return -1;\n" // TODO: should it be formatted from the new string? + val summary2 = + "Test does not iterate for(int i = x - 5; i < x; i++), for(int j = i; j < x + i; j++), returns from: return -1;\n" // TODO: should it be formatted from the new string? val summary3 = "Test iterates the loop for(int i = x - 5; i < x; i++) once,\n" + " inside this loop, the test executes conditions:\n" + " (i < 0): False\n" + @@ -80,7 +77,7 @@ class SummaryCycleTest : SummaryTestCaseGeneratorTest( val mockStrategy = MockStrategyApi.NO_MOCKS val coverage = DoNotCalculate - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test @@ -123,11 +120,10 @@ class SummaryCycleTest : SummaryTestCaseGeneratorTest( methodName3 ) - val method = Cycles::structureLoop + val method = Cycles::structureLoop val mockStrategy = MockStrategyApi.NO_MOCKS val coverage = DoNotCalculate - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } - } \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/controlflow/SummarySwitchTest.kt b/utbot-summary-tests/src/test/kotlin/examples/controlflow/SummarySwitchTest.kt new file mode 100644 index 0000000000..8b2730593e --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/controlflow/SummarySwitchTest.kt @@ -0,0 +1,198 @@ +package examples.controlflow + +import examples.CustomJavaDocTagsEnabler +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.utbot.examples.controlflow.Switch +import org.utbot.examples.exceptions.ExceptionExamples +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate + +@ExtendWith(CustomJavaDocTagsEnabler::class) +class SummarySwitchTest : SummaryTestCaseGeneratorTest( + Switch::class +) { + @Test + fun testSimpleSwitch() { + val summary1 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#simpleSwitch(int)}\n" + + "@utbot.activatesSwitch {@code switch(x) case: 10}\n" + + "@utbot.returnsFrom {@code return 10;}" + val summary2 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#simpleSwitch(int)}\n" + + "@utbot.activatesSwitch {@code switch(x) case: default}\n" + + "@utbot.returnsFrom {@code return -1;}" + val summary3 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#simpleSwitch(int)}\n" + + "@utbot.activatesSwitch {@code switch(x) case: 12}\n" + + "@utbot.returnsFrom {@code return 12;}" + val summary4 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#simpleSwitch(int)}\n" + + "@utbot.activatesSwitch {@code switch(x) case: 13}\n" + + "@utbot.returnsFrom {@code return 13;}" + + val methodName1 = "testSimpleSwitch_Return10" + val methodName2 = "testSimpleSwitch_ReturnNegative1" + val methodName3 = "testSimpleSwitch_Return12" + val methodName4 = "testSimpleSwitch_Return13" + + val displayName1 = "switch(x) case: 10 -> return 10" + val displayName2 = "switch(x) case: default -> return -1" + val displayName3 = "switch(x) case: 12 -> return 12" + val displayName4 = "switch(x) case: 13 -> return 13" + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4 + ) + + val method = Switch::simpleSwitch + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testCharToIntSwitch() { + val summary1 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#charToIntSwitch(char)}\n" + + "@utbot.activatesSwitch {@code switch(c) case: 'C'}\n" + + "@utbot.returnsFrom {@code return 100;}\n" + val summary2 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#charToIntSwitch(char)}\n" + + "@utbot.activatesSwitch {@code switch(c) case: 'V'}\n" + + "@utbot.returnsFrom {@code return 5;}\n" + val summary3 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#charToIntSwitch(char)}\n" + + "@utbot.activatesSwitch {@code switch(c) case: 'I'}\n" + + "@utbot.returnsFrom {@code return 1;}\n" + val summary4 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#charToIntSwitch(char)}\n" + + "@utbot.activatesSwitch {@code switch(c) case: 'X'}\n" + + "@utbot.returnsFrom {@code return 10;}\n" + val summary5 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#charToIntSwitch(char)}\n" + + "@utbot.activatesSwitch {@code switch(c) case: 'M'}\n" + + "@utbot.returnsFrom {@code return 1000;}\n" + val summary6 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#charToIntSwitch(char)}\n" + + "@utbot.activatesSwitch {@code switch(c) case: 'D'}\n" + + "@utbot.returnsFrom {@code return 500;}\n" + val summary7 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#charToIntSwitch(char)}\n" + + "@utbot.activatesSwitch {@code switch(c) case: 'L'}\n" + + "@utbot.returnsFrom {@code return 50;}\n" + val summary8 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#charToIntSwitch(char)}\n" + + "@utbot.invokes {@link java.lang.StringBuilder#append(java.lang.String)}\n" + + "@utbot.invokes {@link java.lang.StringBuilder#append(char)}\n" + + "@utbot.invokes {@link java.lang.StringBuilder#toString()}\n" + + "@utbot.activatesSwitch {@code switch(c) case: default}\n" + + "@utbot.throwsException {@link java.lang.IllegalArgumentException} when: switch(c) case: default\n" + + val methodName1 = "testCharToIntSwitch_Return100" + val methodName2 = "testCharToIntSwitch_Return5" + val methodName3 = "testCharToIntSwitch_Return1" + val methodName4 = "testCharToIntSwitch_Return10" + val methodName5 = "testCharToIntSwitch_Return1000" + val methodName6 = "testCharToIntSwitch_Return500" + val methodName7 = "testCharToIntSwitch_Return50" + val methodName8 = "testCharToIntSwitch_ThrowIllegalArgumentException" + + val displayName1 = "switch(c) case: 'C' -> return 100" + val displayName2 = "switch(c) case: 'V' -> return 5" + val displayName3 = "switch(c) case: 'I' -> return 1" + val displayName4 = "switch(c) case: 'X' -> return 10" + val displayName5 = "switch(c) case: 'M' -> return 1000" + val displayName6 = "switch(c) case: 'D' -> return 500" + val displayName7 = "switch(c) case: 'L' -> return 50" + val displayName8 = "switch(c) case: default -> ThrowIllegalArgumentException" + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4, + summary5, + summary6, + summary7, + summary8, + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4, + displayName5, + displayName6, + displayName7, + displayName8, + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4, + methodName5, + methodName6, + methodName7, + methodName8, + ) + + val method = Switch::charToIntSwitch + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testThrowExceptionInSwitchArgument() { + val summary1 = "@utbot.classUnderTest {@link Switch}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.controlflow.Switch#throwExceptionInSwitchArgument()}\n" + + "@utbot.invokes org.utbot.examples.controlflow.Switch#getChar()\n" + + "@utbot.throwsException {@link java.lang.RuntimeException} in: switch(getChar())\n" + + val methodName1 = "testThrowExceptionInSwitchArgument_ThrowRuntimeException" + + val displayName1 = "switch(getChar()) -> ThrowRuntimeException" + + val summaryKeys = listOf( + summary1, + ) + + val displayNames = listOf( + displayName1, + ) + + val methodNames = listOf( + methodName1, + ) + + val method = Switch::throwExceptionInSwitchArgument + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} diff --git a/utbot-summary-tests/src/test/kotlin/examples/enums/SummaryComplexEnumExampleTest.kt b/utbot-summary-tests/src/test/kotlin/examples/enums/SummaryComplexEnumExampleTest.kt new file mode 100644 index 0000000000..66021da212 --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/enums/SummaryComplexEnumExampleTest.kt @@ -0,0 +1,137 @@ +package examples.enums + +import examples.CustomJavaDocTagsEnabler +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.utbot.examples.enums.ComplexEnumExamples +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate + +@ExtendWith(CustomJavaDocTagsEnabler::class) +class SummaryComplexEnumExampleTest : SummaryTestCaseGeneratorTest( + ComplexEnumExamples::class +) { + @Test + fun testUnsafeWithField() { + val summary1 = "@utbot.classUnderTest {@link ComplexEnumExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.enums.ComplexEnumExamples#countEqualColors(org.utbot.examples.enums.ComplexEnumExamples.Color,org.utbot.examples.enums.ComplexEnumExamples.Color,org.utbot.examples.enums.ComplexEnumExamples.Color)}\n" + + "@utbot.executesCondition {@code (b == a): False}\n" + + "@utbot.executesCondition {@code (c == a): False}\n" + + "@utbot.executesCondition {@code (a == b): False}\n" + + "@utbot.executesCondition {@code (c == b): False}\n" + + "@utbot.executesCondition {@code (equalToA > equalToB): False}\n" + + "@utbot.returnsFrom {@code return equalToB;}" + val summary2 = "@utbot.classUnderTest {@link ComplexEnumExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.enums.ComplexEnumExamples#countEqualColors(org.utbot.examples.enums.ComplexEnumExamples.Color,org.utbot.examples.enums.ComplexEnumExamples.Color,org.utbot.examples.enums.ComplexEnumExamples.Color)}\n" + + "@utbot.executesCondition {@code (b == a): False}\n" + + "@utbot.executesCondition {@code (c == a): True}\n" + + "@utbot.executesCondition {@code (a == b): False}\n" + + "@utbot.executesCondition {@code (c == b): False}\n" + + "@utbot.executesCondition {@code (equalToA > equalToB): True}\n" + + "@utbot.returnsFrom {@code return equalToA;}\n" + val summary3 = "@utbot.classUnderTest {@link ComplexEnumExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.enums.ComplexEnumExamples#countEqualColors(org.utbot.examples.enums.ComplexEnumExamples.Color,org.utbot.examples.enums.ComplexEnumExamples.Color,org.utbot.examples.enums.ComplexEnumExamples.Color)}\n" + + "@utbot.executesCondition {@code (b == a): False}\n" + + "@utbot.executesCondition {@code (c == a): False}\n" + + "@utbot.executesCondition {@code (a == b): False}\n" + + "@utbot.executesCondition {@code (c == b): True}\n" + + "@utbot.executesCondition {@code (equalToA > equalToB): False}\n" + + "@utbot.returnsFrom {@code return equalToB;}\n" + val summary4 = "@utbot.classUnderTest {@link ComplexEnumExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.enums.ComplexEnumExamples#countEqualColors(org.utbot.examples.enums.ComplexEnumExamples.Color,org.utbot.examples.enums.ComplexEnumExamples.Color,org.utbot.examples.enums.ComplexEnumExamples.Color)}\n" + + "@utbot.executesCondition {@code (b == a): True}\n" + + "@utbot.executesCondition {@code (c == a): True}\n" + + "@utbot.executesCondition {@code (a == b): True}\n" + + "@utbot.executesCondition {@code (c == b): True}\n" + + "@utbot.executesCondition {@code (equalToA > equalToB): False}\n" + + "@utbot.returnsFrom {@code return equalToB;}" + + val methodName1 = "testCountEqualColors_EqualToALessOrEqualEqualToB" + val methodName2 = "testCountEqualColors_EqualToAGreaterThanEqualToB" + val methodName3 = "testCountEqualColors_EqualToALessOrEqualEqualToB_1" + val methodName4 = "testCountEqualColors_AEqualsB" + + val displayName1 = "b == a : False -> return equalToB" + val displayName2 = "equalToA > equalToB : True -> return equalToA" + val displayName3 = "b == a : False -> return equalToB" + val displayName4 = "b == a : True -> return equalToB" + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4 + ) + + + val method = ComplexEnumExamples::countEqualColors + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testFindState() { + val summary1 = "@utbot.classUnderTest {@link ComplexEnumExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.enums.ComplexEnumExamples#findState(int)}\n" + + "@utbot.invokes {@link org.utbot.examples.enums.State#findStateByCode(int)}\n" + + "@utbot.returnsFrom {@code return State.findStateByCode(code);}\n" + val summary2 = "@utbot.classUnderTest {@link ComplexEnumExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.enums.ComplexEnumExamples#findState(int)}\n" + + "@utbot.invokes {@link org.utbot.examples.enums.State#findStateByCode(int)}\n" + + "@utbot.returnsFrom {@code return State.findStateByCode(code);}\n" + val summary3 = "@utbot.classUnderTest {@link ComplexEnumExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.enums.ComplexEnumExamples#findState(int)}\n" + + "@utbot.invokes {@link org.utbot.examples.enums.State#findStateByCode(int)}\n" + + "@utbot.returnsFrom {@code return State.findStateByCode(code);}\n" + + val methodName1 = "testFindState_ReturnStateFindStateByCode" + val methodName2 = "testFindState_ReturnStateFindStateByCode_1" + val methodName3 = "testFindState_ReturnStateFindStateByCode_2" + + val displayName1 = "-> return State.findStateByCode(code)" + val displayName2 = "-> return State.findStateByCode(code)" + val displayName3 = "-> return State.findStateByCode(code)" + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + + val method = ComplexEnumExamples::findState + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/exceptions/SummaryExceptionClusteringExamplesTest.kt b/utbot-summary-tests/src/test/kotlin/examples/exceptions/SummaryExceptionClusteringExamplesTest.kt new file mode 100644 index 0000000000..3751f75dd0 --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/exceptions/SummaryExceptionClusteringExamplesTest.kt @@ -0,0 +1,77 @@ +package examples.exceptions + +import examples.CustomJavaDocTagsEnabler +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.utbot.testing.DoNotCalculate +import org.utbot.examples.exceptions.ExceptionClusteringExamples +import org.utbot.framework.plugin.api.MockStrategyApi + +@ExtendWith(CustomJavaDocTagsEnabler::class) +class SummaryExceptionClusteringExamplesTest : SummaryTestCaseGeneratorTest( + ExceptionClusteringExamples::class +) { + @Test + fun testDifferentExceptions() { + val summary1 = "@utbot.classUnderTest {@link ExceptionClusteringExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionClusteringExamples#differentExceptions(int)}\n" + + "@utbot.executesCondition {@code (i == 0): True}\n" + + "@utbot.throwsException {@link java.lang.ArithmeticException} in: return 100 / i;" + + val summary2 = "@utbot.classUnderTest {@link ExceptionClusteringExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionClusteringExamples#differentExceptions(int)}\n" + + "@utbot.executesCondition {@code (i == 0): False}\n" + + "@utbot.executesCondition {@code (i == 1): True}\n" + + "@utbot.throwsException {@link org.utbot.examples.exceptions.MyCheckedException} when: i == 1" + val summary3 = "@utbot.classUnderTest {@link ExceptionClusteringExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionClusteringExamples#differentExceptions(int)}\n" + + "@utbot.executesCondition {@code (i == 0): False}\n" + + "@utbot.executesCondition {@code (i == 1): False}\n" + + "@utbot.executesCondition {@code (i == 2): True}\n" + + "@utbot.throwsException {@link java.lang.IllegalArgumentException} when: i == 2" + val summary4 = "@utbot.classUnderTest {@link ExceptionClusteringExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionClusteringExamples#differentExceptions(int)}\n" + + "@utbot.executesCondition {@code (i == 0): False}\n" + + "@utbot.executesCondition {@code (i == 1): False}\n" + + "@utbot.executesCondition {@code (i == 2): False}\n" + + "@utbot.returnsFrom {@code return i * 2;}\n" + + val methodName1 = "testDifferentExceptions_ThrowArithmeticException" + val methodName2 = "testDifferentExceptions_ThrowMyCheckedException" + val methodName3 = "testDifferentExceptions_ThrowIllegalArgumentException" + val methodName4 = "testDifferentExceptions_INotEquals2" + + val displayName1 = "return 100 / i : True -> ThrowArithmeticException" + val displayName2 = "i == 1 -> ThrowMyCheckedException" + val displayName3 = "i == 2 -> ThrowIllegalArgumentException" + val displayName4 = "i == 0 : False -> return i * 2" + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4 + ) + + val method = ExceptionClusteringExamples::differentExceptions + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/exceptions/SummaryExceptionExampleTest.kt b/utbot-summary-tests/src/test/kotlin/examples/exceptions/SummaryExceptionExampleTest.kt new file mode 100644 index 0000000000..8d054cfdce --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/exceptions/SummaryExceptionExampleTest.kt @@ -0,0 +1,129 @@ +package examples.exceptions + +import examples.CustomJavaDocTagsEnabler +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.utbot.examples.exceptions.ExceptionExamples +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate + +@ExtendWith(CustomJavaDocTagsEnabler::class) +class SummaryExceptionExampleTest : SummaryTestCaseGeneratorTest( + ExceptionExamples::class +) { + @Test + fun testDifferentExceptions() { + val summary1 = "@utbot.classUnderTest {@link ExceptionExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionExamples#nestedExceptions(int)}\n" + + "@utbot.returnsFrom {@code return checkAll(i);}" + val summary2 = "@utbot.classUnderTest {@link ExceptionExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionExamples#nestedExceptions(int)}\n" + + "@utbot.returnsFrom {@code return -100;}\n" + + "@utbot.caughtException {@code RuntimeException e}" + val summary3 = "@utbot.classUnderTest {@link ExceptionExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionExamples#nestedExceptions(int)}\n" + + "@utbot.returnsFrom {@code return 100;}\n" + + "@utbot.caughtException {@code NullPointerException e}" + + val methodName1 = "testNestedExceptions_ReturnCheckAll" + val methodName2 = "testNestedExceptions_CatchRuntimeException" + val methodName3 = "testNestedExceptions_CatchNullPointerException" + + val displayName1 = "-> return checkAll(i)" + val displayName2 = "Catch (RuntimeException e) -> return -100" + val displayName3 = "Catch (NullPointerException e) -> return 100" + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + val method = ExceptionExamples::nestedExceptions + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testHangForSeconds() { + val summary1 = "@utbot.classUnderTest {@link ExceptionExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionExamples#hangForSeconds(int)}\n" + + "@utbot.returnsFrom {@code return seconds;}\n" + val summary2 = "@utbot.classUnderTest {@link ExceptionExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionExamples#hangForSeconds(int)}\n" + + "@utbot.iterates iterate the loop {@code for(int i = 0; i < seconds; i++)} once\n" + + "@utbot.returnsFrom {@code return seconds;}\n" + + "@utbot.detectsSuspiciousBehavior in: return seconds;\n" + + val methodName1 = "testHangForSeconds_ReturnSeconds" + val methodName2 = "testHangForSeconds_ThreadSleep" + + val displayName1 = "-> return seconds" + val displayName2 = "return seconds -> TimeoutExceeded" + + val summaryKeys = listOf( + summary1, + summary2 + ) + + val displayNames = listOf( + displayName1, + displayName2 + ) + + val methodNames = listOf( + methodName1, + methodName2 + ) + + val method = ExceptionExamples::hangForSeconds + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testThrowExceptionInMethodUnderTest() { + val summary1 = "@utbot.classUnderTest {@link ExceptionExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.exceptions.ExceptionExamples#throwExceptionInMethodUnderTest()}\n" + + "@utbot.throwsException {@link java.lang.RuntimeException} in: throw new RuntimeException(\"Exception message\");\n" + + val methodName1 = "testThrowExceptionInMethodUnderTest_ThrowRuntimeException" + + val displayName1 = " -> ThrowRuntimeException" + + val summaryKeys = listOf( + summary1, + ) + + val displayNames = listOf( + displayName1, + ) + + val methodNames = listOf( + methodName1, + ) + + val method = ExceptionExamples::throwExceptionInMethodUnderTest + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/inner/SummaryInnerCallsTest.kt b/utbot-summary-tests/src/test/kotlin/examples/inner/SummaryInnerCallsTest.kt index 005ff1d64c..b6072aa076 100644 --- a/utbot-summary-tests/src/test/kotlin/examples/inner/SummaryInnerCallsTest.kt +++ b/utbot-summary-tests/src/test/kotlin/examples/inner/SummaryInnerCallsTest.kt @@ -1,22 +1,17 @@ package examples.inner import examples.SummaryTestCaseGeneratorTest -import guava.examples.math.Stats -import org.junit.Ignore -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Tag -import org.utbot.examples.inner.InnerCalls import org.junit.jupiter.api.Test -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.controlflow.Cycles +import org.utbot.examples.inner.InnerCalls import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( InnerCalls::class, ) { @Test fun testCallLoopInsideLoop() { - val summary1 = "Test calls Cycles::loopInsideLoop,\n" + + val summary1 = "Test calls {@link org.utbot.examples.controlflow.Cycles#loopInsideLoop(int)},\n" + " there it iterates the loop for(int i = x - 5; i < x; i++) once,\n" + " inside this loop, the test executes conditions:\n" + " (i < 0): False\n" + @@ -25,17 +20,17 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( " (j == 7): True\n" + " returns from: return 1;\n" + "Test afterwards returns from: return cycles.loopInsideLoop(x);\n" - val summary2 = "Test calls Cycles::loopInsideLoop,\n" + + val summary2 = "Test calls {@link org.utbot.examples.controlflow.Cycles#loopInsideLoop(int)},\n" + " there it iterates the loop for(int i = x - 5; i < x; i++) once,\n" + " inside this loop, the test executes conditions:\n" + " (i < 0): True\n" + " returns from: return 2;\n" + "Test then returns from: return cycles.loopInsideLoop(x);\n" - val summary3 = "Test calls Cycles::loopInsideLoop,\n" + + val summary3 = "Test calls {@link org.utbot.examples.controlflow.Cycles#loopInsideLoop(int)},\n" + " there it does not iterate for(int i = x - 5; i < x; i++), for(int j = i; j < x + i; j++), returns from: return -1;\n" + " \n" + "Test later returns from: return cycles.loopInsideLoop(x);\n" - val summary4 = "Test calls Cycles::loopInsideLoop,\n" + + val summary4 = "Test calls {@link org.utbot.examples.controlflow.Cycles#loopInsideLoop(int)},\n" + " there it iterates the loop for(int i = x - 5; i < x; i++) once,\n" + " inside this loop, the test executes conditions:\n" + " (i < 0): False\n" + @@ -45,7 +40,7 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( " (j == 7): True\n" + " returns from: return 1;\n" + "Test later returns from: return cycles.loopInsideLoop(x);\n" - val summary5 = "Test calls Cycles::loopInsideLoop,\n" + + val summary5 = "Test calls {@link org.utbot.examples.controlflow.Cycles#loopInsideLoop(int)},\n" + " there it iterates the loop for(int i = x - 5; i < x; i++) 5 times. \n" + " Test further does not iterate for(int j = i; j < x + i; j++), returns from: return -1;\n" + " \n" + @@ -93,19 +88,19 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( val mockStrategy = MockStrategyApi.NO_MOCKS val coverage = DoNotCalculate - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testCallLeftBinSearch() { //NOTE: 5 and 6 cases has different paths but throws the equal exception. - val summary1 = "Test calls BinarySearch::leftBinSearch,\n" + + val summary1 = "Test calls {@link org.utbot.examples.algorithms.BinarySearch#leftBinSearch(long[],long)},\n" + " there it does not iterate while(left < right - 1), executes conditions:\n" + " (found): False\n" + " returns from: return -1;\n" + " \n" + "Test then returns from: return binarySearch.leftBinSearch(array, key);\n" - val summary2 = "Test calls BinarySearch::leftBinSearch,\n" + + val summary2 = "Test calls {@link org.utbot.examples.algorithms.BinarySearch#leftBinSearch(long[],long)},\n" + " there it iterates the loop while(left < right - 1) once,\n" + " inside this loop, the test executes conditions:\n" + " (array[middle] == key): False,\n" + @@ -115,7 +110,7 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( " returns from: return -1;\n" + " \n" + "Test next returns from: return binarySearch.leftBinSearch(array, key);\n" - val summary3 = "Test calls BinarySearch::leftBinSearch,\n" + + val summary3 = "Test calls {@link org.utbot.examples.algorithms.BinarySearch#leftBinSearch(long[],long)},\n" + " there it iterates the loop while(left < right - 1) once,\n" + " inside this loop, the test executes conditions:\n" + " (array[middle] == key): False,\n" + @@ -125,7 +120,7 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( " returns from: return -1;\n" + " \n" + "Test next returns from: return binarySearch.leftBinSearch(array, key);\n" - val summary4 = "Test calls BinarySearch::leftBinSearch,\n" + + val summary4 = "Test calls {@link org.utbot.examples.algorithms.BinarySearch#leftBinSearch(long[],long)},\n" + " there it iterates the loop while(left < right - 1) once,\n" + " inside this loop, the test executes conditions:\n" + " (array[middle] == key): True,\n" + @@ -139,9 +134,9 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( "throws IllegalArgumentException in: return binarySearch.leftBinSearch(array, key);\n" val summary6 = "Test \n" + "throws IllegalArgumentException in: return binarySearch.leftBinSearch(array, key);\n" - val summary7 = "Test calls BinarySearch::leftBinSearch,\n" + + val summary7 = "Test calls {@link org.utbot.examples.algorithms.BinarySearch#leftBinSearch(long[],long)},\n" + " there it invokes:\n" + - " BinarySearch::isUnsorted once\n" + + " org.utbot.examples.algorithms.BinarySearch#isUnsorted(long[]) once\n" + " triggers recursion of leftBinSearch once, \n" + "Test throws NullPointerException in: return binarySearch.leftBinSearch(array, key);\n" @@ -151,14 +146,15 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( val methodName4 = "testCallLeftBinSearch_Found" val methodName5 = "testCallLeftBinSearch_ThrowIllegalArgumentException" val methodName6 = "testCallLeftBinSearch_ThrowIllegalArgumentException_1" - val methodName7 = "testCallLeftBinSearch_BinarySearchIsUnsorted" + val methodName7 = "testCallLeftBinSearch_ThrowNullPointerException" val displayName1 = "found : False -> return -1" val displayName2 = "array[middle] < key : True -> return -1" val displayName3 = "while(left < right - 1) -> return -1" val displayName4 = "array[middle] == key : True -> return right + 1" - val displayName5 = "return binarySearch.leftBinSearch(array, key) : True -> ThrowIllegalArgumentException" // TODO: probably return statement could be removed + val displayName5 = + "return binarySearch.leftBinSearch(array, key) : True -> ThrowIllegalArgumentException" // TODO: probably return statement could be removed val displayName6 = "return binarySearch.leftBinSearch(array, key) : True -> ThrowIllegalArgumentException" val displayName7 = "return binarySearch.leftBinSearch(array, key) : True -> ThrowNullPointerException" @@ -197,25 +193,27 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( val mockStrategy = MockStrategyApi.NO_MOCKS val coverage = DoNotCalculate - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } // TODO: SAT-1211 @Test fun testCallCreateNewThreeDimensionalArray() { - val summary1 = "Test calls ArrayOfArrays::createNewThreeDimensionalArray,\n" + - " there it executes conditions:\n" + - " (length != 2): True\n" + - " returns from: return new int[0][][];\n" + - " " - val summary2 = "Test calls ArrayOfArrays::createNewThreeDimensionalArray,\n" + - " there it executes conditions:\n" + - " (length != 2): False\n" + - " iterates the loop for(int i = 0; i < length; i++) once,\n" + - " inside this loop, the test iterates the loop for(int j = 0; j < length; j++) once,\n" + - " inside this loop, the test iterates the loop for(int k = 0; k < length; k++)\n" + - " Test then returns from: return matrix;\n" + - " " + val summary1 = + "Test calls {@link org.utbot.examples.arrays.ArrayOfArrays#createNewThreeDimensionalArray(int,int)},\n" + + " there it executes conditions:\n" + + " (length != 2): True\n" + + " returns from: return new int[0][][];\n" + + " " + val summary2 = + "Test calls {@link org.utbot.examples.arrays.ArrayOfArrays#createNewThreeDimensionalArray(int,int)},\n" + + " there it executes conditions:\n" + + " (length != 2): False\n" + + " iterates the loop for(int i = 0; i < length; i++) once,\n" + + " inside this loop, the test iterates the loop for(int j = 0; j < length; j++) once,\n" + + " inside this loop, the test iterates the loop for(int k = 0; k < length; k++)\n" + + " Test then returns from: return matrix;\n" + + " " val methodName1 = "testCallCreateNewThreeDimensionalArray_LengthNotEquals2" val methodName2 = "testCallCreateNewThreeDimensionalArray_LengthEquals2" @@ -242,31 +240,31 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( val mockStrategy = MockStrategyApi.NO_MOCKS val coverage = DoNotCalculate - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testCallInitExamples() { // NOTE: paths are different for test cases 1 and 2 - val summary1 = "Test calls ExceptionExamples::initAnArray,\n" + + val summary1 = "Test calls {@link org.utbot.examples.exceptions.ExceptionExamples#initAnArray(int)},\n" + " there it catches exception:\n" + " IndexOutOfBoundsException e\n" + " returns from: return -3;\n" + " \n" + "Test later returns from: return exceptionExamples.initAnArray(n);\n" - val summary2 = "Test calls ExceptionExamples::initAnArray,\n" + + val summary2 = "Test calls {@link org.utbot.examples.exceptions.ExceptionExamples#initAnArray(int)},\n" + " there it catches exception:\n" + " IndexOutOfBoundsException e\n" + " returns from: return -3;\n" + " \n" + "Test then returns from: return exceptionExamples.initAnArray(n);\n" - val summary3 = "Test calls ExceptionExamples::initAnArray,\n" + + val summary3 = "Test calls {@link org.utbot.examples.exceptions.ExceptionExamples#initAnArray(int)},\n" + " there it catches exception:\n" + " NegativeArraySizeException e\n" + " returns from: return -2;\n" + " \n" + "Test next returns from: return exceptionExamples.initAnArray(n);\n" - val summary4 = "Test calls ExceptionExamples::initAnArray,\n" + + val summary4 = "Test calls {@link org.utbot.examples.exceptions.ExceptionExamples#initAnArray(int)},\n" + " there it returns from: return a[n - 1] + a[n - 2];\n" + " \n" + "Test afterwards returns from: return exceptionExamples.initAnArray(n);\n" @@ -306,32 +304,32 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( methodName4 ) - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testCallFactorial() { - val summary1 = "Test calls Recursion::factorial,\n" + + val summary1 = "Test calls {@link org.utbot.examples.recursion.Recursion#factorial(int)},\n" + " there it executes conditions:\n" + " (n == 0): True\n" + " returns from: return 1;\n" + " \n" + "Test next returns from: return r.factorial(n);\n" - val summary2 = "Test calls Recursion::factorial,\n" + + val summary2 = "Test calls {@link org.utbot.examples.recursion.Recursion#factorial(int)},\n" + " there it executes conditions:\n" + " (n == 0): False\n" + " triggers recursion of factorial once, returns from: return n * factorial(n - 1);\n" + " \n" + "Test further returns from: return r.factorial(n);\n" - val summary3 = "Test calls Recursion::factorial,\n" + + val summary3 = "Test calls {@link org.utbot.examples.recursion.Recursion#factorial(int)},\n" + " there it executes conditions:\n" + " (n < 0): True\n" + " triggers recursion of factorial once, \n" + "Test throws IllegalArgumentException in: return r.factorial(n);\n" - val methodName1 = "testCallFactorial_NEqualsZero" + val methodName1 = "testCallFactorial_ThrowIllegalArgumentException" val methodName2 = "testCallFactorial_NNotEqualsZero" - val methodName3 = "testCallFactorial_NLessThanZero" + val methodName3 = "testCallFactorial_NEqualsZero" val displayName1 = "n == 0 : True -> return 1" val displayName2 = "n == 0 : False -> return n * factorial(n - 1)" @@ -359,35 +357,35 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( methodName3 ) - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testCallSimpleInvoke() { - val summary1 = "Test calls InvokeExample::simpleFormula,\n" + + val summary1 = "Test calls {@link org.utbot.examples.invokes.InvokeExample#simpleFormula(int,int)},\n" + " there it executes conditions:\n" + " (fst < 100): False,\n" + " (snd < 100): True\n" + " \n" + "Test throws IllegalArgumentException in: return invokeExample.simpleFormula(f, s);\n" - val summary2 = "Test calls InvokeExample::simpleFormula,\n" + + val summary2 = "Test calls {@link org.utbot.examples.invokes.InvokeExample#simpleFormula(int,int)},\n" + " there it executes conditions:\n" + " (fst < 100): True\n" + " \n" + "Test throws IllegalArgumentException in: return invokeExample.simpleFormula(f, s);\n" - val summary3 = "Test calls InvokeExample::simpleFormula,\n" + + val summary3 = "Test calls {@link org.utbot.examples.invokes.InvokeExample#simpleFormula(int,int)},\n" + " there it executes conditions:\n" + " (fst < 100): False,\n" + " (snd < 100): False\n" + " invokes:\n" + - " InvokeExample::half once,\n" + - " InvokeExample::mult once\n" + + " org.utbot.examples.invokes.InvokeExample#half(int) once,\n" + + " org.utbot.examples.invokes.InvokeExample#mult(int,int) once\n" + " returns from: return mult(x, y);\n" + " \n" + "Test then returns from: return invokeExample.simpleFormula(f, s);\n" - val methodName1 = "testCallSimpleInvoke_SndLessThan100" - val methodName2 = "testCallSimpleInvoke_FstLessThan100" + val methodName1 = "testCallSimpleInvoke_ThrowIllegalArgumentException" + val methodName2 = "testCallSimpleInvoke_ThrowIllegalArgumentException_1" val methodName3 = "testCallSimpleInvoke_SndGreaterOrEqual100" val displayName1 = "return invokeExample.simpleFormula(f, s) : True -> ThrowIllegalArgumentException" @@ -416,44 +414,49 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( methodName3 ) - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testCallComplicatedMethod() { - val summary1 = "Test calls StringExamples::indexOf,\n" + - " there it invokes:\n" + - " String::indexOf once\n" + - " triggers recursion of indexOf once, \n" + - "Test throws NullPointerException in: return stringExamples.indexOf(s, key);\n" - val summary2 = "Test calls StringExamples::indexOf,\n" + - " there it invokes:\n" + - " String::indexOf once\n" + - " \n" + - "Test throws NullPointerException \n" - val summary3 = "Test calls StringExamples::indexOf,\n" + - " there it executes conditions:\n" + - " (i > 0): False,\n" + - " (i == 0): True\n" + - " returns from: return i;\n" + - " \n" + - "Test further returns from: return stringExamples.indexOf(s, key);\n" - val summary4 = "Test calls StringExamples::indexOf,\n" + - " there it executes conditions:\n" + - " (i > 0): False,\n" + - " (i == 0): False\n" + - " returns from: return i;\n" + - " \n" + - "Test later returns from: return stringExamples.indexOf(s, key);\n" - val summary5 = "Test calls StringExamples::indexOf,\n" + - " there it executes conditions:\n" + - " (i > 0): True\n" + - " returns from: return i;\n" + - " \n" + - "Test afterwards returns from: return stringExamples.indexOf(s, key);\n" - - val methodName1 = "testCallStringExample_StringIndexOf" - val methodName2 = "testCallStringExample_StringIndexOf_1" + val summary1 = + "Test calls {@link org.utbot.examples.strings.StringExamples#indexOf(java.lang.String,java.lang.String)},\n" + + " there it invokes:\n" + + " {@link java.lang.String#indexOf(java.lang.String)} once\n" + + " triggers recursion of indexOf once, \n" + + "Test throws NullPointerException in: return stringExamples.indexOf(s, key);\n" + val summary2 = + "Test calls {@link org.utbot.examples.strings.StringExamples#indexOf(java.lang.String,java.lang.String)},\n" + + " there it invokes:\n" + + " {@link java.lang.String#indexOf(java.lang.String)} once\n" + + " \n" + + "Test throws NullPointerException \n" + val summary3 = + "Test calls {@link org.utbot.examples.strings.StringExamples#indexOf(java.lang.String,java.lang.String)},\n" + + " there it executes conditions:\n" + + " (i > 0): False,\n" + + " (i == 0): True\n" + + " returns from: return i;\n" + + " \n" + + "Test further returns from: return stringExamples.indexOf(s, key);\n" + val summary4 = + "Test calls {@link org.utbot.examples.strings.StringExamples#indexOf(java.lang.String,java.lang.String)},\n" + + " there it executes conditions:\n" + + " (i > 0): False,\n" + + " (i == 0): False\n" + + " returns from: return i;\n" + + " \n" + + "Test later returns from: return stringExamples.indexOf(s, key);\n" + val summary5 = + "Test calls {@link org.utbot.examples.strings.StringExamples#indexOf(java.lang.String,java.lang.String)},\n" + + " there it executes conditions:\n" + + " (i > 0): True\n" + + " returns from: return i;\n" + + " \n" + + "Test afterwards returns from: return stringExamples.indexOf(s, key);\n" + + val methodName1 = "testCallStringExample_ThrowNullPointerException" + val methodName2 = "testCallStringExample_ThrowNullPointerException_1" val methodName3 = "testCallStringExample_IEqualsZero" val methodName4 = "testCallStringExample_INotEqualsZero" val methodName5 = "testCallStringExample_IGreaterThanZero" @@ -492,22 +495,22 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( methodName5 ) - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testCallSimpleSwitch() { - val summary1 = "Test calls Switch::simpleSwitch,\n" + - " there it activates switch case: 12, returns from: return 12;\n" + + val summary1 = "Test calls {@link org.utbot.examples.controlflow.Switch#simpleSwitch(int)},\n" + + " there it activates switch(x) case: 12, returns from: return 12;\n" + " " - val summary2 = "Test calls Switch::simpleSwitch,\n" + - " there it activates switch case: 13, returns from: return 13;\n" + + val summary2 = "Test calls {@link org.utbot.examples.controlflow.Switch#simpleSwitch(int)},\n" + + " there it activates switch(x) case: 13, returns from: return 13;\n" + " " - val summary3 = "Test calls Switch::simpleSwitch,\n" + - " there it activates switch case: 10, returns from: return 10;\n" + + val summary3 = "Test calls {@link org.utbot.examples.controlflow.Switch#simpleSwitch(int)},\n" + + " there it activates switch(x) case: 10, returns from: return 10;\n" + " " - val summary4 = "Test calls Switch::simpleSwitch,\n" + - " there it activates switch case: default, returns from: return -1;\n" + + val summary4 = "Test calls {@link org.utbot.examples.controlflow.Switch#simpleSwitch(int)},\n" + + " there it activates switch(x) case: default, returns from: return -1;\n" + " " val methodName1 = "testCallSimpleSwitch_Return12" @@ -518,7 +521,7 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( val displayName1 = "switch(x) case: 12 -> return 12" val displayName2 = "switch(x) case: 13 -> return 13" val displayName3 = "switch(x) case: 10 -> return 10" - val displayName4 = "switch(x) case: Default -> return -1" + val displayName4 = "switch(x) case: default -> return -1" val method = InnerCalls::callSimpleSwitch val mockStrategy = MockStrategyApi.NO_MOCKS @@ -545,22 +548,22 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( methodName4 ) - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testCallLookup() { - val summary1 = "Test calls Switch::lookupSwitch,\n" + - " there it activates switch case: 20, returns from: return 20;\n" + + val summary1 = "Test calls {@link org.utbot.examples.controlflow.Switch#lookupSwitch(int)},\n" + + " there it activates switch(x) case: 20, returns from: return 20;\n" + " " - val summary2 = "Test calls Switch::lookupSwitch,\n" + - " there it activates switch case: 30, returns from: return 30;\n" + + val summary2 = "Test calls {@link org.utbot.examples.controlflow.Switch#lookupSwitch(int)},\n" + + " there it activates switch(x) case: 30, returns from: return 30;\n" + " " - val summary3 = "Test calls Switch::lookupSwitch,\n" + - " there it activates switch case: 0, returns from: return 0;\n" + + val summary3 = "Test calls {@link org.utbot.examples.controlflow.Switch#lookupSwitch(int)},\n" + + " there it activates switch(x) case: 0, returns from: return 0;\n" + " " - val summary4 = "Test calls Switch::lookupSwitch,\n" + - " there it activates switch case: default, returns from: return -1;\n" + + val summary4 = "Test calls {@link org.utbot.examples.controlflow.Switch#lookupSwitch(int)},\n" + + " there it activates switch(x) case: default, returns from: return -1;\n" + " " val methodName1 = "testCallLookup_Return20" @@ -571,7 +574,7 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( val displayName1 = "switch(x) case: 20 -> return 20" val displayName2 = "switch(x) case: 30 -> return 30" val displayName3 = "switch(x) case: 0 -> return 0" - val displayName4 = "switch(x) case: Default -> return -1" + val displayName4 = "switch(x) case: default -> return -1" val method = InnerCalls::callLookup val mockStrategy = MockStrategyApi.NO_MOCKS @@ -598,39 +601,39 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( methodName4 ) - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testDoubleCall() { - val summary1 = "Test calls InnerCalls::callSimpleInvoke,\n" + - " there it calls InvokeExample::simpleFormula,\n" + + val summary1 = "Test calls {@link org.utbot.examples.inner.InnerCalls#callSimpleInvoke(int,int)},\n" + + " there it calls {@link org.utbot.examples.invokes.InvokeExample#simpleFormula(int,int)},\n" + " there it executes conditions:\n" + " (fst < 100): True\n" + " \n" + "Test throws IllegalArgumentException in: callSimpleInvoke(f, s);\n" - val summary2 = "Test calls InnerCalls::callSimpleInvoke,\n" + - " there it calls InvokeExample::simpleFormula,\n" + + val summary2 = "Test calls {@link org.utbot.examples.inner.InnerCalls#callSimpleInvoke(int,int)},\n" + + " there it calls {@link org.utbot.examples.invokes.InvokeExample#simpleFormula(int,int)},\n" + " there it executes conditions:\n" + " (fst < 100): False,\n" + " (snd < 100): True\n" + " \n" + "Test throws IllegalArgumentException in: callSimpleInvoke(f, s);\n" - val summary3 = "Test calls InnerCalls::callSimpleInvoke,\n" + - " there it calls InvokeExample::simpleFormula,\n" + + val summary3 = "Test calls {@link org.utbot.examples.inner.InnerCalls#callSimpleInvoke(int,int)},\n" + + " there it calls {@link org.utbot.examples.invokes.InvokeExample#simpleFormula(int,int)},\n" + " there it executes conditions:\n" + " (fst < 100): False,\n" + " (snd < 100): False\n" + " invokes:\n" + - " InvokeExample::half once,\n" + - " InvokeExample::mult once\n" + + " org.utbot.examples.invokes.InvokeExample#half(int) once,\n" + + " org.utbot.examples.invokes.InvokeExample#mult(int,int) once\n" + " returns from: return mult(x, y);\n" + " \n" + " Test later returns from: return invokeExample.simpleFormula(f, s);\n" + " " - val methodName1 = "testDoubleSimpleInvoke_FstLessThan100" - val methodName2 = "testDoubleSimpleInvoke_SndLessThan100" + val methodName1 = "testDoubleSimpleInvoke_ThrowIllegalArgumentException" + val methodName2 = "testDoubleSimpleInvoke_ThrowIllegalArgumentException_1" val methodName3 = "testDoubleSimpleInvoke_SndGreaterOrEqual100" val displayName1 = "callSimpleInvoke(f, s) : True -> ThrowIllegalArgumentException" @@ -659,13 +662,13 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( methodName3 ) - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testDoubleCallLoopInsideLoop() { - val summary1 = "Test calls InnerCalls::callLoopInsideLoop,\n" + - " there it calls Cycles::loopInsideLoop,\n" + + val summary1 = "Test calls {@link org.utbot.examples.inner.InnerCalls#callLoopInsideLoop(int)},\n" + + " there it calls {@link org.utbot.examples.controlflow.Cycles#loopInsideLoop(int)},\n" + " there it iterates the loop for(int i = x - 5; i < x; i++) once,\n" + " inside this loop, the test executes conditions:\n" + " (i < 0): True\n" + @@ -673,15 +676,15 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( " Test afterwards returns from: return cycles.loopInsideLoop(x);\n" + " \n" + "Test further returns from: return result;\n" - val summary2 = "Test calls InnerCalls::callLoopInsideLoop,\n" + - " there it calls Cycles::loopInsideLoop,\n" + + val summary2 = "Test calls {@link org.utbot.examples.inner.InnerCalls#callLoopInsideLoop(int)},\n" + + " there it calls {@link org.utbot.examples.controlflow.Cycles#loopInsideLoop(int)},\n" + " there it does not iterate for(int i = x - 5; i < x; i++), for(int j = i; j < x + i; j++), returns from: return -1;\n" + " \n" + " Test next returns from: return cycles.loopInsideLoop(x);\n" + " \n" + "Test later returns from: return result;\n" - val summary3 = "Test calls InnerCalls::callLoopInsideLoop,\n" + - " there it calls Cycles::loopInsideLoop,\n" + + val summary3 = "Test calls {@link org.utbot.examples.inner.InnerCalls#callLoopInsideLoop(int)},\n" + + " there it calls {@link org.utbot.examples.controlflow.Cycles#loopInsideLoop(int)},\n" + " there it iterates the loop for(int i = x - 5; i < x; i++) once,\n" + " inside this loop, the test executes conditions:\n" + " (i < 0): False\n" + @@ -692,8 +695,8 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( " Test next returns from: return cycles.loopInsideLoop(x);\n" + " \n" + "Test further returns from: return result;\n" - val summary4 = "Test calls InnerCalls::callLoopInsideLoop,\n" + - " there it calls Cycles::loopInsideLoop,\n" + + val summary4 = "Test calls {@link org.utbot.examples.inner.InnerCalls#callLoopInsideLoop(int)},\n" + + " there it calls {@link org.utbot.examples.controlflow.Cycles#loopInsideLoop(int)},\n" + " there it iterates the loop for(int i = x - 5; i < x; i++) once,\n" + " inside this loop, the test executes conditions:\n" + " (i < 0): False\n" + @@ -705,8 +708,8 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( " Test further returns from: return cycles.loopInsideLoop(x);\n" + " \n" + "Test then returns from: return result;\n" - val summary5 = "Test calls InnerCalls::callLoopInsideLoop,\n" + - " there it calls Cycles::loopInsideLoop,\n" + + val summary5 = "Test calls {@link org.utbot.examples.inner.InnerCalls#callLoopInsideLoop(int)},\n" + + " there it calls {@link org.utbot.examples.controlflow.Cycles#loopInsideLoop(int)},\n" + " there it iterates the loop for(int i = x - 5; i < x; i++) 5 times. \n" + " Test later does not iterate for(int j = i; j < x + i; j++), returns from: return -1;\n" + " \n" + @@ -754,41 +757,41 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( methodName5 ) - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } @Test fun testInnerCallFib() { - val summary1 = "Test calls Recursion::fib,\n" + + val summary1 = "Test calls {@link org.utbot.examples.recursion.Recursion#fib(int)},\n" + " there it executes conditions:\n" + " (n == 0): False,\n" + " (n == 1): True\n" + " returns from: return 1;\n" + " \n" + "Test next returns from: return r.fib(n);\n" - val summary2 = "Test calls Recursion::fib,\n" + + val summary2 = "Test calls {@link org.utbot.examples.recursion.Recursion#fib(int)},\n" + " there it executes conditions:\n" + " (n == 0): True\n" + " returns from: return 0;\n" + " \n" + "Test next returns from: return r.fib(n);\n" - val summary3 = "Test calls Recursion::fib,\n" + + val summary3 = "Test calls {@link org.utbot.examples.recursion.Recursion#fib(int)},\n" + " there it executes conditions:\n" + " (n == 0): False,\n" + " (n == 1): False\n" + " triggers recursion of fib twice, returns from: return fib(n - 1) + fib(n - 2);\n" + " \n" + "Test next returns from: return r.fib(n);\n" - val summary4 = "Test calls Recursion::fib,\n" + + val summary4 = "Test calls {@link org.utbot.examples.recursion.Recursion#fib(int)},\n" + " there it executes conditions:\n" + " (n < 0): True\n" + " triggers recursion of fib once, \n" + "Test throws IllegalArgumentException in: return r.fib(n);\n" val methodName1 = "testCallFib_NEquals1" - val methodName2 = "testCallFib_NEqualsZero" + val methodName2 = "testCallFib_ThrowIllegalArgumentException" val methodName3 = "testCallFib_NNotEquals1" - val methodName4 = "testCallFib_NLessThanZero" + val methodName4 = "testCallFib_NEqualsZero" val displayName1 = "n == 1 : True -> return 1" val displayName2 = "n == 0 : True -> return 0" @@ -820,6 +823,6 @@ class SummaryInnerCallsTest : SummaryTestCaseGeneratorTest( methodName4 ) - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } } \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/inner/SummaryNestedCallsTest.kt b/utbot-summary-tests/src/test/kotlin/examples/inner/SummaryNestedCallsTest.kt index 096aed7fc9..93216593e4 100644 --- a/utbot-summary-tests/src/test/kotlin/examples/inner/SummaryNestedCallsTest.kt +++ b/utbot-summary-tests/src/test/kotlin/examples/inner/SummaryNestedCallsTest.kt @@ -1,33 +1,29 @@ package examples.inner import examples.SummaryTestCaseGeneratorTest -import org.junit.Ignore -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Tag -import org.utbot.examples.inner.NestedCalls import org.junit.jupiter.api.Test -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.inner.InnerCalls +import org.utbot.examples.inner.NestedCalls import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate class SummaryNestedCallsTest : SummaryTestCaseGeneratorTest( NestedCalls::class, ) { @Test fun testInvokeExample() { - val summary1 = "Test calls NestedCalls\$ExceptionExamples::initAnArray,\n" + + val summary1 = "Test calls {@link org.utbot.examples.inner.NestedCalls.ExceptionExamples#initAnArray(int)},\n" + " there it catches exception:\n" + " IndexOutOfBoundsException e\n" + " returns from: return -3;\n" + " \n" + "Test next returns from: return exceptionExamples.initAnArray(n);\n" - val summary2 = "Test calls NestedCalls\$ExceptionExamples::initAnArray,\n" + + val summary2 = "Test calls {@link org.utbot.examples.inner.NestedCalls.ExceptionExamples#initAnArray(int)},\n" + " there it catches exception:\n" + " NegativeArraySizeException e\n" + " returns from: return -2;\n" + " \n" + "Test afterwards returns from: return exceptionExamples.initAnArray(n);" - val summary3 = "Test calls NestedCalls\$ExceptionExamples::initAnArray,\n" + + val summary3 = "Test calls {@link org.utbot.examples.inner.NestedCalls.ExceptionExamples#initAnArray(int)},\n" + " there it returns from: return a[n - 1] + a[n - 2];\n" + " \n" + "Test next returns from: return exceptionExamples.initAnArray(n);\n" @@ -62,6 +58,6 @@ class SummaryNestedCallsTest : SummaryTestCaseGeneratorTest( methodName3 ) - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) } } \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/mock/SummaryCommonMocksExample.kt b/utbot-summary-tests/src/test/kotlin/examples/mock/SummaryCommonMocksExample.kt new file mode 100644 index 0000000000..77933d82de --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/mock/SummaryCommonMocksExample.kt @@ -0,0 +1,41 @@ +package examples.mock + +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.utbot.examples.mock.CommonMocksExample +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate + +class SummaryCommonMocksExample : SummaryTestCaseGeneratorTest( + CommonMocksExample::class, +) { + @Test + fun testClinitMockExample() { + val summary1 = "Test invokes:\n" + + " {@link java.lang.Integer#intValue()} twice\n" + + "returns from: return -ObjectWithFinalStatic.keyValue;\n" + + val methodName1 = "testClinitMockExample_IntegerIntValue" + + val displayName1 = "IntegerIntValue -> return -ObjectWithFinalStatic.keyValue" + + + val summaryKeys = listOf( + summary1 + ) + + val displayNames = listOf( + displayName1 + ) + + val methodNames = listOf( + methodName1 + ) + + val method = CommonMocksExample::clinitMockExample + val mockStrategy = MockStrategyApi.OTHER_CLASSES + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/nested/SummaryNestedTest.kt b/utbot-summary-tests/src/test/kotlin/examples/nested/SummaryNestedTest.kt new file mode 100644 index 0000000000..0252e3d970 --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/nested/SummaryNestedTest.kt @@ -0,0 +1,55 @@ +package examples.nested + +import examples.CustomJavaDocTagsEnabler +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.utbot.examples.nested.DeepNested +import org.utbot.examples.recursion.Recursion +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate + +@ExtendWith(CustomJavaDocTagsEnabler::class) +class SummaryNestedTest : SummaryTestCaseGeneratorTest( + DeepNested.Nested1.Nested2::class +) { + @Test + fun testNested() { + val summary1 = "@utbot.classUnderTest {@link DeepNested.Nested1.Nested2}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.nested.DeepNested.Nested1.Nested2#f(int)}\n" + + "@utbot.executesCondition {@code (i > 0): False}\n" + + "@utbot.returnsFrom {@code return 0;}\n" + + val summary2 = "@utbot.classUnderTest {@link DeepNested.Nested1.Nested2}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.nested.DeepNested.Nested1.Nested2#f(int)}\n" + + "@utbot.executesCondition {@code (i > 0): True}\n" + + "@utbot.returnsFrom {@code return 10;}" + + val methodName1 = "testF_ILessOrEqualZero" + val methodName2 = "testF_IGreaterThanZero" + + val displayName1 = "i > 0 : False -> return 0" + val displayName2 = "i > 0 : True -> return 10" + + val summaryKeys = listOf( + summary1, + summary2 + ) + + val displayNames = listOf( + displayName1, + displayName2 + ) + + val methodNames = listOf( + methodName1, + methodName2 + ) + + val method = DeepNested.Nested1.Nested2::f + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/objects/SummarySimpleClassExampleTest.kt b/utbot-summary-tests/src/test/kotlin/examples/objects/SummarySimpleClassExampleTest.kt new file mode 100644 index 0000000000..0a2e69bb95 --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/objects/SummarySimpleClassExampleTest.kt @@ -0,0 +1,55 @@ +package examples.objects + +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.utbot.examples.objects.SimpleClassExample +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate + +class SummarySimpleClassExampleTest : SummaryTestCaseGeneratorTest( + SimpleClassExample::class, +) { + @Test + fun testImmutableFieldAccess() { + val summary1 = "Test \n" + + "throws NullPointerException when: c.b == 10\n" + val summary2 = "Test executes conditions:\n" + + " (c.b == 10): False\n" + + "returns from: return 1;\n" + val summary3 = "Test executes conditions:\n" + + " (c.b == 10): True\n" + + "returns from: return 0;\n" + + val methodName1 = "testImmutableFieldAccess_ThrowNullPointerException" + val methodName2 = "testImmutableFieldAccess_CBNotEquals10" + val methodName3 = "testImmutableFieldAccess_CBEquals10" + + val displayName1 = "c.b == 10 -> ThrowNullPointerException" + val displayName2 = "c.b == 10 : False -> return 1" + val displayName3 = "c.b == 10 : True -> return 0" + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + val method = SimpleClassExample::immutableFieldAccess + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/recursion/SummaryRecursionTest.kt b/utbot-summary-tests/src/test/kotlin/examples/recursion/SummaryRecursionTest.kt new file mode 100644 index 0000000000..7fa6019ff4 --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/recursion/SummaryRecursionTest.kt @@ -0,0 +1,131 @@ +package examples.recursion + +import examples.CustomJavaDocTagsEnabler +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.utbot.examples.recursion.Recursion +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate + +@ExtendWith(CustomJavaDocTagsEnabler::class) +class SummaryRecursionTest : SummaryTestCaseGeneratorTest( + Recursion::class +) { + @Test + fun testFib() { + val summary1 = "@utbot.classUnderTest {@link Recursion}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.recursion.Recursion#fib(int)}\n" + + "@utbot.executesCondition {@code (n == 0): False}\n" + + "@utbot.executesCondition {@code (n == 1): True}\n" + + "@utbot.returnsFrom {@code return 1;}" + val summary2 = "@utbot.classUnderTest {@link Recursion}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.recursion.Recursion#fib(int)}\n" + + "@utbot.executesCondition {@code (n == 0): True}\n" + + "@utbot.returnsFrom {@code return 0;}\n" + val summary3 = "@utbot.classUnderTest {@link Recursion}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.recursion.Recursion#fib(int)}\n" + + "@utbot.executesCondition {@code (n == 1): False}\n" + + "@utbot.invokes {@link org.utbot.examples.recursion.Recursion#fib(int)}\n" + + "@utbot.invokes {@link org.utbot.examples.recursion.Recursion#fib(int)}\n" + + "@utbot.triggersRecursion fib, where the test execute conditions:\n" + + " {@code (n == 1): True}\n" + + "return from: {@code return 1;}" + + "@utbot.returnsFrom {@code return fib(n - 1) + fib(n - 2);}" + val summary4 = "@utbot.classUnderTest {@link Recursion}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.recursion.Recursion#fib(int)}\n" + + "@utbot.executesCondition {@code (n < 0): True}\n" + + "@utbot.throwsException {@link java.lang.IllegalArgumentException} when: n < 0" + + val methodName1 = "testFib_Return1" + val methodName2 = "testFib_ReturnZero" + val methodName3 = "testFib_NNotEquals1" + val methodName4 = "testFib_ThrowIllegalArgumentException" + + val displayName1 = "n == 0 : False -> return 1" + val displayName2 = "n == 0 : True -> return 0" + val displayName3 = "return 1 -> return 0" //it looks weird + val displayName4 = "n < 0 -> ThrowIllegalArgumentException" + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4 + ) + + val method = Recursion::fib + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testFactorial() { + val summary1 = "@utbot.classUnderTest {@link Recursion}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.recursion.Recursion#factorial(int)}\n" + + //TODO: Lost information about executed condition, + // see [issue-900](https://github.com/UnitTestBot/UTBotJava/issues/900) + //"@utbot.executesCondition {@code (n == 0): True}\n" + "@utbot.returnsFrom {@code return 1;}" + val summary2 = "@utbot.classUnderTest {@link Recursion}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.recursion.Recursion#factorial(int)}\n" + + "@utbot.executesCondition {@code (n == 0): False}\n" + + "@utbot.invokes {@link org.utbot.examples.recursion.Recursion#factorial(int)}\n" + + "@utbot.triggersRecursion factorial, where the test return from: {@code return 1;}" + + "@utbot.returnsFrom {@code return n * factorial(n - 1);}" + val summary3 = "@utbot.classUnderTest {@link Recursion}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.recursion.Recursion#factorial(int)}\n" + + "@utbot.executesCondition {@code (n < 0): True}\n" + + "@utbot.throwsException {@link java.lang.IllegalArgumentException} when: n < 0" + + val methodName1 = "testFactorial_Return1" + val methodName2 = "testFactorial_NNotEqualsZero" + val methodName3 = "testFactorial_ThrowIllegalArgumentException" + + //TODO: Display names are not complete, see [issue-899](https://github.com/UnitTestBot/UTBotJava/issues/899). + //they should be equal "n == 0 : True -> return 1" and "n == 0 : False -> return n * factorial(n - 1)" respectively + val displayName1 = "-> return 1" + val displayName2 = "-> return 1" + val displayName3 = "n < 0 -> ThrowIllegalArgumentException" + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + val method = Recursion::factorial + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/structures/SummaryMinStackTest.kt b/utbot-summary-tests/src/test/kotlin/examples/structures/SummaryMinStackTest.kt new file mode 100644 index 0000000000..c920c1bf5f --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/structures/SummaryMinStackTest.kt @@ -0,0 +1,204 @@ +package examples.structures + +import examples.CustomJavaDocTagsEnabler +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.utbot.examples.structures.MinStack +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate +@ExtendWith(CustomJavaDocTagsEnabler::class) +class SummaryMinStackTest : SummaryTestCaseGeneratorTest( + MinStack::class +) { + @Test + fun testGetMin() { + val summary1 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#getMin()}\n" + + "@utbot.throwsException {@link java.lang.ArrayIndexOutOfBoundsException} in: return minStack[size - 1];" + + val summary2 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#getMin()}\n" + + "@utbot.throwsException {@link java.lang.NullPointerException} in: return minStack[size - 1];" + + val summary3 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#getMin()}\n" + + "@utbot.returnsFrom {@code return minStack[size - 1];}\n" + + val methodName1 = "testGetMin_ThrowArrayIndexOutOfBoundsException" + val methodName2 = "testGetMin_ThrowNullPointerException" + val methodName3 = "testGetMin_ReturnSize1OfMinStack" + + val displayName1 = "return minStack[size - 1] : True -> ThrowArrayIndexOutOfBoundsException" + val displayName2 = "return minStack[size - 1] : True -> ThrowNullPointerException" + val displayName3 = "-> return minStack[size - 1]" + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + val method = MinStack::getMin + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testRemoveValue() { + val summary1 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#removeValue()}\n" + + "@utbot.executesCondition {@code (size <= 0): True}\n" + + "@utbot.throwsException {@link java.lang.RuntimeException} when: size <= 0" + + val summary2 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#removeValue()}\n" + + "@utbot.executesCondition {@code (size <= 0): False}\n" + + val methodName1 = "testRemoveValue_ThrowRuntimeException" + val methodName2 = "testRemoveValue_SizeGreaterThanZero" + + val displayName1 = "size <= 0 -> ThrowRuntimeException" + val displayName2 = "-> size <= 0 : False" + + val summaryKeys = listOf( + summary1, + summary2 + ) + + val displayNames = listOf( + displayName1, + displayName2 + ) + + val methodNames = listOf( + methodName1, + methodName2 + ) + + val method = MinStack::removeValue + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testAddValue() { + val summary1 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#addValue(long)}\n" + + "@utbot.throwsException {@link java.lang.ArrayIndexOutOfBoundsException} in: stack[size] = value;" + + val summary2 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#addValue(long)}\n" + + "@utbot.throwsException {@link java.lang.NullPointerException} in: stack[size] = value;" + + val summary3 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#addValue(long)}\n" + + "@utbot.executesCondition {@code (size == 0): True}\n" + + "@utbot.throwsException {@link java.lang.ArrayIndexOutOfBoundsException} in: minStack[size] = value;" + + val summary4 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#addValue(long)}\n" + + "@utbot.executesCondition {@code (size == 0): True}\n" + + "@utbot.throwsException {@link java.lang.NullPointerException} in: minStack[size] = value;" + + val summary5 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#addValue(long)}\n" + + "@utbot.executesCondition {@code (size == 0): False}\n" + + "@utbot.throwsException {@link java.lang.NullPointerException} in: minStack[size] = Math.min(minStack[size - 1], value);" + + val summary6 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#addValue(long)}\n" + + "@utbot.executesCondition {@code (size == 0): False}\n" + + "@utbot.throwsException {@link java.lang.ArrayIndexOutOfBoundsException} in: minStack[size] = Math.min(minStack[size - 1], value);" + val summary7 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#addValue(long)}\n" + + "@utbot.executesCondition {@code (size == 0): False}\n" + + "@utbot.invokes {@link java.lang.Math#min(long,long)}\n" + + "@utbot.throwsException {@link java.lang.ArrayIndexOutOfBoundsException} in: minStack[size] = Math.min(minStack[size - 1], value);" + val summary8 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#addValue(long)}\n" + + "@utbot.executesCondition {@code (size == 0): True}\n" + val summary9 = "@utbot.classUnderTest {@link MinStack}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.structures.MinStack#addValue(long)}\n" + + "@utbot.executesCondition {@code (size == 0): False}\n" + + "@utbot.invokes {@link java.lang.Math#min(long,long)}\n" + + val methodName1 = "testAddValue_ThrowArrayIndexOutOfBoundsException" + val methodName2 = "testAddValue_ThrowNullPointerException" + val methodName3 = "testAddValue_ThrowArrayIndexOutOfBoundsException_1" + val methodName4 = "testAddValue_ThrowNullPointerException_1" + val methodName5 = "testAddValue_ThrowNullPointerException_2" + val methodName6 = "testAddValue_ThrowArrayIndexOutOfBoundsException_2" + val methodName7 = "testAddValue_ThrowArrayIndexOutOfBoundsException_3" + val methodName8 = "testAddValue_SizeEqualsZero" + val methodName9 = "testAddValue_SizeNotEqualsZero" + + val displayName1 = "stack[size] = value -> ThrowArrayIndexOutOfBoundsException" + val displayName2 = "stack[size] = value -> ThrowNullPointerException" + val displayName3 = "minStack[size] = value -> ThrowArrayIndexOutOfBoundsException" + val displayName4 = "minStack[size] = value -> ThrowNullPointerException" + val displayName5 = "minStack[size] = Math.min(minStack[size - 1], value) -> ThrowNullPointerException" + val displayName6 = "minStack[size] = Math.min(minStack[size - 1], value) -> ThrowArrayIndexOutOfBoundsException" + val displayName7 = "minStack[size] = Math.min(minStack[size - 1], value) -> ThrowArrayIndexOutOfBoundsException" + val displayName8 = "-> size == 0 : True" + val displayName9 = "size == 0 : False -> MathMin" + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4, + summary5, + summary6, + summary7, + summary8, + summary9 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4, + displayName5, + displayName6, + displayName7, + displayName8, + displayName9 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4, + methodName5, + methodName6, + methodName7, + methodName8, + methodName9, + ) + + val method = MinStack::addValue + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/ternary/SummaryTernary.kt b/utbot-summary-tests/src/test/kotlin/examples/ternary/SummaryTernary.kt deleted file mode 100644 index 893b223665..0000000000 --- a/utbot-summary-tests/src/test/kotlin/examples/ternary/SummaryTernary.kt +++ /dev/null @@ -1,591 +0,0 @@ -package examples.ternary - -import examples.SummaryTestCaseGeneratorTest -import org.junit.Ignore -import org.junit.jupiter.api.Disabled -import org.utbot.examples.ternary.Ternary -import org.junit.jupiter.api.Tag -import org.junit.jupiter.api.Test -import org.utbot.examples.DoNotCalculate -import org.utbot.examples.inner.InnerCalls -import org.utbot.examples.inner.NestedCalls -import org.utbot.framework.plugin.api.MockStrategyApi - -class SummaryTernary : SummaryTestCaseGeneratorTest( - Ternary::class, -) { - @Test - fun testMax() { - val summary1 = "Test executes conditions:\n" + - " (val1 >= val2): False\n" + - "returns from: return val1 >= val2 ? val1 : val2;\n" - val summary2 = "Test executes conditions:\n" + - " (val1 >= val2): True\n" + - "returns from: return val1 >= val2 ? val1 : val2;\n" - - val methodName1 = "testMax_Val1LessThanVal2" - val methodName2 = "testMax_Val1GreaterOrEqualVal2" - - val displayName1 = "val1 >= val2 : False -> return val1 >= val2 ? val1 : val2" - val displayName2 = "val1 >= val2 : True -> return val1 >= val2 ? val1 : val2" - - val method = Ternary::max - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2 - ) - - val displayNames = listOf( - displayName1, - displayName2 - ) - - val methodNames = listOf( - methodName1, - methodName2 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - - @Test - fun testSimpleOperation() { - val summary1 = "Test returns from: return result;\n" - - val methodName1 = "testSimpleOperation_ReturnResult" - - val displayName1 = "-> return result" - - val method = Ternary::simpleOperation - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1 - ) - - val displayNames = listOf( - displayName1 - ) - - val methodNames = listOf( - methodName1 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - - @Test - fun testStringExpr() { - val summary1 = "Test executes conditions:\n" + - " (num > 10): True\n" + - "returns from: return num > 10 ? \"Number is greater than 10\" : num > 5 ? \"Number is greater than 5\" : \"Number is less than equal to 5\";\n" - val summary2 = "Test executes conditions:\n" + - " (num > 10): False,\n" + - " (num > 5): False\n" + - "returns from: return num > 10 ? \"Number is greater than 10\" : num > 5 ? \"Number is greater than 5\" : \"Number is less than equal to 5\";\n" - val summary3 = "Test executes conditions:\n" + - " (num > 10): False,\n" + - " (num > 5): True\n" + - "returns from: return num > 10 ? \"Number is greater than 10\" : num > 5 ? \"Number is greater than 5\" : \"Number is less than equal to 5\";\n" - - val methodName1 = "testStringExpr_NumGreaterThan10" - val methodName2 = "testStringExpr_NumLessOrEqual5" - val methodName3 = "testStringExpr_NumGreaterThan5" - - val displayName1 = "num > 10 : True -> return num > 10 ? \"Number is greater than 10\" : num > 5 ? \"Number is greater than 5\" : \"Number is less than equal to 5\"" - val displayName2 = "num > 5 : False -> return num > 10 ? \"Number is greater than 10\" : num > 5 ? \"Number is greater than 5\" : \"Number is less than equal to 5\"" - val displayName3 = "num > 5 : True -> return num > 10 ? \"Number is greater than 10\" : num > 5 ? \"Number is greater than 5\" : \"Number is less than equal to 5\"" - - val method = Ternary::stringExpr - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2, - summary3 - ) - - val displayNames = listOf( - displayName1, - displayName2, - displayName3 - ) - - val methodNames = listOf( - methodName1, - methodName2, - methodName3 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - - @Test - fun testParse() { - val summary1 = "Test executes conditions:\n" + - " (input == null || input.equals(\"\")): False\n" + - "returns from: return value;\n" - val summary2 = "Test executes conditions:\n" + - " (input == null || input.equals(\"\")): True\n" + - "invokes:\n" + - " String::equals once\n" + - "returns from: return value;\n" - val summary3 = "Test executes conditions:\n" + - " (input == null || input.equals(\"\")): True,\n" + - " (input == null || input.equals(\"\")): False\n" + - "invokes:\n" + - " Integer::parseInt once\n" + - "\n" + - "throws NumberFormatException in: Integer.parseInt(input)\n" - - val methodName1 = "testParse_InputEqualsNullOrInputEquals" - val methodName2 = "testParse_InputNotEqualsNullOrInputEquals" - val methodName3 = "testParse_InputEqualsNullOrInputEquals_1" - - val displayName1 = "input == null || input.equals(\"\") : False -> return value" - val displayName2 = "input == null || input.equals(\"\") : True -> return value" - val displayName3 = "Integer.parseInt(input) : True -> ThrowNumberFormatException" - - val method = Ternary::parse - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2, - summary3 - ) - - val displayNames = listOf( - displayName1, - displayName2, - displayName3 - ) - - val methodNames = listOf( - methodName1, - methodName2, - methodName3 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - @Test - fun testMinValue() { - val summary1 = "Test executes conditions:\n" + - " ((a < b)): False\n" + - "returns from: return (a < b) ? a : b;\n" - val summary2 = "Test executes conditions:\n" + - " ((a < b)): True\n" + - "returns from: return (a < b) ? a : b;\n" - - val methodName1 = "testMinValue_AGreaterOrEqualB" - val methodName2 = "testMinValue_ALessThanB" - - val displayName1 = "a < b : False -> return (a < b) ? a : b" - val displayName2 = "a < b : True -> return (a < b) ? a : b" - - val method = Ternary::minValue - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2 - ) - - val displayNames = listOf( - displayName1, - displayName2 - ) - - val methodNames = listOf( - methodName1, - methodName2 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - - @Test - fun testSubDelay() { - val summary1 = "Test executes conditions:\n" + - " (flag): False\n" + - "returns from: return flag ? 100 : 0;\n" - val summary2 = "Test executes conditions:\n" + - " (flag): True\n" + - "returns from: return flag ? 100 : 0;\n" - - val methodName1 = "testSubDelay_NotFlag" - val methodName2 = "testSubDelay_Flag" - - val displayName1 = "flag : False -> return flag ? 100 : 0" - val displayName2 = "flag : True -> return flag ? 100 : 0" - - val method = Ternary::subDelay - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2 - ) - - val displayNames = listOf( - displayName1, - displayName2 - ) - - val methodNames = listOf( - methodName1, - methodName2 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - - @Test - fun testPlusOrMinus() { - val summary1 = "Test executes conditions:\n" + - " ((num1 > num2)): False\n" + - "returns from: return (num1 > num2) ? (num1 + num2) : (num1 - num2);\n" - val summary2 = "Test executes conditions:\n" + - " ((num1 > num2)): True\n" + - "returns from: return (num1 > num2) ? (num1 + num2) : (num1 - num2);\n" - - val methodName1 = "testPlusOrMinus_Num1LessOrEqualNum2" - val methodName2 = "testPlusOrMinus_Num1GreaterThanNum2" - - val displayName1 = "num1 > num2 : False -> return (num1 > num2) ? (num1 + num2) : (num1 - num2)" - val displayName2 = "num1 > num2 : True -> return (num1 > num2) ? (num1 + num2) : (num1 - num2)" - - val method = Ternary::plusOrMinus - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2 - ) - - val displayNames = listOf( - displayName1, - displayName2 - ) - - val methodNames = listOf( - methodName1, - methodName2 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - - @Test - fun testLongTernary() { - val summary1 = "Test executes conditions:\n" + - " (num1 > num2): True\n" + - "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : 3;\n" - val summary2 = "Test executes conditions:\n" + - " (num1 > num2): False,\n" + - " (num1 == num2): True\n" + - "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : 3;\n" - val summary3 = "Test executes conditions:\n" + - " (num1 > num2): False,\n" + - " (num1 == num2): False\n" + - "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : 3;\n" - - val methodName1 = "testLongTernary_Num1GreaterThanNum2" - val methodName2 = "testLongTernary_Num1EqualsNum2" - val methodName3 = "testLongTernary_Num1NotEqualsNum2" - - val displayName1 = "num1 > num2 : True -> return num1 > num2 ? 1 : num1 == num2 ? 2 : 3" - val displayName2 = "num1 == num2 : True -> return num1 > num2 ? 1 : num1 == num2 ? 2 : 3" - val displayName3 = "num1 == num2 : False -> return num1 > num2 ? 1 : num1 == num2 ? 2 : 3" - - val method = Ternary::longTernary - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2, - summary3 - ) - - val displayNames = listOf( - displayName1, - displayName2, - displayName3 - ) - - val methodNames = listOf( - methodName1, - methodName2, - methodName3 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - - @Test - fun testVeryLongTernary() { - val summary1 = "Test executes conditions:\n" + - " (num1 > num2): True\n" + - "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5;\n" - val summary2 = "Test executes conditions:\n" + - " (num1 > num2): False,\n" + - " (num1 == num2): False,\n" + - " (num2 > num3): False,\n" + - " (num2 == num3): False\n" + - "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5;\n" - val summary3 = "Test executes conditions:\n" + - " (num1 > num2): False,\n" + - " (num1 == num2): True\n" + - "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5;\n" - val summary4 = "Test executes conditions:\n" + - " (num1 > num2): False,\n" + - " (num1 == num2): False,\n" + - " (num2 > num3): False,\n" + - " (num2 == num3): True\n" + - "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5;\n" - val summary5 = "Test executes conditions:\n" + - " (num1 > num2): False,\n" + - " (num1 == num2): False,\n" + - " (num2 > num3): True\n" + - "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5;\n" - - val methodName1 = "testVeryLongTernary_Num1GreaterThanNum2" - val methodName2 = "testVeryLongTernary_Num2NotEqualsNum3" - val methodName3 = "testVeryLongTernary_Num1EqualsNum2" - val methodName4 = "testVeryLongTernary_Num2EqualsNum3" - val methodName5 = "testVeryLongTernary_Num2GreaterThanNum3" - - val displayName1 = "num1 > num2 : True -> return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5" - val displayName2 = "num2 == num3 : False -> return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5" - val displayName3 = "num1 == num2 : True -> return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5" - val displayName4 = "num2 == num3 : True -> return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5" - val displayName5 = "num2 > num3 : True -> return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5" - - val method = Ternary::veryLongTernary - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2, - summary3, - summary4, - summary5 - ) - - val displayNames = listOf( - displayName1, - displayName2, - displayName3, - displayName4, - displayName5 - ) - - val methodNames = listOf( - methodName1, - methodName2, - methodName3, - methodName4, - methodName5 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - - @Test - fun testMinMax() { - val summary1 = "Test executes conditions:\n" + - " (num1 > num2): False\n" + - "calls Ternary::minValue,\n" + - " there it executes conditions:\n" + - " ((a < b)): True\n" + - " returns from: return (a < b) ? a : b;\n" + - " \n" + - "Test then returns from: return a;\n" - val summary2 = "Test executes conditions:\n" + - " (num1 > num2): True\n" + - "calls Ternary::max,\n" + - " there it executes conditions:\n" + - " (val1 >= val2): True\n" + - " returns from: return val1 >= val2 ? val1 : val2;\n" + - " \n" + - "Test further returns from: return a;\n" - val summary3 = "Test executes conditions:\n" + - " (num1 > num2): False\n" + - "calls Ternary::minValue,\n" + - " there it executes conditions:\n" + - " ((a < b)): False\n" + - " returns from: return (a < b) ? a : b;\n" + - " \n" + - "Test next returns from: return a;\n" - - val methodName1 = "testMinMax_ALessThanB" - val methodName2 = "testMinMax_Val1GreaterOrEqualVal2" - val methodName3 = "testMinMax_AGreaterOrEqualB" - - val displayName1 = "a < b : True -> return (a < b) ? a : b" - val displayName2 = "val1 >= val2 : True -> return val1 >= val2 ? val1 : val2" - val displayName3 = "a < b : False -> return (a < b) ? a : b" - - val method = Ternary::minMax - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2, - summary3 - ) - - val displayNames = listOf( - displayName1, - displayName2, - displayName3 - ) - - val methodNames = listOf( - methodName1, - methodName2, - methodName3 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - - - @Test - fun testIntFunc() { - val summary1 = "Test executes conditions:\n" + - " (num1 > num2): True\n" + - "invokes:\n" + - " Ternary::intFunc1 once\n" + - "returns from: return num1 > num2 ? intFunc1() : intFunc2();\n" - val summary2 = "Test executes conditions:\n" + - " (num1 > num2): False\n" + - "invokes:\n" + - " Ternary::intFunc2 once\n" + - "returns from: return num1 > num2 ? intFunc1() : intFunc2();\n" - - val methodName1 = "testIntFunc_Num1GreaterThanNum2" - val methodName2 = "testIntFunc_Num1LessOrEqualNum2" - - val displayName1 = "num1 > num2 : True -> return num1 > num2 ? intFunc1() : intFunc2()" - val displayName2 = "num1 > num2 : False -> return num1 > num2 ? intFunc1() : intFunc2()" - - val method = Ternary::intFunc - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2 - ) - - val displayNames = listOf( - displayName1, - displayName2 - ) - - val methodNames = listOf( - methodName1, - methodName2 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - - @Test - fun testTernaryInTheMiddle() { - val summary1 = "Test executes conditions:\n" + - " (num2 > num3): True\n" + - "returns from: return max(num1 + 228, num2 > num3 ? num2 + 1 : num3 + 2) + 4;\n" - val summary2 = "Test executes conditions:\n" + - " (num2 > num3): False\n" + - "returns from: return max(num1 + 228, num2 > num3 ? num2 + 1 : num3 + 2) + 4;\n" - - val methodName1 = "testTernaryInTheMiddle_Num2GreaterThanNum3" - val methodName2 = "testTernaryInTheMiddle_Num2LessOrEqualNum3" - - val displayName1 = "num2 > num3 : True -> return max(num1 + 228, num2 > num3 ? num2 + 1 : num3 + 2) + 4" - val displayName2 = "num2 > num3 : False -> return max(num1 + 228, num2 > num3 ? num2 + 1 : num3 + 2) + 4" - - val method = Ternary::ternaryInTheMiddle - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2 - ) - - val displayNames = listOf( - displayName1, - displayName2 - ) - - val methodNames = listOf( - methodName1, - methodName2 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - - @Test - fun testTwoIfsOneLine() { - val summary1 = "Test executes conditions:\n" + - " (num1 > num2): False\n" + - "returns from: return a;\n" - val summary2 = "Test executes conditions:\n" + - " (num1 > num2): True,\n" + - " ((num1 - 10) > 0): False\n" + - "returns from: return a;\n" - val summary3 = "Test executes conditions:\n" + - " (num1 > num2): True,\n" + - " ((num1 - 10) > 0): True\n" + - "returns from: return a;\n" - - val methodName1 = "testTwoIfsOneLine_Num1LessOrEqualNum2" - val methodName2 = "testTwoIfsOneLine_Num1Minus10LessOrEqualZero" - val methodName3 = "testTwoIfsOneLine_Num1Minus10GreaterThanZero" - - val displayName1 = "num1 > num2 : False -> return a" - val displayName2 = "(num1 - 10) > 0 : False -> return a" - val displayName3 = "(num1 - 10) > 0 : True -> return a" - - val method = Ternary::twoIfsOneLine - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2, - summary3 - ) - - val displayNames = listOf( - displayName1, - displayName2, - displayName3 - ) - - val methodNames = listOf( - methodName1, - methodName2, - methodName3 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } -} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/ternary/SummaryTernaryTest.kt b/utbot-summary-tests/src/test/kotlin/examples/ternary/SummaryTernaryTest.kt new file mode 100644 index 0000000000..c6c5b6dadb --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/ternary/SummaryTernaryTest.kt @@ -0,0 +1,596 @@ +package examples.ternary + +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.utbot.examples.ternary.Ternary +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate +class SummaryTernaryTest : SummaryTestCaseGeneratorTest( + Ternary::class, +) { + @Test + fun testMax() { + val summary1 = "Test executes conditions:\n" + + " (val1 >= val2): False\n" + + "returns from: return val1 >= val2 ? val1 : val2;\n" + val summary2 = "Test executes conditions:\n" + + " (val1 >= val2): True\n" + + "returns from: return val1 >= val2 ? val1 : val2;\n" + + val methodName1 = "testMax_Val1LessThanVal2" + val methodName2 = "testMax_Val1GreaterOrEqualVal2" + + val displayName1 = "val1 >= val2 : False -> return val1 >= val2 ? val1 : val2" + val displayName2 = "val1 >= val2 : True -> return val1 >= val2 ? val1 : val2" + + val method = Ternary::max + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2 + ) + + val displayNames = listOf( + displayName1, + displayName2 + ) + + val methodNames = listOf( + methodName1, + methodName2 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testSimpleOperation() { + val summary1 = "Test returns from: return result;\n" + + val methodName1 = "testSimpleOperation_ReturnResult" + + val displayName1 = "-> return result" + + val method = Ternary::simpleOperation + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1 + ) + + val displayNames = listOf( + displayName1 + ) + + val methodNames = listOf( + methodName1 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testStringExpr() { + val summary1 = "Test executes conditions:\n" + + " (num > 10): True\n" + + "returns from: return num > 10 ? \"Number is greater than 10\" : num > 5 ? \"Number is greater than 5\" : \"Number is less than equal to 5\";\n" + val summary2 = "Test executes conditions:\n" + + " (num > 10): False,\n" + + " (num > 5): False\n" + + "returns from: return num > 10 ? \"Number is greater than 10\" : num > 5 ? \"Number is greater than 5\" : \"Number is less than equal to 5\";\n" + val summary3 = "Test executes conditions:\n" + + " (num > 10): False,\n" + + " (num > 5): True\n" + + "returns from: return num > 10 ? \"Number is greater than 10\" : num > 5 ? \"Number is greater than 5\" : \"Number is less than equal to 5\";\n" + + val methodName1 = "testStringExpr_NumGreaterThan10" + val methodName2 = "testStringExpr_NumLessOrEqual5" + val methodName3 = "testStringExpr_NumGreaterThan5" + + val displayName1 = + "num > 10 : True -> return num > 10 ? \"Number is greater than 10\" : num > 5 ? \"Number is greater than 5\" : \"Number is less than equal to 5\"" + val displayName2 = + "num > 5 : False -> return num > 10 ? \"Number is greater than 10\" : num > 5 ? \"Number is greater than 5\" : \"Number is less than equal to 5\"" + val displayName3 = + "num > 5 : True -> return num > 10 ? \"Number is greater than 10\" : num > 5 ? \"Number is greater than 5\" : \"Number is less than equal to 5\"" + + val method = Ternary::stringExpr + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testParse() { + val summary1 = "Test executes conditions:\n" + + " (input == null || input.equals(\"\")): False\n" + + "returns from: return value;\n" + val summary2 = "Test executes conditions:\n" + + " (input == null || input.equals(\"\")): True\n" + + "invokes:\n" + + " {@link java.lang.String#equals(java.lang.Object)} once\n" + + "returns from: return value;\n" + val summary3 = "Test executes conditions:\n" + + " (input == null || input.equals(\"\")): True\n" + + "invokes:\n" + + " {@link java.lang.String#equals(java.lang.Object)} once\n" + + "executes conditions:\n" + + " (input == null || input.equals(\"\")): False\n" + + "invokes:\n" + + " {@link java.lang.Integer#parseInt(java.lang.String)} once\n" + + "throws NumberFormatException in: Integer.parseInt(input)" + + val methodName1 = "testParse_InputEqualsNullOrInputEquals" + val methodName2 = "testParse_InputNotEqualsNullOrInputEquals" + val methodName3 = "testParse_ThrowNumberFormatException" + + val displayName1 = "input == null || input.equals(\"\") : False -> return value" + val displayName2 = "input == null || input.equals(\"\") : True -> return value" + val displayName3 = "Integer.parseInt(input) : True -> ThrowNumberFormatException" + + val method = Ternary::parse + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testMinValue() { + val summary1 = "Test executes conditions:\n" + + " ((a < b)): False\n" + + "returns from: return (a < b) ? a : b;\n" + val summary2 = "Test executes conditions:\n" + + " ((a < b)): True\n" + + "returns from: return (a < b) ? a : b;\n" + + val methodName1 = "testMinValue_AGreaterOrEqualB" + val methodName2 = "testMinValue_ALessThanB" + + val displayName1 = "a < b : False -> return (a < b) ? a : b" + val displayName2 = "a < b : True -> return (a < b) ? a : b" + + val method = Ternary::minValue + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2 + ) + + val displayNames = listOf( + displayName1, + displayName2 + ) + + val methodNames = listOf( + methodName1, + methodName2 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testSubDelay() { + val summary1 = "Test executes conditions:\n" + + " (flag): False\n" + + "returns from: return flag ? 100 : 0;\n" + val summary2 = "Test executes conditions:\n" + + " (flag): True\n" + + "returns from: return flag ? 100 : 0;\n" + + val methodName1 = "testSubDelay_NotFlag" + val methodName2 = "testSubDelay_Flag" + + val displayName1 = "flag : False -> return flag ? 100 : 0" + val displayName2 = "flag : True -> return flag ? 100 : 0" + + val method = Ternary::subDelay + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2 + ) + + val displayNames = listOf( + displayName1, + displayName2 + ) + + val methodNames = listOf( + methodName1, + methodName2 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testPlusOrMinus() { + val summary1 = "Test executes conditions:\n" + + " ((num1 > num2)): False\n" + + "returns from: return (num1 > num2) ? (num1 + num2) : (num1 - num2);\n" + val summary2 = "Test executes conditions:\n" + + " ((num1 > num2)): True\n" + + "returns from: return (num1 > num2) ? (num1 + num2) : (num1 - num2);\n" + + val methodName1 = "testPlusOrMinus_Num1LessOrEqualNum2" + val methodName2 = "testPlusOrMinus_Num1GreaterThanNum2" + + val displayName1 = "num1 > num2 : False -> return (num1 > num2) ? (num1 + num2) : (num1 - num2)" + val displayName2 = "num1 > num2 : True -> return (num1 > num2) ? (num1 + num2) : (num1 - num2)" + + val method = Ternary::plusOrMinus + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2 + ) + + val displayNames = listOf( + displayName1, + displayName2 + ) + + val methodNames = listOf( + methodName1, + methodName2 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testLongTernary() { + val summary1 = "Test executes conditions:\n" + + " (num1 > num2): True\n" + + "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : 3;\n" + val summary2 = "Test executes conditions:\n" + + " (num1 > num2): False,\n" + + " (num1 == num2): True\n" + + "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : 3;\n" + val summary3 = "Test executes conditions:\n" + + " (num1 > num2): False,\n" + + " (num1 == num2): False\n" + + "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : 3;\n" + + val methodName1 = "testLongTernary_Num1GreaterThanNum2" + val methodName2 = "testLongTernary_Num1EqualsNum2" + val methodName3 = "testLongTernary_Num1NotEqualsNum2" + + val displayName1 = "num1 > num2 : True -> return num1 > num2 ? 1 : num1 == num2 ? 2 : 3" + val displayName2 = "num1 == num2 : True -> return num1 > num2 ? 1 : num1 == num2 ? 2 : 3" + val displayName3 = "num1 == num2 : False -> return num1 > num2 ? 1 : num1 == num2 ? 2 : 3" + + val method = Ternary::longTernary + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testVeryLongTernary() { + val summary1 = "Test executes conditions:\n" + + " (num1 > num2): True\n" + + "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5;\n" + val summary2 = "Test executes conditions:\n" + + " (num1 > num2): False,\n" + + " (num1 == num2): False,\n" + + " (num2 > num3): False,\n" + + " (num2 == num3): False\n" + + "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5;\n" + val summary3 = "Test executes conditions:\n" + + " (num1 > num2): False,\n" + + " (num1 == num2): True\n" + + "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5;\n" + val summary4 = "Test executes conditions:\n" + + " (num1 > num2): False,\n" + + " (num1 == num2): False,\n" + + " (num2 > num3): False,\n" + + " (num2 == num3): True\n" + + "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5;\n" + val summary5 = "Test executes conditions:\n" + + " (num1 > num2): False,\n" + + " (num1 == num2): False,\n" + + " (num2 > num3): True\n" + + "returns from: return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5;\n" + + val methodName1 = "testVeryLongTernary_Num1GreaterThanNum2" + val methodName2 = "testVeryLongTernary_Num2NotEqualsNum3" + val methodName3 = "testVeryLongTernary_Num1EqualsNum2" + val methodName4 = "testVeryLongTernary_Num2EqualsNum3" + val methodName5 = "testVeryLongTernary_Num2GreaterThanNum3" + + val displayName1 = + "num1 > num2 : True -> return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5" + val displayName2 = + "num2 == num3 : False -> return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5" + val displayName3 = + "num1 == num2 : True -> return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5" + val displayName4 = + "num2 == num3 : True -> return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5" + val displayName5 = + "num2 > num3 : True -> return num1 > num2 ? 1 : num1 == num2 ? 2 : num2 > num3 ? 3 : num2 == num3 ? 4 : 5" + + val method = Ternary::veryLongTernary + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4, + summary5 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4, + displayName5 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4, + methodName5 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testMinMax() { + val summary1 = "Test executes conditions:\n" + + " (num1 > num2): False\n" + + "calls {@link org.utbot.examples.ternary.Ternary#minValue(int,int)},\n" + + " there it executes conditions:\n" + + " ((a < b)): True\n" + + " returns from: return (a < b) ? a : b;\n" + + " \n" + + "Test then returns from: return a;\n" + val summary2 = "Test executes conditions:\n" + + " (num1 > num2): True\n" + + "calls {@link org.utbot.examples.ternary.Ternary#max(int,int)},\n" + + " there it executes conditions:\n" + + " (val1 >= val2): True\n" + + " returns from: return val1 >= val2 ? val1 : val2;\n" + + " \n" + + "Test further returns from: return a;\n" + val summary3 = "Test executes conditions:\n" + + " (num1 > num2): False\n" + + "calls {@link org.utbot.examples.ternary.Ternary#minValue(int,int)},\n" + + " there it executes conditions:\n" + + " ((a < b)): False\n" + + " returns from: return (a < b) ? a : b;\n" + + " \n" + + "Test next returns from: return a;\n" + + val methodName1 = "testMinMax_ALessThanB" + val methodName2 = "testMinMax_Val1GreaterOrEqualVal2" + val methodName3 = "testMinMax_AGreaterOrEqualB" + + val displayName1 = "a < b : True -> return (a < b) ? a : b" + val displayName2 = "val1 >= val2 : True -> return val1 >= val2 ? val1 : val2" + val displayName3 = "a < b : False -> return (a < b) ? a : b" + + val method = Ternary::minMax + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + + @Test + fun testIntFunc() { + val summary1 = "Test executes conditions:\n" + + " (num1 > num2): True\n" + + "invokes:\n" + + " org.utbot.examples.ternary.Ternary#intFunc1() once\n" + + "returns from: return num1 > num2 ? intFunc1() : intFunc2();\n" + val summary2 = "Test executes conditions:\n" + + " (num1 > num2): False\n" + + "invokes:\n" + + " org.utbot.examples.ternary.Ternary#intFunc2() once\n" + + "returns from: return num1 > num2 ? intFunc1() : intFunc2();\n" + + val methodName1 = "testIntFunc_Num1GreaterThanNum2" + val methodName2 = "testIntFunc_Num1LessOrEqualNum2" + + val displayName1 = "num1 > num2 : True -> return num1 > num2 ? intFunc1() : intFunc2()" + val displayName2 = "num1 > num2 : False -> return num1 > num2 ? intFunc1() : intFunc2()" + + val method = Ternary::intFunc + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2 + ) + + val displayNames = listOf( + displayName1, + displayName2 + ) + + val methodNames = listOf( + methodName1, + methodName2 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testTernaryInTheMiddle() { + val summary1 = "Test executes conditions:\n" + + " (num2 > num3): True\n" + + "returns from: return max(num1 + 228, num2 > num3 ? num2 + 1 : num3 + 2) + 4;\n" + val summary2 = "Test executes conditions:\n" + + " (num2 > num3): False\n" + + "returns from: return max(num1 + 228, num2 > num3 ? num2 + 1 : num3 + 2) + 4;\n" + + val methodName1 = "testTernaryInTheMiddle_Num2GreaterThanNum3" + val methodName2 = "testTernaryInTheMiddle_Num2LessOrEqualNum3" + + val displayName1 = "num2 > num3 : True -> return max(num1 + 228, num2 > num3 ? num2 + 1 : num3 + 2) + 4" + val displayName2 = "num2 > num3 : False -> return max(num1 + 228, num2 > num3 ? num2 + 1 : num3 + 2) + 4" + + val method = Ternary::ternaryInTheMiddle + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2 + ) + + val displayNames = listOf( + displayName1, + displayName2 + ) + + val methodNames = listOf( + methodName1, + methodName2 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + + @Test + fun testTwoIfsOneLine() { + val summary1 = "Test executes conditions:\n" + + " (num1 > num2): False\n" + + "returns from: return a;\n" + val summary2 = "Test executes conditions:\n" + + " (num1 > num2): True,\n" + + " ((num1 - 10) > 0): False\n" + + "returns from: return a;\n" + val summary3 = "Test executes conditions:\n" + + " (num1 > num2): True,\n" + + " ((num1 - 10) > 0): True\n" + + "returns from: return a;\n" + + val methodName1 = "testTwoIfsOneLine_Num1LessOrEqualNum2" + val methodName2 = "testTwoIfsOneLine_Num1Minus10LessOrEqualZero" + val methodName3 = "testTwoIfsOneLine_Num1Minus10GreaterThanZero" + + val displayName1 = "num1 > num2 : False -> return a" + val displayName2 = "(num1 - 10) > 0 : False -> return a" + val displayName3 = "(num1 - 10) > 0 : True -> return a" + + val method = Ternary::twoIfsOneLine + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2, + summary3 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3 + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/examples/unsafe/UnsafeWithFieldTest.kt b/utbot-summary-tests/src/test/kotlin/examples/unsafe/UnsafeWithFieldTest.kt new file mode 100644 index 0000000000..ad0c12c0ff --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/examples/unsafe/UnsafeWithFieldTest.kt @@ -0,0 +1,43 @@ +package examples.unsafe + +import examples.CustomJavaDocTagsEnabler +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.utbot.examples.unsafe.UnsafeWithField +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testing.DoNotCalculate + +@ExtendWith(CustomJavaDocTagsEnabler::class) +class UnsafeWithFieldTest : SummaryTestCaseGeneratorTest( + UnsafeWithField::class +) { + @Test + fun testUnsafeWithField() { + val summary1 = "@utbot.classUnderTest {@link UnsafeWithField}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.unsafe.UnsafeWithField#setField(java.text.NumberFormat.Field)}\n" + + "@utbot.returnsFrom {@code return Field.INTEGER;}" + + val methodName1 = "testSetField_ReturnFieldINTEGER" + + val displayName1 = "-> return Field.INTEGER" + + val summaryKeys = listOf( + summary1 + ) + + val displayNames = listOf( + displayName1 + ) + + val methodNames = listOf( + methodName1 + ) + + val method = UnsafeWithField::setField + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/math/SummaryIntMath.kt b/utbot-summary-tests/src/test/kotlin/math/SummaryIntMath.kt deleted file mode 100644 index 43b3520970..0000000000 --- a/utbot-summary-tests/src/test/kotlin/math/SummaryIntMath.kt +++ /dev/null @@ -1,144 +0,0 @@ -package math - -import examples.SummaryTestCaseGeneratorTest -import guava.examples.math.IntMath -import org.junit.jupiter.api.Test -import org.utbot.examples.DoNotCalculate -import org.utbot.framework.plugin.api.MockStrategyApi - -class SummaryIntMath : SummaryTestCaseGeneratorTest( - IntMath::class, -) { - @Test - fun testPow() { - val summary1 = "Test activates switch case: 2, returns from: return 1;\n" - val summary2 = "Test executes conditions:\n" + - " (k < Integer.SIZE): False\n" + - "returns from: return 0;\n" - val summary3 = "Test executes conditions:\n" + - " ((k < Integer.SIZE)): False\n" + - "returns from: return (k < Integer.SIZE) ? (1 << k) : 0;\n" - val summary4 = "Test iterates the loop for(int accum = 1; ; k >>= 1) once,\n" + - " inside this loop, the test returns from: return b * accum;" - val summary5 = "Test executes conditions:\n" + - " ((k < Integer.SIZE)): True\n" + - "returns from: return (k < Integer.SIZE) ? (1 << k) : 0;\n" - val summary6 = "Test executes conditions:\n" + - " ((k == 0)): False\n" + - "returns from: return (k == 0) ? 1 : 0;\n" - val summary7 = "Test iterates the loop for(int accum = 1; ; k >>= 1) once,\n" + - " inside this loop, the test returns from: return accum;" - val summary8 = "Test executes conditions:\n" + - " ((k == 0)): True\n" + - "returns from: return (k == 0) ? 1 : 0;\n" - val summary9 = "Test executes conditions:\n" + - " (k < Integer.SIZE): True,\n" + - " (((k & 1) == 0)): True\n" + - "returns from: return ((k & 1) == 0) ? (1 << k) : -(1 << k);\n" - val summary10 = "Test executes conditions:\n" + - " (k < Integer.SIZE): True,\n" + - " (((k & 1) == 0)): False\n" + - "returns from: return ((k & 1) == 0) ? (1 << k) : -(1 << k);\n" - val summary11 = "Test executes conditions:\n" + - " (((k & 1) == 0)): False\n" + - "returns from: return ((k & 1) == 0) ? 1 : -1;\n" - val summary12 = "Test executes conditions:\n" + - " (((k & 1) == 0)): True\n" + - "returns from: return ((k & 1) == 0) ? 1 : -1;\n" - val summary13 = "Test iterates the loop for(int accum = 1; ; k >>= 1) twice,\n" + - " inside this loop, the test executes conditions:\n" + - " (((k & 1) == 0)): False\n" + - "returns from: return b * accum;" - val summmary14 = "Test iterates the loop for(int accum = 1; ; k >>= 1) twice,\n" + - " inside this loop, the test executes conditions:\n" + - " (((k & 1) == 0)): True\n" + - "returns from: return b * accum;" - - val methodName1 = "testPow_Return1" - val methodName2 = "testPow_KGreaterOrEqualIntegerSIZE" - val methodName3 = "testPow_KGreaterOrEqualIntegerSIZE_1" - val methodName4 = "testPow_ReturnBMultiplyAccum" - val methodName5 = "testPow_KLessThanIntegerSIZE" - val methodName6 = "testPow_KNotEqualsZero" - val methodName7 = "testPow_ReturnAccum" - val methodName8 = "testPow_KEqualsZero" - val methodName9 = "testPow_KBitwiseAnd1EqualsZero" - val methodName10 = "testPow_KBitwiseAnd1NotEqualsZero" - val methodName11 = "testPow_KBitwiseAnd1NotEqualsZero_1" - val methodName12 = "testPow_KBitwiseAnd1EqualsZero_1" - val methodName13 = "testPow_KBitwiseAnd1NotEqualsZero_2" - val methodName14 = "testPow_KBitwiseAnd1EqualsZero_2" - - val displayName1 = "switch(b) case: 2 -> return 1" - val displayName2 = "k < Integer.SIZE : False -> return 0" - val displayName3 = "k < Integer.SIZE : False -> return (k < Integer.SIZE) ? (1 << k) : 0" - val displayName4 = "-> return b * accum" // TODO: weird display name with missed part before -> - val displayName5 = "k < Integer.SIZE : True -> return (k < Integer.SIZE) ? (1 << k) : 0" - val displayName6 = "k == 0 : False -> return (k == 0) ? 1 : 0" - val displayName7 = "-> return accum" // TODO: weird display name with missed part before -> - val displayName8 = "k == 0 : True -> return (k == 0) ? 1 : 0" - val displayName9 = "(k & 1) == 0 : True -> return ((k & 1) == 0) ? (1 << k) : -(1 << k)" - val displayName10 = "(k & 1) == 0 : False -> return ((k & 1) == 0) ? (1 << k) : -(1 << k)" - val displayName11 = "(k & 1) == 0 : False -> return ((k & 1) == 0) ? 1 : -1" - val displayName12 = "(k & 1) == 0 : True -> return ((k & 1) == 0) ? 1 : -1" - val displayName13 = "(k & 1) == 0 : False -> return b * accum" - val displayName14 = "(k & 1) == 0 : True -> return b * accum" - - val summaryKeys = listOf( - summary1, - summary2, - summary3, - summary4, - summary5, - summary6, - summary7, - summary8, - summary9, - summary10, - summary11, - summary12, - summary13, - summmary14 - ) - - val displayNames = listOf( - displayName1, - displayName2, - displayName3, - displayName4, - displayName5, - displayName6, - displayName7, - displayName8, - displayName9, - displayName10, - displayName11, - displayName12, - displayName13, - displayName14 - ) - - val methodNames = listOf( - methodName1, - methodName2, - methodName3, - methodName4, - methodName5, - methodName6, - methodName7, - methodName8, - methodName9, - methodName10, - methodName11, - methodName12, - methodName13, - methodName14 - ) - - val method = IntMath::pow - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } -} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/math/SummaryIntMathLogTest.kt b/utbot-summary-tests/src/test/kotlin/math/SummaryIntMathLogTest.kt new file mode 100644 index 0000000000..bc3b2832c4 --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/math/SummaryIntMathLogTest.kt @@ -0,0 +1,71 @@ +package math + +import examples.CustomJavaDocTagsEnabler +import examples.SummaryTestCaseGeneratorTest +import guava.examples.math.IntMath +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.UtClusterInfo +import org.utbot.testing.DoNotCalculate + +@ExtendWith(CustomJavaDocTagsEnabler::class) +class SummaryIntMathLogTest : SummaryTestCaseGeneratorTest( + IntMath::class, +) { + @Test + fun testLog2() { + val summary1 = "@utbot.classUnderTest {@link IntMath}\n" + + "@utbot.methodUnderTest {@link guava.examples.math.IntMath#log2(int,java.math.RoundingMode)}\n" + val summary2 = "@utbot.classUnderTest {@link IntMath}\n" + + "@utbot.methodUnderTest {@link guava.examples.math.IntMath#log2(int,java.math.RoundingMode)}\n" + val summary3 = "@utbot.classUnderTest {@link IntMath}\n" + + "@utbot.methodUnderTest {@link guava.examples.math.IntMath#log2(int,java.math.RoundingMode)}\n" + val summary4 = "@utbot.classUnderTest {@link IntMath}\n" + + "@utbot.methodUnderTest {@link guava.examples.math.IntMath#log2(int,java.math.RoundingMode)}\n" + + "@utbot.invokes {@link java.math.RoundingMode#ordinal()}\n" + + "@utbot.throwsException {@link java.lang.NullPointerException} in: switch(mode)" + + val methodName1 = "testLog2_IntegerNumberOfLeadingZeros" + val methodName2 = "testLog2_IntegerNumberOfLeadingZeros_1" + val methodName3 = "testLog2_IntMathLessThanBranchFree" + val methodName4 = "testLog2_ThrowNullPointerException" + + val displayName1 = "switch(mode) case: FLOOR -> return (Integer.SIZE - 1) - Integer.numberOfLeadingZeros(x)" + val displayName2 = "switch(mode) case: CEILING -> return Integer.SIZE - Integer.numberOfLeadingZeros(x - 1)" + val displayName3 = "switch(mode) case: HALF_EVEN -> return logFloor + lessThanBranchFree(cmp, x)" + val displayName4 = "switch(mode) -> ThrowNullPointerException" + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4 + ) + + val clusterInfo = listOf( + Pair(UtClusterInfo("SYMBOLIC EXECUTION: SUCCESSFUL EXECUTIONS for method log2(int, java.math.RoundingMode)", null), 3), + Pair(UtClusterInfo("SYMBOLIC EXECUTION: ERROR SUITE for method log2(int, java.math.RoundingMode)", null), 1) + ) + + val method = IntMath::log2 + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames, clusterInfo) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/math/SummaryIntMathPowTest.kt b/utbot-summary-tests/src/test/kotlin/math/SummaryIntMathPowTest.kt new file mode 100644 index 0000000000..788874ecaa --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/math/SummaryIntMathPowTest.kt @@ -0,0 +1,148 @@ +package math + +import examples.SummaryTestCaseGeneratorTest +import guava.examples.math.IntMath +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.UtClusterInfo +import org.utbot.testing.DoNotCalculate +class SummaryIntMathPowTest : SummaryTestCaseGeneratorTest( + IntMath::class, +) { + @Test + fun testPow() { + val summary1 = "Test activates switch(b) case: 1, returns from: return 1;\n" + val summary2 = "Test executes conditions:\n" + + " (k < Integer.SIZE): False\n" + + "returns from: return 0;\n" + val summary3 = "Test executes conditions:\n" + + " ((k < Integer.SIZE)): False\n" + + "returns from: return (k < Integer.SIZE) ? (1 << k) : 0;\n" + val summary4 = "Test iterates the loop for(int accum = 1; ; k >>= 1) once,\n" + + " inside this loop, the test returns from: return b * accum;" + val summary5 = "Test executes conditions:\n" + + " ((k < Integer.SIZE)): True\n" + + "returns from: return (k < Integer.SIZE) ? (1 << k) : 0;\n" + val summary6 = "Test executes conditions:\n" + + " ((k == 0)): False\n" + + "returns from: return (k == 0) ? 1 : 0;\n" + val summary7 = "Test iterates the loop for(int accum = 1; ; k >>= 1) once,\n" + + " inside this loop, the test returns from: return accum;" + val summary8 = "Test executes conditions:\n" + + " ((k == 0)): True\n" + + "returns from: return (k == 0) ? 1 : 0;\n" + val summary9 = "Test executes conditions:\n" + + " (k < Integer.SIZE): True,\n" + + " (((k & 1) == 0)): True\n" + + "returns from: return ((k & 1) == 0) ? (1 << k) : -(1 << k);\n" + val summary10 = "Test executes conditions:\n" + + " (k < Integer.SIZE): True,\n" + + " (((k & 1) == 0)): False\n" + + "returns from: return ((k & 1) == 0) ? (1 << k) : -(1 << k);\n" + val summary11 = "Test executes conditions:\n" + + " (((k & 1) == 0)): False\n" + + "returns from: return ((k & 1) == 0) ? 1 : -1;\n" + val summary12 = "Test executes conditions:\n" + + " (((k & 1) == 0)): True\n" + + "returns from: return ((k & 1) == 0) ? 1 : -1;\n" + val summary13 = "Test iterates the loop for(int accum = 1; ; k >>= 1) twice,\n" + + " inside this loop, the test executes conditions:\n" + + " (((k & 1) == 0)): False\n" + + "returns from: return b * accum;" + val summmary14 = "Test iterates the loop for(int accum = 1; ; k >>= 1) twice,\n" + + " inside this loop, the test executes conditions:\n" + + " (((k & 1) == 0)): True\n" + + "returns from: return b * accum;" + + val methodName1 = "testPow_Return1" + val methodName2 = "testPow_KGreaterOrEqualIntegerSIZE" + val methodName3 = "testPow_KGreaterOrEqualIntegerSIZE_1" + val methodName4 = "testPow_ReturnBMultiplyAccum" + val methodName5 = "testPow_KLessThanIntegerSIZE" + val methodName6 = "testPow_KNotEqualsZero" + val methodName7 = "testPow_ReturnAccum" + val methodName8 = "testPow_KEqualsZero" + val methodName9 = "testPow_KBitwiseAnd1EqualsZero" + val methodName10 = "testPow_KBitwiseAnd1NotEqualsZero" + val methodName11 = "testPow_KBitwiseAnd1NotEqualsZero_1" + val methodName12 = "testPow_KBitwiseAnd1EqualsZero_1" + val methodName13 = "testPow_KBitwiseAnd1NotEqualsZero_2" + val methodName14 = "testPow_KBitwiseAnd1EqualsZero_2" + + val displayName1 = "switch(b) case: 1 -> return 1" + val displayName2 = "k < Integer.SIZE : False -> return 0" + val displayName3 = "k < Integer.SIZE : False -> return (k < Integer.SIZE) ? (1 << k) : 0" + val displayName4 = "-> return b * accum" // TODO: weird display name with missed part before -> + val displayName5 = "k < Integer.SIZE : True -> return (k < Integer.SIZE) ? (1 << k) : 0" + val displayName6 = "k == 0 : False -> return (k == 0) ? 1 : 0" + val displayName7 = "-> return accum" // TODO: weird display name with missed part before -> + val displayName8 = "k == 0 : True -> return (k == 0) ? 1 : 0" + val displayName9 = "(k & 1) == 0 : True -> return ((k & 1) == 0) ? (1 << k) : -(1 << k)" + val displayName10 = "(k & 1) == 0 : False -> return ((k & 1) == 0) ? (1 << k) : -(1 << k)" + val displayName11 = "(k & 1) == 0 : False -> return ((k & 1) == 0) ? 1 : -1" + val displayName12 = "(k & 1) == 0 : True -> return ((k & 1) == 0) ? 1 : -1" + val displayName13 = "(k & 1) == 0 : False -> return b * accum" + val displayName14 = "(k & 1) == 0 : True -> return b * accum" + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4, + summary5, + summary6, + summary7, + summary8, + summary9, + summary10, + summary11, + summary12, + summary13, + summmary14 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4, + displayName5, + displayName6, + displayName7, + displayName8, + displayName9, + displayName10, + displayName11, + displayName12, + displayName13, + displayName14 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4, + methodName5, + methodName6, + methodName7, + methodName8, + methodName9, + methodName10, + methodName11, + methodName12, + methodName13, + methodName14 + ) + + val clusterInfo = listOf( + Pair(UtClusterInfo("SYMBOLIC EXECUTION: SUCCESSFUL EXECUTIONS for method pow(int, int)", null), 14) + ) + + val method = IntMath::pow + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames, clusterInfo) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/math/SummaryOfMath.kt b/utbot-summary-tests/src/test/kotlin/math/SummaryOfMath.kt deleted file mode 100644 index 322cf5ff4f..0000000000 --- a/utbot-summary-tests/src/test/kotlin/math/SummaryOfMath.kt +++ /dev/null @@ -1,215 +0,0 @@ -package math - -import examples.SummaryTestCaseGeneratorTest -import guava.examples.math.Stats -import org.junit.jupiter.api.Test -import org.utbot.examples.DoNotCalculate -import org.utbot.framework.plugin.api.MockStrategyApi - -/** - * It runs test generation for the poor analogue of the Stats.of method ported from the guava-26.0 framework - * and validates generated docs, display names and test method names. - * - * @see Related issue - */ -class SummaryOfMath : SummaryTestCaseGeneratorTest( - Stats::class, -) { - @Test - fun testOfInts() { - val summary1 = "Test calls StatsAccumulator::addAll,\n" + - " there it triggers recursion of addAll once, \n" + - "Test throws NullPointerException in: acummulator.addAll(values);\n" - val summary2 = "Test calls StatsAccumulator::addAll,\n" + - " there it does not iterate for(int value: values), \n" + - "Test later calls StatsAccumulator::snapshot,\n" + - " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + - " \n" + - "Test then returns from: return acummulator.snapshot();" - val summary3 = "Test calls StatsAccumulator::addAll,\n" + - " there it iterates the loop for(int value: values) once. \n" + - "Test later calls StatsAccumulator::snapshot,\n" + - " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + - " \n" + - "Test then returns from: return acummulator.snapshot();" - val summary4 = "Test calls StatsAccumulator::addAll,\n" + - " there it iterates the loop for(int value: values) twice. \n" + - "Test later calls StatsAccumulator::snapshot,\n" + - " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + - " \n" + - "Test later returns from: return acummulator.snapshot();\n" - - val methodName1 = "testOfInts_StatsAccumulatorAddAll" - val methodName2 = "testOfInts_snapshot" - val methodName3 = "testOfInts_IterateForEachLoop" - val methodName4 = "testOfInts_IterateForEachLoop_1" - - val displayName1 = "acummulator.addAll(values) : True -> ThrowNullPointerException" - val displayName2 = "snapshot -> return new Stats(count, mean, sumOfSquaresOfDeltas, min, max)" - val displayName3 = "addAll -> return new Stats(count, mean, sumOfSquaresOfDeltas, min, max)" - val displayName4 = "addAll -> return new Stats(count, mean, sumOfSquaresOfDeltas, min, max)" - - val method = Stats::ofInts - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2, - summary3, - summary4 - ) - - val displayNames = listOf( - displayName1, - displayName2, - displayName3, - displayName4 - ) - - val methodNames = listOf( - methodName1, - methodName2, - methodName3, - methodName4 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } - - @Test - fun testOfDoubles() { - val summary1 = "Test calls StatsAccumulator::addAll,\n" + - " there it triggers recursion of addAll once, \n" + - "Test throws NullPointerException in: acummulator.addAll(values);\n" - val summary2 = "Test calls StatsAccumulator::addAll,\n" + - " there it does not iterate for(double value: values), \n" + - "Test next calls StatsAccumulator::snapshot,\n" + - " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + - " \n" + - "Test later returns from: return acummulator.snapshot();\n" - val summary3 = "Test calls StatsAccumulator::addAll,\n" + - " there it iterates the loop for(double value: values) twice,\n" + - " inside this loop, the test calls StatsAccumulator::add,\n" + - " there it executes conditions:\n" + - " (count == 0): True\n" + - " (!isFinite(value)): True\n" + - " calls StatsAccumulator::add,\n" + - " there it executes conditions:\n" + - " (count == 0): False\n" + - " (isFinite(value) && isFinite(mean)): True\n" + - " (if (isFinite(value) && isFinite(mean)) {\n" + - " double delta = value - mean;\n" + - " mean += delta / count;\n" + - " sumOfSquaresOfDeltas += delta * (value - mean);\n" + - "} else {\n" + - " mean = calculateNewMeanNonFinite(mean, value);\n" + - " sumOfSquaresOfDeltas = NaN;\n" + - "}): False\n" + - "Test afterwards calls StatsAccumulator::snapshot,\n" + - " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + - " \n" + - "Test afterwards returns from: return acummulator.snapshot();\n" - val summary4 = "Test calls StatsAccumulator::addAll,\n" + - " there it iterates the loop for(double value: values) twice,\n" + - " inside this loop, the test calls StatsAccumulator::add,\n" + - " there it executes conditions:\n" + - " (!isFinite(value)): False\n" + - "Test next calls StatsAccumulator::snapshot,\n" + - " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + - " \n" + - "Test then returns from: return acummulator.snapshot();\n" - val summary5 = "Test calls StatsAccumulator::addAll,\n" + - " there it iterates the loop for(double value: values) twice,\n" + - " inside this loop, the test calls StatsAccumulator::add,\n" + - " there it executes conditions:\n" + - " (count == 0): True\n" + - " (!isFinite(value)): False\n" + - " calls StatsAccumulator::add,\n" + - " there it executes conditions:\n" + - " (count == 0): False\n" + - " (isFinite(value) && isFinite(mean)): True\n" + - " (if (isFinite(value) && isFinite(mean)) {\n" + - " double delta = value - mean;\n" + - " mean += delta / count;\n" + - " sumOfSquaresOfDeltas += delta * (value - mean);\n" + - "} else {\n" + - " mean = calculateNewMeanNonFinite(mean, value);\n" + - " sumOfSquaresOfDeltas = NaN;\n" + - "}): True\n" + - "Test later calls StatsAccumulator::snapshot,\n" + - " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + - " \n" + - "Test then returns from: return acummulator.snapshot();\n" - val summary6 = "Test calls StatsAccumulator::addAll,\n" + - " there it iterates the loop for(double value: values) twice,\n" + - " inside this loop, the test calls StatsAccumulator::add,\n" + - " there it executes conditions:\n" + - " (!isFinite(value)): True\n" + - "Test afterwards calls StatsAccumulator::snapshot,\n" + - " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + - " \n" + - "Test then returns from: return acummulator.snapshot();\n" - val summary7 = "Test calls StatsAccumulator::addAll,\n" + - " there it iterates the loop for(double value: values) twice,\n" + - " inside this loop, the test calls StatsAccumulator::add,\n" + - " there it executes conditions:\n" + - " (!isFinite(value)): True\n" + - "Test later calls StatsAccumulator::snapshot,\n" + - " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + - " \n" + - "Test further returns from: return acummulator.snapshot();\n" - - val methodName1 = "testOfDoubles_StatsAccumulatorAddAll" - val methodName2 = "testOfDoubles_snapshot" - val methodName3 = "testOfDoubles_IsFiniteAndIsFinite" - val methodName4 = "testOfDoubles_IsFinite" - val methodName5 = "testOfDoubles_IsFiniteAndIsFinite_1" - val methodName6 = "testOfDoubles_NotIsFinite" - val methodName7 = "testOfDoubles_NotIsFinite_1" - - val displayName1 = "acummulator.addAll(values) : True -> ThrowNullPointerException" - val displayName2 = "snapshot -> return new Stats(count, mean, sumOfSquaresOfDeltas, min, max)" - val displayName3 = "!isFinite(value) : True -> StatsAccumulatorCalculateNewMeanNonFinite" - val displayName4 = "add -> !isFinite(value) : False" - val displayName5 = "!isFinite(value) : False -> isFinite(value) && isFinite(mean)" - val displayName6 = "add -> !isFinite(value) : True" - val displayName7 = "add -> !isFinite(value) : True" - - val method = Stats::ofDoubles - val mockStrategy = MockStrategyApi.NO_MOCKS - val coverage = DoNotCalculate - - val summaryKeys = listOf( - summary1, - summary2, - summary3, - summary4, - summary5, - summary6, - summary7 - ) - - val displayNames = listOf( - displayName1, - displayName2, - displayName3, - displayName4, - displayName5, - displayName6, - displayName7 - ) - - val methodNames = listOf( - methodName1, - methodName2, - methodName3, - methodName4, - methodName5, - methodName6, - methodName7 - ) - - check(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) - } -} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/math/SummaryOfMathTest.kt b/utbot-summary-tests/src/test/kotlin/math/SummaryOfMathTest.kt new file mode 100644 index 0000000000..a74203cf99 --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/math/SummaryOfMathTest.kt @@ -0,0 +1,257 @@ +package math + +import examples.SummaryTestCaseGeneratorTest +import guava.examples.math.Stats +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.UtClusterInfo +import org.utbot.testing.DoNotCalculate + +/** + * It runs test generation for the poor analogue of the Stats.of method ported from the guava-26.0 framework + * and validates generated docs, display names and test method names. + * + * @see Related issue + */ +class SummaryOfMathTest : SummaryTestCaseGeneratorTest( + Stats::class, +) { + @Test + @Disabled("Test fails, https://github.com/UnitTestBot/UTBotJava/issues/826") + fun testOfInts() { + val summary1 = "Test calls {@link guava.examples.math.StatsAccumulator#addAll(int[])},\n" + + " there it triggers recursion of addAll once, \n" + + "Test throws NullPointerException in: acummulator.addAll(values);\n" + val summary2 = "Test calls {@link guava.examples.math.StatsAccumulator#addAll(int[])},\n" + + " there it does not iterate for(int value: values), \n" + + "Test later calls {@link guava.examples.math.StatsAccumulator#snapshot()},\n" + + " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + + " \n" + + "Test then returns from: return acummulator.snapshot();" + val summary3 = "Test calls {@link guava.examples.math.StatsAccumulator#addAll(int[])},\n" + + " there it iterates the loop for(int value: values) once. \n" + + "Test later calls {@link guava.examples.math.StatsAccumulator#snapshot()},\n" + + " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + + " \n" + + "Test then returns from: return acummulator.snapshot();" + val summary4 = "Test calls {@link guava.examples.math.StatsAccumulator#addAll(int[])},\n" + + " there it iterates the loop for(int value: values) twice. \n" + + "Test later calls {@link guava.examples.math.StatsAccumulator#snapshot()},\n" + + " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + + " \n" + + "Test later returns from: return acummulator.snapshot();\n" + + val methodName1 = "testOfInts_StatsAccumulatorAddAll" + val methodName2 = "testOfInts_snapshot" + val methodName3 = "testOfInts_IterateForEachLoop" + val methodName4 = "testOfInts_IterateForEachLoop_1" + + val displayName1 = "acummulator.addAll(values) : True -> ThrowNullPointerException" + val displayName2 = "snapshot -> return new Stats(count, mean, sumOfSquaresOfDeltas, min, max)" + val displayName3 = "addAll -> return new Stats(count, mean, sumOfSquaresOfDeltas, min, max)" + val displayName4 = "addAll -> return new Stats(count, mean, sumOfSquaresOfDeltas, min, max)" + + val method = Stats::ofInts + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4 + ) + + val clusterInfo = listOf( + Pair(UtClusterInfo("SUCCESSFUL EXECUTIONS for method ofInts(int[])", null), 3), + Pair(UtClusterInfo("ERROR SUITE for method ofInts(int[])", null), 1) + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames, clusterInfo) + } + + @Test + @Disabled("Test is flaky, https://github.com/UnitTestBot/UTBotJava/issues/826") + fun testOfDoubles() { + val summary1 = "Test calls {@link guava.examples.math.StatsAccumulator#addAll(double[])},\n" + + " there it triggers recursion of addAll once, \n" + + "Test throws NullPointerException in: acummulator.addAll(values);\n" + val summary2 = "Test calls {@link guava.examples.math.StatsAccumulator#addAll(double[])},\n" + + " there it does not iterate for(double value: values), \n" + + "Test next calls {@link guava.examples.math.StatsAccumulator#snapshot()},\n" + + " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + + " \n" + + "Test later returns from: return acummulator.snapshot();\n" + val summary3 = "Test calls {@link guava.examples.math.StatsAccumulator#addAll(double[])},\n" + + " there it iterates the loop for(double value: values) twice,\n" + + " inside this loop, the test calls {@link guava.examples.math.StatsAccumulator#add(double)},\n" + + " there it executes conditions:\n" + + " (count == 0): True\n" + + " (!isFinite(value)): True\n" + + " calls {@link guava.examples.math.StatsAccumulator#add(double)},\n" + + " there it executes conditions:\n" + + " (count == 0): False\n" + + " (isFinite(value) && isFinite(mean)): True\n" + + " (if (isFinite(value) && isFinite(mean)) {\n" + + " double delta = value - mean;\n" + + " mean += delta / count;\n" + + " sumOfSquaresOfDeltas += delta * (value - mean);\n" + + "} else {\n" + + " mean = calculateNewMeanNonFinite(mean, value);\n" + + " sumOfSquaresOfDeltas = NaN;\n" + + "}): False\n" + + "Test afterwards calls {@link guava.examples.math.StatsAccumulator#snapshot()},\n" + + " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + + " \n" + + "Test afterwards returns from: return acummulator.snapshot();\n" + val summary4 = "Test calls {@link guava.examples.math.StatsAccumulator#addAll(double[])},\n" + + " there it iterates the loop for(double value: values) twice,\n" + + " inside this loop, the test calls {@link guava.examples.math.StatsAccumulator#add(double)},\n" + + " there it executes conditions:\n" + + " (!isFinite(value)): False\n" + + "Test next calls {@link guava.examples.math.StatsAccumulator#snapshot()},\n" + + " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + + " \n" + + "Test then returns from: return acummulator.snapshot();\n" + val summary5 = "Test calls {@link guava.examples.math.StatsAccumulator#addAll(double[])},\n" + + " there it iterates the loop for(double value: values) twice,\n" + + " inside this loop, the test calls {@link guava.examples.math.StatsAccumulator#add(double)},\n" + + " there it executes conditions:\n" + + " (count == 0): True\n" + + " (!isFinite(value)): False\n" + + " calls {@link guava.examples.math.StatsAccumulator#add(double)},\n" + + " there it executes conditions:\n" + + " (count == 0): False\n" + + " (isFinite(value) && isFinite(mean)): True\n" + + " (if (isFinite(value) && isFinite(mean)) {\n" + + " double delta = value - mean;\n" + + " mean += delta / count;\n" + + " sumOfSquaresOfDeltas += delta * (value - mean);\n" + + "} else {\n" + + " mean = calculateNewMeanNonFinite(mean, value);\n" + + " sumOfSquaresOfDeltas = NaN;\n" + + "}): True\n" + + "Test later calls {@link guava.examples.math.StatsAccumulator#snapshot()},\n" + + " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + + " \n" + + "Test then returns from: return acummulator.snapshot();\n" + val summary6 = "Test calls {@link guava.examples.math.StatsAccumulator#addAll(double[])},\n" + + " there it iterates the loop for(double value: values) twice,\n" + + " inside this loop, the test calls {@link guava.examples.math.StatsAccumulator#add(double)},\n" + + " there it executes conditions:\n" + + " (!isFinite(value)): True\n" + + "Test then calls {@link guava.examples.math.StatsAccumulator#snapshot()},\n" + + " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + + " \n" + + "Test afterwards returns from: return acummulator.snapshot();\n" + val summary7 = "Test calls {@link guava.examples.math.StatsAccumulator#addAll(double[])},\n" + + " there it iterates the loop for(double value: values) twice,\n" + + " inside this loop, the test calls {@link guava.examples.math.StatsAccumulator#add(double)},\n" + + " there it executes conditions:\n" + + " (!isFinite(value)): True\n" + + "Test later calls {@link guava.examples.math.StatsAccumulator#snapshot()},\n" + + " there it returns from: return new Stats(count, mean, sumOfSquaresOfDeltas, min, max);\n" + + " \n" + + "Test further returns from: return acummulator.snapshot();\n" + + val methodName1 = "testOfDoubles_StatsAccumulatorAddAll" + val methodName2 = "testOfDoubles_snapshot" + val methodName3 = "testOfDoubles_IsFiniteAndIsFinite" + val methodName4 = "testOfDoubles_IsFinite" + val methodName5 = "testOfDoubles_IsFiniteAndIsFinite_1" + val methodName6 = "testOfDoubles_NotIsFinite" + val methodName7 = "testOfDoubles_NotIsFinite_1" + + val displayName1 = "acummulator.addAll(values) : True -> ThrowNullPointerException" + val displayName2 = "snapshot -> return new Stats(count, mean, sumOfSquaresOfDeltas, min, max)" + val displayName3 = "!isFinite(value) : True -> StatsAccumulatorCalculateNewMeanNonFinite" + val displayName4 = "add -> !isFinite(value) : False" + val displayName5 = "!isFinite(value) : False -> isFinite(value) && isFinite(mean)" + val displayName6 = "add -> !isFinite(value) : True" + val displayName7 = "add -> !isFinite(value) : True" + + val method = Stats::ofDoubles + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + val summaryKeys = listOf( + summary1, + summary2, + summary3, + summary4, + summary5, + summary6, + summary7 + ) + + val displayNames = listOf( + displayName1, + displayName2, + displayName3, + displayName4, + displayName5, + displayName6, + displayName7 + ) + + val methodNames = listOf( + methodName1, + methodName2, + methodName3, + methodName4, + methodName5, + methodName6, + methodName7 + ) + + val clusterInfo = listOf( + Pair(UtClusterInfo("SYMBOLIC EXECUTION: SUCCESSFUL EXECUTIONS #0 for method ofDoubles(double[])", null), 3), + Pair( + UtClusterInfo( + "SYMBOLIC EXECUTION: SUCCESSFUL EXECUTIONS #1 for method ofDoubles(double[])", "\n" + + "Common steps:\n" + + "

    \n" +
    +                            "Tests execute conditions:\n" +
    +                            "    {@code (null): True}\n" +
    +                            "call {@link guava.examples.math.StatsAccumulator#add(double)},\n" +
    +                            "    there it execute conditions:\n" +
    +                            "        {@code (count == 0): True}\n" +
    +                            "    invoke:\n" +
    +                            "        {@link guava.examples.math.StatsAccumulator#isFinite(double)} twice\n" +
    +                            "Tests later invoke:\n" +
    +                            "    {@link guava.examples.math.StatsAccumulator#add(double)} once\n" +
    +                            "execute conditions:\n" +
    +                            "    {@code (null): False}\n" +
    +                            "call {@link guava.examples.math.StatsAccumulator#isFinite(double)},\n" +
    +                            "    there it invoke:\n" +
    +                            "        {@link guava.examples.math.StatsAccumulator#isFinite(double)} once\n" +
    +                            "    execute conditions:\n" +
    +                            "        {@code (null): False}\n" +
    +                            "    invoke:\n" +
    +                            "        {@link guava.examples.math.StatsAccumulator#calculateNewMeanNonFinite(double,double)} twice,\n" +
    +                            "        {@link java.lang.Math#min(double,double)} twice,\n" +
    +                            "        {@link java.lang.Math#max(double,double)} twice\n" +
    +                            "
    " + ), 3 + ), + Pair(UtClusterInfo("SYMBOLIC EXECUTION: ERROR SUITE for method ofDoubles(double[])", null), 1) + ) + + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames, clusterInfo) + } +} \ No newline at end of file diff --git a/utbot-summary-tests/src/test/kotlin/math/SummaryOverflowExamples.kt b/utbot-summary-tests/src/test/kotlin/math/SummaryOverflowExamples.kt new file mode 100644 index 0000000000..e67605af5e --- /dev/null +++ b/utbot-summary-tests/src/test/kotlin/math/SummaryOverflowExamples.kt @@ -0,0 +1,54 @@ +package math + +import examples.CustomJavaDocTagsEnabler +import examples.SummaryTestCaseGeneratorTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.utbot.examples.math.OverflowExamples +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.testcheckers.withTreatingOverflowAsError +import org.utbot.testing.DoNotCalculate + +@ExtendWith(CustomJavaDocTagsEnabler::class) +class SummaryOverflowExamples : SummaryTestCaseGeneratorTest( + OverflowExamples::class +) { + @Test + fun testShortMulOverflow() { + val summary1 = "@utbot.classUnderTest {@link OverflowExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.math.OverflowExamples#shortMulOverflow(short,short)}\n" + + "@utbot.returnsFrom {@code return (short) (x * y);}\n" + val summary2 = "@utbot.classUnderTest {@link OverflowExamples}\n" + + "@utbot.methodUnderTest {@link org.utbot.examples.math.OverflowExamples#shortMulOverflow(short,short)}\n" + + "@utbot.detectsSuspiciousBehavior in: return (short) (x * y);\n" + + val methodName1 = "testShortMulOverflow_ReturnXy" + val methodName2 = "testShortMulOverflow_DetectOverflow" + + val displayName1 = "-> return (short) (x * y)" + val displayName2 = "return (short) (x * y) : True -> DetectOverflow" + + val summaryKeys = listOf( + summary1, + summary2 + ) + + val displayNames = listOf( + displayName1, + displayName2 + ) + + val methodNames = listOf( + methodName1, + methodName2 + ) + + val method = OverflowExamples::shortMulOverflow + val mockStrategy = MockStrategyApi.NO_MOCKS + val coverage = DoNotCalculate + + withTreatingOverflowAsError { + summaryCheck(method, mockStrategy, coverage, summaryKeys, methodNames, displayNames) + } + } +} \ No newline at end of file diff --git a/utbot-summary/build.gradle b/utbot-summary/build.gradle deleted file mode 100644 index 020fef96c2..0000000000 --- a/utbot-summary/build.gradle +++ /dev/null @@ -1,14 +0,0 @@ -apply from: "${parent.projectDir}/gradle/include/jvm-project.gradle" - -dependencies { - implementation "com.github.UnitTestBot:soot:${soot_commit_hash}" - api project(':utbot-framework-api') - compile(project(':utbot-instrumentation')) - - implementation group: 'com.github.haifengl', name: 'smile-kotlin', version: '2.6.0' - implementation group: 'com.github.haifengl', name: 'smile-core', version: '2.6.0' - - implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlin_logging_version - - implementation group: 'com.github.javaparser', name: 'javaparser-core', version: '3.22.1' -} diff --git a/utbot-summary/build.gradle.kts b/utbot-summary/build.gradle.kts new file mode 100644 index 0000000000..3c41ca537d --- /dev/null +++ b/utbot-summary/build.gradle.kts @@ -0,0 +1,20 @@ +val kotlinLoggingVersion: String by rootProject +val junit4Version: String by rootProject +val junit5Version: String by rootProject +val sootVersion: String by rootProject +val mockitoVersion: String by rootProject + +dependencies { + implementation(project(":utbot-framework-api")) + implementation("org.unittestbot.soot:soot-utbot-fork:${sootVersion}") { + exclude(group="com.google.guava", module="guava") + } + implementation(project(":utbot-java-fuzzing")) + implementation(project(":utbot-instrumentation")) + implementation(group = "com.github.haifengl", name = "smile-kotlin", version = "2.6.0") + implementation(group = "com.github.haifengl", name = "smile-core", version = "2.6.0") + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) + implementation("com.github.javaparser:javaparser-core:3.22.1") + testImplementation("org.mockito:mockito-core:$mockitoVersion") + testImplementation("org.junit.jupiter:junit-jupiter:$junit5Version") +} diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/AbstractTextBuilder.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/AbstractTextBuilder.kt index f15ca4f9c2..02c389b6f1 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/AbstractTextBuilder.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/AbstractTextBuilder.kt @@ -5,16 +5,16 @@ import com.github.javaparser.ast.expr.VariableDeclarationExpr import com.github.javaparser.ast.stmt.ExpressionStmt import com.github.javaparser.ast.stmt.ForEachStmt import com.github.javaparser.ast.stmt.ForStmt +import com.github.javaparser.ast.stmt.IfStmt import com.github.javaparser.ast.stmt.Statement -import com.github.javaparser.ast.stmt.SwitchEntry import com.github.javaparser.ast.stmt.SwitchStmt import com.github.javaparser.ast.stmt.WhileStmt import org.utbot.framework.plugin.api.Step import org.utbot.summary.ast.JimpleToASTMap -import org.utbot.summary.comment.IterationDescription -import org.utbot.summary.comment.SimpleSentenceBlock -import org.utbot.summary.comment.StmtDescription -import org.utbot.summary.comment.StmtType +import org.utbot.summary.comment.classic.symbolic.IterationDescription +import org.utbot.summary.comment.classic.symbolic.SimpleSentenceBlock +import org.utbot.summary.comment.classic.symbolic.StmtDescription +import org.utbot.summary.comment.classic.symbolic.StmtType import org.utbot.summary.comment.getTextIterationDescription import org.utbot.summary.comment.getTextTypeIterationDescription import org.utbot.summary.comment.numberWithSuffix @@ -23,8 +23,7 @@ import org.utbot.summary.tag.TraceTagWithoutExecution import soot.SootMethod import soot.jimple.Stmt import soot.jimple.internal.JIfStmt -import soot.jimple.internal.JLookupSwitchStmt -import soot.jimple.internal.JTableSwitchStmt +import soot.jimple.internal.JReturnStmt abstract class AbstractTextBuilder( val traceTag: TraceTagWithoutExecution, @@ -57,7 +56,6 @@ abstract class AbstractTextBuilder( } } - protected fun textReturn( statementTag: StatementTag, sentenceBlock: SimpleSentenceBlock, @@ -77,49 +75,22 @@ abstract class AbstractTextBuilder( returnType = StmtType.Return } jimpleToASTMap[statementTag.step.stmt]?.let { + val description = if (it is IfStmt) it.thenStmt else it sentenceBlock.stmtTexts.add( StmtDescription( returnType, - it.toString(), + description.toString(), prefix = prefixReturnText ) ) } } - - protected fun textSwitchCase(step: Step, jimpleToASTMap: JimpleToASTMap): String? { - val stmt = step.stmt - val astNode = jimpleToASTMap[stmt] - if (stmt is JLookupSwitchStmt) { - val lookup = stmt.lookupValues - val decision = step.decision - val case = ( - if (decision >= lookup.size) { - null - } else { - lookup[step.decision] - } - )?.value - - if (astNode is SwitchStmt) { - return JimpleToASTMap.getSwitchCaseLabel(astNode, case) - } - } - //needed more tests to cover these cases - if (stmt is JTableSwitchStmt && astNode is SwitchStmt) { - val switchCase = JimpleToASTMap.mapSwitchCase(astNode, step.decision) - if (switchCase is SwitchEntry) { - val case = switchCase.labels.first - if (case.isPresent) { - return "${case.get()}" - } else { - return "default" - } + protected fun textSwitchCase(step: Step, jimpleToASTMap: JimpleToASTMap): String? = + (jimpleToASTMap[step.stmt] as? SwitchStmt) + ?.let { switchStmt -> + NodeConverter.convertSwitchStmt(switchStmt, step, removeSpaces = false) } - } - return null - } protected fun textCondition(statementTag: StatementTag, jimpleToASTMap: JimpleToASTMap): String? { var reversed = true diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/DBSCANClusteringConstants.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/DBSCANClusteringConstants.kt new file mode 100644 index 0000000000..29b4a52d70 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/DBSCANClusteringConstants.kt @@ -0,0 +1,44 @@ +package org.utbot.summary + +import org.utbot.framework.plugin.api.util.IndentUtil + +object DBSCANClusteringConstants { + /** + * Sets minimum number of successful execution + * for applying the clustering algorithm. + */ + internal const val MIN_NUMBER_OF_EXECUTIONS_FOR_CLUSTERING: Int = 4 + + /** + * DBSCAN hyperparameter. + * + * Sets minimum number of executions to form a cluster. + */ + internal const val MIN_EXEC_DBSCAN: Int = 2 + + /** + * DBSCAN hyperparameter. + * + * Sets radius of search for algorithm. + */ + internal const val RADIUS_DBSCAN: Float = 5.0f +} + +object SummarySentenceConstants { + const val SENTENCE_SEPARATION = ",\n" + const val TAB = IndentUtil.TAB + const val NEW_LINE = "\n" + const val DOT_SYMBOL = '.' + const val COMMA_SYMBOL = ',' + const val SEMI_COLON_SYMBOL = ';' + const val CARRIAGE_RETURN = "\r" + + const val FROM_TO_NAMES_TRANSITION = "->" + const val FROM_TO_NAMES_COLON = ":" + const val AT_CODE = "@code" + const val OPEN_BRACKET = "{" + const val CLOSE_BRACKET = "}" + + const val JAVA_CLASS_DELIMITER = "$" + const val JAVA_DOC_CLASS_DELIMITER = "." +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/NodeConverter.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/NodeConverter.kt new file mode 100644 index 0000000000..a1e02cfaa6 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/NodeConverter.kt @@ -0,0 +1,438 @@ +package org.utbot.summary + +import com.github.javaparser.ast.Node +import com.github.javaparser.ast.body.VariableDeclarator +import com.github.javaparser.ast.expr.ArrayAccessExpr +import com.github.javaparser.ast.expr.ArrayCreationExpr +import com.github.javaparser.ast.expr.BinaryExpr +import com.github.javaparser.ast.expr.BooleanLiteralExpr +import com.github.javaparser.ast.expr.CastExpr +import com.github.javaparser.ast.expr.CharLiteralExpr +import com.github.javaparser.ast.expr.ClassExpr +import com.github.javaparser.ast.expr.ConditionalExpr +import com.github.javaparser.ast.expr.DoubleLiteralExpr +import com.github.javaparser.ast.expr.EnclosedExpr +import com.github.javaparser.ast.expr.FieldAccessExpr +import com.github.javaparser.ast.expr.InstanceOfExpr +import com.github.javaparser.ast.expr.IntegerLiteralExpr +import com.github.javaparser.ast.expr.LiteralExpr +import com.github.javaparser.ast.expr.LongLiteralExpr +import com.github.javaparser.ast.expr.MethodCallExpr +import com.github.javaparser.ast.expr.NameExpr +import com.github.javaparser.ast.expr.NullLiteralExpr +import com.github.javaparser.ast.expr.StringLiteralExpr +import com.github.javaparser.ast.expr.UnaryExpr +import com.github.javaparser.ast.expr.VariableDeclarationExpr +import com.github.javaparser.ast.stmt.CatchClause +import com.github.javaparser.ast.stmt.ExpressionStmt +import com.github.javaparser.ast.stmt.ForEachStmt +import com.github.javaparser.ast.stmt.ForStmt +import com.github.javaparser.ast.stmt.IfStmt +import com.github.javaparser.ast.stmt.ReturnStmt +import com.github.javaparser.ast.stmt.SwitchEntry +import com.github.javaparser.ast.stmt.SwitchStmt +import com.github.javaparser.ast.stmt.ThrowStmt +import com.github.javaparser.ast.stmt.WhileStmt +import org.utbot.framework.plugin.api.Step +import org.utbot.summary.SummarySentenceConstants.SEMI_COLON_SYMBOL +import org.utbot.summary.ast.JimpleToASTMap +import org.utbot.summary.comment.getTextIterationDescription +import soot.jimple.internal.JIfStmt +import soot.jimple.internal.JLookupSwitchStmt +import soot.jimple.internal.JReturnStmt +import soot.jimple.internal.JTableSwitchStmt +import kotlin.jvm.optionals.getOrNull + + +private const val STATEMENT_DECISION_TRUE = 1 +private const val STATEMENT_DECISION_FALSE = 0 + +class NodeConverter { + + companion object { + /** + * Converts ASTNode into String + * @return String that can be a javadoc + */ + fun convertNodeToString(ASTNode: Node, step: Step): String? { + var res = "" + var node = ASTNode + if (node is EnclosedExpr) node = JimpleToASTMap.unEncloseExpr(node) + convertNodeToStringRecursively(node, step)?.let { + res += it + } + if (step.decision == STATEMENT_DECISION_TRUE) { + if (node is BooleanLiteralExpr + || node is NameExpr + || node is MethodCallExpr + || node is CastExpr + || node is FieldAccessExpr + || node is InstanceOfExpr + || node is ArrayAccessExpr + ) res = "Not$res" + else if (node is UnaryExpr && node.operator == UnaryExpr.Operator.LOGICAL_COMPLEMENT) { + res = + res.removePrefix(convertUnaryOperator(UnaryExpr.Operator.LOGICAL_COMPLEMENT)) // double negative expression is positive expression + } + } + return res.ifEmpty { null } + } + + /** + * Used in a conversion of Node into javadoc String + */ + private fun convertNodeToStringRecursively(ASTNode: Node, step: Step): String? { + val res = when (ASTNode) { + is EnclosedExpr -> convertNodeToStringRecursively(JimpleToASTMap.unEncloseExpr(ASTNode), step) + is BinaryExpr -> convertBinaryExpr(ASTNode, step) + is UnaryExpr -> + convertUnaryOperator(ASTNode.operator) + + convertNodeToStringRecursively(ASTNode.expression, step) + is NameExpr -> "${ASTNode.name}" + is LiteralExpr -> convertLiteralExpression(ASTNode) + is ArrayAccessExpr -> "${ASTNode.index.toString().capitalize()}Of${ + ASTNode.name.toString().capitalize() + }" + is FieldAccessExpr -> { + if (ASTNode.scope is FieldAccessExpr) "${ + convertNodeToStringRecursively( + ASTNode.scope, + step + ) + }${ASTNode.name.toString().capitalize()}" + else "${ASTNode.scope.toString().capitalize()}${ASTNode.name.toString().capitalize()}" + } + is CastExpr -> ASTNode.expression.toString().capitalize() + is MethodCallExpr -> { + if (ASTNode.scope.isPresent) "${ + ASTNode.scope.get().toString().capitalize() + }${ASTNode.name.toString().capitalize()}" + else ASTNode.name.toString().capitalize() + } + is InstanceOfExpr -> { + if (step.decision == STATEMENT_DECISION_TRUE) "${ + ASTNode.expression.toString().capitalize() + }NotInstanceOf${ASTNode.type.toString().capitalize()}" + else "${ASTNode.expression.toString().capitalize()}InstanceOf${ + ASTNode.type.toString().capitalize() + }" + } + is ClassExpr -> "${ASTNode.type}Class" + is ArrayCreationExpr -> "NewArrayOf${ASTNode.createdType().toString().capitalize()}" + is ReturnStmt -> { + if (ASTNode.expression.isPresent) convertNodeToStringRecursively( + ASTNode.expression.get(), + step + ) + else "" + } + is ConditionalExpr -> "${convertNodeToStringRecursively(ASTNode.condition, step)}" + is VariableDeclarationExpr -> ASTNode.variables.joinToString(separator = "") { variable -> + convertNodeToStringRecursively( + variable, + step + ) ?: "" + } + + is VariableDeclarator -> { + val initializer = ASTNode.initializer + if (initializer.isPresent) "${ASTNode.type.toString().capitalize()}${ + ASTNode.name.toString().capitalize() + }InitializedBy${convertNodeToStringRecursively(ASTNode.initializer.get(), step)}" + else "${ASTNode.type.toString().capitalize()}${ASTNode.name.toString().capitalize()}IsInitialized" + } + is CatchClause -> ASTNode.parameter.type.toString() + .capitalize() //add ${ASTNode.parameter.name.toString().capitalize() to print variable name + + is WhileStmt -> convertNodeToStringRecursively(ASTNode.condition, step) + is IfStmt -> convertNodeToStringRecursively(ASTNode.condition, step) + is SwitchEntry -> convertSwitchEntry(ASTNode, step, removeSpaces = true) + is ThrowStmt -> "Throws${ASTNode.expression.toString().removePrefix("new").capitalize()}" + is SwitchStmt -> convertSwitchStmt(ASTNode, step, removeSpaces = true) + is ExpressionStmt -> convertNodeToStringRecursively(ASTNode.expression, step) + + else -> { + null + } + } ?: return null + return postProcessName(res) + } + + /** + * Converts ASTNode into String + * @return String that can be a DisplayName + */ + fun convertNodeToDisplayNameString(ASTNode: Node, step: Step): String { + var node = ASTNode + if (node is ExpressionStmt) node = node.expression + if (node is EnclosedExpr) node = JimpleToASTMap.unEncloseExpr(node) + var res = convertNodeToDisplayNameStringRecursively(node, step) + if (node is ReturnStmt) node = node.expression.getOrNull() ?: node + if (step.stmt is JReturnStmt) return res + if (nodeContainsBooleanCondition(node)) { + res += if (step.decision == STATEMENT_DECISION_TRUE) { + " : False" + } else { + " : True" + } + } + return res + } + + /** + * Checks if node contain any boolean condition + */ + private fun nodeContainsBooleanCondition(node: Node): Boolean { + if (node is ArrayAccessExpr + || node is FieldAccessExpr + || node is CastExpr + || node is NameExpr + || node is BinaryExpr + || node is MethodCallExpr + || node is InstanceOfExpr + || node is UnaryExpr + || node is BooleanLiteralExpr + || node is VariableDeclarationExpr && node.variables.any { nodeContainsBooleanCondition(it) } + ) return true + if (node is VariableDeclarator) { + val initializer = node.initializer.getOrNull() + if (initializer != null) { + return nodeContainsBooleanCondition(initializer) + } + } + return false + } + + /** + * Used in a conversion of Node into DisplayName String + */ + private fun convertNodeToDisplayNameStringRecursively(ASTNode: Node, step: Step): String { + val res = when (ASTNode) { + is EnclosedExpr -> convertNodeToDisplayNameStringRecursively( + JimpleToASTMap.unEncloseExpr(ASTNode), + step + ) + is WhileStmt -> getTextIterationDescription(ASTNode) + is ForStmt -> getTextIterationDescription(ASTNode) + is ForEachStmt -> getTextIterationDescription(ASTNode) + is IfStmt -> convertNodeToDisplayNameStringRecursively(ASTNode.condition, step) + is SwitchEntry -> convertSwitchEntry(ASTNode, step, removeSpaces = false) + is ThrowStmt -> "Throws ${ASTNode.expression.toString().removePrefix("new").capitalize()}" + is SwitchStmt -> convertSwitchStmt(ASTNode, step, removeSpaces = false) + is ExpressionStmt -> convertNodeToDisplayNameStringRecursively(ASTNode.expression, step) + is VariableDeclarationExpr -> ASTNode.variables.joinToString(separator = " ") { variable -> + convertNodeToDisplayNameStringRecursively( + variable, + step + ) + } + else -> { + ASTNode.toString() + } + } + return displayNamePostprocessor(res) + } + + /** + * Replaces one+ whitespaces with one whitespace + */ + private fun displayNamePostprocessor(displayName: String) = + displayName.replace("\\s+".toRegex(), " ").replace("$SEMI_COLON_SYMBOL", "") + + private fun convertBinaryExpr(binaryExpr: BinaryExpr, step: Step): String { + var res = "" + val left = convertNodeToStringRecursively(binaryExpr.left, step) + if (left != null) res += left.capitalize() + val stmt = step.stmt + res += if (stmt is JIfStmt) { + convertBinaryOperator( + binaryExpr.operator, + JimpleToASTMap.isOperatorReversed(stmt, binaryExpr), + step.decision + ).capitalize() + } else { + convertBinaryOperator(binaryExpr.operator, false, step.decision).capitalize() + } + val right = convertNodeToStringRecursively(binaryExpr.right, step) + if (right != null) res += right.capitalize() + return res + } + + fun convertSwitchStmt(switchStmt: SwitchStmt, step: Step, removeSpaces: Boolean = true): String = + convertSwitchLabel(switchStmt, step) + ?.let { label -> + val selector = switchStmt.selector.toString() + formatSwitchLabel(label, selector, removeSpaces) + } + ?: "switch(${switchStmt.selector})" + + fun convertSwitchEntry(switchEntry: SwitchEntry, step: Step, removeSpaces: Boolean = true): String = + (switchEntry.parentNode.getOrNull() as? SwitchStmt) + ?.let { switchStmt -> + val label = convertSwitchLabel(switchStmt, step) ?: getSwitchLabel(switchEntry) + val selector = switchStmt.selector.toString() + formatSwitchLabel(label, selector, removeSpaces) + } + ?: switchEntry.toString() + + private fun getSwitchLabel(node: SwitchEntry): String { + val case = node.labels.first + return if (case.isPresent) "${case.get()}" else "default" + } + + private fun formatSwitchLabel(label: String, selector: String, removeSpaces: Boolean = true): String { + return if (removeSpaces) "Switch${selector.capitalize()}Case" + label.replace(" ", "") + else "switch($selector) case: $label" + } + + private fun convertSwitchLabel(switchStmt: SwitchStmt, step: Step): String? = + when (val stmt = step.stmt) { + is JLookupSwitchStmt -> { + val lookup = stmt.lookupValues + val case = + if (step.decision >= lookup.size) null + else lookup[step.decision].value + + JimpleToASTMap.getSwitchCaseLabel(switchStmt, case) + } + + is JTableSwitchStmt -> { + JimpleToASTMap + .mapSwitchCase(switchStmt, step) + ?.let { getSwitchLabel(it) } + } + + else -> null + } + + /** + * Converts literal into String + */ + private fun convertLiteralExpression(literal: LiteralExpr): String { + val res = when (literal) { + is StringLiteralExpr -> literal.asString() + is CharLiteralExpr -> if (isLegitSymbolForFunctionName(literal.asChar())) "${literal.asChar()}" else "Char" + is DoubleLiteralExpr -> { + when (val literalAsDouble = literal.asDouble()) { + Double.NaN -> "NaN" + Double.MIN_VALUE -> "MinValue" + Double.MAX_VALUE -> "MaxValue" + Double.NEGATIVE_INFINITY -> "NegativeInfinity" + Double.POSITIVE_INFINITY -> "Infinity" + else -> { + when { + literalAsDouble != literalAsDouble -> "NaN" + literalAsDouble < 0 -> "Negative${literal.asDouble().toInt()}d" + literalAsDouble == 0.0 -> "Zero" + literalAsDouble == -0.0 -> "Zero" + else -> "${ + literal.asDouble().toInt() + }d" //seems kinda wrong, . can be replaced with "dot" or something else + } + } + } + } + is IntegerLiteralExpr -> { + var str = "" + if (literal.asNumber().toInt() < 0) str += "Negative" + str += if (literal.asNumber().toInt() == 0) "Zero" else "$literal" + str + } + is LongLiteralExpr -> { + var str = "" + if (literal.asNumber().toLong() < 0) str += "Negative" + str += if (literal.asNumber().toInt() == 0) "Zero" else "$literal" + str + } + is BooleanLiteralExpr -> literal.toString() + is NullLiteralExpr -> "Null" + else -> "" + } + return res.capitalize() + } + + /** + * Checks if symbol can be used in function name + * @see Java documentation: Identifiers + */ + private fun isLegitSymbolForFunctionName(ch: Char): Boolean { + return (ch in '0'..'9' + || ch in 'a'..'z' + || ch in 'A'..'Z' + || ch == '_' + || ch == '$' + ) + } + + /** + * Capitalizes method name. + * + * It splits the text by delimiters, capitalizes each part, removes special characters and concatenates result. + */ + private fun postProcessName(name: String) = + name.split(".", "(", ")", ",") + .joinToString("") { it -> it.capitalize().filter { isLegitSymbolForFunctionName(it) } } + + /** + * Converts Javaparser BinaryOperator and all of its children into a String + */ + private fun convertBinaryOperator( + binaryOperator: BinaryExpr.Operator, + isOperatorReversed: Boolean, + decision: Int + ): String { + var operator = binaryOperator + if ((isOperatorReversed && decision == STATEMENT_DECISION_TRUE) || (!isOperatorReversed && decision == STATEMENT_DECISION_FALSE)) { + operator = reverseBinaryOperator(operator) ?: binaryOperator + } + return when (operator) { + BinaryExpr.Operator.OR -> "Or" + BinaryExpr.Operator.AND -> "And" + BinaryExpr.Operator.BINARY_OR -> "BitwiseOr" + BinaryExpr.Operator.BINARY_AND -> "BitwiseAnd" + BinaryExpr.Operator.XOR -> "Xor" + BinaryExpr.Operator.EQUALS -> "Equals" + BinaryExpr.Operator.NOT_EQUALS -> "NotEquals" + BinaryExpr.Operator.LESS -> "LessThan" + BinaryExpr.Operator.GREATER -> "GreaterThan" + BinaryExpr.Operator.LESS_EQUALS -> "LessOrEqual" + BinaryExpr.Operator.GREATER_EQUALS -> "GreaterOrEqual" + BinaryExpr.Operator.LEFT_SHIFT -> "LeftShift" + BinaryExpr.Operator.SIGNED_RIGHT_SHIFT -> "RightShift" + BinaryExpr.Operator.UNSIGNED_RIGHT_SHIFT -> "UnsignedRightShift" + BinaryExpr.Operator.PLUS -> "Plus" + BinaryExpr.Operator.MINUS -> "Minus" + BinaryExpr.Operator.MULTIPLY -> "Multiply" + BinaryExpr.Operator.DIVIDE -> "Divide" + BinaryExpr.Operator.REMAINDER -> "RemainderOf" //does it sounds strange? or is it ok + } + } + + /** + * Reverts Javaparser binary operator if possible + */ + private fun reverseBinaryOperator(operator: BinaryExpr.Operator) = when (operator) { + BinaryExpr.Operator.EQUALS -> BinaryExpr.Operator.NOT_EQUALS + BinaryExpr.Operator.NOT_EQUALS -> BinaryExpr.Operator.EQUALS + BinaryExpr.Operator.LESS -> BinaryExpr.Operator.GREATER_EQUALS + BinaryExpr.Operator.GREATER -> BinaryExpr.Operator.LESS_EQUALS + BinaryExpr.Operator.LESS_EQUALS -> BinaryExpr.Operator.GREATER + BinaryExpr.Operator.GREATER_EQUALS -> BinaryExpr.Operator.LESS + else -> null + } + + /** + * Converts Javaparser unary operator to String + */ + private fun convertUnaryOperator(unaryOperator: UnaryExpr.Operator) = when (unaryOperator) { + UnaryExpr.Operator.PLUS -> "Plus" + UnaryExpr.Operator.MINUS -> "Negative" + UnaryExpr.Operator.PREFIX_INCREMENT -> "PrefixIncrement" + UnaryExpr.Operator.PREFIX_DECREMENT -> "PrefixDecrement" + UnaryExpr.Operator.LOGICAL_COMPLEMENT -> "Not" //! or LogicalComplement + UnaryExpr.Operator.BITWISE_COMPLEMENT -> "BitwiseComplement" + UnaryExpr.Operator.POSTFIX_INCREMENT -> "PostfixIncrement" + UnaryExpr.Operator.POSTFIX_DECREMENT -> "PostfixDecrement" + } + } +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/Summarization.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/Summarization.kt index fb473101e5..1583cf2664 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/Summarization.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/Summarization.kt @@ -1,131 +1,171 @@ package org.utbot.summary import com.github.javaparser.ast.body.MethodDeclaration -import org.utbot.framework.UtSettings import org.utbot.framework.plugin.api.UtClusterInfo -import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtSymbolicExecution import org.utbot.framework.plugin.api.UtExecutionCluster -import org.utbot.framework.plugin.api.UtTestCase +import org.utbot.framework.plugin.api.UtMethodTestSet import org.utbot.instrumentation.instrumentation.instrumenter.Instrumenter import org.utbot.summary.SummarySentenceConstants.NEW_LINE -import org.utbot.summary.UtSummarySettings.GENERATE_CLUSTER_COMMENTS -import org.utbot.summary.UtSummarySettings.GENERATE_COMMENTS -import org.utbot.summary.UtSummarySettings.GENERATE_DISPLAYNAME_FROM_TO_STYLE -import org.utbot.summary.UtSummarySettings.GENERATE_DISPLAY_NAMES -import org.utbot.summary.UtSummarySettings.GENERATE_NAMES import org.utbot.summary.analysis.ExecutionStructureAnalysis import org.utbot.summary.ast.JimpleToASTMap import org.utbot.summary.ast.SourceCodeParser -import org.utbot.summary.comment.SimpleClusterCommentBuilder -import org.utbot.summary.comment.SimpleCommentBuilder +import org.utbot.summary.comment.cluster.SymbolicExecutionClusterCommentBuilder +import org.utbot.summary.comment.classic.symbolic.SimpleCommentBuilder import org.utbot.summary.name.SimpleNameBuilder import java.io.File import java.nio.file.Path -import java.nio.file.Paths import mu.KotlinLogging +import org.utbot.common.measureTime +import org.utbot.common.info +import org.utbot.framework.SummariesGenerationType.* +import org.utbot.framework.UtSettings.enableClusterCommentsGeneration +import org.utbot.framework.UtSettings.enableJavaDocGeneration +import org.utbot.framework.UtSettings.useDisplayNameArrowStyle +import org.utbot.framework.UtSettings.enableDisplayNameGeneration +import org.utbot.framework.UtSettings.enableTestNamesGeneration +import org.utbot.framework.UtSettings.summaryGenerationType +import org.utbot.framework.UtSettings.useCustomJavaDocTags +import org.utbot.framework.plugin.api.util.isConstructor +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedValue +import org.utbot.fuzzer.UtFuzzedExecution +import org.utbot.summary.fuzzer.names.MethodBasedNameSuggester +import org.utbot.summary.fuzzer.names.ModelBasedNameSuggester +import org.utbot.summary.comment.customtags.symbolic.CustomJavaDocCommentBuilder import soot.SootMethod private val logger = KotlinLogging.logger {} -fun UtTestCase.summarize(sourceFile: File?, searchDirectory: Path = Paths.get("")): UtTestCase { - if (!UtSettings.enableMachineLearningModule) return this - - return try { - makeDiverseExecutions(this) - val invokeDescriptions = invokeDescriptions(this, searchDirectory) - // every cluster has summary and list of executions - val executionClusters = Summarization(sourceFile, invokeDescriptions).summary(this) - val updatedExecutions = executionClusters.flatMap { it.executions } - var pos = 0 - val clustersInfo = executionClusters.map { - val clusterSize = it.executions.size - val indices = pos until (pos + clusterSize) - pos += clusterSize - it.clusterInfo to indices - } - this.copy(executions = updatedExecutions, clustersInfo = clustersInfo) - } catch (e: Throwable) { - logger.info(e) { "Summary generation error" } - this +fun Collection.summarizeAll(searchDirectory: Path, sourceFile: File?): List = logger.info().measureTime({ + "----------------------------------------------------------------------------------------\n" + + "-------------------Summarization started for ${this.size} test cases--------------------\n" + + "----------------------------------------------------------------------------------------" + }) { + this.map { + it.summarizeOne(searchDirectory, sourceFile) } } -fun UtTestCase.summarize(searchDirectory: Path): UtTestCase = - this.summarize(Instrumenter.computeSourceFileByClass(this.method.clazz.java, searchDirectory), searchDirectory) +private fun UtMethodTestSet.summarizeOne(searchDirectory: Path, sourceFile: File?): UtMethodTestSet = logger.info().measureTime({ "Summarization for ${this.method}"} ){ + if (summaryGenerationType == NONE) return this + + val sourceFileToAnalyze = sourceFile + ?: when (summaryGenerationType) { + FULL -> Instrumenter.adapter.computeSourceFileByClass(this.method.classId.jClass, searchDirectory) + LIGHT, + NONE -> null + } + + makeDiverseExecutions(this) + // HACK: we avoid calling [invokeDescriptions] method to save time, it is useless in Contest + val invokeDescriptions = when (summaryGenerationType) { + FULL -> invokeDescriptions(this, searchDirectory) + LIGHT, + NONE -> emptyList() + } -class Summarization(val sourceFile: File?, val invokeDescriptions: List) { + // every cluster has summary and list of executions + val executionClusters = Summarization(sourceFileToAnalyze, invokeDescriptions).fillSummaries(this) + val updatedExecutions = executionClusters.flatMap { it.executions } + var pos = 0 + val clustersInfo = executionClusters.map { + val clusterSize = it.executions.size + val indices = pos until (pos + clusterSize) + pos += clusterSize + it.clusterInfo to indices + } + return this.copy( + executions = updatedExecutions, + clustersInfo = clustersInfo + ) // TODO: looks weird and don't create the real copy +} + +open class Summarization(val sourceFile: File?, val invokeDescriptions: List) { private val tagGenerator = TagGenerator() private val jimpleBodyAnalysis = ExecutionStructureAnalysis() - fun summary(testCase: UtTestCase): List { - val namesCounter = mutableMapOf() - - if (testCase.executions.isEmpty()) { + fun fillSummaries(testSet: UtMethodTestSet): List { + if (testSet.executions.isEmpty()) { logger.info { "No execution traces found in test case " + - "for method ${testCase.method.clazz.qualifiedName}, " + "${testCase.jimpleBody}" + "for method ${testSet.method.classId.name}, " + "${testSet.jimpleBody}" } - return listOf(UtExecutionCluster(UtClusterInfo(), testCase.executions)) + return listOf(UtExecutionCluster(UtClusterInfo(), testSet.executions)) } - // init - val sootToAST = sootToAST(testCase) - val jimpleBody = testCase.jimpleBody - val updatedExecutions = mutableListOf() - val clustersToReturn = mutableListOf() - - // TODO: Now it excludes tests generated by Fuzzer, handle it properly, related to the https://github.com/UnitTestBot/UTBotJava/issues/428 - val executionsProducedByFuzzer = getExecutionsWithEmptyPath(testCase) + val executionClusters = mutableListOf() - if (executionsProducedByFuzzer.isNotEmpty()) { - executionsProducedByFuzzer.forEach { - logger.info { - "The path for test ${it.testMethodName} " + - "for method ${testCase.method.clazz.qualifiedName} is empty and summaries could not be generated." - } + when (summaryGenerationType) { + FULL -> { + executionClusters += generateSummariesForTestsWithNonEmptyPathsProducedBySymbolicExecutor(testSet) + executionClusters += generateFuzzerBasedSummariesForTests(testSet) + executionClusters += generateSummariesForTestsWithEmptyPathsProducedBySymbolicExecutor(testSet) } - - clustersToReturn.add( - UtExecutionCluster( - UtClusterInfo(), - executionsProducedByFuzzer - ) - ) + LIGHT -> { + executionClusters += generateFuzzerBasedSummariesForTests(testSet, MethodDescriptionSource.SYMBOLIC) + executionClusters += generateFuzzerBasedSummariesForTests(testSet) + } + NONE -> error("We must not fill summaries if SummariesGenerationType is NONE") } + return if (enableClusterCommentsGeneration && executionClusters.size > 0) + executionClusters + else + listOf(UtExecutionCluster(UtClusterInfo(), testSet.executions)) + } + + private fun generateSummariesForTestsWithNonEmptyPathsProducedBySymbolicExecutor( + testSet: UtMethodTestSet + ): List { + val clustersToReturn: MutableList = mutableListOf() + val testSetWithNonEmptyPaths = prepareTestSetForByteCodeAnalysis(testSet) + + val sootToAST = sootToAST(testSetWithNonEmptyPaths) + val jimpleBody = testSet.jimpleBody + val updatedExecutions = mutableListOf() + val namesCounter = mutableMapOf() + // analyze if (jimpleBody != null && sootToAST != null) { val methodUnderTest = jimpleBody.method - val clusteredTags = tagGenerator.testCaseToTags(testCase) + val clusteredTags = tagGenerator.testSetToTags(testSetWithNonEmptyPaths) jimpleBodyAnalysis.traceStructuralAnalysis(jimpleBody, clusteredTags, methodUnderTest, invokeDescriptions) val numberOfSuccessfulClusters = clusteredTags.filter { it.isSuccessful }.size for (clusterTraceTags in clusteredTags) { - val clusterHeader = clusterTraceTags.summary.takeIf { GENERATE_CLUSTER_COMMENTS } + val clusterHeader = clusterTraceTags.clusterHeader.takeIf { enableClusterCommentsGeneration } val clusterContent = if ( - GENERATE_CLUSTER_COMMENTS && clusterTraceTags.isSuccessful // add only for successful executions + enableClusterCommentsGeneration && clusterTraceTags.isSuccessful // add only for successful executions && numberOfSuccessfulClusters > 1 // there is more than one successful execution && clusterTraceTags.traceTags.size > 1 // add if there is more than 1 execution ) { - SimpleClusterCommentBuilder(clusterTraceTags.commonStepsTraceTag, sootToAST) - .buildString(methodUnderTest) - .takeIf { it.isNotBlank() } - ?.let { - buildString { - append("${NEW_LINE}Common steps:") - append("$NEW_LINE$it") - } + SymbolicExecutionClusterCommentBuilder(clusterTraceTags.commonStepsTraceTag, sootToAST) + .buildString(methodUnderTest) + .takeIf { it.isNotBlank() } + ?.let { + buildString { + append("${NEW_LINE}Common steps:") + append("$NEW_LINE$it") } + } } else { - null + null // TODO: handle it correctly, something like common cluster or something else } for (traceTags in clusterTraceTags.traceTags) { - if (GENERATE_COMMENTS) { - traceTags.execution.summary = SimpleCommentBuilder(traceTags, sootToAST).buildDocStmts(methodUnderTest) + if (enableJavaDocGeneration) { + if (useCustomJavaDocTags) { + traceTags.execution.summary = + CustomJavaDocCommentBuilder(traceTags, sootToAST).buildDocStatements(methodUnderTest) + } else { + traceTags.execution.summary = + SimpleCommentBuilder(traceTags, sootToAST).buildDocStmts(methodUnderTest) + } } - if (GENERATE_DISPLAY_NAMES || GENERATE_NAMES) { + if (enableDisplayNameGeneration || enableTestNamesGeneration) { val simpleNameBuilder = SimpleNameBuilder(traceTags, sootToAST, methodUnderTest) val name = simpleNameBuilder.name val displayName = simpleNameBuilder.displayName @@ -133,14 +173,14 @@ class Summarization(val sourceFile: File?, val invokeDescriptions: List { + val clustersToReturn: MutableList = mutableListOf() + val testSetWithEmptyPaths = prepareTestSetWithEmptyPaths(testSet) + + val executionsWithEmptyPaths = testSetWithEmptyPaths.executions + + if (executionsWithEmptyPaths.isNotEmpty()) { + executionsWithEmptyPaths.forEach { + logger.info { + "The path for test ${it.testMethodName} " + + "for method ${testSet.method.classId.name} is empty and summaries could not be generated." + } + } + + val clusteredExecutions = groupExecutionsWithEmptyPaths(testSetWithEmptyPaths) + + clusteredExecutions.forEach { + clustersToReturn.add( + UtExecutionCluster( + UtClusterInfo(it.header), + it.executions + ) + ) + } + } + return clustersToReturn.toList() } - private fun getExecutionsWithEmptyPath(testCase: UtTestCase) = - testCase.executions.filter { it.path.isEmpty() } + private fun generateFuzzerBasedSummariesForTests( + testSet: UtMethodTestSet, + descriptionSource: MethodDescriptionSource = MethodDescriptionSource.FUZZER + ): List { + val clustersToReturn: MutableList = mutableListOf() + val methodTestSet = prepareMethodTestSet(testSet, descriptionSource) - /* - * asts of invokes also included - * */ + if (methodTestSet.executions.isNotEmpty()) { + methodTestSet.executions.forEach { utExecution -> + val nameSuggester = sequenceOf(ModelBasedNameSuggester(), MethodBasedNameSuggester(descriptionSource)) + val testMethodName = try { + nameSuggester.flatMap { + when (descriptionSource) { + MethodDescriptionSource.FUZZER -> { + with(utExecution as UtFuzzedExecution) { + it.suggest( + utExecution.fuzzedMethodDescription as FuzzedMethodDescription, + utExecution.fuzzingValues as List, + utExecution.result + ) + } + } + + MethodDescriptionSource.SYMBOLIC -> { + val executableId = testSet.method + val description = FuzzedMethodDescription(executableId).apply { + packageName = executableId.classId.packageName + className = executableId.classId.simpleName + compilableName = if (!executableId.isConstructor) executableId.name else null + canonicalName = executableId.classId.canonicalName + isNested = executableId.classId.isNested + } + it.suggest( + description, + utExecution.stateBefore.parameters.map { value -> FuzzedValue(value) }, + utExecution.result + ) + } + } + }.firstOrNull() + } catch (t: Throwable) { + logger.error(t) { "Cannot create suggested test name for $utExecution" } // TODO: add better explanation or default behaviour + null + } + + utExecution.testMethodName = testMethodName?.testName + utExecution.displayName = testMethodName?.displayName + utExecution.summary = testMethodName?.javaDoc + } + + when(descriptionSource){ + MethodDescriptionSource.FUZZER -> { + val clusteredExecutions = groupFuzzedExecutions(methodTestSet) + clusteredExecutions.forEach { + clustersToReturn.add( + UtExecutionCluster( + UtClusterInfo(it.header), + it.executions + ) + ) + } + } + MethodDescriptionSource.SYMBOLIC -> { + clustersToReturn.add( + UtExecutionCluster( + UtClusterInfo(), + methodTestSet.executions + ) + ) + } + } + } + + return clustersToReturn.toList() + } + + open fun prepareMethodTestSet(testSet: UtMethodTestSet, descriptionSource: MethodDescriptionSource): UtMethodTestSet{ + return when (descriptionSource) { + MethodDescriptionSource.FUZZER -> prepareTestSetWithFuzzedExecutions(testSet) + MethodDescriptionSource.SYMBOLIC -> prepareTestSetForByteCodeAnalysis(testSet) + } + } + + /** Filter and copies executions with non-empty paths. */ + private fun prepareTestSetForByteCodeAnalysis(testSet: UtMethodTestSet): UtMethodTestSet { + val executions = + testSet.executions.filterIsInstance() + .filter { it.path.isNotEmpty() } + + return UtMethodTestSet( + method = testSet.method, + executions = executions, + jimpleBody = testSet.jimpleBody, + errors = testSet.errors, + clustersInfo = testSet.clustersInfo + ) + } + + /** Filter and copies fuzzed executions. */ + private fun prepareTestSetWithFuzzedExecutions(testSet: UtMethodTestSet): UtMethodTestSet { + val executions = testSet.executions.filterIsInstance() + + return UtMethodTestSet( + method = testSet.method, + executions = executions, + jimpleBody = testSet.jimpleBody, + errors = testSet.errors, + clustersInfo = testSet.clustersInfo + ) + } + + /** Filter and copies executions with non-empty paths. */ + private fun prepareTestSetWithEmptyPaths(testSet: UtMethodTestSet): UtMethodTestSet { + val executions = + testSet.executions.filterIsInstance() + .filter { it.path.isEmpty() } + + return UtMethodTestSet( + method = testSet.method, + executions = executions, + jimpleBody = testSet.jimpleBody, + errors = testSet.errors, + clustersInfo = testSet.clustersInfo + ) + } + + /** ASTs of invokes are also included. */ private fun sootToAST( - testCase: UtTestCase + testSet: UtMethodTestSet ): MutableMap? { val sootToAST = mutableMapOf() - val jimpleBody = testCase.jimpleBody + val jimpleBody = testSet.jimpleBody if (jimpleBody == null) { - logger.info { "No jimple body of method under test" } + logger.debug { "No jimple body of method under test ${testSet.method.name}." } return null } - val methodUnderTestAST = sourceFile?.let { - SourceCodeParser(it, testCase).methodAST - } - if (methodUnderTestAST == null) { - logger.info { "Couldn't parse source file of method under test" } - return null - } + if (sourceFile != null && sourceFile.exists()) { + val methodUnderTestAST = SourceCodeParser(sourceFile, testSet).methodAST + + if (methodUnderTestAST == null) { + logger.debug { "Couldn't parse source file with path ${sourceFile.absolutePath} of method under test ${testSet.method.name}." } + return null + } - sootToAST[jimpleBody.method] = JimpleToASTMap(jimpleBody.units, methodUnderTestAST) - invokeDescriptions.forEach { - sootToAST[it.sootMethod] = JimpleToASTMap(it.sootMethod.jimpleBody().units, it.ast) + sootToAST[jimpleBody.method] = JimpleToASTMap(jimpleBody.units, methodUnderTestAST) + invokeDescriptions.forEach { + sootToAST[it.sootMethod] = JimpleToASTMap(it.sootMethod.jimpleBody().units, it.ast) + } + return sootToAST + } else { + logger.debug { "Couldn't find source file of method under test ${testSet.method.name}." } + return null } - return sootToAST } } -private fun makeDiverseExecutions(testCase: UtTestCase) { - val maxDepth = testCase.executions.flatMap { it.path }.maxOfOrNull { it.depth } ?: 0 +private fun makeDiverseExecutions(testSet: UtMethodTestSet) { + val symbolicExecutions = testSet.executions.filterIsInstance() + + val maxDepth = symbolicExecutions.flatMap { it.path }.maxOfOrNull { it.depth } ?: 0 if (maxDepth > 0) { logger.info { "Recursive function, max recursion: $maxDepth" } return } - var diversity = percentageDiverseExecutions(testCase.executions) + var diversity = percentageDiverseExecutions(symbolicExecutions) if (diversity >= 50) { logger.info { "Diversity execution path percentage: $diversity" } return @@ -212,8 +409,8 @@ private fun makeDiverseExecutions(testCase: UtTestCase) { for (depth in 1..2) { logger.info { "Depth to add: $depth" } - stepsUpToDepth(testCase.executions, depth) - diversity = percentageDiverseExecutions(testCase.executions) + stepsUpToDepth(symbolicExecutions, depth) + diversity = percentageDiverseExecutions(symbolicExecutions) if (diversity >= 50) { logger.info { "Diversity execution path percentage: $diversity" } @@ -222,22 +419,38 @@ private fun makeDiverseExecutions(testCase: UtTestCase) { } } -private fun invokeDescriptions(testCase: UtTestCase, seachDirectory: Path): List { - val sootInvokes = testCase.executions.flatMap { it.path.invokeJimpleMethods() }.toSet() +private fun invokeDescriptions(testSet: UtMethodTestSet, searchDirectory: Path): List { + val sootInvokes = + testSet.executions.filterIsInstance().flatMap { it.path.invokeJimpleMethods() }.toSet() + return sootInvokes //TODO(SAT-1170) .filterNot { "\$lambda" in it.declaringClass.name } .mapNotNull { sootMethod -> - val methodFile = Instrumenter.computeSourceFileByClass( + val methodFile = Instrumenter.adapter.computeSourceFileByNameAndPackage( sootMethod.declaringClass.name, sootMethod.declaringClass.javaPackageName.replace(".", File.separator), - seachDirectory + searchDirectory ) - val ast = methodFile?.let { - SourceCodeParser(sootMethod, it).methodAST + + if (methodFile != null && methodFile.exists()) { + val ast = methodFile.let { + SourceCodeParser(sootMethod, it).methodAST + } + if (ast != null) InvokeDescription(sootMethod, ast) else null + } else { + null } - if (ast != null) InvokeDescription(sootMethod, ast) else null } } -data class InvokeDescription(val sootMethod: SootMethod, val ast: MethodDeclaration) \ No newline at end of file +data class InvokeDescription(val sootMethod: SootMethod, val ast: MethodDeclaration) + +/** + * Sometimes, we need to use fuzzer for preparing summaries even for [UtSymbolicExecution]s. + * See [Summarization.generateFuzzerBasedSummariesForTests]. + */ +enum class MethodDescriptionSource { + FUZZER, + SYMBOLIC, +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/TagGenerator.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/TagGenerator.kt index 862d2a261e..0e5947483c 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/TagGenerator.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/TagGenerator.kt @@ -2,30 +2,38 @@ package org.utbot.summary import org.utbot.framework.plugin.api.Step import org.utbot.framework.plugin.api.UtConcreteExecutionFailure +import org.utbot.framework.plugin.api.UtConcreteExecutionProcessedFailure import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtExecutionResult import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.framework.plugin.api.UtExplicitlyThrownException import org.utbot.framework.plugin.api.UtImplicitlyThrownException +import org.utbot.framework.plugin.api.UtMethodTestSet import org.utbot.framework.plugin.api.UtOverflowFailure -import org.utbot.framework.plugin.api.UtTestCase +import org.utbot.framework.plugin.api.UtSandboxFailure +import org.utbot.framework.plugin.api.UtStreamConsumingFailure +import org.utbot.framework.plugin.api.UtSymbolicExecution import org.utbot.framework.plugin.api.UtTimeoutException -import org.utbot.summary.UtSummarySettings.MIN_NUMBER_OF_EXECUTIONS_FOR_CLUSTERING +import org.utbot.framework.plugin.api.UtTaintAnalysisFailure +import org.utbot.framework.plugin.api.util.humanReadableName +import org.utbot.framework.plugin.api.util.isCheckedException +import org.utbot.fuzzer.UtFuzzedExecution +import org.utbot.summary.DBSCANClusteringConstants.MIN_NUMBER_OF_EXECUTIONS_FOR_CLUSTERING import org.utbot.summary.clustering.MatrixUniqueness import org.utbot.summary.clustering.SplitSteps import org.utbot.summary.tag.TraceTag import org.utbot.summary.tag.TraceTagWithoutExecution class TagGenerator { - fun testCaseToTags(testCase: UtTestCase): List { - val clusteredExecutions = toClusterExecutions(testCase) + fun testSetToTags(testSet: UtMethodTestSet): List { + val clusteredExecutions = toClusterExecutions(testSet) val traceTagClusters = mutableListOf() val numberOfSuccessfulClusters = clusteredExecutions.filterIsInstance().size if (clusteredExecutions.isNotEmpty()) { val listOfSplitSteps = clusteredExecutions.map { - val mUniqueness = MatrixUniqueness(it.executions) + val mUniqueness = MatrixUniqueness(it.executions as List) mUniqueness.splitSteps() } @@ -35,7 +43,7 @@ class TagGenerator { // we only want to find intersections if there is more than one successful execution if (numberOfSuccessfulClusters > 1 && REMOVE_INTERSECTIONS) { val commonStepsInSuccessfulEx = listOfSplitSteps - .filterIndexed { i, _ -> clusteredExecutions[i] is SuccessfulExecutionCluster } //search only in successful + .filterIndexed { i, _ -> clusteredExecutions[i] is SuccessfulExecutionCluster } // search only in successful .map { it.commonSteps } .filter { it.isNotEmpty() } if (commonStepsInSuccessfulEx.size > 1) { @@ -60,7 +68,7 @@ class TagGenerator { traceTagClusters.add( TraceTagCluster( cluster.header, - generateExecutionTags(cluster.executions, splitSteps), + generateExecutionTags(cluster.executions as List, splitSteps), TraceTagWithoutExecution( commonStepsInCluster.toList(), cluster.executions.first().result, @@ -79,84 +87,157 @@ class TagGenerator { /** * @return list of TraceTag created from executions and splitsSteps */ -private fun generateExecutionTags(executions: List, splitSteps: SplitSteps): List = +private fun generateExecutionTags(executions: List, splitSteps: SplitSteps): List = executions.map { TraceTag(it, splitSteps) } + /** - * Splits executions into clusters - * By default there is 5 types of clusters: - * Success, UnexpectedFail, ExpectedCheckedThrow, ExpectedUncheckedThrow, UnexpectedUncheckedThrow - * These are split by the type of execution result + * Splits executions with empty paths into clusters. * - * If Success cluster has more than MIN_NUMBER_OF_EXECUTIONS_FOR_CLUSTERING execution - * then clustering algorithm splits those into more clusters + * @return clustered executions. + */ +fun groupExecutionsWithEmptyPaths(testSet: UtMethodTestSet): List { + val methodExecutions = testSet.executions.filterIsInstance() + val clusters = mutableListOf() + val commentPrefix = "OTHER:" + val commentPostfix = "for method ${testSet.method.humanReadableName}" + + val grouped = methodExecutions.groupBy { it.result.clusterKind() } + + val successfulExecutions = grouped[ExecutionGroup.SUCCESSFUL_EXECUTIONS] ?: emptyList() + if (successfulExecutions.isNotEmpty()) { + clusters += SuccessfulExecutionCluster( + "$commentPrefix ${ExecutionGroup.SUCCESSFUL_EXECUTIONS.displayName} $commentPostfix", + successfulExecutions.toList() + ) + } + + clusters += addClustersOfFailedExecutions(grouped, commentPrefix, commentPostfix) + return clusters +} + +/** + * Splits fuzzed executions into clusters. * - * @return clustered executions + * @return clustered executions. */ -private fun toClusterExecutions(testCase: UtTestCase): List { - val methodExecutions = testCase.executions.filter { it.path.isNotEmpty() } // TODO: Now it excludes tests generated by Fuzzer, handle it properly, related to the https://github.com/UnitTestBot/UTBotJava/issues/428 +fun groupFuzzedExecutions(testSet: UtMethodTestSet): List { + val methodExecutions = testSet.executions.filterIsInstance() val clusters = mutableListOf() - val commentPostfix = "for method ${testCase.method.displayName}" + val commentPrefix = "FUZZER:" + val commentPostfix = "for method ${testSet.method.humanReadableName}" val grouped = methodExecutions.groupBy { it.result.clusterKind() } - val successfulExecutions = grouped[ClusterKind.SUCCESSFUL_EXECUTIONS] ?: emptyList() + val successfulExecutions = grouped[ExecutionGroup.SUCCESSFUL_EXECUTIONS] ?: emptyList() + if (successfulExecutions.isNotEmpty()) { + clusters += SuccessfulExecutionCluster( + "$commentPrefix ${ExecutionGroup.SUCCESSFUL_EXECUTIONS.displayName} $commentPostfix", + successfulExecutions.toList() + ) + } + + clusters += addClustersOfFailedExecutions(grouped, commentPrefix, commentPostfix) + return clusters +} + +/** + * Splits symbolic executions into clusters. + * + * If Success cluster has more than [MIN_NUMBER_OF_EXECUTIONS_FOR_CLUSTERING] execution + * then clustering algorithm splits those into more clusters. + * + * @return clustered executions. + */ +private fun toClusterExecutions(testSet: UtMethodTestSet): List { + val methodExecutions = testSet.executions.filterIsInstance() + val clusters = mutableListOf() + val commentPrefix = "SYMBOLIC EXECUTION:" + val commentPostfix = "for method ${testSet.method.humanReadableName}" + + val grouped = methodExecutions.groupBy { it.result.clusterKind() } + + val successfulExecutions = grouped[ExecutionGroup.SUCCESSFUL_EXECUTIONS] ?: emptyList() if (successfulExecutions.isNotEmpty()) { val clustered = if (successfulExecutions.size >= MIN_NUMBER_OF_EXECUTIONS_FOR_CLUSTERING) { - MatrixUniqueness.dbscanClusterExecutions(successfulExecutions) + MatrixUniqueness.dbscanClusterExecutions(successfulExecutions) // TODO: only successful? } else emptyMap() if (clustered.size > 1) { for (c in clustered) { clusters += SuccessfulExecutionCluster( - "${ClusterKind.SUCCESSFUL_EXECUTIONS.displayName} #${clustered.keys.indexOf(c.key)} $commentPostfix", + "$commentPrefix ${ExecutionGroup.SUCCESSFUL_EXECUTIONS.displayName} #${clustered.keys.indexOf(c.key)} $commentPostfix", c.value.toList() ) } } else { clusters += SuccessfulExecutionCluster( - "${ClusterKind.SUCCESSFUL_EXECUTIONS.displayName} $commentPostfix", + "$commentPrefix ${ExecutionGroup.SUCCESSFUL_EXECUTIONS.displayName} $commentPostfix", successfulExecutions.toList() ) } } - clusters += grouped - .filterNot { (kind, _) -> kind == ClusterKind.SUCCESSFUL_EXECUTIONS } + clusters += addClustersOfFailedExecutions(grouped, commentPrefix, commentPostfix) + return clusters +} + +private fun addClustersOfFailedExecutions( + grouped: Map>, + commentPrefix: String, + commentPostfix: String +): List { + val clusters = grouped + .filterNot { (kind, _) -> kind == ExecutionGroup.SUCCESSFUL_EXECUTIONS } .map { (suffixId, group) -> - FailedExecutionCluster("${suffixId.displayName} $commentPostfix", group) - } + FailedExecutionCluster("$commentPrefix ${suffixId.displayName} $commentPostfix", group) + } + return clusters } -enum class ClusterKind { +/** The group of execution to be presented in the generated source file with tests. */ +enum class ExecutionGroup { SUCCESSFUL_EXECUTIONS, ERROR_SUITE, CHECKED_EXCEPTIONS, EXPLICITLY_THROWN_UNCHECKED_EXCEPTIONS, OVERFLOWS, TIMEOUTS, - CRASH_SUITE; + + /** + * Executions that caused by `InstrumentedProcessDeath` exception. + * Generated tests will be disabled du to possible JVM crash. + */ + CRASH_SUITE, + + TAINT_ANALYSIS, + SECURITY; val displayName: String get() = toString().replace('_', ' ') } private fun UtExecutionResult.clusterKind() = when (this) { - is UtExecutionSuccess -> ClusterKind.SUCCESSFUL_EXECUTIONS - is UtImplicitlyThrownException -> if (this.isCheckedException) ClusterKind.CHECKED_EXCEPTIONS else ClusterKind.ERROR_SUITE - is UtExplicitlyThrownException -> if (this.isCheckedException) ClusterKind.CHECKED_EXCEPTIONS else ClusterKind.EXPLICITLY_THROWN_UNCHECKED_EXCEPTIONS - is UtOverflowFailure -> ClusterKind.OVERFLOWS - is UtTimeoutException -> ClusterKind.TIMEOUTS - is UtConcreteExecutionFailure -> ClusterKind.CRASH_SUITE + is UtExecutionSuccess -> ExecutionGroup.SUCCESSFUL_EXECUTIONS + is UtImplicitlyThrownException -> if (this.exception.isCheckedException) ExecutionGroup.CHECKED_EXCEPTIONS else ExecutionGroup.ERROR_SUITE + is UtExplicitlyThrownException -> if (this.exception.isCheckedException) ExecutionGroup.CHECKED_EXCEPTIONS else ExecutionGroup.EXPLICITLY_THROWN_UNCHECKED_EXCEPTIONS + is UtStreamConsumingFailure -> ExecutionGroup.ERROR_SUITE + is UtOverflowFailure -> ExecutionGroup.OVERFLOWS + is UtTimeoutException -> ExecutionGroup.TIMEOUTS + is UtConcreteExecutionFailure -> ExecutionGroup.CRASH_SUITE + is UtSandboxFailure -> ExecutionGroup.SECURITY + is UtTaintAnalysisFailure -> ExecutionGroup.TAINT_ANALYSIS + is UtConcreteExecutionProcessedFailure -> + error("Processed failure must not be found in generated tests, it can just happen on intermediate phases of tests generation") } /** * Structure used to represent execution cluster with header */ -private sealed class ExecutionCluster(var header: String, val executions: List) +sealed class ExecutionCluster(var header: String, val executions: List) /** * Represents successful execution cluster @@ -181,7 +262,7 @@ private const val REMOVE_INTERSECTIONS: Boolean = true * Contains the entities required for summarization */ data class TraceTagCluster( - var summary: String, + var clusterHeader: String, val traceTags: List, val commonStepsTraceTag: TraceTagWithoutExecution, val isSuccessful: Boolean diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/UtSummarySettings.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/UtSummarySettings.kt deleted file mode 100644 index 65a607c861..0000000000 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/UtSummarySettings.kt +++ /dev/null @@ -1,67 +0,0 @@ -package org.utbot.summary - -object UtSummarySettings { - /** - * If True test comments will be generated - */ - var GENERATE_COMMENTS = true - - /** - * If True cluster comments will be generated - */ - var GENERATE_CLUSTER_COMMENTS = true - - /** - * If True names for tests will be generated - */ - var GENERATE_NAMES = true - - /** - * If True display names for tests will be generated - */ - var GENERATE_DISPLAY_NAMES = true - - /** - * generate display name in from -> to style - */ - var GENERATE_DISPLAYNAME_FROM_TO_STYLE = true - - /** - * If True mutation descriptions for tests will be generated - * TODO: implement - */ - var GENERATE_MUTATION_DESCRIPTIONS = true - - /** - * Sets minimum number of successful execution - * for applying the clustering algorithm - */ - const val MIN_NUMBER_OF_EXECUTIONS_FOR_CLUSTERING: Int = 4 - - /** - * DBSCAN hyperparameter - * Sets minimum number of executions to form a cluster - */ - var MIN_EXEC_DBSCAN: Int = 2 - - /** - * DBSCAN hyperparameter - * Sets radius of search for algorithm - */ - var RADIUS_DBSCAN: Double = 5.0 -} - -object SummarySentenceConstants { - const val SENTENCE_SEPARATION = ",\n" - const val TAB = " " - const val NEW_LINE = "\n" - const val DOT_SYMBOL = '.' - const val COMMA_SYMBOL = ',' - const val SEMI_COLON_SYMBOL = ';' - const val CARRIAGE_RETURN = "\r" - - const val FROM_TO_NAMES_TRANSITION = "->" - const val AT_CODE = "@code" - const val OPEN_BRACKET = "{" - const val CLOSE_BRACKET = "}" -} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/Util.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/Util.kt index ef5720412d..b5f41607e5 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/Util.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/Util.kt @@ -1,16 +1,9 @@ package org.utbot.summary import org.utbot.framework.plugin.api.Step -import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtMethod +import org.utbot.framework.plugin.api.UtSymbolicExecution import org.utbot.summary.tag.BasicTypeTag import org.utbot.summary.tag.getBasicTypeTag -import java.lang.reflect.Constructor -import java.lang.reflect.Method -import kotlin.reflect.KFunction -import kotlin.reflect.KProperty -import kotlin.reflect.jvm.javaConstructor -import kotlin.reflect.jvm.javaMethod import soot.SootClass import soot.SootMethod import soot.jimple.JimpleBody @@ -36,7 +29,7 @@ fun List.invokeJimpleMethods(): List = it.invokeExpr.method } -fun stepsUpToDepth(executions: List, depth: Int) { +fun stepsUpToDepth(executions: List, depth: Int) { for (execution in executions) { execution.path.clear() execution.path.addAll(execution.fullPath.filter { it.depth <= depth }) @@ -46,13 +39,13 @@ fun stepsUpToDepth(executions: List, depth: Int) { /* * from 0 to 100 * */ -fun percentageDiverseExecutions(executions: List): Int { +fun percentageDiverseExecutions(executions: List): Int { if (executions.isEmpty()) return 100 val diverseExecutions = numberDiverseExecutionsBasedOnPaths(executions) return 100 * diverseExecutions.size / executions.size } -fun numberDiverseExecutionsBasedOnPaths(executions: List) = executions.filter { current -> +fun numberDiverseExecutionsBasedOnPaths(executions: List) = executions.filter { current -> executions.filter { it != current }.any { other -> current.path.isEqualPath(other.path) }.not() @@ -70,21 +63,4 @@ fun SootClass.adjustLevel(level: Int) { fun SootMethod.jimpleBody(): JimpleBody { declaringClass.adjustLevel(SootClass.BODIES) return retrieveActiveBody() as JimpleBody -} - -val UtMethod.javaConstructor: Constructor<*>? - get() = (callable as? KFunction<*>)?.javaConstructor - -val UtMethod.javaMethod: Method? - get() = (callable as? KFunction<*>)?.javaMethod ?: (callable as? KProperty<*>)?.getter?.javaMethod - -val UtMethod.displayName: String - get() { - val methodName = this.callable.name - val javaMethod = this.javaMethod ?: this.javaConstructor - if (javaMethod != null) { - val parameters = javaMethod.parameters.joinToString(separator = ", ") { it.type.canonicalName } - return "${methodName}($parameters)" - } - return "${methodName}()" - } \ No newline at end of file +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/ast/JimpleToASTMap.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/ast/JimpleToASTMap.kt index 13b0ed1673..5ccb1440bf 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/ast/JimpleToASTMap.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/ast/JimpleToASTMap.kt @@ -25,12 +25,14 @@ import com.github.javaparser.ast.stmt.Statement import com.github.javaparser.ast.stmt.SwitchEntry import com.github.javaparser.ast.stmt.SwitchStmt import com.github.javaparser.ast.stmt.WhileStmt +import org.utbot.framework.plugin.api.Step import org.utbot.summary.comment.isLoopStatement import java.util.LinkedList import java.util.Queue +import java.util.stream.Collectors +import java.util.stream.Stream import kotlin.Int.Companion.MAX_VALUE import kotlin.math.abs -import kotlin.streams.toList import soot.Unit import soot.Value import soot.jimple.internal.JCaughtExceptionRef @@ -75,11 +77,16 @@ class JimpleToASTMap(stmts: Iterable, methodDeclaration: MethodDeclaration alignNonAlignedReturns() } + /** + * Avoid conflict with java.util.stream.Stream.toList (available since Java 16 only) + */ + private fun Stream.asList(): List = collect(Collectors.toList()) + /** * Function that maps statements inside of ternary conditions to correct AST nodes */ private fun mapTernaryConditional(methodDeclaration: MethodDeclaration, stmts: Iterable) { - for (condExpr in methodDeclaration.stream().toList().filterIsInstance()) { + for (condExpr in methodDeclaration.stream().asList().filterIsInstance()) { val begin = condExpr.begin.orElse(null) val end = condExpr.end.orElse(null) if (begin == null || end == null) continue @@ -122,7 +129,7 @@ class JimpleToASTMap(stmts: Iterable, methodDeclaration: MethodDeclaration * Node is valid if there is only one return statement inside of it */ private fun validateReturnASTNode(returnNode: Node): Node { - val returns = returnNode.stream().filter { it is ReturnStmt }.toList() + val returns = returnNode.stream().filter { it is ReturnStmt }.asList() if (returns.size == 1) return returns[0] return returnNode } @@ -146,17 +153,17 @@ class JimpleToASTMap(stmts: Iterable, methodDeclaration: MethodDeclaration val loopList = mutableListOf() when (loop) { is ForStmt -> { - loopList.addAll(loop.initialization.stream().toList()) - val compare = loop.compare.orElse(null)?.stream()?.toList() + loopList.addAll(loop.initialization.stream().asList()) + val compare = loop.compare.orElse(null)?.stream()?.asList() if (compare != null) loopList.addAll(compare) - loopList.addAll(loop.update.flatMap { it.stream().toList() }) + loopList.addAll(loop.update.flatMap { it.stream().asList() }) } is WhileStmt -> { - loopList.addAll(loop.condition.stream().toList()) + loopList.addAll(loop.condition.stream().asList()) } is ForEachStmt -> { - loopList.addAll(loop.iterable.stream().toList()) - loopList.addAll(loop.variable.stream().toList()) + loopList.addAll(loop.iterable.stream().asList()) + loopList.addAll(loop.variable.stream().asList()) } } for (stmt in stmtToASTNode.filter { it.value in loopList }.map { it.key }) stmtToASTNode[stmt] = loop @@ -167,7 +174,7 @@ class JimpleToASTMap(stmts: Iterable, methodDeclaration: MethodDeclaration val stmts = stmtToASTNode.keys.toTypedArray() for (index in stmts.indices) { val stmt = stmts[index] - if (stmt is JIdentityStmt && stmt.rightBox.value is JCaughtExceptionRef) { + if (stmt is JIdentityStmt && stmt.rightOp is JCaughtExceptionRef) { if (index + 1 < stmts.size) { val nextASTNode = stmtToASTNode[stmts[index + 1]] if (nextASTNode != null) { @@ -188,9 +195,9 @@ class JimpleToASTMap(stmts: Iterable, methodDeclaration: MethodDeclaration val stmts = stmtToASTNode.keys.toTypedArray() for (nonAlignedReturn in nonAlignedReturns) { val index = stmts.indexOf(nonAlignedReturn) - if (index - 1 >= 0 && stmts[index - 1] is JIfStmt) { - stmtToASTNode[nonAlignedReturn] = stmtToASTNode[stmts[index - 1]] - } + val ternaryIfStmtIndex = stmts.indexOfLast { it is JIfStmt && stmts.indexOf(it) <= index } + + stmtToASTNode[nonAlignedReturn] = stmtToASTNode[stmts[ternaryIfStmtIndex]] } } @@ -339,12 +346,14 @@ class JimpleToASTMap(stmts: Iterable, methodDeclaration: MethodDeclaration } /** - * @return SwitchEntry №decision + * @return SwitchEntry */ - fun mapSwitchCase(switchStmt: SwitchStmt, decision: Int): SwitchEntry? { - val switchStmts = switchStmt.childNodes.filterIsInstance() - return if (decision < switchStmts.size) switchStmts[decision] - else null + fun mapSwitchCase(switchStmt: SwitchStmt, step: Step): SwitchEntry? { + val neededLine = step.stmt.unitBoxes[step.decision].unit.javaSourceStartLineNumber + return switchStmt + .childNodes + .filterIsInstance() + .find { neededLine in (it.range.get().begin.line..it.range.get().end.line) } } /** diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/ast/SourceCodeParser.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/ast/SourceCodeParser.kt index 89f1407b21..8fa3f041e5 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/ast/SourceCodeParser.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/ast/SourceCodeParser.kt @@ -7,9 +7,8 @@ import com.github.javaparser.ast.Node import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration import com.github.javaparser.ast.body.MethodDeclaration import com.github.javaparser.ast.body.TypeDeclaration -import org.utbot.framework.plugin.api.UtTestCase +import org.utbot.framework.plugin.api.UtMethodTestSet import java.io.File -import java.nio.file.Path import kotlin.math.abs import soot.SootMethod @@ -22,13 +21,13 @@ class SourceCodeParser { private val cu: ParseResult var methodAST: MethodDeclaration? = null - constructor(sourceFile: File, testCase: UtTestCase) { + constructor(sourceFile: File, testSet: UtMethodTestSet) { val parser = JavaParser() cu = parser.parse(sourceFile) - val className = testCase.method.clazz.simpleName - val methodName = testCase.method.callable.name + val className = testSet.method.classId.simpleName + val methodName = testSet.method.name - val lineNumbers = testCase.jimpleBody?.units?.map { it.javaSourceStartLineNumber } + val lineNumbers = testSet.jimpleBody?.units?.map { it.javaSourceStartLineNumber } val maxLineNumber = lineNumbers?.maxOrNull() if (className != null && maxLineNumber != null) findMethod(className, methodName, maxLineNumber) } @@ -37,7 +36,6 @@ class SourceCodeParser { val methodName = sootMethod.name val className = sootMethod.declaredClassName - val maxLineNumber = if (sootMethod.hasActiveBody()) sootMethod.retrieveActiveBody()?.units?.maxOfOrNull { it.javaSourceStartLineNumber } @@ -57,7 +55,7 @@ class SourceCodeParser { val clazz = it.types.firstOrNull { clazz -> clazz.name.identifier == className } ?: traverseInnerClassDeclarations( - it.types.flatMap { declaration -> declaration.childNodes }, + it.types.flatMap { declaration -> declaration.childNodes }.filterIsInstance(), className ) @@ -86,9 +84,23 @@ class SourceCodeParser { * Identifier is ClassOrInterfaceDeclaration */ private fun traverseInnerClassDeclarations( - nodes: List, className: String - ): TypeDeclaration<*>? = nodes.filterIsInstance() - .firstOrNull { it.name.identifier == className } + nodes: List, className: String + ): TypeDeclaration<*>? { + var result = nodes.firstOrNull { it.name.identifier == className } + + if (result != null) return result + + run childrenSearch@ { + nodes.forEach { + val childNodes = it.childNodes.filterIsInstance() + if (childNodes.isNotEmpty()) { + result = traverseInnerClassDeclarations(childNodes, className) as ClassOrInterfaceDeclaration? + if (result!= null) return@childrenSearch + } + } + } + return result + } } /** diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/ExecutionDistance.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/ExecutionDistance.kt deleted file mode 100644 index c1b2e79ea4..0000000000 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/ExecutionDistance.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.utbot.summary.clustering - -import org.utbot.framework.plugin.api.Step -import smile.math.distance.Distance - -class ExecutionDistance : Distance> { - override fun d(x: Iterable, y: Iterable): Double { - return compareTwoPaths(x, y) - } - - /** - * Minimum Edit Distance - */ - private fun compareTwoPaths(path1: Iterable, path2: Iterable): Double { - val distances = Array(path1.count()) { i -> Array(path2.count()) { j -> i + j } } - - for (i in 1 until path1.count()) { - for (j in 1 until path2.count()) { - val stmt1 = path1.elementAt(i) - val stmt2 = path2.elementAt(j) - - val d1 = distances[i - 1][j] + 1 //path 1 insert -> diff stmt from path2 - val d2 = distances[i][j - 1] + 1 // path 2 insert -> diff stmt from path1 - val d3 = distances[i - 1][j - 1] + distance(stmt1, stmt2) // aligned or diff - distances[i][j] = minOf(d1, d2, d3) - } - } - return distances.last().last().toDouble() - } - - private fun distance(stmt1: Step, stmt2: Step): Int { - return if (stmt1 == stmt2) 0 else 2 - } -} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/ExecutionMetric.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/ExecutionMetric.kt new file mode 100644 index 0000000000..44043d9136 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/ExecutionMetric.kt @@ -0,0 +1,48 @@ +package org.utbot.summary.clustering + +import org.utbot.framework.plugin.api.Step +import org.utbot.summary.clustering.dbscan.Metric + +/** The existing implementation of [Metric] for the space of [Step]. */ +class ExecutionMetric : Metric> { + /** + * Minimum Edit Distance + */ + private fun compareTwoPaths(path1: Iterable, path2: Iterable): Double { + require(path1.count() > 0) { "Two paths can not be compared: path1 is empty!"} + require(path2.count() > 0) { "Two paths can not be compared: path2 is empty!"} + + val distances = Array(path1.count()) { i -> Array(path2.count()) { j -> i + j } } + + for (i in 1 until path1.count()) { + for (j in 1 until path2.count()) { + val stmt1 = path1.elementAt(i) + val stmt2 = path2.elementAt(j) + + val d1 = distances[i - 1][j] + 1 // path 1 insert -> diff stmt from path2 + val d2 = distances[i][j - 1] + 1 // path 2 insert -> diff stmt from path1 + val d3 = distances[i - 1][j - 1] + distance(stmt1, stmt2) // aligned or diff + distances[i][j] = minOf(d1, d2, d3) + } + } + + if (distances.isNotEmpty()) { + val last = distances.last() + if (last.isNotEmpty()) { + return last.last().toDouble() + } else { + throw IllegalStateException("Last row in the distance matrix has 0 columns. It should contain more or equal 1 column.") + } + } else { + throw IllegalStateException("Distance matrix has 0 rows. It should contain more or equal 1 row.") + } + } + + private fun distance(stmt1: Step, stmt2: Step): Int { + return if (stmt1 == stmt2) 0 else 2 + } + + override fun compute(object1: Iterable, object2: Iterable): Double { + return compareTwoPaths(object1, object2) + } +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/MatrixUniqueness.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/MatrixUniqueness.kt index c67d9d0f55..f2977f184d 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/MatrixUniqueness.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/MatrixUniqueness.kt @@ -1,21 +1,18 @@ package org.utbot.summary.clustering import org.utbot.framework.plugin.api.Step -import org.utbot.framework.plugin.api.UtExecution -import org.utbot.framework.plugin.api.UtTestCase -import org.utbot.summary.UtSummarySettings -import smile.clustering.dbscan +import org.utbot.framework.plugin.api.UtSymbolicExecution +import org.utbot.summary.DBSCANClusteringConstants +import org.utbot.summary.clustering.dbscan.DBSCANTrainer +import org.utbot.summary.clustering.dbscan.neighbor.LinearRangeQuery -class MatrixUniqueness { +class MatrixUniqueness(executions: List) { - private var methodExecutions: List + private var methodExecutions: List = executions private val allSteps = mutableListOf() private val matrix: List - constructor(testCase: UtTestCase) : this(testCase.executions) - - constructor (executions: List) { - this.methodExecutions = executions + init { executions.forEach { it.path.forEach { step -> if (step !in allSteps) allSteps.add(step) @@ -25,7 +22,10 @@ class MatrixUniqueness { } /** - * Creates uniquness matrix. Rows are executions, columns are unique steps from all executions + * Creates uniqueness matrix. + * + * Rows are executions, columns are unique steps from all executions + * * Every matrix i,j is 1 or 0, as if step in execution or not. */ private fun createMatrix(): List { @@ -53,10 +53,10 @@ class MatrixUniqueness { private fun colSums(matrix: List) = matrix.first().indices.map { col -> this.colSum(matrix, col) } /** - * Splits all steps into common, partly common and unique + * Splits all steps into common, partly common and unique. * - * Unique steps are steps that only occur in one execution - * Common steps are steps that occur in all executions + * Unique steps are steps that only occur in one execution. + * Common steps are steps that occur in all executions. * Partly common steps are steps that occur more than one time, but not in all executions */ fun splitSteps(): SplitSteps { @@ -78,19 +78,24 @@ class MatrixUniqueness { } companion object { - /** - * Returns map: cluster identifier, List - * DBSCAN - Density-Based Spatial Clustering of Applications with Noise - * Finds core samples of high density and expands clusters from them - */ + /** Returns map: cluster identifier, List. */ fun dbscanClusterExecutions( - methodExecutions: List, - minPts: Int = UtSummarySettings.MIN_EXEC_DBSCAN, - radius: Double = UtSummarySettings.RADIUS_DBSCAN - ): Map> { + methodExecutions: List, + minPts: Int = DBSCANClusteringConstants.MIN_EXEC_DBSCAN, + radius: Float = DBSCANClusteringConstants.RADIUS_DBSCAN + ): Map> { + val executionPaths = methodExecutions.map { it.path.asIterable() }.toTypedArray() - val cluster = dbscan(executionPaths, ExecutionDistance(), minPts, radius) - return methodExecutions.withIndex().groupBy({ cluster.y[it.index] }, { it.value }) + + val dbscan = DBSCANTrainer( + eps = radius, + minSamples = minPts, + metric = ExecutionMetric(), + rangeQuery = LinearRangeQuery() + ) + val dbscanModel = dbscan.fit(executionPaths) + val clusterLabels = dbscanModel.clusterLabels + return methodExecutions.withIndex().groupBy({ clusterLabels[it.index] }, { it.value }) } } } diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/DBSCANModel.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/DBSCANModel.kt new file mode 100644 index 0000000000..d514f8c426 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/DBSCANModel.kt @@ -0,0 +1,13 @@ +package org.utbot.summary.clustering.dbscan + +/** + * Keeps the information about clusters produced by [DBSCANTrainer]. + * + * @property [numberOfClusters] Number of clusters. + * @property [clusterLabels] It contains labels of clusters in the range ```[0; k)``` + * or [Int.MIN_VALUE] if point could not be assigned to any cluster. + */ +data class DBSCANModel( + val numberOfClusters: Int = 0, + val clusterLabels: IntArray +) \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/DBSCANTrainer.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/DBSCANTrainer.kt new file mode 100644 index 0000000000..93308f25a9 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/DBSCANTrainer.kt @@ -0,0 +1,118 @@ +package org.utbot.summary.clustering.dbscan + +import org.utbot.summary.clustering.dbscan.neighbor.LinearRangeQuery +import org.utbot.summary.clustering.dbscan.neighbor.Neighbor +import org.utbot.summary.clustering.dbscan.neighbor.RangeQuery + +private const val NOISE = Int.MIN_VALUE +private const val CLUSTER_PART = -2 +private const val UNDEFINED = -1 + +/** + * DBSCAN algorithm implementation. + * + * NOTE: The existing implementation with the [LinearRangeQuery] has a complexity O(n^2) in the worst case. + * + * @property [eps] The radius of search. Should be more than 0.0. + * @property [minSamples] The minimum number of samples to form the cluster. Should be more than 0. + * @property [metric] Metric to calculate distances. + * @property [rangeQuery] Gives access to the data in the implemented order. + * + * @see + * A Density-Based Algorithm for Discovering Clusters in Large Spatial Databases with Noise + */ +class DBSCANTrainer(val eps: Float, val minSamples: Int, val metric: Metric, val rangeQuery: RangeQuery) { + init { + require(minSamples > 0) { "MinSamples parameter should be more than 0: $minSamples" } + require(eps > 0.0f) { "Eps parameter should be more than 0: $eps" } + } + + /** Builds a clustering model based on the given data. */ + fun fit(data: Array): DBSCANModel { + require(data.isNotEmpty()) { "Nothing to learn, data is empty." } + + if (rangeQuery is LinearRangeQuery) { + rangeQuery.data = data + rangeQuery.metric = metric + } // TODO: could be refactored if we add some new variants of RangeQuery + + val labels = IntArray(data.size) { UNDEFINED } + + // It changes in the range [0; k), where k is a final number of clusters found by DBSCAN + var clusterLabel = 0 + + for (i in data.indices) { + if (labels[i] == UNDEFINED) { + val neighbors = rangeQuery.findNeighbors(data[i], eps).toMutableList() + if (neighbors.size < minSamples) { + labels[i] = NOISE + } else { + labels[i] = clusterLabel + expandCluster(neighbors, labels, clusterLabel) + + // If the existing cluster can not be expanded, the cluster label is incremented. + clusterLabel++ + } + } + } + + return DBSCANModel(numberOfClusters = clusterLabel, clusterLabels = labels) + } + + private fun expandCluster( + neighbors: MutableList>, + labels: IntArray, + k: Int + ) { + // Neighbors to expand. + neighbors.forEach { + if (labels[it.index] == UNDEFINED) { + // All neighbors of a cluster point became cluster points. + labels[it.index] = CLUSTER_PART + } + } + + // NOTE: the size of neighbors could grow from iteration to iteration and the classical for-loop in Kotlin could not be used + var j = 0 + + // Process every seed point Q. + while (j < neighbors.count()) + { + val q = neighbors[j] + val idx = q.index + + // Change Noise to border point. + if (labels[idx] == NOISE) { + labels[idx] = k + } + + if (labels[idx] == UNDEFINED || labels[idx] == CLUSTER_PART) { + labels[idx] = k + + val qNeighbors = rangeQuery.findNeighbors(q.key, eps) + + if (qNeighbors.size >= minSamples) { + mergeTwoGroupsInCluster(qNeighbors, labels, neighbors) + } + } + j++ + } + } + + private fun mergeTwoGroupsInCluster( + qNeighbors: List>, + labels: IntArray, + neighbors: MutableList> + ) { + for (qNeighbor in qNeighbors) { + val label = labels[qNeighbor.index] + if (label == UNDEFINED) { + labels[qNeighbor.index] = CLUSTER_PART + } + + if (label == UNDEFINED || label == NOISE) { + neighbors.add(qNeighbor) + } + } + } +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/Metric.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/Metric.kt new file mode 100644 index 0000000000..0115619ac7 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/Metric.kt @@ -0,0 +1,6 @@ +package org.utbot.summary.clustering.dbscan + +interface Metric { + /** Computes the distance between [object1] and [object2] according the given metric. */ + fun compute(object1: T, object2: T): Double +} diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/neighbor/LinearRangeQuery.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/neighbor/LinearRangeQuery.kt new file mode 100644 index 0000000000..a37bcccd9d --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/neighbor/LinearRangeQuery.kt @@ -0,0 +1,26 @@ +package org.utbot.summary.clustering.dbscan.neighbor + +import org.utbot.summary.clustering.dbscan.Metric + +/** + * This approach implements brute-force search with complexity O(n). + * + * @property [data] The whole dataset to search in it. + * @property [metric] Metric. + */ +class LinearRangeQuery : RangeQuery { + lateinit var data: Array + lateinit var metric: Metric + + override fun findNeighbors(queryKey: K, radius: Float): List> { + val neighbors = mutableListOf>() + data.forEachIndexed { index, point -> + val distance = metric.compute(queryKey, point) + if (distance <= radius && queryKey != point) { + neighbors.add(Neighbor(point, index, distance)) + } + } + + return neighbors + } +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/neighbor/Neighbor.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/neighbor/Neighbor.kt new file mode 100644 index 0000000000..54c32a4131 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/neighbor/Neighbor.kt @@ -0,0 +1,17 @@ +package org.utbot.summary.clustering.dbscan.neighbor + +/** + * Neighbor abstraction for algorithms with searching in metric space specialization. + * + * @property [key] Search key. + * @property [index] Direct index to access the point in the basic data structure that keeps a set of points. + * @property [distance] Numerical value that keeps distance from the [key] point in the chosen metric space. + * + * NOTE: Neighbors should be ordered and this is implemented via [Comparable] interface. + */ +class Neighbor(val key: K, val index: Int, private val distance: Double) : Comparable> { + override fun compareTo(other: Neighbor): Int { + val distance = distance.compareTo(other.distance) + return if (distance == 0) index.compareTo(other.index) else distance + } +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/neighbor/RangeQuery.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/neighbor/RangeQuery.kt new file mode 100644 index 0000000000..7b4adf91ba --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/clustering/dbscan/neighbor/RangeQuery.kt @@ -0,0 +1,7 @@ +package org.utbot.summary.clustering.dbscan.neighbor + +/** This is a basic interface for our approaches to ask the set of all points return the subset of the closest neighbors. */ +interface RangeQuery { + /** Returns the list of the closest neighbors in the [radius] from the [queryKey]. */ + fun findNeighbors(queryKey: K, radius: Float): List> +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/SentenceUtil.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/SentenceUtil.kt index 8057a5e74c..03061d0f80 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/SentenceUtil.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/SentenceUtil.kt @@ -1,6 +1,7 @@ package org.utbot.summary.comment import com.github.javaparser.ast.Node +import com.github.javaparser.ast.body.MethodDeclaration import com.github.javaparser.ast.stmt.BlockStmt import com.github.javaparser.ast.stmt.ForEachStmt import com.github.javaparser.ast.stmt.ForStmt @@ -14,6 +15,9 @@ import org.utbot.summary.SummarySentenceConstants.COMMA_SYMBOL import org.utbot.summary.SummarySentenceConstants.DOT_SYMBOL import org.utbot.summary.SummarySentenceConstants.NEW_LINE import org.utbot.summary.SummarySentenceConstants.TAB +import org.utbot.summary.comment.classic.symbolic.SquashedStmtTexts +import org.utbot.summary.comment.classic.symbolic.StmtType +import kotlin.jvm.optionals.getOrNull fun numberWithSuffix(number: Int) = when (number % 10) { 1 -> "${number}st" @@ -23,21 +27,41 @@ fun numberWithSuffix(number: Int) = when (number % 10) { } /** - * Returns reason for given ThrowStmt - * It can be parent ast node - * or grandparent node if parent is BlockStmt class, + * Returns a reason for the given ThrowStmt: a condition or ThrowStmt itself. + * + * ThrowStmt is returned when its grandparent is MethodDeclaration (method under test). + * E.g. + * `public void myMethod() throws RuntimeException { throw new RuntimeException("message") }` + * + * Keep in mind, for inner method that throws something the reason will be that inner method. */ -fun getExceptionReason(throwStmt: ThrowStmt): Node? { - val parent = throwStmt.parentNode.orElse(null) - if (parent != null) { - return if (parent is BlockStmt) { - parent.parentNode.orElse(null) - } else { - parent +private fun getExceptionReason(throwStmt: ThrowStmt): Node? = + throwStmt + .parentNode + .getOrNull() + ?.let { parent -> + if (parent is BlockStmt) { + val grandParent = parent.parentNode.getOrNull() + if (grandParent is MethodDeclaration) throwStmt + else grandParent + } else parent + } + +fun getExceptionReasonForComment(throwStmt: ThrowStmt): Node? = + getExceptionReason(throwStmt) + +fun getExceptionReasonForName(throwStmt: ThrowStmt): Node? = + getExceptionReason(throwStmt) + ?.let { + if (it is ThrowStmt) { + /** + * When the node is ThrowStmt it means that we have a deal with a case + * when the method under test just throws an exception with no conditions. + * So instead of rendering the whole method in display name we want to omit it at all. + */ + null + } else it } - } - return null -} fun numberOccurrencesToText(n: Int): String = when (n) { 0 -> "" @@ -89,14 +113,26 @@ val nextSynonyms = arrayOf( "then" ) -val skipInvokes = arrayOf( +val forbiddenMethodInvokes = arrayOf( "", + "", "valueOf", - "getClass" + "getClass", ) -// TODO: SAT-1589 -fun shouldSkipInvoke(invoke: String) = (invoke in skipInvokes) || (invoke.endsWith('$')) +val forbiddenClassInvokes = arrayOf( + "soot.dummy.InvokeDynamic" +) + +/** + * Filters out + * ```soot.dummy.InvokeDynamic#makeConcat```, + * ```soot.dummy.InvokeDynamic#makeConcatWithConstants```, + * constructor calls (``````), ```bootstrap$``` + * and other unwanted things from name and comment text. + */ +fun shouldSkipInvoke(className: String, methodName: String) = + className in forbiddenClassInvokes || methodName in forbiddenMethodInvokes || methodName.endsWith("$") fun squashDocRegularStatements(sentences: List): List { val newStatements = mutableListOf() diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/SimpleClusterCommentBuilder.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/SimpleClusterCommentBuilder.kt deleted file mode 100644 index 5079251acf..0000000000 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/SimpleClusterCommentBuilder.kt +++ /dev/null @@ -1,177 +0,0 @@ -package org.utbot.summary.comment - -import com.github.javaparser.ast.stmt.CatchClause -import com.github.javaparser.ast.stmt.ForStmt -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.DocStatement -import org.utbot.summary.SummarySentenceConstants.CARRIAGE_RETURN -import org.utbot.summary.ast.JimpleToASTMap -import org.utbot.summary.tag.BasicTypeTag -import org.utbot.summary.tag.CallOrderTag -import org.utbot.summary.tag.StatementTag -import org.utbot.summary.tag.TraceTagWithoutExecution -import org.utbot.summary.tag.UniquenessTag -import soot.SootMethod -import soot.jimple.internal.JAssignStmt -import soot.jimple.internal.JInvokeStmt -import soot.jimple.internal.JVirtualInvokeExpr - -/** - * Inherits from SimpleCommentBuilder - */ -class SimpleClusterCommentBuilder( - traceTag: TraceTagWithoutExecution, - sootToAST: MutableMap -) : SimpleCommentBuilder(traceTag, sootToAST, stringTemplates = StringsTemplatesPlural()) { - /** - * Builds cluster comment, ignores thrown exceptions - */ - override fun buildString(currentMethod: SootMethod): String { - val root = SimpleSentenceBlock(stringTemplates = StringsTemplatesPlural()) - - skippedIterations() - buildSentenceBlock(traceTag.rootStatementTag, root, currentMethod) - var sentence = toSentence(root) - if (sentence.isEmpty()) { - return genWarnNotification() - } - sentence = splitLongSentence(sentence) - sentence = lastCommaToDot(sentence) - - return "
    \n$sentence
    ".replace(CARRIAGE_RETURN, "") - } - - override fun buildDocStmts(currentMethod: SootMethod): List { - val root = SimpleSentenceBlock(stringTemplates = StringsTemplatesPlural()) - - skippedIterations() - buildSentenceBlock(traceTag.rootStatementTag, root, currentMethod) - val sentence = toDocStmts(root) - - if (sentence.isEmpty()) { - return listOf(DocRegularStmt(genWarnNotification())) //TODO SAT-1310 - } -// sentence = splitLongSentence(sentence) //TODO SAT-1309 -// sentence = lastCommaToDot(sentence) //TODO SAT-1309 - - val docStatements = toDocStmts(root) - return listOf(DocPreTagStatement(docStatements)) - } - - /** - * Builds sentence blocks as parent one, - * but ignores few types of statementTag that are considered in SimpleCommentBuilder - */ - private fun buildSentenceBlock( - statementTag: StatementTag?, - sentenceBlock: SimpleSentenceBlock, - currentMethod: SootMethod - ) { - val jimpleToASTMap = sootToAST[currentMethod] - if (statementTag == null) return - if (jimpleToASTMap == null) return - val recursion = statementTag.recursion - val stmt = statementTag.step.stmt - val invoke = statementTag.invoke - var createNextBlock = false - - val localNoIterations = statementTagSkippedIteration(statementTag, currentMethod) - if (localNoIterations.isNotEmpty()) { - sentenceBlock.notExecutedIterations = localNoIterations - methodToNoIterationDescription[currentMethod]?.removeAll(localNoIterations) - } - - val invokeSootMethod = statementTag.invokeSootMethod() - var invokeRegistered = false - if (invoke != null && invokeSootMethod != null) { - val className = invokeSootMethod.declaringClass.javaStyleName - val methodName = invokeSootMethod.name - val sentenceInvoke = SimpleSentenceBlock(stringTemplates = StringsTemplatesPlural()) - buildSentenceBlock(invoke, sentenceInvoke, invokeSootMethod) - sentenceInvoke.squashStmtText() - if (!sentenceInvoke.isEmpty()) { - sentenceBlock.invokeSentenceBlock = Pair(invokeDescription(className, methodName), sentenceInvoke) - createNextBlock = true - invokeRegistered = true - } - } - if (statementTag.basicTypeTag == BasicTypeTag.Invoke && statementTag.uniquenessTag == UniquenessTag.Unique && !invokeRegistered) { - if (statementTag.executionFrequency <= 1) { - addTextInvoke(sentenceBlock, stmt, statementTag.executionFrequency) - } - if (statementTag.executionFrequency > 1 && statementTag.callOrderTag == CallOrderTag.First) { - addTextInvoke(sentenceBlock, stmt, statementTag.executionFrequency) - } - } - - if (statementTag.basicTypeTag == BasicTypeTag.RecursionAssignment && statementTag.uniquenessTag == UniquenessTag.Unique && !invokeRegistered) { - if (statementTag.executionFrequency <= 1) { - addTextRecursion(sentenceBlock, stmt, statementTag.executionFrequency) - } - if (statementTag.executionFrequency > 1 && statementTag.callOrderTag == CallOrderTag.First) { - addTextRecursion(sentenceBlock, stmt, statementTag.executionFrequency) - } - } - - if (jimpleToASTMap[statementTag.step.stmt] !is ForStmt) { - - if (statementTag.basicTypeTag == BasicTypeTag.Condition && statementTag.callOrderTag == CallOrderTag.First) { - if (statementTag.uniquenessTag == UniquenessTag.Unique) { - val conditionText = textCondition(statementTag, jimpleToASTMap) - if (conditionText != null) { - sentenceBlock.stmtTexts.add(StmtDescription(StmtType.Condition, conditionText)) - } - } - } - - if (statementTag.basicTypeTag == BasicTypeTag.SwitchCase && statementTag.uniquenessTag == UniquenessTag.Unique) { - val switchCase = textSwitchCase(statementTag.step, jimpleToASTMap) - if (switchCase != null) { - sentenceBlock.stmtTexts.add(StmtDescription(StmtType.SwitchCase, switchCase)) - } - } - if (statementTag.basicTypeTag == BasicTypeTag.CaughtException && statementTag.uniquenessTag == UniquenessTag.Unique) { - jimpleToASTMap[stmt].let { - if (it is CatchClause) { - sentenceBlock.stmtTexts.add(StmtDescription(StmtType.CaughtException, it.parameter.toString())) - } - } - } - if (statementTag.basicTypeTag == BasicTypeTag.Return && statementTag.uniquenessTag == UniquenessTag.Unique) { - textReturn(statementTag, sentenceBlock, stmt, jimpleToASTMap) - } - } - - if (statementTag.iterations.isNotEmpty()) { - val iterationSentenceBlock = buildIterationsBlock(statementTag.iterations, statementTag.step, currentMethod) - sentenceBlock.iterationSentenceBlocks.add(iterationSentenceBlock) - createNextBlock = true - } - - if (recursion != null) { - if (stmt is JAssignStmt) { - val name = (stmt.rightBox.value as JVirtualInvokeExpr).method.name - val sentenceRecursionBlock = SimpleSentenceBlock(stringTemplates = StringsTemplatesPlural()) - buildSentenceBlock(recursion, sentenceRecursionBlock, currentMethod) - sentenceBlock.recursion = Pair(name, sentenceRecursionBlock) - createNextBlock = true - } - if (stmt is JInvokeStmt) { - val name = stmt.invokeExpr.method.name - val sentenceRecursion = SimpleSentenceBlock(stringTemplates = StringsTemplatesPlural()) - buildSentenceBlock(recursion, sentenceRecursion, currentMethod) - sentenceBlock.recursion = Pair(name, sentenceRecursion) - createNextBlock = true - } - } - - if (createNextBlock) { - val nextSentenceBlock = SimpleSentenceBlock(stringTemplates = StringsTemplatesPlural()) - sentenceBlock.nextBlock = nextSentenceBlock - buildSentenceBlock(statementTag.next, nextSentenceBlock, currentMethod) - } else { - buildSentenceBlock(statementTag.next, sentenceBlock, currentMethod) - } - } -} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/SimpleCommentBuilder.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/SimpleCommentBuilder.kt deleted file mode 100644 index 3a4a7622a0..0000000000 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/SimpleCommentBuilder.kt +++ /dev/null @@ -1,388 +0,0 @@ -package org.utbot.summary.comment - -import com.github.javaparser.ast.body.MethodDeclaration -import com.github.javaparser.ast.stmt.CatchClause -import com.github.javaparser.ast.stmt.ForStmt -import com.github.javaparser.ast.stmt.IfStmt -import com.github.javaparser.ast.stmt.Statement -import com.github.javaparser.ast.stmt.SwitchStmt -import com.github.javaparser.ast.stmt.ThrowStmt -import org.utbot.framework.plugin.api.ConcreteExecutionFailureException -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.DocStatement -import org.utbot.framework.plugin.api.Step -import org.utbot.framework.plugin.api.exceptionOrNull -import org.utbot.summary.AbstractTextBuilder -import org.utbot.summary.SummarySentenceConstants.CARRIAGE_RETURN -import org.utbot.summary.ast.JimpleToASTMap -import org.utbot.summary.tag.BasicTypeTag -import org.utbot.summary.tag.CallOrderTag -import org.utbot.summary.tag.StatementTag -import org.utbot.summary.tag.TraceTagWithoutExecution -import org.utbot.summary.tag.UniquenessTag -import soot.SootMethod -import soot.jimple.Stmt -import soot.jimple.internal.JAssignStmt -import soot.jimple.internal.JInvokeStmt -import soot.jimple.internal.JVirtualInvokeExpr - -private const val JVM_CRASH_REASON = "JVM crash" - -open class SimpleCommentBuilder( - traceTag: TraceTagWithoutExecution, - sootToAST: MutableMap, - val stringTemplates: StringsTemplatesInterface = StringsTemplatesSingular() -) : - AbstractTextBuilder(traceTag, sootToAST) { - - /** - * Creates String from SimpleSentenceBlock - */ - open fun buildString(currentMethod: SootMethod): String { - val root = SimpleSentenceBlock(stringTemplates = stringTemplates) - - val thrownException = traceTag.result.exceptionOrNull() - if (thrownException == null) { - root.exceptionThrow = traceTag.result.exceptionOrNull()?.let { it::class.qualifiedName } - } else { - val exceptionName = thrownException.javaClass.simpleName - val reason = findExceptionReason(currentMethod, thrownException) - root.exceptionThrow = "$exceptionName $reason" - } - skippedIterations() - buildSentenceBlock(traceTag.rootStatementTag, root, currentMethod) - var sentence = toSentence(root) - if (sentence.isEmpty()) return genWarnNotification() - sentence = splitLongSentence(sentence) - sentence = lastCommaToDot(sentence) - - return "
    \n$sentence
    ".replace(CARRIAGE_RETURN, "") - } - - /** - * Creates List from SimpleSentenceBlock - */ - open fun buildDocStmts(currentMethod: SootMethod): List { - val root = SimpleSentenceBlock(stringTemplates = stringTemplates) - - val thrownException = traceTag.result.exceptionOrNull() - if (thrownException == null) { - root.exceptionThrow = traceTag.result.exceptionOrNull()?.let { it::class.qualifiedName } - } else { - val exceptionName = thrownException.javaClass.simpleName - val reason = findExceptionReason(currentMethod, thrownException) - root.exceptionThrow = "$exceptionName $reason" - } - skippedIterations() - buildSentenceBlock(traceTag.rootStatementTag, root, currentMethod) - val docStmts = toDocStmts(root) - - if (docStmts.isEmpty()) { - return listOf(DocRegularStmt(genWarnNotification())) //TODO SAT-1310 - } -// sentence = splitLongSentence(sentence) //TODO SAT-1309 -// sentence = lastCommaToDot(sentence) //TODO SAT-1309 - - return listOf(DocPreTagStatement(docStmts)) - } - - protected fun genWarnNotification(): String = " " //why is it empty? - - /** - * Transforms rootSentenceBlock into String - */ - protected fun toSentence(rootSentenceBlock: SimpleSentenceBlock): String { - rootSentenceBlock.squashStmtText() - val buildSentence = rootSentenceBlock.toSentence() - if (buildSentence.isEmpty()) return "" - return "${stringTemplates.sentenceBeginning} $buildSentence" - } - - /** - * Transforms rootSentenceBlock into List - */ - protected fun toDocStmts(rootSentenceBlock: SimpleSentenceBlock): List { - val stmts = mutableListOf() - - rootSentenceBlock.squashStmtText() - stmts += rootSentenceBlock.toDocStmt() - if (stmts.isEmpty()) return emptyList() - - stmts.add(0, DocRegularStmt("${stringTemplates.sentenceBeginning} ")) - return stmts - } - - private fun findExceptionReason(currentMethod: SootMethod, thrownException: Throwable): String { - val path = traceTag.path - if (path.isEmpty()) { - if (thrownException is ConcreteExecutionFailureException) { - return JVM_CRASH_REASON - } - - error("Cannot find last path step for exception $thrownException") - } - - return findExceptionReason(path.last(), currentMethod) - } - - /** - * Tries to find ast node where exception was thrown - * or condition if exception was thrown manually in function body - */ - protected fun findExceptionReason(step: Step, currentMethod: SootMethod): String { - var reason = "" - val exceptionStmt = step.stmt - val jimpleToASTMap = sootToAST[currentMethod] ?: return "" - var exceptionNode = jimpleToASTMap.stmtToASTNode[exceptionStmt] - if (exceptionNode is ThrowStmt) { - exceptionNode = getExceptionReason(exceptionNode) - reason += "after condition: " - } else reason += "in: " - - //special case if reason is MethodDeclaration -> exception was thrown after body execution, not after condition - if (exceptionNode is MethodDeclaration) return "in ${exceptionNode.name} function body" - //node is SwitchStmt only when jimple stmt is inside selector - if (exceptionNode is SwitchStmt) exceptionNode = exceptionNode.selector - - if (exceptionNode == null) return "" - - reason += when { - exceptionNode is IfStmt -> exceptionNode.condition.toString() - isLoopStatement(exceptionNode) -> getTextIterationDescription(exceptionNode) - exceptionNode is SwitchStmt -> textSwitchCase(step, jimpleToASTMap) - else -> exceptionNode.toString() - } - - return reason.replace(CARRIAGE_RETURN, "") - } - - /** - * Sentence blocks are built based on unique and partly unique statement tags. - */ - private fun buildSentenceBlock( - statementTag: StatementTag?, - sentenceBlock: SimpleSentenceBlock, - currentMethod: SootMethod - ) { - val jimpleToASTMap = sootToAST[currentMethod] - if (statementTag == null) return - if (jimpleToASTMap == null) return - val recursion = statementTag.recursion - val stmt = statementTag.step.stmt - val invoke = statementTag.invoke - var createNextBlock = false - - val localNoIterations = statementTagSkippedIteration(statementTag, currentMethod) - if (localNoIterations.isNotEmpty()) { - sentenceBlock.notExecutedIterations = localNoIterations - methodToNoIterationDescription[currentMethod]?.removeAll(localNoIterations) - } - - val invokeSootMethod = statementTag.invokeSootMethod() - var invokeRegistered = false - if (invoke != null && invokeSootMethod != null) { - val className = invokeSootMethod.declaringClass.javaStyleName - val methodName = invokeSootMethod.name - val sentenceInvoke = SimpleSentenceBlock(stringTemplates = sentenceBlock.stringTemplates) - buildSentenceBlock(invoke, sentenceInvoke, invokeSootMethod) - sentenceInvoke.squashStmtText() - if (!sentenceInvoke.isEmpty()) { - sentenceBlock.invokeSentenceBlock = Pair(invokeDescription(className, methodName), sentenceInvoke) - createNextBlock = true - invokeRegistered = true - } - } - if (statementTag.basicTypeTag == BasicTypeTag.Invoke && statementTag.uniquenessTag == UniquenessTag.Unique && !invokeRegistered) { - if (statementTag.executionFrequency <= 1) { - addTextInvoke(sentenceBlock, stmt, statementTag.executionFrequency) - } - if (statementTag.executionFrequency > 1 && statementTag.callOrderTag == CallOrderTag.First) { - addTextInvoke(sentenceBlock, stmt, statementTag.executionFrequency) - } - } - - if (statementTag.basicTypeTag == BasicTypeTag.RecursionAssignment && statementTag.uniquenessTag == UniquenessTag.Unique && !invokeRegistered) { - if (statementTag.executionFrequency <= 1) { - addTextRecursion(sentenceBlock, stmt, statementTag.executionFrequency) - } - if (statementTag.executionFrequency > 1 && statementTag.callOrderTag == CallOrderTag.First) { - addTextRecursion(sentenceBlock, stmt, statementTag.executionFrequency) - } - } - - if (jimpleToASTMap[statementTag.step.stmt] !is ForStmt) { - - if (statementTag.basicTypeTag == BasicTypeTag.Condition && statementTag.callOrderTag == CallOrderTag.First) { - if (statementTag.uniquenessTag == UniquenessTag.Unique) { - val conditionText = textCondition(statementTag, jimpleToASTMap) - if (conditionText != null) { - sentenceBlock.stmtTexts.add(StmtDescription(StmtType.Condition, conditionText)) - } - } - if (statementTag.uniquenessTag == UniquenessTag.Partly && statementTag.executionFrequency == 1) { - val conditionText = textCondition(statementTag, jimpleToASTMap) - if (conditionText != null) { - sentenceBlock.stmtTexts.add(StmtDescription(StmtType.Condition, conditionText)) - } - } - } - - if (statementTag.basicTypeTag == BasicTypeTag.SwitchCase && statementTag.uniquenessTag == UniquenessTag.Unique) { - val switchCase = textSwitchCase(statementTag.step, jimpleToASTMap) - if (switchCase != null) { - sentenceBlock.stmtTexts.add(StmtDescription(StmtType.SwitchCase, switchCase)) - } - } - if (statementTag.basicTypeTag == BasicTypeTag.CaughtException) { - jimpleToASTMap[stmt].let { - if (it is CatchClause) { - sentenceBlock.stmtTexts.add(StmtDescription(StmtType.CaughtException, it.parameter.toString())) - } - } - } - if (statementTag.basicTypeTag == BasicTypeTag.Return) { - textReturn(statementTag, sentenceBlock, stmt, jimpleToASTMap) - } - } - - if (statementTag.iterations.isNotEmpty()) { - val iterationSentenceBlock = buildIterationsBlock(statementTag.iterations, statementTag.step, currentMethod) - sentenceBlock.iterationSentenceBlocks.add(iterationSentenceBlock) - createNextBlock = true - } - - if (recursion != null) { - if (stmt is JAssignStmt) { - val name = (stmt.rightBox.value as JVirtualInvokeExpr).method.name - val sentenceRecursionBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) - buildSentenceBlock(recursion, sentenceRecursionBlock, currentMethod) - sentenceBlock.recursion = Pair(name, sentenceRecursionBlock) - createNextBlock = true - } - if (stmt is JInvokeStmt) { - val name = stmt.invokeExpr.method.name - val sentenceRecursion = SimpleSentenceBlock(stringTemplates = stringTemplates) - buildSentenceBlock(recursion, sentenceRecursion, currentMethod) - sentenceBlock.recursion = Pair(name, sentenceRecursion) - createNextBlock = true - } - } - - if (createNextBlock) { - val nextSentenceBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) - sentenceBlock.nextBlock = nextSentenceBlock - buildSentenceBlock(statementTag.next, nextSentenceBlock, currentMethod) - } else { - buildSentenceBlock(statementTag.next, sentenceBlock, currentMethod) - } - } - - /** - * Adds RecursionAssignment into sentenceBlock.stmtTexts - */ - protected fun addTextRecursion(sentenceBlock: SimpleSentenceBlock, stmt: Stmt, frequency: Int) { - if (stmt is JAssignStmt || stmt is JInvokeStmt) { - val methodName = stmt.invokeExpr.method.name - addTextRecursion(sentenceBlock, methodName, frequency) - } - } - - /** - * Adds Invoke into sentenceBlock.stmtTexts - */ - protected fun addTextInvoke(sentenceBlock: SimpleSentenceBlock, stmt: Stmt, frequency: Int) { - if (stmt is JAssignStmt || stmt is JInvokeStmt) { - val className = stmt.invokeExpr.methodRef.declaringClass.javaStyleName - val methodName = stmt.invokeExpr.method.name - addTextInvoke(sentenceBlock, className, methodName, frequency) - } - } - - /** - * Adds Invoke into sentenceBlock.stmtTexts - */ - protected fun addTextInvoke( - sentenceBlock: SimpleSentenceBlock, - className: String, - methodName: String, - frequency: Int - ) { - if (!shouldSkipInvoke(methodName)) - sentenceBlock.stmtTexts.add( - StmtDescription( - StmtType.Invoke, - invokeDescription(className, methodName), - frequency - ) - ) - } - - /** - * Adds RecursionAssignment into sentenceBlock.stmtTexts - */ - protected fun addTextRecursion( - sentenceBlock: SimpleSentenceBlock, - methodName: String, - frequency: Int - ) { - if (!shouldSkipInvoke(methodName)) - sentenceBlock.stmtTexts.add( - StmtDescription( - StmtType.RecursionAssignment, - methodName, - frequency - ) - ) - } - - /** - * Returns method call as styled String - */ - protected fun invokeDescription(className: String, methodName: String) = "$className::$methodName" //TODO SAT-1311 - - protected fun buildIterationsBlock( - iterations: List, - activatedStep: Step, - currentMethod: SootMethod - ): Pair> { - val result = mutableListOf() - val jimpleToASTMap = sootToAST[currentMethod] - iterations.forEach { - val sentenceBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) - buildSentenceBlock(it, sentenceBlock, currentMethod) - result.add(sentenceBlock) - } - val firstStmtNode = jimpleToASTMap?.get(iterations.first().step.stmt) - val activatedNode = jimpleToASTMap?.get(activatedStep.stmt) - val line = iterations.first().line - - var iterationDescription = "" - if (firstStmtNode is Statement) { - iterationDescription = getTextIterationDescription(firstStmtNode) - } - // getTextIterationDescription can return empty description, - // that is why if else is not used here. - if (iterationDescription.isEmpty() && activatedNode is Statement) { - iterationDescription = getTextIterationDescription(activatedNode) - } - //heh, we are still looking for loop txt - if (iterationDescription.isEmpty()) { - val nearestNode = jimpleToASTMap?.nearestIterationNode(activatedNode, line) - if (nearestNode != null) { - iterationDescription = getTextIterationDescription(nearestNode) - } - } - - if (iterationDescription.isEmpty()) { - val nearestNode = jimpleToASTMap?.nearestIterationNode(firstStmtNode, line) - if (nearestNode != null) { - iterationDescription = getTextIterationDescription(nearestNode) - } - } - - return Pair(iterationDescription, result) - } -} - -data class IterationDescription(val from: Int, val to: Int, val description: String, val typeDescription: String) diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/fuzzer/SimpleCommentForTestProducedByFuzzerBuilder.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/fuzzer/SimpleCommentForTestProducedByFuzzerBuilder.kt new file mode 100644 index 0000000000..ee272f2f3a --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/fuzzer/SimpleCommentForTestProducedByFuzzerBuilder.kt @@ -0,0 +1,48 @@ +package org.utbot.summary.comment.classic.fuzzer + +import org.utbot.framework.plugin.api.DocPreTagStatement +import org.utbot.framework.plugin.api.DocRegularStmt +import org.utbot.framework.plugin.api.DocStatement +import org.utbot.framework.plugin.api.UtExecutionResult +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedValue +import org.utbot.summary.SummarySentenceConstants.NEW_LINE +import org.utbot.summary.comment.customtags.getClassReference +import org.utbot.summary.comment.customtags.getFullClassName +import org.utbot.summary.comment.customtags.getMethodReferenceForFuzzingTest + +class SimpleCommentForTestProducedByFuzzerBuilder( + val methodDescription: FuzzedMethodDescription, + val values: List, + val result: UtExecutionResult? +) { + fun buildDocStatements(): List { + val packageName = methodDescription.packageName + val className = methodDescription.className + val methodName = methodDescription.compilableName + val canonicalName = methodDescription.canonicalName + val isNested = methodDescription.isNested + + val result = if (packageName != null && className != null && methodName != null) { + val fullClassName = getFullClassName(canonicalName, packageName, className, isNested) + + val methodReference = getMethodReferenceForFuzzingTest( + fullClassName, + methodName, + methodDescription.parameters, + false + ) + + val classReference = getClassReference(fullClassName) + + val docStatements = mutableListOf() + docStatements.add(DocRegularStmt("Class under test: $classReference$NEW_LINE")) + docStatements.add(DocRegularStmt("Method under test: $methodReference$NEW_LINE")) + docStatements + } else { + emptyList() + } + + return listOf(DocPreTagStatement(result)) + } +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/symbolic/SimpleCommentBuilder.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/symbolic/SimpleCommentBuilder.kt new file mode 100644 index 0000000000..cab592b7bb --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/symbolic/SimpleCommentBuilder.kt @@ -0,0 +1,421 @@ +package org.utbot.summary.comment.classic.symbolic + +import com.github.javaparser.ast.stmt.CatchClause +import com.github.javaparser.ast.stmt.ForStmt +import com.github.javaparser.ast.stmt.IfStmt +import com.github.javaparser.ast.stmt.Statement +import com.github.javaparser.ast.stmt.SwitchStmt +import com.github.javaparser.ast.stmt.ThrowStmt +import com.github.javaparser.ast.stmt.SwitchEntry +import org.utbot.framework.plugin.api.ArtificialError +import org.utbot.framework.plugin.api.InstrumentedProcessDeathException +import org.utbot.framework.plugin.api.DocPreTagStatement +import org.utbot.framework.plugin.api.DocRegularStmt +import org.utbot.framework.plugin.api.DocStatement +import org.utbot.framework.plugin.api.Step +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.framework.plugin.api.exceptionOrNull +import org.utbot.summary.AbstractTextBuilder +import org.utbot.summary.NodeConverter +import org.utbot.summary.SummarySentenceConstants.CARRIAGE_RETURN +import org.utbot.summary.ast.JimpleToASTMap +import org.utbot.summary.comment.* +import org.utbot.summary.comment.customtags.getMethodReferenceForSymbolicTest +import org.utbot.summary.tag.BasicTypeTag +import org.utbot.summary.tag.CallOrderTag +import org.utbot.summary.tag.StatementTag +import org.utbot.summary.tag.TraceTagWithoutExecution +import org.utbot.summary.tag.UniquenessTag +import soot.SootMethod +import soot.Type +import soot.jimple.Stmt +import soot.jimple.internal.JAssignStmt +import soot.jimple.internal.JInvokeStmt +import soot.jimple.internal.JVirtualInvokeExpr + +private const val JVM_CRASH_REASON = "JVM crash" +const val EMPTY_STRING = "" + +open class SimpleCommentBuilder( + traceTag: TraceTagWithoutExecution, + sootToAST: MutableMap, + val stringTemplates: StringsTemplatesInterface = StringsTemplatesSingular() +) : + AbstractTextBuilder(traceTag, sootToAST) { + + /** + * Creates String from SimpleSentenceBlock + */ + open fun buildString(currentMethod: SootMethod): String { + val root = SimpleSentenceBlock(stringTemplates = stringTemplates) + buildThrownExceptionInfo(root, currentMethod) + skippedIterations() + buildSentenceBlock(traceTag.rootStatementTag, root, currentMethod) + var sentence = toSentence(root) + + if (sentence.isEmpty()) { + return EMPTY_STRING + } + + sentence = splitLongSentence(sentence) + sentence = lastCommaToDot(sentence) + + return "
    \n$sentence
    ".replace(CARRIAGE_RETURN, "") + } + + private fun buildThrownExceptionInfo( + root: SimpleSentenceBlock, + currentMethod: SootMethod + ) { + traceTag.result.exceptionOrNull()?.let { + val exceptionName = it.javaClass.simpleName + val reason = findExceptionReason(currentMethod, it) + + when (it) { + is TimeoutException, + is ArtificialError -> root.detectedError = reason + else -> root.exceptionThrow = "$exceptionName $reason" + } + } + } + + /** + * Creates List<[DocStatement]> from [SimpleSentenceBlock]. + */ + open fun buildDocStmts(currentMethod: SootMethod): List { + val sentenceBlock = buildSentenceBlock(currentMethod) + val docStmts = toDocStmts(sentenceBlock) + + if (docStmts.isEmpty()) { + return emptyList() + } +// sentence = splitLongSentence(sentence) //TODO SAT-1309 +// sentence = lastCommaToDot(sentence) //TODO SAT-1309 + + return listOf(DocPreTagStatement(docStmts)) + } + + private fun buildSentenceBlock(currentMethod: SootMethod): SimpleSentenceBlock { + val rootSentenceBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) + buildThrownExceptionInfo(rootSentenceBlock, currentMethod) + skippedIterations() + buildSentenceBlock(traceTag.rootStatementTag, rootSentenceBlock, currentMethod) + return rootSentenceBlock + } + + /** + * Transforms rootSentenceBlock into String + */ + protected fun toSentence(rootSentenceBlock: SimpleSentenceBlock): String { + rootSentenceBlock.squashStmtText() + val buildSentence = rootSentenceBlock.toSentence() + if (buildSentence.isEmpty()) return "" + return "${stringTemplates.sentenceBeginning} $buildSentence" + } + + /** + * Transforms rootSentenceBlock into List + */ + protected fun toDocStmts(rootSentenceBlock: SimpleSentenceBlock): List { + val stmts = mutableListOf() + + rootSentenceBlock.squashStmtText() + stmts += rootSentenceBlock.toDocStmt() + if (stmts.isEmpty()) return emptyList() + + stmts.add(0, DocRegularStmt("${stringTemplates.sentenceBeginning} ")) + return stmts + } + + protected fun findExceptionReason(currentMethod: SootMethod, thrownException: Throwable): String { + val path = traceTag.path + if (path.isEmpty()) { + if (thrownException is InstrumentedProcessDeathException) { + return JVM_CRASH_REASON + } + + error("Cannot find last path step for exception $thrownException") + } + + return findExceptionReason(path.last(), currentMethod) + } + + /** + * Tries to find AST node where the exception was thrown + * or AST node that contains a condition which lead to the throw. + */ + private fun findExceptionReason(step: Step, currentMethod: SootMethod): String = + StringBuilder() + .let { stringBuilder -> + val jimpleToASTMap = sootToAST[currentMethod] ?: return "" + + val exceptionNode = + jimpleToASTMap.stmtToASTNode[step.stmt] + .let { node -> + if (node is ThrowStmt) getExceptionReasonForComment(node) + else node + } + ?.also { node -> + stringBuilder.append( + if (node is IfStmt || node is SwitchEntry) "when: " + else "in: " + ) + } + ?: return "" + + stringBuilder + .append( + when { + exceptionNode is IfStmt -> exceptionNode.condition.toString() + exceptionNode is SwitchEntry -> NodeConverter.convertSwitchEntry(exceptionNode, step, removeSpaces = false) + exceptionNode is SwitchStmt -> NodeConverter.convertSwitchStmt(exceptionNode, step, removeSpaces = false) + isLoopStatement(exceptionNode) -> getTextIterationDescription(exceptionNode) + else -> exceptionNode.toString() + } + ) + } + .toString() + .replace(CARRIAGE_RETURN, "") + + /** + * Sentence blocks are built based on unique and partly unique statement tags. + */ + open fun buildSentenceBlock( + statementTag: StatementTag?, + sentenceBlock: SimpleSentenceBlock, + currentMethod: SootMethod + ) { + val jimpleToASTMap = sootToAST[currentMethod] + if (statementTag == null) return + if (jimpleToASTMap == null) return + val recursion = statementTag.recursion + val stmt = statementTag.step.stmt + val invoke = statementTag.invoke + var createNextBlock = false + + val localNoIterations = statementTagSkippedIteration(statementTag, currentMethod) + if (localNoIterations.isNotEmpty()) { + sentenceBlock.notExecutedIterations = localNoIterations + methodToNoIterationDescription[currentMethod]?.removeAll(localNoIterations) + } + + val invokeSootMethod = statementTag.invokeSootMethod() + var invokeRegistered = false + if (invoke != null && invokeSootMethod != null) { + val className = invokeSootMethod.declaringClass.name + val methodName = invokeSootMethod.name + val methodParameterTypes = invokeSootMethod.parameterTypes + val sentenceInvoke = SimpleSentenceBlock(stringTemplates = sentenceBlock.stringTemplates) + buildSentenceBlock(invoke, sentenceInvoke, invokeSootMethod) + sentenceInvoke.squashStmtText() + if (!sentenceInvoke.isEmpty()) { + sentenceBlock.invokeSentenceBlock = + Pair( + getMethodReferenceForSymbolicTest(className, methodName, methodParameterTypes, invokeSootMethod.isPrivate), + sentenceInvoke + ) + createNextBlock = true + invokeRegistered = true + } + } + if (statementTag.basicTypeTag == BasicTypeTag.Invoke && statementTag.uniquenessTag == UniquenessTag.Unique && !invokeRegistered) { + if (statementTag.executionFrequency <= 1) { + addTextInvoke(sentenceBlock, stmt, statementTag.executionFrequency) + } + if (statementTag.executionFrequency > 1 && statementTag.callOrderTag == CallOrderTag.First) { + addTextInvoke(sentenceBlock, stmt, statementTag.executionFrequency) + } + } + + if (statementTag.basicTypeTag == BasicTypeTag.RecursionAssignment && statementTag.uniquenessTag == UniquenessTag.Unique && !invokeRegistered) { + if (statementTag.executionFrequency <= 1) { + addTextRecursion(sentenceBlock, stmt, statementTag.executionFrequency) + } + if (statementTag.executionFrequency > 1 && statementTag.callOrderTag == CallOrderTag.First) { + addTextRecursion(sentenceBlock, stmt, statementTag.executionFrequency) + } + } + + if (jimpleToASTMap[statementTag.step.stmt] !is ForStmt) { + + if (statementTag.basicTypeTag == BasicTypeTag.Condition && statementTag.callOrderTag == CallOrderTag.First) { + if (statementTag.uniquenessTag == UniquenessTag.Unique) { + val conditionText = textCondition(statementTag, jimpleToASTMap) + if (conditionText != null) { + sentenceBlock.stmtTexts.add(StmtDescription(StmtType.Condition, conditionText)) + } + } + if (statementTag.uniquenessTag == UniquenessTag.Partly && statementTag.executionFrequency == 1) { + val conditionText = textCondition(statementTag, jimpleToASTMap) + if (conditionText != null) { + sentenceBlock.stmtTexts.add(StmtDescription(StmtType.Condition, conditionText)) + } + } + } + + if (statementTag.basicTypeTag == BasicTypeTag.SwitchCase && statementTag.uniquenessTag == UniquenessTag.Unique) { + textSwitchCase(statementTag.step, jimpleToASTMap) + ?.let { description -> + sentenceBlock.stmtTexts.add(StmtDescription(StmtType.SwitchCase, description)) + } + } + if (statementTag.basicTypeTag == BasicTypeTag.CaughtException) { + jimpleToASTMap[stmt].let { + if (it is CatchClause) { + sentenceBlock.stmtTexts.add(StmtDescription(StmtType.CaughtException, it.parameter.toString())) + } + } + } + if (statementTag.basicTypeTag == BasicTypeTag.Return) { + textReturn(statementTag, sentenceBlock, stmt, jimpleToASTMap) + } + } + + if (statementTag.iterations.isNotEmpty()) { + val iterationSentenceBlock = buildIterationsBlock(statementTag.iterations, statementTag.step, currentMethod) + sentenceBlock.iterationSentenceBlocks.add(iterationSentenceBlock) + createNextBlock = true + } + + if (recursion != null) { + if (stmt is JAssignStmt) { + val name = (stmt.rightOp as JVirtualInvokeExpr).method.name + val sentenceRecursionBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) + buildSentenceBlock(recursion, sentenceRecursionBlock, currentMethod) + sentenceBlock.recursion = Pair(name, sentenceRecursionBlock) + createNextBlock = true + } + if (stmt is JInvokeStmt) { + val name = stmt.invokeExpr.method.name + val sentenceRecursion = SimpleSentenceBlock(stringTemplates = stringTemplates) + buildSentenceBlock(recursion, sentenceRecursion, currentMethod) + sentenceBlock.recursion = Pair(name, sentenceRecursion) + createNextBlock = true + } + } + + if (createNextBlock) { + val nextSentenceBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) + sentenceBlock.nextBlock = nextSentenceBlock + buildSentenceBlock(statementTag.next, nextSentenceBlock, currentMethod) + } else { + buildSentenceBlock(statementTag.next, sentenceBlock, currentMethod) + } + } + + /** + * Adds RecursionAssignment into sentenceBlock.stmtTexts + */ + protected fun addTextRecursion(sentenceBlock: SimpleSentenceBlock, stmt: Stmt, frequency: Int) { + if (stmt is JAssignStmt || stmt is JInvokeStmt) { + val className = stmt.invokeExpr.methodRef.declaringClass.name + val methodName = stmt.invokeExpr.method.name + addTextRecursion(sentenceBlock, className, methodName, frequency) + } + } + + /** + * Adds Invoke into sentenceBlock.stmtTexts + */ + protected fun addTextInvoke(sentenceBlock: SimpleSentenceBlock, stmt: Stmt, frequency: Int) { + if (stmt is JAssignStmt || stmt is JInvokeStmt) { + val className = stmt.invokeExpr.methodRef.declaringClass.name + val methodName = stmt.invokeExpr.method.name + val methodParameterTypes = stmt.invokeExpr.method.parameterTypes + val isPrivate = stmt.invokeExpr.method.isPrivate + addTextInvoke( + sentenceBlock, + className, + methodName, + methodParameterTypes, + isPrivate, + frequency + ) + } + } + + /** + * Adds Invoke into sentenceBlock.stmtTexts + */ + protected fun addTextInvoke( + sentenceBlock: SimpleSentenceBlock, + className: String, + methodName: String, + methodParameterTypes: List, + isPrivate: Boolean, + frequency: Int + ) { + if (!shouldSkipInvoke(className, methodName)) + sentenceBlock.stmtTexts.add( + StmtDescription( + StmtType.Invoke, + getMethodReferenceForSymbolicTest(className, methodName, methodParameterTypes, isPrivate), + frequency + ) + ) + } + + /** + * Adds RecursionAssignment into sentenceBlock.stmtTexts + */ + protected fun addTextRecursion( + sentenceBlock: SimpleSentenceBlock, + className: String, + methodName: String, + frequency: Int + ) { + if (!shouldSkipInvoke(className, methodName)) + sentenceBlock.stmtTexts.add( + StmtDescription( + StmtType.RecursionAssignment, + methodName, + frequency + ) + ) + } + + protected fun buildIterationsBlock( + iterations: List, + activatedStep: Step, + currentMethod: SootMethod + ): Pair> { + val result = mutableListOf() + val jimpleToASTMap = sootToAST[currentMethod] + iterations.forEach { + val sentenceBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) + buildSentenceBlock(it, sentenceBlock, currentMethod) + result.add(sentenceBlock) + } + val firstStmtNode = jimpleToASTMap?.get(iterations.first().step.stmt) + val activatedNode = jimpleToASTMap?.get(activatedStep.stmt) + val line = iterations.first().line + + var iterationDescription = "" + if (firstStmtNode is Statement) { + iterationDescription = getTextIterationDescription(firstStmtNode) + } + // getTextIterationDescription can return empty description, + // that is why if else is not used here. + if (iterationDescription.isEmpty() && activatedNode is Statement) { + iterationDescription = getTextIterationDescription(activatedNode) + } + //heh, we are still looking for loop txt + if (iterationDescription.isEmpty()) { + val nearestNode = jimpleToASTMap?.nearestIterationNode(activatedNode, line) + if (nearestNode != null) { + iterationDescription = getTextIterationDescription(nearestNode) + } + } + + if (iterationDescription.isEmpty()) { + val nearestNode = jimpleToASTMap?.nearestIterationNode(firstStmtNode, line) + if (nearestNode != null) { + iterationDescription = getTextIterationDescription(nearestNode) + } + } + + return Pair(iterationDescription, result) + } +} + +data class IterationDescription(val from: Int, val to: Int, val description: String, val typeDescription: String) diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/SimpleSentenceBlock.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/symbolic/SimpleSentenceBlock.kt similarity index 94% rename from utbot-summary/src/main/kotlin/org/utbot/summary/comment/SimpleSentenceBlock.kt rename to utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/symbolic/SimpleSentenceBlock.kt index 0caa0eaf42..faa6b2fdd1 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/SimpleSentenceBlock.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/symbolic/SimpleSentenceBlock.kt @@ -1,4 +1,4 @@ -package org.utbot.summary.comment +package org.utbot.summary.comment.classic.symbolic import org.utbot.framework.plugin.api.DocCodeStmt import org.utbot.framework.plugin.api.DocRegularStmt @@ -12,12 +12,14 @@ import org.utbot.summary.SummarySentenceConstants.NEW_LINE import org.utbot.summary.SummarySentenceConstants.OPEN_BRACKET import org.utbot.summary.SummarySentenceConstants.SENTENCE_SEPARATION import org.utbot.summary.SummarySentenceConstants.TAB +import org.utbot.summary.comment.* class SimpleSentenceBlock(val stringTemplates: StringsTemplatesInterface) { val iterationSentenceBlocks = mutableListOf>>() var notExecutedIterations: List? = null var recursion: Pair? = null var exceptionThrow: String? = null + var detectedError: String? = null var nextBlock: SimpleSentenceBlock? = null val stmtTexts = mutableListOf() @@ -121,6 +123,15 @@ class SimpleSentenceBlock(val stringTemplates: StringsTemplatesInterface) { result += NEW_LINE } + detectedError?.let { + if (restartSentence) { + result += stringTemplates.sentenceBeginning + " " + restartSentence = false + } + result += stringTemplates.suspiciousBehaviorDetectedSentence.format(it) + result += NEW_LINE + } + return result } @@ -237,6 +248,16 @@ class SimpleSentenceBlock(val stringTemplates: StringsTemplatesInterface) { docStmts += DocRegularStmt(NEW_LINE) } + detectedError?.let { + docStmts += DocRegularStmt(NEW_LINE) + if (restartSentence) { + docStmts += DocRegularStmt(stringTemplates.sentenceBeginning + " ") + restartSentence = false + } + docStmts += DocRegularStmt(stringTemplates.suspiciousBehaviorDetectedSentence.format(it)) + docStmts += DocRegularStmt(NEW_LINE) + } + return docStmts } @@ -322,6 +343,7 @@ class SimpleSentenceBlock(val stringTemplates: StringsTemplatesInterface) { if (nextBlock?.isEmpty() == false) return false if (notExecutedIterations.isNullOrEmpty().not()) return false exceptionThrow?.let { return false } + detectedError?.let { return false } if (invokeSentenceBlock != null) return false return true } @@ -477,6 +499,7 @@ interface StringsTemplatesInterface { val noIteration: String val codeSentence: String val throwSentence: String + val suspiciousBehaviorDetectedSentence: String val conditionLine: String val returnLine: String @@ -497,13 +520,14 @@ open class StringsTemplatesSingular : StringsTemplatesInterface { override val noIteration: String = "does not iterate" override val codeSentence: String = "$OPEN_BRACKET$AT_CODE %s$CLOSE_BRACKET" override val throwSentence: String = "throws %s" + override val suspiciousBehaviorDetectedSentence: String = "detects suspicious behavior %s" //used in Squashing override val conditionLine: String = "executes conditions:$NEW_LINE" override val returnLine: String = "returns from: " override val countedReturnLine: String = "returns from:$NEW_LINE" override val invokeLine: String = "invokes:$NEW_LINE" - override val switchCaseLine: String = "activates switch case: " + override val switchCaseLine: String = "activates " override val caughtExceptionLine: String = "catches exception:$NEW_LINE" override val recursionAssignmentLine: String = "triggers recursion of " } @@ -518,13 +542,14 @@ class StringsTemplatesPlural : StringsTemplatesSingular() { override val noIteration: String = "does not iterate" override val codeSentence: String = "$OPEN_BRACKET$AT_CODE %s$CLOSE_BRACKET" override val throwSentence: String = "throw %s" + override val suspiciousBehaviorDetectedSentence: String = "detect suspicious behavior %s" //used in Squashing override val conditionLine: String = "execute conditions:$NEW_LINE" override val returnLine: String = "return from: " override val countedReturnLine: String = "return from:$NEW_LINE" override val invokeLine: String = "invoke:$NEW_LINE" - override val switchCaseLine: String = "activate switch case: " + override val switchCaseLine: String = "activate " override val caughtExceptionLine: String = "catches exception:$NEW_LINE" override val recursionAssignmentLine: String = "trigger recursion of " } diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/cluster/SymbolicExecutionClusterCommentBuilder.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/cluster/SymbolicExecutionClusterCommentBuilder.kt new file mode 100644 index 0000000000..7ea53fbab4 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/cluster/SymbolicExecutionClusterCommentBuilder.kt @@ -0,0 +1,187 @@ +package org.utbot.summary.comment.cluster + +import com.github.javaparser.ast.stmt.CatchClause +import com.github.javaparser.ast.stmt.ForStmt +import org.utbot.framework.plugin.api.DocPreTagStatement +import org.utbot.framework.plugin.api.DocStatement +import org.utbot.summary.SummarySentenceConstants.CARRIAGE_RETURN +import org.utbot.summary.ast.JimpleToASTMap +import org.utbot.summary.comment.* +import org.utbot.summary.comment.classic.symbolic.* +import org.utbot.summary.comment.customtags.getMethodReferenceForSymbolicTest +import org.utbot.summary.tag.BasicTypeTag +import org.utbot.summary.tag.CallOrderTag +import org.utbot.summary.tag.StatementTag +import org.utbot.summary.tag.TraceTagWithoutExecution +import org.utbot.summary.tag.UniquenessTag +import soot.SootMethod +import soot.jimple.internal.JAssignStmt +import soot.jimple.internal.JInvokeStmt +import soot.jimple.internal.JVirtualInvokeExpr + +/** + * Inherits from SimpleCommentBuilder + */ +class SymbolicExecutionClusterCommentBuilder( + traceTag: TraceTagWithoutExecution, + sootToAST: MutableMap +) : SimpleCommentBuilder(traceTag, sootToAST, stringTemplates = StringsTemplatesPlural()) { + /** + * Builds cluster comment, ignores thrown exceptions + */ + override fun buildString(currentMethod: SootMethod): String { + val root = SimpleSentenceBlock(stringTemplates = StringsTemplatesPlural()) + + skippedIterations() + buildSentenceBlock(traceTag.rootStatementTag, root, currentMethod) + var sentence = toSentence(root) + + if (sentence.isEmpty()) { + return EMPTY_STRING + } + + sentence = splitLongSentence(sentence) + sentence = lastCommaToDot(sentence) + + return "
    \n$sentence
    ".replace(CARRIAGE_RETURN, EMPTY_STRING) + } + + override fun buildDocStmts(currentMethod: SootMethod): List { + val root = SimpleSentenceBlock(stringTemplates = StringsTemplatesPlural()) + + skippedIterations() + buildSentenceBlock(traceTag.rootStatementTag, root, currentMethod) + val sentence = toDocStmts(root) + + if (sentence.isEmpty()) { + return emptyList() + } +// sentence = splitLongSentence(sentence) //TODO SAT-1309 +// sentence = lastCommaToDot(sentence) //TODO SAT-1309 + + val docStatements = toDocStmts(root) + return listOf(DocPreTagStatement(docStatements)) + } + + /** + * Builds sentence blocks as parent one, + * but ignores few types of statementTag that are considered in SimpleCommentBuilder + */ + override fun buildSentenceBlock( + statementTag: StatementTag?, + sentenceBlock: SimpleSentenceBlock, + currentMethod: SootMethod + ) { + val jimpleToASTMap = sootToAST[currentMethod] + if (statementTag == null) return + if (jimpleToASTMap == null) return + val recursion = statementTag.recursion + val stmt = statementTag.step.stmt + val invoke = statementTag.invoke + var createNextBlock = false + + val localNoIterations = statementTagSkippedIteration(statementTag, currentMethod) + if (localNoIterations.isNotEmpty()) { + sentenceBlock.notExecutedIterations = localNoIterations + methodToNoIterationDescription[currentMethod]?.removeAll(localNoIterations) + } + + val invokeSootMethod = statementTag.invokeSootMethod() + var invokeRegistered = false + if (invoke != null && invokeSootMethod != null) { + val className = invokeSootMethod.declaringClass.name + val methodName = invokeSootMethod.name + val methodParameterTypes = invokeSootMethod.parameterTypes + val isPrivate = invokeSootMethod.isPrivate + val sentenceInvoke = SimpleSentenceBlock(stringTemplates = StringsTemplatesPlural()) + buildSentenceBlock(invoke, sentenceInvoke, invokeSootMethod) + sentenceInvoke.squashStmtText() + if (!sentenceInvoke.isEmpty()) { + sentenceBlock.invokeSentenceBlock = + Pair( + getMethodReferenceForSymbolicTest(className, methodName, methodParameterTypes, isPrivate), + sentenceInvoke + ) + createNextBlock = true + invokeRegistered = true + } + } + if (statementTag.basicTypeTag == BasicTypeTag.Invoke && statementTag.uniquenessTag == UniquenessTag.Unique && !invokeRegistered) { + if (statementTag.executionFrequency <= 1) { + addTextInvoke(sentenceBlock, stmt, statementTag.executionFrequency) + } + if (statementTag.executionFrequency > 1 && statementTag.callOrderTag == CallOrderTag.First) { + addTextInvoke(sentenceBlock, stmt, statementTag.executionFrequency) + } + } + + if (statementTag.basicTypeTag == BasicTypeTag.RecursionAssignment && statementTag.uniquenessTag == UniquenessTag.Unique && !invokeRegistered) { + if (statementTag.executionFrequency <= 1) { + addTextRecursion(sentenceBlock, stmt, statementTag.executionFrequency) + } + if (statementTag.executionFrequency > 1 && statementTag.callOrderTag == CallOrderTag.First) { + addTextRecursion(sentenceBlock, stmt, statementTag.executionFrequency) + } + } + + if (jimpleToASTMap[statementTag.step.stmt] !is ForStmt) { + + if (statementTag.basicTypeTag == BasicTypeTag.Condition && statementTag.callOrderTag == CallOrderTag.First) { + if (statementTag.uniquenessTag == UniquenessTag.Unique) { + val conditionText = textCondition(statementTag, jimpleToASTMap) + if (conditionText != null) { + sentenceBlock.stmtTexts.add(StmtDescription(StmtType.Condition, conditionText)) + } + } + } + + if (statementTag.basicTypeTag == BasicTypeTag.SwitchCase && statementTag.uniquenessTag == UniquenessTag.Unique) { + textSwitchCase(statementTag.step, jimpleToASTMap) + ?.let { description -> + sentenceBlock.stmtTexts.add(StmtDescription(StmtType.SwitchCase, description)) + } + } + if (statementTag.basicTypeTag == BasicTypeTag.CaughtException && statementTag.uniquenessTag == UniquenessTag.Unique) { + jimpleToASTMap[stmt].let { + if (it is CatchClause) { + sentenceBlock.stmtTexts.add(StmtDescription(StmtType.CaughtException, it.parameter.toString())) + } + } + } + if (statementTag.basicTypeTag == BasicTypeTag.Return && statementTag.uniquenessTag == UniquenessTag.Unique) { + textReturn(statementTag, sentenceBlock, stmt, jimpleToASTMap) + } + } + + if (statementTag.iterations.isNotEmpty()) { + val iterationSentenceBlock = buildIterationsBlock(statementTag.iterations, statementTag.step, currentMethod) + sentenceBlock.iterationSentenceBlocks.add(iterationSentenceBlock) + createNextBlock = true + } + + if (recursion != null) { + if (stmt is JAssignStmt) { + val name = (stmt.rightOp as JVirtualInvokeExpr).method.name + val sentenceRecursionBlock = SimpleSentenceBlock(stringTemplates = StringsTemplatesPlural()) + buildSentenceBlock(recursion, sentenceRecursionBlock, currentMethod) + sentenceBlock.recursion = Pair(name, sentenceRecursionBlock) + createNextBlock = true + } + if (stmt is JInvokeStmt) { + val name = stmt.invokeExpr.method.name + val sentenceRecursion = SimpleSentenceBlock(stringTemplates = StringsTemplatesPlural()) + buildSentenceBlock(recursion, sentenceRecursion, currentMethod) + sentenceBlock.recursion = Pair(name, sentenceRecursion) + createNextBlock = true + } + } + + if (createNextBlock) { + val nextSentenceBlock = SimpleSentenceBlock(stringTemplates = StringsTemplatesPlural()) + sentenceBlock.nextBlock = nextSentenceBlock + buildSentenceBlock(statementTag.next, nextSentenceBlock, currentMethod) + } else { + buildSentenceBlock(statementTag.next, sentenceBlock, currentMethod) + } + } +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/CustomJavaDocTagProvider.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/CustomJavaDocTagProvider.kt new file mode 100644 index 0000000000..73819ea445 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/CustomJavaDocTagProvider.kt @@ -0,0 +1,112 @@ +package org.utbot.summary.comment.customtags.symbolic + +import org.utbot.framework.plugin.api.DocRegularLineStmt +import org.utbot.framework.plugin.api.DocRegularStmt +import org.utbot.framework.plugin.api.DocStatement +import org.utbot.summary.comment.customtags.fuzzer.CommentWithCustomTagForTestProducedByFuzzer + +/** + * Provides a list of supported custom JavaDoc tags. + */ +class CustomJavaDocTagProvider { + // The tags' order is important because plugin builds final JavaDoc comment according to it. + fun getPluginCustomTags(): List = + listOf( + CustomJavaDocTag.ClassUnderTest, + CustomJavaDocTag.MethodUnderTest, + CustomJavaDocTag.ExpectedResult, + CustomJavaDocTag.ActualResult, + CustomJavaDocTag.Executes, + CustomJavaDocTag.Invokes, + CustomJavaDocTag.Iterates, + CustomJavaDocTag.SwitchCase, + CustomJavaDocTag.Recursion, + CustomJavaDocTag.ReturnsFrom, + CustomJavaDocTag.CaughtException, + CustomJavaDocTag.ThrowsException, + CustomJavaDocTag.DetectsSuspiciousBehavior, + ) +} + +sealed class CustomJavaDocTag( + val name: String, + val message: String, + private val valueRetriever: (CustomJavaDocComment) -> Any, + private val valueRetrieverFuzzer: ((CommentWithCustomTagForTestProducedByFuzzer) -> Any)? // TODO: remove after refactoring +) { + object ClassUnderTest : + CustomJavaDocTag( + "utbot.classUnderTest", + "Class under test", + CustomJavaDocComment::classUnderTest, + CommentWithCustomTagForTestProducedByFuzzer::classUnderTest + ) + + object MethodUnderTest : + CustomJavaDocTag( + "utbot.methodUnderTest", + "Method under test", + CustomJavaDocComment::methodUnderTest, + CommentWithCustomTagForTestProducedByFuzzer::methodUnderTest + ) + + object ExpectedResult : + CustomJavaDocTag("utbot.expectedResult", "Expected result", CustomJavaDocComment::expectedResult, null) + + object ActualResult : + CustomJavaDocTag("utbot.actualResult", "Actual result", CustomJavaDocComment::actualResult, null) + + object Executes : + CustomJavaDocTag("utbot.executesCondition", "Executes condition", CustomJavaDocComment::executesCondition, null) + + object Invokes : CustomJavaDocTag("utbot.invokes", "Invokes", CustomJavaDocComment::invokes, null) + object Iterates : CustomJavaDocTag("utbot.iterates", "Iterates", CustomJavaDocComment::iterates, null) + object SwitchCase : + CustomJavaDocTag("utbot.activatesSwitch", "Activates switch", CustomJavaDocComment::switchCase, null) + + object Recursion : + CustomJavaDocTag("utbot.triggersRecursion", "Triggers recursion ", CustomJavaDocComment::recursion, null) + + object ReturnsFrom : CustomJavaDocTag("utbot.returnsFrom", "Returns from", CustomJavaDocComment::returnsFrom, null) + object CaughtException : + CustomJavaDocTag("utbot.caughtException", "Caught exception", CustomJavaDocComment::caughtException, null) + + object ThrowsException : + CustomJavaDocTag("utbot.throwsException", "Throws exception", CustomJavaDocComment::throwsException, null) + + object DetectsSuspiciousBehavior : + CustomJavaDocTag("utbot.detectsSuspiciousBehavior", "Detects suspicious behavior", CustomJavaDocComment::detectsSuspiciousBehavior, null) + + fun generateDocStatement(comment: CustomJavaDocComment): DocRegularStmt? = + when (val value = valueRetriever.invoke(comment)) { + is String -> value.takeIf { it.isNotEmpty() }?.let { + DocRegularStmt("@$name $value\n") + } + is List<*> -> value.takeIf { it.isNotEmpty() }?.let { + val valueToString = value.joinToString(separator = "\n", postfix = "\n") { "@$name $it" } + + DocRegularStmt(valueToString) + } + else -> null + } + + // TODO: could be universal with the function above after creation of hierarchy data classes related to the comments + fun generateDocStatementForTestProducedByFuzzer(comment: CommentWithCustomTagForTestProducedByFuzzer): DocStatement? { + if (valueRetrieverFuzzer != null) { //TODO: it required only when we have two different retrievers + return when (val value = valueRetrieverFuzzer!!.invoke(comment)) { // TODO: unsafe !! - resolve + is String -> value.takeIf { it.isNotEmpty() }?.let { + DocRegularLineStmt("@$name $value") + } + + is List<*> -> value.takeIf { it.isNotEmpty() }?.let { + val valueToString = value.joinToString(separator = ",\n", postfix = "\n") + + DocRegularStmt("@$name $valueToString") + } + + else -> null + } + } + return null + } +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/CustomTagsUtil.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/CustomTagsUtil.kt new file mode 100644 index 0000000000..5d503461f2 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/CustomTagsUtil.kt @@ -0,0 +1,84 @@ +package org.utbot.summary.comment.customtags + +import org.utbot.framework.plugin.api.ClassId +import org.utbot.summary.SummarySentenceConstants.CARRIAGE_RETURN +import org.utbot.summary.SummarySentenceConstants.JAVA_CLASS_DELIMITER +import org.utbot.summary.SummarySentenceConstants.JAVA_DOC_CLASS_DELIMITER +import org.utbot.summary.comment.classic.symbolic.EMPTY_STRING +import soot.Type + +/** + * Returns a reference to the invoked method. IDE can't resolve references to private methods in comments, + * so we add @link tag only if the invoked method is not private. + * + * It looks like {@link packageName.className#methodName(type1, type2)}. + * + * In case when an enclosing class in nested, we need to replace '$' with '.' + * to render the reference. + */ +fun getMethodReferenceForSymbolicTest( + className: String, + methodName: String, + methodParameterTypes: List, + isPrivate: Boolean +): String { + val methodParametersAsString = if (methodParameterTypes.isNotEmpty()) methodParameterTypes.joinToString(",") else EMPTY_STRING + + return formMethodReferenceForJavaDoc(className, methodName, methodParametersAsString, isPrivate) +} + +/** + * Returns a reference to the invoked method. + * + * It looks like {@link packageName.className#methodName(type1, type2)}. + * + * In case when an enclosing class in nested, we need to replace '$' with '.' + * to render the reference. + */ +fun getMethodReferenceForFuzzingTest(className: String, methodName: String, methodParameterTypes: List, isPrivate: Boolean): String { + val methodParametersAsString = if (methodParameterTypes.isNotEmpty()) methodParameterTypes.joinToString(",") { it.canonicalName } else EMPTY_STRING + + return formMethodReferenceForJavaDoc(className, methodName, methodParametersAsString, isPrivate).replace( + CARRIAGE_RETURN, EMPTY_STRING + ) +} + +private fun formMethodReferenceForJavaDoc( + className: String, + methodName: String, + methodParametersAsString: String, + isPrivate: Boolean +): String { + // to avoid $ in names for static inner classes + val prettyClassName: String = className.replace(JAVA_CLASS_DELIMITER, JAVA_DOC_CLASS_DELIMITER) + val validMethodParameters = methodParametersAsString.replace(JAVA_CLASS_DELIMITER, JAVA_DOC_CLASS_DELIMITER) + + val text = if (validMethodParameters == EMPTY_STRING) { + "$prettyClassName#$methodName()" + } else { + "$prettyClassName#$methodName($validMethodParameters)" + } + + return if (isPrivate) { + text + } else { + "{@link $text}" + } +} + +/** + * Returns a reference to the class. + * Replaces '$' with '.' in case a class is nested. + */ +fun getClassReference(fullClassName: String): String { + return "{@link ${fullClassName.replace(JAVA_CLASS_DELIMITER, JAVA_DOC_CLASS_DELIMITER)}}".replace(CARRIAGE_RETURN, EMPTY_STRING) +} + +/** Returns correct full class name. */ +fun getFullClassName(canonicalName: String?, packageName: String, className: String, isNested: Boolean): String { + return if (isNested && canonicalName != null) { + canonicalName + } else { + if (packageName.isEmpty()) className else "$packageName.$className" + } +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/fuzzer/CommentWithCustomTagForTestProducedByFuzzer.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/fuzzer/CommentWithCustomTagForTestProducedByFuzzer.kt new file mode 100644 index 0000000000..6cefc2c888 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/fuzzer/CommentWithCustomTagForTestProducedByFuzzer.kt @@ -0,0 +1,15 @@ +package org.utbot.summary.comment.customtags.fuzzer + +import org.utbot.summary.comment.classic.symbolic.EMPTY_STRING + +/** + * Represents a set of plugin's custom JavaDoc tags. + */ +data class CommentWithCustomTagForTestProducedByFuzzer( + val classUnderTest: String = EMPTY_STRING, + val methodUnderTest: String = EMPTY_STRING, + val expectedResult: String = EMPTY_STRING, + val actualResult: String = EMPTY_STRING, + var returnsFrom: String = EMPTY_STRING, + var throwsException: String = EMPTY_STRING +) \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/fuzzer/CommentWithCustomTagForTestProducedByFuzzerBuilder.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/fuzzer/CommentWithCustomTagForTestProducedByFuzzerBuilder.kt new file mode 100644 index 0000000000..ca65e00a8c --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/fuzzer/CommentWithCustomTagForTestProducedByFuzzerBuilder.kt @@ -0,0 +1,59 @@ +package org.utbot.summary.comment.customtags.fuzzer + +import org.utbot.framework.plugin.api.DocCustomTagStatement +import org.utbot.framework.plugin.api.DocStatement +import org.utbot.framework.plugin.api.UtExecutionResult +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedValue +import org.utbot.summary.comment.customtags.getClassReference +import org.utbot.summary.comment.customtags.getFullClassName +import org.utbot.summary.comment.customtags.getMethodReferenceForFuzzingTest +import org.utbot.summary.comment.customtags.symbolic.CustomJavaDocTagProvider + +/** + * Builds JavaDoc comments for generated tests using plugin's custom JavaDoc tags. + */ +class CommentWithCustomTagForTestProducedByFuzzerBuilder( + val methodDescription: FuzzedMethodDescription, + val values: List, + val result: UtExecutionResult? +) { + /** + * Collects statements for final JavaDoc comment. + */ + fun buildDocStatements(): List { + val comment = buildCustomJavaDocComment() + val docStatementList = + CustomJavaDocTagProvider().getPluginCustomTags() + .mapNotNull { it.generateDocStatementForTestProducedByFuzzer(comment) } + return listOf(DocCustomTagStatement(docStatementList)) + } + + private fun buildCustomJavaDocComment(): CommentWithCustomTagForTestProducedByFuzzer { + val packageName = methodDescription.packageName + val className = methodDescription.className + val methodName = methodDescription.compilableName + val canonicalName = methodDescription.canonicalName + val isNested = methodDescription.isNested + + return if (packageName != null && className != null && methodName != null) { + val fullClassName = getFullClassName(canonicalName, packageName, className, isNested) + + val methodReference = getMethodReferenceForFuzzingTest( + fullClassName, + methodName, + methodDescription.parameters, + false + ) + + val classReference = getClassReference(fullClassName) + + CommentWithCustomTagForTestProducedByFuzzer( + classUnderTest = classReference, + methodUnderTest = methodReference, + ) + } else { + CommentWithCustomTagForTestProducedByFuzzer() + } + } +} diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/symbolic/CustomJavaDocComment.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/symbolic/CustomJavaDocComment.kt new file mode 100644 index 0000000000..373f101ac1 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/symbolic/CustomJavaDocComment.kt @@ -0,0 +1,23 @@ +package org.utbot.summary.comment.customtags.symbolic + +import org.utbot.summary.comment.classic.symbolic.EMPTY_STRING + +/** + * Represents a set of plugin's custom JavaDoc tags. + */ +data class CustomJavaDocComment( + val classUnderTest: String = EMPTY_STRING, + val methodUnderTest: String = EMPTY_STRING, + val expectedResult: String = EMPTY_STRING, + val actualResult: String = EMPTY_STRING, + var executesCondition: List = listOf(), + var invokes: List = listOf(), + var iterates: List = listOf(), + var switchCase: String = EMPTY_STRING, + var recursion: String = EMPTY_STRING, + var returnsFrom: String = EMPTY_STRING, + var countedReturn: String = EMPTY_STRING, + var caughtException: String = EMPTY_STRING, + var throwsException: String = EMPTY_STRING, + var detectsSuspiciousBehavior: String = EMPTY_STRING +) \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/symbolic/CustomJavaDocCommentBuilder.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/symbolic/CustomJavaDocCommentBuilder.kt new file mode 100644 index 0000000000..613a9daea6 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/customtags/symbolic/CustomJavaDocCommentBuilder.kt @@ -0,0 +1,116 @@ +package org.utbot.summary.comment.customtags.symbolic + +import org.utbot.framework.plugin.api.ArtificialError +import org.utbot.framework.plugin.api.DocCustomTagStatement +import org.utbot.framework.plugin.api.DocStatement +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.framework.plugin.api.exceptionOrNull +import org.utbot.summary.SummarySentenceConstants.CARRIAGE_RETURN +import org.utbot.summary.ast.JimpleToASTMap +import org.utbot.summary.comment.* +import org.utbot.summary.comment.classic.symbolic.* +import org.utbot.summary.comment.customtags.getClassReference +import org.utbot.summary.comment.customtags.getMethodReferenceForSymbolicTest +import org.utbot.summary.tag.TraceTagWithoutExecution +import soot.SootMethod + +/** + * Builds JavaDoc comments for generated tests using plugin's custom JavaDoc tags. + */ +class CustomJavaDocCommentBuilder( + traceTag: TraceTagWithoutExecution, + sootToAST: MutableMap +) : SimpleCommentBuilder(traceTag, sootToAST, stringTemplates = StringsTemplatesPlural()) { + + /** + * Collects statements for final JavaDoc comment. + */ + fun buildDocStatements(method: SootMethod): List { + val comment = buildCustomJavaDocComment(method) + val docStatementList = + CustomJavaDocTagProvider().getPluginCustomTags().mapNotNull { it.generateDocStatement(comment) } + return listOf(DocCustomTagStatement(docStatementList)) + } + + private fun buildCustomJavaDocComment(currentMethod: SootMethod): CustomJavaDocComment { + val methodReference = getMethodReferenceForSymbolicTest( + currentMethod.declaringClass.name, + currentMethod.name, + currentMethod.parameterTypes, + false + ) + val classReference = getClassReference(currentMethod.declaringClass.javaStyleName) + + val comment = CustomJavaDocComment( + classUnderTest = classReference, + methodUnderTest = methodReference, + ) + + val rootSentenceBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) + skippedIterations() + buildSentenceBlock(traceTag.rootStatementTag, rootSentenceBlock, currentMethod) + rootSentenceBlock.squashStmtText() + + // builds Throws exception section + val thrownException = traceTag.result.exceptionOrNull() + if (thrownException != null) { + val exceptionName = thrownException.javaClass.name + val reason = findExceptionReason(currentMethod, thrownException) + .replace(CARRIAGE_RETURN, "") + + when (thrownException) { + is TimeoutException, + is ArtificialError -> comment.detectsSuspiciousBehavior = reason + else -> comment.throwsException = "{@link $exceptionName} $reason" + } + } + + if (rootSentenceBlock.recursion != null) { + comment.recursion += rootSentenceBlock.recursion?.first + val insideRecursionSentence = rootSentenceBlock.recursion?.second?.toSentence() + if (!insideRecursionSentence.isNullOrEmpty()) { + comment.recursion += stringTemplates.insideRecursionSentence.format(insideRecursionSentence) + .replace(CARRIAGE_RETURN, "").trim() + } + } + + generateSequence(rootSentenceBlock) { it.nextBlock }.forEach { + it.stmtTexts.forEach { statement -> + processStatement(statement, comment) + } + + it.invokeSentenceBlock?.let { + comment.invokes += it.first.replace(CARRIAGE_RETURN, "") + it.second.stmtTexts.forEach { statement -> + processStatement(statement, comment) + } + } + + it.iterationSentenceBlocks.forEach { (loopDesc, sentenceBlocks) -> + comment.iterates += stringTemplates.iterationSentence.format( + stringTemplates.codeSentence.format(loopDesc), + numberOccurrencesToText( + sentenceBlocks.size + ) + ).replace(CARRIAGE_RETURN, "") + } + } + + return comment + } + + private fun processStatement( + statement: StmtDescription, + comment: CustomJavaDocComment + ) { + when (statement.stmtType) { + StmtType.Invoke -> comment.invokes += statement.description.replace(CARRIAGE_RETURN, "") + StmtType.Condition -> comment.executesCondition += "{@code ${statement.description.replace(CARRIAGE_RETURN, "")}}" + StmtType.Return -> comment.returnsFrom = "{@code ${statement.description.replace(CARRIAGE_RETURN, "")}}" + StmtType.CaughtException -> comment.caughtException = "{@code ${statement.description.replace(CARRIAGE_RETURN, "")}}" + StmtType.SwitchCase -> comment.switchCase = "{@code ${statement.description.replace(CARRIAGE_RETURN, "")}}" + StmtType.CountedReturn -> comment.countedReturn = "{@code ${statement.description.replace(CARRIAGE_RETURN, "")}}" + StmtType.RecursionAssignment -> comment.recursion = "of {@code ${statement.description.replace(CARRIAGE_RETURN, "")}}" + } + } +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/MethodBasedNameSuggester.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/MethodBasedNameSuggester.kt new file mode 100644 index 0000000000..e38f566657 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/MethodBasedNameSuggester.kt @@ -0,0 +1,21 @@ +package org.utbot.summary.fuzzer.names + +import org.utbot.framework.plugin.api.UtExecutionResult +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedValue +import org.utbot.summary.MethodDescriptionSource +import java.util.* + +class MethodBasedNameSuggester(private val source: MethodDescriptionSource = MethodDescriptionSource.FUZZER) : NameSuggester { + override fun suggest( + description: FuzzedMethodDescription, + values: List, + result: UtExecutionResult? + ): Sequence { + val compilableName = description.compilableName?.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + ?: "Created" + // See [Summarization.generateSummariesForTests]. + val suffix = if (source == MethodDescriptionSource.FUZZER) "ByFuzzer" else "" + return sequenceOf(TestSuggestedInfo("test${compilableName}${suffix}")) + } +} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/ModelBasedNameSuggester.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/ModelBasedNameSuggester.kt new file mode 100644 index 0000000000..d7390ca2ae --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/ModelBasedNameSuggester.kt @@ -0,0 +1,185 @@ +package org.utbot.summary.fuzzer.names + +import org.utbot.framework.UtSettings +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.fuzzer.FuzzedMethodDescription +import org.utbot.fuzzer.FuzzedValue +import org.utbot.summary.SummarySentenceConstants.FROM_TO_NAMES_COLON +import org.utbot.summary.SummarySentenceConstants.FROM_TO_NAMES_TRANSITION +import org.utbot.summary.comment.classic.fuzzer.SimpleCommentForTestProducedByFuzzerBuilder +import org.utbot.summary.comment.customtags.fuzzer.CommentWithCustomTagForTestProducedByFuzzerBuilder +import java.util.* + +class ModelBasedNameSuggester( + private val suggester: List = listOf( + PrimitiveModelNameSuggester, + ArrayModelNameSuggester, + ) +) : NameSuggester { + + var maxNumberOfParametersWhenNameIsSuggested = 3 + set(value) { + field = maxOf(0, value) + } + + override fun suggest( + description: FuzzedMethodDescription, + values: List, + result: UtExecutionResult? + ): Sequence { + if (description.parameters.size > maxNumberOfParametersWhenNameIsSuggested) { + return emptySequence() + } + + return sequenceOf( + TestSuggestedInfo( + testName = if (UtSettings.enableTestNamesGeneration) createTestName(description, values, result) else null, + displayName = if (UtSettings.enableDisplayNameGeneration) createDisplayName(description, values, result) else null, + javaDoc = if (UtSettings.enableJavaDocGeneration) createJavaDoc(description, values, result) else null + ) + ) + } + + /** + * Name of a test. + * + * Result example: + * + * 1. *Without any information*: `testMethod` + * 2. *With parameters only*: `testMethodNameWithCornerCasesAndEmptyString` + * 3. *With return value*: `testMethodReturnZeroWithNonEmptyString` + * 4. *When throws an exception*: `testMethodThrowsNPEWithEmptyString` + */ + private fun createTestName( + description: FuzzedMethodDescription, + values: List, + result: UtExecutionResult? + ): String { + val returnString = when (result) { + is UtExecutionSuccess -> (result.model as? UtPrimitiveModel)?.value?.let { v -> + when (v) { + is Number -> prettifyNumber(v) + is Boolean -> v.toString() + .replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + + else -> null + }?.let { "Returns$it" } + } + + is UtExplicitlyThrownException, is UtImplicitlyThrownException -> result.exceptionOrNull()?.let { t -> + prettifyException(t).let { "Throws$it" } + } + + else -> null // TODO: handle other types of the UtExecutionResult + } ?: "" + + val parameters = values.asSequence() + .flatMap { value -> + suggester.map { suggester -> + suggester.suggest(description, value) + } + } + .filterNot { it.isNullOrBlank() } + .groupingBy { it } + .eachCount() + .entries + .joinToString(separator = "And") { (name, count) -> + name + if (count > 1) "s" else "" + } + + return buildString { + append("test") + append(description.compilableName?.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } + ?: "Method") + append(returnString) + if (parameters.isNotEmpty()) { + append("With", parameters) + } + } + } + + /** + * Display name of a test. + * + * Result example: + * 1. **Full name**: `firstArg = 12, secondArg < 100.0, thirdArg = empty string -> throw IllegalArgumentException` + * 2. **Name without appropriate information**: `arg_0 = 0 and others -> return 0` + * + * NOTE: The ```:``` symbol is used as a separator instead + * of ```->``` if the [UtSettings.GENERATE_DISPLAYNAME_FROM_TO_STYLE] is false. + */ + private fun createDisplayName( + description: FuzzedMethodDescription, + values: List, + result: UtExecutionResult? + ): String { + val displayNameSeparator = if (UtSettings.useDisplayNameArrowStyle) FROM_TO_NAMES_TRANSITION else FROM_TO_NAMES_COLON + + val summaries = values.asSequence() + .mapIndexed { index, value -> + value.summary?.replace("%var%", description.parameterNameMap(index) ?: "arg_$index") + } + .filterNotNull() + .toList() + + val postfix = when { + summaries.isEmpty() && values.isNotEmpty() -> "with generated values" + summaries.size < values.size -> " and others" + else -> "" + } + val parameters = summaries.joinToString(postfix = postfix) + + val returnValue = when (result) { + is UtExecutionSuccess -> result.model.let { m -> + when { + m is UtPrimitiveModel && m.classId != voidClassId -> "$displayNameSeparator return " + m.value + m is UtNullModel -> "$displayNameSeparator return null" + else -> null + } + } + + is UtExplicitlyThrownException, is UtImplicitlyThrownException -> "$displayNameSeparator throw ${(result as UtExecutionFailure).exception::class.java.simpleName}" + else -> null + } + + return listOfNotNull(parameters, returnValue).joinToString(separator = " ") + } + + /** + * Builds the JavaDoc. + */ + private fun createJavaDoc( + description: FuzzedMethodDescription, + values: List, + result: UtExecutionResult? + ): List { + return if (UtSettings.useCustomJavaDocTags) { + CommentWithCustomTagForTestProducedByFuzzerBuilder(description, values, result).buildDocStatements() + } else SimpleCommentForTestProducedByFuzzerBuilder(description, values, result).buildDocStatements() + } + + companion object { + private fun prettifyNumber(value: T): String? { + return when { + value.toDouble() == 0.0 -> "Zero" + value.toDouble() == 1.0 -> "One" + value is Double -> when { + value.isNaN() -> "Nan" + value.isInfinite() -> "Infinity" + else -> null + } + + (value is Byte || value is Short || value is Int || value is Long) && value.toLong() in 0..99999 -> value.toString() + else -> null + } + } + + private fun prettifyException(throwable: Throwable): String = + throwable.javaClass.simpleName + .toCharArray() + .asSequence() + .filter { it.isUpperCase() } + .joinToString(separator = "") + } +} \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/NameSuggester.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/NameSuggester.kt similarity index 82% rename from utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/NameSuggester.kt rename to utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/NameSuggester.kt index 33300f7c31..3c9dfd35ed 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/NameSuggester.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/NameSuggester.kt @@ -1,4 +1,4 @@ -package org.utbot.fuzzer.names +package org.utbot.summary.fuzzer.names import org.utbot.framework.plugin.api.UtExecutionResult import org.utbot.fuzzer.FuzzedMethodDescription @@ -6,11 +6,9 @@ import org.utbot.fuzzer.FuzzedValue /** * Name suggester generates a sequence of suggested test information such as: - * - test name - * - display test name. + * - method test name. + * - display name. */ interface NameSuggester { - fun suggest(description: FuzzedMethodDescription, values: List, result: UtExecutionResult?): Sequence - } \ No newline at end of file diff --git a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/SingleModelNameSuggester.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/SingleModelNameSuggester.kt similarity index 98% rename from utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/SingleModelNameSuggester.kt rename to utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/SingleModelNameSuggester.kt index 11339a87d4..5df9841484 100644 --- a/utbot-fuzzers/src/main/kotlin/org/utbot/fuzzer/names/SingleModelNameSuggester.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/SingleModelNameSuggester.kt @@ -1,4 +1,4 @@ -package org.utbot.fuzzer.names +package org.utbot.summary.fuzzer.names import org.utbot.framework.plugin.api.UtArrayModel import org.utbot.framework.plugin.api.UtPrimitiveModel diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/TestSuggestedInfo.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/TestSuggestedInfo.kt new file mode 100644 index 0000000000..a9ce55d95d --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/fuzzer/names/TestSuggestedInfo.kt @@ -0,0 +1,12 @@ +package org.utbot.summary.fuzzer.names + +import org.utbot.framework.plugin.api.DocStatement + +/** + * Information that can be used to generate test meta-information, including name, display name and JavaDoc. + */ +class TestSuggestedInfo( + val testName: String? = null, + val displayName: String? = null, + val javaDoc: List? = null +) \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/name/NameUtil.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/name/NameUtil.kt index 5370925280..483c3c8a64 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/name/NameUtil.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/name/NameUtil.kt @@ -1,6 +1,9 @@ package org.utbot.summary.name +import org.utbot.framework.plugin.api.ArtificialError import org.utbot.framework.plugin.api.Step +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.framework.plugin.api.util.prettyName import org.utbot.summary.tag.UniquenessTag import soot.SootMethod @@ -23,6 +26,8 @@ data class TestNameDescription( if (this.uniquenessTag == UniquenessTag.Common && other.uniquenessTag == UniquenessTag.Partly) return -1 if (this.uniquenessTag == UniquenessTag.Common && other.uniquenessTag == UniquenessTag.Unique) return -1 + if (this.nameType == NameType.ThrowsException && other.nameType != NameType.ThrowsException) return 1 + if (this.nameType != NameType.ThrowsException && other.nameType == NameType.ThrowsException) return -1 if (this.nameType == NameType.CaughtException && other.nameType != NameType.CaughtException) return 1 if (this.nameType != NameType.CaughtException && other.nameType == NameType.CaughtException) return -1 @@ -44,12 +49,22 @@ data class TestNameDescription( if (this.index > other.index) return 1 if (this.index < other.index) return -1 + return 0 } } enum class NameType { - Condition, Return, Invoke, SwitchCase, CaughtException, NoIteration, ThrowsException, StartIteration + Condition, + Return, + Invoke, + SwitchCase, + CaughtException, + NoIteration, + ThrowsException, + StartIteration, + ArtificialError, + TimeoutError } data class DisplayNameCandidate(val name: String, val uniquenessTag: UniquenessTag, val index: Int) @@ -61,4 +76,23 @@ fun List.returnsToUnique() = this.map { } else { it } +} + +fun buildNameFromThrowable(exception: Throwable): String? { + val exceptionName = exception.prettyName + + if (exceptionName.isNullOrEmpty()) return null + return when (exception) { + is TimeoutException -> "${exception.prettyName}Exceeded" + is ArtificialError -> "Detect${exception.prettyName}" + else -> "Throw$exceptionName" + } +} + +fun getThrowableNameType(exception: Throwable): NameType { + return when (exception) { + is ArtificialError -> NameType.ArtificialError + is TimeoutException -> NameType.TimeoutError + else -> NameType.ThrowsException + } } \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/name/NodeConvertor.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/name/NodeConvertor.kt deleted file mode 100644 index 8a67b6dfb6..0000000000 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/name/NodeConvertor.kt +++ /dev/null @@ -1,421 +0,0 @@ -package org.utbot.summary.name - -import com.github.javaparser.ast.Node -import com.github.javaparser.ast.body.VariableDeclarator -import com.github.javaparser.ast.expr.ArrayAccessExpr -import com.github.javaparser.ast.expr.ArrayCreationExpr -import com.github.javaparser.ast.expr.BinaryExpr -import com.github.javaparser.ast.expr.BooleanLiteralExpr -import com.github.javaparser.ast.expr.CastExpr -import com.github.javaparser.ast.expr.CharLiteralExpr -import com.github.javaparser.ast.expr.ClassExpr -import com.github.javaparser.ast.expr.ConditionalExpr -import com.github.javaparser.ast.expr.DoubleLiteralExpr -import com.github.javaparser.ast.expr.EnclosedExpr -import com.github.javaparser.ast.expr.FieldAccessExpr -import com.github.javaparser.ast.expr.InstanceOfExpr -import com.github.javaparser.ast.expr.IntegerLiteralExpr -import com.github.javaparser.ast.expr.LiteralExpr -import com.github.javaparser.ast.expr.LongLiteralExpr -import com.github.javaparser.ast.expr.MethodCallExpr -import com.github.javaparser.ast.expr.NameExpr -import com.github.javaparser.ast.expr.NullLiteralExpr -import com.github.javaparser.ast.expr.StringLiteralExpr -import com.github.javaparser.ast.expr.UnaryExpr -import com.github.javaparser.ast.expr.VariableDeclarationExpr -import com.github.javaparser.ast.stmt.CatchClause -import com.github.javaparser.ast.stmt.ExpressionStmt -import com.github.javaparser.ast.stmt.ForEachStmt -import com.github.javaparser.ast.stmt.ForStmt -import com.github.javaparser.ast.stmt.IfStmt -import com.github.javaparser.ast.stmt.ReturnStmt -import com.github.javaparser.ast.stmt.SwitchEntry -import com.github.javaparser.ast.stmt.SwitchStmt -import com.github.javaparser.ast.stmt.ThrowStmt -import com.github.javaparser.ast.stmt.WhileStmt -import org.utbot.framework.plugin.api.Step -import org.utbot.summary.SummarySentenceConstants.SEMI_COLON_SYMBOL -import org.utbot.summary.ast.JimpleToASTMap -import org.utbot.summary.comment.getTextIterationDescription -import soot.jimple.internal.JIfStmt -import soot.jimple.internal.JLookupSwitchStmt -import soot.jimple.internal.JReturnStmt -import soot.jimple.internal.JTableSwitchStmt - - -private const val STATEMENT_DECISION_TRUE = 1 -private const val STATEMENT_DECISION_FALSE = 0 - -class NodeConvertor { - - companion object { - /** - * Converts ASTNode into String - * @return String that can be a javadoc - */ - fun convertNodeToString(ASTNode: Node, step: Step): String? { - var res = "" - var node = ASTNode - if (node is EnclosedExpr) node = JimpleToASTMap.unEncloseExpr(node) - convertNodeToStringRecursively(node, step)?.let { - res += it - } - if (step.decision == STATEMENT_DECISION_TRUE) { - if (node is BooleanLiteralExpr - || node is NameExpr - || node is MethodCallExpr - || node is CastExpr - || node is FieldAccessExpr - || node is InstanceOfExpr - || node is ArrayAccessExpr - ) res = "Not$res" - else if (node is UnaryExpr && node.operator == UnaryExpr.Operator.LOGICAL_COMPLEMENT) { - res = - res.removePrefix(convertUnaryOperator(UnaryExpr.Operator.LOGICAL_COMPLEMENT)) // double negative expression is positive expression - } - } - return res.ifEmpty { null } - } - - /** - * Used in a conversion of Node into javadoc String - */ - private fun convertNodeToStringRecursively(ASTNode: Node, step: Step): String? { - val res = when (ASTNode) { - is EnclosedExpr -> convertNodeToStringRecursively(JimpleToASTMap.unEncloseExpr(ASTNode), step) - is BinaryExpr -> convertBinaryExpr(ASTNode, step) - is UnaryExpr -> - convertUnaryOperator(ASTNode.operator) + - convertNodeToStringRecursively(ASTNode.expression, step) - is NameExpr -> "${ASTNode.name}" - is LiteralExpr -> convertLiteralExpression(ASTNode) - is ArrayAccessExpr -> "${ASTNode.index.toString().capitalize()}Of${ - ASTNode.name.toString().capitalize() - }" - is FieldAccessExpr -> { - if (ASTNode.scope is FieldAccessExpr) "${ - convertNodeToStringRecursively( - ASTNode.scope, - step - ) - }${ASTNode.name.toString().capitalize()}" - else "${ASTNode.scope.toString().capitalize()}${ASTNode.name.toString().capitalize()}" - } - is CastExpr -> ASTNode.expression.toString().capitalize() - is MethodCallExpr -> { - if (ASTNode.scope.isPresent) "${ - ASTNode.scope.get().toString().capitalize() - }${ASTNode.name.toString().capitalize()}" - else ASTNode.name.toString().capitalize() - } - is InstanceOfExpr -> { - if (step.decision == STATEMENT_DECISION_TRUE) "${ - ASTNode.expression.toString().capitalize() - }NotInstanceOf${ASTNode.type.toString().capitalize()}" - else "${ASTNode.expression.toString().capitalize()}InstanceOf${ - ASTNode.type.toString().capitalize() - }" - } - is ClassExpr -> "${ASTNode.type}Class" - is ArrayCreationExpr -> "NewArrayOf${ASTNode.createdType().toString().capitalize()}" - is ReturnStmt -> { - if (ASTNode.expression.isPresent) convertNodeToStringRecursively( - ASTNode.expression.get(), - step - ) - else "" - } - is ConditionalExpr -> "${convertNodeToStringRecursively(ASTNode.condition, step)}" - is VariableDeclarationExpr -> ASTNode.variables.joinToString(separator = "") { variable -> - convertNodeToStringRecursively( - variable, - step - ) ?: "" - } - - is VariableDeclarator -> { - val initializer = ASTNode.initializer - if (initializer.isPresent) "${ASTNode.type.toString().capitalize()}${ - ASTNode.name.toString().capitalize() - }InitializedBy${convertNodeToStringRecursively(ASTNode.initializer.get(), step)}" - else "${ASTNode.type.toString().capitalize()}${ASTNode.name.toString().capitalize()}IsInitialized" - } - is CatchClause -> ASTNode.parameter.type.toString() - .capitalize() //add ${ASTNode.parameter.name.toString().capitalize() to print variable name - - is WhileStmt -> convertNodeToStringRecursively(ASTNode.condition, step) - is IfStmt -> convertNodeToStringRecursively(ASTNode.condition, step) - is ThrowStmt -> "Throws${ASTNode.expression.toString().removePrefix("new").capitalize()}" - is SwitchStmt -> convertSwitch(ASTNode, step) - is ExpressionStmt -> convertNodeToStringRecursively(ASTNode.expression, step) - - else -> { - null - } - } ?: return null - return postProcessName(res) - } - - /** - * Converts ASTNode into String - * @return String that can be a DisplayName - */ - fun convertNodeToDisplayNameString(ASTNode: Node, step: Step): String { - var node = ASTNode - if (node is ExpressionStmt) node = node.expression - if (node is EnclosedExpr) node = JimpleToASTMap.unEncloseExpr(node) - var res = convertNodeToDisplayNameStringRecursively(node, step) - if (node is ReturnStmt) node = node.expression.orElse(null) ?: node - if (step.stmt is JReturnStmt) return res - if (nodeContainsBooleanCondition(node)) { - res += if (step.decision == STATEMENT_DECISION_TRUE) { - " : False" - } else { - " : True" - } - } - return res - } - - /** - * Checks if node contain any boolean condition - */ - private fun nodeContainsBooleanCondition(node: Node): Boolean { - if (node is ArrayAccessExpr - || node is FieldAccessExpr - || node is CastExpr - || node is NameExpr - || node is BinaryExpr - || node is MethodCallExpr - || node is InstanceOfExpr - || node is UnaryExpr - || node is BooleanLiteralExpr - || node is VariableDeclarationExpr && node.variables.any { nodeContainsBooleanCondition(it) } - ) return true - if (node is VariableDeclarator) { - val initializer = node.initializer.orElse(null) - if (initializer != null) { - return nodeContainsBooleanCondition(initializer) - } - } - return false - } - - /** - * Used in a conversion of Node into DisplayName String - */ - private fun convertNodeToDisplayNameStringRecursively(ASTNode: Node, step: Step): String { - val res = when (ASTNode) { - is EnclosedExpr -> convertNodeToDisplayNameStringRecursively( - JimpleToASTMap.unEncloseExpr(ASTNode), - step - ) - is WhileStmt -> getTextIterationDescription(ASTNode) - is ForStmt -> getTextIterationDescription(ASTNode) - is ForEachStmt -> getTextIterationDescription(ASTNode) - is IfStmt -> convertNodeToDisplayNameStringRecursively(ASTNode.condition, step) - is ThrowStmt -> "Throws ${ASTNode.expression.toString().removePrefix("new").capitalize()}" - is SwitchStmt -> convertSwitch(ASTNode, step, removeSpaces = false) - is ExpressionStmt -> convertNodeToDisplayNameStringRecursively(ASTNode.expression, step) - is VariableDeclarationExpr -> ASTNode.variables.joinToString(separator = " ") { variable -> - convertNodeToDisplayNameStringRecursively( - variable, - step - ) - } - else -> { - ASTNode.toString() - } - } - return displayNamePostprocessor(res) - } - - /** - * Replaces one+ whitespaces with one whitespace - */ - private fun displayNamePostprocessor(displayName: String) = - displayName.replace("\\s+".toRegex(), " ").replace("$SEMI_COLON_SYMBOL", "") - - private fun convertBinaryExpr(binaryExpr: BinaryExpr, step: Step): String { - var res = "" - val left = convertNodeToStringRecursively(binaryExpr.left, step) - if (left != null) res += left.capitalize() - val stmt = step.stmt - res += if (stmt is JIfStmt) { - convertBinaryOperator( - binaryExpr.operator, - JimpleToASTMap.isOperatorReversed(stmt, binaryExpr), - step.decision - ).capitalize() - } else { - convertBinaryOperator(binaryExpr.operator, false, step.decision).capitalize() - } - val right = convertNodeToStringRecursively(binaryExpr.right, step) - if (right != null) res += right.capitalize() - return res - } - - /** - * Converts switchNode into String - */ - private fun convertSwitch(switchNode: SwitchStmt, step: Step, removeSpaces: Boolean = true): String { - val stmt = step.stmt - var res = "" - if (stmt is JLookupSwitchStmt) { - val lookup = stmt.lookupValues - val decision = step.decision - val case = ( - if (decision >= lookup.size) { - null - } else { - lookup[step.decision] - } - )?.value - res += JimpleToASTMap.getSwitchCaseLabel(switchNode, case).capitalize() - } - if (stmt is JTableSwitchStmt) { - val switchCase = JimpleToASTMap.mapSwitchCase(switchNode, step.decision) - if (switchCase is SwitchEntry) { - val case = switchCase.labels.first - res += if (case.isPresent) { - case.get().toString().capitalize() - } else { - "Default" - } - } - } - res = if (removeSpaces) { - "Switch${switchNode.selector.toString().capitalize()}Case" + res.replace(" ", "") - } else { - "switch(${switchNode.selector}) case: " + res - } - return res - } - - /** - * Converts literal into String - */ - private fun convertLiteralExpression(literal: LiteralExpr): String { - val res = when (literal) { - is StringLiteralExpr -> literal.asString() - is CharLiteralExpr -> if (isLegitSymbolForFunctionName(literal.asChar())) "${literal.asChar()}" else "Char" - is DoubleLiteralExpr -> { - when (val literalAsDouble = literal.asDouble()) { - Double.NaN -> "NaN" - Double.MIN_VALUE -> "MinValue" - Double.MAX_VALUE -> "MaxValue" - Double.NEGATIVE_INFINITY -> "NegativeInfinity" - Double.POSITIVE_INFINITY -> "Infinity" - else -> { - when { - literalAsDouble != literalAsDouble -> "NaN" - literalAsDouble < 0 -> "Negative${literal.asDouble().toInt()}d" - literalAsDouble == 0.0 -> "Zero" - literalAsDouble == -0.0 -> "Zero" - else -> "${ - literal.asDouble().toInt() - }d" //seems kinda wrong, . can be replaced with "dot" or something else - } - } - } - } - is IntegerLiteralExpr -> { - var str = "" - if (literal.asNumber().toInt() < 0) str += "Negative" - str += if (literal.asNumber().toInt() == 0) "Zero" else "$literal" - str - } - is LongLiteralExpr -> { - var str = "" - if (literal.asNumber().toLong() < 0) str += "Negative" - str += if (literal.asNumber().toInt() == 0) "Zero" else "$literal" - str - } - is BooleanLiteralExpr -> literal.toString() - is NullLiteralExpr -> "Null" - else -> "" - } - return res.capitalize() - } - - /** - * Checks if symbol can be used in function name - * @see Java documentation: Identifiers - */ - private fun isLegitSymbolForFunctionName(ch: Char): Boolean { - return (ch in '0'..'9' - || ch in 'a'..'z' - || ch in 'A'..'Z' - || ch == '_' - || ch == '$' - ) - } - - /** - * Filters out all of the symbols that cannot be used in a function name and capitalizes String - */ - private fun postProcessName(name: String) = name.filter { isLegitSymbolForFunctionName(it) }.capitalize() - - /** - * Converts Javaparser BinaryOperator and all of its children into a String - */ - private fun convertBinaryOperator( - binaryOperator: BinaryExpr.Operator, - isOperatorReversed: Boolean, - decision: Int - ): String { - var operator = binaryOperator - if ((isOperatorReversed && decision == STATEMENT_DECISION_TRUE) || (!isOperatorReversed && decision == STATEMENT_DECISION_FALSE)) { - operator = reverseBinaryOperator(operator) ?: binaryOperator - } - return when (operator) { - BinaryExpr.Operator.OR -> "Or" - BinaryExpr.Operator.AND -> "And" - BinaryExpr.Operator.BINARY_OR -> "BitwiseOr" - BinaryExpr.Operator.BINARY_AND -> "BitwiseAnd" - BinaryExpr.Operator.XOR -> "Xor" - BinaryExpr.Operator.EQUALS -> "Equals" - BinaryExpr.Operator.NOT_EQUALS -> "NotEquals" - BinaryExpr.Operator.LESS -> "LessThan" - BinaryExpr.Operator.GREATER -> "GreaterThan" - BinaryExpr.Operator.LESS_EQUALS -> "LessOrEqual" - BinaryExpr.Operator.GREATER_EQUALS -> "GreaterOrEqual" - BinaryExpr.Operator.LEFT_SHIFT -> "LeftShift" - BinaryExpr.Operator.SIGNED_RIGHT_SHIFT -> "RightShift" - BinaryExpr.Operator.UNSIGNED_RIGHT_SHIFT -> "UnsignedRightShift" - BinaryExpr.Operator.PLUS -> "Plus" - BinaryExpr.Operator.MINUS -> "Minus" - BinaryExpr.Operator.MULTIPLY -> "Multiply" - BinaryExpr.Operator.DIVIDE -> "Divide" - BinaryExpr.Operator.REMAINDER -> "RemainderOf" //does it sounds strange? or is it ok - } - } - - /** - * Reverts Javaparser binary operator if possible - */ - private fun reverseBinaryOperator(operator: BinaryExpr.Operator) = when (operator) { - BinaryExpr.Operator.EQUALS -> BinaryExpr.Operator.NOT_EQUALS - BinaryExpr.Operator.NOT_EQUALS -> BinaryExpr.Operator.EQUALS - BinaryExpr.Operator.LESS -> BinaryExpr.Operator.GREATER_EQUALS - BinaryExpr.Operator.GREATER -> BinaryExpr.Operator.LESS_EQUALS - BinaryExpr.Operator.LESS_EQUALS -> BinaryExpr.Operator.GREATER - BinaryExpr.Operator.GREATER_EQUALS -> BinaryExpr.Operator.LESS - else -> null - } - - /** - * Converts Javaparser unary operator to String - */ - private fun convertUnaryOperator(unaryOperator: UnaryExpr.Operator) = when (unaryOperator) { - UnaryExpr.Operator.PLUS -> "Plus" - UnaryExpr.Operator.MINUS -> "Negative" - UnaryExpr.Operator.PREFIX_INCREMENT -> "PrefixIncrement" - UnaryExpr.Operator.PREFIX_DECREMENT -> "PrefixDecrement" - UnaryExpr.Operator.LOGICAL_COMPLEMENT -> "Not" //! or LogicalComplement - UnaryExpr.Operator.BITWISE_COMPLEMENT -> "BitwiseComplement" - UnaryExpr.Operator.POSTFIX_INCREMENT -> "PostfixIncrement" - UnaryExpr.Operator.POSTFIX_DECREMENT -> "PostfixDecrement" - } - } -} \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/name/SimpleNameBuilder.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/name/SimpleNameBuilder.kt index 5a9d7e17c5..4c51cb46a6 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/name/SimpleNameBuilder.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/name/SimpleNameBuilder.kt @@ -1,466 +1,475 @@ -package org.utbot.summary.name - -import com.github.javaparser.ast.stmt.CatchClause -import com.github.javaparser.ast.stmt.ForStmt -import com.github.javaparser.ast.stmt.ThrowStmt -import org.utbot.framework.plugin.api.ConcreteExecutionFailureException -import org.utbot.framework.plugin.api.Step -import org.utbot.framework.plugin.api.exceptionOrNull -import org.utbot.framework.plugin.api.isFailure -import org.utbot.summary.AbstractTextBuilder -import org.utbot.summary.SummarySentenceConstants.FROM_TO_NAMES_TRANSITION -import org.utbot.summary.ast.JimpleToASTMap -import org.utbot.summary.comment.getExceptionReason -import org.utbot.summary.comment.getTextTypeIterationDescription -import org.utbot.summary.comment.shouldSkipInvoke -import org.utbot.summary.name.NodeConvertor.Companion.convertNodeToDisplayNameString -import org.utbot.summary.tag.BasicTypeTag -import org.utbot.summary.tag.CallOrderTag -import org.utbot.summary.tag.StatementTag -import org.utbot.summary.tag.TraceTagWithoutExecution -import org.utbot.summary.tag.UniquenessTag -import soot.SootMethod -import soot.jimple.internal.JAssignStmt -import soot.jimple.internal.JInvokeStmt -import soot.jimple.internal.JVirtualInvokeExpr - -class SimpleNameBuilder( - traceTag: TraceTagWithoutExecution, - sootToAST: MutableMap, - val methodUnderTest: SootMethod -) : - AbstractTextBuilder(traceTag, sootToAST) { - - private var testNameDescription: TestNameDescription? = null - private val testNames: MutableList = collectCandidateNames() - val fromToName = fromToName() - val name = buildMethodName() - val displayName = buildDisplayName() - - - private fun collectCandidateNames(): MutableList { - val testNames = mutableListOf() - collectTags(traceTag.rootStatementTag, testNames, methodUnderTest) - exceptionThrow(testNames) - return testNames - } - - /** - * Collects Tags and chooses node that will be used in name of the test case - * @return name of the test case - */ - private fun buildMethodName(): String { - val methodName = methodUnderTest.name.capitalize() - testNames.sortDescending() - testNameDescription = testNames.firstOrNull() - val subName = testNameDescription?.name - return if (subName != null) { - "test${methodName}_$subName" - } else { - "test$methodName" - } - } - - /** - * Should be run after build function, else testNameDescription is null and displayName will be empty - * - * @return string with the node that is used to create name of the function - * Such description can use special symbols and spaces - */ - private fun buildDisplayName(): String { - val nameDescription = testNameDescription - val sootMethod = testNameDescription?.method - val jimpleToASTMap = sootMethod?.let { sootToAST[it] } - var res = "" - if (nameDescription != null && jimpleToASTMap != null) { - val index = nameDescription.index - val step = traceTag.path[index] - val astNode = jimpleToASTMap[step.stmt] - - if (astNode != null) { - if (traceTag.result.isFailure) { - res += "Throw ${traceTag.result.exceptionOrNull()?.let { it::class.simpleName }}" - res += exceptionPlace(jimpleToASTMap) - } else if (index > 0) { - return convertNodeToDisplayNameString(astNode, step) - } - } - } - return res - } - - private fun exceptionPlace( - jimpleToASTMap: JimpleToASTMap, - placeAfter: String = " after condition: ", - placeIn: String = " in: " - ): String { - if (traceTag.path.isEmpty()) return "" - - if (traceTag.result.isFailure) { - val lastStep = traceTag.path.last() - val lastNode = jimpleToASTMap[lastStep.stmt] - if (lastNode is ThrowStmt) { - val exceptionReason = getExceptionReason(lastNode) ?: return "" - return placeAfter + convertNodeToDisplayNameString(exceptionReason, lastStep) - } else if (lastNode != null) { - return placeIn + convertNodeToDisplayNameString(lastNode, lastStep) - } - } - return "" - } - - private fun fromToName(): String { - val jimpleToASTMap = sootToAST[methodUnderTest] - val maxDepth = testNames.maxOfOrNull { it.depth } ?: 0 - - val candidateNames = testNames.returnsToUnique().filter { it.depth == maxDepth } - .filter { - it.nameType != NameType.StartIteration && it.nameType != NameType.NoIteration - }.mapNotNull { nameDescription -> - fromNameDescriptionToCandidateSimpleName(nameDescription) - }.toMutableList() - - if (traceTag.result.isFailure && jimpleToASTMap != null) { - val throwPlace = exceptionPlace(jimpleToASTMap, placeAfter = "", placeIn = "") - candidateNames.add( - 0, - DisplayNameCandidate( - throwPlace, - UniquenessTag.Unique, - traceTag.path.size - ) - ) - } - - val chosenNames = choosePairFromToNames(candidateNames) - if (chosenNames != null) { - val firstName = chosenNames.first - val secondName = chosenNames.second - if (firstName != null) { - return "$firstName $FROM_TO_NAMES_TRANSITION $secondName" - } else { - return "$FROM_TO_NAMES_TRANSITION $secondName" - } - } - return "" - } - - private fun fromNameDescriptionToCandidateSimpleName(nameDescription: TestNameDescription): DisplayNameCandidate? { - if (nameDescription.nameType == NameType.ThrowsException) { - return DisplayNameCandidate( - nameDescription.name, - nameDescription.uniquenessTag, - traceTag.path.size + 1 - ) - } - if (nameDescription.nameType == NameType.Invoke) { - return DisplayNameCandidate( - nameDescription.name, - nameDescription.uniquenessTag, - nameDescription.index - ) - } - val node = sootToAST[nameDescription.method]?.get(nameDescription.step.stmt) - if (node is CatchClause) { - return DisplayNameCandidate( - "Catch (${node.parameter})", - nameDescription.uniquenessTag, - nameDescription.index - ) - } - if (node != null) { - val name = convertNodeToDisplayNameString(node, nameDescription.step) - return DisplayNameCandidate( - name, - nameDescription.uniquenessTag, - nameDescription.index - ) - } else { - return null - } - } - - /* - * First, the method tries to find two unique tags which - * can represented as From unique tag -> To unique tag. - * Example: Unique condition -> Unique return. - * If the method didn't find two different and unique tags then - * it will try to build names in the following priority: - * 2. Partly Unique Tag -> Unique Tag - * 3. Partly Unique Tag -> Partly Unique Tag - * 4. Unique Tag -> Partly Unique Tag - * 4. Unique Tag -> Any Tag - * 5. Any Tag -> Unique Tag - * 6. Any Tag -> Partly Unique Tag - * 7. -> Unique Tag - * 8. -> Partly Unique Tag - * 9. -> Any last Tag - * otherwise, returns null - */ - private fun choosePairFromToNames(candidates: List): Pair? { - - val fromNameUnique = candidates.firstOrNull { it.uniquenessTag == UniquenessTag.Unique } - val toNameUnique = candidates.lastOrNull { it.uniquenessTag == UniquenessTag.Unique } - // from unique tag -> to unique tag - buildCandidate(fromNameUnique, toNameUnique, null)?.let { - return it - } - val fromNamePartly = candidates.firstOrNull { it.uniquenessTag == UniquenessTag.Partly } - val toNamePartly = candidates.lastOrNull { it.uniquenessTag == UniquenessTag.Partly } - // from partly unique tag -> to unique tag - // from partly unique tag -> to partly unique tag - buildCandidate(fromNamePartly, toNameUnique, toNamePartly)?.let { - return it - } - val toNameAny = candidates.lastOrNull() - // from unique tag -> to partly unique - // from unique tag -> to any - buildCandidate(fromNameUnique, toNamePartly, toNameAny)?.let { - return it - } - val fromNameAny = candidates.firstOrNull() - // from any tag -> to unique tag - // from any tag -> to partly unique tag - buildCandidate(fromNameAny, toNameUnique, toNamePartly)?.let { - return it - } - - if (toNameUnique != null) { - return Pair(null, toNameUnique.name) - } - if (toNamePartly != null) { - return Pair(null, toNamePartly.name) - } - if (toNameAny != null) { - return Pair(null, toNameAny.name) - } - return null - } - - /** - * The method tries to build a pair name with an attempt order: - * 1. from candidate name -> to candidate name 1 - * 2. from candidate name -> to candidate name 2 - * otherwise, returns null - */ - fun buildCandidate( - fromCandidateName: DisplayNameCandidate?, - toCandidateName1: DisplayNameCandidate?, - toCandidateName2: DisplayNameCandidate? - ): Pair? { - if (fromCandidateName != null && toCandidateName1 != null - && fromCandidateName.name != toCandidateName1.name - && fromCandidateName.index < toCandidateName1.index - ) { - return Pair(fromCandidateName.name, toCandidateName1.name) - } - if (fromCandidateName != null && toCandidateName2 != null - && fromCandidateName.name != toCandidateName2.name - && fromCandidateName.index < toCandidateName2.index - ) { - return Pair(fromCandidateName.name, toCandidateName2.name) - } - return null - } - - /** - * [TraceTagWithoutExecution.path] could be empty in case exception is thrown not in source code but in engine - * (for example, [ConcreteExecutionFailureException]). - */ - private fun exceptionThrow(testNames: MutableList) { - val throwsException = traceTag.result.exceptionOrNull()?.let { it::class.simpleName } - if (!(throwsException.isNullOrEmpty() || traceTag.path.isEmpty())) { - testNames.add(TestNameDescription( - "Throw$throwsException", - testNames.maxOfOrNull { it.depth } ?: 0, - testNames.maxOfOrNull { it.line } ?: 0, - UniquenessTag.Unique, - NameType.ThrowsException, - testNames.maxOfOrNull { it.index } ?: 0, - traceTag.path.last(), - methodUnderTest - )) - } - } - - private fun collectTags( - statementTag: StatementTag?, - testNames: MutableList, - currentMethod: SootMethod - ) { - val jimpleToASTMap = sootToAST[currentMethod] - if (statementTag == null) return - if (jimpleToASTMap == null) return - val recursion = statementTag.recursion - val stmt = statementTag.step.stmt - val depth = statementTag.step.depth - val line = statementTag.line - val invoke = statementTag.invoke - - val localNoIterations = statementTagSkippedIteration(statementTag, currentMethod) - if (localNoIterations.isNotEmpty()) { - localNoIterations.forEach { - testNames.add( - TestNameDescription( - "NoIteration${it.typeDescription}", - depth, - it.from, - UniquenessTag.Unique, - NameType.NoIteration, - statementTag.index, - statementTag.step, - currentMethod - ) - ) - methodToNoIterationDescription[currentMethod]?.remove(it) - } - } - - val invokeSootMethod = statementTag.invokeSootMethod() - if (invoke != null && invokeSootMethod != null) { - val beforeInvokeNumberNames = testNames.size - collectTags(invoke, testNames, invokeSootMethod) - if (testNames.size != beforeInvokeNumberNames) { - testNames.add( - beforeInvokeNumberNames, - TestNameDescription( - invokeSootMethod.name, - depth + 1, - 0, - UniquenessTag.Common, - NameType.Invoke, - statementTag.index, - statementTag.step, - invokeSootMethod - ) - ) - } - } - - if (jimpleToASTMap[statementTag.step.stmt] !is ForStmt) { - val nodeAST = jimpleToASTMap[stmt] - if (nodeAST != null) { - if (statementTag.basicTypeTag == BasicTypeTag.Condition && statementTag.callOrderTag == CallOrderTag.First) { - var conditionName: String? = null - if (statementTag.uniquenessTag == UniquenessTag.Unique - || (statementTag.uniquenessTag == UniquenessTag.Partly && statementTag.executionFrequency == 1) - ) { - conditionName = NodeConvertor.convertNodeToString(nodeAST, statementTag.step) - } - conditionName?.let { - testNames.add( - TestNameDescription( - it, - depth, - line, - statementTag.uniquenessTag, - NameType.Condition, - statementTag.index, - statementTag.step, - currentMethod - ) - ) - } - } - - - var prefix = "" - var name = NodeConvertor.convertNodeToString(nodeAST, statementTag.step) - var type: NameType? = null - - if (statementTag.basicTypeTag == BasicTypeTag.SwitchCase && statementTag.uniquenessTag == UniquenessTag.Unique) { - type = NameType.SwitchCase - } else if (statementTag.basicTypeTag == BasicTypeTag.CaughtException) { - type = NameType.CaughtException - prefix = "Catch" - } else if (statementTag.basicTypeTag == BasicTypeTag.Return) { - type = NameType.Return - prefix = "Return" - name = name ?: "" - } else if (statementTag.basicTypeTag == BasicTypeTag.Invoke && statementTag.uniquenessTag == UniquenessTag.Unique) { - val methodName = stmt.invokeExpr.method.name.capitalize() - if (!shouldSkipInvoke(methodName)) { - if (stmt is JAssignStmt || stmt is JInvokeStmt) { - type = NameType.Invoke - prefix += stmt.invokeExpr.methodRef.declaringClass.javaStyleName.substringBefore('$') //class name - prefix += methodName - name = - "" //todo change var name to val name, everything should be mapped through .convertNodeToString - } - } - } - - if (type != null) { - testNames.add( - TestNameDescription( - prefix + name, - depth, - line, - statementTag.uniquenessTag, - type, - statementTag.index, - statementTag.step, - currentMethod - ) - ) - } - } - } - - if (statementTag.iterations.isNotEmpty()) { - val firstNode = jimpleToASTMap[statementTag.iterations.first().step.stmt] - - val iterationDescription = if (firstNode != null) getTextTypeIterationDescription(firstNode) else null - - if (iterationDescription != null && iterationDescription.isNotEmpty()) { - testNames.add( - TestNameDescription( - "Iterate$iterationDescription", - depth, - line, - UniquenessTag.Partly, - NameType.StartIteration, - statementTag.index, - statementTag.step, - currentMethod - ) - ) - } - - statementTag.iterations.forEach { - collectTags(it, testNames, currentMethod) - } - } - - if (recursion != null) { - val name = when (stmt) { - is JAssignStmt -> "Recursion" + (stmt.rightBox.value as JVirtualInvokeExpr).method.name //todo through .convertNodeToString - is JInvokeStmt -> "Recursion" + stmt.invokeExpr.method.name //todo through .convertNodeToString - else -> "" - } - if (name.isNotEmpty()) { - testNames.add( - TestNameDescription( - name, - depth, - line, - statementTag.uniquenessTag, - NameType.Invoke, - statementTag.index, - statementTag.step, - currentMethod - ) - ) - collectTags(recursion, testNames, currentMethod) - } - } - collectTags(statementTag.next, testNames, currentMethod) - } - - override fun conditionStep(step: Step, reversed: Boolean, jimpleToASTMap: JimpleToASTMap): String { - val nodeAST = jimpleToASTMap[step.stmt] - return if (nodeAST != null) { - NodeConvertor.convertNodeToString(nodeAST, step) ?: "" - } else "" - } +package org.utbot.summary.name + +import com.github.javaparser.ast.stmt.CatchClause +import com.github.javaparser.ast.stmt.ForStmt +import com.github.javaparser.ast.stmt.ThrowStmt +import org.utbot.framework.plugin.api.InstrumentedProcessDeathException +import org.utbot.framework.plugin.api.Step +import org.utbot.framework.plugin.api.exceptionOrNull +import org.utbot.framework.plugin.api.isFailure +import org.utbot.summary.AbstractTextBuilder +import org.utbot.summary.NodeConverter +import org.utbot.summary.SummarySentenceConstants.FROM_TO_NAMES_TRANSITION +import org.utbot.summary.ast.JimpleToASTMap +import org.utbot.summary.comment.getExceptionReasonForName +import org.utbot.summary.comment.getTextTypeIterationDescription +import org.utbot.summary.comment.shouldSkipInvoke +import org.utbot.summary.NodeConverter.Companion.convertNodeToDisplayNameString +import org.utbot.summary.tag.BasicTypeTag +import org.utbot.summary.tag.CallOrderTag +import org.utbot.summary.tag.StatementTag +import org.utbot.summary.tag.TraceTagWithoutExecution +import org.utbot.summary.tag.UniquenessTag +import soot.SootMethod +import soot.jimple.internal.JAssignStmt +import soot.jimple.internal.JInvokeStmt +import soot.jimple.internal.JVirtualInvokeExpr + +class SimpleNameBuilder( + traceTag: TraceTagWithoutExecution, + sootToAST: MutableMap, + val methodUnderTest: SootMethod +) : + AbstractTextBuilder(traceTag, sootToAST) { + + private var testNameDescription: TestNameDescription? = null + private val testNames: MutableList = collectCandidateNames() + val fromToName = fromToName() + val name = buildMethodName() + val displayName = buildDisplayName() + + + private fun collectCandidateNames(): MutableList { + val testNames = mutableListOf() + collectTags(traceTag.rootStatementTag, testNames, methodUnderTest) + exceptionThrow(testNames) + return testNames + } + + /** + * Collects Tags and chooses node that will be used in name of the test case + * @return name of the test case + */ + private fun buildMethodName(): String { + val methodName = methodUnderTest.name.capitalize() + testNames.sortDescending() + testNameDescription = testNames.firstOrNull() + val subName = testNameDescription?.name + return if (subName != null) { + "test${methodName}_$subName" + } else { + "test$methodName" + } + } + + /** + * Should be run after build function, else testNameDescription is null and displayName will be empty + * + * @return string with the node that is used to create name of the function + * Such description can use special symbols and spaces + */ + private fun buildDisplayName(): String { + val nameDescription = testNameDescription + val sootMethod = testNameDescription?.method + val jimpleToASTMap = sootMethod?.let { sootToAST[it] } + var res = "" + if (nameDescription != null && jimpleToASTMap != null) { + val index = nameDescription.index + val step = traceTag.path[index] + val astNode = jimpleToASTMap[step.stmt] + + if (astNode != null) { + if (traceTag.result.isFailure) { + res += "Throw ${traceTag.result.exceptionOrNull()?.let { it::class.simpleName }}" + res += exceptionPlace(jimpleToASTMap) + } else if (index > 0) { + return convertNodeToDisplayNameString(astNode, step) + } + } + } + return res + } + + private fun exceptionPlace( + jimpleToASTMap: JimpleToASTMap, + placeAfter: String = " when: ", + placeIn: String = " in: " + ): String { + if (traceTag.path.isEmpty()) return "" + + if (traceTag.result.isFailure) { + val lastStep = traceTag.path.last() + val lastNode = jimpleToASTMap[lastStep.stmt] + if (lastNode is ThrowStmt) { + val exceptionReason = getExceptionReasonForName(lastNode) ?: return "" + return placeAfter + convertNodeToDisplayNameString(exceptionReason, lastStep) + } else if (lastNode != null) { + return placeIn + convertNodeToDisplayNameString(lastNode, lastStep) + } + } + return "" + } + + private fun fromToName(): String { + val jimpleToASTMap = sootToAST[methodUnderTest] + val maxDepth = testNames.maxOfOrNull { it.depth } ?: 0 + + val candidateNames = testNames.returnsToUnique().filter { it.depth == maxDepth } + .filter { + it.nameType != NameType.StartIteration && it.nameType != NameType.NoIteration + }.mapNotNull { nameDescription -> + fromNameDescriptionToCandidateSimpleName(nameDescription) + }.toMutableList() + + if (traceTag.result.isFailure && jimpleToASTMap != null) { + val throwPlace = exceptionPlace(jimpleToASTMap, placeAfter = "", placeIn = "") + candidateNames.add( + 0, + DisplayNameCandidate( + throwPlace, + UniquenessTag.Unique, + traceTag.path.size + ) + ) + } + + val chosenNames = choosePairFromToNames(candidateNames) + if (chosenNames != null) { + val firstName = chosenNames.first + val secondName = chosenNames.second + if (firstName != null) { + return "$firstName $FROM_TO_NAMES_TRANSITION $secondName" + } else { + return "$FROM_TO_NAMES_TRANSITION $secondName" + } + } + return "" + } + + private fun fromNameDescriptionToCandidateSimpleName(nameDescription: TestNameDescription): DisplayNameCandidate? { + if (nameDescription.nameType == NameType.ArtificialError || + nameDescription.nameType == NameType.TimeoutError || + nameDescription.nameType == NameType.ThrowsException + ) { + return DisplayNameCandidate( + nameDescription.name, + nameDescription.uniquenessTag, + traceTag.path.size + 1 + ) + } + if (nameDescription.nameType == NameType.Invoke) { + return DisplayNameCandidate( + nameDescription.name, + nameDescription.uniquenessTag, + nameDescription.index + ) + } + val node = sootToAST[nameDescription.method]?.get(nameDescription.step.stmt) + if (node is CatchClause) { + return DisplayNameCandidate( + "Catch (${node.parameter})", + nameDescription.uniquenessTag, + nameDescription.index + ) + } + if (node != null) { + val name = convertNodeToDisplayNameString(node, nameDescription.step) + return DisplayNameCandidate( + name, + nameDescription.uniquenessTag, + nameDescription.index + ) + } else { + return null + } + } + + /* + * First, the method tries to find two unique tags which + * can represented as From unique tag -> To unique tag. + * Example: Unique condition -> Unique return. + * If the method didn't find two different and unique tags then + * it will try to build names in the following priority: + * 2. Partly Unique Tag -> Unique Tag + * 3. Partly Unique Tag -> Partly Unique Tag + * 4. Unique Tag -> Partly Unique Tag + * 4. Unique Tag -> Any Tag + * 5. Any Tag -> Unique Tag + * 6. Any Tag -> Partly Unique Tag + * 7. -> Unique Tag + * 8. -> Partly Unique Tag + * 9. -> Any last Tag + * otherwise, returns null + */ + private fun choosePairFromToNames(candidates: List): Pair? { + + val fromNameUnique = candidates.firstOrNull { it.uniquenessTag == UniquenessTag.Unique } + val toNameUnique = candidates.lastOrNull { it.uniquenessTag == UniquenessTag.Unique } + // from unique tag -> to unique tag + buildCandidate(fromNameUnique, toNameUnique, null)?.let { + return it + } + val fromNamePartly = candidates.firstOrNull { it.uniquenessTag == UniquenessTag.Partly } + val toNamePartly = candidates.lastOrNull { it.uniquenessTag == UniquenessTag.Partly } + // from partly unique tag -> to unique tag + // from partly unique tag -> to partly unique tag + buildCandidate(fromNamePartly, toNameUnique, toNamePartly)?.let { + return it + } + val toNameAny = candidates.lastOrNull() + // from unique tag -> to partly unique + // from unique tag -> to any + buildCandidate(fromNameUnique, toNamePartly, toNameAny)?.let { + return it + } + val fromNameAny = candidates.firstOrNull() + // from any tag -> to unique tag + // from any tag -> to partly unique tag + buildCandidate(fromNameAny, toNameUnique, toNamePartly)?.let { + return it + } + + if (toNameUnique != null) { + return Pair(null, toNameUnique.name) + } + if (toNamePartly != null) { + return Pair(null, toNamePartly.name) + } + if (toNameAny != null) { + return Pair(null, toNameAny.name) + } + return null + } + + /** + * The method tries to build a pair name with an attempt order: + * 1. from candidate name -> to candidate name 1 + * 2. from candidate name -> to candidate name 2 + * otherwise, returns null + */ + fun buildCandidate( + fromCandidateName: DisplayNameCandidate?, + toCandidateName1: DisplayNameCandidate?, + toCandidateName2: DisplayNameCandidate? + ): Pair? { + if (fromCandidateName != null && toCandidateName1 != null + && fromCandidateName.name != toCandidateName1.name + && fromCandidateName.index < toCandidateName1.index + ) { + return Pair(fromCandidateName.name, toCandidateName1.name) + } + if (fromCandidateName != null && toCandidateName2 != null + && fromCandidateName.name != toCandidateName2.name + && fromCandidateName.index < toCandidateName2.index + ) { + return Pair(fromCandidateName.name, toCandidateName2.name) + } + return null + } + + /** + * [TraceTagWithoutExecution.path] could be empty in case exception is thrown not in source code but in engine + * (for example, [InstrumentedProcessDeathException]). + */ + private fun exceptionThrow(testNames: MutableList) { + val exception = traceTag.result.exceptionOrNull() ?: return + val name = buildNameFromThrowable(exception) + + if (name != null && traceTag.path.isNotEmpty()) { + val nameType = getThrowableNameType(exception) + + testNames.add(TestNameDescription( + name, + testNames.maxOfOrNull { it.depth } ?: 0, + testNames.maxOfOrNull { it.line } ?: 0, + UniquenessTag.Unique, + nameType, + testNames.maxOfOrNull { it.index } ?: 0, + traceTag.path.last(), + methodUnderTest + )) + } + } + + private fun collectTags( + statementTag: StatementTag?, + testNames: MutableList, + currentMethod: SootMethod + ) { + val jimpleToASTMap = sootToAST[currentMethod] + if (statementTag == null) return + if (jimpleToASTMap == null) return + val recursion = statementTag.recursion + val stmt = statementTag.step.stmt + val depth = statementTag.step.depth + val line = statementTag.line + val invoke = statementTag.invoke + + val localNoIterations = statementTagSkippedIteration(statementTag, currentMethod) + if (localNoIterations.isNotEmpty()) { + localNoIterations.forEach { + testNames.add( + TestNameDescription( + "NoIteration${it.typeDescription}", + depth, + it.from, + UniquenessTag.Unique, + NameType.NoIteration, + statementTag.index, + statementTag.step, + currentMethod + ) + ) + methodToNoIterationDescription[currentMethod]?.remove(it) + } + } + + val invokeSootMethod = statementTag.invokeSootMethod() + if (invoke != null && invokeSootMethod != null) { + val beforeInvokeNumberNames = testNames.size + collectTags(invoke, testNames, invokeSootMethod) + if (testNames.size != beforeInvokeNumberNames) { + testNames.add( + beforeInvokeNumberNames, + TestNameDescription( + invokeSootMethod.name, + depth + 1, + 0, + UniquenessTag.Common, + NameType.Invoke, + statementTag.index, + statementTag.step, + invokeSootMethod + ) + ) + } + } + + if (jimpleToASTMap[statementTag.step.stmt] !is ForStmt) { + val nodeAST = jimpleToASTMap[stmt] + if (nodeAST != null) { + if (statementTag.basicTypeTag == BasicTypeTag.Condition && statementTag.callOrderTag == CallOrderTag.First) { + var conditionName: String? = null + if (statementTag.uniquenessTag == UniquenessTag.Unique + || (statementTag.uniquenessTag == UniquenessTag.Partly && statementTag.executionFrequency == 1) + ) { + conditionName = NodeConverter.convertNodeToString(nodeAST, statementTag.step) + } + conditionName?.let { + testNames.add( + TestNameDescription( + it, + depth, + line, + statementTag.uniquenessTag, + NameType.Condition, + statementTag.index, + statementTag.step, + currentMethod + ) + ) + } + } + + + var prefix = "" + var name = NodeConverter.convertNodeToString(nodeAST, statementTag.step) + var type: NameType? = null + + if (statementTag.basicTypeTag == BasicTypeTag.SwitchCase && statementTag.uniquenessTag == UniquenessTag.Unique) { + type = NameType.SwitchCase + } else if (statementTag.basicTypeTag == BasicTypeTag.CaughtException) { + type = NameType.CaughtException + prefix = "Catch" + } else if (statementTag.basicTypeTag == BasicTypeTag.Return) { + type = NameType.Return + prefix = "Return" + name = name ?: "" + } else if (statementTag.basicTypeTag == BasicTypeTag.Invoke && statementTag.uniquenessTag == UniquenessTag.Unique) { + val declaringClass = stmt.invokeExpr.methodRef.declaringClass + val methodName = stmt.invokeExpr.method.name.capitalize() + if (!shouldSkipInvoke(declaringClass.name, methodName)) { + if (stmt is JAssignStmt || stmt is JInvokeStmt) { + type = NameType.Invoke + prefix += declaringClass.javaStyleName.substringBefore('$') + prefix += methodName + name = + "" //todo change var name to val name, everything should be mapped through .convertNodeToString + } + } + } + + if (type != null) { + testNames.add( + TestNameDescription( + prefix + name, + depth, + line, + statementTag.uniquenessTag, + type, + statementTag.index, + statementTag.step, + currentMethod + ) + ) + } + } + } + + if (statementTag.iterations.isNotEmpty()) { + val firstNode = jimpleToASTMap[statementTag.iterations.first().step.stmt] + + val iterationDescription = if (firstNode != null) getTextTypeIterationDescription(firstNode) else null + + if (iterationDescription != null && iterationDescription.isNotEmpty()) { + testNames.add( + TestNameDescription( + "Iterate$iterationDescription", + depth, + line, + UniquenessTag.Partly, + NameType.StartIteration, + statementTag.index, + statementTag.step, + currentMethod + ) + ) + } + + statementTag.iterations.forEach { + collectTags(it, testNames, currentMethod) + } + } + + if (recursion != null) { + val name = when (stmt) { + is JAssignStmt -> "Recursion" + (stmt.rightOp as JVirtualInvokeExpr).method.name //todo through .convertNodeToString + is JInvokeStmt -> "Recursion" + stmt.invokeExpr.method.name //todo through .convertNodeToString + else -> "" + } + if (name.isNotEmpty()) { + testNames.add( + TestNameDescription( + name, + depth, + line, + statementTag.uniquenessTag, + NameType.Invoke, + statementTag.index, + statementTag.step, + currentMethod + ) + ) + collectTags(recursion, testNames, currentMethod) + } + } + collectTags(statementTag.next, testNames, currentMethod) + } + + override fun conditionStep(step: Step, reversed: Boolean, jimpleToASTMap: JimpleToASTMap): String { + val nodeAST = jimpleToASTMap[step.stmt] + return if (nodeAST != null) { + NodeConverter.convertNodeToString(nodeAST, step) ?: "" + } else "" + } } \ No newline at end of file diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/tag/StatementTreeBuilder.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/tag/StatementTreeBuilder.kt index dff44ea997..230e56ec10 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/tag/StatementTreeBuilder.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/tag/StatementTreeBuilder.kt @@ -43,7 +43,7 @@ class StatementTreeBuilder(private val splitSteps: SplitSteps, private val trace * @see ExecutionStructureAnalysis * as they require initial tree tag structure. Otherwise it would be unreadable code here. */ - private fun buildStatementTree(startIndex: Int = 0, statementTag: StatementTag? = null, depth: Int = 0): Int { + private fun buildStatementTree(startIndex: Int = 0, statementTag: StatementTag? = null, depth: Int = 0, isInvokedRecursively: Boolean = false): Int { var currentIndex = startIndex var previousStatementTag = statementTag while (currentIndex < traceSteps.size) { @@ -54,14 +54,28 @@ class StatementTreeBuilder(private val splitSteps: SplitSteps, private val trace nextStatementTag?.let { when { it.step.depth < depth -> { - return currentIndex-- + // When analyzing a steps' path, we can accidentally return before we process the whole path, + // this leads to missing info in summaries. + // In order to solve it, we track whether we called the method from [StatementTreeBuilder.build()] + // or called it here recursively. + // In the first case we do not return, + // in the second case we return an index. + currentIndex-- + if (isInvokedRecursively) return currentIndex + else return@let } it.step.depth > depth -> { currentStatement.invoke = nextStatementTag + + // We save the nextStatementTag in .next as well at the end of the step path + // in order to recover info from it + if (currentIndex == traceSteps.lastIndex) currentStatement.next = nextStatementTag + currentIndex = buildStatementTree( currentIndex + 1, nextStatementTag, - nextStatementTag.step.depth + nextStatementTag.step.depth, + isInvokedRecursively = true ) currentIndex-- } diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/tag/TagUtils.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/tag/TagUtils.kt index c4ce6c3ef2..59c748e1e6 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/tag/TagUtils.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/tag/TagUtils.kt @@ -60,7 +60,7 @@ fun getBasicTypeTag(stmt: Stmt): BasicTypeTag = when (stmt) { } fun basicIdentityTag(stmt: JIdentityStmt): BasicTypeTag { - if (stmt.rightBox.value is JCaughtExceptionRef) { + if (stmt.rightOp is JCaughtExceptionRef) { return BasicTypeTag.CaughtException } return BasicTypeTag.Initialization diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/tag/TraceTag.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/tag/TraceTag.kt index 7fc15d7fab..f002310c99 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/tag/TraceTag.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/tag/TraceTag.kt @@ -1,11 +1,11 @@ package org.utbot.summary.tag -import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtSymbolicExecution import org.utbot.summary.SummarySentenceConstants.NEW_LINE import org.utbot.summary.clustering.SplitSteps -class TraceTag(val execution: UtExecution, splitSteps: SplitSteps) : TraceTagWithoutExecution(execution, splitSteps) { +class TraceTag(val execution: UtSymbolicExecution, splitSteps: SplitSteps) : TraceTagWithoutExecution(execution, splitSteps) { override fun toString(): String { return "${NEW_LINE}Input params:${execution.stateBefore.parameters} Output: $result${NEW_LINE} ${rootStatementTag.toString()}" } diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/tag/TraceTagWithoutExecution.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/tag/TraceTagWithoutExecution.kt index 87ef7e7cd7..cd3b99e12c 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/tag/TraceTagWithoutExecution.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/tag/TraceTagWithoutExecution.kt @@ -1,7 +1,7 @@ package org.utbot.summary.tag import org.utbot.framework.plugin.api.Step -import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtSymbolicExecution import org.utbot.framework.plugin.api.UtExecutionResult import org.utbot.summary.clustering.SplitSteps import soot.jimple.Stmt @@ -19,7 +19,7 @@ open class TraceTagWithoutExecution(val path: List, val result: UtExecutio val noIterationCall = mutableListOf>() var returnsToNumber: Map? = null - constructor(execution: UtExecution, splitSteps: SplitSteps) : this(execution.path, execution.result, splitSteps) + constructor(execution: UtSymbolicExecution, splitSteps: SplitSteps) : this(execution.path, execution.result, splitSteps) /* * If stmt is already contained in the previously registered iteration, it is not registered a second time. diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/usvm/USummarization.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/usvm/USummarization.kt new file mode 100644 index 0000000000..d4aab53e33 --- /dev/null +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/usvm/USummarization.kt @@ -0,0 +1,77 @@ +package org.utbot.summary.usvm + +import org.utbot.framework.plugin.api.UtMethodTestSet +import mu.KotlinLogging +import org.utbot.common.measureTime +import org.utbot.common.info +import org.utbot.framework.SummariesGenerationType.* +import org.utbot.framework.UtSettings.enableDisplayNameGeneration +import org.utbot.framework.UtSettings.enableJavaDocGeneration +import org.utbot.framework.UtSettings.enableTestNamesGeneration +import org.utbot.framework.UtSettings.summaryGenerationType +import org.utbot.summary.InvokeDescription +import org.utbot.summary.MethodDescriptionSource +import org.utbot.summary.Summarization +import java.io.File + +private val logger = KotlinLogging.logger {} + +/** +USummarization is used to generate summaries for *usvm-sbft*. + +To generate summary, use the following settings: +- *SummariesGenerationType == LIGHT* +- *enableTestNamesGeneration = true* +- *enableDisplayNameGeneration = false* +- *enableJavaDocGeneration = true* + */ + +fun Collection.summarizeAll(): List = + logger.info().measureTime({ + "----------------------------------------------------------------------------------------\n" + + "-------------------Summarization started for ${this.size} test cases--------------------\n" + + "----------------------------------------------------------------------------------------" + }) { + this.map { + it.summarizeOne() + } + } + +private fun UtMethodTestSet.summarizeOne(): UtMethodTestSet = + logger.info().measureTime({ "Summarization for ${this.method}" }) { + + if (summaryGenerationType != LIGHT || !enableTestNamesGeneration || enableDisplayNameGeneration || !enableJavaDocGeneration) { + logger.info { + "Incorrect settings are used to generate Summaries for usvm-sbft" + } + return this + } + + USummarization(sourceFile = null, invokeDescriptions = emptyList()).fillSummaries(this) + return this + } + +class USummarization(sourceFile: File?, invokeDescriptions: List) : + Summarization(sourceFile, invokeDescriptions) { + + /* + * Used to prepare methodTestSet for further generation of summaries. + * In the case of generating tests using USVM, we only need to work with Symbolic tests. + */ + override fun prepareMethodTestSet( + testSet: UtMethodTestSet, + descriptionSource: MethodDescriptionSource + ): UtMethodTestSet { + return when (descriptionSource) { + MethodDescriptionSource.FUZZER -> UtMethodTestSet( + method = testSet.method, + executions = emptyList(), + jimpleBody = testSet.jimpleBody, + errors = testSet.errors, + clustersInfo = testSet.clustersInfo + ) + + MethodDescriptionSource.SYMBOLIC -> testSet + } + } +} diff --git a/utbot-summary/src/test/kotlin/org/utbot/summary/clustering/ExecutionMetricTest.kt b/utbot-summary/src/test/kotlin/org/utbot/summary/clustering/ExecutionMetricTest.kt new file mode 100644 index 0000000000..853260f360 --- /dev/null +++ b/utbot-summary/src/test/kotlin/org/utbot/summary/clustering/ExecutionMetricTest.kt @@ -0,0 +1,26 @@ +package org.utbot.summary.clustering + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +import org.utbot.framework.plugin.api.Step +import java.lang.IllegalArgumentException + +internal class ExecutionMetricTest { + @Test + fun computeWithTwoEmptySteps() { + val executionMetric = ExecutionMetric() + val object1 = listOf() + val object2 = listOf() + + + val exception = Assertions.assertThrows(IllegalArgumentException::class.java) { + executionMetric.compute(object1 = object1, object2 = object2) + } + + Assertions.assertEquals( + "Two paths can not be compared: path1 is empty!", + exception.message + ) + } +} \ No newline at end of file diff --git a/utbot-summary/src/test/kotlin/org/utbot/summary/clustering/dbscan/DBSCANTrainerTest.kt b/utbot-summary/src/test/kotlin/org/utbot/summary/clustering/dbscan/DBSCANTrainerTest.kt new file mode 100644 index 0000000000..e00d951635 --- /dev/null +++ b/utbot-summary/src/test/kotlin/org/utbot/summary/clustering/dbscan/DBSCANTrainerTest.kt @@ -0,0 +1,227 @@ +package org.utbot.summary.clustering.dbscan + +import org.junit.jupiter.api.Test + +import org.junit.jupiter.api.Assertions.* +import org.utbot.summary.clustering.dbscan.neighbor.LinearRangeQuery +import java.lang.IllegalArgumentException +import kotlin.math.sqrt + +internal class DBSCANTrainerTest { + /** Helper test class for keeping ```(x, y)``` data. */ + data class Point(val x: Float, val y: Float) + + /** Helper [Metric] interface implementation, emulates the Euclidean distance. */ + class TestEuclideanMetric : Metric { + override fun compute(object1: Point, object2: Point): Double { + return sqrt((object2.y - object1.y) * (object2.y - object1.y) + (object2.x - object1.x) * (object2.x - object1.x)).toDouble(); + } + } + + @Test + fun emptyData() { + val testData = arrayOf() + + val dbscan = DBSCANTrainer( + eps = 0.3f, + minSamples = 10, + metric = TestEuclideanMetric(), + rangeQuery = LinearRangeQuery() + ) + + val exception = assertThrows(IllegalArgumentException::class.java) { + dbscan.fit(testData) + } + + assertEquals( + "Nothing to learn, data is empty.", + exception.message + ) + } + + /** + * Basic training on the synthetic data produced by the following Python script + * + * ``` + * import numpy as np + * + * from sklearn.cluster import DBSCAN + * from sklearn.datasets import make_blobs + * from sklearn.preprocessing import StandardScaler + * centers = [[1, 1], [-1, -1], [1, -1]] + * X, labels_true = make_blobs( n_samples=150, centers=centers, cluster_std=0.4, random_state=0) + * X = StandardScaler().fit_transform(X) + * ``` + */ + @Test + fun fit() { + val testData = arrayOf( + Point(0.51306161f, 1.1471073f), + Point(0.65512213f, -0.97066103f), + Point(1.26449613f, 1.83734944f), + Point(0.21216956f, -0.378767f), + Point(-1.14479616f, -1.11145131f), + Point(-1.58153887f, -0.08196208f), + Point(0.68254979f, 1.1919578f), + Point(0.8696672f, -0.64867363f), + Point(0.61143818f, -0.24018834f), + Point(1.00293973f, 0.97573626f), + Point(-1.31881688f, -0.01560197f), + Point(0.19938146f, -0.88057948f), + Point(0.70288688f, -0.45600334f), + Point(0.39380809f, -0.08454808f), + Point(0.72528092f, 1.41221765f), + Point(0.65361304f, 1.43176371f), + Point(0.32385524f, 1.03936418f), + Point(0.46518951f, 1.09421048f), + Point(-0.9317319f, -0.55894622f), + Point(0.96247469f, 1.31228971f), + Point(1.39551198f, 0.88413591f), + Point(-0.55513847f, -1.20821209f), + Point(-0.13006728f, 0.12120668f), + Point(0.34633163f, -1.25444427f), + Point(-1.17539483f, -0.16636096f), + Point(0.65798122f, -0.5354049f), + Point(0.40147441f, 1.12480245f), + Point(-1.08732589f, -0.74995774f), + Point(1.02084117f, -0.5595343f), + Point(0.83145875f, -0.41939857f), + Point(0.25429041f, 0.71164368f), + Point(0.82080917f, -1.76332956f), + Point(0.54271592f, 1.28676704f), + Point(-1.5439909f, -1.54936442f), + Point(0.4647383f, 0.80490875f), + Point(0.93527623f, -0.41244765f), + Point(0.29053258f, -0.81791807f), + Point(0.97237203f, -0.86484064f), + Point(0.24560256f, 1.675701f), + Point(-1.58357069f, -1.00510479f), + Point(0.43127435f, -0.70360332f), + Point(1.24950949f, -1.48959247f), + Point(-1.47038338f, -0.67631311f), + Point(0.78716138f, 0.93212787f), + Point(-1.30748385f, -1.1382141f), + Point(1.35500499f, 1.42078681f), + Point(-1.79807073f, -0.57907958f), + Point(0.84687941f, 0.66636195f), + Point(1.12595818f, 1.19478593f), + Point(-1.62915162f, 0.06104132f), + Point(0.29503262f, -0.84287903f), + Point(0.17436004f, 1.56779641f), + Point(-1.78931547f, -0.30544452f), + Point(0.40932172f, -0.83543907f), + Point(0.73407798f, 1.10835044f), + Point(-1.69686198f, -0.41757271f), + Point(-1.02900758f, -0.52437524f), + Point(-0.44552695f, -0.1624096f), + Point(0.04515838f, -0.44531824f), + Point(0.41639988f, 1.12356039f), + Point(0.41883977f, -0.87053195f), + Point(-1.06646137f, -0.76427654f), + Point(-1.75121296f, 0.07411488f), + Point(0.66875136f, 1.96066291f), + Point(0.74615069f, 1.64538505f), + Point(-1.4539805f, -0.9743326f), + Point(0.83834828f, 1.39488498f), + Point(1.14611708f, 1.73333403f), + Point(0.02666318f, 1.44518563f), + Point(0.61263928f, -0.79914282f), + Point(-0.5612403f, -0.33012658f), + Point(0.71430928f, 1.42150062f), + Point(-0.8271744f, -0.55964167f), + Point(1.11054723f, 0.78379483f), + Point(0.20866016f, 1.61584836f), + Point(-1.74117296f, -0.8536984f), + Point(0.45219304f, -0.52102926f), + Point(0.03304239f, 1.18200098f), + Point(-1.46240807f, 0.03735307f), + Point(-1.6835453f, -1.28496829f), + Point(0.52848656f, 1.32579874f), + Point(0.62424741f, 1.42485476f), + Point(-0.92140293f, -0.7435152f), + Point(0.72019561f, -0.80753388f), + Point(-1.77168534f, -0.35415786f), + Point(-0.99006985f, -0.36228449f), + Point(1.43008949f, -0.53114204f), + Point(-1.39699376f, -0.37048473f), + Point(-0.33447176f, 1.51953577f), + Point(-1.54094919f, -0.41958353f), + Point(1.24707045f, 2.00352637f), + Point(-1.05179021f, -0.32382983f), + Point(0.80410635f, 1.54016696f), + Point(0.77419081f, -0.72136257f), + Point(0.48321364f, -0.49553707f), + Point(-1.22688273f, -0.43571376f), + Point(-0.35946552f, -0.31515231f), + Point(-1.56393f, -0.74142087f), + Point(-0.85120093f, -1.10386605f), + Point(0.54370978f, -1.33609677f), + Point(-1.80709156f, -0.86295711f), + Point(-1.4306462f, -1.21880623f), + Point(1.56628119f, -1.09610687f), + Point(0.5429767f, -0.64517576f), + Point(0.7210137f, 1.8314722f), + Point(1.0476718f, 2.13794048f), + Point(0.82209878f, 0.99808183f), + Point(0.72589108f, -0.59266492f), + Point(0.31720674f, 0.49316348f), + Point(-0.95678938f, -0.93676362f), + Point(0.38067925f, -1.22208381f), + Point(0.50685865f, 1.74115147f), + Point(0.62138202f, -0.28566211f), + Point(0.31420085f, 1.41562276f), + Point(1.24935081f, 1.18495494f), + Point(-0.09312197f, -0.60957458f), + Point(0.25558171f, -0.21125889f), + Point(0.94997215f, 1.31513688f), + Point(-0.92055416f, -0.64901292f), + Point(0.34641694f, 0.59232248f), + Point(-0.00310758f, 2.02491012f), + Point(-1.33063994f, -0.94161521f), + Point(-0.53956611f, -0.1063121f), + Point(0.50831758f, -0.53894866f), + Point(-1.64934396f, -0.2479317f), + Point(1.54882393f, -0.69958647f), + Point(-1.13713306f, -1.10898152f), + Point(1.11560774f, -0.2625019f), + Point(1.09499453f, -0.42783123f), + Point(0.91515798f, -1.31309166f), + Point(-1.04742583f, -1.30728723f), + Point(0.93460287f, -0.17592166f), + Point(0.10733517f, -0.87532123f), + Point(0.69067372f, 1.38272846f), + Point(-1.87571495f, -0.51193531f), + Point(0.77670292f, -0.44591649f), + Point(1.03645977f, 1.20591592f), + Point(0.30957047f, 1.28512294f), + Point(-1.60652529f, -0.95177271f), + Point(-1.59341756f, -0.47303068f), + Point(0.41518085f, -0.83790075f), + Point(0.06165044f, -0.65847604f), + Point(0.85786827f, -0.7283573f), + Point(0.86856118f, -0.90745093f), + Point(-1.55601094f, -0.67072178f), + Point(-1.48701576f, 0.06862574f), + Point(1.55291185f, 0.69826175f), + Point(0.43088221f, -0.7758177f), + Point(-1.7243115f, -0.66279942f), + Point(0.52016266f, -0.77638553f) + ) + + val dbscan = DBSCANTrainer( + eps = 0.3f, + minSamples = 10, + metric = TestEuclideanMetric(), + rangeQuery = LinearRangeQuery() + ) + + val dbscanModel = dbscan.fit(testData) + val clusterLabels = dbscanModel.clusterLabels + + assertEquals(150, clusterLabels.size) + assertEquals(27, clusterLabels.count { it == 0 }) + assertEquals(35, clusterLabels.count { it == 1 }) + assertEquals(18, clusterLabels.count { it == 2 }) + assertEquals(70, clusterLabels.count { it == Int.MIN_VALUE }) + } +} \ No newline at end of file diff --git a/utbot-summary/src/test/kotlin/org/utbot/summary/comment/SimpleCommentBuilderTest.kt b/utbot-summary/src/test/kotlin/org/utbot/summary/comment/SimpleCommentBuilderTest.kt new file mode 100644 index 0000000000..17fa0d3d17 --- /dev/null +++ b/utbot-summary/src/test/kotlin/org/utbot/summary/comment/SimpleCommentBuilderTest.kt @@ -0,0 +1,83 @@ +package org.utbot.summary.comment + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` +import org.utbot.framework.plugin.api.Step +import org.utbot.framework.plugin.api.UtOverflowFailure +import org.utbot.summary.ast.JimpleToASTMap +import org.utbot.summary.comment.classic.symbolic.SimpleCommentBuilder +import org.utbot.summary.comment.customtags.getMethodReferenceForSymbolicTest +import org.utbot.summary.tag.StatementTag +import org.utbot.summary.tag.TraceTag +import soot.SootMethod +import soot.jimple.Stmt +import soot.jimple.internal.JReturnStmt + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SimpleCommentBuilderTest { + private lateinit var traceTag: TraceTag + private lateinit var jimpleToASTMap: JimpleToASTMap + private lateinit var sootToAst: MutableMap + private lateinit var sootMethod: SootMethod + private lateinit var statementTag: StatementTag + private lateinit var step: Step + private lateinit var statement: Stmt + + @BeforeAll + fun setUp() { + traceTag = mock(TraceTag::class.java) + sootMethod = mock(SootMethod::class.java) + jimpleToASTMap = mock(JimpleToASTMap::class.java) + statementTag = mock(StatementTag::class.java) + step = mock(Step::class.java) + statement = mock(JReturnStmt::class.java) + sootToAst = mutableMapOf() + + `when`(statementTag.step).thenReturn(step) + `when`(step.stmt).thenReturn(statement) + `when`(traceTag.path).thenReturn(listOf(step)) + `when`(traceTag.rootStatementTag).thenReturn(statementTag) + `when`(traceTag.result).thenReturn(UtOverflowFailure(Throwable())) + + sootToAst[sootMethod] = jimpleToASTMap + } + + @Test + fun `throws throwable if execution result is null`() { + val commentBuilder = SimpleCommentBuilder(traceTag, sootToAst) + val comment = commentBuilder.buildString(sootMethod) + val expectedComment = "
    \n" +
    +                "Test throws Throwable \n" +
    +                "
    " + assertEquals(expectedComment, comment) + } + + @Test + fun `builds one doc statement`() { + val commentBuilder = SimpleCommentBuilder(traceTag, sootToAst) + val statements = commentBuilder.buildDocStmts(sootMethod) + val expectedDocStatement = "Test \n" + + "throws Throwable \n" + assertEquals(statements.size, 1) + assertEquals(statements[0].toString(), expectedDocStatement) + } + + @Test + fun `builds inline link for method`() { + val methodReference = getMethodReferenceForSymbolicTest("org.utbot.ClassName", "methodName", listOf(), false) + val expectedMethodReference = "{@link org.utbot.ClassName#methodName()}" + assertEquals(methodReference, expectedMethodReference) + } + + @Test + fun `builds inline link for method in nested class`() { + val methodReference = + getMethodReferenceForSymbolicTest("org.utbot.ClassName\$NestedClassName", "methodName", listOf(), false) + val expectedMethodReference = "{@link org.utbot.ClassName.NestedClassName#methodName()}" + assertEquals(methodReference, expectedMethodReference) + } +} \ No newline at end of file diff --git a/utbot-summary/src/test/kotlin/org/utbot/summary/comment/SymbolicExecutionClusterCommentBuilderTest.kt b/utbot-summary/src/test/kotlin/org/utbot/summary/comment/SymbolicExecutionClusterCommentBuilderTest.kt new file mode 100644 index 0000000000..5a3b7fe255 --- /dev/null +++ b/utbot-summary/src/test/kotlin/org/utbot/summary/comment/SymbolicExecutionClusterCommentBuilderTest.kt @@ -0,0 +1,63 @@ +package org.utbot.summary.comment + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` +import org.utbot.framework.plugin.api.Step +import org.utbot.framework.plugin.api.UtOverflowFailure +import org.utbot.summary.ast.JimpleToASTMap +import org.utbot.summary.comment.cluster.SymbolicExecutionClusterCommentBuilder +import org.utbot.summary.tag.StatementTag +import org.utbot.summary.tag.TraceTag +import soot.SootMethod +import soot.jimple.Stmt +import soot.jimple.internal.JReturnStmt + +private const val EMPTY_STRING = "" + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SymbolicExecutionClusterCommentBuilderTest { + private lateinit var traceTag: TraceTag + private lateinit var jimpleToASTMap: JimpleToASTMap + private lateinit var sootToAst: MutableMap + private lateinit var sootMethod: SootMethod + private lateinit var statementTag: StatementTag + private lateinit var step: Step + private lateinit var statement: Stmt + + @BeforeAll + fun setUp() { + traceTag = mock(TraceTag::class.java) + sootMethod = mock(SootMethod::class.java) + jimpleToASTMap = mock(JimpleToASTMap::class.java) + statementTag = mock(StatementTag::class.java) + step = mock(Step::class.java) + statement = mock(JReturnStmt::class.java) + sootToAst = mutableMapOf() + + `when`(statementTag.step).thenReturn(step) + `when`(step.stmt).thenReturn(statement) + `when`(traceTag.path).thenReturn(listOf(step)) + `when`(traceTag.rootStatementTag).thenReturn(statementTag) + `when`(traceTag.result).thenReturn(UtOverflowFailure(Throwable())) + + sootToAst[sootMethod] = jimpleToASTMap + } + + @Test + fun `builds empty comment if execution result is null`() { + val commentBuilder = SymbolicExecutionClusterCommentBuilder(traceTag, sootToAst) + val comment = commentBuilder.buildString(sootMethod) + assertEquals(EMPTY_STRING, comment) + } + + @Test + fun `does not build any statements for javadoc if execution result is null`() { + val commentBuilder = SymbolicExecutionClusterCommentBuilder(traceTag, sootToAst) + val statements = commentBuilder.buildDocStmts(sootMethod) + assertEquals(statements.size, 0) + } +} \ No newline at end of file diff --git a/utbot-summary/src/test/kotlin/org/utbot/summary/name/SimpleNameBuilderTest.kt b/utbot-summary/src/test/kotlin/org/utbot/summary/name/SimpleNameBuilderTest.kt new file mode 100644 index 0000000000..fc0acf2a91 --- /dev/null +++ b/utbot-summary/src/test/kotlin/org/utbot/summary/name/SimpleNameBuilderTest.kt @@ -0,0 +1,74 @@ +package org.utbot.summary.name + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` +import org.utbot.framework.plugin.api.UtOverflowFailure +import org.utbot.summary.ast.JimpleToASTMap +import org.utbot.summary.tag.TraceTag +import org.utbot.summary.tag.UniquenessTag +import soot.SootMethod + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SimpleNameBuilderTest { + private lateinit var traceTag: TraceTag + private lateinit var jimpleToASTMap: JimpleToASTMap + private lateinit var sootToAst: MutableMap + private lateinit var sootMethod: SootMethod + + @BeforeAll + fun setUp() { + traceTag = mock(TraceTag::class.java) + sootMethod = mock(SootMethod::class.java) + jimpleToASTMap = mock(JimpleToASTMap::class.java) + sootToAst = mutableMapOf() + + `when`(traceTag.result).thenReturn(UtOverflowFailure(Throwable())) + + sootToAst[sootMethod] = jimpleToASTMap + } + + @Test + fun `method name should start with test`() { + `when`(sootMethod.name).thenReturn("methodName") + + val simpleNameBuilder = SimpleNameBuilder(traceTag, sootToAst, sootMethod) + val methodName = simpleNameBuilder.name + assert(methodName.startsWith("test")) + } + + @Test + fun `creates a name pair with a candidate with a bigger index`() { + `when`(sootMethod.name).thenReturn("") + + val simpleNameBuilder = SimpleNameBuilder(traceTag, sootToAst, sootMethod) + val fromCandidateName = DisplayNameCandidate("fromCandidate", UniquenessTag.Unique, 3) + + val toCandidate1 = DisplayNameCandidate("candidate1", UniquenessTag.Common, 2) + val toCandidate2 = DisplayNameCandidate("candidate2", UniquenessTag.Common, 4) + + val candidate = simpleNameBuilder.buildCandidate(fromCandidateName, toCandidate1, toCandidate2) + + val resultPair = Pair(fromCandidateName.name, toCandidate2.name) + assertEquals(candidate, resultPair) + } + + @Test + fun `returns null if candidates are equal`() { + `when`(sootMethod.name).thenReturn("") + + val simpleNameBuilder = SimpleNameBuilder(traceTag, sootToAst, sootMethod) + val fromCandidateName = DisplayNameCandidate("candidate", UniquenessTag.Unique, 0) + + // two equal candidates + val toCandidate1 = DisplayNameCandidate("candidate", UniquenessTag.Common, 1) + val toCandidate2 = DisplayNameCandidate("candidate", UniquenessTag.Common, 1) + + val candidate = simpleNameBuilder.buildCandidate(fromCandidateName, toCandidate1, toCandidate2) + + assertEquals(candidate, null) + } +} \ No newline at end of file diff --git a/utbot-summary/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/utbot-summary/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..1f0955d450 --- /dev/null +++ b/utbot-summary/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/utbot-testing/build.gradle b/utbot-testing/build.gradle new file mode 100644 index 0000000000..a66c022c29 --- /dev/null +++ b/utbot-testing/build.gradle @@ -0,0 +1,63 @@ +compileKotlin { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } +} + +compileTestKotlin { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_1_8 + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +dependencies { + api project(':utbot-framework-api') + + api project(':utbot-java-fuzzing') + api project(':utbot-instrumentation') + api project(':utbot-summary') + + implementation(project(":utbot-framework")) + testImplementation project(':utbot-sample') + testImplementation project(":utbot-framework").sourceSets.test.output + testImplementation project(":utbot-core").sourceSets.test.output + + implementation("org.unittestbot.soot:soot-utbot-fork:${sootVersion}") { + exclude group:'com.google.guava', module:'guava' + } + + implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: jacksonVersion + implementation group: 'com.github.curious-odd-man', name: 'rgxgen', version: rgxgenVersion + implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: log4j2Version + implementation group: 'io.github.microutils', name: 'kotlin-logging', version: kotlinLoggingVersion + implementation group: 'org.jacoco', name: 'org.jacoco.report', version: jacocoVersion + implementation group: 'org.apache.commons', name: 'commons-text', version: apacheCommonsTextVersion + // we need this for construction mocks from composite models + implementation group: 'org.mockito', name: 'mockito-core', version: mockitoVersion + + // To use JUnit4, comment out JUnit5 and uncomment JUnit4 dependencies here. Please also check "test" section + // testImplementation group: 'junit', name: 'junit', version: '4.13.1' + implementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.8.1' + implementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.1' + + // used for testing code generation + testImplementation group: 'commons-io', name: 'commons-io', version: commonsIoVersion + testImplementation group: 'junit', name: 'junit', version: junit4Version + testImplementation group: 'org.junit.platform', name: 'junit-platform-console-standalone', version: junit4PlatformVersion + testImplementation group: 'org.antlr', name: 'antlr4', version: antlrVersion + testImplementation group: 'org.mockito', name: 'mockito-core', version: mockitoVersion + testImplementation group: 'org.mockito', name: 'mockito-inline', version: mockitoInlineVersion + testImplementation group: 'com.google.guava', name: 'guava', version: guavaVersion + + testImplementation group: 'org.mockito', name: 'mockito-inline', version: mockitoInlineVersion + testImplementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j2Version + + // TODO sbft-usvm-merge: UtBot engine expects `com.github.UnitTestBot.ksmt` here + implementation group: 'io.ksmt', name: 'ksmt-core', version: ksmtVersion + implementation group: 'io.ksmt', name: 'ksmt-z3', version: ksmtVersion +} \ No newline at end of file diff --git a/utbot-testing/src/main/java/org/utbot/api/java/AbstractUtBotJavaApiTest.java b/utbot-testing/src/main/java/org/utbot/api/java/AbstractUtBotJavaApiTest.java new file mode 100644 index 0000000000..90b81bdb51 --- /dev/null +++ b/utbot-testing/src/main/java/org/utbot/api/java/AbstractUtBotJavaApiTest.java @@ -0,0 +1,114 @@ +package org.utbot.api.java; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.utbot.common.PathUtil; +import org.utbot.external.api.TestMethodInfo; +import org.utbot.external.api.UtModelFactory; +import org.utbot.framework.plugin.api.*; +import org.utbot.framework.plugin.api.util.UtContext; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.utbot.framework.plugin.api.util.IdUtilKt.getExecutableId; + +/** + * Extracted to share between modules + */ +public class AbstractUtBotJavaApiTest { + + public static final String GENERATED_TEST_CLASS_NAME = "GeneratedTest"; + + public static Method getMethodByName(Class clazz, String name, Class... parameters) { + try { + return clazz.getDeclaredMethod(name, parameters); + } catch (NoSuchMethodException ignored) { + Assertions.fail(); + } + throw new RuntimeException(); + } + + private AutoCloseable context; + // Helpful hand to create models for classes + protected UtModelFactory modelFactory; + + @BeforeEach + public void setUp() { + context = UtContext.Companion.setUtContext(new UtContext(UtContext.class.getClassLoader())); + modelFactory = new UtModelFactory(); + } + + @AfterEach + public void tearDown() { + try { + context.close(); + modelFactory = null; + } catch (Exception e) { + Assertions.fail(); + } + } + + protected static TestMethodInfo buildTestMethodInfo( + Method methodUnderTest, + UtCompositeModel classUnderTestModel, + List parametersModels, + Map staticsModels + ) { + + ExecutableId methodExecutableId = getExecutableId(methodUnderTest); + EnvironmentModels methodState = new EnvironmentModels( + classUnderTestModel, + parametersModels, + staticsModels, + methodExecutableId + ); + + return new TestMethodInfo(methodUnderTest, methodState); + } + + /** + * Utility method to get class path of the class + */ + @NotNull + protected static String getClassPath(Class clazz) { + try { + return normalizePath(clazz.getProtectionDomain().getCodeSource().getLocation()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + @NotNull + protected static String normalizePath(URL url) throws URISyntaxException { + return new File(url.toURI()).getPath(); + } + + /** + * @return classpath of the current thread. For testing environment only. + */ + @NotNull + protected static String getDependencyClassPath() { + + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + URL[] urls = PathUtil.getUrlsFromClassLoader(contextClassLoader); + + return Arrays.stream(urls).map(url -> + { + try { + return new File(url.toURI()).toString(); + } catch (URISyntaxException e) { + Assertions.fail(e); + } + throw new RuntimeException(); + }).collect(Collectors.joining(File.pathSeparator)); + } +} diff --git a/utbot-testing/src/main/kotlin/org/utbot/testing/CheckersUtil.kt b/utbot-testing/src/main/kotlin/org/utbot/testing/CheckersUtil.kt new file mode 100644 index 0000000000..2779d8a910 --- /dev/null +++ b/utbot-testing/src/main/kotlin/org/utbot/testing/CheckersUtil.kt @@ -0,0 +1,106 @@ +package org.utbot.testing + +import org.utbot.framework.codegen.domain.* +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.MockFramework +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.MockStrategyApi.NO_MOCKS +import org.utbot.framework.plugin.api.MockStrategyApi.OTHER_CLASSES +import org.utbot.framework.util.Conflict +import org.utbot.framework.util.ConflictTriggers + +data class TestInfrastructureConfiguration( + val projectType: ProjectType, + val testFramework: TestFramework, + val mockFramework: MockFramework, + val mockStrategy: MockStrategyApi, + val staticsMocking: StaticsMocking, + val parametrizedTestSource: ParametrizedTestSource, + val codegenLanguage: CodegenLanguage, + val forceStaticMocking: ForceStaticMocking, + val resetNonFinalFieldsAfterClinit: Boolean = true, + val generateUtilClassFile: Boolean, + val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.PASS, + val enableTestsTimeout: Boolean = false // our tests should not fail due to timeout +) { + val isParametrizedAndMocked: Boolean + get() = parametrizedTestSource == ParametrizedTestSource.PARAMETRIZE && + (mockStrategy != NO_MOCKS || + conflictTriggers[Conflict.ForceMockHappened] ?: false || conflictTriggers[Conflict.ForceStaticMockHappened] ?: false) + + val isDisabled: Boolean + get() = run { + // TODO Any? JIRA:1366 + if (codegenLanguage == CodegenLanguage.KOTLIN) return true + + // TODO a problem with try-with-resources JIRA:1329 + if (codegenLanguage == CodegenLanguage.KOTLIN && staticsMocking == MockitoStaticMocking) return true + + // TODO There is no assertArrayEquals JIRA:1416 + if (testFramework == TestNg) return true + + // because otherwise the code generator will not create mocks even for mandatory to mock classes + if (forceStaticMocking == ForceStaticMocking.FORCE && staticsMocking == NoStaticMocking) return true + + // junit4 doesn't support parametrized tests + if (testFramework == Junit4 && parametrizedTestSource == ParametrizedTestSource.PARAMETRIZE) return true + + //if we do not use mocks at all, we do not use static mocking too + if (mockStrategy == NO_MOCKS && staticsMocking == MockitoStaticMocking) return true + + // if we want to generate mocks for every class but CUT, we must have specified staticsMocking + if (mockStrategy == OTHER_CLASSES && staticsMocking == NoStaticMocking) return true + + return false + } +} + +val conflictTriggers: ConflictTriggers = ConflictTriggers() + +val allTestInfrastructureConfigurations: List = run { + val possibleConfiguration = mutableListOf() + + for (mockStrategy in listOf(NO_MOCKS, OTHER_CLASSES)) { + for (testFramework in TestFramework.allItems) { + val mockFramework = MockFramework.MOCKITO + val forceStaticMocking = ForceStaticMocking.FORCE + + for (staticsMocking in StaticsMocking.allItems) { + for (parametrizedTestSource in ParametrizedTestSource.allItems) { + for (codegenLanguage in CodegenLanguage.allItems) { + // We should not reset values for non-final static fields in parameterized tests + val resetNonFinalFieldsAfterClinit = + parametrizedTestSource == ParametrizedTestSource.DO_NOT_PARAMETRIZE + + possibleConfiguration += TestInfrastructureConfiguration( + ProjectType.PureJvm, + testFramework, + mockFramework, + mockStrategy, + staticsMocking, + parametrizedTestSource, + codegenLanguage, + forceStaticMocking, + resetNonFinalFieldsAfterClinit, + generateUtilClassFile = false + ) + possibleConfiguration += TestInfrastructureConfiguration( + ProjectType.PureJvm, + testFramework, + mockFramework, + mockStrategy, + staticsMocking, + parametrizedTestSource, + codegenLanguage, + forceStaticMocking, + resetNonFinalFieldsAfterClinit, + generateUtilClassFile = true + ) + } + } + } + } + } + + possibleConfiguration.toList() +} \ No newline at end of file diff --git a/utbot-testing/src/main/kotlin/org/utbot/testing/CodeGenerationIntegrationTest.kt b/utbot-testing/src/main/kotlin/org/utbot/testing/CodeGenerationIntegrationTest.kt new file mode 100644 index 0000000000..021a5d6417 --- /dev/null +++ b/utbot-testing/src/main/kotlin/org/utbot/testing/CodeGenerationIntegrationTest.kt @@ -0,0 +1,199 @@ +package org.utbot.testing + +import org.utbot.common.FileUtil +import org.utbot.common.withAccessibility +import org.utbot.framework.UtSettings +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.UtMethodTestSet +import org.utbot.instrumentation.ConcreteExecutor +import kotlin.reflect.KClass +import mu.KotlinLogging +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.MethodOrderer +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInfo +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.TestMethodOrder +import org.junit.jupiter.api.extension.BeforeAllCallback +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.fail +import org.junit.jupiter.engine.descriptor.ClassTestDescriptor +import org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.context.ApplicationContext +import java.nio.file.Path + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation::class) +@ExtendWith(CodeGenerationIntegrationTest.Companion.ReadRunningTestsNumberBeforeAllTestsCallback::class) +abstract class CodeGenerationIntegrationTest( + private val testClass: KClass<*>, + private var testCodeGeneration: Boolean = true, + private val configurationsToTest: List, + private val applicationContext: ApplicationContext = defaultApplicationContext, +) { + private val testSets: MutableList = arrayListOf() + + fun processTestSet(testSet: UtMethodTestSet) { + if (testCodeGeneration) testSets += testSet + } + + protected fun withEnabledTestingCodeGeneration(testCodeGeneration: Boolean, block: () -> Unit) { + val prev = this.testCodeGeneration + + try { + this.testCodeGeneration = testCodeGeneration + block() + } finally { + this.testCodeGeneration = prev + } + } + + // save all generated test cases from current class to test code generation + private fun addTestScenario(pkg: Package) { + if (testCodeGeneration) { + packageResult.getOrPut(pkg) { mutableListOf() } += TestScenario( + testClass, + testSets, + configurationsToTest + ) + } + } + + private fun cleanAfterProcessingPackage(pkg: Package) { + // clean test cases after cur package processing + packageResult[pkg]?.clear() + + // clean cur package test classes info + testClassesAmountByPackage[pkg] = 0 + processedTestClassesAmountByPackage[pkg] = 0 + } + + @Test + @Order(Int.MAX_VALUE) + fun processTestCases(testInfo: TestInfo) { + val pkg = testInfo.testClass.get().`package` + addTestScenario(pkg) + + // check if tests are inside package and current test is not the last one + if (runningTestsNumber > 1 && !isPackageFullyProcessed(testInfo.testClass.get())) { + logger.info("Package $pkg is not fully processed yet, code generation will be tested later") + return + } + ConcreteExecutor.defaultPool.close() + + FileUtil.clearTempDirectory(UtSettings.daysLimitForTempFiles) + + val testScenarios = packageResult[pkg] ?: return + try { + val pipelineErrors = mutableListOf() + + for (testScenarioForClass in testScenarios) { + for (configuration in testScenarioForClass.checkedConfigurations) { + try { + val classStages = ClassStages( + testScenarioForClass.testClass, + StageStatusCheck( + firstStage = CodeGeneration, + lastStage = configuration.lastStage, + status = ExecutionStatus.SUCCESS, + ), + testScenarioForClass.testSets, + ) + + val pipelineConfig = TestCodeGeneratorPipeline.configurePipeline(configuration) + TestCodeGeneratorPipeline(pipelineConfig, applicationContext).runClassesCodeGenerationTests(classStages) + } catch (e: RuntimeException) { + logger.warn(e) { "error in test pipeline" } + pipelineErrors.add(e.message) + } + } + } + + if (pipelineErrors.isNotEmpty()) + fail { pipelineErrors.joinToString(System.lineSeparator()) } + } finally { + cleanAfterProcessingPackage(pkg) + } + } + + companion object { + + init { + // trigger old temporary file deletion + FileUtil.OldTempFileDeleter + } + + private val packageResult: MutableMap> = mutableMapOf() + + private var allRunningTestClasses: List = mutableListOf() + + data class TestScenario( + val testClass: KClass<*>, + val testSets: List, + val checkedConfigurations: List, + ) + + val standardTestingConfigurations = listOf( + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.DO_NOT_PARAMETRIZE, TestExecution), + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.PARAMETRIZE, TestExecution), + Configuration(CodegenLanguage.KOTLIN, ParametrizedTestSource.DO_NOT_PARAMETRIZE, TestExecution), + ) + + val ignoreKotlinCompilationConfigurations = listOf( + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.DO_NOT_PARAMETRIZE, TestExecution), + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.PARAMETRIZE, TestExecution), + Configuration(CodegenLanguage.KOTLIN, ParametrizedTestSource.DO_NOT_PARAMETRIZE, CodeGeneration), + ) + + class ReadRunningTestsNumberBeforeAllTestsCallback : BeforeAllCallback { + override fun beforeAll(extensionContext: ExtensionContext) { + val clazz = Class.forName("org.junit.jupiter.engine.descriptor.AbstractExtensionContext") + val field = clazz.getDeclaredField("testDescriptor") + runningTestsNumber = field.withAccessibility { + val testDescriptor = field.get(extensionContext.parent.get()) + // get all running tests and filter disabled + allRunningTestClasses = (testDescriptor as JupiterEngineDescriptor).children + .map { it as ClassTestDescriptor } + .filter { it.testClass.getAnnotation(Disabled::class.java) == null } + .toList() + allRunningTestClasses.size + } + } + } + + private var processedTestClassesAmountByPackage: MutableMap = mutableMapOf() + private var testClassesAmountByPackage: MutableMap = mutableMapOf() + + private var runningTestsNumber: Int = 0 + + internal val logger = KotlinLogging.logger { } + + @JvmStatic + protected val testCaseGeneratorCache = mutableMapOf() + + data class BuildInfo(val buildDir: Path, val dependencyPath: String?) + + private fun getTestPackageSize(packageName: String): Int = + // filter all not disabled tests classes + allRunningTestClasses + .filter { it.testClass.`package`.name == packageName } + .distinctBy { it.testClass.name.substringBeforeLast("Kt") } + .size + + private fun isPackageFullyProcessed(testClass: Class<*>): Boolean { + val currentPackage = testClass.`package` + + if (currentPackage !in testClassesAmountByPackage) + testClassesAmountByPackage[currentPackage] = getTestPackageSize(currentPackage.name) + + processedTestClassesAmountByPackage.merge(currentPackage, 1, Int::plus) + + val processed = processedTestClassesAmountByPackage[currentPackage]!! + val total = testClassesAmountByPackage[currentPackage]!! + return processed == total + } + } +} \ No newline at end of file diff --git a/utbot-framework/src/test/kotlin/org/utbot/framework/codegen/CompilationAndRunUtils.kt b/utbot-testing/src/main/kotlin/org/utbot/testing/CompilationAndRunUtils.kt similarity index 82% rename from utbot-framework/src/test/kotlin/org/utbot/framework/codegen/CompilationAndRunUtils.kt rename to utbot-testing/src/main/kotlin/org/utbot/testing/CompilationAndRunUtils.kt index 72c3cb2f88..dcaf335f47 100644 --- a/utbot-framework/src/test/kotlin/org/utbot/framework/codegen/CompilationAndRunUtils.kt +++ b/utbot-testing/src/main/kotlin/org/utbot/testing/CompilationAndRunUtils.kt @@ -1,10 +1,12 @@ -package org.utbot.framework.codegen +package org.utbot.testing import org.utbot.framework.plugin.api.CodegenLanguage import java.io.File import java.nio.file.Path -import mu.KotlinLogging -import org.apache.commons.io.FileUtils +import org.utbot.common.FileUtil +import org.utbot.framework.codegen.domain.Junit5 +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.process.OpenModulesContainer data class ClassUnderTest( val testClassSimpleName: String, @@ -12,7 +14,12 @@ data class ClassUnderTest( val generatedTestFile: File ) -private val logger = KotlinLogging.logger {} +fun writeFile(fileContents: String, targetFile: File): File { + val targetDir = targetFile.parentFile + targetDir.mkdirs() + targetFile.writeText(fileContents) + return targetFile +} fun writeTest( testContents: String, @@ -26,13 +33,10 @@ fun writeTest( File(buildDirectory.toFile(), "${testClassName.substringAfterLast(".")}${generatedLanguage.extension}") ) - val targetDir = classUnderTest.generatedTestFile.parentFile - targetDir.mkdirs() logger.info { - "File size for ${classUnderTest.testClassSimpleName}: ${FileUtils.byteCountToDisplaySize(testContents.length.toLong())}" + "File size for ${classUnderTest.testClassSimpleName}: ${FileUtil.byteCountToDisplaySize(testContents.length.toLong())}" } - classUnderTest.generatedTestFile.writeText(testContents) - return classUnderTest.generatedTestFile + return writeFile(testContents, classUnderTest.generatedTestFile) } fun compileTests( @@ -56,8 +60,18 @@ fun runTests( ) { val classpath = System.getProperty("java.class.path") + File.pathSeparator + buildDirectory val executionInvoke = generatedLanguage.executorInvokeCommand + val additionalArguments = buildList { + addAll(OpenModulesContainer.javaVersionSpecificArguments) + add("-ea") + } - val command = testFramework.getRunTestsCommand(executionInvoke, classpath, testsNames, buildDirectory) + val command = testFramework.getRunTestsCommand( + executionInvoke, + classpath, + testsNames, + buildDirectory, + additionalArguments + ) logger.trace { "Command to run test: [${command.joinToString(" ")}]" } diff --git a/utbot-testing/src/main/kotlin/org/utbot/testing/Configurations.kt b/utbot-testing/src/main/kotlin/org/utbot/testing/Configurations.kt new file mode 100644 index 0000000000..e45de519d9 --- /dev/null +++ b/utbot-testing/src/main/kotlin/org/utbot/testing/Configurations.kt @@ -0,0 +1,47 @@ +package org.utbot.testing + +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.context.simple.SimpleApplicationContext +import org.utbot.framework.context.simple.SimpleMockerContext +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.MockStrategyApi + +interface AbstractConfiguration { + val projectType: ProjectType + val mockStrategy: MockStrategyApi + val language: CodegenLanguage + val parametrizedTestSource: ParametrizedTestSource + val lastStage: Stage +} + +data class Configuration( + override val language: CodegenLanguage, + override val parametrizedTestSource: ParametrizedTestSource, + override val lastStage: Stage, +): AbstractConfiguration { + override val projectType: ProjectType + get() = ProjectType.PureJvm + + override val mockStrategy: MockStrategyApi + get() = MockStrategyApi.defaultItem +} + +data class SpringConfiguration( + override val language: CodegenLanguage, + override val parametrizedTestSource: ParametrizedTestSource, + override val lastStage: Stage, +): AbstractConfiguration { + override val projectType: ProjectType + get() = ProjectType.Spring + + override val mockStrategy: MockStrategyApi + get() = MockStrategyApi.springDefaultItem +} + +val defaultApplicationContext = SimpleApplicationContext( + SimpleMockerContext( + mockFrameworkInstalled = true, + staticsMockingIsConfigured = true, + ) +) diff --git a/utbot-testing/src/main/kotlin/org/utbot/testing/TestCodeGeneratorPipeline.kt b/utbot-testing/src/main/kotlin/org/utbot/testing/TestCodeGeneratorPipeline.kt new file mode 100644 index 0000000000..4e1d16a386 --- /dev/null +++ b/utbot-testing/src/main/kotlin/org/utbot/testing/TestCodeGeneratorPipeline.kt @@ -0,0 +1,434 @@ +package org.utbot.testing + +import mu.KotlinLogging +import org.junit.jupiter.api.Assertions.assertFalse +import org.utbot.common.FileUtil +import org.utbot.common.measureTime +import org.utbot.common.info +import org.utbot.framework.codegen.generator.CodeGeneratorResult +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.codegen.generator.CodeGeneratorParams +import org.utbot.framework.codegen.services.language.CgLanguageAssistant +import org.utbot.framework.codegen.tree.ututils.UtilClassKind +import org.utbot.framework.codegen.tree.ututils.UtilClassKind.Companion.UT_UTILS_INSTANCE_NAME +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.description +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.withUtContext +import java.io.File +import java.nio.file.Path +import kotlin.reflect.KClass + +internal val logger = KotlinLogging.logger {} + +class TestCodeGeneratorPipeline( + private val testInfrastructureConfiguration: TestInfrastructureConfiguration, + private val applicationContext: ApplicationContext +) { + + fun runClassesCodeGenerationTests(classesStages: ClassStages) { + val pipeline = with(classesStages) { + ClassPipeline(StageContext(testClass, testSets, testSets.size), check) + } + + checkPipelinesResults(pipeline) + } + + private fun runPipelinesStages(classPipeline: ClassPipeline): CodeGenerationResult { + val classUnderTest = classPipeline.stageContext.classUnderTest + val classPipelineName = classUnderTest.qualifiedName + ?: error("${classUnderTest.simpleName} doesn't have a fqn name") + + logger + .info() + .measureTime({ "Executing code generation tests for [$classPipelineName]" }) { + CodeGeneration.verifyPipeline(classPipeline)?.let { + withUtContext(UtContext(classUnderTest.java.classLoader)) { + processCodeGenerationStage(it) + } + } + + Compilation.verifyPipeline(classPipeline)?.let { + processCompilationStages(it) + } + + TestExecution.verifyPipeline(classPipeline)?.let { + processTestExecutionStages(it) + } + + return with(classPipeline.stageContext) { + CodeGenerationResult(classUnderTest, numberOfTestCases, stages) + } + } + } + + @Suppress("UNCHECKED_CAST") + private fun processCodeGenerationStage(classPipeline: ClassPipeline) { + with(classPipeline.stageContext) { + val information = StageExecutionInformation(CodeGeneration) + val testSets = data as List + + val codegenLanguage = testInfrastructureConfiguration.codegenLanguage + val parametrizedTestSource = testInfrastructureConfiguration.parametrizedTestSource + + val codeGenerationResult = callToCodeGenerator(testSets, classUnderTest) + val testClass = codeGenerationResult.generatedCode + + // actual number of the tests in the generated testClass + val generatedMethodsCount = testClass + .lines() + .count { + val trimmedLine = it.trimStart() + val prefix = when (codegenLanguage) { + CodegenLanguage.JAVA -> + when (parametrizedTestSource) { + ParametrizedTestSource.DO_NOT_PARAMETRIZE -> "@Test" + ParametrizedTestSource.PARAMETRIZE -> "public void parameterizedTestsFor" + } + + CodegenLanguage.KOTLIN -> + when (parametrizedTestSource) { + ParametrizedTestSource.DO_NOT_PARAMETRIZE -> "@Test" + ParametrizedTestSource.PARAMETRIZE -> "fun parameterizedTestsFor" + } + } + trimmedLine.startsWith(prefix) + } + // expected number of the tests in the generated testClass + val expectedNumberOfGeneratedMethods = + when (parametrizedTestSource) { + ParametrizedTestSource.DO_NOT_PARAMETRIZE -> testSets.sumOf { it.executions.size } + ParametrizedTestSource.PARAMETRIZE -> testSets.filter { it.executions.isNotEmpty() }.size + } + + // check for error in the generated file + runCatching { + val separator = System.lineSeparator() + require(ERROR_REGION_BEGINNING !in testClass) { + val lines = testClass.lines().withIndex().toList() + + val errorsRegionsBeginningIndices = lines + .filter { it.value.trimStart().startsWith(ERROR_REGION_BEGINNING) } + + val errorsRegionsEndIndices = lines + .filter { it.value.trimStart().startsWith(ERROR_REGION_END) } + + val errorRegions = errorsRegionsBeginningIndices.map { beginning -> + val endIndex = errorsRegionsEndIndices.indexOfFirst { it.index > beginning.index } + lines.subList(beginning.index + 1, errorsRegionsEndIndices[endIndex].index).map { it.value } + } + + val errorText = errorRegions.joinToString(separator, separator, separator) { errorRegion -> + val text = errorRegion.joinToString(separator = separator) + "Error region in ${classUnderTest.simpleName}: $text" + } + + logger.error(errorText) + + "Errors regions has been generated: $errorText" + } + + // for now, we skip a comparing of generated and expected test methods + // in parametrized test generation mode + // because there are problems with determining expected number of methods, + // due to a feature that generates several separated parametrized tests + // when we have several executions with different result type + if (parametrizedTestSource != ParametrizedTestSource.PARAMETRIZE) { + require(generatedMethodsCount == expectedNumberOfGeneratedMethods) { + "Something went wrong during the code generation for ${classUnderTest.simpleName}. " + + "Expected to generate $expectedNumberOfGeneratedMethods test methods, " + + "but got only $generatedMethodsCount" + } + } + }.onFailure { + val classes = listOf(classPipeline).retrieveClasses() + val buildDirectory = classes.createBuildDirectory() + + val testClassName = classPipeline.retrieveTestClassName("BrokenGeneratedTest") + val generatedTestFile = writeTest(testClass, testClassName, buildDirectory, codegenLanguage) + val generatedUtilClassFile = codeGenerationResult.utilClassKind?.writeUtilClassToFile(buildDirectory, codegenLanguage) + + logger.error("Broken test has been written to the file: [$generatedTestFile]") + if (generatedUtilClassFile != null) { + logger.error("Util class for the broken test has been written to the file: [$generatedUtilClassFile]") + } + logger.error("Failed configuration: $testInfrastructureConfiguration") + + throw it + } + + classPipeline.stageContext = copy(data = codeGenerationResult, stages = stages + information.completeStage()) + } + } + + private fun UtilClassKind.writeUtilClassToFile(buildDirectory: Path, language: CodegenLanguage): File { + val utilClassFile = File(buildDirectory.toFile(), "$UT_UTILS_INSTANCE_NAME${language.extension}") + val utilClassText = getUtilClassText(language) + return writeFile(utilClassText, utilClassFile) + } + + private data class GeneratedTestClassInfo( + val testClassName: String, + val generatedTestFile: File, + val generatedUtilClassFile: File? + ) + + @Suppress("UNCHECKED_CAST") + private fun processCompilationStages(classesPipeline: ClassPipeline) { + val information = StageExecutionInformation(Compilation) + val classes = listOf(classesPipeline).retrieveClasses() + val buildDirectory = classes.createBuildDirectory() + + val codegenLanguage = testInfrastructureConfiguration.codegenLanguage + + val codeGeneratorResult = classesPipeline.stageContext.data as CodeGeneratorResult//String + val testClass = codeGeneratorResult.generatedCode + + val testClassName = classesPipeline.retrieveTestClassName("GeneratedTest") + val generatedTestFile = writeTest(testClass, testClassName, buildDirectory, codegenLanguage) + val generatedUtilClassFile = codeGeneratorResult.utilClassKind?.writeUtilClassToFile(buildDirectory, codegenLanguage) + + logger.info("Test has been written to the file: [$generatedTestFile]") + if (generatedUtilClassFile != null) { + logger.info("Util class for the test has been written to the file: [$generatedUtilClassFile]") + } + + val testClassesNamesToTestGeneratedTests = GeneratedTestClassInfo(testClassName, generatedTestFile, generatedUtilClassFile) + + + val sourceFiles = mutableListOf().apply { + testClassesNamesToTestGeneratedTests.generatedTestFile.absolutePath?.let { this += it } + testClassesNamesToTestGeneratedTests.generatedUtilClassFile?.absolutePath?.let { this += it } + } + compileTests( + "$buildDirectory", + sourceFiles, + codegenLanguage + ) + + classesPipeline.stageContext = classesPipeline.stageContext.copy( + data = CompilationResult("$buildDirectory", testClassesNamesToTestGeneratedTests.testClassName), + stages = classesPipeline.stageContext.stages + information.completeStage() + ) + } + + /** + * For simple CUT equals to its fqn + [testSuffix] suffix, + * for nested CUT is its package + dot + its simple name + [testSuffix] suffix (to avoid outer class mention). + */ + private fun ClassPipeline.retrieveTestClassName(testSuffix: String): String = + stageContext.classUnderTest.let { "${it.java.`package`.name}.${it.simpleName}" } + testSuffix + + private fun List>.createBuildDirectory() = + FileUtil.isolateClassFiles(*toTypedArray()).toPath() + + private fun List.retrieveClasses() = map { it.stageContext.classUnderTest.java } + + @Suppress("UNCHECKED_CAST") + private fun processTestExecutionStages(classesPipeline: ClassPipeline) { + val information = StageExecutionInformation(TestExecution) + val compilationResult = classesPipeline.stageContext.data as CompilationResult + val buildDirectory = compilationResult.buildDirectory + val testClassesNames = listOf(compilationResult.testClassName) + + with(testInfrastructureConfiguration) { + runTests(buildDirectory, testClassesNames, testFramework, codegenLanguage) + } + + classesPipeline.stageContext = classesPipeline.stageContext.copy( + data = Unit, + stages = classesPipeline.stageContext.stages + information.completeStage() + ) + } + + private fun callToCodeGenerator( + testSets: List, + classUnderTest: KClass<*> + ): CodeGeneratorResult { + val params = mutableMapOf>() + + withUtContext(UtContext(classUnderTest.java.classLoader)) { + val codeGenerator = with(testInfrastructureConfiguration) { + applicationContext.createCodeGenerator( + CodeGeneratorParams( + classUnderTest.id, + projectType = projectType, + generateUtilClassFile = generateUtilClassFile, + paramNames = params, + testFramework = testFramework, + staticsMocking = staticsMocking, + forceStaticMocking = forceStaticMocking, + generateWarningsForStaticMocking = false, + codegenLanguage = codegenLanguage, + cgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage(codegenLanguage), + parameterizedTestSource = parametrizedTestSource, + runtimeExceptionTestsBehaviour = runtimeExceptionTestsBehaviour, + enableTestsTimeout = enableTestsTimeout, + ) + ) + } + val testClassCustomName = "${classUnderTest.java.simpleName}GeneratedTest" + + return codeGenerator.generateAsStringWithTestReport(testSets, testClassCustomName) + } + } + + private fun checkPipelinesResults(classesPipeline: ClassPipeline) { + val result = runPipelinesStages(classesPipeline) + val classChecks = classesPipeline.stageContext.classUnderTest to listOf(classesPipeline.check) + processResults(result, classChecks) + } + + private fun processResults( + result: CodeGenerationResult, + classChecks: Pair, List> + ) { + val stageStatusChecks = classChecks.second.mapNotNull { check -> + runCatching { check(result) } + .fold( + onSuccess = { if (it) null else check.description }, + onFailure = { it.description } + ) + } + val transformedResult: Pair, List> = result.classUnderTest to stageStatusChecks + val failedResultExists = transformedResult.second.isNotEmpty() + + assertFalse(failedResultExists) { + "There are failed checks: ${transformedResult.second}" + } + } + + companion object { + private val defaultConfiguration = + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.DO_NOT_PARAMETRIZE, TestExecution) + + //TODO: this variable must be lateinit, without strange default initializer + var currentTestInfrastructureConfiguration: TestInfrastructureConfiguration = configurePipeline(defaultConfiguration) + + fun configurePipeline(configuration: AbstractConfiguration) = + TestInfrastructureConfiguration( + projectType = configuration.projectType, + testFramework = TestFramework.defaultItem, + codegenLanguage = configuration.language, + mockFramework = MockFramework.defaultItem, + mockStrategy = configuration.mockStrategy, + staticsMocking = StaticsMocking.defaultItem, + parametrizedTestSource = configuration.parametrizedTestSource, + forceStaticMocking = ForceStaticMocking.defaultItem, + generateUtilClassFile = false + ).also { + currentTestInfrastructureConfiguration = it + } + + private const val ERROR_REGION_BEGINNING = "///region Errors" + private const val ERROR_REGION_END = "///endregion" + } +} + +enum class ExecutionStatus { + IN_PROCESS, FAILED, SUCCESS +} + +sealed class Stage(private val name: String, val nextStage: Stage?) { + override fun toString() = name + + fun verifyPipeline(classesPipeline: ClassPipeline): ClassPipeline? = + if (classesPipeline.check.firstStage <= this && classesPipeline.check.lastStage >= this) classesPipeline else null + + abstract operator fun compareTo(stage: Stage): Int +} + +object CodeGeneration : Stage("Code Generation", Compilation) { + override fun compareTo(stage: Stage): Int = if (stage is CodeGeneration) 0 else -1 +} + +object Compilation : Stage("Compilation", TestExecution) { + override fun compareTo(stage: Stage): Int = + when (stage) { + is CodeGeneration -> 1 + is Compilation -> 0 + else -> -1 + } +} + +object TestExecution : Stage("Test Execution", null) { + override fun compareTo(stage: Stage): Int = if (stage is TestExecution) 0 else 1 +} + +private fun pipeline(firstStage: Stage = CodeGeneration, lastStage: Stage = TestExecution): Sequence = + generateSequence(firstStage) { if (it == lastStage) null else it.nextStage } + +data class StageExecutionInformation( + val stage: Stage, + val status: ExecutionStatus = ExecutionStatus.IN_PROCESS +) { + fun completeStage(status: ExecutionStatus = ExecutionStatus.SUCCESS) = copy(status = status) +} + +data class CodeGenerationResult( + val classUnderTest: KClass<*>, + val numberOfTestCases: Int, + val stageStatisticInformation: List +) + +sealed class PipelineResultCheck( + val description: String, + private val check: (CodeGenerationResult) -> Boolean +) { + open operator fun invoke(codeGenerationResult: CodeGenerationResult) = check(codeGenerationResult) +} + +/** + * Checks that stage failed and all previous stages are successfully processed. + */ +class StageStatusCheck( + val firstStage: Stage = CodeGeneration, + val lastStage: Stage, + status: ExecutionStatus, +) : PipelineResultCheck( + description = "Expect [$lastStage] to be in [$status] status", + check = constructPipelineResultCheck(firstStage, lastStage, status) +) + +private fun constructPipelineResultCheck( + firstStage: Stage, + lastStage: Stage, + status: ExecutionStatus +): (CodeGenerationResult) -> Boolean = + { result -> + val statuses = result.stageStatisticInformation.associate { it.stage to it.status } + val failedPrevStage = pipeline(firstStage, lastStage) + .takeWhile { it != lastStage } + .firstOrNull { statuses[it] != ExecutionStatus.SUCCESS } + + if (failedPrevStage != null) error("[$lastStage] is not started cause $failedPrevStage has failed") + + statuses[lastStage] == status + } + +data class CompilationResult(val buildDirectory: String, val testClassName: String) + +/** + * Context to run Stage. Contains class under test, data (input of current stage), number of analyzed test cases and + * stage execution information. + */ +data class StageContext( + val classUnderTest: KClass<*>, + val data: Any = Unit, + val numberOfTestCases: Int = 0, + val stages: List = emptyList(), + val status: ExecutionStatus = ExecutionStatus.SUCCESS +) + +data class ClassStages( + val testClass: KClass<*>, + val check: StageStatusCheck, + val testSets: List = emptyList() +) + +data class ClassPipeline(var stageContext: StageContext, val check: StageStatusCheck) \ No newline at end of file diff --git a/utbot-testing/src/main/kotlin/org/utbot/testing/TestSpecificTestCaseGenerator.kt b/utbot-testing/src/main/kotlin/org/utbot/testing/TestSpecificTestCaseGenerator.kt new file mode 100644 index 0000000000..5580b593ee --- /dev/null +++ b/utbot-testing/src/main/kotlin/org/utbot/testing/TestSpecificTestCaseGenerator.kt @@ -0,0 +1,114 @@ +package org.utbot.testing + +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +import org.utbot.engine.EngineController +import org.utbot.engine.Mocker +import org.utbot.engine.UtBotSymbolicEngine +import org.utbot.engine.util.mockListeners.ForceMockListener +import org.utbot.engine.util.mockListeners.ForceStaticMockListener +import org.utbot.framework.UtSettings +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.TestCaseGenerator +import org.utbot.framework.plugin.api.UtError +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtMethodTestSet +import org.utbot.framework.plugin.api.UtSymbolicExecution +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.services.JdkInfoDefaultProvider +import org.utbot.framework.util.Conflict +import org.utbot.framework.util.jimpleBody +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.taint.TaintConfigurationProvider +import java.nio.file.Path + +/** + * Special [UtMethodTestSet] generator for test methods that has a correct + * wrapper for suspend function [TestCaseGenerator.generateAsync]. + */ +class TestSpecificTestCaseGenerator( + buildDir: Path, + classpath: String?, + dependencyPaths: String, + engineActions: MutableList<(UtBotSymbolicEngine) -> Unit> = mutableListOf(), + isCanceled: () -> Boolean = { false }, + private val taintConfigurationProvider: TaintConfigurationProvider? = null, + applicationContext: ApplicationContext = defaultApplicationContext, +): TestCaseGenerator( + listOf(buildDir), + classpath, + dependencyPaths, + JdkInfoDefaultProvider().info, + engineActions, + isCanceled, + forceSootReload = false, + applicationContext = applicationContext, +) { + + private val logger = KotlinLogging.logger {} + + fun generate( + method: ExecutableId, + mockStrategy: MockStrategyApi, + additionalMockAlwaysClasses: Set = emptySet() + ): UtMethodTestSet { + if (isCanceled()) { + return UtMethodTestSet(method) + } + + logger.trace { "UtSettings:${System.lineSeparator()}" + UtSettings.toString() } + + val executions = mutableListOf() + val errors = mutableMapOf() + + val mockAlwaysDefaults = Mocker.javaDefaultClasses.mapTo(mutableSetOf()) { it.id } + additionalMockAlwaysClasses + val defaultTimeEstimator = ExecutionTimeEstimator(UtSettings.utBotGenerationTimeoutInMillis, 1) + + val forceMockListener = ForceMockListener.create(this, conflictTriggers) + val forceStaticMockListener = ForceStaticMockListener.create(this, conflictTriggers) + + runBlocking { + val controller = EngineController() + controller.job = launch { + super + .generateAsync( + controller, + method, + mockStrategy, + mockAlwaysDefaults, + defaultTimeEstimator, + taintConfigurationProvider + ) + .collect { + when (it) { + is UtExecution -> { + if (it is UtSymbolicExecution && + (conflictTriggers.triggered(Conflict.ForceMockHappened) || + conflictTriggers.triggered(Conflict.ForceStaticMockHappened)) + ) { + it.containsMocking = true + } + executions += it + } + is UtError -> errors.merge(it.description, 1, Int::plus) + } + } + } + } + + conflictTriggers.reset(Conflict.ForceMockHappened, Conflict.ForceStaticMockHappened) + forceMockListener.detach(this, forceMockListener) + forceStaticMockListener.detach(this, forceStaticMockListener) + + val minimizedExecutions = super.minimizeExecutions( + method, + executions, + rerunExecutor = ConcreteExecutor(concreteExecutionContext.instrumentationFactory, classpathForEngine) + ) + return UtMethodTestSet(method, minimizedExecutions, jimpleBody(method), errors) + } +} \ No newline at end of file diff --git a/utbot-testing/src/main/kotlin/org/utbot/testing/UtModelTestCaseChecker.kt b/utbot-testing/src/main/kotlin/org/utbot/testing/UtModelTestCaseChecker.kt new file mode 100644 index 0000000000..8297859d49 --- /dev/null +++ b/utbot-testing/src/main/kotlin/org/utbot/testing/UtModelTestCaseChecker.kt @@ -0,0 +1,227 @@ +@file:Suppress("NestedLambdaShadowedImplicitParameter") + +package org.utbot.testing + +import org.junit.jupiter.api.Assertions.assertTrue +import org.utbot.common.ClassLocation +import org.utbot.common.FileUtil.findPathToClassFiles +import org.utbot.common.FileUtil.locateClass +import org.utbot.common.WorkaroundReason.HACK +import org.utbot.common.workaround +import org.utbot.engine.prettify +import org.utbot.framework.UtSettings.checkSolverTimeoutMillis +import org.utbot.framework.UtSettings.useFuzzing +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.MockStrategyApi.NO_MOCKS +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtDirectSetFieldModel +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtExecutionResult +import org.utbot.framework.plugin.api.UtMethodTestSet +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.exceptionOrNull +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.declaringClazz +import org.utbot.framework.plugin.api.util.defaultValueModel +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.framework.util.Conflict +import org.utbot.testcheckers.ExecutionsNumberMatcher +import java.nio.file.Path +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KFunction1 +import kotlin.reflect.KFunction2 +import kotlin.reflect.KFunction3 + +abstract class UtModelTestCaseChecker( + testClass: KClass<*>, + testCodeGeneration: Boolean = true, + configurations: List = standardTestingConfigurations, +) : CodeGenerationIntegrationTest(testClass, testCodeGeneration, configurations) { + protected fun check( + method: KFunction2<*, *, *>, + branches: ExecutionsNumberMatcher, + vararg matchers: (UtModel, UtExecutionResult) -> Boolean, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, + mockStrategy, + branches, + matchers, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected fun check( + method: KFunction3<*, *, *, *>, + branches: ExecutionsNumberMatcher, + vararg matchers: (UtModel, UtModel, UtExecutionResult) -> Boolean, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, + mockStrategy, + branches, + matchers, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected fun checkStatic( + method: KFunction1<*, *>, + branches: ExecutionsNumberMatcher, + vararg matchers: (UtModel, UtExecutionResult) -> Boolean, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, + mockStrategy, + branches, + matchers, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected fun checkStaticsAfter( + method: KFunction2<*, *, *>, + branches: ExecutionsNumberMatcher, + vararg matchers: (UtModel, StaticsModelType, UtExecutionResult) -> Boolean, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, + arguments = ::withStaticsAfter, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + private fun internalCheck( + method: KFunction<*>, + mockStrategy: MockStrategyApi, + branches: ExecutionsNumberMatcher, + matchers: Array>, + arguments: (UtExecution) -> List = ::withResult, + additionalMockAlwaysClasses: Set = emptySet() + ) { + workaround(HACK) { + // @todo change to the constructor parameter + checkSolverTimeoutMillis = 0 + useFuzzing = false + } + val executableId = method.executableId + + withUtContext(UtContext(method.declaringClazz.classLoader)) { + val testSet = executions(executableId, mockStrategy, additionalMockAlwaysClasses) + + assertTrue(testSet.errors.isEmpty()) { + "We have errors: ${testSet.errors.entries.map { "${it.value}: ${it.key}" }.prettify()}" + } + + // if force mocking took place in parametrized test generation, + // we do not need to process this [testSet] + if (TestCodeGeneratorPipeline.currentTestInfrastructureConfiguration.isParametrizedAndMocked) { + conflictTriggers.reset(Conflict.ForceMockHappened, Conflict.ForceStaticMockHappened) + return + } + + val executions = testSet.executions + assertTrue(branches(executions.size)) { + "Branch count matcher '$branches' fails for #executions=${executions.size}: ${executions.prettify()}" + } + executions.checkMatchers(matchers, arguments) + + processTestSet(testSet) + } + } + + private fun List.checkMatchers( + matchers: Array>, + arguments: (UtExecution) -> List + ) { + val exceptions = mutableMapOf>() + val notMatched = matchers.indices.filter { i -> + this.none { ex -> + runCatching { invokeMatcher(matchers[i], arguments(ex)) } + .onFailure { exceptions.merge(i, listOf(it)) { v1, v2 -> v1 + v2 } } + .getOrDefault(false) + } + } + + val matchersNumbers = notMatched.map { it + 1 } + assertTrue(notMatched.isEmpty()) { + "Execution matchers $matchersNumbers not match to ${this.prettify()}, found exceptions: ${exceptions.prettify()}" + } + } + + private fun executions( + method: ExecutableId, + mockStrategy: MockStrategyApi, + additionalMockAlwaysClasses: Set = emptySet() + ): UtMethodTestSet { + val classLocation = locateClass(method.classId.jClass) + if (classLocation != previousClassLocation) { + buildDir = findPathToClassFiles(classLocation) + previousClassLocation = classLocation + } + + val buildInfo = CodeGenerationIntegrationTest.Companion.BuildInfo(buildDir, dependencyPath = null) + val testCaseGenerator = testCaseGeneratorCache + .getOrPut(buildInfo) { + TestSpecificTestCaseGenerator( + buildDir, + classpath = null, + dependencyPaths = System.getProperty("java.class.path"), + ) + } + + return testCaseGenerator.generate(method, mockStrategy, additionalMockAlwaysClasses) + } + + protected inline fun UtExecutionResult.isException(): Boolean = exceptionOrNull() is T + + /** + * Finds mocked method and returns values. + */ + protected fun UtModel.mocksMethod(method: KFunction<*>): List? { + if (this !is UtCompositeModel) return null + if (!isMock) return null + return mocks[method.executableId] + } + + protected inline fun UtExecutionResult.primitiveValue(): T = getOrThrow().primitiveValue() + + /** + * Finds field model in [UtCompositeModel] and [UtAssembleModel]. For assemble model supports direct field access only. + */ + protected fun UtModel.findField(fieldName: String, declaringClass: ClassId = this.classId): UtModel = + findField(FieldId(declaringClass, fieldName)) + + /** + * Finds field model in [UtCompositeModel] and [UtAssembleModel]. For assemble model supports direct field access only. + */ + @Suppress("MemberVisibilityCanBePrivate") + protected fun UtModel.findField(fieldId: FieldId): UtModel = when (this) { + is UtCompositeModel -> this.fields[fieldId]!! + is UtAssembleModel -> { + val fieldAccess = this.modificationsChain + .filterIsInstance() + .singleOrNull { it.fieldId == fieldId } + fieldAccess?.fieldModel ?: fieldId.type.defaultValueModel() + } + + else -> error("Can't get ${fieldId.name} from $this") + } + + companion object { + private var previousClassLocation: ClassLocation? = null + private lateinit var buildDir: Path + } +} + +private fun withResult(ex: UtExecution) = ex.stateBefore.parameters + ex.result +private fun withStaticsAfter(ex: UtExecution) = ex.stateBefore.parameters + ex.stateAfter.statics + ex.result + +private typealias StaticsModelType = Map \ No newline at end of file diff --git a/utbot-testing/src/main/kotlin/org/utbot/testing/UtValueTestCaseChecker.kt b/utbot-testing/src/main/kotlin/org/utbot/testing/UtValueTestCaseChecker.kt new file mode 100644 index 0000000000..bc77d9de81 --- /dev/null +++ b/utbot-testing/src/main/kotlin/org/utbot/testing/UtValueTestCaseChecker.kt @@ -0,0 +1,2376 @@ +@file:Suppress("NestedLambdaShadowedImplicitParameter") + +package org.utbot.testing + +import org.junit.jupiter.api.Assertions.assertTrue +import org.utbot.common.ClassLocation +import org.utbot.common.FileUtil.clearTempDirectory +import org.utbot.common.FileUtil.findPathToClassFiles +import org.utbot.common.FileUtil.locateClass +import org.utbot.engine.prettify +import org.utbot.framework.SummariesGenerationType +import org.utbot.framework.UtSettings +import org.utbot.framework.UtSettings.daysLimitForTempFiles +import org.utbot.framework.context.ApplicationContext +import org.utbot.framework.coverage.Coverage +import org.utbot.framework.coverage.counters +import org.utbot.framework.coverage.methodCoverage +import org.utbot.framework.coverage.toAtLeast +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.FieldMockTarget +import org.utbot.framework.plugin.api.MockId +import org.utbot.framework.plugin.api.MockInfo +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.MockStrategyApi.NO_MOCKS +import org.utbot.framework.plugin.api.ObjectMockTarget +import org.utbot.framework.plugin.api.ParameterMockTarget +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtConcreteValue +import org.utbot.framework.plugin.api.UtInstrumentation +import org.utbot.framework.plugin.api.UtMethodTestSet +import org.utbot.framework.plugin.api.UtMockValue +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtValueExecution +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.declaringClazz +import org.utbot.framework.plugin.api.util.enclosingClass +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.kClass +import org.utbot.framework.plugin.api.util.withUtContext +import org.utbot.framework.util.Conflict +import org.utbot.framework.util.toValueTestCase +import org.utbot.summary.summarizeAll +import org.utbot.testcheckers.ExecutionsNumberMatcher +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths +import java.util.stream.Collectors +import java.util.stream.Stream +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KFunction0 +import kotlin.reflect.KFunction1 +import kotlin.reflect.KFunction2 +import kotlin.reflect.KFunction3 +import kotlin.reflect.KFunction4 +import kotlin.reflect.KFunction5 + +abstract class UtValueTestCaseChecker( + testClass: KClass<*>, + testCodeGeneration: Boolean = true, + val configurations: List = standardTestingConfigurations, + val applicationContext: ApplicationContext = defaultApplicationContext, +) : CodeGenerationIntegrationTest(testClass, testCodeGeneration, configurations) { + // contains already analyzed by the engine methods + private val analyzedMethods: MutableMap = mutableMapOf() + + val searchDirectory: Path = Paths.get("../utbot-sample/src/main/java") + + init { + UtSettings.checkSolverTimeoutMillis = 0 + UtSettings.checkNpeInNestedMethods = true + UtSettings.checkNpeInNestedNotPrivateMethods = true + UtSettings.substituteStaticsWithSymbolicVariable = true + UtSettings.useAssembleModelGenerator = true + UtSettings.saveRemainingStatesForConcreteExecution = false + UtSettings.useFuzzing = false + UtSettings.useCustomJavaDocTags = false + UtSettings.summaryGenerationType = SummariesGenerationType.FULL + } + + // checks paramsBefore and result + protected inline fun check( + method: KFunction1<*, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet(), + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun check( + method: KFunction2<*, T, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun check( + method: KFunction3<*, T1, T2, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun check( + method: KFunction4<*, T1, T2, T3, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun check( + method: KFunction5<*, T1, T2, T3, T4, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // check paramsBefore and Result, suitable to check exceptions + protected inline fun checkWithException( + method: KFunction1<*, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithException( + method: KFunction2<*, T, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithException( + method: KFunction3<*, T1, T2, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithException( + method: KFunction4<*, T1, T2, T3, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithException( + method: KFunction5<*, T1, T2, T3, T4, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // check this, paramsBefore and result value + protected inline fun checkWithThis( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withThisAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithThis( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, + arguments = ::withThisAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithThis( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, + arguments = ::withThisAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithThis( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, + arguments = ::withThisAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithThis( + method: KFunction5, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, T4, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, + arguments = ::withThisAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithThisAndException( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withThisAndException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithThisAndException( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, + arguments = ::withThisAndException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithThisAndException( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, + arguments = ::withThisAndException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithThisAndException( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, + arguments = ::withThisAndException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithThisAndException( + method: KFunction5, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, T4, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, + arguments = ::withThisAndException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // checks paramsBefore, mocks and result value + protected inline fun checkMocksInStaticMethod( + method: KFunction0, + branches: ExecutionsNumberMatcher, + vararg matchers: (Mocks, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withMocks, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocksInStaticMethod( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, Mocks, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withMocks, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocksInStaticMethod( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, Mocks, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withMocks, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocksInStaticMethod( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, Mocks, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withMocks, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocksInStaticMethod( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, Mocks, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withMocks, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // checks paramsBefore, mocks and result value + protected inline fun checkMocks( + method: KFunction1<*, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (Mocks, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withMocks, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocks( + method: KFunction2<*, T, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, Mocks, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withMocks, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocks( + method: KFunction3<*, T1, T2, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, Mocks, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withMocks, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocks( + method: KFunction4<*, T1, T2, T3, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, Mocks, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withMocks, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocks( + method: KFunction5<*, T1, T2, T3, T4, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, Mocks, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withMocks, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkThisMocksAndExceptions( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, Mocks, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withThisMocksAndExceptions, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkThisMocksAndExceptions( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, Mocks, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, + arguments = ::withThisMocksAndExceptions, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkThisMocksAndExceptions( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, Mocks, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withThisMocksAndExceptions, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkThisMocksAndExceptions( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, Mocks, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withThisMocksAndExceptions, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkThisMocksAndExceptions( + method: KFunction5, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, T4, Mocks, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withThisMocksAndExceptions, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // check paramsBefore, mocks and instrumentation and result value + protected inline fun checkMocksAndInstrumentation( + method: KFunction1<*, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (Mocks, Instrumentation, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withMocksAndInstrumentation, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocksAndInstrumentation( + method: KFunction2<*, T, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, Mocks, Instrumentation, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withMocksAndInstrumentation, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocksAndInstrumentation( + method: KFunction3<*, T1, T2, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, Mocks, Instrumentation, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withMocksAndInstrumentation, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocksAndInstrumentation( + method: KFunction4<*, T1, T2, T3, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, Mocks, Instrumentation, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withMocksAndInstrumentation, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocksAndInstrumentation( + method: KFunction5<*, T1, T2, T3, T4, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, Mocks, Instrumentation, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withMocksAndInstrumentation, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // check this, paramsBefore, mocks, instrumentation and return value + protected inline fun checkMocksInstrumentationAndThis( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, Mocks, Instrumentation, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withMocksInstrumentationAndThis, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocksInstrumentationAndThis( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, Mocks, Instrumentation, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, + arguments = ::withMocksInstrumentationAndThis, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocksInstrumentationAndThis( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, Mocks, Instrumentation, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, + arguments = ::withMocksInstrumentationAndThis, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocksInstrumentationAndThis( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, Mocks, Instrumentation, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, + arguments = ::withMocksInstrumentationAndThis, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMocksInstrumentationAndThis( + method: KFunction5, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, T4, Mocks, Instrumentation, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, + arguments = ::withMocksInstrumentationAndThis, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // checks paramsBefore and return value for static methods + protected inline fun checkStaticMethod( + method: KFunction0, + branches: ExecutionsNumberMatcher, + vararg matchers: (R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethod( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethod( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethod( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethod( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // checks paramsBefore and Result, suitable for exceptions check + protected inline fun checkStaticMethodWithException( + method: KFunction0, + branches: ExecutionsNumberMatcher, + vararg matchers: (Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodWithException( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodWithException( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodWithException( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodWithException( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withException, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // check arguments, statics and return value + protected inline fun checkStatics( + method: KFunction1<*, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withStaticsBefore, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStatics( + method: KFunction2<*, T, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withStaticsBefore, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStatics( + method: KFunction3<*, T1, T2, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withStaticsBefore, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStatics( + method: KFunction4<*, T1, T2, T3, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withStaticsBefore, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStatics( + method: KFunction5<*, T1, T2, T3, T4, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withStaticsBefore, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // check arguments, statics and Result for exceptions check + protected inline fun checkStaticsAndException( + method: KFunction1<*, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (StaticsType, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withStaticsBeforeAndExceptions, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsAndException( + method: KFunction2<*, T, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, StaticsType, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withStaticsBeforeAndExceptions, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsAndException( + method: KFunction3<*, T1, T2, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, StaticsType, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withStaticsBeforeAndExceptions, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsAndException( + method: KFunction4<*, T1, T2, T3, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, StaticsType, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withStaticsBeforeAndExceptions, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsAndException( + method: KFunction5<*, T1, T2, T3, T4, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, StaticsType, Result) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withStaticsBeforeAndExceptions, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsAfter( + method: KFunction1<*, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withStaticsAfter, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsAfter( + method: KFunction2<*, T, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withStaticsAfter, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsAfter( + method: KFunction3<*, T1, T2, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withStaticsAfter, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsAfter( + method: KFunction4<*, T1, T2, T3, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withStaticsAfter, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsAfter( + method: KFunction5<*, T1, T2, T3, T4, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withStaticsAfter, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkThisAndStaticsAfter( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withThisAndStaticsAfter, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkThisAndStaticsAfter( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, + arguments = ::withThisAndStaticsAfter, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkThisAndStaticsAfter( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, + arguments = ::withThisAndStaticsAfter, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkThisAndStaticsAfter( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, + arguments = ::withThisAndStaticsAfter, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkThisAndStaticsAfter( + method: KFunction5, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, T4, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, + arguments = ::withThisAndStaticsAfter, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // checks paramsBefore, staticsBefore and return value for static methods + protected inline fun checkStaticsInStaticMethod( + method: KFunction0, + branches: ExecutionsNumberMatcher, + vararg matchers: (StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withStaticsBefore, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsInStaticMethod( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withStaticsBefore, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsInStaticMethod( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withStaticsBefore, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsInStaticMethod( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withStaticsBefore, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsInStaticMethod( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withStaticsBefore, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsInStaticMethod( + method: KFunction5, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, T5, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withStaticsBefore, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // check this, arguments and result value + protected inline fun checkStaticsWithThis( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withThisStaticsBeforeAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsWithThis( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, + arguments = ::withThisStaticsBeforeAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsWithThis( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, + arguments = ::withThisStaticsBeforeAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsWithThis( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, + arguments = ::withThisStaticsBeforeAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticsWithThis( + method: KFunction5, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, T4, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, + arguments = ::withThisStaticsBeforeAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkParamsMutationsAndResult( + method: KFunction2<*, T, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withParamsMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkParamsMutationsAndResult( + method: KFunction3<*, T1, T2, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T1, T2, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withParamsMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkParamsMutationsAndResult( + method: KFunction4<*, T1, T2, T3, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T1, T2, T3, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withParamsMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkParamsMutationsAndResult( + method: KFunction5<*, T1, T2, T3, T4, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, T1, T2, T3, T4, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withParamsMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // checks mutations in the parameters + protected inline fun checkParamsMutations( + method: KFunction2<*, T, *>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withParamsMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkParamsMutations( + method: KFunction3<*, T1, T2, *>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T1, T2) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withParamsMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkParamsMutations( + method: KFunction4<*, T1, T2, T3, *>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T1, T2, T3) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withParamsMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkParamsMutations( + method: KFunction5<*, T1, T2, T3, T4, *>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, T1, T2, T3, T4) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withParamsMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // checks mutations in the parameters and statics for static method + protected fun checkStaticMethodMutation( + method: KFunction0<*>, + branches: ExecutionsNumberMatcher, + vararg matchers: (StaticsType, StaticsType) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodMutation( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, StaticsType, T, StaticsType) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodMutation( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, StaticsType, T1, T2, StaticsType) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodMutation( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, StaticsType, T1, T2, T3, StaticsType) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodMutation( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, StaticsType, T1, T2, T3, T4, StaticsType) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodMutationAndResult( + method: KFunction0, + branches: ExecutionsNumberMatcher, + vararg matchers: (StaticsType, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodMutationAndResult( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, StaticsType, T, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodMutationAndResult( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, StaticsType, T1, T2, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodMutationAndResult( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, StaticsType, T1, T2, T3, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkStaticMethodMutationAndResult( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, StaticsType, T1, T2, T3, T4, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + + // checks mutations in the parameters and statics + protected inline fun checkMutations( + method: KFunction2<*, T, *>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, StaticsType, T, StaticsType) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMutations( + method: KFunction3<*, T1, T2, *>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, StaticsType, T1, T2, StaticsType) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMutations( + method: KFunction4<*, T1, T2, T3, *>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, StaticsType, T1, T2, T3, StaticsType) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMutations( + method: KFunction5<*, T1, T2, T3, T4, *>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, StaticsType, T1, T2, T3, T4, StaticsType) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withMutations, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // checks mutations in the parameters and statics + protected inline fun checkMutationsAndResult( + method: KFunction1<*, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (StaticsType, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + arguments = ::withMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMutationsAndResult( + method: KFunction2<*, T, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, StaticsType, T, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMutationsAndResult( + method: KFunction3<*, T1, T2, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, StaticsType, T1, T2, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + arguments = ::withMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMutationsAndResult( + method: KFunction4<*, T1, T2, T3, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, StaticsType, T1, T2, T3, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + arguments = ::withMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkMutationsAndResult( + method: KFunction5<*, T1, T2, T3, T4, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, StaticsType, T1, T2, T3, T4, StaticsType, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + arguments = ::withMutationsAndResult, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + // checks mutations in this, parameters and statics + protected inline fun checkAllMutationsWithThis( + method: KFunction1, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, StaticsType, T, StaticsType, R) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + arguments = ::withMutationsAndThis, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkAllMutationsWithThis( + method: KFunction2, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, StaticsType, T, T1, StaticsType, R) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, + arguments = ::withMutationsAndThis, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkAllMutationsWithThis( + method: KFunction3, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, StaticsType, T, T1, T2, StaticsType, R) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, + arguments = ::withMutationsAndThis, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkAllMutationsWithThis( + method: KFunction4, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, StaticsType, T, T1, T2, T3, StaticsType) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, + arguments = ::withMutationsAndThis, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkAllMutationsWithThis( + method: KFunction5, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, T1, T2, T3, T4, StaticsType, T, T1, T2, T3, T4, StaticsType) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, T1::class, T2::class, T3::class, T4::class, + arguments = ::withMutationsAndThis, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + //region checks substituting statics with symbolic variable or not + protected inline fun checkWithoutStaticsSubstitution( + method: KFunction1<*, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithoutStaticsSubstitution( + method: KFunction2<*, T, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T::class, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithoutStaticsSubstitution( + method: KFunction3<*, T1, T2, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithoutStaticsSubstitution( + method: KFunction4<*, T1, T2, T3, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + protected inline fun checkWithoutStaticsSubstitution( + method: KFunction5<*, T1, T2, T3, T4, R>, + branches: ExecutionsNumberMatcher, + vararg matchers: (T1, T2, T3, T4, R?) -> Boolean, + coverage: CoverageMatcher = Full, + mockStrategy: MockStrategyApi = NO_MOCKS, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) = internalCheck( + method, mockStrategy, branches, matchers, coverage, T1::class, T2::class, T3::class, T4::class, + additionalDependencies = additionalDependencies, + additionalMockAlwaysClasses = additionalMockAlwaysClasses + ) + + //endregion + + /** + * @param method method under test + * @param generateWithNested a flag indicating if we need to generate nested test classes + * or just generate one top-level test class + * @see [ClassWithStaticAndInnerClassesTest] + */ + fun checkAllCombinations( + method: KFunction<*>, + generateWithNested: Boolean = false, + additionalMockAlwaysClasses: Set = emptySet() + ) { + val failed = mutableListOf() + val succeeded = mutableListOf() + + allTestInfrastructureConfigurations + .filterNot { it.isDisabled } + .forEach { config -> + runCatching { + internalCheckForCodeGeneration(method, config, generateWithNested, additionalMockAlwaysClasses) + }.onFailure { + failed += config + }.onSuccess { + succeeded += config + } + } + + // TODO check that all generated classes have different content JIRA:1415 + + logger.info { "Total configurations: ${succeeded.size + failed.size}. Failed: ${failed.size}." } + require(failed.isEmpty()) { + val separator = System.lineSeparator() + val failedConfigurations = failed.joinToString(prefix = separator, separator = separator) + logger.error { "Failed configurations: $failedConfigurations" } + "Failed configurations: $failedConfigurations" + } + } + + @Suppress("ControlFlowWithEmptyBody", "UNUSED_VARIABLE") + private fun internalCheckForCodeGeneration( + method: KFunction<*>, + testInfrastructureConfiguration: TestInfrastructureConfiguration, + generateWithNested: Boolean, + additionalMockAlwaysClasses: Set = emptySet() + ) { + withSettingsFromTestFrameworkConfiguration(testInfrastructureConfiguration) { + with(testInfrastructureConfiguration) { + + val executableId = method.executableId + computeAdditionalDependenciesClasspathAndBuildDir(method.declaringClazz, emptyArray()) + val utContext = UtContext(method.declaringClazz.classLoader) + + clearTempDirectory(daysLimitForTempFiles) + + withUtContext(utContext) { + val methodWithStrategy = + MethodWithMockStrategy(executableId, mockStrategy, resetNonFinalFieldsAfterClinit) + + val (testSet, coverage) = analyzedMethods.getOrPut(methodWithStrategy) { + walk(executableId, mockStrategy, additionalMockAlwaysClasses = additionalMockAlwaysClasses) + } + + // if force mocking took place in parametrized test generation, + // we do not need to process this [testSet] + if (TestCodeGeneratorPipeline.currentTestInfrastructureConfiguration.isParametrizedAndMocked) { + conflictTriggers.reset(Conflict.ForceMockHappened, Conflict.ForceStaticMockHappened) + return + } + + val methodUnderTestOwnerId = testSet.method.classId + val classUnderTest = if (generateWithNested) { + generateSequence(methodUnderTestOwnerId) { clazz -> clazz.enclosingClass }.last().kClass + } else { + methodUnderTestOwnerId.kClass + } + + val stageStatusCheck = StageStatusCheck( + firstStage = CodeGeneration, + lastStage = TestExecution, + status = ExecutionStatus.SUCCESS + ) + val classStages = ClassStages(classUnderTest, stageStatusCheck, listOf(testSet)) + + TestCodeGeneratorPipeline(testInfrastructureConfiguration, applicationContext).runClassesCodeGenerationTests(classStages) + } + } + } + } + + inline fun internalCheck( + method: KFunction, + mockStrategy: MockStrategyApi, + branches: ExecutionsNumberMatcher, + matchers: Array>, + coverageMatcher: CoverageMatcher, + vararg classes: KClass<*>, + noinline arguments: (UtValueExecution<*>) -> List = ::withResult, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ) { + if (UtSettings.checkAllCombinationsForEveryTestInSamples) { + checkAllCombinations(method) + } + + val executableId = method.executableId + + withUtContext(UtContext(method.declaringClazz.classLoader)) { + val additionalDependenciesClassPath = + computeAdditionalDependenciesClasspathAndBuildDir(executableId.classId.jClass, additionalDependencies) + + val (testSet, coverage) = if (coverageMatcher is DoNotCalculate) { + val testSet = executions( + executableId, + mockStrategy, + additionalDependenciesClassPath, + additionalMockAlwaysClasses + ) + + MethodResult(testSet, Coverage()) + } else { + walk(executableId, mockStrategy, additionalDependenciesClassPath, additionalMockAlwaysClasses) + } + listOf(testSet).summarizeAll(searchDirectory, sourceFile = null) + val valueTestCase = testSet.toValueTestCase() + + assertTrue(testSet.errors.isEmpty()) { + "We have errors: ${ + testSet.errors.entries.map { "${it.value}: ${it.key}" }.prettify() + }" + } + + // if force mocking took place in parametrized test generation, + // we do not need to process this [testSet] + if (TestCodeGeneratorPipeline.currentTestInfrastructureConfiguration.isParametrizedAndMocked) { + conflictTriggers.reset(Conflict.ForceMockHappened, Conflict.ForceStaticMockHappened) + return + } + + val valueExecutions = valueTestCase.executions + assertTrue(branches(valueExecutions.size)) { + "Branch count matcher '$branches' fails for ${valueExecutions.size}: ${valueExecutions.prettify()}" + } + + valueExecutions.checkTypes(R::class, classes.toList()) + + valueExecutions.checkMatchers(matchers, arguments) + assertTrue(coverageMatcher(coverage)) { + "Coverage matcher '$coverageMatcher' fails for $coverage (at least: ${coverage.toAtLeast()})" + } + + processTestSet(testSet) + } + } + + fun List>.checkTypes( + resultType: KClass<*>, + argumentTypes: List> + ) { + for (execution in this) { + val typesWithArgs = argumentTypes.zip(execution.stateBefore.params.map { it.type }) + + assertTrue(typesWithArgs.all { it.second::class.isInstance(it.first::class) }) { "Param types do not match" } + + execution.returnValue.getOrNull()?.let { returnValue -> + assertTrue(resultType::class.isInstance(returnValue::class)) { "Return type does not match" } + } + } + } + + fun List>.checkMatchers( + matchers: Array>, + arguments: (UtValueExecution<*>) -> List + ) { + val notMatched = matchers.indices.filter { i -> + this.none { ex -> + runCatching { invokeMatcher(matchers[i], arguments(ex)) }.getOrDefault(false) + } + } + + val matchersNumbers = notMatched.map { it + 1 } + + assertTrue(notMatched.isEmpty()) { "Execution matchers $matchersNumbers not match to ${this.prettify()}" } + + // Uncomment if you want to check that each value matches at least one matcher. +// val notMatchedValues = this.filter { ex -> +// matchers.none { matcher -> +// runCatching { +// invokeMatcher(matcher, arguments(ex)) +// }.getOrDefault(false) +// } +// } +// assertTrue(notMatchedValues.isEmpty()) { "Values not match to matchers, ${notMatchedValues.prettify()}" } + } + + fun walk( + method: ExecutableId, + mockStrategy: MockStrategyApi, + additionalDependenciesClassPath: String = "", + additionalMockAlwaysClasses: Set = emptySet() + ): MethodResult { + val testSet = executions(method, mockStrategy, additionalDependenciesClassPath, additionalMockAlwaysClasses) + val methodCoverage = methodCoverage( + method, + testSet.toValueTestCase().executions, + buildDir.toString() + File.pathSeparator + additionalDependenciesClassPath + ) + return MethodResult(testSet, methodCoverage) + } + + open fun executions( + method: ExecutableId, + mockStrategy: MockStrategyApi, + additionalDependenciesClassPath: String, + additionalMockAlwaysClasses: Set = emptySet() + ): UtMethodTestSet { + val buildInfo = CodeGenerationIntegrationTest.Companion.BuildInfo(buildDir, additionalDependenciesClassPath) + + val testCaseGenerator = createTestCaseGenerator(buildInfo) + return testCaseGenerator.generate(method, mockStrategy, additionalMockAlwaysClasses) + } + + // factory method + open fun createTestCaseGenerator(buildInfo: CodeGenerationIntegrationTest.Companion.BuildInfo) = + testCaseGeneratorCache.getOrPut(buildInfo) { + TestSpecificTestCaseGenerator( + buildInfo.buildDir, + buildInfo.dependencyPath, + System.getProperty("java.class.path"), + applicationContext = applicationContext + ) + } + + fun executionsModel( + method: ExecutableId, + mockStrategy: MockStrategyApi, + additionalDependencies: Array> = emptyArray(), + additionalMockAlwaysClasses: Set = emptySet() + ): UtMethodTestSet { + val additionalDependenciesClassPath = + computeAdditionalDependenciesClasspathAndBuildDir(method.classId.jClass, additionalDependencies) + withUtContext(UtContext(method.classId.jClass.classLoader)) { + val buildInfo = CodeGenerationIntegrationTest.Companion.BuildInfo(buildDir, additionalDependenciesClassPath) + val testCaseGenerator = createTestCaseGenerator(buildInfo) + return testCaseGenerator.generate(method, mockStrategy, additionalMockAlwaysClasses) + } + } + + companion object { + private var previousClassLocation: ClassLocation? = null + private lateinit var buildDir: Path + + fun computeAdditionalDependenciesClasspathAndBuildDir( + clazz: Class<*>, + additionalDependencies: Array> + ): String { + val additionalDependenciesClassPath = additionalDependencies + .map { locateClass(it) } + .joinToString(File.pathSeparator) { findPathToClassFiles(it).toAbsolutePath().toString() } + val classLocation = locateClass(clazz) + if (classLocation != previousClassLocation) { + buildDir = findPathToClassFiles(classLocation) + previousClassLocation = classLocation + } + return additionalDependenciesClassPath + } + } + + data class MethodWithMockStrategy( + val method: ExecutableId, + val mockStrategy: MockStrategyApi, + val substituteStatics: Boolean + ) + + data class MethodResult(val testSet: UtMethodTestSet, val coverage: Coverage) +} + +@Suppress("UNCHECKED_CAST") +// TODO please use matcher.reflect().call(...) when it will be ready, currently call isn't supported in kotlin reflect +fun invokeMatcher(matcher: Function, params: List) = when (matcher) { + is Function1<*, *> -> (matcher as Function1).invoke(params[0]) + is Function2<*, *, *> -> (matcher as Function2).invoke(params[0], params[1]) + is Function3<*, *, *, *> -> (matcher as Function3).invoke( + params[0], params[1], params[2] + ) + is Function4<*, *, *, *, *> -> (matcher as Function4).invoke( + params[0], params[1], params[2], params[3] + ) + is Function5<*, *, *, *, *, *> -> (matcher as Function5).invoke( + params[0], params[1], params[2], params[3], params[4], + ) + is Function6<*, *, *, *, *, *, *> -> (matcher as Function6).invoke( + params[0], params[1], params[2], params[3], params[4], params[5], + ) + is Function7<*, *, *, *, *, *, *, *> -> (matcher as Function7).invoke( + params[0], params[1], params[2], params[3], params[4], params[5], params[6], + ) + else -> error("Function with arity > 7 not supported") +} + +fun between(bounds: IntRange) = ExecutionsNumberMatcher("$bounds") { it in bounds } +val ignoreExecutionsNumber = ExecutionsNumberMatcher("Do not calculate") { it > 0 } + +fun atLeast(percents: Int) = AtLeast(percents) + +fun signatureOf(function: Function<*>): String { + function as KFunction + return function.executableId.signature +} + +fun MockInfo.mocksMethod(method: Function<*>) = signatureOf(method) == this.method.signature + +fun List.singleMockOrNull(field: String, method: Function<*>): MockInfo? = + singleOrNull { (it.mock as? FieldMockTarget)?.field == field && it.mocksMethod(method) } + +fun List.singleMock(field: String, method: Function<*>): MockInfo = + single { (it.mock as? FieldMockTarget)?.field == field && it.mocksMethod(method) } + +fun List.singleMock(id: MockId, method: Function<*>): MockInfo = + single { (it.mock as? ObjectMockTarget)?.id == id && it.mocksMethod(method) } + +inline fun MockInfo.value(index: Int = 0): T = + when (val value = (values[index] as? UtConcreteValue<*>)?.value) { + is T -> value + else -> error("Unsupported type: ${values[index]}") + } + +fun MockInfo.mockValue(index: Int = 0): UtMockValue = values[index] as UtMockValue + +fun MockInfo.isParameter(num: Int): Boolean = (mock as? ParameterMockTarget)?.index == num + +inline fun Result<*>.isException(): Boolean = exceptionOrNull() is T + +inline fun UtCompositeModel.mockValues(methodName: String): List = + mocks.filterKeys { it.name == methodName }.values.flatten().map { it.primitiveValue() } + +inline fun UtModel.primitiveValue(): T = + (this as? UtPrimitiveModel)?.value as? T ?: error("Can't transform $this to ${T::class}") + +sealed class CoverageMatcher(private val description: String, private val cmp: (Coverage) -> Boolean) { + operator fun invoke(c: Coverage) = cmp(c) + override fun toString() = description +} + +object Full : CoverageMatcher("full coverage", { it.counters.all { it.total == it.covered } }) + +class AtLeast(percents: Int) : CoverageMatcher("at least $percents% coverage", + { it.counters.all { 100 * it.covered >= percents * it.total } }) + +object DoNotCalculate : CoverageMatcher("Do not calculate", { true }) + +class FullWithAssumptions(assumeCallsNumber: Int) : CoverageMatcher( + "full coverage except failed assume calls", + { it.instructionCounter.let { it.covered >= it.total - assumeCallsNumber } } +) { + init { + require(assumeCallsNumber > 0) { + "Non-positive number of assume calls $assumeCallsNumber passed (for zero calls use Full coverage matcher" + } + } +} + +// simple matchers +fun withResult(ex: UtValueExecution<*>) = ex.paramsBefore + ex.evaluatedResult +fun withException(ex: UtValueExecution<*>) = ex.paramsBefore + ex.returnValue +fun withStaticsBefore(ex: UtValueExecution<*>) = ex.paramsBefore + ex.staticsBefore + ex.evaluatedResult +fun withStaticsBeforeAndExceptions(ex: UtValueExecution<*>) = ex.paramsBefore + ex.staticsBefore + ex.returnValue +fun withStaticsAfter(ex: UtValueExecution<*>) = ex.paramsBefore + ex.staticsAfter + ex.evaluatedResult +fun withThisAndStaticsAfter(ex: UtValueExecution<*>) = listOf(ex.callerBefore) + ex.paramsBefore + ex.staticsAfter + ex.evaluatedResult +fun withThisAndResult(ex: UtValueExecution<*>) = listOf(ex.callerBefore) + ex.paramsBefore + ex.evaluatedResult +fun withThisStaticsBeforeAndResult(ex: UtValueExecution<*>) = + listOf(ex.callerBefore) + ex.paramsBefore + ex.staticsBefore + ex.evaluatedResult + +fun withThisAndException(ex: UtValueExecution<*>) = listOf(ex.callerBefore) + ex.paramsBefore + ex.returnValue +fun withMocks(ex: UtValueExecution<*>) = ex.paramsBefore + listOf(ex.mocks) + ex.evaluatedResult +fun withMocksAndInstrumentation(ex: UtValueExecution<*>) = + ex.paramsBefore + listOf(ex.mocks) + listOf(ex.instrumentation) + ex.evaluatedResult + +fun withThisMocksAndExceptions(ex: UtValueExecution<*>) = + listOf(ex.callerBefore) + ex.paramsBefore + listOf(ex.mocks) + ex.returnValue + +fun withMocksInstrumentationAndThis(ex: UtValueExecution<*>) = + listOf(ex.callerBefore) + ex.paramsBefore + listOf(ex.mocks) + listOf(ex.instrumentation) + ex.evaluatedResult + +// mutations +fun withParamsMutations(ex: UtValueExecution<*>) = ex.paramsBefore + ex.paramsAfter +fun withMutations(ex: UtValueExecution<*>) = ex.paramsBefore + ex.staticsBefore + ex.paramsAfter + ex.staticsAfter +fun withParamsMutationsAndResult(ex: UtValueExecution<*>) = ex.paramsBefore + ex.paramsAfter + ex.evaluatedResult +fun withMutationsAndResult(ex: UtValueExecution<*>) = + ex.paramsBefore + ex.staticsBefore + ex.paramsAfter + ex.staticsAfter + ex.evaluatedResult + +fun withMutationsAndThis(ex: UtValueExecution<*>) = + mutableListOf().apply { + add(ex.callerBefore) + addAll(ex.paramsBefore) + add(ex.staticsBefore) + + add(ex.callerAfter) + addAll(ex.paramsAfter) + add(ex.staticsAfter) + + add(ex.evaluatedResult) + } + +private val UtValueExecution<*>.callerBefore get() = stateBefore.caller!!.value +private val UtValueExecution<*>.paramsBefore get() = stateBefore.params.map { it.value } +private val UtValueExecution<*>.staticsBefore get() = stateBefore.statics + +private val UtValueExecution<*>.callerAfter get() = stateAfter.caller!!.value +private val UtValueExecution<*>.paramsAfter get() = stateAfter.params.map { it.value } +private val UtValueExecution<*>.staticsAfter get() = stateAfter.statics + +private val UtValueExecution<*>.evaluatedResult get() = returnValue.getOrNull() + +fun Map>.findByName(name: String) = entries.single { name == it.key.name }.value.value +fun Map>.singleValue() = values.single().value + +typealias StaticsType = Map> +private typealias Mocks = List +private typealias Instrumentation = List + +inline fun withSettingsFromTestFrameworkConfiguration( + config: TestInfrastructureConfiguration, + block: () -> T +): T { + val substituteStaticsWithSymbolicVariable = UtSettings.substituteStaticsWithSymbolicVariable + UtSettings.substituteStaticsWithSymbolicVariable = config.resetNonFinalFieldsAfterClinit + + val previousConfig = TestCodeGeneratorPipeline.currentTestInfrastructureConfiguration + TestCodeGeneratorPipeline.currentTestInfrastructureConfiguration = config + try { + return block() + } finally { + UtSettings.substituteStaticsWithSymbolicVariable = substituteStaticsWithSymbolicVariable + TestCodeGeneratorPipeline.currentTestInfrastructureConfiguration = previousConfig + } +} + +/** + * Avoid conflict with java.util.stream.Stream.toList (available since Java 16 only) + */ +fun Stream.asList(): List = collect(Collectors.toList()) diff --git a/utbot-testing/src/main/kotlin/org/utbot/testing/UtValueTestCaseCheckerForTaint.kt b/utbot-testing/src/main/kotlin/org/utbot/testing/UtValueTestCaseCheckerForTaint.kt new file mode 100644 index 0000000000..050b26b3af --- /dev/null +++ b/utbot-testing/src/main/kotlin/org/utbot/testing/UtValueTestCaseCheckerForTaint.kt @@ -0,0 +1,37 @@ +package org.utbot.testing + +import org.junit.jupiter.api.AfterAll +import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.plugin.api.* +import org.utbot.taint.TaintConfigurationProvider +import kotlin.reflect.KClass + +open class UtValueTestCaseCheckerForTaint( + testClass: KClass<*>, + testCodeGeneration: Boolean = true, + pipelines: List = listOf( + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.DO_NOT_PARAMETRIZE, Compilation), + Configuration(CodegenLanguage.JAVA, ParametrizedTestSource.PARAMETRIZE, Compilation), + Configuration(CodegenLanguage.KOTLIN, ParametrizedTestSource.DO_NOT_PARAMETRIZE, CodeGeneration), + ), + private val taintConfigurationProvider: TaintConfigurationProvider, +) : UtValueTestCaseChecker(testClass, testCodeGeneration, pipelines) { + + init { + UtSettings.useTaintAnalysis = true + } + + override fun createTestCaseGenerator(buildInfo: CodeGenerationIntegrationTest.Companion.BuildInfo) = + TestSpecificTestCaseGenerator( + buildInfo.buildDir, + buildInfo.dependencyPath, + System.getProperty("java.class.path"), + taintConfigurationProvider = taintConfigurationProvider, + ) + + @AfterAll + fun reset() { + UtSettings.useTaintAnalysis = false + } +} \ No newline at end of file diff --git a/utbot-testing/src/main/kotlin/org/utbot/testing/Utils.kt b/utbot-testing/src/main/kotlin/org/utbot/testing/Utils.kt new file mode 100644 index 0000000000..60ced254e9 --- /dev/null +++ b/utbot-testing/src/main/kotlin/org/utbot/testing/Utils.kt @@ -0,0 +1,11 @@ +package org.utbot.testing + +import org.utbot.framework.plugin.api.UtExecutionFailure +import org.utbot.framework.plugin.api.UtExecutionResult +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtModel + +fun UtExecutionResult.getOrThrow(): UtModel = when (this) { + is UtExecutionSuccess -> model + is UtExecutionFailure -> throw exception +} diff --git a/utbot-ui-commons/build.gradle.kts b/utbot-ui-commons/build.gradle.kts new file mode 100644 index 0000000000..10ab87140d --- /dev/null +++ b/utbot-ui-commons/build.gradle.kts @@ -0,0 +1,79 @@ +val kotlinLoggingVersion: String by rootProject +val semVer: String? by rootProject +val slf4jVersion: String by rootProject + +// === IDE settings === +val projectType: String by rootProject +val communityEdition: String by rootProject +val ultimateEdition: String by rootProject + +val ideType: String by rootProject +val androidStudioPath: String? by rootProject + +val ideaVersion: String? by rootProject +val pycharmVersion: String? by rootProject +val golandVersion: String? by rootProject + +val javaIde: String? by rootProject +val pythonIde: String? by rootProject +val jsIde: String? by rootProject +val goIde: String? by rootProject + +val ideVersion = when(ideType) { + "PC", "PY" -> pycharmVersion + "GO" -> golandVersion + else -> ideaVersion +} + +val pythonCommunityPluginVersion: String? by rootProject +val pythonUltimatePluginVersion: String? by rootProject +val goPluginVersion: String? by rootProject + +// https://plugins.jetbrains.com/docs/intellij/android-studio.html#configuring-the-plugin-pluginxml-file +val ideTypeOrAndroidStudio = if (androidStudioPath == null) ideType else "IC" + +project.tasks.asMap["runIde"]?.enabled = false +// === IDE settings === + +plugins { + id("org.jetbrains.intellij") version "1.13.1" +} + +intellij { + version.set(ideVersion) + type.set(ideType) +} + +tasks { + compileKotlin { + kotlinOptions { + jvmTarget = "17" + freeCompilerArgs = freeCompilerArgs + listOf("-Xallow-result-return-type", "-Xsam-conversions=class") + allWarningsAsErrors = false + } + } + + java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + runIde { + jvmArgs("-Xmx2048m") + jvmArgs("--add-exports", "java.desktop/sun.awt.windows=ALL-UNNAMED") + } + + patchPluginXml { + sinceBuild.set("223") + untilBuild.set("232.*") + version.set(semVer) + } +} + +dependencies { + implementation(group = "io.github.microutils", name = "kotlin-logging", version = kotlinLoggingVersion) + implementation(group = "org.jetbrains", name = "annotations", version = "16.0.2") + implementation(project(":utbot-api")) + implementation(project(":utbot-framework")) + implementation(group = "org.slf4j", name = "slf4j-api", version = slf4jVersion) +} diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/language/agnostic/LanguageAssistant.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/language/agnostic/LanguageAssistant.kt new file mode 100644 index 0000000000..732feff717 --- /dev/null +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/language/agnostic/LanguageAssistant.kt @@ -0,0 +1,114 @@ +package org.utbot.intellij.plugin.language.agnostic + +import mu.KotlinLogging +import com.intellij.lang.Language +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.PlatformDataKeys +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiFileSystemItem +import com.intellij.psi.PsiManager + +private val logger = KotlinLogging.logger {} + +abstract class LanguageAssistant { + + abstract fun update(e: AnActionEvent) + abstract fun actionPerformed(e: AnActionEvent) + + companion object { + private val languages = mutableMapOf() + + fun get(e: AnActionEvent): LanguageAssistant? { + val project = e.project ?: return null + val editor = e.getData(CommonDataKeys.EDITOR) + if (editor != null) { + //The action is being called from editor + e.getData(CommonDataKeys.PSI_FILE)?.language?.let { language -> + updateLanguages(language) + return languages[language.id] + } ?: return null + } else { + // The action is being called from 'Project' tool window + val language = when (val element = e.getData(CommonDataKeys.PSI_ELEMENT)) { + is PsiFileSystemItem -> { + e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY)?.let { + findLanguageRecursively(project, it) + } + } + is PsiElement -> { + element.containingFile?.let { getLanguageFromFile(it) } + } + else -> { + val someSelection = e.getData(PlatformDataKeys.PSI_ELEMENT_ARRAY)?: return null + someSelection.firstNotNullOfOrNull { + when(it) { + is PsiFileSystemItem -> findLanguageRecursively(project, arrayOf(it.virtualFile)) + is PsiElement -> it.language + else -> { null } + } + } + } + } ?: return null + + updateLanguages(language) + return languages[language.id] + } + } + + private fun updateLanguages(language: Language) { + if (!languages.containsKey(language.id)) { + loadWithException(language)?.let { + languages.put(language.id, it.kotlin.objectInstance as LanguageAssistant) + } + } + } + + private fun getLanguageFromFile(file: PsiFile): Language? { + updateLanguages(file.language) + return if (languages.containsKey(file.language.id)) + file.language + else + null + } + + private fun findLanguageRecursively(directory: PsiDirectory): Language? { + return directory.files + .firstNotNullOfOrNull { getLanguageFromFile(it) } ?: + directory.subdirectories.firstNotNullOfOrNull { findLanguageRecursively(it) } + } + + private fun findLanguageRecursively(project: Project, virtualFiles: Array): Language? { + val psiFiles = virtualFiles.mapNotNull { + PsiManager.getInstance(project).findFile(it) + } + val psiDirectories = virtualFiles.mapNotNull { + PsiManager.getInstance(project).findDirectory(it) + } + + val fileLanguage = psiFiles.firstNotNullOfOrNull { getLanguageFromFile(it) } + return fileLanguage ?: psiDirectories.firstNotNullOfOrNull { findLanguageRecursively(it) } + } + } +} + +private fun loadWithException(language: Language): Class<*>? { + try { + return when (language.id) { + "Python" -> Class.forName("org.utbot.intellij.plugin.python.language.PythonLanguageAssistant") + "ECMAScript 6" -> Class.forName("org.utbot.intellij.plugin.js.language.JsLanguageAssistant") + "go" -> Class.forName("org.utbot.intellij.plugin.go.language.GoLanguageAssistant") + "JAVA", "kotlin" -> Class.forName("org.utbot.intellij.plugin.language.JvmLanguageAssistant") + else -> error("Unknown language id: ${language.id}") + } + } catch (e: ClassNotFoundException) { + logger.info("Language ${language.id} is disabled") + } catch (e: IllegalStateException) { + logger.info("Language ${language.id} is not supported") + } + return null +} \ No newline at end of file diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/models/BaseTestModel.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/models/BaseTestModel.kt new file mode 100644 index 0000000000..c6d22c4530 --- /dev/null +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/models/BaseTestModel.kt @@ -0,0 +1,17 @@ +package org.utbot.intellij.plugin.models + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import org.utbot.framework.codegen.domain.ProjectType +import org.utbot.framework.plugin.api.CodegenLanguage + + +open class BaseTestsModel( + val project: Project, +) { + var testSourceRoot: VirtualFile? = null + var testPackageName: String? = null + open var sourceRootHistory : MutableList = mutableListOf() + open lateinit var codegenLanguage: CodegenLanguage + open lateinit var projectType: ProjectType +} diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/BaseSettingsWindow.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/BaseSettingsWindow.kt new file mode 100644 index 0000000000..8e95275aaf --- /dev/null +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/BaseSettingsWindow.kt @@ -0,0 +1,85 @@ +package org.utbot.intellij.plugin.settings + +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogPanel +import com.intellij.ui.dsl.builder.* +import javax.swing.* +import kotlin.reflect.KClass +import org.utbot.framework.codegen.domain.HangingTestsTimeout +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.plugin.api.CodeGenerationSettingItem +import org.utbot.intellij.plugin.ui.components.CodeGenerationSettingItemRenderer + +class BaseSettingsWindow(val project: Project) { + private val settings = project.service() + + private lateinit var enableExperimentalLanguagesCheckBox: JCheckBox + + private fun Row.createCombo(loader: KClass<*>, values: Array<*>) { + comboBox(DefaultComboBoxModel(values)) + .bindItem( + getter = { settings.providerNameByServiceLoader(loader) }, + setter = { settings.setProviderByLoader(loader, it as CodeGenerationSettingItem) }, + ).component.renderer = CodeGenerationSettingItemRenderer() + } + + val panel: JPanel = panel { + group("Common Settings") { + row("Tests with exceptions:") { + createCombo(RuntimeExceptionTestsBehaviour::class, RuntimeExceptionTestsBehaviour.values()) + } + + row("Hanging test timeout:") { + spinner( + range = IntRange( + HangingTestsTimeout.MIN_TIMEOUT_MS.toInt(), + HangingTestsTimeout.MAX_TIMEOUT_MS.toInt() + ), + step = 50 + ).bindIntValue( + getter = { + settings.hangingTestsTimeout.timeoutMs + .coerceIn(HangingTestsTimeout.MIN_TIMEOUT_MS, HangingTestsTimeout.MAX_TIMEOUT_MS).toInt() + }, + setter = { + settings.hangingTestsTimeout = HangingTestsTimeout(it.toLong()) + } + ) + + label("milliseconds per method") + contextHelp( + "Set this timeout to define which test is \"hanging\". Increase it to test the " + + "time-consuming method or decrease if the execution speed is critical for you." + ) + } + + row { + enableExperimentalLanguagesCheckBox = checkBox("Experimental languages support") + .onApply { + settings.state.enableExperimentalLanguagesSupport = + enableExperimentalLanguagesCheckBox.isSelected + } + .onReset { + enableExperimentalLanguagesCheckBox.isSelected = + settings.experimentalLanguagesSupport == true + } + .onIsModified { enableExperimentalLanguagesCheckBox.isSelected xor settings.experimentalLanguagesSupport } + .component + contextHelp("Enable JavaScript and Python if IDE supports them") + }.bottomGap(BottomGap.MEDIUM) + } + } + + fun isModified(): Boolean { + return (panel as DialogPanel).isModified() + } + + fun apply() { + (panel as DialogPanel).apply() + } + + fun reset() { + (panel as DialogPanel).reset() + } +} diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt new file mode 100644 index 0000000000..e75e865f35 --- /dev/null +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt @@ -0,0 +1,318 @@ +@file:Suppress("MemberVisibilityCanBePrivate") + +package org.utbot.intellij.plugin.settings + +import com.intellij.openapi.components.PersistentStateComponent +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import com.intellij.openapi.project.Project +import com.intellij.util.xmlb.Converter +import com.intellij.util.xmlb.annotations.OptionTag +import java.io.IOException +import java.nio.file.Files +import java.nio.file.Paths +import org.utbot.common.FileUtil +import org.utbot.engine.Mocker +import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.domain.ForceStaticMocking +import org.utbot.framework.codegen.domain.HangingTestsTimeout +import org.utbot.framework.codegen.domain.Junit4 +import org.utbot.framework.codegen.domain.Junit5 +import org.utbot.framework.codegen.domain.MockitoStaticMocking +import org.utbot.framework.codegen.domain.NoStaticMocking +import org.utbot.framework.codegen.domain.ParametrizedTestSource +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.TestFramework +import org.utbot.framework.codegen.domain.TestNg +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.CodeGenerationSettingItem +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.JavaDocCommentStyle +import org.utbot.framework.plugin.api.MockFramework +import org.utbot.framework.plugin.api.MockStrategyApi +import org.utbot.framework.plugin.api.TreatOverflowAsError +import java.util.concurrent.CompletableFuture +import kotlin.reflect.KClass +import org.utbot.common.isWindows +import org.utbot.framework.SummariesGenerationType +import org.utbot.framework.codegen.domain.UnknownTestFramework +import org.utbot.framework.plugin.api.SpringTestType +import org.utbot.framework.plugin.api.isSummarizationCompatible +import org.utbot.framework.plugin.api.SpringProfileNames +import org.utbot.framework.plugin.api.SpringConfig + +@State( + name = "UtBotSettings", + storages = [Storage("utbot-settings.xml")] +) +@Service(Service.Level.PROJECT) +class Settings(val project: Project) : PersistentStateComponent { + data class State( + var sourceRootHistory: MutableList = mutableListOf(), + var codegenLanguage: CodegenLanguage = CodegenLanguage.defaultItem, + @OptionTag(converter = TestFrameworkConverter::class) + var testFramework: TestFramework = TestFramework.defaultItem, + var mockStrategy: MockStrategyApi = MockStrategyApi.defaultItem, + var mockFramework: MockFramework = MockFramework.defaultItem, + @OptionTag(converter = StaticsMockingConverter::class) + var staticsMocking: StaticsMocking = StaticsMocking.defaultItem, + var runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.defaultItem, + @OptionTag(converter = HangingTestsTimeoutConverter::class) + var hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(), + var useTaintAnalysis: Boolean = false, + var runInspectionAfterTestGeneration: Boolean = true, + var forceStaticMocking: ForceStaticMocking = ForceStaticMocking.defaultItem, + var treatOverflowAsError: TreatOverflowAsError = TreatOverflowAsError.defaultItem, + var parametrizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem, + var classesToMockAlways: Array = Mocker.defaultSuperClassesToMockAlwaysNames.toTypedArray(), + var springTestType: SpringTestType = SpringTestType.defaultItem, + var springConfig: String = SpringConfig.defaultItem, + var springProfileNames: String = SpringProfileNames.defaultItem, + var fuzzingValue: Double = 0.05, + var runGeneratedTestsWithCoverage: Boolean = false, + var commentStyle: JavaDocCommentStyle = JavaDocCommentStyle.defaultItem, + var summariesGenerationType: SummariesGenerationType = UtSettings.summaryGenerationType, + var generationTimeoutInMillis: Long = UtSettings.utBotGenerationTimeoutInMillis, + var enableExperimentalLanguagesSupport: Boolean = true, + var isSpringHandled: Boolean = false, + ) { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as State + + if (sourceRootHistory != other.sourceRootHistory) return false + if (codegenLanguage != other.codegenLanguage) return false + if (testFramework != other.testFramework) return false + if (mockStrategy != other.mockStrategy) return false + if (mockFramework != other.mockFramework) return false + if (staticsMocking != other.staticsMocking) return false + if (runtimeExceptionTestsBehaviour != other.runtimeExceptionTestsBehaviour) return false + if (hangingTestsTimeout != other.hangingTestsTimeout) return false + if (useTaintAnalysis != other.useTaintAnalysis) return false + if (runInspectionAfterTestGeneration != other.runInspectionAfterTestGeneration) return false + if (forceStaticMocking != other.forceStaticMocking) return false + if (treatOverflowAsError != other.treatOverflowAsError) return false + if (parametrizedTestSource != other.parametrizedTestSource) return false + if (!classesToMockAlways.contentEquals(other.classesToMockAlways)) return false + if (springTestType != other.springTestType) return false + if (springConfig != other.springConfig) return false + if (springProfileNames != other.springProfileNames) return false + if (fuzzingValue != other.fuzzingValue) return false + if (runGeneratedTestsWithCoverage != other.runGeneratedTestsWithCoverage) return false + if (commentStyle != other.commentStyle) return false + if (summariesGenerationType != other.summariesGenerationType) return false + if (generationTimeoutInMillis != other.generationTimeoutInMillis) return false + + return true + } + override fun hashCode(): Int { + var result = sourceRootHistory.hashCode() + result = 31 * result + codegenLanguage.hashCode() + result = 31 * result + testFramework.hashCode() + result = 31 * result + mockStrategy.hashCode() + result = 31 * result + mockFramework.hashCode() + result = 31 * result + staticsMocking.hashCode() + result = 31 * result + runtimeExceptionTestsBehaviour.hashCode() + result = 31 * result + hangingTestsTimeout.hashCode() + result = 31 * result + useTaintAnalysis.hashCode() + result = 31 * result + runInspectionAfterTestGeneration.hashCode() + result = 31 * result + forceStaticMocking.hashCode() + result = 31 * result + treatOverflowAsError.hashCode() + result = 31 * result + parametrizedTestSource.hashCode() + result = 31 * result + classesToMockAlways.contentHashCode() + result = 31 * result + springTestType.hashCode() + result = 31 * result + springConfig.hashCode() + result = 31 * result + springProfileNames.hashCode() + result = 31 * result + fuzzingValue.hashCode() + result = 31 * result + if (runGeneratedTestsWithCoverage) 1 else 0 + result = 31 * result + summariesGenerationType.hashCode() + result = 31 * result + generationTimeoutInMillis.hashCode() + + return result + } + } + + private var state = State() + val sourceRootHistory: MutableList get() = state.sourceRootHistory + + val codegenLanguage: CodegenLanguage get() = state.codegenLanguage + + val testFramework: TestFramework get() = state.testFramework + + val mockStrategy: MockStrategyApi get() = state.mockStrategy + + val runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour get() = state.runtimeExceptionTestsBehaviour + + var hangingTestsTimeout: HangingTestsTimeout + get() = state.hangingTestsTimeout + set(value) { + state.hangingTestsTimeout = value + } + + var generationTimeoutInMillis : Long + get() = state.generationTimeoutInMillis + set(value) { + state.generationTimeoutInMillis = value + } + + val staticsMocking: StaticsMocking get() = state.staticsMocking + + val useTaintAnalysis: Boolean get() = state.useTaintAnalysis + + val runInspectionAfterTestGeneration: Boolean get() = state.runInspectionAfterTestGeneration + + val forceStaticMocking: ForceStaticMocking get() = state.forceStaticMocking + + val experimentalLanguagesSupport: Boolean get () = state.enableExperimentalLanguagesSupport + + val treatOverflowAsError: TreatOverflowAsError get() = state.treatOverflowAsError + + val parametrizedTestSource: ParametrizedTestSource get() = state.parametrizedTestSource + + val classesToMockAlways: Set get() = state.classesToMockAlways.toSet() + + val springTestType: SpringTestType get() = state.springTestType + + val springConfig: String get() = state.springConfig + + val springProfileNames: String get() = state.springProfileNames + + val javaDocCommentStyle: JavaDocCommentStyle get() = state.commentStyle + + var fuzzingValue: Double + get() = state.fuzzingValue + set(value) { + state.fuzzingValue = value.coerceIn(0.0, 1.0) + } + var runGeneratedTestsWithCoverage = state.runGeneratedTestsWithCoverage + + var enableSummariesGeneration = state.summariesGenerationType + + /** + * Defaults in Spring are slightly different, so for every Spring project we update settings, but only + * do it once so user is not stuck with defaults, hence this flag is needed to avoid repeated updates. + */ + var isSpringHandled: Boolean + get() = state.isSpringHandled + set(value) { + state.isSpringHandled = value + } + + fun setClassesToMockAlways(classesToMockAlways: List) { + state.classesToMockAlways = classesToMockAlways.distinct().toTypedArray() + } + + override fun getState(): State = state + + override fun initializeComponent() { + super.initializeComponent() + CompletableFuture.runAsync { + FileUtil.clearTempDirectory(UtSettings.daysLimitForTempFiles) + + // Don't replace file with custom user's settings + if (UtSettings.areCustomized()) return@runAsync + // In case settings.properties file is not yet presented + // (or stays with all default template values) in {homeDir}/.utbot folder + // we copy (or re-write) it from plugin resource file + val settingsClass = javaClass + Paths.get(UtSettings.defaultSettingsPath()).toFile().apply { + try { + this.parentFile.apply { + if (this.mkdirs() && isWindows) Files.setAttribute(this.toPath(), "dos:hidden", true) + } + settingsClass.getResource("../../../../../settings.properties")?.let { + this.writeBytes(it.openStream().readBytes()) + } + } catch (ignored: IOException) { + } + } + } + } + + override fun loadState(state: State) { + this.state = state + if (!state.codegenLanguage.isSummarizationCompatible()) { + this.state.summariesGenerationType = SummariesGenerationType.NONE + } + } + + // these classes are all ref types so we can use only names here + fun chosenClassesToMockAlways(): Set = state.classesToMockAlways.mapTo(mutableSetOf()) { ClassId(it) } + + fun setProviderByLoader(loader: KClass<*>, provider: CodeGenerationSettingItem) = + when (loader) { + // TODO: service loaders for test generator and code generator are removed from settings temporarily +// TestGeneratorServiceLoader::class -> setGeneratorName(provider) +// CodeGeneratorServiceLoader::class -> setCodeGeneratorName(provider) + MockStrategyApi::class -> state.mockStrategy = provider as MockStrategyApi + CodegenLanguage::class -> state.codegenLanguage = provider as CodegenLanguage + RuntimeExceptionTestsBehaviour::class -> { + state.runtimeExceptionTestsBehaviour = provider as RuntimeExceptionTestsBehaviour + } + ForceStaticMocking::class -> state.forceStaticMocking = provider as ForceStaticMocking + TreatOverflowAsError::class -> { + // TODO: SAT-1566 + state.treatOverflowAsError = provider as TreatOverflowAsError + UtSettings.treatOverflowAsError = provider == TreatOverflowAsError.AS_ERROR + } + JavaDocCommentStyle::class -> state.commentStyle = provider as JavaDocCommentStyle + // TODO: add error processing + else -> error("Unknown class [$loader] to map value [$provider]") + } + + fun providerNameByServiceLoader(loader: KClass<*>): CodeGenerationSettingItem = + when (loader) { + // TODO: service loaders for test generator and code generator are removed from settings temporarily +// TestGeneratorServiceLoader::class -> generatorName +// CodeGeneratorServiceLoader::class -> codeGeneratorName + MockStrategyApi::class -> mockStrategy + CodegenLanguage::class -> codegenLanguage + RuntimeExceptionTestsBehaviour::class -> runtimeExceptionTestsBehaviour + ForceStaticMocking::class -> forceStaticMocking + TreatOverflowAsError::class -> treatOverflowAsError + JavaDocCommentStyle::class -> javaDocCommentStyle + // TODO: add error processing + else -> error("Unknown service loader: $loader") + } +} + +// use it to serialize testFramework in State +private class TestFrameworkConverter : Converter() { + override fun toString(value: TestFramework): String = value.id + + override fun fromString(value: String): TestFramework = when (value) { + Junit4.id -> Junit4 + Junit5.id -> Junit5 + TestNg.id -> TestNg + else -> UnknownTestFramework(value) + } +} + +// use it to serialize staticsMocking in State +private class StaticsMockingConverter : Converter() { + override fun toString(value: StaticsMocking): String = "$value" + + override fun fromString(value: String): StaticsMocking = when (value) { + NoStaticMocking.id -> NoStaticMocking + MockitoStaticMocking.id -> MockitoStaticMocking + else -> error("Unknown StaticsMocking $value") + } +} + +// TODO is it better to use kotlinx.serialization? +// use it to serialize hangingTestsTimeout in State +private class HangingTestsTimeoutConverter : Converter() { + override fun toString(value: HangingTestsTimeout): String = + "HangingTestsTimeout:${value.timeoutMs}" + + override fun fromString(value: String): HangingTestsTimeout { + val arguments = value.substringAfter("HangingTestsTimeout:") + val timeoutMs = arguments.toLong() + return HangingTestsTimeout(timeoutMs) + } +} diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Configurable.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/Configurable.kt similarity index 91% rename from utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Configurable.kt rename to utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/Configurable.kt index 55acb7b882..3c09b84ea2 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Configurable.kt +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/Configurable.kt @@ -7,11 +7,11 @@ import javax.swing.JComponent class Configurable(val project: Project) : SearchableConfigurable { private val displayName: String = "UtBot Configuration" private val id: String = "org.utbot.intellij.plugin.settings.UtBotSettingsConfigurable" - private val settingsWindow = SettingsWindow(project) + private val settingsWindow = BaseSettingsWindow(project) override fun createComponent(): JComponent = settingsWindow.panel - override fun isModified(): Boolean = settingsWindow.isModified + override fun isModified(): Boolean = settingsWindow.isModified() override fun apply() = settingsWindow.apply() diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/TestFrameworkMapper.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/TestFrameworkMapper.kt new file mode 100644 index 0000000000..79c290b82e --- /dev/null +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/TestFrameworkMapper.kt @@ -0,0 +1,9 @@ +package org.utbot.intellij.plugin.settings + +import org.utbot.framework.codegen.domain.TestFramework + +interface TestFrameworkMapper { + fun toString(value: TestFramework): String + fun fromString(value: String): TestFramework + fun handleUnknown(testFramework: TestFramework): TestFramework +} \ No newline at end of file diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt new file mode 100644 index 0000000000..be3623d853 --- /dev/null +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/Notifications.kt @@ -0,0 +1,207 @@ +package org.utbot.intellij.plugin.ui + +import com.intellij.notification.Notification +import com.intellij.notification.NotificationDisplayType +import com.intellij.notification.NotificationGroup +import com.intellij.notification.NotificationListener +import com.intellij.notification.NotificationType +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.keymap.KeymapUtil +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectActivity +import com.intellij.openapi.wm.WindowManager +import com.intellij.ui.GotItTooltip +import javax.swing.event.HyperlinkEvent +import mu.KotlinLogging + +abstract class Notifier { + protected val logger = KotlinLogging.logger {} + + protected abstract val notificationType: NotificationType + protected abstract val displayId: String + protected open fun content(project: Project?, module: Module?, info: String): String = info + + open fun notify(info: String, project: Project? = null, module: Module? = null) { + notify(info, project, module, AnAction.EMPTY_ARRAY) + } + + open fun notify(info: String, project: Project? = null, module: Module? = null, actions: Array) { + notificationGroup + .createNotification(content(project, module, info), notificationType) + .apply { actions.forEach { this.addAction(it) } } + .notify(project) + } + + protected open val notificationDisplayType = NotificationDisplayType.BALLOON + + protected val notificationGroup: NotificationGroup + get() = NotificationGroup.findRegisteredGroup(displayId) ?: NotificationGroup(displayId, notificationDisplayType) +} + +abstract class WarningNotifier : Notifier() { + override val notificationType: NotificationType = NotificationType.WARNING + final override fun notify(info: String, project: Project?, module: Module?) { + super.notify(info, project, module) + } +} + +abstract class ErrorNotifier : Notifier() { + final override val notificationType: NotificationType = NotificationType.ERROR + + override fun notify(info: String, project: Project?, module: Module?) { + super.notify(info, project, module) + error(content(project, module, info)) + } +} + +object CommonErrorNotifier : ErrorNotifier() { + override val displayId: String = "UTBot plugin errors" +} + +class CommonLoggingNotifier(val type :NotificationType = NotificationType.WARNING) : Notifier() { + override val displayId: String = "UTBot plugin errors" + override val notificationType = type + + override fun notify(info: String, project: Project?, module: Module?) { + super.notify(info, project, module) + when (notificationType) { + NotificationType.WARNING -> logger.warn(content(project, module, info)) + NotificationType.INFORMATION -> logger.info(content(project, module, info)) + else -> logger.error(content(project, module, info)) + } + } +} + +object UnsupportedJdkNotifier : ErrorNotifier() { + override val displayId: String = "Unsupported JDK" + override fun content(project: Project?, module: Module?, info: String): String = + "JDK versions older than 8 are not supported. This project's JDK version is $info" +} + +object InvalidClassNotifier : WarningNotifier() { + override val displayId: String = "Invalid class" + override fun content(project: Project?, module: Module?, info: String): String = + "Generate tests with UnitTestBot for the $info is not supported." +} + +object MissingLibrariesNotifier : WarningNotifier() { + override val displayId: String = "Missing libraries" + override fun content(project: Project?, module: Module?, info: String): String = + "Library $info missing on the test classpath of module ${module?.name}" +} + +@Suppress("unused") +object UnsupportedTestFrameworkNotifier : ErrorNotifier() { + override val displayId: String = "Unsupported test framework" + override fun content(project: Project?, module: Module?, info: String): String = + "Test framework $info is not supported yet" +} + +abstract class UrlNotifier : Notifier() { + + protected abstract val titleText: String + protected abstract val urlOpeningListener: NotificationListener + + override fun notify(info: String, project: Project?, module: Module?) { + notificationGroup.createNotification(content(project, module, info), notificationType) + .setTitle(titleText).setListener(urlOpeningListener).notify(project) + } +} + +abstract class InformationUrlNotifier : UrlNotifier() { + override val notificationType: NotificationType = NotificationType.INFORMATION +} + +abstract class WarningUrlNotifier : UrlNotifier() { + override val notificationType: NotificationType = NotificationType.WARNING +} + +abstract class EventLogNotifier : InformationUrlNotifier() { + override val notificationDisplayType = NotificationDisplayType.NONE +} + +object SarifReportNotifier : EventLogNotifier() { + + override val displayId: String = "SARIF report" + + override val titleText: String = "" // no title + + override val urlOpeningListener: NotificationListener = NotificationListener.UrlOpeningListener(false) +} + +object TestsReportNotifier : InformationUrlNotifier() { + override val displayId: String = "Generated unit tests report" + + override val titleText: String = "UnitTestBot: unit tests generated successfully" + + public override val urlOpeningListener: TestReportUrlOpeningListener = TestReportUrlOpeningListener +} + +// TODO replace inheritance with decorators +object WarningTestsReportNotifier : WarningUrlNotifier() { + override val displayId: String = "Generated unit tests report" + + override val titleText: String = "UnitTestBot: unit tests generated with warnings" + + public override val urlOpeningListener: TestReportUrlOpeningListener = TestReportUrlOpeningListener +} + +object DetailsTestsReportNotifier : EventLogNotifier() { + override val displayId: String = "Test report details" + + override val titleText: String = "Test report details of the unit tests generation via UnitTestBot" + + public override val urlOpeningListener: TestReportUrlOpeningListener = TestReportUrlOpeningListener +} + +/** + * Listener that handles URLs starting with [prefix], like "#utbot/configure-mockito". + */ +object TestReportUrlOpeningListener: NotificationListener.Adapter() { + const val prefix = "#utbot/" + const val mockitoSuffix = "configure-mockito" + const val mockitoInlineSuffix = "mockito-inline" + const val eventLogSuffix = "event-log" + + val callbacks: Map Unit>> = hashMapOf( + Pair(mockitoSuffix, mutableListOf()), + Pair(mockitoInlineSuffix, mutableListOf()), + Pair(eventLogSuffix, mutableListOf()), + ) + + private val defaultListener = NotificationListener.UrlOpeningListener(false) + + override fun hyperlinkActivated(notification: Notification, e: HyperlinkEvent) { + val description = e.description + if (description.startsWith(prefix)) { + handleDescription(description.removePrefix(prefix)) + } else { + return defaultListener.hyperlinkUpdate(notification, e) + } + } + + private fun handleDescription(descriptionSuffix: String) = + callbacks[descriptionSuffix]?.map { it() } ?: error("No such command with #utbot prefix: $descriptionSuffix") +} + +object GotItTooltipActivity : ProjectActivity { + private const val KEY = "UTBot.GotItMessageWasShown" + override suspend fun execute(project: Project) { + ApplicationManager.getApplication().invokeLater { + val shortcut = ActionManager.getInstance() + .getKeyboardShortcut("org.utbot.intellij.plugin.ui.actions.GenerateTestsAction")?:return@invokeLater + val shortcutText = KeymapUtil.getShortcutText(shortcut) + val message = GotItTooltip(KEY, + "
    You can get test coverage for methods,
    Java classes, and even for whole source roots
    with $shortcutText
    ") + .withHeader("UnitTestBot is ready!") + if (message.canShow()) { + WindowManager.getInstance().getFrame(project)?.rootPane?.let { + message.show(it, GotItTooltip.BOTTOM_LEFT) + } + } + } + } +} diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/components/CodeGenerationSettingItemRenderer.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/components/CodeGenerationSettingItemRenderer.kt new file mode 100644 index 0000000000..48e46a1e3d --- /dev/null +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/components/CodeGenerationSettingItemRenderer.kt @@ -0,0 +1,22 @@ +package org.utbot.intellij.plugin.ui.components + +import java.awt.Component +import javax.swing.DefaultListCellRenderer +import javax.swing.JList +import org.utbot.framework.plugin.api.CodeGenerationSettingItem + +class CodeGenerationSettingItemRenderer : DefaultListCellRenderer() { + override fun getListCellRendererComponent( + list: JList<*>?, + value: Any?, + index: Int, + isSelected: Boolean, + cellHasFocus: Boolean + ): Component { + return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus).apply { + if (value is CodeGenerationSettingItem) { + text = value.displayName + } + } + } +} \ No newline at end of file diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestSourceDirectoryChooser.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestSourceDirectoryChooser.kt new file mode 100644 index 0000000000..8876815f6c --- /dev/null +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/components/TestSourceDirectoryChooser.kt @@ -0,0 +1,57 @@ +package org.utbot.intellij.plugin.ui.components + +import com.intellij.openapi.fileChooser.FileChooserDescriptor +import com.intellij.openapi.project.Project +import com.intellij.openapi.project.guessProjectDir +import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.ui.TextBrowseFolderListener +import com.intellij.openapi.ui.TextFieldWithBrowseButton +import com.intellij.openapi.ui.ValidationInfo +import com.intellij.openapi.vfs.VirtualFile +import java.nio.file.Paths +import org.utbot.common.PathUtil.replaceSeparator +import org.utbot.intellij.plugin.models.BaseTestsModel + +class TestSourceDirectoryChooser( + val model: BaseTestsModel, + file: VirtualFile? = null +) : TextFieldWithBrowseButton() { + private val projectRoot = file + ?.let { getContentRoot(model.project, file) } + ?: model.project.guessProjectDir() + ?: error("Source file lies outside of a module") + + init { + val descriptor = FileChooserDescriptor( + false, + true, + false, + false, + false, + false + ) + descriptor.setRoots(projectRoot) + addBrowseFolderListener( + TextBrowseFolderListener(descriptor, model.project) + ) + text = replaceSeparator(Paths.get(projectRoot.path, defaultDirectory).toString()) + } + + fun validatePath(): ValidationInfo? { + val typedPath = Paths.get(text).toAbsolutePath() + return if (typedPath.startsWith(replaceSeparator(projectRoot.path))) { + defaultDirectory = Paths.get(projectRoot.path).relativize(typedPath).toString() + null + } else + ValidationInfo("Specified directory lies outside of the project", this) + } + + private fun getContentRoot(project: Project, file: VirtualFile): VirtualFile { + return ProjectFileIndex.getInstance(project) + .getContentRootForFile(file) ?: error("Source file lies outside of a module") + } + + companion object { + private var defaultDirectory = "utbot_tests" + } +} \ No newline at end of file diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/DialogWindowUtils.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/DialogWindowUtils.kt new file mode 100644 index 0000000000..c462a1a7a6 --- /dev/null +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/DialogWindowUtils.kt @@ -0,0 +1,20 @@ +package org.utbot.intellij.plugin.ui.utils + +import com.intellij.ui.ColoredListCellRenderer +import com.intellij.ui.SimpleTextAttributes +import org.utbot.framework.codegen.domain.TestFramework +import javax.swing.JList + +fun createTestFrameworksRenderer(additionalText: String): ColoredListCellRenderer { + return object : ColoredListCellRenderer() { + override fun customizeCellRenderer( + list: JList, value: TestFramework, + index: Int, selected: Boolean, hasFocus: Boolean + ) { + this.append(value.displayName, SimpleTextAttributes.REGULAR_ATTRIBUTES) + if (!value.isInstalled) { + this.append(additionalText, SimpleTextAttributes.ERROR_ATTRIBUTES) + } + } + } +} diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ErrorUtils.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ErrorUtils.kt new file mode 100644 index 0000000000..05472409d9 --- /dev/null +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/ui/utils/ErrorUtils.kt @@ -0,0 +1,17 @@ +package org.utbot.intellij.plugin.ui.utils + +import com.intellij.openapi.application.invokeLater +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages + +fun showErrorDialogLater(project: Project, message: String, title: String) { + invokeLater { + Messages.showErrorDialog(project, message, title) + } +} + +fun showWarningDialogLater(project: Project, message: String, title: String) { + invokeLater { + Messages.showWarningDialog(project, message, title) + } +} \ No newline at end of file diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt new file mode 100644 index 0000000000..98b891ac98 --- /dev/null +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/util/IntelliJApiHelper.kt @@ -0,0 +1,75 @@ +package org.utbot.intellij.plugin.util + +import com.intellij.ide.plugins.PluginManagerCore +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.progress.ProgressIndicator +import com.intellij.openapi.project.Project +import com.intellij.util.PlatformUtils +import com.intellij.util.ReflectionUtil +import com.intellij.util.concurrency.AppExecutorUtil +import mu.KotlinLogging +import org.utbot.framework.CancellationStrategyType.* +import org.utbot.framework.UtSettings + +/** + * This object is required to encapsulate Android API usage and grant safe access to it. + */ +object IntelliJApiHelper { + private val logger = KotlinLogging.logger {} + enum class Target { THREAD_POOL, READ_ACTION, WRITE_ACTION, EDT_LATER } + + fun run(target: Target, indicator: ProgressIndicator? = null, logMessage : String, runnable: Runnable) { + logger.info { "[${target}]: " + logMessage + + if (indicator != null) ", indicator[${indicator.text}; ${(indicator.fraction * 100).toInt()}%]" else "" } + + if (indicator?.isCanceled == true) { + when (UtSettings.cancellationStrategyType) { + NONE, + SAVE_PROCESSED_RESULTS -> {} + CANCEL_EVERYTHING -> { + logger.info { "Indicator is already cancelled" } + return + } + } + } + + val wrapper = Runnable { + try { + runnable.run() + } catch (e: Exception) { + logger.error(e) { target.toString() } + throw e + } + } + when (target) { + Target.THREAD_POOL -> AppExecutorUtil.getAppExecutorService().submit { wrapper.run() } + Target.READ_ACTION -> runReadAction { wrapper.run() } + Target.WRITE_ACTION -> runWriteAction { wrapper.run() } + Target.EDT_LATER -> ApplicationManager.getApplication().invokeLater( wrapper, ModalityState.NON_MODAL ) + } + } + + private val isAndroidPluginAvailable: Boolean = + !PluginManagerCore.isDisabled(PluginId.getId("org.jetbrains.android")) + + fun isAndroidStudio(): Boolean = + isAndroidPluginAvailable && ("AndroidStudio" == PlatformUtils.getPlatformPrefix()) + + fun androidGradleSDK(project: Project): String? { + if (!isAndroidPluginAvailable) return null + try { + val finderClass = Class.forName("com.android.tools.idea.gradle.util.GradleProjectSettingsFinder") + var method = ReflectionUtil.getMethod(finderClass, "findGradleProjectSettings", Project::class.java) ?: return null + val gradleProjectSettings = method.invoke(project) ?: return null + method = ReflectionUtil.getMethod(gradleProjectSettings.javaClass, "getGradleJvm") ?: return null + val gradleJvm = method.invoke(gradleProjectSettings) + return if (gradleJvm is String) gradleJvm else null + } catch (e: Exception) { + return null + } + } +} diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/util/UtSettingsHelper.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/util/UtSettingsHelper.kt new file mode 100644 index 0000000000..3f6a400a5c --- /dev/null +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/util/UtSettingsHelper.kt @@ -0,0 +1,31 @@ +package org.utbot.intellij.plugin.util + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.OpenFileDescriptor +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VfsUtil +import java.io.File +import java.nio.charset.Charset +import org.utbot.framework.UtSettings + +fun showSettingsEditor(project: Project, key: String? = null) { + val ioFile = File(UtSettings.getPath()) + ApplicationManager.getApplication().executeOnPooledThread { + var logicalLine: Int = -1 + key?.let { + logicalLine = ioFile.readLines(Charset.defaultCharset()) + .indexOfFirst { s -> s.startsWith("$key=") or s.startsWith("#$key=") } + } + VfsUtil.findFileByIoFile(ioFile, true)?.let { + val descriptor = if (logicalLine != -1) { + OpenFileDescriptor(project, it, logicalLine, 0) + } else { + OpenFileDescriptor(project, it) + } + ApplicationManager.getApplication().invokeLater { + FileEditorManager.getInstance(project).openTextEditor(descriptor, true) + } + } + } +} diff --git a/utbot-usvm/build.gradle.kts b/utbot-usvm/build.gradle.kts new file mode 100644 index 0000000000..67c5b6d982 --- /dev/null +++ b/utbot-usvm/build.gradle.kts @@ -0,0 +1,74 @@ +val jacoDbVersion: String by rootProject +val usvmVersion: String by rootProject +val approximationsVersion: String by rootProject + +val approximationsRepo = "com.github.UnitTestBot.java-stdlib-approximations" +val usvmRepo = "com.github.UnitTestBot.usvm" + +repositories { + mavenCentral() + maven("https://jitpack.io") +} + +val approximations: Configuration by configurations.creating {} +val usvmApproximationsApi: Configuration by configurations.creating {} +val usvmInstrumentationCollector: Configuration by configurations.creating {} +val usvmInstrumentationRunner: Configuration by configurations.creating {} + +dependencies { + implementation(project(":utbot-framework-api")) + + implementation(group = "org.jacodb", name = "jacodb-core", version = jacoDbVersion) + implementation(group = "org.jacodb", name = "jacodb-analysis", version = jacoDbVersion) + implementation(group = "org.jacodb", name = "jacodb-approximations", version = jacoDbVersion) + + implementation(group = usvmRepo, name = "usvm-core", version = usvmVersion) + implementation(group = usvmRepo, name = "usvm-jvm", version = usvmVersion) + implementation(group = usvmRepo, name = "usvm-jvm-api", version = usvmVersion) + implementation(group = usvmRepo, name = "usvm-jvm-instrumentation", version = usvmVersion) + implementation(group = usvmRepo, name = "usvm-jvm-instrumentation-collectors", version = usvmVersion) + + approximations("$approximationsRepo:approximations:$approximationsVersion") + + usvmApproximationsApi("$usvmRepo:usvm-jvm-api:$usvmVersion") + usvmInstrumentationCollector("$usvmRepo:usvm-jvm-instrumentation-collectors:$usvmVersion") + usvmInstrumentationRunner("$usvmRepo:usvm-jvm-instrumentation:$usvmVersion") + usvmInstrumentationRunner("$usvmRepo:usvm-jvm-instrumentation-collectors:$usvmVersion") +} + +// TODO replace with runner from usvm (unavailable due to huge jar size) +val usvmInstrumentationRunnerJarTask by tasks.register("usvmInstrumentationRunnerJar") { + archiveBaseName.set("usvm-jvm-instrumentation-runner") + archiveVersion.set("") + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + + manifest { + attributes( + "Main-Class" to "org.usvm.instrumentation.rd.InstrumentedProcessKt", + "Premain-Class" to "org.usvm.instrumentation.agent.Agent", + "Can-Retransform-Classes" to "true", + "Can-Redefine-Classes" to "true" + ) + } + + from(configurations.named("usvmInstrumentationRunner").get().map { + if (it.isDirectory) it else zipTree(it) + }) +} + +tasks.processResources { + listOf( + approximations, + usvmApproximationsApi, + usvmInstrumentationCollector, + usvmInstrumentationRunnerJarTask, + ).forEach { source -> + from(source) { + into("lib") + rename { + it.replace("-$usvmVersion", "") + .replace("-$approximationsVersion", "") + } + } + } +} diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/ConverterUtils.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/ConverterUtils.kt new file mode 100644 index 0000000000..02d2840044 --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/ConverterUtils.kt @@ -0,0 +1,108 @@ +package org.utbot.usvm.converter + +import org.jacodb.analysis.library.analyzers.thisInstance +import org.jacodb.api.JcArrayType +import org.jacodb.api.JcClassOrInterface +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcField +import org.jacodb.api.JcMethod +import org.jacodb.api.JcPrimitiveType +import org.jacodb.api.JcRefType +import org.jacodb.api.JcType +import org.jacodb.api.TypeName +import org.jacodb.api.ext.boolean +import org.jacodb.api.ext.byte +import org.jacodb.api.ext.char +import org.jacodb.api.ext.double +import org.jacodb.api.ext.float +import org.jacodb.api.ext.int +import org.jacodb.api.ext.long +import org.jacodb.api.ext.short +import org.jacodb.api.ext.void +import org.jacodb.api.ext.toType +import org.usvm.instrumentation.testcase.api.UTestInst +import org.usvm.instrumentation.testcase.descriptor.UTestObjectDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestValueDescriptor +import org.usvm.instrumentation.util.getFieldByName +import org.usvm.instrumentation.util.toJavaClass +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.booleanClassId +import org.utbot.framework.plugin.api.util.byteClassId +import org.utbot.framework.plugin.api.util.charClassId +import org.utbot.framework.plugin.api.util.doubleClassId +import org.utbot.framework.plugin.api.util.fieldId +import org.utbot.framework.plugin.api.util.floatClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.intClassId +import org.utbot.framework.plugin.api.util.longClassId +import org.utbot.framework.plugin.api.util.shortClassId +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.framework.plugin.api.util.voidClassId + +fun JcMethod.toExecutableId(classpath: JcClasspath): ExecutableId { + val type = this.thisInstance.type.classId + val parameters = this.parameters.map { it.type.findClassId(classpath) } + + if (isConstructor) { + return ConstructorId(type, parameters) + } + + val returnClassId = this.returnType.findClassId(classpath) + + return MethodId(type, this.name, returnClassId, parameters) +} + +private fun JcType.replaceToBoundIfGeneric(): JcType { + return when (this) { + is JcArrayType -> this.classpath.arrayTypeOf(elementType.replaceToBoundIfGeneric()) + is JcRefType -> this.jcClass.toType() + else -> this + } +} + +val JcType?.classId: ClassId + get() { + if (this !is JcPrimitiveType) { + return runCatching { + this + ?.replaceToBoundIfGeneric() + ?.toJavaClass(utContext.classLoader, initialize = false) + ?.id + ?: error("Can not construct classId for $this") + }.getOrElse { e -> + throw IllegalStateException("JcType.classId failed on ${this?.typeName}", e) + } + } + + val cp = this.classpath + return when (this) { + cp.boolean -> booleanClassId + cp.byte -> byteClassId + cp.short -> shortClassId + cp.int -> intClassId + cp.long -> longClassId + cp.float -> floatClassId + cp.double -> doubleClassId + cp.char -> charClassId + cp.void -> voidClassId + else -> error("$this is not a primitive type") + } + } + +val JcClassOrInterface.classId: ClassId + get() = this.toJavaClass(utContext.classLoader, initialize = false).id + +fun TypeName.findClassId(classpath: JcClasspath): ClassId = + classpath.findTypeOrNull(this.typeName)?.classId + ?: error("Can not construct classId for $this") + +val JcField.fieldId: FieldId + get() = enclosingClass.toType().toJavaClass(utContext.classLoader, initialize = false).getFieldByName(name)?.fieldId + ?: error("Can not construct fieldId for $this") + +val UTestValueDescriptor.origin: UTestInst? + get() = (this as? UTestObjectDescriptor)?.originUTestExpr \ No newline at end of file diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/InstructionIdProvider.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/InstructionIdProvider.kt new file mode 100644 index 0000000000..196728c769 --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/InstructionIdProvider.kt @@ -0,0 +1,12 @@ +package org.utbot.usvm.converter + +fun interface InstructionIdProvider { + fun provideInstructionId(methodSignature: String, instIndex: Int): Long +} + +class SimpleInstructionIdProvider : InstructionIdProvider { + private val instructionIds = mutableMapOf, Long>() + + override fun provideInstructionId(methodSignature: String, instIndex: Int): Long = + instructionIds.getOrPut(methodSignature to instIndex) { instructionIds.size.toLong() } +} \ No newline at end of file diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/JcToUtExecutionConverter.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/JcToUtExecutionConverter.kt new file mode 100644 index 0000000000..2b1392b7e7 --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/JcToUtExecutionConverter.kt @@ -0,0 +1,345 @@ +package org.utbot.usvm.converter + +import mu.KotlinLogging +import org.jacodb.api.JcClassOrInterface +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcTypedMethod +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.ext.jcdbSignature +import org.usvm.instrumentation.testcase.api.UTestExecutionExceptionResult +import org.usvm.instrumentation.testcase.api.UTestExecutionFailedResult +import org.usvm.instrumentation.testcase.api.UTestExecutionInitFailedResult +import org.usvm.instrumentation.testcase.api.UTestExecutionState +import org.usvm.instrumentation.testcase.api.UTestExecutionSuccessResult +import org.usvm.instrumentation.testcase.api.UTestExecutionTimedOutResult +import org.usvm.instrumentation.testcase.api.UTestSetStaticFieldStatement +import org.usvm.instrumentation.testcase.descriptor.Descriptor2ValueConverter +import org.usvm.instrumentation.testcase.descriptor.UTestExceptionDescriptor +import org.usvm.instrumentation.util.enclosingClass +import org.usvm.instrumentation.util.enclosingMethod +import org.utbot.common.isPublic +import org.utbot.framework.fuzzer.IdGenerator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.Instruction +import org.utbot.framework.plugin.api.MissingState +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtExecutionFailure +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtExplicitlyThrownException +import org.utbot.framework.plugin.api.UtImplicitlyThrownException +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtStatementCallModel +import org.utbot.framework.plugin.api.UtTimeoutException +import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper +import org.utbot.framework.plugin.api.util.executableId +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.framework.utils.UtilMethodProvider +import org.utbot.usvm.jc.JcExecution +import org.utbot.usvm.jc.UTestConcreteExecutionResult +import org.utbot.usvm.jc.UTestResultWrapper +import org.utbot.usvm.jc.UTestSymbolicExceptionResult +import org.utbot.usvm.jc.UTestSymbolicSuccessResult +import java.util.IdentityHashMap + +private val logger = KotlinLogging.logger {} + +class JcToUtExecutionConverter( + private val jcExecution: JcExecution, + private val jcClasspath: JcClasspath, + private val idGenerator: IdGenerator, + private val instructionIdProvider: InstructionIdProvider, + private val utilMethodProvider: UtilMethodProvider, +) { + private val toValueConverter = Descriptor2ValueConverter(utContext.classLoader) + + private val instToModelConverter = UTestInstToUtModelConverter(jcExecution.uTest, jcClasspath, idGenerator, utilMethodProvider) + private var jcToUtModelConverter = JcToUtModelConverter(idGenerator, instToModelConverter) + private var uTestProcessResult = instToModelConverter.processUTest() + + fun convert() = jcExecution.uTestExecutionResultWrappers.firstNotNullOfOrNull { result -> + runCatching { convert(result) } + .onFailure { e -> + logger.warn(e) { + "Recoverable: JcToUtExecutionConverter.convert(${jcExecution.method.method}) " + + "failed for ${result::class.java}" + } + } + .getOrNull() + } ?: error("Failed to construct UtExecution for all uTestExecutionResultWrappers on ${jcExecution.method.method}") + + private fun convert(uTestResultWrapper: UTestResultWrapper): UtExecution? { + val coverage = convertCoverage(getTrace(uTestResultWrapper), jcExecution.method.enclosingType.jcClass) + + val utUsvmExecution: UtUsvmExecution = when (uTestResultWrapper) { + is UTestSymbolicExceptionResult -> { + UtUsvmExecution( + stateBefore = constructStateBeforeFromUTest(), + stateAfter = MissingState, + result = createExecutionFailureResult( + exceptionDescriptor = UTestExceptionDescriptor( + type = uTestResultWrapper.exceptionType, + message = "", + stackTrace = emptyList(), + raisedByUserCode = true, + ), + jcTypedMethod = jcExecution.method, + ), + coverage = coverage, + instrumentation = uTestProcessResult.instrumentation, + ) + } + + is UTestSymbolicSuccessResult -> { + uTestResultWrapper.initStatements.forEach { instToModelConverter.processInst(it) } + instToModelConverter.processInst(uTestResultWrapper.result) + + val resultUtModel = instToModelConverter.findModelByInst(uTestResultWrapper.result) + + UtUsvmExecution( + stateBefore = constructStateBeforeFromUTest(), + stateAfter = MissingState, + result = UtExecutionSuccess(resultUtModel), + coverage = coverage, + instrumentation = uTestProcessResult.instrumentation, + ) + } + + is UTestConcreteExecutionResult -> + when (val executionResult = uTestResultWrapper.uTestExecutionResult) { + is UTestExecutionSuccessResult -> UtUsvmExecution( + stateBefore = convertState(executionResult.initialState, EnvironmentStateKind.INITIAL, jcExecution.method), + stateAfter = convertState(executionResult.resultState, EnvironmentStateKind.FINAL, jcExecution.method), + // TODO usvm-sbft: ask why `UTestExecutionSuccessResult.result` is nullable + result = UtExecutionSuccess(executionResult.result?.let { + jcToUtModelConverter.convert(it, EnvironmentStateKind.FINAL) + } ?: UtVoidModel), + coverage = coverage, + instrumentation = uTestProcessResult.instrumentation, + ) + + is UTestExecutionExceptionResult -> { + UtUsvmExecution( + stateBefore = convertState(executionResult.initialState, EnvironmentStateKind.INITIAL, jcExecution.method), + stateAfter = convertState(executionResult.resultState, EnvironmentStateKind.FINAL, jcExecution.method), + result = createExecutionFailureResult(executionResult.cause, jcExecution.method), + coverage = coverage, + instrumentation = uTestProcessResult.instrumentation, + ) + } + + is UTestExecutionInitFailedResult -> { + logger.warn(convertException(executionResult.cause)) { + "Execution failed before method under test call on ${jcExecution.method.method}" + } + null + } + + is UTestExecutionFailedResult -> { + logger.error(convertException(executionResult.cause)) { + "Concrete execution failed on ${jcExecution.method.method}" + } + null + } + + is UTestExecutionTimedOutResult -> { + logger.warn { "Timeout on ${jcExecution.method.method}" } + UtUsvmExecution( + stateBefore = constructStateBeforeFromUTest(), + stateAfter = MissingState, + result = UtTimeoutException(TimeoutException("Concrete execution timed out")), + coverage = coverage, + instrumentation = uTestProcessResult.instrumentation, + ) + } + } + } ?: return null + + return utUsvmExecution + .mapModels(jcToUtModelConverter.utCyclicReferenceModelResolver) + .mapModels(constructAssemblingMapper()) + .mapModels(constructAssembleToCompositeModelMapper()) + .mapModels(constructConstArrayModelMapper()) + } + + private fun constructAssemblingMapper(): UtModelDeepMapper = UtModelDeepMapper { model -> + // TODO usvm-sbft: support constructors with parameters here if it is really required + // Unfortunately, it is not possible to use [AssembleModelGeneral] as it requires soot being initialized. + if (model !is UtAssembleModel + || utilMethodProvider.createInstanceMethodId != model.instantiationCall.statement + || model.modificationsChain.isNotEmpty()) { + return@UtModelDeepMapper model + } + + val instantiatingClassName = (model + .instantiationCall + .params + .single() as UtPrimitiveModel).value.toString() + + val defaultConstructor = ClassId(instantiatingClassName) + .jClass + .constructors + .firstOrNull { it.isPublic && it.parameters.isEmpty() } + + + defaultConstructor?.let { ctor -> + UtAssembleModel( + id = idGenerator.createId(), + classId = model.classId, + modelName = "", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = ctor.executableId, + params = emptyList(), + ) + ) + } ?: model + } + + private fun constructConstArrayModelMapper(): UtModelDeepMapper = UtModelDeepMapper { model -> + if (model is UtArrayModel) { + val storeGroups = model.stores.entries.groupByTo(IdentityHashMap()) { it.value } + val mostCommonStore = storeGroups.maxByOrNull { it.value.size } ?: return@UtModelDeepMapper model + if (mostCommonStore.value.size > 1) { + model.constModel = mostCommonStore.key + mostCommonStore.value.forEach { (index, _) -> model.stores.remove(index) } + } + } + model + } + + private fun constructAssembleToCompositeModelMapper(): UtModelDeepMapper = UtModelDeepMapper { model -> + if (model is UtAssembleModel + && utilMethodProvider.createInstanceMethodId == model.instantiationCall.statement + && model.modificationsChain.all { + utilMethodProvider.setFieldMethodId == (it as? UtStatementCallModel)?.statement + } + ) { + UtCompositeModel( + id = model.id, + classId = model.classId, + isMock = false, + fields = model.modificationsChain.associateTo(mutableMapOf()) { + // `setFieldMethodId` call example for reference: + // setField(outputStream, "java.io.ByteArrayOutputStream", "buf", buf); + + val params = (it as UtStatementCallModel).params + val fieldId = FieldId( + declaringClass = ClassId((params[1] as UtPrimitiveModel).value as String), + name = ((params[2] as UtPrimitiveModel).value as String) + ) + // We prefer `model.origin?.fields?.get(fieldId)` over `params[3]`, because + // - `model.origin?.fields?.get(fieldId)` is created from concrete execution initial state + // - `params[3]` is created from jcMachine output, which could be a bit off + fieldId to (model.origin?.fields?.get(fieldId) ?: params[3]) + } + ) + } else { + model + } + } + + private fun convertException(exceptionDescriptor: UTestExceptionDescriptor): Throwable = + toValueConverter.buildObjectFromDescriptor(exceptionDescriptor.dropStaticFields( + cache = mutableMapOf() + )) as Throwable + + /** + * Gets trace from execution result if it is present. + * + * Otherwise, (e.g. we use symbolic result if concrete fails), + * minimization will take just 'UtSettings.maxUnknownCoverageExecutionsPerMethodPerResultType' executions. + */ + private fun getTrace(executionResult: UTestResultWrapper): List? = when (executionResult) { + is UTestConcreteExecutionResult -> when (val res = executionResult.uTestExecutionResult) { + is UTestExecutionExceptionResult -> res.trace + is UTestExecutionInitFailedResult -> res.trace + is UTestExecutionSuccessResult -> res.trace + is UTestExecutionFailedResult -> emptyList() + is UTestExecutionTimedOutResult -> emptyList() + } + is UTestSymbolicExceptionResult -> emptyList() + is UTestSymbolicSuccessResult -> emptyList() + } + + private fun convertState( + state: UTestExecutionState, + stateKind: EnvironmentStateKind, + method: JcTypedMethod, + ): EnvironmentModels { + val thisInstance = + if (method.isStatic) null + else if (method.method.isConstructor) null + else jcToUtModelConverter.convert(state.instanceDescriptor ?: error("Unexpected null instanceDescriptor"), stateKind) + val parameters = state.argsDescriptors.map { + jcToUtModelConverter.convert(it ?: error("Unexpected null argDescriptor"), stateKind) + } + val statics = state.statics + .entries + .associate { (jcField, uTestDescr) -> + jcField.fieldId to jcToUtModelConverter.convert(uTestDescr, stateKind) + } + val executableId: ExecutableId = method.method.toExecutableId(jcClasspath) + return EnvironmentModels(thisInstance, parameters, statics, executableId) + } + + private fun constructStateBeforeFromUTest(): EnvironmentModels { + val uTest = jcExecution.uTest + val method = jcExecution.method + val thisInstance = + if (method.isStatic) null + else if (method.method.isConstructor) null + else instToModelConverter.findModelByInst(uTest.callMethodExpression.instance ?: error("Unexpected null instance expression")) + val parameters = uTest.callMethodExpression.args.map { + instToModelConverter.findModelByInst(it) + } + val statics = uTest.initStatements.filterIsInstance() + .associate { + it.field.fieldId to instToModelConverter.findModelByInst(it.value) + } + val executableId: ExecutableId = method.method.toExecutableId(jcClasspath) + return EnvironmentModels(thisInstance, parameters, statics, executableId) + } + + private fun createExecutionFailureResult( + exceptionDescriptor: UTestExceptionDescriptor, + jcTypedMethod: JcTypedMethod, + ): UtExecutionFailure { + val exception = convertException(exceptionDescriptor) + val fromNestedMethod = exception.stackTrace.firstOrNull()?.let { stackTraceElement -> + stackTraceElement.className != jcTypedMethod.enclosingType.jcClass.name || + stackTraceElement.methodName != jcTypedMethod.name + } ?: false + return if (exceptionDescriptor.raisedByUserCode) { + UtExplicitlyThrownException(exception, fromNestedMethod) + } else { + UtImplicitlyThrownException(exception, fromNestedMethod) + } + } + + private fun convertCoverage(jcCoverage: List?, jcClass: JcClassOrInterface) = Coverage( + coveredInstructions = jcCoverage.orEmpty().map { + val methodSignature = it.enclosingMethod.jcdbSignature + Instruction( + internalName = it.enclosingClass.name.replace('.', '/'), + methodSignature = methodSignature, + lineNumber = it.lineNumber, + id = instructionIdProvider.provideInstructionId(methodSignature, it.location.index) + ) + }, + // TODO usvm-sbft: maybe add cache here + // TODO usvm-sbft: make sure static initializers are included into instructions count + // I assume they are counted as part of `` method + instructionsCount = jcClass.declaredMethods.sumOf { it.instList.size.toLong() } + ) +} \ No newline at end of file diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/JcToUtModelConverter.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/JcToUtModelConverter.kt new file mode 100644 index 0000000000..5719d1c5d8 --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/JcToUtModelConverter.kt @@ -0,0 +1,213 @@ +package org.utbot.usvm.converter + +import org.jacodb.api.JcClasspath +import org.usvm.instrumentation.testcase.api.UTestExpression +import org.usvm.instrumentation.testcase.api.UTestMock +import org.usvm.instrumentation.testcase.descriptor.UTestArrayDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestClassDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestConstantDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestCyclicReferenceDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestEnumValueDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestExceptionDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestObjectDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestRefDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestValueDescriptor +import org.usvm.instrumentation.util.InstrumentationModuleConstants.nameForExistingButNullString +import org.utbot.framework.fuzzer.IdGenerator +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtClassRefModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtCustomModel +import org.utbot.framework.plugin.api.UtEnumConstantModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper +import org.utbot.framework.plugin.api.mapper.UtModelMapper +import org.utbot.framework.plugin.api.util.classClassId +import org.utbot.framework.plugin.api.util.id +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.stringClassId + +enum class EnvironmentStateKind { + INITIAL, FINAL +} + +data class UtCyclicReferenceModel( + override val id: Int?, + override val classId: ClassId, + val refId: Int, + val stateKind: EnvironmentStateKind, +) : UtCustomModel(id, classId) { + override fun shallowMap(mapper: UtModelMapper): UtCustomModel = this +} + +class JcToUtModelConverter( + private val idGenerator: IdGenerator, + private val instToUtModelConverter: UTestInstToUtModelConverter, +) { + private val descriptorToModelCache = + mutableMapOf, UtModel>() + private val refIdAndStateKindToDescriptorCache = + mutableMapOf, UTestValueDescriptor>() + val utCyclicReferenceModelResolver = UtModelDeepMapper { model -> + when (model) { + is UtCyclicReferenceModel -> getModelByRefIdAndStateKind(model.refId, model.stateKind) + ?: error("Invalid UTestCyclicReferenceDescriptor: $model") + else -> model + } + } + + fun convert( + valueDescriptor: UTestValueDescriptor, + stateKind: EnvironmentStateKind, + ): UtModel = descriptorToModelCache.getOrPut(valueDescriptor to stateKind) { + if (stateKind == EnvironmentStateKind.INITIAL || valueDescriptor.origin is UTestMock) + valueDescriptor.origin?.let { originExpr -> + val model = instToUtModelConverter.findModelByInst(originExpr as UTestExpression) + if (model is UtAssembleModel && model.origin == null) { + val compositeOrigin = convertIgnoringOriginExprForThisModel( + valueDescriptor = valueDescriptor, + stateKind = stateKind, + curModelId = model.id ?: idGenerator.createId(), + ) + if (compositeOrigin is UtCompositeModel) + return@getOrPut model.copy(origin = compositeOrigin) + } + return@getOrPut model + } + + val previousStateModel = + if (stateKind == EnvironmentStateKind.FINAL && valueDescriptor is UTestRefDescriptor) { + (getModelByRefIdAndStateKind(valueDescriptor.refId, EnvironmentStateKind.INITIAL) as? UtReferenceModel) + } else { + null + } + + return@getOrPut convertIgnoringOriginExprForThisModel( + valueDescriptor, + stateKind, + curModelId = previousStateModel?.id ?: idGenerator.createId() + ) + } + + private fun convertIgnoringOriginExprForThisModel( + valueDescriptor: UTestValueDescriptor, + stateKind: EnvironmentStateKind, + curModelId: Int, + ): UtModel = descriptorToModelCache.getOrPut(valueDescriptor to stateKind) { + if (valueDescriptor is UTestRefDescriptor) { + refIdAndStateKindToDescriptorCache[valueDescriptor.refId to stateKind] = valueDescriptor + } + + return when (valueDescriptor) { + is UTestObjectDescriptor -> { + val fields = mutableMapOf() + + val model = UtCompositeModel( + id = curModelId, + classId = valueDescriptor.type.classId, + isMock = false, + mocks = mutableMapOf(), + fields = fields, + ) + + descriptorToModelCache[valueDescriptor to stateKind] = model + + fields += valueDescriptor.fields + .entries + .filter { (jcField, _) -> !jcField.isStatic } + .associate { (jcField, fieldDescr) -> + val fieldId = FieldId(jcField.enclosingClass.classId, jcField.name) + val fieldModel = convert(fieldDescr, stateKind) + fieldId to fieldModel + } + + model + } + + is UTestArrayDescriptor -> { + val stores = mutableMapOf() + + val model = UtArrayModel( + id = curModelId, + classId = valueDescriptor.type.classId, + length = valueDescriptor.length, + constModel = UtNullModel(valueDescriptor.elementType.classId), + stores = stores, + ) + + descriptorToModelCache[valueDescriptor to stateKind] = model + + valueDescriptor.value + .map { elemDescr -> convert(elemDescr, stateKind) } + .forEachIndexed { index, elemModel -> stores += index to elemModel } + + model + } + + is UTestClassDescriptor -> UtClassRefModel( + id = curModelId, + classId = classClassId, + value = valueDescriptor.classType.classId, + ) + + is UTestConstantDescriptor.Null -> UtNullModel(valueDescriptor.type.classId) + + is UTestConstantDescriptor.Boolean -> UtPrimitiveModel(valueDescriptor.value) + is UTestConstantDescriptor.Byte -> UtPrimitiveModel(valueDescriptor.value) + is UTestConstantDescriptor.Char -> UtPrimitiveModel(valueDescriptor.value) + is UTestConstantDescriptor.Double -> UtPrimitiveModel(valueDescriptor.value) + is UTestConstantDescriptor.Float -> UtPrimitiveModel(valueDescriptor.value) + is UTestConstantDescriptor.Int -> UtPrimitiveModel(valueDescriptor.value) + is UTestConstantDescriptor.Long -> UtPrimitiveModel(valueDescriptor.value) + is UTestConstantDescriptor.Short -> UtPrimitiveModel(valueDescriptor.value) + is UTestConstantDescriptor.String -> constructString(valueDescriptor.value) + + is UTestCyclicReferenceDescriptor -> getModelByRefIdAndStateKind(valueDescriptor.refId, stateKind) + ?: UtCyclicReferenceModel( + id = curModelId, + classId = valueDescriptor.type.classId, + refId = valueDescriptor.refId, + stateKind = stateKind + ) + + is UTestEnumValueDescriptor -> UtEnumConstantModel( + id = curModelId, + classId = valueDescriptor.type.classId, + value = valueDescriptor.type.classId.jClass.enumConstants.find { + // [valueDescriptor.enumValueName] is the enum value to which toString() was applied + (it as Enum<*>).toString() == valueDescriptor.enumValueName + } as Enum<*> + ) + is UTestExceptionDescriptor -> UtCompositeModel( + id = curModelId, + classId = valueDescriptor.type.classId, + isMock = false, + fields = mutableMapOf( + // TODO usvm-sbft: ask why `UTestExceptionDescriptor.message` is not nullable, support it here + FieldId(Throwable::class.java.id, "detailMessage") to UtPrimitiveModel(valueDescriptor.message) + ) + ) + } + } + + private fun constructString(valueDescriptorValue: String): UtModel { + if(valueDescriptorValue == nameForExistingButNullString){ + return UtNullModel(stringClassId) + } + return UtPrimitiveModel(valueDescriptorValue) + } + + private fun getModelByRefIdAndStateKind( + refId: Int, + stateKind: EnvironmentStateKind + ): UtModel? = + refIdAndStateKindToDescriptorCache[refId to stateKind]?.let { + descriptorToModelCache[it to stateKind] + } +} \ No newline at end of file diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UTestInstToUtModelConverter.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UTestInstToUtModelConverter.kt new file mode 100644 index 0000000000..54df355e48 --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UTestInstToUtModelConverter.kt @@ -0,0 +1,381 @@ +package org.utbot.usvm.converter + +import mu.KotlinLogging +import org.jacodb.api.JcClasspath +import org.usvm.instrumentation.testcase.UTest +import org.usvm.instrumentation.testcase.api.UTestAllocateMemoryCall +import org.usvm.instrumentation.testcase.api.UTestArithmeticExpression +import org.usvm.instrumentation.testcase.api.UTestArrayGetExpression +import org.usvm.instrumentation.testcase.api.UTestArrayLengthExpression +import org.usvm.instrumentation.testcase.api.UTestArraySetStatement +import org.usvm.instrumentation.testcase.api.UTestBinaryConditionExpression +import org.usvm.instrumentation.testcase.api.UTestBinaryConditionStatement +import org.usvm.instrumentation.testcase.api.UTestBooleanExpression +import org.usvm.instrumentation.testcase.api.UTestByteExpression +import org.usvm.instrumentation.testcase.api.UTestCastExpression +import org.usvm.instrumentation.testcase.api.UTestCharExpression +import org.usvm.instrumentation.testcase.api.UTestClassExpression +import org.usvm.instrumentation.testcase.api.UTestConstructorCall +import org.usvm.instrumentation.testcase.api.UTestCreateArrayExpression +import org.usvm.instrumentation.testcase.api.UTestDoubleExpression +import org.usvm.instrumentation.testcase.api.UTestExpression +import org.usvm.instrumentation.testcase.api.UTestFloatExpression +import org.usvm.instrumentation.testcase.api.UTestGetFieldExpression +import org.usvm.instrumentation.testcase.api.UTestGetStaticFieldExpression +import org.usvm.instrumentation.testcase.api.UTestGlobalMock +import org.usvm.instrumentation.testcase.api.UTestInst +import org.usvm.instrumentation.testcase.api.UTestIntExpression +import org.usvm.instrumentation.testcase.api.UTestLongExpression +import org.usvm.instrumentation.testcase.api.UTestMethodCall +import org.usvm.instrumentation.testcase.api.UTestMockObject +import org.usvm.instrumentation.testcase.api.UTestNullExpression +import org.usvm.instrumentation.testcase.api.UTestSetFieldStatement +import org.usvm.instrumentation.testcase.api.UTestSetStaticFieldStatement +import org.usvm.instrumentation.testcase.api.UTestShortExpression +import org.usvm.instrumentation.testcase.api.UTestStaticMethodCall +import org.usvm.instrumentation.testcase.api.UTestStringExpression +import org.utbot.framework.fuzzer.IdGenerator +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.UtArrayModel +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtClassRefModel +import org.utbot.framework.plugin.api.UtCompositeModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtInstrumentation +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation +import org.utbot.framework.plugin.api.UtNullModel +import org.utbot.framework.plugin.api.UtPrimitiveModel +import org.utbot.framework.plugin.api.UtReferenceModel +import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation +import org.utbot.framework.plugin.api.util.classClassId +import org.utbot.framework.plugin.api.util.objectClassId +import org.utbot.framework.plugin.api.util.voidClassId +import org.utbot.framework.utils.UtilMethodProvider + +private val logger = KotlinLogging.logger {} + +class UTestInstToUtModelConverter( + private val uTest: UTest, + private val jcClasspath: JcClasspath, + private val idGenerator: IdGenerator, + private val utilMethodProvider: UtilMethodProvider, +) { + private val exprToModelCache = mutableMapOf() + private val instrumentations = mutableListOf() + + fun processUTest(): UTestAnalysisResult { + uTest.initStatements.forEach { uInst -> processInst(uInst) } + removeInstantiationCallFromThisInstanceModificationChain(processExpr(uTest.callMethodExpression)) + + return UTestAnalysisResult(instrumentations) + } + + fun findModelByInst(expr: UTestExpression): UtModel { + val alreadyCreatedModel = exprToModelCache.getValue(expr) + removeInstantiationCallFromThisInstanceModificationChain(alreadyCreatedModel) + return alreadyCreatedModel + } + + private fun removeInstantiationCallFromThisInstanceModificationChain(model: UtModel) { + if (model is UtAssembleModel) { + val instantiationCall = model.instantiationCall + if (instantiationCall is UtExecutableCallModel) { + val instanceModel = instantiationCall.instance as? UtAssembleModel + instanceModel?.let { + (it.modificationsChain as MutableList).remove(instantiationCall) + } + } + } + } + + fun processInst(uTestInst: UTestInst) { + when (uTestInst) { + is UTestExpression -> processExpr(uTestInst) + + is UTestArraySetStatement -> { + val arrayModel = processExpr(uTestInst.arrayInstance) + require(arrayModel is UtArrayModel) + + val storeIndex = uTestInst.index as UTestIntExpression + val elementModel = processExpr(uTestInst.setValueExpression) + + arrayModel.stores[storeIndex.value] = elementModel + } + + is UTestSetFieldStatement -> { + val instanceExpr = uTestInst.instance + + when (val instanceModel = processExpr(instanceExpr)) { + is UtAssembleModel -> { + val fieldType = uTestInst.field.enclosingClass.classId + val fieldName = uTestInst.field.name + val setValueModel = processExpr(uTestInst.value) + + val methodCall = UtExecutableCallModel( + instance = null, + executable = utilMethodProvider.setFieldMethodId, + params = listOf( + instanceModel, + UtPrimitiveModel(fieldType.name), + UtPrimitiveModel(fieldName), + setValueModel, + ), + ) + + (instanceModel.modificationsChain as MutableList).add(methodCall) + } + is UtCompositeModel -> { + instanceModel.fields[uTestInst.field.fieldId] = processExpr(uTestInst.value) + } + else -> logger.warn { + "Field ${uTestInst.field} can't be set for a model of type ${instanceModel::class}, " + + "when generating tests for ${uTest.callMethodExpression.method}" + } + } + } + + is UTestSetStaticFieldStatement -> processExpr(uTestInst.value) + + is UTestBinaryConditionStatement -> error("This expression type is not supported") + } + } + + private fun processExpr(uTestExpr: UTestExpression): UtModel = exprToModelCache.getOrPut(uTestExpr) { + when (uTestExpr) { + is UTestAllocateMemoryCall -> { + val createInstanceCall = UtExecutableCallModel( + instance = null, + executable = utilMethodProvider.createInstanceMethodId, + params = listOf(UtPrimitiveModel(uTestExpr.clazz.classId.name)), + ) + + UtAssembleModel( + id = idGenerator.createId(), + classId = uTestExpr.clazz.classId, + modelName = "", + instantiationCall = createInstanceCall, + ) + } + + is UTestConstructorCall -> { + val constructorCall = UtExecutableCallModel( + instance = null, + executable = uTestExpr.method.toExecutableId(jcClasspath), + params = uTestExpr.args.map { arg -> + processExpr(arg) + }, + ) + + UtAssembleModel( + id = idGenerator.createId(), + classId = uTestExpr.type.classId, + modelName = "", + instantiationCall = constructorCall, + ) + } + + is UTestMethodCall -> { + val instanceModel = processExpr(uTestExpr.instance) as UtReferenceModel + + val methodCall = UtExecutableCallModel( + instance = instanceModel, + executable = uTestExpr.method.toExecutableId(jcClasspath), + params = uTestExpr.args.map { arg -> processExpr(arg) }, + ) + + (instanceModel as? UtAssembleModel)?.let { + (it.modificationsChain as MutableList).add(methodCall) + } ?: logger.warn { + "Call ${uTestExpr.method} can't be added to a non-assemble model of type ${instanceModel::class}, " + + "when generating tests for ${uTest.callMethodExpression.method}" + } + + UtAssembleModel( + id = idGenerator.createId(), + classId = uTestExpr.type.classId, + modelName = "", + instantiationCall = methodCall, + ) + } + + is UTestStaticMethodCall -> { + UtAssembleModel( + id = idGenerator.createId(), + classId = uTestExpr.type.classId, + modelName = "", + instantiationCall = UtExecutableCallModel( + instance = null, + executable = uTestExpr.method.toExecutableId(jcClasspath), + params = uTestExpr.args.map { arg -> processExpr(arg) }, + ), + ) + } + + is UTestClassExpression -> UtClassRefModel( + id = idGenerator.createId(), + classId = classClassId, + value = uTestExpr.type.classId, + ) + + + is UTestBooleanExpression -> UtPrimitiveModel(uTestExpr.value) + is UTestByteExpression -> UtPrimitiveModel(uTestExpr.value) + is UTestCharExpression -> UtPrimitiveModel(uTestExpr.value) + is UTestDoubleExpression -> UtPrimitiveModel(uTestExpr.value) + is UTestFloatExpression -> UtPrimitiveModel(uTestExpr.value) + is UTestIntExpression -> UtPrimitiveModel(uTestExpr.value) + is UTestLongExpression -> UtPrimitiveModel(uTestExpr.value) + is UTestShortExpression -> UtPrimitiveModel(uTestExpr.value) + is UTestStringExpression -> UtPrimitiveModel(uTestExpr.value) + + is UTestNullExpression -> UtNullModel(uTestExpr.type.classId) + + is UTestCreateArrayExpression -> { + require(uTestExpr.size is UTestIntExpression) + val arrayLength = uTestExpr.size as UTestIntExpression + + UtArrayModel( + id = idGenerator.createId(), + classId = uTestExpr.type.classId, + length = arrayLength.value, + constModel = UtNullModel(objectClassId), + stores = mutableMapOf(), + ) + } + + is UTestGetFieldExpression -> { + val instanceModel = processExpr(uTestExpr.instance) + + val getFieldCall = UtExecutableCallModel( + instance = null, + executable = utilMethodProvider.getFieldValueMethodId, + params = listOf( + instanceModel, + UtPrimitiveModel(uTestExpr.field.enclosingClass.classId.name), + UtPrimitiveModel(uTestExpr.field.name), + ), + ) + + UtAssembleModel( + id = idGenerator.createId(), + classId = uTestExpr.type.classId, + modelName = "", + instantiationCall = getFieldCall, + ) + } + + is UTestGetStaticFieldExpression -> { + val getStaticFieldCall = UtExecutableCallModel( + instance = null, + executable = utilMethodProvider.getStaticFieldValueMethodId, + params = listOf( + UtPrimitiveModel(uTestExpr.field.enclosingClass.classId.name), + UtPrimitiveModel(uTestExpr.field.name), + ), + ) + + UtAssembleModel( + id = idGenerator.createId(), + classId = uTestExpr.type.classId, + modelName = "", + instantiationCall = getStaticFieldCall, + ) + } + + is UTestMockObject -> { + val fields = mutableMapOf() + val mocks = mutableMapOf>() + + val newModel = UtCompositeModel( + id = idGenerator.createId(), + classId = uTestExpr.type.classId, + isMock = true, + fields = fields, + mocks = mocks, + ) + exprToModelCache[uTestExpr] = newModel + + fields += uTestExpr.fields + .entries + .associate { (jcField, uTestExpr) -> + jcField.fieldId to processExpr(uTestExpr) + } + + mocks += uTestExpr.methods + .entries + .associate { (jcMethod, uTestExprs) -> + val executableId: ExecutableId = jcMethod.toExecutableId(jcClasspath) + val models = uTestExprs.map { expr -> + processExpr(expr) + } + + executableId to models + } + + newModel + } + + is UTestGlobalMock -> { + val methodsToExprs = uTestExpr.methods.entries + val initMethodExprs = methodsToExprs.filter { it.key.isConstructor } + val otherMethodsExprs = methodsToExprs.minus(initMethodExprs.toSet()) + + otherMethodsExprs + .forEach { (jcMethod, uTestExprs) -> + val methodId = jcMethod.toExecutableId(jcClasspath) as MethodId + val valueModels = uTestExprs.map { expr -> processExpr(expr) } + val methodInstrumentation = UtStaticMethodInstrumentation( + methodId = methodId, + values = valueModels, + ) + + instrumentations += methodInstrumentation + } + + initMethodExprs + .forEach { (jcMethod, uTestExprs) -> + // TODO usvm-sbft: it can be .map { expr -> processExpr(expr) } here + // However, there's no special treatment for cases when method occurs in a global mock + val valueModels = uTestExprs.map { _ -> UtCompositeModel( + id=idGenerator.createId(), + classId = voidClassId, + isMock = true, + ) + } + val methodInstrumentation = UtNewInstanceInstrumentation( + classId = jcMethod.enclosingClass.classId, + instances = valueModels, + // [UTestGlobalMock] does not have an equivalent of [callSites], + // but it is used only in UtBot instrumentation. We use USVM one, so it is not a problem. + callSites = emptySet(), + ) + + instrumentations += methodInstrumentation + } + + // UtClassRefModel is returned here for consistency with [Descriptor2ValueConverter] + // which returns Class<*> instance for [UTestGlobalMock] descriptors. + UtClassRefModel( + id = idGenerator.createId(), + classId = classClassId, + value = uTestExpr.type.classId + ) + } + + is UTestArithmeticExpression -> error("This expression type is not supported") + is UTestBinaryConditionExpression -> error("This expression type is not supported") + + is UTestCastExpression -> error("This expression type is not supported") + + is UTestArrayGetExpression -> error("This expression type is not supported") + is UTestArrayLengthExpression -> error("This expression type is not supported") + } + } +} + +data class UTestAnalysisResult( + val instrumentation: List, +) \ No newline at end of file diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UTestValueDescriptorUtils.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UTestValueDescriptorUtils.kt new file mode 100644 index 0000000000..cf969c7fba --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UTestValueDescriptorUtils.kt @@ -0,0 +1,47 @@ +package org.utbot.usvm.converter + +import org.usvm.instrumentation.testcase.descriptor.UTestArrayDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestClassDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestConstantDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestCyclicReferenceDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestEnumValueDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestExceptionDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestObjectDescriptor +import org.usvm.instrumentation.testcase.descriptor.UTestValueDescriptor + +fun UTestValueDescriptor.dropStaticFields( + cache: MutableMap +): UTestValueDescriptor = cache.getOrPut(this) { + when (this) { + is UTestArrayDescriptor -> UTestArrayDescriptor( + elementType = elementType, + length = length, + value = value.map { it.dropStaticFields(cache) }, + refId = refId + ) + + is UTestClassDescriptor -> this + is UTestConstantDescriptor -> this + is UTestCyclicReferenceDescriptor -> this + is UTestEnumValueDescriptor -> UTestEnumValueDescriptor( + type = type, + enumValueName = enumValueName, + fields = emptyMap(), + refId = refId + ) + + is UTestExceptionDescriptor -> UTestExceptionDescriptor( + type = type, + message = message, + stackTrace = stackTrace.map { it.dropStaticFields(cache) }, + raisedByUserCode = raisedByUserCode + ) + + is UTestObjectDescriptor -> UTestObjectDescriptor( + type = type, + fields = fields.entries.filter { !it.key.isStatic }.associate { it.key to it.value.dropStaticFields(cache) }, + originUTestExpr = originUTestExpr, + refId = refId + ) + } +} \ No newline at end of file diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UtUsvmExecution.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UtUsvmExecution.kt new file mode 100644 index 0000000000..4d8513f5fa --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UtUsvmExecution.kt @@ -0,0 +1,81 @@ +package org.utbot.usvm.converter + +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.DocStatement +import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtExecutionResult +import org.utbot.framework.plugin.api.UtExecutionWithInstrumentation +import org.utbot.framework.plugin.api.UtInstrumentation +import org.utbot.framework.plugin.api.mapper.UtModelMapper +import org.utbot.framework.plugin.api.mapper.mapModelIfExists +import org.utbot.framework.plugin.api.mapper.mapModels + +class UtUsvmExecution( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage?, + summary: List? = emptyList(), + testMethodName: String? = null, + displayName: String? = null, + override val instrumentation: List +) : UtExecution( + stateBefore, + stateAfter, + result, + coverage, + summary, + testMethodName, + displayName +), UtExecutionWithInstrumentation { + override fun copy( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage?, + summary: List?, + testMethodName: String?, + displayName: String? + ): UtExecution = UtUsvmExecution( + stateBefore, + stateAfter, + result, + coverage, + summary, + testMethodName, + displayName, + instrumentation, + ) + + fun copy( + stateBefore: EnvironmentModels = this.stateBefore, + stateAfter: EnvironmentModels = this.stateAfter, + result: UtExecutionResult = this.result, + coverage: Coverage? = this.coverage, + summary: List? = this.summary, + testMethodName: String? = this.testMethodName, + displayName: String? = this.displayName, + instrumentation: List = this.instrumentation, + ) = UtUsvmExecution( + stateBefore, + stateAfter, + result, + coverage, + summary, + testMethodName, + displayName, + instrumentation, + ) +} + +fun UtUsvmExecution.mapModels(mapper: UtModelMapper) = copy( + stateBefore = stateBefore.mapModels(mapper), + stateAfter = stateAfter.mapModels(mapper), + result = result.mapModelIfExists(mapper), + coverage = this.coverage, + summary = this.summary, + testMethodName = this.testMethodName, + displayName = this.displayName, + instrumentation = instrumentation.map { it.mapModels(mapper) }, +) \ No newline at end of file diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcContainer.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcContainer.kt new file mode 100644 index 0000000000..0cf23878ee --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcContainer.kt @@ -0,0 +1,104 @@ +package org.utbot.usvm.jc + +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcDatabase +import org.jacodb.impl.JcSettings +import org.jacodb.impl.features.classpaths.UnknownClasses +import org.jacodb.impl.jacodb +import org.usvm.instrumentation.executor.UTestConcreteExecutor +import org.usvm.instrumentation.instrumentation.JcRuntimeTraceInstrumenterFactory +import org.usvm.util.classpathWithApproximations +import java.io.File +import kotlin.time.Duration.Companion.seconds + +private val logger = KotlinLogging.logger {} + +// TODO usvm-sbft-refactoring: copied from `usvm/usvm-jvm/test`, extract this class back to USVM project +class JcContainer private constructor( + usePersistence: Boolean, + persistenceDir: File, + classpath: List, + javaHome: File, + builder: JcSettings.() -> Unit, +) : AutoCloseable { + val db: JcDatabase + val cp: JcClasspath + val runner: UTestConcreteExecutor + + init { + val cpPath = classpath.map { it.absolutePath }.sorted() + + /** + * Persist jacodb cp to achieve: + * 1. Faster concrete executor initialization + * 2. Faster analysis for classes from the same cp + * */ + val persistenceLocation = if (usePersistence) { + persistenceDir.resolve("jcdb-persistence-${cpPath.hashCode()}").absolutePath + } else { + null + } + + val (db, cp) = runBlocking { + val db = jacodb { + useJavaRuntime(javaHome) + + builder() + + if (persistenceLocation != null) { + persistent(location = persistenceLocation, clearOnStart = false) + } + } + val cp = db.classpathWithApproximations(classpath, listOf(UnknownClasses)) + db to cp + } + this.db = db + this.cp = cp + this.runner = UTestConcreteExecutor( + JcRuntimeTraceInstrumenterFactory::class, + cpPath, + cp, + javaHome.absolutePath, + persistenceLocation, + TEST_EXECUTION_TIMEOUT + ) + runBlocking { + db.awaitBackgroundJobs() + } + } + + override fun close() { + cp.close() + db.close() + runner.close() + } + + companion object : AutoCloseable { + val TEST_EXECUTION_TIMEOUT = 1.seconds + + private val cache = HashMap, JcContainer>() + + operator fun invoke( + usePersistence: Boolean, + persistenceDir: File, + classpath: List, + javaHome: File, + builder: JcSettings.() -> Unit, + ): JcContainer { + return cache[classpath] ?: run { + // TODO usvm-sbft: right now max cache size is 1, do we need to increase it? + logger.info { "JcContainer cache miss" } + close() + JcContainer(usePersistence, persistenceDir, classpath, javaHome, builder) + .also { cache[classpath] = it } + } + } + + override fun close() { + cache.values.forEach { it.close() } + cache.clear() + } + } +} diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcExecution.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcExecution.kt new file mode 100644 index 0000000000..b8b396ca77 --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcExecution.kt @@ -0,0 +1,36 @@ +package org.utbot.usvm.jc + +import org.jacodb.api.JcType +import org.jacodb.api.JcTypedMethod +import org.usvm.api.JcCoverage +import org.usvm.instrumentation.testcase.UTest +import org.usvm.instrumentation.testcase.api.UTestExecutionResult +import org.usvm.instrumentation.testcase.api.UTestExpression +import org.usvm.instrumentation.testcase.api.UTestInst + +/** + * [uTestExecutionResultWrappers] may contain either: + * - both concrete and symbolic results in priority decreasing order + * - single symbolic or concrete result if we discarded one of the result kinds + */ +data class JcExecution( + val method: JcTypedMethod, + val uTest: UTest, + val uTestExecutionResultWrappers: Sequence, + val coverage: JcCoverage +) + +sealed interface UTestResultWrapper + +class UTestConcreteExecutionResult( + val uTestExecutionResult: UTestExecutionResult +) : UTestResultWrapper + +class UTestSymbolicExceptionResult( + val exceptionType: JcType +) : UTestResultWrapper + +class UTestSymbolicSuccessResult( + val initStatements: List, + val result: UTestExpression +) : UTestResultWrapper diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcJars.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcJars.kt new file mode 100644 index 0000000000..e69ab3eafe --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcJars.kt @@ -0,0 +1,17 @@ +package org.utbot.usvm.jc + +import org.utbot.common.JarUtils + +object JcJars { + val approximationsJar by lazy { extractUsvmJar("approximations.jar") } + val approximationsApiJar by lazy { extractUsvmJar("usvm-jvm-api.jar") } + val collectorsJar by lazy { extractUsvmJar("usvm-jvm-instrumentation-collectors.jar") } + val runnerJar by lazy { extractUsvmJar("usvm-jvm-instrumentation-runner.jar") } + + private fun extractUsvmJar(jarFileName: String) = JarUtils.extractJarFileFromResources( + jarFileName = jarFileName, + jarResourcePath = "lib/$jarFileName", + targetDirectoryName = "usvm" + ) +} + diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcTestExecutor.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcTestExecutor.kt new file mode 100644 index 0000000000..8ff326c6dd --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcTestExecutor.kt @@ -0,0 +1,239 @@ +package org.utbot.usvm.jc + +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +import org.jacodb.api.JcClassType +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcField +import org.jacodb.api.JcMethod +import org.jacodb.api.JcType +import org.jacodb.api.JcTypedMethod +import org.jacodb.api.ext.constructors +import org.jacodb.api.ext.findTypeOrNull +import org.jacodb.api.ext.objectType +import org.jacodb.api.ext.toType +import org.jacodb.approximation.JcEnrichedVirtualField +import org.usvm.UConcreteHeapRef +import org.usvm.UExpr +import org.usvm.UHeapRef +import org.usvm.UIndexedMethodReturnValue +import org.usvm.UMockSymbol +import org.usvm.api.JcCoverage +import org.usvm.api.JcTest +import org.usvm.api.util.JcTestStateResolver +import org.usvm.collection.field.UFieldLValue +import org.usvm.instrumentation.executor.UTestConcreteExecutor +import org.usvm.instrumentation.testcase.UTest +import org.usvm.instrumentation.testcase.api.UTestAllocateMemoryCall +import org.usvm.instrumentation.testcase.api.UTestExecutionExceptionResult +import org.usvm.instrumentation.testcase.api.UTestExecutionFailedResult +import org.usvm.instrumentation.testcase.api.UTestExecutionSuccessResult +import org.usvm.instrumentation.testcase.api.UTestExpression +import org.usvm.instrumentation.testcase.api.UTestMethodCall +import org.usvm.instrumentation.testcase.api.UTestMockObject +import org.usvm.instrumentation.testcase.api.UTestNullExpression +import org.usvm.instrumentation.testcase.api.UTestStaticMethodCall +import org.usvm.machine.JcContext +import org.usvm.machine.JcMocker +import org.usvm.machine.state.JcMethodResult +import org.usvm.machine.state.JcState +import org.usvm.machine.state.localIdx +import org.usvm.memory.ULValue +import org.usvm.memory.UReadOnlyMemory +import org.usvm.memory.URegisterStackLValue +import org.usvm.model.UModelBase + +private val logger = KotlinLogging.logger {} + +/** + * A class, responsible for resolving a single [JcExecution] for a specific method from a symbolic state. + * + * Uses concrete execution + */ +// TODO usvm-sbft-refactoring: copied from `usvm/usvm-jvm/test`, extract this class back to USVM project +class JcTestExecutor( + val classpath: JcClasspath, + private val runner: UTestConcreteExecutor +) { + /** + * Resolves a [JcTest] from a [method] from a [state]. + */ + fun execute( + method: JcTypedMethod, + state: JcState, + stringConstants: Map, + classConstants: Map, + ): JcExecution? { + val model = state.models.first() + + val ctx = state.ctx + + val mocker = state.memory.mocker as JcMocker +// val staticMethodMocks = mocker.statics TODO global mocks????????????????????????? + val methodMocks = mocker.symbols + + val resolvedMethodMocks = methodMocks.entries.groupBy({ model.eval(it.key) }, { it.value }) + .mapValues { it.value.flatten() } + + val memoryScope = MemoryScope(ctx, model, model, stringConstants, classConstants, resolvedMethodMocks, method) + + val uTest = memoryScope.createUTest() + + val concreteResult = runCatching { + runBlocking { + UTestConcreteExecutionResult(runner.executeAsync(uTest)) + } + } + .onFailure { e -> logger.warn(e) { "Recoverable: runner.executeAsync(uTest) failed on ${method.method}" } } + .getOrNull() + + val symbolicResult by lazy { + when (val methodResult = state.methodResult) { + is JcMethodResult.JcException -> UTestSymbolicExceptionResult(methodResult.type) + is JcMethodResult.Success -> { + val resultScope = MemoryScope(ctx, model, state.memory, stringConstants, classConstants, resolvedMethodMocks, method) + val resultExpr = resultScope.resolveExpr(methodResult.value, method.returnType) + val resultInitializer = resultScope.decoderApi.initializerInstructions() + UTestSymbolicSuccessResult(resultInitializer, resultExpr) + } + JcMethodResult.NoCall -> null + } + } + + val testExecutionResult = concreteResult?.uTestExecutionResult + + // Drop crashed executions + if (testExecutionResult is UTestExecutionFailedResult) { + logger.warn { "JVM crash in concrete execution for method ${method.method}, dropping state" } + return null + } + + // sometimes symbolic result more preferable than concolic + val preferableResult = + if (testExecutionResult is UTestExecutionSuccessResult || testExecutionResult is UTestExecutionExceptionResult) { + concreteResult + } else { + symbolicResult + ?: concreteResult + ?: error("Can't create JcExecution, there's no symbolic nor concrete result for ${method.method}") + } + + val coverage = resolveCoverage(method, state) + + return JcExecution( + method = method, + uTest = uTest, + uTestExecutionResultWrappers = sequence { + yield(preferableResult) + if (preferableResult !== symbolicResult) + symbolicResult?.let { yield(it) } + }, + coverage = coverage + ) + } + + @Suppress("UNUSED_PARAMETER") + private fun resolveCoverage(method: JcTypedMethod, state: JcState): JcCoverage { + // TODO: extract coverage + return JcCoverage(emptyMap()) + } + + /** + * An actual class for resolving objects from [UExpr]s. + * + * @param model a model to which compose expressions. + * @param memory a read-only memory to read [ULValue]s from. + */ + private class MemoryScope( + ctx: JcContext, + model: UModelBase, + memory: UReadOnlyMemory, + stringConstants: Map, + classConstants: Map, + private val resolvedMethodMocks: Map>>, + method: JcTypedMethod, + ) : JcTestStateResolver(ctx, model, memory, stringConstants, classConstants, method) { + + override val decoderApi = JcTestExecutorDecoderApi(ctx) + + fun createUTest(): UTest { + val thisInstance = if (!method.isStatic) { + val ref = URegisterStackLValue(ctx.addressSort, idx = 0) + resolveLValue(ref, method.enclosingType) + } else { + UTestNullExpression(ctx.cp.objectType) + } + + val parameters = method.parameters.mapIndexed { idx, param -> + val registerIdx = method.method.localIdx(idx) + val ref = URegisterStackLValue(ctx.typeToSort(param.type), registerIdx) + resolveLValue(ref, param.type) + } + + val initStmts = decoderApi.initializerInstructions() + val callExpr = if (method.isStatic) { + UTestStaticMethodCall(method.method, parameters) + } else { + UTestMethodCall(thisInstance, method.method, parameters) + } + return UTest(initStmts, callExpr) + } + + override fun allocateClassInstance(type: JcClassType): UTestExpression = + UTestAllocateMemoryCall(type.jcClass) + + override fun allocateString(value: UTestExpression): UTestExpression { + val stringConstructor = ctx.stringType.constructors + .firstOrNull { it.parameters.size == 1 && it.parameters.single().type == value.type } + ?: error("Can't allocate string from value: $value") + return decoderApi.invokeMethod(stringConstructor.method, listOf(value)) + } + + override fun resolveObject(ref: UConcreteHeapRef, heapRef: UHeapRef, type: JcClassType): UTestExpression { + if (ref !in resolvedMethodMocks || type.jcClass.name != "java.util.Random") { + return super.resolveObject(ref, heapRef, type) + } + + // Hack: mock only Random + + val mocks = resolvedMethodMocks.getValue(ref) + + val fieldValues = mutableMapOf() + val methods = mutableMapOf>() + + val instance = UTestMockObject(type, fieldValues, methods) + saveResolvedRef(ref.address, instance) + + val mockedMethodValues = mutableMapOf>>() + mocks.filterIsInstance>().forEach { mockValue -> + // todo: filter out approximations-only methods + mockedMethodValues.getOrPut(mockValue.method) { mutableListOf() }.add(mockValue) + } + + mockedMethodValues.forEach { (method, values) -> + val mockedValueType = requireNotNull(ctx.cp.findTypeOrNull(method.returnType)) { + "No such type found: ${method.returnType}" + } + + methods[method] = values + .sortedBy { it.callIndex } + .map { resolveExpr(it, mockedValueType) } + } + + val fields = generateSequence(type.jcClass) { it.superClass } + .map { it.toType() } + .flatMap { it.declaredFields } + .filter { !it.isStatic } + .filterNot { it.field is JcEnrichedVirtualField } + + for (field in fields) { + val lvalue = UFieldLValue(ctx.typeToSort(field.fieldType), heapRef, field.field) + val fieldValue = resolveLValue(lvalue, field.fieldType) + + fieldValues[field.field] = fieldValue + } + + return instance + } + } +} diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcTestExecutorDecoderApi.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcTestExecutorDecoderApi.kt new file mode 100644 index 0000000000..9cf13d7687 --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcTestExecutorDecoderApi.kt @@ -0,0 +1,126 @@ +package org.utbot.usvm.jc + +import org.jacodb.api.JcClassOrInterface +import org.jacodb.api.JcField +import org.jacodb.api.JcMethod +import org.jacodb.api.JcType +import org.jacodb.api.ext.boolean +import org.jacodb.api.ext.byte +import org.jacodb.api.ext.char +import org.jacodb.api.ext.double +import org.jacodb.api.ext.float +import org.jacodb.api.ext.int +import org.jacodb.api.ext.long +import org.jacodb.api.ext.objectType +import org.jacodb.api.ext.short +import org.jacodb.api.ext.toType +import org.usvm.api.decoder.DecoderApi +import org.usvm.instrumentation.testcase.api.UTestArrayGetExpression +import org.usvm.instrumentation.testcase.api.UTestArrayLengthExpression +import org.usvm.instrumentation.testcase.api.UTestArraySetStatement +import org.usvm.instrumentation.testcase.api.UTestBooleanExpression +import org.usvm.instrumentation.testcase.api.UTestByteExpression +import org.usvm.instrumentation.testcase.api.UTestCastExpression +import org.usvm.instrumentation.testcase.api.UTestCharExpression +import org.usvm.instrumentation.testcase.api.UTestClassExpression +import org.usvm.instrumentation.testcase.api.UTestConstructorCall +import org.usvm.instrumentation.testcase.api.UTestCreateArrayExpression +import org.usvm.instrumentation.testcase.api.UTestDoubleExpression +import org.usvm.instrumentation.testcase.api.UTestExpression +import org.usvm.instrumentation.testcase.api.UTestFloatExpression +import org.usvm.instrumentation.testcase.api.UTestGetFieldExpression +import org.usvm.instrumentation.testcase.api.UTestGetStaticFieldExpression +import org.usvm.instrumentation.testcase.api.UTestInst +import org.usvm.instrumentation.testcase.api.UTestIntExpression +import org.usvm.instrumentation.testcase.api.UTestLongExpression +import org.usvm.instrumentation.testcase.api.UTestMethodCall +import org.usvm.instrumentation.testcase.api.UTestNullExpression +import org.usvm.instrumentation.testcase.api.UTestSetFieldStatement +import org.usvm.instrumentation.testcase.api.UTestSetStaticFieldStatement +import org.usvm.instrumentation.testcase.api.UTestShortExpression +import org.usvm.instrumentation.testcase.api.UTestStaticMethodCall +import org.usvm.instrumentation.testcase.api.UTestStringExpression +import org.usvm.instrumentation.util.stringType +import org.usvm.machine.JcContext + +// TODO usvm-sbft-refactoring: copied from `usvm/usvm-jvm/test`, extract this class back to USVM project +class JcTestExecutorDecoderApi( + private val ctx: JcContext +) : DecoderApi { + private val instructions = mutableListOf() + + fun initializerInstructions(): List = instructions + + override fun setField(field: JcField, instance: UTestExpression, value: UTestExpression) { + instructions += if (field.isStatic) { + UTestSetStaticFieldStatement(field, value) + } else { + UTestSetFieldStatement(instance, field, value) + } + } + + override fun getField(field: JcField, instance: UTestExpression): UTestExpression = + if (field.isStatic) { + UTestGetStaticFieldExpression(field) + } else { + UTestGetFieldExpression(instance, field) + } + + override fun invokeMethod(method: JcMethod, args: List): UTestExpression = + when { + method.isConstructor -> UTestConstructorCall(method, args) + method.isStatic -> UTestStaticMethodCall(method, args) + else -> UTestMethodCall(args.first(), method, args.drop(1)) + }.also { + instructions += it + } + + override fun createBoolConst(value: Boolean): UTestExpression = + UTestBooleanExpression(value, ctx.cp.boolean) + + override fun createByteConst(value: Byte): UTestExpression = + UTestByteExpression(value, ctx.cp.byte) + + override fun createShortConst(value: Short): UTestExpression = + UTestShortExpression(value, ctx.cp.short) + + override fun createIntConst(value: Int): UTestExpression = + UTestIntExpression(value, ctx.cp.int) + + override fun createLongConst(value: Long): UTestExpression = + UTestLongExpression(value, ctx.cp.long) + + override fun createFloatConst(value: Float): UTestExpression = + UTestFloatExpression(value, ctx.cp.float) + + override fun createDoubleConst(value: Double): UTestExpression = + UTestDoubleExpression(value, ctx.cp.double) + + override fun createCharConst(value: Char): UTestExpression = + UTestCharExpression(value, ctx.cp.char) + + override fun createStringConst(value: String): UTestExpression = + UTestStringExpression(value, ctx.cp.stringType()) + + override fun createClassConst(type: JcType): UTestExpression = + UTestClassExpression(type) + + override fun createNullConst(type: JcType): UTestExpression = + UTestNullExpression(type) + + override fun setArrayIndex(array: UTestExpression, index: UTestExpression, value: UTestExpression) { + instructions += UTestArraySetStatement(array, index, value) + } + + override fun getArrayIndex(array: UTestExpression, index: UTestExpression): UTestExpression = + UTestArrayGetExpression(array, index) + + override fun getArrayLength(array: UTestExpression): UTestExpression = + UTestArrayLengthExpression(array) + + override fun createArray(elementType: JcType, size: UTestExpression): UTestExpression = + UTestCreateArrayExpression(elementType, size) + + override fun castClass(type: JcClassOrInterface, obj: UTestExpression): UTestExpression = + UTestCastExpression(obj, type.toType()) +}